ccpr: Rename GrCCPathParser to GrCCFiller

Various renames and other refactorings that will allow us to add new
stroking classes alongside the existing code for fills.

Bug: skia:
Change-Id: Ib477f9e1d87f9d4c1604719f9af0695a53614081
Reviewed-on: https://skia-review.googlesource.com/147503
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/bench/GrCCGeometryBench.cpp b/bench/GrCCFillGeometryBench.cpp
similarity index 96%
rename from bench/GrCCGeometryBench.cpp
rename to bench/GrCCFillGeometryBench.cpp
index 5406cea..84a9e1e 100644
--- a/bench/GrCCGeometryBench.cpp
+++ b/bench/GrCCFillGeometryBench.cpp
@@ -7,7 +7,7 @@
 
 #include "Benchmark.h"
 
-#include "ccpr/GrCCGeometry.h"
+#include "ccpr/GrCCFillGeometry.h"
 #include "SkGeometry.h"
 
 static int kNumBaseLoops = 50000;
@@ -61,7 +61,7 @@
 private:
     SkPoint fPoints[5];
     SkString fName;
-    GrCCGeometry fGeometry{4*100*kNumBaseLoops, 2*100*kNumBaseLoops};
+    GrCCFillGeometry fGeometry{4*100*kNumBaseLoops, 2*100*kNumBaseLoops};
 
     typedef Benchmark INHERITED;
 };
diff --git a/gn/bench.gni b/gn/bench.gni
index 07a7e57..60410ad 100644
--- a/gn/bench.gni
+++ b/gn/bench.gni
@@ -51,7 +51,7 @@
   "$_bench/GeometryBench.cpp",
   "$_bench/GMBench.cpp",
   "$_bench/GradientBench.cpp",
-  "$_bench/GrCCGeometryBench.cpp",
+  "$_bench/GrCCFillGeometryBench.cpp",
   "$_bench/GrMemoryPoolBench.cpp",
   "$_bench/GrMipMapBench.cpp",
   "$_bench/GrResourceCacheBench.cpp",
diff --git a/gn/gpu.gni b/gn/gpu.gni
index fc115fb..b92b6cd 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -313,12 +313,12 @@
   "$_src/gpu/ccpr/GrCCCubicShader.h",
   "$_src/gpu/ccpr/GrCCDrawPathsOp.cpp",
   "$_src/gpu/ccpr/GrCCDrawPathsOp.h",
-  "$_src/gpu/ccpr/GrCCGeometry.cpp",
-  "$_src/gpu/ccpr/GrCCGeometry.h",
+  "$_src/gpu/ccpr/GrCCFiller.cpp",
+  "$_src/gpu/ccpr/GrCCFiller.h",
+  "$_src/gpu/ccpr/GrCCFillGeometry.cpp",
+  "$_src/gpu/ccpr/GrCCFillGeometry.h",
   "$_src/gpu/ccpr/GrCCPathCache.cpp",
   "$_src/gpu/ccpr/GrCCPathCache.h",
-  "$_src/gpu/ccpr/GrCCPathParser.cpp",
-  "$_src/gpu/ccpr/GrCCPathParser.h",
   "$_src/gpu/ccpr/GrCCPathProcessor.cpp",
   "$_src/gpu/ccpr/GrCCPathProcessor.h",
   "$_src/gpu/ccpr/GrCCPerFlushResources.cpp",
diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp
index f883dbd..8d34520 100644
--- a/samplecode/SampleCCPRGeometry.cpp
+++ b/samplecode/SampleCCPRGeometry.cpp
@@ -21,7 +21,7 @@
 #include "SkPath.h"
 #include "SkRectPriv.h"
 #include "ccpr/GrCCCoverageProcessor.h"
-#include "ccpr/GrCCGeometry.h"
+#include "ccpr/GrCCFillGeometry.h"
 #include "gl/GrGLGpu.cpp"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "ops/GrDrawOp.h"
