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

#ifndef GrBatchBuffer_DEFINED
#define GrBatchBuffer_DEFINED

#include "GrBufferAllocPool.h"
#include "batches/GrVertexBatch.h"

class GrResourceProvider;

/** Simple class that performs the upload on behalf of a GrBatchUploader. */
class GrBatchUploader::TextureUploader {
public:
    TextureUploader(GrGpu* gpu) : fGpu(gpu) { SkASSERT(gpu); }

    /**
        * Updates the pixels in a rectangle of a texture.
        *
        * @param left          left edge of the rectangle to write (inclusive)
        * @param top           top edge of the rectangle to write (inclusive)
        * @param width         width of rectangle to write in pixels.
        * @param height        height of rectangle to write in pixels.
        * @param config        the pixel config of the source buffer
        * @param buffer        memory to read pixels from
        * @param rowBytes      number of bytes between consecutive rows. Zero
        *                      means rows are tightly packed.
        */
    bool writeTexturePixels(GrTexture* texture,
                            int left, int top, int width, int height,
                            GrPixelConfig config, const void* buffer,
                            size_t rowBytes) {
        return fGpu->writePixels(texture, left, top, width, height, config, buffer, rowBytes);
    }

private:
    GrGpu* fGpu;
};

/** Tracks the state across all the GrBatches in a GrDrawTarget flush. */
class GrBatchFlushState {
public:
    GrBatchFlushState(GrGpu*, GrResourceProvider*);

    ~GrBatchFlushState() { this->reset(); }

    void advanceToken() { ++fCurrentToken; }

    void advanceLastFlushedToken() { ++fLastFlushedToken; }

    /** Inserts an upload to be executred after all batches in the flush prepared their draws
        but before the draws are executed to the backend 3D API. */
    void addASAPUpload(GrBatchUploader* upload) {
        fAsapUploads.push_back().reset(SkRef(upload));
    }

    const GrCaps& caps() const { return *fGpu->caps(); }
    GrResourceProvider* resourceProvider() const { return fResourceProvider; }

    /** Has the token been flushed to the backend 3D API. */
    bool hasTokenBeenFlushed(GrBatchToken token) const { return fLastFlushedToken >= token; }

    /** The current token advances once for every contiguous set of uninterrupted draws prepared
        by a batch. */
    GrBatchToken currentToken() const { return fCurrentToken; }

    /** The last token flushed to all the way to the backend API. */
    GrBatchToken lastFlushedToken() const { return fLastFlushedToken; }

    /** This is a magic token that can be used to indicate that an upload should occur before
        any draws for any batch in the current flush execute. */
    GrBatchToken asapToken() const { return fLastFlushedToken + 1; }

    void* makeVertexSpace(size_t vertexSize, int vertexCount,
                          const GrVertexBuffer** buffer, int* startVertex);
    uint16_t* makeIndexSpace(int indexCount, const GrIndexBuffer** buffer, int* startIndex);

    /** This is called after each batch has a chance to prepare its draws and before the draws
        are issued. */
    void preIssueDraws() {
        fVertexPool.unmap();
        fIndexPool.unmap();
        int uploadCount = fAsapUploads.count();
        for (int i = 0; i < uploadCount; i++) {
            fAsapUploads[i]->upload(&fUploader);
        }
        fAsapUploads.reset();
    }

    void putBackIndices(size_t indices) { fIndexPool.putBack(indices * sizeof(uint16_t)); }

    void putBackVertexSpace(size_t sizeInBytes) { fVertexPool.putBack(sizeInBytes); }

    GrBatchUploader::TextureUploader* uploader() { return &fUploader; }

    GrGpu* gpu() { return fGpu; }

    void reset() {
        fVertexPool.reset();
        fIndexPool.reset();
    }

private:
    GrGpu*                                          fGpu;
    GrBatchUploader::TextureUploader                fUploader;

    GrResourceProvider*                             fResourceProvider;

    GrVertexBufferAllocPool                         fVertexPool;
    GrIndexBufferAllocPool                          fIndexPool;

    SkTArray<SkAutoTUnref<GrBatchUploader>, true>   fAsapUploads;

    GrBatchToken                                    fCurrentToken;

    GrBatchToken                                    fLastFlushedToken;
};

/**
 * GrDrawBatch instances use this object to allocate space for their geometry and to issue the draws
 * that render their batch.
 */
class GrDrawBatch::Target {
public:
    Target(GrBatchFlushState* state, GrDrawBatch* batch) : fState(state), fBatch(batch) {}

    void upload(GrBatchUploader* upload) {
        if (this->asapToken() == upload->lastUploadToken()) {
            fState->addASAPUpload(upload);
        } else {
            fBatch->fInlineUploads.push_back().reset(SkRef(upload));
        }
    }

    bool hasTokenBeenFlushed(GrBatchToken token) const {
        return fState->hasTokenBeenFlushed(token);
    }
    GrBatchToken currentToken() const { return fState->currentToken(); }
    GrBatchToken asapToken() const { return fState->asapToken(); }

    const GrCaps& caps() const { return fState->caps(); }

    GrResourceProvider* resourceProvider() const { return fState->resourceProvider(); }

protected:
    GrDrawBatch* batch() { return fBatch; }
    GrBatchFlushState* state() { return fState; }

private:
    GrBatchFlushState*  fState;
    GrDrawBatch*        fBatch;
};

/** Extension of GrDrawBatch::Target for use by GrVertexBatch. Adds the ability to create vertex
    draws. */
class GrVertexBatch::Target : public GrDrawBatch::Target {
public:
    Target(GrBatchFlushState* state, GrVertexBatch* batch) : INHERITED(state, batch) {}

    void initDraw(const GrPrimitiveProcessor* primProc, const GrPipeline* pipeline) {
        GrVertexBatch::DrawArray* draws = this->vertexBatch()->fDrawArrays.addToTail();
        draws->fPrimitiveProcessor.reset(primProc);
        this->state()->advanceToken();
    }

    void draw(const GrVertices& vertices) {
        this->vertexBatch()->fDrawArrays.tail()->fDraws.push_back(vertices);
    }

    void* makeVertexSpace(size_t vertexSize, int vertexCount,
                          const GrVertexBuffer** buffer, int* startVertex) {
        return this->state()->makeVertexSpace(vertexSize, vertexCount, buffer, startVertex);
    }

    uint16_t* makeIndexSpace(int indexCount, const GrIndexBuffer** buffer, int* startIndex) {
        return this->state()->makeIndexSpace(indexCount, buffer, startIndex);
    }

    /** Helpers for batches which over-allocate and then return data to the pool. */
    void putBackIndices(int indices) { this->state()->putBackIndices(indices); }
    void putBackVertices(int vertices, size_t vertexStride) {
        this->state()->putBackVertexSpace(vertices * vertexStride);
    }

private:
    GrVertexBatch* vertexBatch() { return static_cast<GrVertexBatch*>(this->batch()); }
    typedef GrDrawBatch::Target INHERITED;
};

#endif