ccpr: Tessellate fans for very large and/or simple paths

This increases CPU work, but reduces overdraw on the GPU as compared to
Redbook fanning.

TBR=bsalomon@google.com

Change-Id: I396b887075d4422531908c2361ee1e26f076d5c3
Reviewed-on: https://skia-review.googlesource.com/107141
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCPathParser.cpp b/src/gpu/ccpr/GrCCPathParser.cpp
index 2a632d3..b19ffdf 100644
--- a/src/gpu/ccpr/GrCCPathParser.cpp
+++ b/src/gpu/ccpr/GrCCPathParser.cpp
@@ -16,9 +16,10 @@
 #include "SkPathPriv.h"
 #include "SkPoint.h"
 #include "ccpr/GrCCGeometry.h"
+#include <stdlib.h>
 
-using TriangleInstance = GrCCCoverageProcessor::TriangleInstance;
-using CubicInstance = GrCCCoverageProcessor::CubicInstance;
+using TriPointInstance = GrCCCoverageProcessor::TriPointInstance;
+using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance;
 
 GrCCPathParser::GrCCPathParser(int maxTotalPaths, int maxPathPoints, int numSkPoints,
                                int numSkVerbs)
@@ -32,7 +33,8 @@
     // that "end" at the beginning of the data. These will not be drawn, but will only be be read by
     // the first actual batch.
     fScissorSubBatches.push_back() = {PrimitiveTallies(), SkIRect::MakeEmpty()};
