During flush store GrOp draw and upload records on GrOpFlushState instead of on the ops themselves.

Bug: skia:
Change-Id: Id99267d9e7762829a3f9bebce0e92e7b97a092f8
Reviewed-on: https://skia-review.googlesource.com/66680
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 9b15f3c..66cb6cc 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -193,7 +193,7 @@
     }
 
     // Upload all data to the GPU
-    fFlushState.preIssueDraws();
+    fFlushState.preExecuteDraws();
 
     // Execute the onFlush op lists first, if any.
     for (sk_sp<GrOpList>& onFlushOpList : onFlushOpLists) {
diff --git a/src/gpu/GrOpFlushState.cpp b/src/gpu/GrOpFlushState.cpp
index fa70a05..6ac5d13 100644
--- a/src/gpu/GrOpFlushState.cpp
+++ b/src/gpu/GrOpFlushState.cpp
@@ -12,13 +12,29 @@
 #include "GrResourceProvider.h"
 #include "GrTexture.h"
 
+template <typename T>
+template <typename... Args>
+T& GrOpFlushState::List<T>::append(SkArenaAlloc* arena, Args... args) {
+    SkASSERT(!fHead == !fTail);
+    auto* n = arena->make<Node>(std::forward<Args>(args)...);
+    if (!fTail) {
+        fHead = fTail = n;
+    } else {
+        fTail = fTail->fNext = n;
+    }
+    return fTail->fT;
+}
+
+template <typename T>
+typename GrOpFlushState::List<T>::Iter& GrOpFlushState::List<T>::Iter::operator++() {
+    fCurr = fCurr->fNext;
+    return *this;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
 GrOpFlushState::GrOpFlushState(GrGpu* gpu, GrResourceProvider* resourceProvider)
-        : fGpu(gpu)
-        , fResourceProvider(resourceProvider)
-        , fCommandBuffer(nullptr)
-        , fVertexPool(gpu)
-        , fIndexPool(gpu)
-        , fOpArgs(nullptr) {}
+        : fVertexPool(gpu), fIndexPool(gpu), fGpu(gpu), fResourceProvider(resourceProvider) {}
 
 const GrCaps& GrOpFlushState::caps() const {
     return *fGpu->caps();
@@ -28,6 +44,51 @@
     return fCommandBuffer->asRTCommandBuffer();
 }
 
+void GrOpFlushState::executeDrawsAndUploadsForMeshDrawOp(uint32_t opID, const SkRect& opBounds) {
+    SkASSERT(this->rtCommandBuffer());
+    while (fCurrDraw != fDraws.end() && fCurrDraw->fOpID == opID) {
+        GrDeferredUploadToken drawToken = this->nextTokenToFlush();
+        while (fCurrUpload != fInlineUploads.end() &&
+               fCurrUpload->fUploadBeforeToken == drawToken) {
+            this->rtCommandBuffer()->inlineUpload(this, fCurrUpload->fUpload);
+            ++fCurrUpload;
+        }
+        SkASSERT(fCurrDraw->fPipeline->proxy() == this->drawOpArgs().fProxy);
+        this->rtCommandBuffer()->draw(*fCurrDraw->fPipeline, *fCurrDraw->fGeometryProcessor,
+                                      fMeshes.begin() + fCurrMesh, nullptr, fCurrDraw->fMeshCnt,
+                                      opBounds);
+        fCurrMesh += fCurrDraw->fMeshCnt;
+        this->flushToken();
+        ++fCurrDraw;
+    }
+}
+
+void GrOpFlushState::preExecuteDraws() {
+    fVertexPool.unmap();
+    fIndexPool.unmap();
+    for (auto& upload : fAsapUploads) {
+        this->doUpload(upload);
+    }
+    // Setup execution iterators.
+    fCurrDraw = fDraws.begin();
+    fCurrUpload = fInlineUploads.begin();
+    fCurrMesh = 0;
+}
+
+void GrOpFlushState::reset() {
+    SkASSERT(fCurrDraw == fDraws.end());
+    SkASSERT(fCurrUpload == fInlineUploads.end());
+    fVertexPool.reset();
+    fIndexPool.reset();
+    fArena.reset();
+    fAsapUploads.reset();
+    fInlineUploads.reset();
+    fDraws.reset();
+    fMeshes.reset();
+    fCurrMesh = 0;
+    fBaseDrawToken = GrDeferredUploadToken::AlreadyFlushedToken();
+}
+
 void GrOpFlushState::doUpload(GrDeferredTextureUploadFn& upload) {
     GrDeferredTextureUploadWritePixelsFn wp = [this](GrTextureProxy* proxy, int left, int top,
                                                      int width, int height, GrPixelConfig config,
@@ -62,18 +123,12 @@
 }
 
 GrDeferredUploadToken GrOpFlushState::addInlineUpload(GrDeferredTextureUploadFn&& upload) {
-    SkASSERT(fOpArgs);
-    SkASSERT(fOpArgs->fOp);
-    // Here we're dangerously relying on only GrDrawOps calling this method. This gets fixed by
-    // storing inline uploads on GrOpFlushState and removing GrDrawOp::FlushStateAccess.
-    auto op = static_cast<GrDrawOp*>(fOpArgs->fOp);
-    auto token = this->nextDrawToken();
-    GrDrawOp::FlushStateAccess(op).addInlineUpload(std::move(upload), token);
-    return token;
+    return fInlineUploads.append(&fArena, std::move(upload), this->nextDrawToken())
+            .fUploadBeforeToken;
 }
 
 GrDeferredUploadToken GrOpFlushState::addASAPUpload(GrDeferredTextureUploadFn&& upload) {
-    fASAPUploads.emplace_back(std::move(upload));
+    fAsapUploads.append(&fArena, std::move(upload));
     return this->nextTokenToFlush();
 }
 
@@ -81,30 +136,29 @@
                           const GrMesh& mesh) {
     SkASSERT(fOpArgs);
     SkASSERT(fOpArgs->fOp);
-    // Here we're dangerously relying on only GrMeshDrawOps calling this method. This gets fixed by
-    // storing draw data on GrOpFlushState and removing GrMeshDrawOp::FlushStateAccess.
-    auto op = static_cast<GrMeshDrawOp*>(fOpArgs->fOp);
-    GrMeshDrawOp::FlushStateAccess fsa(op);
-
-    fsa.addMesh(mesh);
-    GrMeshDrawOp::FlushStateAccess::QueuedDraw* lastDraw = fsa.lastDraw();
-    if (lastDraw) {
+    fMeshes.push_back(mesh);
+    bool firstDraw = fDraws.begin() == fDraws.end();
+    if (!firstDraw) {
+        Draw& lastDraw = *fDraws.begin();
         // If the last draw shares a geometry processor and pipeline and there are no intervening
         // uploads, add this mesh to it.
-        if (lastDraw->fGeometryProcessor == gp && lastDraw->fPipeline == pipeline &&
-            (fsa.lastUploadToken() != this->nextDrawToken())) {
-            ++lastDraw->fMeshCnt;
-            return;
+        if (lastDraw.fGeometryProcessor == gp && lastDraw.fPipeline == pipeline) {
+            if (fInlineUploads.begin() == fInlineUploads.end() ||
+                fInlineUploads.tail()->fUploadBeforeToken != this->nextDrawToken()) {
+                ++lastDraw.fMeshCnt;
+                return;
+            }
         }
     }
-    GrMeshDrawOp::FlushStateAccess::QueuedDraw* draw = fsa.addDraw();
+    auto& draw = fDraws.append(&fArena);
     GrDeferredUploadToken token = this->issueDrawToken();
 
-    draw->fGeometryProcessor.reset(gp);
-    draw->fPipeline = pipeline;
-    draw->fMeshCnt = 1;
-    if (!lastDraw) {
-        fsa.setBaseDrawToken(token);
+    draw.fGeometryProcessor.reset(gp);
+    draw.fPipeline = pipeline;
+    draw.fMeshCnt = 1;
+    draw.fOpID = fOpArgs->fOp->uniqueID();
+    if (firstDraw) {
+        fBaseDrawToken = token;
     }
 }
 
diff --git a/src/gpu/GrOpFlushState.h b/src/gpu/GrOpFlushState.h
index 947fd14..b20098a 100644
--- a/src/gpu/GrOpFlushState.h
+++ b/src/gpu/GrOpFlushState.h
@@ -8,6 +8,7 @@
 #ifndef GrOpFlushState_DEFINED
 #define GrOpFlushState_DEFINED
 
+#include <utility>
 #include "GrAppliedClip.h"
 #include "GrBufferAllocPool.h"
 #include "GrDeferredUpload.h"
@@ -19,48 +20,6 @@
 class GrGpuRTCommandBuffer;
 class GrResourceProvider;
 
-// TODO: Store uploads on GrOpFlushState rather than GrDrawOp and remove this.
-class GrDrawOp::FlushStateAccess {
-private:
-    friend class GrOpFlushState;
-
-    explicit FlushStateAccess(GrDrawOp* op) : fOp(op) {}
-
-    void addInlineUpload(GrDeferredTextureUploadFn&& upload, GrDeferredUploadToken token) {
-        fOp->fInlineUploads.emplace_back(std::move(upload), token);
-    }
-
-    GrDrawOp* fOp;
-};
-
-// TODO: Store draw related data on GrOpFlushState rather than GrMeshDrawOp and remove this.
-class GrMeshDrawOp::FlushStateAccess {
-private:
-    friend class GrOpFlushState;
-    using QueuedDraw = GrMeshDrawOp::QueuedDraw;
-
-    explicit FlushStateAccess(GrMeshDrawOp* op) : fOp(op) {}
-
-    void addMesh(const GrMesh& mesh) { fOp->fMeshes.push_back(mesh); }
-
-    QueuedDraw* lastDraw() {
-        return fOp->fQueuedDraws.empty() ? nullptr : &fOp->fQueuedDraws.back();
-    }
-
-    QueuedDraw* addDraw() { return &fOp->fQueuedDraws.push_back(); }
-
-    GrDeferredUploadToken lastUploadToken() const {
-        if (fOp->fInlineUploads.empty()) {
-            return GrDeferredUploadToken::AlreadyFlushedToken();
-        }
-        return fOp->fInlineUploads.back().fUploadBeforeToken;
-    }
-
-    void setBaseDrawToken(GrDeferredUploadToken token) { fOp->fBaseDrawToken = token; }
-
-    GrMeshDrawOp* fOp;
-};
-
 /** Tracks the state across all the GrOps (really just the GrDrawOps) in a GrOpList flush. */
 class GrOpFlushState final : public GrDeferredUploadTarget, public GrMeshDrawOp::Target {
 public:
@@ -69,20 +28,14 @@
     ~GrOpFlushState() final { this->reset(); }
 
     /** This is called after each op 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++) {
-            this->doUpload(fASAPUploads[i]);
-        }
-        fASAPUploads.reset();
-    }
+        executed. */
+    void preExecuteDraws();
 
     void doUpload(GrDeferredTextureUploadFn&);
 
+    /** Called as ops are executed. Must be called in the same order as the ops were prepared. */
+    void executeDrawsAndUploadsForMeshDrawOp(uint32_t opID, const SkRect& opBounds);
+
     GrGpuCommandBuffer* commandBuffer() { return fCommandBuffer; }
     // Helper function used by Ops that are only called via RenderTargetOpLists
     GrGpuRTCommandBuffer* rtCommandBuffer();
@@ -90,11 +43,7 @@
 
     GrGpu* gpu() { return fGpu; }
 
-    void reset() {
-        fVertexPool.reset();
-        fIndexPool.reset();
-        fPipelines.reset();
-    }
+    void reset();
 
     /** Additional data required on a per-op basis when executing GrOps. */
     struct OpArgs {
@@ -115,14 +64,6 @@
         return *fOpArgs;
     }
 
-    /** Expose base class methods for incrementing the last flushed and next draw token. */
-
-    void flushToken() { this->GrDeferredUploadTarget::flushToken(); }
-
-    GrDeferredUploadToken issueDrawToken() {
-        return this->GrDeferredUploadTarget::issueDrawToken();
-    }
-
     /** Overrides of GrDeferredUploadTarget. */
 
     GrDeferredUploadToken addInlineUpload(GrDeferredTextureUploadFn&&) final;
@@ -149,16 +90,104 @@
 
 private:
     /** GrMeshDrawOp::Target override. */
-    SkArenaAlloc* pipelineArena() override { return &fPipelines; }
+    SkArenaAlloc* pipelineArena() override { return &fArena; }
+
+    struct InlineUpload {
+        InlineUpload(GrDeferredTextureUploadFn&& upload, GrDeferredUploadToken token)
+                : fUpload(std::move(upload)), fUploadBeforeToken(token) {}
+        GrDeferredTextureUploadFn fUpload;
+        GrDeferredUploadToken fUploadBeforeToken;
+    };
+
+    // A set of contiguous draws that share a draw token, geometry processor, and pipeline. The
+    // meshes for the draw are stored in the fMeshes array. The reason for coalescing meshes
+    // that share a geometry processor into a Draw is that it allows the Gpu object to setup
+    // the shared state once and then issue draws for each mesh.
+    struct Draw {
+        int fMeshCnt = 0;
+        GrPendingProgramElement<const GrGeometryProcessor> fGeometryProcessor;
+        const GrPipeline* fPipeline;
+        uint32_t fOpID;
+    };
+
+    /**
+     * A singly linked list of Ts stored in a SkArenaAlloc. The arena rather than the list owns
+     * the elements. This supports forward iteration and range based for loops.
+     */
+    template <typename T>
+    class List {
+    private:
+        struct Node;
+
+    public:
+        List() = default;
+
+        void reset() { fHead = fTail = nullptr; }
+
+        template <typename... Args>
+        T& append(SkArenaAlloc* arena, Args... args);
+
+        class Iter {
+        public:
+            Iter() = default;
+            Iter& operator++();
+            T& operator*() const { return fCurr->fT; }
+            T* operator->() const { return &fCurr->fT; }
+            bool operator==(const Iter& that) const { return fCurr == that.fCurr; }
+            bool operator!=(const Iter& that) const { return !(*this == that); }
+
+        private:
+            friend class List;
+            explicit Iter(Node* node) : fCurr(node) {}
+            Node* fCurr = nullptr;
+        };
+
+        Iter begin() { return Iter(fHead); }
+        Iter end() { return Iter(); }
+        Iter tail() { return Iter(fTail); }
+
+    private:
+        struct Node {
+            template <typename... Args>
+            Node(Args... args) : fT(std::forward<Args>(args)...) {}
+            T fT;
+            Node* fNext = nullptr;
+        };
+        Node* fHead = nullptr;
+        Node* fTail = nullptr;
+    };
+
+    // Storage for ops' pipelines, draws, and inline uploads.
+    SkArenaAlloc fArena{sizeof(GrPipeline) * 100};
+
+    // Store vertex and index data on behalf of ops that are flushed.
+    GrVertexBufferAllocPool fVertexPool;
+    GrIndexBufferAllocPool fIndexPool;
+
+    // Data stored on behalf of the ops being flushed.
+    List<GrDeferredTextureUploadFn> fAsapUploads;
+    List<InlineUpload> fInlineUploads;
+    List<Draw> fDraws;
+    // TODO: These should go in the arena. However, GrGpuCommandBuffer and other classes currently
+    // accept contiguous arrays of meshes.
+    SkSTArray<16, GrMesh> fMeshes;
+
+    // All draws we store have an implicit draw token. This is the draw token for the first draw
+    // in fDraws.
+    GrDeferredUploadToken fBaseDrawToken = GrDeferredUploadToken::AlreadyFlushedToken();
+
+    // Info about the op that is currently preparing or executing using the flush state or null if
+    // an op is not currently preparing of executing.
+    OpArgs* fOpArgs = nullptr;
 
     GrGpu* fGpu;
     GrResourceProvider* fResourceProvider;
-    GrGpuCommandBuffer* fCommandBuffer;
-    GrVertexBufferAllocPool fVertexPool;
-    GrIndexBufferAllocPool fIndexPool;
-    SkSTArray<4, GrDeferredTextureUploadFn> fASAPUploads;
-    OpArgs* fOpArgs;
-    SkArenaAlloc fPipelines{sizeof(GrPipeline) * 100};
+    GrGpuCommandBuffer* fCommandBuffer = nullptr;
+
+    // Variables that are used to track where we are in lists as ops are executed
+    List<Draw>::Iter fCurrDraw;
+    int fCurrMesh;
+    List<InlineUpload>::Iter fCurrUpload;
 };
 
 #endif
diff --git a/src/gpu/ops/GrDrawOp.h b/src/gpu/ops/GrDrawOp.h
index f4a7102..7871d9e 100644
--- a/src/gpu/ops/GrDrawOp.h
+++ b/src/gpu/ops/GrDrawOp.h
@@ -20,9 +20,6 @@
  */
 class GrDrawOp : public GrOp {
 public:
-    /** Provides GrOpFlushState with privileged access to GrDrawOp. */
-    class FlushStateAccess;
-
     GrDrawOp(uint32_t classID) : INHERITED(classID) {}
 
     /**
@@ -50,16 +47,6 @@
     virtual RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
                                         GrPixelConfigIsClamped) = 0;
 
-protected:
-    struct QueuedUpload {
-        QueuedUpload(GrDeferredTextureUploadFn&& upload, GrDeferredUploadToken token)
-                : fUpload(std::move(upload)), fUploadBeforeToken(token) {}
-        GrDeferredTextureUploadFn fUpload;
-        GrDeferredUploadToken fUploadBeforeToken;
-    };
-
-    SkTArray<QueuedUpload> fInlineUploads;
-
 private:
     typedef GrOp INHERITED;
 };
diff --git a/src/gpu/ops/GrMeshDrawOp.cpp b/src/gpu/ops/GrMeshDrawOp.cpp
index a00af15..0bc782c 100644
--- a/src/gpu/ops/GrMeshDrawOp.cpp
+++ b/src/gpu/ops/GrMeshDrawOp.cpp
@@ -10,8 +10,7 @@
 #include "GrOpFlushState.h"
 #include "GrResourceProvider.h"
 
-GrMeshDrawOp::GrMeshDrawOp(uint32_t classID)
-        : INHERITED(classID), fBaseDrawToken(GrDeferredUploadToken::AlreadyFlushedToken()) {}
+GrMeshDrawOp::GrMeshDrawOp(uint32_t classID) : INHERITED(classID) {}
 
 void GrMeshDrawOp::onPrepare(GrOpFlushState* state) { this->onPrepareDraws(state); }
 
@@ -57,28 +56,5 @@
 }
 
 void GrMeshDrawOp::onExecute(GrOpFlushState* state) {
-    int currUploadIdx = 0;
-    int currMeshIdx = 0;
-
-    SkASSERT(fQueuedDraws.empty() || fBaseDrawToken == state->nextTokenToFlush());
-    SkASSERT(state->rtCommandBuffer());
-
-    for (int currDrawIdx = 0; currDrawIdx < fQueuedDraws.count(); ++currDrawIdx) {
-        GrDeferredUploadToken drawToken = state->nextTokenToFlush();
-        while (currUploadIdx < fInlineUploads.count() &&
-               fInlineUploads[currUploadIdx].fUploadBeforeToken == drawToken) {
-            state->rtCommandBuffer()->inlineUpload(state, fInlineUploads[currUploadIdx++].fUpload);
-        }
-        const QueuedDraw& draw = fQueuedDraws[currDrawIdx];
-        SkASSERT(draw.fPipeline->proxy() == state->drawOpArgs().fProxy);
-        state->rtCommandBuffer()->draw(*draw.fPipeline, *draw.fGeometryProcessor.get(),
-                                       fMeshes.begin() + currMeshIdx, nullptr, draw.fMeshCnt,
-                                       this->bounds());
-        currMeshIdx += draw.fMeshCnt;
-        state->flushToken();
-    }
-    SkASSERT(currUploadIdx == fInlineUploads.count());
-    SkASSERT(currMeshIdx == fMeshes.count());
-    fQueuedDraws.reset();
-    fInlineUploads.reset();
+    state->executeDrawsAndUploadsForMeshDrawOp(this->uniqueID(), this->bounds());
 }
diff --git a/src/gpu/ops/GrMeshDrawOp.h b/src/gpu/ops/GrMeshDrawOp.h
index 232adb2..1b8831c 100644
--- a/src/gpu/ops/GrMeshDrawOp.h
+++ b/src/gpu/ops/GrMeshDrawOp.h
@@ -25,8 +25,6 @@
 public:
     /** Abstract interface that represents a destination for a GrMeshDrawOp. */
     class Target;
-    /** Provides GrOpFlushState with privileged access to GrMeshDrawOp. */
-    class FlushStateAccess;
 
 protected:
     GrMeshDrawOp(uint32_t classID);
@@ -69,27 +67,7 @@
 private:
     void onPrepare(GrOpFlushState* state) final;
     void onExecute(GrOpFlushState* state) final;
-
     virtual void onPrepareDraws(Target*) = 0;
-
-    // A set of contiguous draws that share a draw token and primitive processor. The draws all use
-    // the op's pipeline. The meshes for the draw are stored in the fMeshes array and each
-    // Queued draw uses fMeshCnt meshes from the fMeshes array. The reason for coallescing meshes
-    // that share a primitive processor into a QueuedDraw is that it allows the Gpu object to setup
-    // the shared state once and then issue draws for each mesh.
-    struct QueuedDraw {
-        int fMeshCnt = 0;
-        GrPendingProgramElement<const GrGeometryProcessor> fGeometryProcessor;
-        const GrPipeline* fPipeline;
-    };
-
-    // All draws in all the GrMeshDrawOps have implicit tokens based on the order they are enqueued
-    // globally across all ops. This is the offset of the first entry in fQueuedDraws.
-    // fQueuedDraws[i]'s token is fBaseDrawToken + i.
-    GrDeferredUploadToken fBaseDrawToken;
-    SkSTArray<4, GrMesh> fMeshes;
-    SkSTArray<4, QueuedDraw, true> fQueuedDraws;
-
     typedef GrDrawOp INHERITED;
 };
 
@@ -154,7 +132,7 @@
     /**
      * Helper that makes a pipeline targeting the op's render target that incorporates the op's
      * GrAppliedClip.
-     * */
+     */
     GrPipeline* makePipeline(uint32_t pipelineFlags, GrProcessorSet&& processorSet,
                              GrAppliedClip&& clip) {
         GrPipeline::InitArgs pipelineArgs;