@@ -76,7 +76,8 @@
 
 public:
     DrawCoverageCountOp(CCPRGeometryView* view) : INHERITED(ClassID()), fView(view) {
-        this->setBounds(SkRectPriv::MakeLargest(), GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
+        this->setBounds(SkRect::MakeIWH(fView->width(), fView->height()), GrOp::HasAABloat::kNo,
+                        GrOp::IsZeroArea::kNo);
     }
 
     const char* name() const override {
@@ -191,9 +192,10 @@
     if (GrRenderTargetContext* rtc = canvas->internal_private_accessTopLayerRenderTargetContext()) {
         // Render coverage count.
         GrContext* ctx = canvas->getGrContext();
+        SkASSERT(ctx);
+
         GrOpMemoryPool* pool = ctx->contextPriv().opMemoryPool();
 
-        SkASSERT(ctx);
         sk_sp<GrRenderTargetContext> ccbuff =
                 ctx->contextPriv().makeDeferredRenderTargetContext(SkBackingFit::kApprox,
                                                                    this->width(), this->height(),
@@ -249,26 +251,27 @@
 }
 
 void CCPRGeometryView::updateGpuData() {
+    using Verb = GrCCFillGeometry::Verb;
     fTriPointInstances.reset();
     fQuadPointInstances.reset();
 
     if (PrimitiveType::kCubics == fPrimitiveType) {
         double t[2], s[2];
         fCubicType = GrPathUtils::getCubicKLM(fPoints, &fCubicKLM, t, s);
-        GrCCGeometry geometry;
+        GrCCFillGeometry geometry;
         geometry.beginContour(fPoints[0]);
         geometry.cubicTo(fPoints, kDebugBloat / 2, kDebugBloat / 2);
         geometry.endContour();
         int ptsIdx = 0;
-        for (GrCCGeometry::Verb verb : geometry.verbs()) {
+        for (Verb verb : geometry.verbs()) {
             switch (verb) {
-                case GrCCGeometry::Verb::kLineTo:
+                case Verb::kLineTo:
                     ++ptsIdx;
                     continue;
-                case GrCCGeometry::Verb::kMonotonicQuadraticTo:
+                case Verb::kMonotonicQuadraticTo:
                     ptsIdx += 2;
                     continue;
-                case GrCCGeometry::Verb::kMonotonicCubicTo:
+                case Verb::kMonotonicCubicTo:
                     fQuadPointInstances.push_back().set(&geometry.points()[ptsIdx], 0, 0);
                     ptsIdx += 3;
                     continue;
@@ -278,7 +281,7 @@
         }
     } else if (PrimitiveType::kTriangles != fPrimitiveType) {
         SkPoint P3[3] = {fPoints[0], fPoints[1], fPoints[3]};
-        GrCCGeometry geometry;
+        GrCCFillGeometry geometry;
         geometry.beginContour(P3[0]);
         if (PrimitiveType::kQuadratics == fPrimitiveType) {
             geometry.quadraticTo(P3);
@@ -288,23 +291,22 @@
         }
         geometry.endContour();
         int ptsIdx = 0, conicWeightIdx = 0;
-        for (GrCCGeometry::Verb verb : geometry.verbs()) {
-            if (GrCCGeometry::Verb::kBeginContour == verb ||
-                GrCCGeometry::Verb::kEndOpenContour == verb ||
-                GrCCGeometry::Verb::kEndClosedContour == verb) {
+        for (Verb verb : geometry.verbs()) {
+            if (Verb::kBeginContour == verb ||
+                Verb::kEndOpenContour == verb ||
+                Verb::kEndClosedContour == verb) {
                 continue;
             }
-            if (GrCCGeometry::Verb::kLineTo == verb) {
+            if (Verb::kLineTo == verb) {
                 ++ptsIdx;
                 continue;
             }
-            SkASSERT(GrCCGeometry::Verb::kMonotonicQuadraticTo == verb ||
-                     GrCCGeometry::Verb::kMonotonicConicTo == verb);
+            SkASSERT(Verb::kMonotonicQuadraticTo == verb || Verb::kMonotonicConicTo == verb);
             if (PrimitiveType::kQuadratics == fPrimitiveType &&
-                GrCCGeometry::Verb::kMonotonicQuadraticTo == verb) {
+                Verb::kMonotonicQuadraticTo == verb) {
                 fTriPointInstances.push_back().set(&geometry.points()[ptsIdx], Sk2f(0, 0));
             } else if (PrimitiveType::kConics == fPrimitiveType &&
-                       GrCCGeometry::Verb::kMonotonicConicTo == verb) {
+                       Verb::kMonotonicConicTo == verb) {
                 fQuadPointInstances.push_back().setW(&geometry.points()[ptsIdx], Sk2f(0, 0),
                                                      geometry.getConicWeight(conicWeightIdx++));
             }
diff --git a/src/gpu/ccpr/GrCCGeometry.cpp b/src/gpu/ccpr/GrCCFillGeometry.cpp
similarity index 94%
rename from src/gpu/ccpr/GrCCGeometry.cpp
rename to src/gpu/ccpr/GrCCFillGeometry.cpp
index 17a54af..81692cf 100644
--- a/src/gpu/ccpr/GrCCGeometry.cpp
+++ b/src/gpu/ccpr/GrCCFillGeometry.cpp
@@ -5,7 +5,7 @@
  * found in the LICENSE file.
  */
 
-#include "GrCCGeometry.h"
+#include "GrCCFillGeometry.h"
 
 #include "GrTypes.h"
 #include "SkGeometry.h"
@@ -13,19 +13,14 @@
 #include <cmath>
 #include <cstdlib>
 
-// We convert between SkPoint and Sk2f freely throughout this file.
-GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT);
-GR_STATIC_ASSERT(2 * sizeof(float) == sizeof(SkPoint));
-GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX));
-
 static constexpr float kFlatnessThreshold = 1/16.f; // 1/16 of a pixel.
 
-void GrCCGeometry::beginPath() {
+void GrCCFillGeometry::beginPath() {
     SkASSERT(!fBuildingContour);
     fVerbs.push_back(Verb::kBeginPath);
 }
 
-void GrCCGeometry::beginContour(const SkPoint& pt) {
+void GrCCFillGeometry::beginContour(const SkPoint& pt) {
     SkASSERT(!fBuildingContour);
     // Store the current verb count in the fTriangles field for now. When we close the contour we
     // will use this value to calculate the actual number of triangles in its fan.
@@ -38,7 +33,7 @@
     SkDEBUGCODE(fBuildingContour = true);
 }
 
-void GrCCGeometry::lineTo(const SkPoint P[2]) {
+void GrCCFillGeometry::lineTo(const SkPoint P[2]) {
     SkASSERT(fBuildingContour);
     SkASSERT(P[0] == fPoints.back());
     Sk2f p0 = Sk2f::Load(P);
@@ -46,7 +41,7 @@
     this->appendLine(p0, p1);
 }
 
-inline void GrCCGeometry::appendLine(const Sk2f& p0, const Sk2f& p1) {
+inline void GrCCFillGeometry::appendLine(const Sk2f& p0, const Sk2f& p1) {
     SkASSERT(fPoints.back() == SkPoint::Make(p0[0], p0[1]));
     if ((p0 == p1).allTrue()) {
         return;
@@ -138,7 +133,7 @@
     return SkNx_fma(t, b - a, a);
 }
 
-void GrCCGeometry::quadraticTo(const SkPoint P[3]) {
+void GrCCFillGeometry::quadraticTo(const SkPoint P[3]) {
     SkASSERT(fBuildingContour);
     SkASSERT(P[0] == fPoints.back());
     Sk2f p0 = Sk2f::Load(P);
@@ -155,7 +150,7 @@
     this->appendQuadratics(p0, p1, p2);
 }
 
-inline void GrCCGeometry::appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) {
+inline void GrCCFillGeometry::appendQuadratics(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) {
     Sk2f tan0 = p1 - p0;
     Sk2f tan1 = p2 - p1;
 
@@ -193,7 +188,8 @@
     this->appendMonotonicQuadratic(p012, p12, p2);
 }
 
-inline void GrCCGeometry::appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) {
+inline void GrCCFillGeometry::appendMonotonicQuadratic(const Sk2f& p0, const Sk2f& p1,
+                                                       const Sk2f& p2) {
     // Don't send curves to the GPU if we know they are nearly flat (or just very small).
     if (are_collinear(p0, p1, p2)) {
         this->appendLine(p0, p2);
@@ -465,7 +461,7 @@
     }
 }
 
-void GrCCGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) {
+void GrCCFillGeometry::cubicTo(const SkPoint P[4], float inflectPad, float loopIntersectPad) {
     SkASSERT(fBuildingContour);
     SkASSERT(P[0] == fPoints.back());
 
@@ -541,9 +537,9 @@
     *abcd = lerp(*abc, *bcd, TT);
 }
 
-void GrCCGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1,
-                                const Sk2f& p2, const Sk2f& p3, const float chops[], int numChops,
-                                float localT0, float localT1) {
+void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1,
+                                    const Sk2f& p2, const Sk2f& p3, const float chops[],
+                                    int numChops, float localT0, float localT1) {
     if (numChops) {
         SkASSERT(numChops > 0);
         int midChopIdx = numChops/2;
@@ -576,8 +572,8 @@
     this->appendCubics(mode, p0, p1, p2, p3);
 }
 
-void GrCCGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1,
-                                const Sk2f& p2, const Sk2f& p3, int maxSubdivisions) {
+void GrCCFillGeometry::appendCubics(AppendCubicMode mode, const Sk2f& p0, const Sk2f& p1,
+                                    const Sk2f& p2, const Sk2f& p3, int maxSubdivisions) {
     if (SkCubicType::kLoop != fCurrCubicType) {
         // Serpentines and cusps are always monotonic after chopping around inflection points.
         SkASSERT(!SkCubicIsDegenerate(fCurrCubicType));
@@ -672,11 +668,11 @@
     return std::abs(q*q - r) < std::abs(a*c - r) ? q/a : c/q;
 }
 
-inline void GrCCGeometry::chopAndAppendCubicAtMidTangent(AppendCubicMode mode, const Sk2f& p0,
-                                                         const Sk2f& p1, const Sk2f& p2,
-                                                         const Sk2f& p3, const Sk2f& tan0,
-                                                         const Sk2f& tan1,
-                                                         int maxFutureSubdivisions) {
+inline void GrCCFillGeometry::chopAndAppendCubicAtMidTangent(AppendCubicMode mode, const Sk2f& p0,
+                                                             const Sk2f& p1, const Sk2f& p2,
+                                                             const Sk2f& p3, const Sk2f& tan0,
+                                                             const Sk2f& tan1,
+                                                             int maxFutureSubdivisions) {
     float midT = find_midtangent(tan0, tan1, p3 + (p1 - p2)*3 - p0,
                                              (p0 - p1*2 + p2)*2,
                                              p1 - p0);
@@ -694,7 +690,7 @@
     this->appendCubics(mode, pT, p11, p12, p3, maxFutureSubdivisions);
 }
 
-void GrCCGeometry::conicTo(const SkPoint P[3], float w) {
+void GrCCFillGeometry::conicTo(const SkPoint P[3], float w) {
     SkASSERT(fBuildingContour);
     SkASSERT(P[0] == fPoints.back());
     Sk2f p0 = Sk2f::Load(P);
@@ -743,7 +739,8 @@
     this->appendMonotonicConic(p0, p1, p2, w);
 }
 
-void GrCCGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2, float w) {
+void GrCCFillGeometry::appendMonotonicConic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
+                                            float w) {
     SkASSERT(w >= 0);
 
     Sk2f base = p2 - p0;
@@ -784,7 +781,7 @@
     ++fCurrContourTallies.fConics;
 }
 
-GrCCGeometry::PrimitiveTallies GrCCGeometry::endContour() {
+GrCCFillGeometry::PrimitiveTallies GrCCFillGeometry::endContour() {
     SkASSERT(fBuildingContour);
     SkASSERT(fVerbs.count() >= fCurrContourTallies.fTriangles);
 
diff --git a/src/gpu/ccpr/GrCCGeometry.h b/src/gpu/ccpr/GrCCFillGeometry.h
similarity index 83%
rename from src/gpu/ccpr/GrCCGeometry.h
rename to src/gpu/ccpr/GrCCFillGeometry.h
index 5c5d1d2..df817db 100644
--- a/src/gpu/ccpr/GrCCGeometry.h
+++ b/src/gpu/ccpr/GrCCFillGeometry.h
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
-#ifndef GrGrCCGeometry_DEFINED
-#define GrGrCCGeometry_DEFINED
+#ifndef GrGrCCFillGeometry_DEFINED
+#define GrGrCCFillGeometry_DEFINED
 
 #include "SkGeometry.h"
 #include "SkNx.h"
@@ -15,14 +15,14 @@
 
 /**
  * This class chops device-space contours up into a series of segments that CCPR knows how to
- * render. (See GrCCGeometry::Verb.)
+ * fill. (See GrCCFillGeometry::Verb.)
  *
  * NOTE: This must be done in device space, since an affine transformation can change whether a
  * curve is monotonic.
  */
-class GrCCGeometry {
+class GrCCFillGeometry {
 public:
-    // These are the verbs that CCPR knows how to draw. If a path has any segments that don't map to
+    // These are the verbs that CCPR knows how to fill. If a path has any segments that don't map to
     // this list, then they are chopped into smaller ones that do. A list of these comprise a
     // compact representation of what can later be expanded into GPU instance data.
     enum class Verb : uint8_t {
@@ -49,7 +49,7 @@
         bool operator==(const PrimitiveTallies&);
     };
 
-    GrCCGeometry(int numSkPoints = 0, int numSkVerbs = 0, int numConicWeights = 0)
+    GrCCFillGeometry(int numSkPoints = 0, int numSkVerbs = 0, int numConicWeights = 0)
             : fPoints(numSkPoints * 3) // Reserve for a 3x expansion in points and verbs.
             , fVerbs(numSkVerbs * 3)
             , fConicWeights(numConicWeights * 3/2) {}
@@ -64,17 +64,6 @@
         fVerbs.reset();
     }
 
-    // This is included in case the caller needs to discard previously added contours. It is up to
-    // the caller to track counts and ensure we don't pop back into the middle of a different
-    // contour.
-    void resize_back(int numPoints, int numVerbs) {
-        SkASSERT(!fBuildingContour);
-        fPoints.resize_back(numPoints);
-        fVerbs.resize_back(numVerbs);
-        SkASSERT(fVerbs.empty() || fVerbs.back() == Verb::kEndOpenContour ||
-                 fVerbs.back() == Verb::kEndClosedContour);
-    }
-
     void beginPath();
     void beginContour(const SkPoint&);
     void lineTo(const SkPoint P[2]);
@@ -129,7 +118,7 @@
     SkSTArray<32, float, true> fConicWeights;
 };
 
-inline void GrCCGeometry::PrimitiveTallies::operator+=(const PrimitiveTallies& b) {
+inline void GrCCFillGeometry::PrimitiveTallies::operator+=(const PrimitiveTallies& b) {
     fTriangles += b.fTriangles;
     fWeightedTriangles += b.fWeightedTriangles;
     fQuadratics += b.fQuadratics;
@@ -137,8 +126,8 @@
     fConics += b.fConics;
 }
 
-GrCCGeometry::PrimitiveTallies
-inline GrCCGeometry::PrimitiveTallies::operator-(const PrimitiveTallies& b) const {
+GrCCFillGeometry::PrimitiveTallies
+inline GrCCFillGeometry::PrimitiveTallies::operator-(const PrimitiveTallies& b) const {
     return {fTriangles - b.fTriangles,
             fWeightedTriangles - b.fWeightedTriangles,
             fQuadratics - b.fQuadratics,
@@ -146,7 +135,7 @@
             fConics - b.fConics};
 }
 
-inline bool GrCCGeometry::PrimitiveTallies::operator==(const PrimitiveTallies& b) {
+inline bool GrCCFillGeometry::PrimitiveTallies::operator==(const PrimitiveTallies& b) {
     return fTriangles == b.fTriangles && fWeightedTriangles == b.fWeightedTriangles &&
            fQuadratics == b.fQuadratics && fCubics == b.fCubics && fConics == b.fConics;
 }
diff --git a/src/gpu/ccpr/GrCCPathParser.cpp b/src/gpu/ccpr/GrCCFiller.cpp
similarity index 64%
rename from src/gpu/ccpr/GrCCPathParser.cpp
rename to src/gpu/ccpr/GrCCFiller.cpp
index 190f162..cdace98 100644
--- a/src/gpu/ccpr/GrCCPathParser.cpp
+++ b/src/gpu/ccpr/GrCCFiller.cpp
@@ -5,7 +5,7 @@
  * found in the LICENSE file.
  */
 
-#include "GrCCPathParser.h"
+#include "GrCCFiller.h"
 
 #include "GrCaps.h"
 #include "GrGpuCommandBuffer.h"
@@ -15,105 +15,36 @@
 #include "SkPath.h"
 #include "SkPathPriv.h"
 #include "SkPoint.h"
-#include "ccpr/GrCCGeometry.h"
 #include <stdlib.h>
 
 using TriPointInstance = GrCCCoverageProcessor::TriPointInstance;
 using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance;
 
-GrCCPathParser::GrCCPathParser(int numPaths, const PathStats& pathStats)
-          // Overallocate by one point to accomodate for overflow with Sk4f. (See parsePath.)
-        : fLocalDevPtsBuffer(pathStats.fMaxPointsPerPath + 1)
-        , fGeometry(pathStats.fNumTotalSkPoints, pathStats.fNumTotalSkVerbs,
+GrCCFiller::GrCCFiller(int numPaths, const PathStats& pathStats)
+        : fGeometry(pathStats.fNumTotalSkPoints, pathStats.fNumTotalSkVerbs,
                     pathStats.fNumTotalConicWeights)
-        , fPathsInfo(numPaths)
+        , fPathInfos(numPaths)
         , fScissorSubBatches(numPaths)
         , fTotalPrimitiveCounts{PrimitiveTallies(), PrimitiveTallies()} {
     // Batches decide what to draw by looking where the previous one ended. Define initial batches
     // 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(),
-                                         PrimitiveTallies()};
+    fBatches.push_back() = {PrimitiveTallies(), fScissorSubBatches.count(), PrimitiveTallies()};
 }
 
-void GrCCPathParser::parsePath(const SkMatrix& m, const SkPath& path, SkRect* devBounds,
-                               SkRect* devBounds45) {
-    const SkPoint* pts = SkPathPriv::PointData(path);
-    int numPts = path.countPoints();
-    SkASSERT(numPts + 1 <= fLocalDevPtsBuffer.count());
+void GrCCFiller::parseDeviceSpaceFill(const SkPath& path, const SkPoint* deviceSpacePts,
+                                      GrScissorTest scissorTest, const SkIRect& clippedDevIBounds,
+                                      const SkIVector& devToAtlasOffset) {
+    SkASSERT(!fInstanceBuffer);  // Can't call after prepareToDraw().
+    SkASSERT(!path.isEmpty());
 
-    if (!numPts) {
-        devBounds->setEmpty();
-        devBounds45->setEmpty();
-        this->parsePath(path, nullptr);
-        return;
-    }
-
-    // m45 transforms path points into "45 degree" device space. A bounding box in this space gives
-    // the circumscribing octagon's diagonals. We could use SK_ScalarRoot2Over2, but an orthonormal
-    // transform is not necessary as long as the shader uses the correct inverse.
-    SkMatrix m45;
-    m45.setSinCos(1, 1);
-    m45.preConcat(m);
-
-    // X,Y,T are two parallel view matrices that accumulate two bounding boxes as they map points:
-    // device-space bounds and "45 degree" device-space bounds (| 1 -1 | * devCoords).
-    //                                                          | 1  1 |
-    Sk4f X = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY());
-    Sk4f Y = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY());
-    Sk4f T = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY());
-
-    // Map the path's points to device space and accumulate bounding boxes.
-    Sk4f devPt = SkNx_fma(Y, Sk4f(pts[0].y()), T);
-    devPt = SkNx_fma(X, Sk4f(pts[0].x()), devPt);
-    Sk4f topLeft = devPt;
-    Sk4f bottomRight = devPt;
-
-    // Store all 4 values [dev.x, dev.y, dev45.x, dev45.y]. We are only interested in the first two,
-    // and will overwrite [dev45.x, dev45.y] with the next point. This is why the dst buffer must
-    // be at least one larger than the number of points.
-    devPt.store(&fLocalDevPtsBuffer[0]);
-
-    for (int i = 1; i < numPts; ++i) {
-        devPt = SkNx_fma(Y, Sk4f(pts[i].y()), T);
-        devPt = SkNx_fma(X, Sk4f(pts[i].x()), devPt);
-        topLeft = Sk4f::Min(topLeft, devPt);
-        bottomRight = Sk4f::Max(bottomRight, devPt);
-        devPt.store(&fLocalDevPtsBuffer[i]);
-    }
-
-    SkPoint topLeftPts[2], bottomRightPts[2];
-    topLeft.store(topLeftPts);
-    bottomRight.store(bottomRightPts);
-    devBounds->setLTRB(topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(),
-                       bottomRightPts[0].y());
-    devBounds45->setLTRB(topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(),
-                         bottomRightPts[1].y());
-
-    this->parsePath(path, fLocalDevPtsBuffer.get());
-}
-
-void GrCCPathParser::parseDeviceSpacePath(const SkPath& deviceSpacePath) {
-    this->parsePath(deviceSpacePath, SkPathPriv::PointData(deviceSpacePath));
-}
-
-void GrCCPathParser::parsePath(const SkPath& path, const SkPoint* deviceSpacePts) {
-    SkASSERT(!fInstanceBuffer); // Can't call after finalize().
-    SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath() for the last one first.
-    SkDEBUGCODE(fParsingPath = true);
-    SkASSERT(path.isEmpty() || deviceSpacePts);
-
-    fCurrPathPointsIdx = fGeometry.points().count();
-    fCurrPathVerbsIdx = fGeometry.verbs().count();
-    fCurrPathPrimitiveCounts = PrimitiveTallies();
+    int currPathPointsIdx = fGeometry.points().count();
+    int currPathVerbsIdx = fGeometry.verbs().count();
+    PrimitiveTallies currPathPrimitiveCounts = PrimitiveTallies();
 
     fGeometry.beginPath();
 
-    if (path.isEmpty()) {
-        return;
-    }
-
     const float* conicWeights = SkPathPriv::ConicWeightData(path);
     int ptsIdx = 0;
     int conicWeightsIdx = 0;
@@ -122,13 +53,17 @@
     for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
         switch (verb) {
             case SkPath::kMove_Verb:
-                this->endContourIfNeeded(insideContour);
+                if (insideContour) {
+                    currPathPrimitiveCounts += fGeometry.endContour();
+                }
                 fGeometry.beginContour(deviceSpacePts[ptsIdx]);
                 ++ptsIdx;
                 insideContour = true;
                 continue;
             case SkPath::kClose_Verb:
-                this->endContourIfNeeded(insideContour);
+                if (insideContour) {
+                    currPathPrimitiveCounts += fGeometry.endContour();
+                }
                 insideContour = false;
                 continue;
             case SkPath::kLine_Verb:
@@ -155,123 +90,124 @@
     SkASSERT(ptsIdx == path.countPoints());
     SkASSERT(conicWeightsIdx == SkPathPriv::ConicWeightCnt(path));
 
-    this->endContourIfNeeded(insideContour);
-}
-
-void GrCCPathParser::endContourIfNeeded(bool insideContour) {
     if (insideContour) {
-        fCurrPathPrimitiveCounts += fGeometry.endContour();
+        currPathPrimitiveCounts += fGeometry.endContour();
     }
-}
 
-void GrCCPathParser::saveParsedPath(GrScissorTest scissorTest, const SkIRect& clippedDevIBounds,
-                                    const SkIVector& devToAtlasOffset) {
-    SkASSERT(fParsingPath);
-
-    fPathsInfo.emplace_back(scissorTest, devToAtlasOffset);
+    fPathInfos.emplace_back(scissorTest, devToAtlasOffset);
 
     // Tessellate fans from very large and/or simple paths, in order to reduce overdraw.
-    int numVerbs = fGeometry.verbs().count() - fCurrPathVerbsIdx - 1;
+    int numVerbs = fGeometry.verbs().count() - currPathVerbsIdx - 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.fWeightedTriangles = 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. We use "winding" fill type right now because we are
-        // producing a coverage count, and must fill in every region that has non-zero wind. The
-        // path processor will convert coverage count to the appropriate fill type later.
-        SkPath fan;
-        fan.setFillType(SkPath::kWinding_FillType);
-        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:
-                case GrCCGeometry::Verb::kMonotonicConicTo:
-                    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) {
-            int tessWinding = vertices[i].fWinding;
-            SkASSERT(tessWinding == vertices[i + 1].fWinding);
-            SkASSERT(tessWinding == vertices[i + 2].fWinding);
-
-            // Ensure this triangle's points actually wind in the same direction as tessWinding.
-            // CCPR shaders use the sign of wind to determine which direction to bloat, so even for
-            // "wound" triangles the winding sign and point ordering need to agree.
-            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 = ax*by - ay*bx;
-            if ((wind > 0) != (-tessWinding > 0)) { // Tessellator has opposite winding sense.
-                std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos);
-            }
-
-            if (1 == abs(tessWinding)) {
-                ++fCurrPathPrimitiveCounts.fTriangles;
-            } else {
-                ++fCurrPathPrimitiveCounts.fWeightedTriangles;
-            }
-        }
-
-        fPathsInfo.back().adoptFanTessellation(vertices, count);
+        fPathInfos.back().tessellateFan(fGeometry, currPathVerbsIdx, currPathPointsIdx,
+                                        clippedDevIBounds, &currPathPrimitiveCounts);
     }
 
-    fTotalPrimitiveCounts[(int)scissorTest] += fCurrPathPrimitiveCounts;
+    fTotalPrimitiveCounts[(int)scissorTest] += currPathPrimitiveCounts;
 
     if (GrScissorTest::kEnabled == scissorTest) {
         fScissorSubBatches.push_back() = {fTotalPrimitiveCounts[(int)GrScissorTest::kEnabled],
                                           clippedDevIBounds.makeOffset(devToAtlasOffset.fX,
                                                                        devToAtlasOffset.fY)};
     }
-
-    SkDEBUGCODE(fParsingPath = false);
 }
 
-void GrCCPathParser::discardParsedPath() {
-    SkASSERT(fParsingPath);
-    fGeometry.resize_back(fCurrPathPointsIdx, fCurrPathVerbsIdx);
-    SkDEBUGCODE(fParsingPath = false);
+void GrCCFiller::PathInfo::tessellateFan(const GrCCFillGeometry& geometry, int verbsIdx,
+                                         int ptsIdx, const SkIRect& clippedDevIBounds,
+                                         PrimitiveTallies* newTriangleCounts) {
+    using Verb = GrCCFillGeometry::Verb;
+    SkASSERT(-1 == fFanTessellationCount);
+    SkASSERT(!fFanTessellation);
+
+    const SkTArray<Verb, true>& verbs = geometry.verbs();
+    const SkTArray<SkPoint, true>& pts = geometry.points();
+
+    newTriangleCounts->fTriangles =
+            newTriangleCounts->fWeightedTriangles = 0;
+
+    // Build an SkPath of the Redbook fan. We use "winding" fill type right now because we are
+    // producing a coverage count, and must fill in every region that has non-zero wind. The
+    // path processor will convert coverage count to the appropriate fill type later.
+    SkPath fan;
+    fan.setFillType(SkPath::kWinding_FillType);
+    SkASSERT(Verb::kBeginPath == verbs[verbsIdx]);
+    for (int i = verbsIdx + 1; i < verbs.count(); ++i) {
+        switch (verbs[i]) {
+            case Verb::kBeginPath:
+                SK_ABORT("Invalid GrCCFillGeometry");
+                continue;
+
+            case Verb::kBeginContour:
+                fan.moveTo(pts[ptsIdx++]);
+                continue;
+
+            case Verb::kLineTo:
+                fan.lineTo(pts[ptsIdx++]);
+                continue;
+
+            case Verb::kMonotonicQuadraticTo:
+            case Verb::kMonotonicConicTo:
+                fan.lineTo(pts[ptsIdx + 1]);
+                ptsIdx += 2;
+                continue;
+
+            case Verb::kMonotonicCubicTo:
+                fan.lineTo(pts[ptsIdx + 2]);
+                ptsIdx += 3;
+                continue;
+
+            case Verb::kEndClosedContour:
+            case Verb::kEndOpenContour:
+                fan.close();
+                continue;
+        }
+    }
+
+    GrTessellator::WindingVertex* vertices = nullptr;
+    fFanTessellationCount =
+            GrTessellator::PathToVertices(fan, std::numeric_limits<float>::infinity(),
+                                          SkRect::Make(clippedDevIBounds), &vertices);
+    if (fFanTessellationCount <= 0) {
+        SkASSERT(0 == fFanTessellationCount);
+        SkASSERT(nullptr == vertices);
+        return;
+    }
+
+    SkASSERT(0 == fFanTessellationCount % 3);
+    for (int i = 0; i < fFanTessellationCount; i += 3) {
+        int tessWinding = vertices[i].fWinding;
+        SkASSERT(tessWinding == vertices[i + 1].fWinding);
+        SkASSERT(tessWinding == vertices[i + 2].fWinding);
+
+        // Ensure this triangle's points actually wind in the same direction as tessWinding.
+        // CCPR shaders use the sign of wind to determine which direction to bloat, so even for
+        // "wound" triangles the winding sign and point ordering need to agree.
+        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 = ax*by - ay*bx;
+        if ((wind > 0) != (-tessWinding > 0)) { // Tessellator has opposite winding sense.
+            std::swap(vertices[i + 1].fPos, vertices[i + 2].fPos);
+        }
+
+        if (1 == abs(tessWinding)) {
+            ++newTriangleCounts->fTriangles;
+        } else {
+            ++newTriangleCounts->fWeightedTriangles;
+        }
+    }
+
+    fFanTessellation.reset(vertices);
 }
 
-GrCCPathParser::CoverageCountBatchID GrCCPathParser::closeCurrentBatch() {
+GrCCFiller::BatchID GrCCFiller::closeCurrentBatch() {
     SkASSERT(!fInstanceBuffer);
-    SkASSERT(!fCoverageCountBatches.empty());
+    SkASSERT(!fBatches.empty());
 
-    const auto& lastBatch = fCoverageCountBatches.back();
+    const auto& lastBatch = fBatches.back();
     int maxMeshes = 1 + fScissorSubBatches.count() - lastBatch.fEndScissorSubBatchIdx;
     fMaxMeshesPerDraw = SkTMax(fMaxMeshesPerDraw, maxMeshes);
 
@@ -282,12 +218,12 @@
                         lastScissorSubBatch.fEndPrimitiveIndices;
 
     // This will invalidate lastBatch.
-    fCoverageCountBatches.push_back() = {
+    fBatches.push_back() = {
         fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled],
         fScissorSubBatches.count(),
         batchTotalCounts
     };
-    return fCoverageCountBatches.count() - 1;
+    return fBatches.count() - 1;
 }
 
 // Emits a contour's triangle fan.
@@ -334,7 +270,7 @@
                                  const Sk2f& devToAtlasOffset,
                                  TriPointInstance* triPointInstanceData,
                                  QuadPointInstance* quadPointInstanceData,
-                                 GrCCGeometry::PrimitiveTallies* indices) {
+                                 GrCCFillGeometry::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,
@@ -347,11 +283,12 @@
     }
 }
 
-bool GrCCPathParser::finalize(GrOnFlushResourceProvider* onFlushRP) {
-    SkASSERT(!fParsingPath); // Call saveParsedPath() or discardParsedPath().
-    SkASSERT(fCoverageCountBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch().
+bool GrCCFiller::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) {
+    using Verb = GrCCFillGeometry::Verb;
+    SkASSERT(!fInstanceBuffer);
+    SkASSERT(fBatches.back().fEndNonScissorIndices == // Call closeCurrentBatch().
              fTotalPrimitiveCounts[(int)GrScissorTest::kDisabled]);
-    SkASSERT(fCoverageCountBatches.back().fEndScissorSubBatchIdx == fScissorSubBatches.count());
+    SkASSERT(fBatches.back().fEndScissorSubBatchIdx == fScissorSubBatches.count());
 
     // Here we build a single instance buffer to share with every internal batch.
     //
@@ -393,6 +330,7 @@
     fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType,
                                             quadEndIdx * sizeof(QuadPointInstance));
     if (!fInstanceBuffer) {
+        SkDebugf("WARNING: failed to allocate CCPR fill instance buffer.\n");
         return false;
     }
 
@@ -401,7 +339,7 @@
             reinterpret_cast<QuadPointInstance*>(triPointInstanceData);
     SkASSERT(quadPointInstanceData);
 
-    PathInfo* nextPathInfo = fPathsInfo.begin();
+    PathInfo* nextPathInfo = fPathInfos.begin();
     Sk2f devToAtlasOffset;
     PrimitiveTallies instanceIndices[2] = {fBaseInstances[0], fBaseInstances[1]};
     PrimitiveTallies* currIndices = nullptr;
@@ -413,9 +351,9 @@
     int nextConicWeightIdx = 0;
 
     // Expand the ccpr verbs into GPU instance buffers.
-    for (GrCCGeometry::Verb verb : fGeometry.verbs()) {
+    for (Verb verb : fGeometry.verbs()) {
         switch (verb) {
-            case GrCCGeometry::Verb::kBeginPath:
+            case Verb::kBeginPath:
                 SkASSERT(currFan.empty());
                 currIndices = &instanceIndices[(int)nextPathInfo->scissorTest()];
                 devToAtlasOffset = Sk2f(static_cast<float>(nextPathInfo->devToAtlasOffset().fX),
@@ -429,7 +367,7 @@
                 ++nextPathInfo;
                 continue;
 
-            case GrCCGeometry::Verb::kBeginContour:
+            case Verb::kBeginContour:
                 SkASSERT(currFan.empty());
                 ++ptsIdx;
                 if (!currFanIsTessellated) {
@@ -437,7 +375,7 @@
                 }
                 continue;
 
-            case GrCCGeometry::Verb::kLineTo:
+            case Verb::kLineTo:
                 ++ptsIdx;
                 if (!currFanIsTessellated) {
                     SkASSERT(!currFan.empty());
@@ -445,7 +383,7 @@
                 }
                 continue;
 
-            case GrCCGeometry::Verb::kMonotonicQuadraticTo:
+            case Verb::kMonotonicQuadraticTo:
                 triPointInstanceData[currIndices->fQuadratics++].set(&pts[ptsIdx],
                                                                      devToAtlasOffset);
                 ptsIdx += 2;
@@ -455,7 +393,7 @@
                 }
                 continue;
 
-            case GrCCGeometry::Verb::kMonotonicCubicTo:
+            case Verb::kMonotonicCubicTo:
                 quadPointInstanceData[currIndices->fCubics++].set(&pts[ptsIdx], devToAtlasOffset[0],
                                                                   devToAtlasOffset[1]);
                 ptsIdx += 3;
@@ -465,7 +403,7 @@
                 }
                 continue;
 
-            case GrCCGeometry::Verb::kMonotonicConicTo:
+            case Verb::kMonotonicConicTo:
                 quadPointInstanceData[currIndices->fConics++].setW(
                         &pts[ptsIdx], devToAtlasOffset,
                         fGeometry.getConicWeight(nextConicWeightIdx));
@@ -477,13 +415,13 @@
                 }
                 continue;
 
-            case GrCCGeometry::Verb::kEndClosedContour:  // endPt == startPt.
+            case Verb::kEndClosedContour:  // endPt == startPt.
                 if (!currFanIsTessellated) {
                     SkASSERT(!currFan.empty());
                     currFan.pop_back();
                 }
             // fallthru.
-            case GrCCGeometry::Verb::kEndOpenContour:  // endPt != startPt.
+            case Verb::kEndOpenContour:  // endPt != startPt.
                 SkASSERT(!currFanIsTessellated || currFan.empty());
                 if (!currFanIsTessellated && currFan.count() >= 3) {
                     int fanSize = currFan.count();
@@ -503,7 +441,7 @@
 
     fInstanceBuffer->unmap();
 
-    SkASSERT(nextPathInfo == fPathsInfo.end());
+    SkASSERT(nextPathInfo == fPathInfos.end());
     SkASSERT(ptsIdx == pts.count() - 1);
     SkASSERT(instanceIndices[0].fTriangles == fBaseInstances[1].fTriangles);
     SkASSERT(instanceIndices[1].fTriangles == fBaseInstances[0].fQuadratics);
@@ -522,13 +460,13 @@
     return true;
 }
 
-void GrCCPathParser::drawCoverageCount(GrOpFlushState* flushState, CoverageCountBatchID batchID,
-                                       const SkIRect& drawBounds) const {
+void GrCCFiller::drawFills(GrOpFlushState* flushState, BatchID batchID,
+                           const SkIRect& drawBounds) const {
     using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
 
     SkASSERT(fInstanceBuffer);
 
-    const PrimitiveTallies& batchTotalCounts = fCoverageCountBatches[batchID].fTotalPrimitiveCounts;
+    const PrimitiveTallies& batchTotalCounts = fBatches[batchID].fTotalPrimitiveCounts;
 
     GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrScissorTest::kEnabled,
                         SkBlendMode::kPlus);
@@ -559,11 +497,10 @@
     }
 }
 
-void GrCCPathParser::drawPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline,
-                                    CoverageCountBatchID batchID,
-                                    GrCCCoverageProcessor::PrimitiveType primitiveType,
-                                    int PrimitiveTallies::*instanceType,
-                                    const SkIRect& drawBounds) const {
+void GrCCFiller::drawPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline,
+                                BatchID batchID, GrCCCoverageProcessor::PrimitiveType primitiveType,
+                                int PrimitiveTallies::*instanceType,
+                                const SkIRect& drawBounds) const {
     SkASSERT(pipeline.isScissorEnabled());
 
     // Don't call reset(), as that also resets the reserve count.
@@ -573,9 +510,9 @@
     GrCCCoverageProcessor proc(flushState->resourceProvider(), primitiveType);
 
     SkASSERT(batchID > 0);
-    SkASSERT(batchID < fCoverageCountBatches.count());
-    const CoverageCountBatch& previousBatch = fCoverageCountBatches[batchID - 1];
-    const CoverageCountBatch& batch = fCoverageCountBatches[batchID];
+    SkASSERT(batchID < fBatches.count());
+    const Batch& previousBatch = fBatches[batchID - 1];
+    const Batch& batch = fBatches[batchID];
     SkDEBUGCODE(int totalInstanceCount = 0);
 
     if (int instanceCount = batch.fEndNonScissorIndices.*instanceType -
diff --git a/src/gpu/ccpr/GrCCFiller.h b/src/gpu/ccpr/GrCCFiller.h
new file mode 100644
index 0000000..40dc657
--- /dev/null
+++ b/src/gpu/ccpr/GrCCFiller.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrCCPathParser_DEFINED
+#define GrCCPathParser_DEFINED
+
+#include "GrMesh.h"
+#include "SkPath.h"
+#include "SkPathPriv.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "GrTessellator.h"
+#include "ccpr/GrCCCoverageProcessor.h"
+#include "ccpr/GrCCFillGeometry.h"
+#include "ops/GrDrawOp.h"
+
+class GrOnFlushResourceProvider;
+class SkMatrix;
+class SkPath;
+
+/**
+ * This class parses SkPaths into CCPR primitives in GPU buffers, then issues calls to draw their
+ * coverage counts.
+ */
+class GrCCFiller {
+public:
+    struct PathStats {
+        int fMaxPointsPerPath = 0;
+        int fNumTotalSkPoints = 0;
+        int fNumTotalSkVerbs = 0;
+        int fNumTotalConicWeights = 0;
+
+        void statPath(const SkPath&);
+    };
+
+    GrCCFiller(int numPaths, const PathStats&);
+
+    // Parses a device-space SkPath into the current batch, using the SkPath's original verbs and
+    // 'deviceSpacePts'. Accepts an optional post-device-space translate for placement in an atlas.
+    void parseDeviceSpaceFill(const SkPath&, const SkPoint* deviceSpacePts, GrScissorTest,
+                              const SkIRect& clippedDevIBounds, const SkIVector& devToAtlasOffset);
+
+    using BatchID = int;
+
+    // Compiles the outstanding parsed paths into a batch, and returns an ID that can be used to
+    // draw their fills in the future.
+    BatchID closeCurrentBatch();
+
+    // Builds internal GPU buffers and prepares for calls to drawFills(). Caller must close the
+    // current batch before calling this method, and cannot parse new paths afer.
+    bool prepareToDraw(GrOnFlushResourceProvider*);
+
+    // Called after prepareToDraw(). Draws the given batch of path fills.
+    void drawFills(GrOpFlushState*, BatchID, const SkIRect& drawBounds) const;
+
+private:
+    static constexpr int kNumScissorModes = 2;
+    using PrimitiveTallies = GrCCFillGeometry::PrimitiveTallies;
+
+    // Every kBeginPath verb has a corresponding PathInfo entry.
+    class PathInfo {
+    public:
+        PathInfo(GrScissorTest scissorTest, const SkIVector& devToAtlasOffset)
+                : fScissorTest(scissorTest), fDevToAtlasOffset(devToAtlasOffset) {}
+
+        GrScissorTest scissorTest() const { return fScissorTest; }
+        const SkIVector& devToAtlasOffset() const { return fDevToAtlasOffset; }
+
+        // An empty tessellation fan is also valid; we use negative count to denote not tessellated.
+        bool hasFanTessellation() const { return fFanTessellationCount >= 0; }
+        int fanTessellationCount() const {
+            SkASSERT(this->hasFanTessellation());
+            return fFanTessellationCount;
+        }
+        const GrTessellator::WindingVertex* fanTessellation() const {
+            SkASSERT(this->hasFanTessellation());
+            return fFanTessellation.get();
+        }
+        void tessellateFan(const GrCCFillGeometry&, int verbsIdx, int ptsIdx,
+                           const SkIRect& clippedDevIBounds, PrimitiveTallies* newTriangleCounts);
+
+    private:
+        GrScissorTest fScissorTest;
+        SkIVector fDevToAtlasOffset;  // Translation from device space to location in atlas.
+        int fFanTessellationCount = -1;
+        std::unique_ptr<const GrTessellator::WindingVertex[]> fFanTessellation;
+    };
+
+    // Defines a batch of CCPR primitives. Start indices are deduced by looking at the previous
+    // Batch in the list.
+    struct Batch {
+        PrimitiveTallies fEndNonScissorIndices;
+        int fEndScissorSubBatchIdx;
+        PrimitiveTallies fTotalPrimitiveCounts;
+    };
+
+    // Defines a sub-batch that will be drawn with the given scissor rect. Start indices are deduced
+    // by looking at the previous ScissorSubBatch in the list.
+    struct ScissorSubBatch {
+        PrimitiveTallies fEndPrimitiveIndices;
+        SkIRect fScissor;
+    };
+
+    void drawPrimitives(GrOpFlushState*, const GrPipeline&, BatchID,
+                        GrCCCoverageProcessor::PrimitiveType, int PrimitiveTallies::*instanceType,
+                        const SkIRect& drawBounds) const;
+
+    GrCCFillGeometry fGeometry;
+    SkSTArray<32, PathInfo, true> fPathInfos;
+    SkSTArray<32, Batch, true> fBatches;
+    SkSTArray<32, ScissorSubBatch, true> fScissorSubBatches;
+    PrimitiveTallies fTotalPrimitiveCounts[kNumScissorModes];
+    int fMaxMeshesPerDraw = 0;
+
+    sk_sp<GrBuffer> fInstanceBuffer;
+    PrimitiveTallies fBaseInstances[kNumScissorModes];
+    mutable SkSTArray<32, GrMesh> fMeshesScratchBuffer;
+    mutable SkSTArray<32, SkIRect> fScissorRectScratchBuffer;
+};
+
+inline void GrCCFiller::PathStats::statPath(const SkPath& path) {
+    fMaxPointsPerPath = SkTMax(fMaxPointsPerPath, path.countPoints());
+    fNumTotalSkPoints += path.countPoints();
+    fNumTotalSkVerbs += path.countVerbs();
+    fNumTotalConicWeights += SkPathPriv::ConicWeightCnt(path);
+}
+
+#endif
diff --git a/src/gpu/ccpr/GrCCPathCache.cpp b/src/gpu/ccpr/GrCCPathCache.cpp
index 7a00d35..01781b0 100644
--- a/src/gpu/ccpr/GrCCPathCache.cpp
+++ b/src/gpu/ccpr/GrCCPathCache.cpp
@@ -9,7 +9,6 @@
 
 #include "GrShape.h"
 #include "SkNx.h"
-#include "ccpr/GrCCPathParser.h"
 
 // The maximum number of cache entries we allow in our own cache.
 static constexpr int kMaxCacheCount = 1 << 16;
diff --git a/src/gpu/ccpr/GrCCPathParser.h b/src/gpu/ccpr/GrCCPathParser.h
deleted file mode 100644
index 9ec2a3c..0000000
--- a/src/gpu/ccpr/GrCCPathParser.h
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrCCPathParser_DEFINED
-#define GrCCPathParser_DEFINED
-
-#include "GrMesh.h"
-#include "SkPath.h"
-#include "SkPathPriv.h"
-#include "SkRect.h"
-#include "SkRefCnt.h"
-#include "GrTessellator.h"
-#include "ccpr/GrCCCoverageProcessor.h"
-#include "ccpr/GrCCGeometry.h"
-#include "ops/GrDrawOp.h"
-
-class GrOnFlushResourceProvider;
-class SkMatrix;
-class SkPath;
-
-/**
- * This class parses SkPaths into CCPR primitives in GPU buffers, then issues calls to draw their
- * coverage counts.
- */
-class GrCCPathParser {
-public:
-    struct PathStats {
-        int fMaxPointsPerPath = 0;
-        int fNumTotalSkPoints = 0;
-        int fNumTotalSkVerbs = 0;
-        int fNumTotalConicWeights = 0;
-
-        void statPath(const SkPath&);
-    };
-
-    GrCCPathParser(int numPaths, const PathStats&);
-
-    ~GrCCPathParser() {
-        // Enforce the contract that the client always calls saveParsedPath or discardParsedPath.
-        SkASSERT(!fParsingPath);
-    }
-
-    using CoverageCountBatchID = int;
-
-    // Parses an SkPath into a temporary staging area. The path will not be included in the current
-    // batch until there is a matching call to saveParsedPath. The user must complement this with a
-    // following call to either saveParsedPath or discardParsedPath.
-    //
-    // Returns two tight bounding boxes: device space and "45 degree" (| 1 -1 | * devCoords) space.
-    //                                                                 | 1  1 |
-    void parsePath(const SkMatrix&, const SkPath&, SkRect* devBounds, SkRect* devBounds45);
-
-    // Parses a device-space SkPath into a temporary staging area. The path will not be included in
-    // the current batch until there is a matching call to saveParsedPath. The user must complement
-    // this with a following call to either saveParsedPath or discardParsedPath.
-    void parseDeviceSpacePath(const SkPath&);
-
-    // Commits the currently-parsed path from staging to the current batch, and specifies whether
-    // the mask should be rendered with a scissor in effect. Accepts an optional post-device-space
-    // translate for placement in an atlas.
-    void saveParsedPath(GrScissorTest, const SkIRect& clippedDevIBounds,
-                        const SkIVector& devToAtlasOffset);
-    void discardParsedPath();
-
-    // Compiles the outstanding saved paths into a batch, and returns an ID that can be used to draw
-    // their coverage counts in the future.
-    CoverageCountBatchID closeCurrentBatch();
-
-    // Builds internal GPU buffers and prepares for calls to drawCoverageCount. Caller must close
-    // the current batch before calling this method, and cannot parse new paths afer.
-    bool finalize(GrOnFlushResourceProvider*);
-
-    // Called after finalize. Draws the given batch of parsed paths.
-    void drawCoverageCount(GrOpFlushState*, CoverageCountBatchID, const SkIRect& drawBounds) const;
-
-private:
-    static constexpr int kNumScissorModes = 2;
-    using PrimitiveTallies = GrCCGeometry::PrimitiveTallies;
-
-    // Every kBeginPath verb has a corresponding PathInfo entry.
-    class PathInfo {
-    public:
-        PathInfo(GrScissorTest scissorTest, const SkIVector& devToAtlasOffset)
-                : fScissorTest(scissorTest), fDevToAtlasOffset(devToAtlasOffset) {}
-
-        GrScissorTest scissorTest() const { return fScissorTest; }
-        const SkIVector& devToAtlasOffset() const { return fDevToAtlasOffset; }
-
-        // An empty tessellation fan is also valid; we use negative count to denote not tessellated.
-        bool hasFanTessellation() const { return fFanTessellationCount >= 0; }
-        int fanTessellationCount() const {
-            SkASSERT(this->hasFanTessellation());
-            return fFanTessellationCount;
-        }
-        const GrTessellator::WindingVertex* fanTessellation() const {
-            SkASSERT(this->hasFanTessellation());
-            return fFanTessellation.get();
-        }
-
-        void adoptFanTessellation(const GrTessellator::WindingVertex* vertices, int count) {
-            SkASSERT(count >= 0);
-            fFanTessellation.reset(vertices);
-            fFanTessellationCount = count;
-        }
-
-    private:
-        GrScissorTest fScissorTest;
-        SkIVector fDevToAtlasOffset;  // Translation from device space to location in atlas.
-        int fFanTessellationCount = -1;
-        std::unique_ptr<const GrTessellator::WindingVertex[]> fFanTessellation;
-    };
-
-    // Defines a batch of CCPR primitives. Start indices are deduced by looking at the previous
-    // CoverageCountBatch in the list.
-    struct CoverageCountBatch {
-        PrimitiveTallies fEndNonScissorIndices;
-        int fEndScissorSubBatchIdx;
-        PrimitiveTallies fTotalPrimitiveCounts;
-    };
-
-    // Defines a sub-batch from CoverageCountBatch that will be drawn with the given scissor rect.
-    // Start indices are deduced by looking at the previous ScissorSubBatch in the list.
-    struct ScissorSubBatch {
-        PrimitiveTallies fEndPrimitiveIndices;
-        SkIRect fScissor;
-    };
-
-    void parsePath(const SkPath&, const SkPoint* deviceSpacePts);
-    void endContourIfNeeded(bool insideContour);
-
-    void drawPrimitives(GrOpFlushState*, const GrPipeline&, CoverageCountBatchID,
-                        GrCCCoverageProcessor::PrimitiveType, int PrimitiveTallies::*instanceType,
-                        const SkIRect& drawBounds) const;
-
-    // Staging area for the path being parsed.
-    SkDEBUGCODE(int fParsingPath = false);
-    const SkAutoSTArray<32, SkPoint> fLocalDevPtsBuffer;
-    int fCurrPathPointsIdx;
-    int fCurrPathVerbsIdx;
-    PrimitiveTallies fCurrPathPrimitiveCounts;
-
-    GrCCGeometry fGeometry;
-    SkSTArray<32, PathInfo, true> fPathsInfo;
-    SkSTArray<32, CoverageCountBatch, true> fCoverageCountBatches;
-    SkSTArray<32, ScissorSubBatch, true> fScissorSubBatches;
-    PrimitiveTallies fTotalPrimitiveCounts[kNumScissorModes];
-    int fMaxMeshesPerDraw = 0;
-
-    sk_sp<GrBuffer> fInstanceBuffer;
-    PrimitiveTallies fBaseInstances[kNumScissorModes];
-    mutable SkSTArray<32, GrMesh> fMeshesScratchBuffer;
-    mutable SkSTArray<32, SkIRect> fScissorRectScratchBuffer;
-};
-
-inline void GrCCPathParser::PathStats::statPath(const SkPath& path) {
-    fMaxPointsPerPath = SkTMax(fMaxPointsPerPath, path.countPoints());
-    fNumTotalSkPoints += path.countPoints();
-    fNumTotalSkVerbs += path.countVerbs();
-    fNumTotalConicWeights += SkPathPriv::ConicWeightCnt(path);
-}
-
-#endif
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index 886d679..0ab4e5d 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -15,7 +15,7 @@
 #include "SkMakeUnique.h"
 #include "ccpr/GrCCPathCache.h"
 
-using CoverageCountBatchID = GrCCPathParser::CoverageCountBatchID;
+using FillBatchID = GrCCFiller::BatchID;
 using PathInstance = GrCCPathProcessor::Instance;
 
 namespace {
@@ -101,7 +101,7 @@
 
     static std::unique_ptr<GrDrawOp> Make(GrContext* context,
                                           sk_sp<const GrCCPerFlushResources> resources,
-                                          CoverageCountBatchID batchID, const SkISize& drawBounds) {
+                                          FillBatchID batchID, const SkISize& drawBounds) {
         GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
 
         return pool->allocate<RenderAtlasOp>(std::move(resources), batchID, drawBounds);
@@ -111,20 +111,20 @@
     const char* name() const override { return "RenderAtlasOp (CCPR)"; }
 
     void onExecute(GrOpFlushState* flushState) override {
-        fResources->pathParser().drawCoverageCount(flushState, fBatchID, fDrawBounds);
+        fResources->filler().drawFills(flushState, fBatchID, fDrawBounds);
     }
 
 private:
     friend class ::GrOpMemoryPool; // for ctor
 
-    RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, CoverageCountBatchID batchID,
+    RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, FillBatchID batchID,
                   const SkISize& drawBounds)
             : AtlasOp(ClassID(), std::move(resources), drawBounds)
             , fBatchID(batchID)
             , fDrawBounds(SkIRect::MakeWH(drawBounds.width(), drawBounds.height())) {
     }
 
-    const CoverageCountBatchID fBatchID;
+    const FillBatchID fBatchID;
     const SkIRect fDrawBounds;
 };
 
@@ -138,7 +138,11 @@
 
 GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushRP,
                                              const GrCCPerFlushResourceSpecs& specs)
-        : fPathParser(specs.fNumRenderedPaths + specs.fNumClipPaths, specs.fRenderedPathStats)
+          // Overallocate by one point so we can call Sk4f::Store at the final SkPoint in the array.
+          // (See transform_path_pts below.)
+          // FIXME: instead use built-in instructions to write only the first two lanes of an Sk4f.
+        : fLocalDevPtsBuffer(specs.fRenderedPathStats.fMaxPointsPerPath + 1)
+        , fFiller(specs.fNumRenderedPaths + specs.fNumClipPaths, specs.fRenderedPathStats)
         , fCopyAtlasStack(kAlpha_8_GrPixelConfig, specs.fCopyAtlasSpecs, onFlushRP->caps())
         , fRenderedAtlasStack(kAlpha_half_GrPixelConfig, specs.fRenderedAtlasSpecs,
                               onFlushRP->caps())
@@ -183,6 +187,56 @@
     return &fCopyAtlasStack.current();
 }
 
+static void transform_path_pts(const SkMatrix& m, const SkPath& path,
+                               const SkAutoSTArray<32, SkPoint>& outDevPts, SkRect* devBounds,
+                               SkRect* devBounds45) {
+    const SkPoint* pts = SkPathPriv::PointData(path);
+    int numPts = path.countPoints();
+    SkASSERT(numPts + 1 <= outDevPts.count());
+    SkASSERT(numPts);
+
+    // m45 transforms path points into "45 degree" device space. A bounding box in this space gives
+    // the circumscribing octagon's diagonals. We could use SK_ScalarRoot2Over2, but an orthonormal
+    // transform is not necessary as long as the shader uses the correct inverse.
+    SkMatrix m45;
+    m45.setSinCos(1, 1);
+    m45.preConcat(m);
+
+    // X,Y,T are two parallel view matrices that accumulate two bounding boxes as they map points:
+    // device-space bounds and "45 degree" device-space bounds (| 1 -1 | * devCoords).
+    //                                                          | 1  1 |
+    Sk4f X = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY());
+    Sk4f Y = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY());
+    Sk4f T = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY());
+
+    // Map the path's points to device space and accumulate bounding boxes.
+    Sk4f devPt = SkNx_fma(Y, Sk4f(pts[0].y()), T);
+    devPt = SkNx_fma(X, Sk4f(pts[0].x()), devPt);
+    Sk4f topLeft = devPt;
+    Sk4f bottomRight = devPt;
+
+    // Store all 4 values [dev.x, dev.y, dev45.x, dev45.y]. We are only interested in the first two,
+    // and will overwrite [dev45.x, dev45.y] with the next point. This is why the dst buffer must
+    // be at least one larger than the number of points.
+    devPt.store(&outDevPts[0]);
+
+    for (int i = 1; i < numPts; ++i) {
+        devPt = SkNx_fma(Y, Sk4f(pts[i].y()), T);
+        devPt = SkNx_fma(X, Sk4f(pts[i].x()), devPt);
+        topLeft = Sk4f::Min(topLeft, devPt);
+        bottomRight = Sk4f::Max(bottomRight, devPt);
+        devPt.store(&outDevPts[i]);
+    }
+
+    SkPoint topLeftPts[2], bottomRightPts[2];
+    topLeft.store(topLeftPts);
+    bottomRight.store(bottomRightPts);
+    devBounds->setLTRB(topLeftPts[0].x(), topLeftPts[0].y(), bottomRightPts[0].x(),
+                       bottomRightPts[0].y());
+    devBounds45->setLTRB(topLeftPts[1].x(), topLeftPts[1].y(), bottomRightPts[1].x(),
+                         bottomRightPts[1].y());
+}
+
 const GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const SkIRect& clipIBounds,
                                                           const SkMatrix& m, const SkPath& path,
                                                           SkRect* devBounds, SkRect* devBounds45,
@@ -191,13 +245,24 @@
     SkASSERT(this->isMapped());
     SkASSERT(fNextPathInstanceIdx < fEndPathInstance);
 
-    fPathParser.parsePath(m, path, devBounds, devBounds45);
+    if (path.isEmpty()) {
+        SkDEBUGCODE(--fEndPathInstance);
+        return nullptr;
+    }
+
+    transform_path_pts(m, path, fLocalDevPtsBuffer, devBounds, devBounds45);
     devBounds->roundOut(devIBounds);
 
-    if (!this->placeParsedPathInAtlas(clipIBounds, *devIBounds, devToAtlasOffset)) {
+    GrScissorTest scissorTest;
+    SkIRect clippedPathIBounds;
+    if (!this->placeRenderedPathInAtlas(clipIBounds, *devIBounds, &scissorTest, &clippedPathIBounds,
+                                        devToAtlasOffset)) {
         SkDEBUGCODE(--fEndPathInstance);
         return nullptr;  // Path was degenerate or clipped away.
     }
+
+    fFiller.parseDeviceSpaceFill(path, fLocalDevPtsBuffer.begin(), scissorTest, clippedPathIBounds,
+                                 *devToAtlasOffset);
     return &fRenderedAtlasStack.current();
 }
 
@@ -205,37 +270,45 @@
         const SkIRect& clipIBounds, const SkPath& devPath, const SkIRect& devPathIBounds,
         SkIVector* devToAtlasOffset) {
     SkASSERT(this->isMapped());
-    fPathParser.parseDeviceSpacePath(devPath);
-    if (!this->placeParsedPathInAtlas(clipIBounds, devPathIBounds, devToAtlasOffset)) {
+
+    if (devPath.isEmpty()) {
         return nullptr;
     }
+
+    GrScissorTest scissorTest;
+    SkIRect clippedPathIBounds;
+    if (!this->placeRenderedPathInAtlas(clipIBounds, devPathIBounds, &scissorTest,
+                                        &clippedPathIBounds, devToAtlasOffset)) {
+        return nullptr;
+    }
+
+    fFiller.parseDeviceSpaceFill(devPath, SkPathPriv::PointData(devPath), scissorTest,
+                                 clippedPathIBounds, *devToAtlasOffset);
     return &fRenderedAtlasStack.current();
 }
 
-bool GrCCPerFlushResources::placeParsedPathInAtlas(const SkIRect& clipIBounds,
-                                                   const SkIRect& pathIBounds,
-                                                   SkIVector* devToAtlasOffset) {
-    GrScissorTest scissorTest;
-    SkIRect clippedPathIBounds;
+bool GrCCPerFlushResources::placeRenderedPathInAtlas(const SkIRect& clipIBounds,
+                                                     const SkIRect& pathIBounds,
+                                                     GrScissorTest* scissorTest,
+                                                     SkIRect* clippedPathIBounds,
+                                                     SkIVector* devToAtlasOffset) {
     if (clipIBounds.contains(pathIBounds)) {
-        clippedPathIBounds = pathIBounds;
-        scissorTest = GrScissorTest::kDisabled;
-    } else if (clippedPathIBounds.intersect(clipIBounds, pathIBounds)) {
-        scissorTest = GrScissorTest::kEnabled;
+        *clippedPathIBounds = pathIBounds;
+        *scissorTest = GrScissorTest::kDisabled;
+    } else if (clippedPathIBounds->intersect(clipIBounds, pathIBounds)) {
+        *scissorTest = GrScissorTest::kEnabled;
     } else {
-        fPathParser.discardParsedPath();
         return false;
     }
 
     if (GrCCAtlas* retiredAtlas =
-                fRenderedAtlasStack.addRect(clippedPathIBounds, devToAtlasOffset)) {
+                fRenderedAtlasStack.addRect(*clippedPathIBounds, devToAtlasOffset)) {
         // We did not fit in the previous coverage count atlas and it was retired. Close the path
         // parser's current batch (which does not yet include the path we just parsed). We will
         // render this batch into the retired atlas during finalize().
-        CoverageCountBatchID batchID = fPathParser.closeCurrentBatch();
+        FillBatchID batchID = fFiller.closeCurrentBatch();
         retiredAtlas->setUserBatchID(batchID);
     }
-    fPathParser.saveParsedPath(scissorTest, clippedPathIBounds, *devToAtlasOffset);
     return true;
 }
 
@@ -253,14 +326,13 @@
         fCopyAtlasStack.current().setUserBatchID(fNextCopyInstanceIdx);
     }
     if (!fRenderedAtlasStack.empty()) {
-        CoverageCountBatchID batchID = fPathParser.closeCurrentBatch();
+        FillBatchID batchID = fFiller.closeCurrentBatch();
         fRenderedAtlasStack.current().setUserBatchID(batchID);
     }
 
     // Build the GPU buffers to render path coverage counts. (This must not happen until after the
     // final call to fPathParser.closeCurrentBatch().)
-    if (!fPathParser.finalize(onFlushRP)) {
-        SkDebugf("WARNING: failed to allocate GPU buffers for CCPR. No paths will be drawn.\n");
+    if (!fFiller.prepareToDraw(onFlushRP)) {
         return false;
     }
 
@@ -320,5 +392,5 @@
     fRenderedPathStats.fNumTotalSkPoints += fCopyPathStats.fNumTotalSkPoints;
     fRenderedPathStats.fNumTotalSkVerbs += fCopyPathStats.fNumTotalSkVerbs;
     fRenderedPathStats.fNumTotalConicWeights += fCopyPathStats.fNumTotalConicWeights;
-    fCopyPathStats = GrCCPathParser::PathStats();
+    fCopyPathStats = GrCCFiller::PathStats();
 }
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h
index cdc89f8..3fa392e 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.h
+++ b/src/gpu/ccpr/GrCCPerFlushResources.h
@@ -10,7 +10,7 @@
 
 #include "GrNonAtomicRef.h"
 #include "ccpr/GrCCAtlas.h"
-#include "ccpr/GrCCPathParser.h"
+#include "ccpr/GrCCFiller.h"
 #include "ccpr/GrCCPathProcessor.h"
 
 class GrCCPathCacheEntry;
@@ -24,12 +24,12 @@
     int fNumCachedPaths = 0;
 
     int fNumCopiedPaths = 0;
-    GrCCPathParser::PathStats fCopyPathStats;
+    GrCCFiller::PathStats fCopyPathStats;
     GrCCAtlas::Specs fCopyAtlasSpecs;
 
     int fNumRenderedPaths = 0;
     int fNumClipPaths = 0;
-    GrCCPathParser::PathStats fRenderedPathStats;
+    GrCCFiller::PathStats fRenderedPathStats;
     GrCCAtlas::Specs fRenderedAtlasSpecs;
 
     bool isEmpty() const {
@@ -85,7 +85,7 @@
                   SkTArray<sk_sp<GrRenderTargetContext>>* out);
 
     // Accessors used by draw calls, once the resources have been finalized.
-    const GrCCPathParser& pathParser() const { SkASSERT(!this->isMapped()); return fPathParser; }
+    const GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; }
     const GrBuffer* indexBuffer() const { SkASSERT(!this->isMapped()); return fIndexBuffer.get(); }
     const GrBuffer* vertexBuffer() const { SkASSERT(!this->isMapped()); return fVertexBuffer.get();}
     GrBuffer* instanceBuffer() const { SkASSERT(!this->isMapped()); return fInstanceBuffer.get(); }
@@ -107,10 +107,12 @@
     }
 
 private:
-    bool placeParsedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds,
-                                SkIVector* devToAtlasOffset);
+    bool placeRenderedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds,
+                                  GrScissorTest*, SkIRect* clippedPathIBounds,
+                                  SkIVector* devToAtlasOffset);
 
-    GrCCPathParser fPathParser;
+    const SkAutoSTArray<32, SkPoint> fLocalDevPtsBuffer;
+    GrCCFiller fFiller;
     GrCCAtlasStack fCopyAtlasStack;
     GrCCAtlasStack fRenderedAtlasStack;
 
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index d68d4c2..f783259 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -15,7 +15,6 @@
 #include "ccpr/GrCCClipProcessor.h"
 #include "ccpr/GrCCDrawPathsOp.h"
 #include "ccpr/GrCCPathCache.h"
-#include "ccpr/GrCCPathParser.h"
 
 using PathInstance = GrCCPathProcessor::Instance;