-    fCoverageCountBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count()};
+    fCoverageCountBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count(),
+                                         PrimitiveTallies()};
 }
 
 void GrCCPathParser::parsePath(const SkMatrix& m, const SkPath& path, SkRect* devBounds,
@@ -105,6 +107,7 @@
     fCurrPathPointsIdx = fGeometry.points().count();
     fCurrPathVerbsIdx = fGeometry.verbs().count();
     fCurrPathPrimitiveCounts = PrimitiveTallies();
+    fCurrPathFillType = path.getFillType();
 
     fGeometry.beginPath();
 
@@ -160,7 +163,81 @@
                                     int16_t atlasOffsetX, int16_t atlasOffsetY) {
     SkASSERT(fParsingPath);
 
-    fPathsInfo.push_back() = {scissorMode, atlasOffsetX, atlasOffsetY};
+    fPathsInfo.emplace_back(scissorMode, atlasOffsetX, atlasOffsetY);
+
+    // Tessellate fans from very large and/or simple paths, in order to reduce overdraw.
+    int numVerbs = fGeometry.verbs().count() - fCurrPathVerbsIdx - 1;
+    int64_t tessellationWork = (int64_t)numVerbs * (32 - SkCLZ(numVerbs)); // N log N.
+    int64_t fanningWork = (int64_t)clippedDevIBounds.height() * clippedDevIBounds.width();
+    if (tessellationWork * (50*50) + (100*100) < fanningWork) { // Don't tessellate under 100x100.
+        fCurrPathPrimitiveCounts.fTriangles =
+                fCurrPathPrimitiveCounts.fWoundTriangles = 0;
+
+        const SkTArray<GrCCGeometry::Verb, true>& verbs = fGeometry.verbs();
+        const SkTArray<SkPoint, true>& pts = fGeometry.points();
+        int ptsIdx = fCurrPathPointsIdx;
+
+        // Build an SkPath of the Redbook fan.
+        SkPath fan;
+        fan.setFillType(fCurrPathFillType);
+        SkASSERT(GrCCGeometry::Verb::kBeginPath == verbs[fCurrPathVerbsIdx]);
+        for (int i = fCurrPathVerbsIdx + 1; i < fGeometry.verbs().count(); ++i) {
+            switch (verbs[i]) {
+                case GrCCGeometry::Verb::kBeginPath:
+                    SK_ABORT("Invalid GrCCGeometry");
+                    continue;
+
+                case GrCCGeometry::Verb::kBeginContour:
+                    fan.moveTo(pts[ptsIdx++]);
+                    continue;
+
+                case GrCCGeometry::Verb::kLineTo:
+                    fan.lineTo(pts[ptsIdx++]);
+                    continue;
+
+                case GrCCGeometry::Verb::kMonotonicQuadraticTo:
+                    fan.lineTo(pts[ptsIdx + 1]);
+                    ptsIdx += 2;
+                    continue;
+
+                case GrCCGeometry::Verb::kMonotonicCubicTo:
+                    fan.lineTo(pts[ptsIdx + 2]);
+                    ptsIdx += 3;
+                    continue;
+
+                case GrCCGeometry::Verb::kEndClosedContour:
+                case GrCCGeometry::Verb::kEndOpenContour:
+                    fan.close();
+                    continue;
+            }
+        }
+        GrTessellator::WindingVertex* vertices = nullptr;
+        int count = GrTessellator::PathToVertices(fan, std::numeric_limits<float>::infinity(),
+                                                  SkRect::Make(clippedDevIBounds), &vertices);
+        SkASSERT(0 == count % 3);
+        for (int i = 0; i < count; i += 3) {
+            SkASSERT(vertices[i].fWinding == vertices[i + 1].fWinding);
+            SkASSERT(vertices[i].fWinding == vertices[i + 2].fWinding);
+            if (1 == abs(vertices[i].fWinding)) {
+                // Ensure this triangle's points actually wind in the same direction as fWinding.
+                float ax = vertices[i].fPos.fX - vertices[i + 1].fPos.fX;
+                float ay = vertices[i].fPos.fY - vertices[i + 1].fPos.fY;
+                float bx = vertices[i].fPos.fX - vertices[i + 2].fPos.fX;
+                float by = vertices[i].fPos.fY - vertices[i + 2].fPos.fY;
+                float wind = ay*bx - ax*by;
+                if ((wind > 0) != (vertices[i].fWinding > 0)) {
+                    std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos);
+                }
+                ++fCurrPathPrimitiveCounts.fTriangles;
+            } else {
+                ++fCurrPathPrimitiveCounts.fWoundTriangles;
+            }
+        }
+
+        fPathsInfo.back().fFanTessellation.reset(vertices);
+        fPathsInfo.back().fFanTessellationCount = count;
+    }
+
     fTotalPrimitiveCounts[(int)scissorMode] += fCurrPathPrimitiveCounts;
 
     if (ScissorMode::kScissored == scissorMode) {
@@ -180,15 +257,23 @@
 GrCCPathParser::CoverageCountBatchID GrCCPathParser::closeCurrentBatch() {
     SkASSERT(!fInstanceBuffer);
     SkASSERT(!fCoverageCountBatches.empty());
+    const auto& lastBatch = fCoverageCountBatches.back();
+    const auto& lastScissorSubBatch = fScissorSubBatches[lastBatch.fEndScissorSubBatchIdx - 1];
 
-    int maxMeshes = 1 + fScissorSubBatches.count() -
-                        fCoverageCountBatches.back().fEndScissorSubBatchIdx;
-    fMaxMeshesPerDraw = SkTMax(fMaxMeshesPerDraw, maxMeshes);
+    PrimitiveTallies batchTotalCounts = fTotalPrimitiveCounts[(int)ScissorMode::kNonScissored] -
+                                        lastBatch.fEndNonScissorIndices;
+    batchTotalCounts += fTotalPrimitiveCounts[(int)ScissorMode::kScissored] -
+                        lastScissorSubBatch.fEndPrimitiveIndices;
 
     fCoverageCountBatches.push_back() = {
         fTotalPrimitiveCounts[(int)ScissorMode::kNonScissored],
-        fScissorSubBatches.count()
+        fScissorSubBatches.count(),
+        batchTotalCounts
     };
+
+    int maxMeshes = 1 + fScissorSubBatches.count() - lastBatch.fEndScissorSubBatchIdx;
+    fMaxMeshesPerDraw = SkTMax(fMaxMeshesPerDraw, maxMeshes);
+
     return fCoverageCountBatches.count() - 1;
 }
 
@@ -205,10 +290,10 @@
 // elements past the end for this method to use as scratch space.
 //
 // Returns the next triangle instance after the final one emitted.
-static TriangleInstance* emit_recursive_fan(const SkTArray<SkPoint, true>& pts,
+static TriPointInstance* emit_recursive_fan(const SkTArray<SkPoint, true>& pts,
                                             SkTArray<int32_t, true>& indices, int firstIndex,
                                             int indexCount, const Sk2f& atlasOffset,
-                                            TriangleInstance out[]) {
+                                            TriPointInstance out[]) {
     if (indexCount < 3) {
         return out;
     }
@@ -232,6 +317,22 @@
     return out;
 }
 
+static void emit_tessellated_fan(const GrTessellator::WindingVertex* vertices, int numVertices,
+                                 const Sk2f& atlasOffset, TriPointInstance* triPointInstanceData,
+                                 QuadPointInstance* quadPointInstanceData,
+                                 GrCCGeometry::PrimitiveTallies* indices) {
+    for (int i = 0; i < numVertices; i += 3) {
+        if (1 == abs(vertices[i].fWinding)) {
+            triPointInstanceData[indices->fTriangles++].set(vertices[i].fPos, vertices[i + 1].fPos,
+                                                            vertices[i + 2].fPos, atlasOffset);
+        } else {
+            quadPointInstanceData[indices->fWoundTriangles++].set(
+                    vertices[i].fPos, vertices[i+1].fPos, vertices[i + 2].fPos, atlasOffset,
+                    static_cast<float>(vertices[i].fWinding));
+        }
+    }
+}
+
 bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) {
     SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath().
     SkASSERT(fCoverageCountBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch().
@@ -250,7 +351,7 @@
     //
     // We already know how big to make each of the 6 arrays from fTotalPrimitiveCounts, so layout is
     // straightforward. Start with triangles and quadratics. They both view the instance buffer as
-    // an array of TriangleInstance[], so we can begin at zero and lay them out one after the other.
+    // an array of TriPointInstance[], so we can begin at zero and lay them out one after the other.
     fBaseInstances[0].fTriangles = 0;
     fBaseInstances[1].fTriangles = fBaseInstances[0].fTriangles +
                                    fTotalPrimitiveCounts[0].fTriangles;
@@ -260,85 +361,112 @@
                                     fTotalPrimitiveCounts[0].fQuadratics;
     int triEndIdx = fBaseInstances[1].fQuadratics + fTotalPrimitiveCounts[1].fQuadratics;
 
-    // Cubics view the same instance buffer as an array of CubicInstance[]. So, reinterpreting the
-    // instance data as CubicInstance[], we start them on the first index that will not overwrite
-    // previous TriangleInstance data.
-    int cubicBaseIdx =
-            GR_CT_DIV_ROUND_UP(triEndIdx * sizeof(TriangleInstance), sizeof(CubicInstance));
-    fBaseInstances[0].fCubics = cubicBaseIdx;
+    // Wound triangles and cubics both view the same instance buffer as an array of
+    // QuadPointInstance[]. So, reinterpreting the instance data as QuadPointInstance[], we start
+    // them on the first index that will not overwrite previous TriPointInstance data.
+    int quadBaseIdx =
+            GR_CT_DIV_ROUND_UP(triEndIdx * sizeof(TriPointInstance), sizeof(QuadPointInstance));
+    fBaseInstances[0].fWoundTriangles = quadBaseIdx;
+    fBaseInstances[1].fWoundTriangles = fBaseInstances[0].fWoundTriangles +
+                                        fTotalPrimitiveCounts[0].fWoundTriangles;
+    fBaseInstances[0].fCubics = fBaseInstances[1].fWoundTriangles +
+                                fTotalPrimitiveCounts[1].fWoundTriangles;
     fBaseInstances[1].fCubics = fBaseInstances[0].fCubics + fTotalPrimitiveCounts[0].fCubics;
-    int cubicEndIdx = fBaseInstances[1].fCubics + fTotalPrimitiveCounts[1].fCubics;
+    int quadEndIdx = fBaseInstances[1].fCubics + fTotalPrimitiveCounts[1].fCubics;
 
     fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType,
-                                            cubicEndIdx * sizeof(CubicInstance));
+                                            quadEndIdx * sizeof(QuadPointInstance));
     if (!fInstanceBuffer) {
         return false;
     }
 
-    TriangleInstance* triangleInstanceData = static_cast<TriangleInstance*>(fInstanceBuffer->map());
-    CubicInstance* cubicInstanceData = reinterpret_cast<CubicInstance*>(triangleInstanceData);
-    SkASSERT(cubicInstanceData);
+    TriPointInstance* triPointInstanceData = static_cast<TriPointInstance*>(fInstanceBuffer->map());
+    QuadPointInstance* quadPointInstanceData =
+            reinterpret_cast<QuadPointInstance*>(triPointInstanceData);
+    SkASSERT(quadPointInstanceData);
 
-    PathInfo* currPathInfo = fPathsInfo.begin();
+    PathInfo* nextPathInfo = fPathsInfo.begin();
     float atlasOffsetX = 0.0, atlasOffsetY = 0.0;
     Sk2f atlasOffset;
-    int ptsIdx = -1;
     PrimitiveTallies instanceIndices[2] = {fBaseInstances[0], fBaseInstances[1]};
     PrimitiveTallies* currIndices = nullptr;
     SkSTArray<256, int32_t, true> currFan;
+    bool currFanIsTessellated = false;
 
     const SkTArray<SkPoint, true>& pts = fGeometry.points();
+    int ptsIdx = -1;
 
     // Expand the ccpr verbs into GPU instance buffers.
     for (GrCCGeometry::Verb verb : fGeometry.verbs()) {
         switch (verb) {
             case GrCCGeometry::Verb::kBeginPath:
                 SkASSERT(currFan.empty());
-                currIndices = &instanceIndices[(int)currPathInfo->fScissorMode];
-                atlasOffsetX = static_cast<float>(currPathInfo->fAtlasOffsetX);
-                atlasOffsetY = static_cast<float>(currPathInfo->fAtlasOffsetY);
+                currIndices = &instanceIndices[(int)nextPathInfo->fScissorMode];
+                atlasOffsetX = static_cast<float>(nextPathInfo->fAtlasOffsetX);
+                atlasOffsetY = static_cast<float>(nextPathInfo->fAtlasOffsetY);
                 atlasOffset = {atlasOffsetX, atlasOffsetY};
-                ++currPathInfo;
+                currFanIsTessellated = nextPathInfo->fFanTessellation.get();
+                if (currFanIsTessellated) {
+                    emit_tessellated_fan(nextPathInfo->fFanTessellation.get(),
+                                         nextPathInfo->fFanTessellationCount, atlasOffset,
+                                         triPointInstanceData, quadPointInstanceData, currIndices);
+                }
+                ++nextPathInfo;
                 continue;
 
             case GrCCGeometry::Verb::kBeginContour:
                 SkASSERT(currFan.empty());
-                currFan.push_back(++ptsIdx);
+                ++ptsIdx;
+                if (!currFanIsTessellated) {
+                    currFan.push_back(ptsIdx);
+                }
                 continue;
 
             case GrCCGeometry::Verb::kLineTo:
-                SkASSERT(!currFan.empty());
-                currFan.push_back(++ptsIdx);
+                ++ptsIdx;
+                if (!currFanIsTessellated) {
+                    SkASSERT(!currFan.empty());
+                    currFan.push_back(ptsIdx);
+                }
                 continue;
 
             case GrCCGeometry::Verb::kMonotonicQuadraticTo:
-                SkASSERT(!currFan.empty());
-                triangleInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx], atlasOffset);
-                currFan.push_back(ptsIdx += 2);
+                triPointInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx], atlasOffset);
+                ptsIdx += 2;
+                if (!currFanIsTessellated) {
+                    SkASSERT(!currFan.empty());
+                    currFan.push_back(ptsIdx);
+                }
                 continue;
 
             case GrCCGeometry::Verb::kMonotonicCubicTo:
