Reenable GrOp chaining.

GrRenderTargetOpList maintains an array of op chains. When it receives a
new op it tries to add it to an existing chain, working backwards from
the end of the current array. If the op can be added to a chain it
additionally tries to merge the new op with ops already in the chain
before adding it to the tail of the chain.

In forward combining it tries to concatenate chains. If chains can
concatenate it also attempts to merge ops between the two chains.

Now op chaining results reported by Op subclasses must be transitive.
Moreover, if op A is able to merge with B then it must be the case that
any op that can chain with A will either merge or chain with any op that
can chain to B.

Bug: skia:8491

Change-Id: Ib6a2a669acd4257134a37d271289b8b3f247cd3f
Reviewed-on: https://skia-review.googlesource.com/c/170351
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/src/gpu/GrRenderTargetOpList.h b/src/gpu/GrRenderTargetOpList.h
index 27922a8..6eb19ec 100644
--- a/src/gpu/GrRenderTargetOpList.h
+++ b/src/gpu/GrRenderTargetOpList.h
@@ -47,7 +47,7 @@
         INHERITED::makeClosed(caps);
     }
 
-    bool isEmpty() const { return fRecordedOps.empty(); }
+    bool isEmpty() const { return fOpChains.empty(); }
 
     /**
      * Empties the draw buffer of any queued up draws.
@@ -118,36 +118,77 @@
 
     void deleteOps();
 
-    struct RecordedOp {
-        RecordedOp(std::unique_ptr<GrOp> op, GrAppliedClip* appliedClip, const DstProxy* dstProxy)
-                : fOp(std::move(op)), fAppliedClip(appliedClip) {
-            if (dstProxy) {
-                fDstProxy = *dstProxy;
-            }
+    class OpChain {
+    public:
+        OpChain(const OpChain&) = delete;
+        OpChain& operator=(const OpChain&) = delete;
+        OpChain(std::unique_ptr<GrOp>, GrAppliedClip*, const DstProxy*);
+
+        ~OpChain() {
+            // The ops are stored in a GrMemoryPool and must be explicitly deleted via the pool.
+            SkASSERT(fList.empty());
         }
 
-        ~RecordedOp() {
-            // The ops are stored in a GrMemoryPool so had better have been handled separately
-            SkASSERT(!fOp);
-        }
+        void visitProxies(const GrOp::VisitProxyFunc&, GrOp::VisitorType) const;
 
-        void deleteOp(GrOpMemoryPool* opMemoryPool);
+        GrOp* head() const { return fList.head(); }
 
-        void visitProxies(const GrOp::VisitProxyFunc& func, GrOp::VisitorType visitor) const {
-            if (fOp) {
-                fOp->visitProxies(func, visitor);
-            }
-            if (fDstProxy.proxy()) {
-                func(fDstProxy.proxy());
-            }
-            if (fAppliedClip) {
-                fAppliedClip->visitProxies(func);
-            }
-        }
+        GrAppliedClip* appliedClip() const { return fAppliedClip; }
+        const DstProxy& dstProxy() const { return fDstProxy; }
+        const SkRect& bounds() const { return fBounds; }
 
-        std::unique_ptr<GrOp> fOp;
-        DstProxy              fDstProxy;
-        GrAppliedClip*        fAppliedClip;
+        // Deletes all the ops in the chain via the pool.
+        void deleteOps(GrOpMemoryPool* pool);
+
+        // Attempts to move the ops from the passed chain to this chain at the head. Also attempts
+        // to merge ops between the chains. Upon success the passed chain is empty.
+        // Fails when the chains aren't of the same op type, have different clips or dst proxies.
+        bool prependChain(OpChain*, const GrCaps&, GrOpMemoryPool*, GrAuditTrail*);
+
+        // Attempts to add 'op' to this chain either by merging or adding to the tail. Returns
+        // 'op' to the caller upon failure, otherwise null. Fails when the op and chain aren't of
+        // the same op type, have different clips or dst proxies.
+        std::unique_ptr<GrOp> appendOp(std::unique_ptr<GrOp> op, const DstProxy*,
+                                       const GrAppliedClip*, const GrCaps&, GrOpMemoryPool*,
+                                       GrAuditTrail*);
+
+    private:
+        class List {
+        public:
+            List() = default;
+            List(std::unique_ptr<GrOp>);
+            List(List&&);
+            List& operator=(List&& that);
+
+            bool empty() const { return !SkToBool(fHead); }
+            GrOp* head() const { return fHead.get(); }
+            GrOp* tail() const { return fTail; }
+
+            std::unique_ptr<GrOp> popHead();
+            std::unique_ptr<GrOp> removeOp(GrOp* op);
+            void pushHead(std::unique_ptr<GrOp> op);
+            void pushTail(std::unique_ptr<GrOp>);
+
+            void validate() const;
+
+        private:
+            std::unique_ptr<GrOp> fHead;
+            GrOp* fTail = nullptr;
+        };
+
+        void validate() const;
+
+        std::tuple<List, List> TryConcat(List chainA, const DstProxy& dstProxyA,
+                                         const GrAppliedClip* appliedClipA, List chainB,
+                                         const DstProxy& dstProxyB,
+                                         const GrAppliedClip* appliedClipB, const GrCaps&,
+                                         GrOpMemoryPool*, GrAuditTrail*);
+        List DoConcat(List, List, const GrCaps&, GrOpMemoryPool*, GrAuditTrail*);
+
+        List fList;
+        DstProxy fDstProxy;
+        GrAppliedClip* fAppliedClip;
+        SkRect fBounds;
     };
 
     void purgeOpsWithUninstantiatedProxies() override;
@@ -159,15 +200,12 @@
 
     void forwardCombine(const GrCaps&);
 
-    GrOp::CombineResult combineIfPossible(const RecordedOp& a, GrOp* b, const GrAppliedClip* bClip,
-                                          const DstProxy* bDstTexture, const GrCaps&);
-
     uint32_t                       fLastClipStackGenID;
     SkIRect                        fLastDevClipBounds;
     int                            fLastClipNumAnalyticFPs;
 
     // For ops/opList we have mean: 5 stdDev: 28
-    SkSTArray<25, RecordedOp, true> fRecordedOps;
+    SkSTArray<25, OpChain, true> fOpChains;
 
     // MDB TODO: 4096 for the first allocation of the clip space will be huge overkill.
     // Gather statistics to determine the correct size.