/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef gr_instanced_InstancedRendering_DEFINED
#define gr_instanced_InstancedRendering_DEFINED

#include "../private/GrInstancedPipelineInfo.h"
#include "GrGpu.h"
#include "GrMemoryPool.h"
#include "SkTInternalLList.h"
#include "instanced/InstancedRenderingTypes.h"
#include "ops/GrDrawOp.h"

class GrResourceProvider;

namespace gr_instanced {

class InstanceProcessor;

/**
 * This class serves as a centralized clearinghouse for instanced rendering. It accumulates data for
 * instanced draws into one location, and creates special ops that pull from this data. The
 * nature of instanced rendering allows these ops to combine well and render efficiently.
 *
 * During a flush, this class assembles the accumulated draw data into a single vertex and texel
 * buffer, and its subclass draws the ops using backend-specific instanced rendering APIs.
 *
 * This class is responsible for the CPU side of instanced rendering. Shaders are implemented by
 * InstanceProcessor.
 */
class InstancedRendering : public SkNoncopyable {
public:
    virtual ~InstancedRendering() { SkASSERT(State::kRecordingDraws == fState); }

    GrGpu* gpu() const { return fGpu.get(); }

    /**
     * These methods make a new record internally for an instanced draw, and return an op that is
     * effectively just an index to that record. The returned op is not self-contained, but
     * rather relies on this class to handle the rendering. The client must call beginFlush() on
     * this class before attempting to flush ops returned by it. It is invalid to record new
     * draws between beginFlush() and endFlush().
     */
    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&,
                                                               GrPaint&&, GrAA,
                                                               const GrInstancedPipelineInfo&);

    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&,
                                                               GrPaint&&, const SkRect& localRect,
                                                               GrAA,
                                                               const GrInstancedPipelineInfo&);

    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordRect(const SkRect&, const SkMatrix&,
                                                               GrPaint&&,
                                                               const SkMatrix& localMatrix, GrAA,
                                                               const GrInstancedPipelineInfo&);

    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordOval(const SkRect&, const SkMatrix&,
                                                               GrPaint&&, GrAA,
                                                               const GrInstancedPipelineInfo&);

    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordRRect(const SkRRect&, const SkMatrix&,
                                                                GrPaint&&, GrAA,
                                                                const GrInstancedPipelineInfo&);

    std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT recordDRRect(const SkRRect& outer,
                                                                 const SkRRect& inner,
                                                                 const SkMatrix&, GrPaint&&, GrAA,
                                                                 const GrInstancedPipelineInfo&);

    /**
     * Compiles all recorded draws into GPU buffers and allows the client to begin flushing the
     * ops created by this class.
     */
    void beginFlush(GrResourceProvider*);

    /**
     * Called once the ops created previously by this class have all been released. Allows the
     * client to begin recording draws again.
     */
    void endFlush();

    enum class ResetType : bool {
        kDestroy,
        kAbandon
    };

    /**
     * Resets all GPU resources, including those that are held long term. They will be lazily
     * reinitialized if the class begins to be used again.
     */
    void resetGpuResources(ResetType);

protected:
    class Op : public GrDrawOp {
    public:
        SK_DECLARE_INTERNAL_LLIST_INTERFACE(Op);

        ~Op() override;
        const char* name() const override { return "InstancedRendering::Op"; }

        SkString dumpInfo() const override {
            SkString string;
            string.printf(
                    "AA: %d, ShapeTypes: 0x%02x, IShapeTypes: 0x%02x, Persp %d, "
                    "NonSquare: %d, PLoad: %0.2f, Tracked: %d, NumDraws: %d, "
                    "GeomChanges: %d\n",
                    (unsigned)fInfo.fAAType,
                    fInfo.fShapeTypes,
                    fInfo.fInnerShapeTypes,
                    fInfo.fHasPerspective,
                    fInfo.fNonSquare,
                    fPixelLoad,
                    fIsTracked,
                    fNumDraws,
                    fNumChangesInGeometry);
            string.append(INHERITED::dumpInfo());
            return string;
        }

        struct Draw {
            Instance     fInstance;
            IndexRange   fGeometry;
            Draw*        fNext;
        };

        Draw& getSingleDraw() const { SkASSERT(fHeadDraw && !fHeadDraw->fNext); return *fHeadDraw; }
        Instance& getSingleInstance() const { return this->getSingleDraw().fInstance; }

        void appendRRectParams(const SkRRect&);
        void appendParamsTexel(const SkScalar* vals, int count);
        void appendParamsTexel(SkScalar x, SkScalar y, SkScalar z, SkScalar w);
        void appendParamsTexel(SkScalar x, SkScalar y, SkScalar z);
        FixedFunctionFlags fixedFunctionFlags() const override {
            return GrAATypeIsHW(fInfo.aaType()) ? FixedFunctionFlags::kUsesHWAA
                                                : FixedFunctionFlags::kNone;
        }
        bool xpRequiresDstTexture(const GrCaps&, const GrAppliedClip*) override;

        // Registers the op with the InstancedRendering list of tracked ops.
        void wasRecorded() override;

    protected:
        Op(uint32_t classID, GrPaint&&, InstancedRendering*);

        InstancedRendering* const fInstancedRendering;
        OpInfo fInfo;
        SkScalar fPixelLoad;
        GrProcessorSet fProcessors;
        SkSTArray<5, ParamsTexel, true> fParams;
        bool fIsTracked : 1;
        bool fDrawColorsAreOpaque : 1;
        bool fDrawColorsAreSame : 1;
        int fNumDraws;
        int fNumChangesInGeometry;
        Draw* fHeadDraw;
        Draw* fTailDraw;

    private:
        bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override;
        void onPrepare(GrOpFlushState*) override {}
        void onExecute(GrOpFlushState*) override;

        typedef GrDrawOp INHERITED;

        friend class InstancedRendering;
    };

    typedef SkTInternalLList<Op> OpList;

    InstancedRendering(GrGpu* gpu);

    const OpList& trackedOps() const { return fTrackedOps; }
    const GrBuffer* vertexBuffer() const { SkASSERT(fVertexBuffer); return fVertexBuffer.get(); }
    const GrBuffer* indexBuffer() const { SkASSERT(fIndexBuffer); return fIndexBuffer.get(); }

    virtual void onBeginFlush(GrResourceProvider*) = 0;
    virtual void onDraw(const GrPipeline&, const InstanceProcessor&, const Op*) = 0;
    virtual void onEndFlush() = 0;
    virtual void onResetGpuResources(ResetType) = 0;

private:
    enum class State : bool {
        kRecordingDraws,
        kFlushing
    };

    std::unique_ptr<Op> SK_WARN_UNUSED_RESULT recordShape(ShapeType, const SkRect& bounds,
                                                          const SkMatrix& viewMatrix, GrPaint&&,
                                                          const SkRect& localRect, GrAA aa,
                                                          const GrInstancedPipelineInfo&);

    bool selectAntialiasMode(const SkMatrix& viewMatrix, GrAA aa, const GrInstancedPipelineInfo&,
                             GrAAType*);

    virtual std::unique_ptr<Op> makeOp(GrPaint&&) = 0;

    const sk_sp<GrGpu> fGpu;
    State fState;
    GrObjectMemoryPool<Op::Draw> fDrawPool;
    SkSTArray<1024, ParamsTexel, true> fParams;
    OpList fTrackedOps;
    sk_sp<const GrBuffer> fVertexBuffer;
    sk_sp<const GrBuffer> fIndexBuffer;
    sk_sp<GrBuffer> fParamsBuffer;
};

}

#endif