-                SkASSERT(!currFan.empty());
-                cubicInstanceData[currIndices->fCubics++].set(&pts[ptsIdx], atlasOffsetX,
-                                                              atlasOffsetY);
-                currFan.push_back(ptsIdx += 3);
+                quadPointInstanceData[currIndices->fCubics++].set(&pts[ptsIdx], atlasOffsetX,
+                                                                  atlasOffsetY);
+                ptsIdx += 3;
+                if (!currFanIsTessellated) {
+                    SkASSERT(!currFan.empty());
+                    currFan.push_back(ptsIdx);
+                }
                 continue;
 
             case GrCCGeometry::Verb::kEndClosedContour:  // endPt == startPt.
-                SkASSERT(!currFan.empty());
-                currFan.pop_back();
+                if (!currFanIsTessellated) {
+                    SkASSERT(!currFan.empty());
+                    currFan.pop_back();
+                }
             // fallthru.
             case GrCCGeometry::Verb::kEndOpenContour:  // endPt != startPt.
-                if (currFan.count() >= 3) {
+                SkASSERT(!currFanIsTessellated || currFan.empty());
+                if (!currFanIsTessellated && currFan.count() >= 3) {
                     int fanSize = currFan.count();
                     // Reserve space for emit_recursive_fan. Technically this can grow to
                     // fanSize + log3(fanSize), but we approximate with log2.
                     currFan.push_back_n(SkNextLog2(fanSize));
-                    SkDEBUGCODE(TriangleInstance* end =)
+                    SkDEBUGCODE(TriPointInstance* end =)
                             emit_recursive_fan(pts, currFan, 0, fanSize, atlasOffset,
-                                               triangleInstanceData + currIndices->fTriangles);
+                                               triPointInstanceData + currIndices->fTriangles);
                     currIndices->fTriangles += fanSize - 2;
-                    SkASSERT(triangleInstanceData + currIndices->fTriangles == end);
+                    SkASSERT(triPointInstanceData + currIndices->fTriangles == end);
                 }
                 currFan.reset();
                 continue;
@@ -347,14 +475,16 @@
 
     fInstanceBuffer->unmap();
 
-    SkASSERT(currPathInfo == fPathsInfo.end());
+    SkASSERT(nextPathInfo == fPathsInfo.end());
     SkASSERT(ptsIdx == pts.count() - 1);
     SkASSERT(instanceIndices[0].fTriangles == fBaseInstances[1].fTriangles);
     SkASSERT(instanceIndices[1].fTriangles == fBaseInstances[0].fQuadratics);
     SkASSERT(instanceIndices[0].fQuadratics == fBaseInstances[1].fQuadratics);
     SkASSERT(instanceIndices[1].fQuadratics == triEndIdx);
+    SkASSERT(instanceIndices[0].fWoundTriangles == fBaseInstances[1].fWoundTriangles);
+    SkASSERT(instanceIndices[1].fWoundTriangles == fBaseInstances[0].fCubics);
     SkASSERT(instanceIndices[0].fCubics == fBaseInstances[1].fCubics);
-    SkASSERT(instanceIndices[1].fCubics == cubicEndIdx);
+    SkASSERT(instanceIndices[1].fCubics == quadEndIdx);
 
     fMeshesScratchBuffer.reserve(fMaxMeshesPerDraw);
     fDynamicStatesScratchBuffer.reserve(fMaxMeshesPerDraw);
@@ -365,36 +495,56 @@
 void GrCCPathParser::drawCoverageCount(GrOpFlushState* flushState, CoverageCountBatchID batchID,
                                        const SkIRect& drawBounds) const {
     using RenderPass = GrCCCoverageProcessor::RenderPass;
+    using WindMethod = GrCCCoverageProcessor::WindMethod;
 
     SkASSERT(fInstanceBuffer);
 
+    const PrimitiveTallies& batchTotalCounts = fCoverageCountBatches[batchID].fTotalPrimitiveCounts;
+
     GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrPipeline::ScissorState::kEnabled,
                         SkBlendMode::kPlus);
 
-    // Triangles.
-    this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleHulls,
-                         &PrimitiveTallies::fTriangles, drawBounds);
-    this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleEdges,
-                         &PrimitiveTallies::fTriangles, drawBounds);  // Might get skipped.
-    this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
-                         &PrimitiveTallies::fTriangles, drawBounds);
+    if (batchTotalCounts.fTriangles) {
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleHulls,
+                             WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles, drawBounds);
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleEdges,
+                             WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles,
+                             drawBounds); // Might get skipped.
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
+                             WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles, drawBounds);
+    }
 
-    // Quadratics.
-    this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kQuadraticHulls,
-                         &PrimitiveTallies::fQuadratics, drawBounds);
-    this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kQuadraticCorners,
-                         &PrimitiveTallies::fQuadratics, drawBounds);
+    if (batchTotalCounts.fWoundTriangles) {
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleHulls,
+                             WindMethod::kInstanceData, &PrimitiveTallies::fWoundTriangles,
+                             drawBounds);
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleEdges,
+                             WindMethod::kInstanceData, &PrimitiveTallies::fWoundTriangles,
+                             drawBounds); // Might get skipped.
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
+                             WindMethod::kInstanceData, &PrimitiveTallies::fWoundTriangles,
+                             drawBounds);
+    }
 
-    // Cubics.
-    this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kCubicHulls,
-                         &PrimitiveTallies::fCubics, drawBounds);
-    this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kCubicCorners,
-                         &PrimitiveTallies::fCubics, drawBounds);
+    if (batchTotalCounts.fQuadratics) {
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kQuadraticHulls,
+                             WindMethod::kCrossProduct, &PrimitiveTallies::fQuadratics, drawBounds);
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kQuadraticCorners,
+                             WindMethod::kCrossProduct, &PrimitiveTallies::fQuadratics, drawBounds);
+    }
+
+    if (batchTotalCounts.fCubics) {
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kCubicHulls,
+                             WindMethod::kCrossProduct, &PrimitiveTallies::fCubics, drawBounds);
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kCubicCorners,
+                             WindMethod::kCrossProduct, &PrimitiveTallies::fCubics, drawBounds);
+    }
 }
 
 void GrCCPathParser::drawRenderPass(GrOpFlushState* flushState, const GrPipeline& pipeline,
                                     CoverageCountBatchID batchID,
                                     GrCCCoverageProcessor::RenderPass renderPass,
+                                    GrCCCoverageProcessor::WindMethod windMethod,
                                     int PrimitiveTallies::*instanceType,
                                     const SkIRect& drawBounds) const {
     SkASSERT(pipeline.getScissorState().enabled());
@@ -407,12 +557,13 @@
     fMeshesScratchBuffer.pop_back_n(fMeshesScratchBuffer.count());
     fDynamicStatesScratchBuffer.pop_back_n(fDynamicStatesScratchBuffer.count());
 
-    GrCCCoverageProcessor proc(flushState->resourceProvider(), renderPass, flushState->caps());
+    GrCCCoverageProcessor proc(flushState->resourceProvider(), renderPass, windMethod);
 
     SkASSERT(batchID > 0);
     SkASSERT(batchID < fCoverageCountBatches.count());
     const CoverageCountBatch& previousBatch = fCoverageCountBatches[batchID - 1];
     const CoverageCountBatch& batch = fCoverageCountBatches[batchID];
+    SkDEBUGCODE(int totalInstanceCount = 0);
 
     if (int instanceCount = batch.fEndNonScissorIndices.*instanceType -
                             previousBatch.fEndNonScissorIndices.*instanceType) {
@@ -422,6 +573,7 @@
         proc.appendMesh(fInstanceBuffer.get(), instanceCount, baseInstance, &fMeshesScratchBuffer);
         fDynamicStatesScratchBuffer.push_back().fScissorRect.setXYWH(0, 0, drawBounds.width(),
                                                                      drawBounds.height());
+        SkDEBUGCODE(totalInstanceCount += instanceCount);
     }
 
     SkASSERT(previousBatch.fEndScissorSubBatchIdx > 0);
@@ -439,10 +591,12 @@
         proc.appendMesh(fInstanceBuffer.get(), instanceCount,
                         baseScissorInstance + startIndex, &fMeshesScratchBuffer);
         fDynamicStatesScratchBuffer.push_back().fScissorRect = scissorSubBatch.fScissor;
+        SkDEBUGCODE(totalInstanceCount += instanceCount);
     }
 
     SkASSERT(fMeshesScratchBuffer.count() == fDynamicStatesScratchBuffer.count());
     SkASSERT(fMeshesScratchBuffer.count() <= fMaxMeshesPerDraw);
+    SkASSERT(totalInstanceCount == batch.fTotalPrimitiveCounts.*instanceType);
 
     if (!fMeshesScratchBuffer.empty()) {
         SkASSERT(flushState->rtCommandBuffer());