Revert "ccpr: Implement stroking with fine triangle strips"

This reverts commit 2f2757fa6ba8134330e05694d08907f6e37abb41.

Reason for revert: issues with DDL

Original change's description:
> ccpr: Implement stroking with fine triangle strips
> 
> Implements strokes by linearizing the curve into fine triangle strips
> and interpolating a coverage ramp for edge AA. Each triangle in the
> strip emits either positive or negative coverage, depending on its
> winding direction. Joins and caps are drawn with the existing CCPR
> shaders for triangles and conics.
> 
> Conic strokes and non-rigid-body transforms are not yet supported.
> 
> Bug: skia:
> Change-Id: I45a819abd64e91c2b62e992587eb85c703e09e77
> Reviewed-on: https://skia-review.googlesource.com/148243
> Commit-Queue: Chris Dalton <csmartdalton@google.com>
> Reviewed-by: Brian Salomon <bsalomon@google.com>
> Reviewed-by: Allan MacKinnon <allanmac@google.com>

TBR=egdaniel@google.com,bsalomon@google.com,robertphillips@google.com,caryclark@google.com,csmartdalton@google.com,reed@google.com,allanmac@google.com

Change-Id: I1980b09976df8275817eaffb6766dbd9fd3e59c7
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: skia:
Reviewed-on: https://skia-review.googlesource.com/150980
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCAtlas.cpp b/src/gpu/ccpr/GrCCAtlas.cpp
index 4ef632a..988d5fa 100644
--- a/src/gpu/ccpr/GrCCAtlas.cpp
+++ b/src/gpu/ccpr/GrCCAtlas.cpp
@@ -134,16 +134,10 @@
     return true;
 }
 
-void GrCCAtlas::setFillBatchID(int id) {
+void GrCCAtlas::setUserBatchID(int id) {
     // This can't be called anymore once makeRenderTargetContext() has been called.
     SkASSERT(!fTextureProxy->isInstantiated());
-    fFillBatchID = id;
-}
-
-void GrCCAtlas::setStrokeBatchID(int id) {
-    // This can't be called anymore once makeRenderTargetContext() has been called.
-    SkASSERT(!fTextureProxy->isInstantiated());
-    fStrokeBatchID = id;
+    fUserBatchID = id;
 }
 
 static uint32_t next_atlas_unique_id() {
diff --git a/src/gpu/ccpr/GrCCAtlas.h b/src/gpu/ccpr/GrCCAtlas.h
index 6412895..1a7ba1f 100644
--- a/src/gpu/ccpr/GrCCAtlas.h
+++ b/src/gpu/ccpr/GrCCAtlas.h
@@ -57,12 +57,10 @@
     bool addRect(const SkIRect& devIBounds, SkIVector* atlasOffset);
     const SkISize& drawBounds() { return fDrawBounds; }
 
-    // This is an optional space for the caller to jot down which user-defined batches to use when
+    // This is an optional space for the caller to jot down which user-defined batch to use when
     // they render the content of this atlas.
-    void setFillBatchID(int id);
-    int getFillBatchID() const { return fFillBatchID; }
-    void setStrokeBatchID(int id);
-    int getStrokeBatchID() const { return fStrokeBatchID; }
+    void setUserBatchID(int id);
+    int getUserBatchID() const { return fUserBatchID; }
 
     // Manages a unique resource cache key that gets assigned to the atlas texture. The unique key
     // does not get assigned to the texture proxy until it is instantiated.
@@ -100,8 +98,7 @@
     std::unique_ptr<Node> fTopNode;
     SkISize fDrawBounds = {0, 0};
 
-    int fFillBatchID;
-    int fStrokeBatchID;
+    int fUserBatchID;
 
     // Not every atlas will have a unique key -- a mainline CCPR one won't if we don't stash any
     // paths, and only the first atlas in the stack is eligible to be stashed.
diff --git a/src/gpu/ccpr/GrCCClipPath.cpp b/src/gpu/ccpr/GrCCClipPath.cpp
index d70b4f7..5702a96 100644
--- a/src/gpu/ccpr/GrCCClipPath.cpp
+++ b/src/gpu/ccpr/GrCCClipPath.cpp
@@ -52,7 +52,7 @@
     SkASSERT(this->isInitialized());
 
     ++specs->fNumClipPaths;
-    specs->fRenderedPathStats[GrCCPerFlushResourceSpecs::kFillIdx].statPath(fDeviceSpacePath);
+    specs->fRenderedPathStats.statPath(fDeviceSpacePath);
 
     SkIRect ibounds;
     if (ibounds.intersect(fAccessRect, fPathDevIBounds)) {
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.h b/src/gpu/ccpr/GrCCCoverageProcessor.h
index c51f743..e5b6bc1 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.h
@@ -52,7 +52,6 @@
 
         void set(const SkPoint[3], const Sk2f& trans);
         void set(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& trans);
-        void set(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& trans);
     };
 
     // Defines a single primitive shape with 4 input points, or 3 input points plus a "weight"
@@ -65,7 +64,6 @@
         void set(const SkPoint[4], float dx, float dy);
         void setW(const SkPoint[3], const Sk2f& trans, float w);
         void setW(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& trans, float w);
-        void setW(const Sk2f& P0, const Sk2f& P1, const Sk2f& P2, const Sk2f& trans, float w);
     };
 
     GrCCCoverageProcessor(GrResourceProvider* rp, PrimitiveType type)
@@ -300,15 +298,10 @@
 
 inline void GrCCCoverageProcessor::TriPointInstance::set(const SkPoint& p0, const SkPoint& p1,
                                                          const SkPoint& p2, const Sk2f& trans) {
-    Sk2f P0 = Sk2f::Load(&p0);
-    Sk2f P1 = Sk2f::Load(&p1);
-    Sk2f P2 = Sk2f::Load(&p2);
-    this->set(P0, P1, P2, trans);
-}
-
-inline void GrCCCoverageProcessor::TriPointInstance::set(const Sk2f& P0, const Sk2f& P1,
-                                                         const Sk2f& P2, const Sk2f& trans) {
-    Sk2f::Store3(this, P0 + trans, P1 + trans, P2 + trans);
+    Sk2f P0 = Sk2f::Load(&p0) + trans;
+    Sk2f P1 = Sk2f::Load(&p1) + trans;
+    Sk2f P2 = Sk2f::Load(&p2) + trans;
+    Sk2f::Store3(this, P0, P1, P2);
 }
 
 inline void GrCCCoverageProcessor::QuadPointInstance::set(const SkPoint p[4], float dx, float dy) {
@@ -326,17 +319,11 @@
 inline void GrCCCoverageProcessor::QuadPointInstance::setW(const SkPoint& p0, const SkPoint& p1,
                                                            const SkPoint& p2, const Sk2f& trans,
                                                            float w) {
-    Sk2f P0 = Sk2f::Load(&p0);
-    Sk2f P1 = Sk2f::Load(&p1);
-    Sk2f P2 = Sk2f::Load(&p2);
-    this->setW(P0, P1, P2, trans, w);
-}
-
-inline void GrCCCoverageProcessor::QuadPointInstance::setW(const Sk2f& P0, const Sk2f& P1,
-                                                           const Sk2f& P2, const Sk2f& trans,
-                                                           float w) {
+    Sk2f P0 = Sk2f::Load(&p0) + trans;
+    Sk2f P1 = Sk2f::Load(&p1) + trans;
+    Sk2f P2 = Sk2f::Load(&p2) + trans;
     Sk2f W = Sk2f(w);
-    Sk2f::Store4(this, P0 + trans, P1 + trans, P2 + trans, W);
+    Sk2f::Store4(this, P0, P1, P2, W);
 }
 
 #endif
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index 2ec8379..aed0672 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -28,82 +28,25 @@
     return sk_64_mul(r.height(), r.width());
 }
 
-std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(
-        GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
-        GrPaint&& paint) {
-    static constexpr int kPathCropThreshold = GrCoverageCountingPathRenderer::kPathCropThreshold;
-
-    SkRect conservativeDevBounds;
-    m.mapRect(&conservativeDevBounds, shape.bounds());
-
-    const SkStrokeRec& stroke = shape.style().strokeRec();
-    float strokeDevWidth = 0;
-    float conservativeInflationRadius = 0;
-    if (!stroke.isFillStyle()) {
-        if (stroke.isHairlineStyle()) {
-            strokeDevWidth = 1;
-        } else {
-            SkASSERT(m.isSimilarity());  // Otherwise matrixScaleFactor = m.getMaxScale().
-            float matrixScaleFactor = SkVector::Length(m.getScaleX(), m.getSkewY());
-            strokeDevWidth = stroke.getWidth() * matrixScaleFactor;
-        }
-        // Inflate for a minimum stroke width of 1. In some cases when the stroke is less than 1px
-        // wide, we may inflate it to 1px and instead reduce the opacity.
-        conservativeInflationRadius = SkStrokeRec::GetInflationRadius(
-                stroke.getJoin(), stroke.getMiter(), stroke.getCap(), SkTMax(strokeDevWidth, 1.f));
-        conservativeDevBounds.outset(conservativeInflationRadius, conservativeInflationRadius);
-    }
-
-    std::unique_ptr<GrCCDrawPathsOp> op;
-    float conservativeSize = SkTMax(conservativeDevBounds.height(), conservativeDevBounds.width());
-    if (conservativeSize > kPathCropThreshold) {
-        // The path is too large. Crop it or analytic AA can run out of fp32 precision.
-        SkPath croppedDevPath;
-        shape.asPath(&croppedDevPath);
-        croppedDevPath.transform(m, &croppedDevPath);
-
-        SkIRect cropBox = clipIBounds;
-        GrShape croppedDevShape;
-        if (stroke.isFillStyle()) {
-            GrCoverageCountingPathRenderer::CropPath(croppedDevPath, cropBox, &croppedDevPath);
-            croppedDevShape = GrShape(croppedDevPath);
-            conservativeDevBounds = croppedDevShape.bounds();
-        } else {
-            int r = SkScalarCeilToInt(conservativeInflationRadius);
-            cropBox.outset(r, r);
-            GrCoverageCountingPathRenderer::CropPath(croppedDevPath, cropBox, &croppedDevPath);
-            SkStrokeRec devStroke = stroke;
-            devStroke.setStrokeStyle(strokeDevWidth);
-            croppedDevShape = GrShape(croppedDevPath, GrStyle(devStroke, nullptr));
-            conservativeDevBounds = croppedDevPath.getBounds();
-            conservativeDevBounds.outset(conservativeInflationRadius, conservativeInflationRadius);
-        }
-
-        // FIXME: This breaks local coords: http://skbug.com/8003
-        return InternalMake(context, clipIBounds, SkMatrix::I(), croppedDevShape, strokeDevWidth,
-                            conservativeDevBounds, std::move(paint));
-    }
-
-    return InternalMake(context, clipIBounds, m, shape, strokeDevWidth, conservativeDevBounds,
-                        std::move(paint));
-}
-
-std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::InternalMake(
-        GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
-        float strokeDevWidth, const SkRect& conservativeDevBounds, GrPaint&& paint) {
-    SkIRect shapeConservativeIBounds;
-    conservativeDevBounds.roundOut(&shapeConservativeIBounds);
+std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(GrContext* context,
+                                                       const SkIRect& clipIBounds,
+                                                       const SkMatrix& m,
+                                                       const GrShape& shape,
+                                                       const SkRect& devBounds,
+                                                       GrPaint&& paint) {
+    SkIRect shapeDevIBounds;
+    devBounds.roundOut(&shapeDevIBounds);  // GrCCPathParser might find slightly tighter bounds.
 
     SkIRect maskDevIBounds;
     Visibility maskVisibility;
-    if (clipIBounds.contains(shapeConservativeIBounds)) {
-        maskDevIBounds = shapeConservativeIBounds;
+    if (clipIBounds.contains(shapeDevIBounds)) {
+        maskDevIBounds = shapeDevIBounds;
         maskVisibility = Visibility::kComplete;
     } else {
-        if (!maskDevIBounds.intersect(clipIBounds, shapeConservativeIBounds)) {
+        if (!maskDevIBounds.intersect(clipIBounds, shapeDevIBounds)) {
             return nullptr;
         }
-        int64_t unclippedArea = area(shapeConservativeIBounds);
+        int64_t unclippedArea = area(shapeDevIBounds);
         int64_t clippedArea = area(maskDevIBounds);
         maskVisibility = (clippedArea >= unclippedArea/2 || unclippedArea < 100*100)
                 ? Visibility::kMostlyComplete  // i.e., visible enough to justify rendering the
@@ -113,24 +56,22 @@
 
     GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
 
-    return pool->allocate<GrCCDrawPathsOp>(m, shape, strokeDevWidth, shapeConservativeIBounds,
-                                           maskDevIBounds, maskVisibility, conservativeDevBounds,
-                                           std::move(paint));
+    return pool->allocate<GrCCDrawPathsOp>(m, shape, shapeDevIBounds, maskDevIBounds,
+                                           maskVisibility, devBounds, std::move(paint));
 }
 
-GrCCDrawPathsOp::GrCCDrawPathsOp(const SkMatrix& m, const GrShape& shape, float strokeDevWidth,
-                                 const SkIRect& shapeConservativeIBounds,
-                                 const SkIRect& maskDevIBounds, Visibility maskVisibility,
-                                 const SkRect& conservativeDevBounds, GrPaint&& paint)
+GrCCDrawPathsOp::GrCCDrawPathsOp(const SkMatrix& m, const GrShape& shape,
+                                 const SkIRect& shapeDevIBounds, const SkIRect& maskDevIBounds,
+                                 Visibility maskVisibility, const SkRect& devBounds,
+                                 GrPaint&& paint)
         : GrDrawOp(ClassID())
         , fViewMatrixIfUsingLocalCoords(has_coord_transforms(paint) ? m : SkMatrix::I())
-        , fDraws(m, shape, strokeDevWidth, shapeConservativeIBounds, maskDevIBounds, maskVisibility,
-                 paint.getColor())
+        , fDraws(m, shape, shapeDevIBounds, maskDevIBounds, maskVisibility, paint.getColor())
         , fProcessors(std::move(paint)) {  // Paint must be moved after fetching its color above.
     SkDEBUGCODE(fBaseInstance = -1);
     // FIXME: intersect with clip bounds to (hopefully) improve batching.
     // (This is nontrivial due to assumptions in generating the octagon cover geometry.)
-    this->setBounds(conservativeDevBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
+    this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
 }
 
 GrCCDrawPathsOp::~GrCCDrawPathsOp() {
@@ -141,14 +82,12 @@
 }
 
 GrCCDrawPathsOp::SingleDraw::SingleDraw(const SkMatrix& m, const GrShape& shape,
-                                        float strokeDevWidth,
-                                        const SkIRect& shapeConservativeIBounds,
+                                        const SkIRect& shapeDevIBounds,
                                         const SkIRect& maskDevIBounds, Visibility maskVisibility,
                                         GrColor color)
         : fMatrix(m)
         , fShape(shape)
-        , fStrokeDevWidth(strokeDevWidth)
-        , fShapeConservativeIBounds(shapeConservativeIBounds)
+        , fShapeDevIBounds(shapeDevIBounds)
         , fMaskDevIBounds(maskDevIBounds)
         , fMaskVisibility(maskVisibility)
         , fColor(color) {
@@ -172,39 +111,9 @@
 GrDrawOp::RequiresDstTexture GrCCDrawPathsOp::finalize(const GrCaps& caps,
                                                        const GrAppliedClip* clip) {
     SkASSERT(1 == fNumDraws);  // There should only be one single path draw in this Op right now.
-    SingleDraw* draw = &fDraws.head();
-
-    const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
-            draw->fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
-            &draw->fColor);
-
-    // Lines start looking jagged when they get thinner than 1px. For thin strokes it looks better
-    // if we can convert them to hairline (i.e., inflate the stroke width to 1px), and instead
-    // reduce the opacity to create the illusion of thin-ness. This strategy also helps reduce
-    // artifacts from coverage dilation when there are self intersections.
-    if (analysis.isCompatibleWithCoverageAsAlpha() &&
-            !draw->fShape.style().strokeRec().isFillStyle() && draw->fStrokeDevWidth < 1) {
-        // Modifying the shape affects its cache key. The draw can't have a cache entry yet or else
-        // our next step would invalidate it.
-        SkASSERT(!draw->fCacheEntry);
-        SkASSERT(SkStrokeRec::kStroke_Style == draw->fShape.style().strokeRec().getStyle());
-
-        SkPath path;
-        draw->fShape.asPath(&path);
-
-        // Create a hairline version of our stroke.
-        SkStrokeRec hairlineStroke = draw->fShape.style().strokeRec();
-        hairlineStroke.setStrokeStyle(0);
-
-        // How transparent does a 1px stroke have to be in order to appear as thin as the real one?
-        GrColor coverageAsAlpha = GrColorPackA4(SkScalarFloorToInt(draw->fStrokeDevWidth * 255));
-
-        draw->fShape = GrShape(path, GrStyle(hairlineStroke, nullptr));
-        draw->fStrokeDevWidth = 1;
-        // fShapeConservativeIBounds already accounted for this possibility of inflating the stroke.
-        draw->fColor = GrColorMul(draw->fColor, coverageAsAlpha);
-    }
-
+    GrProcessorSet::Analysis analysis =
+            fProcessors.finalize(fDraws.head().fColor, GrProcessorAnalysisCoverage::kSingleChannel,
+                                 clip, false, caps, &fDraws.head().fColor);
     return RequiresDstTexture(analysis.requiresDstTexture());
 }
 
@@ -271,11 +180,8 @@
                 // can copy it into a new 8-bit atlas and keep it in the resource cache.
                 if (stashedAtlasKey.isValid() && stashedAtlasKey == cacheEntry->atlasKey()) {
                     SkASSERT(!cacheEntry->hasCachedAtlas());
-                    int idx = (draw.fShape.style().strokeRec().isFillStyle())
-                            ? GrCCPerFlushResourceSpecs::kFillIdx
-                            : GrCCPerFlushResourceSpecs::kStrokeIdx;
-                    ++specs->fNumCopiedPaths[idx];
-                    specs->fCopyPathStats[idx].statPath(path);
+                    ++specs->fNumCopiedPaths;
+                    specs->fCopyPathStats.statPath(path);
                     specs->fCopyAtlasSpecs.accountForSpace(cacheEntry->width(),
                                                            cacheEntry->height());
                     continue;
@@ -285,23 +191,18 @@
                 cacheEntry->resetAtlasKeyAndInfo();
             }
 
-            if (Visibility::kMostlyComplete == draw.fMaskVisibility && cacheEntry->hitCount() > 1) {
-                int shapeSize = SkTMax(draw.fShapeConservativeIBounds.height(),
-                                       draw.fShapeConservativeIBounds.width());
-                if (shapeSize <= onFlushRP->caps()->maxRenderTargetSize()) {
-                    // We've seen this path before with a compatible matrix, and it's mostly
-                    // visible. Just render the whole mask so we can try to cache it.
-                    draw.fMaskDevIBounds = draw.fShapeConservativeIBounds;
-                    draw.fMaskVisibility = Visibility::kComplete;
-                }
+            if (Visibility::kMostlyComplete == draw.fMaskVisibility && cacheEntry->hitCount() > 1 &&
+                SkTMax(draw.fShapeDevIBounds.height(),
+                       draw.fShapeDevIBounds.width()) <= onFlushRP->caps()->maxRenderTargetSize()) {
+                // We've seen this path before with a compatible matrix, and it's mostly visible.
+                // Just render the whole mask so we can try to cache it.
+                draw.fMaskDevIBounds = draw.fShapeDevIBounds;
+                draw.fMaskVisibility = Visibility::kComplete;
             }
         }
 
-        int idx = (draw.fShape.style().strokeRec().isFillStyle())
-                ? GrCCPerFlushResourceSpecs::kFillIdx
-                : GrCCPerFlushResourceSpecs::kStrokeIdx;
-        ++specs->fNumRenderedPaths[idx];
-        specs->fRenderedPathStats[idx].statPath(path);
+        ++specs->fNumRenderedPaths;
+        specs->fRenderedPathStats.statPath(path);
         specs->fRenderedAtlasSpecs.accountForSpace(draw.fMaskDevIBounds.width(),
                                                    draw.fMaskDevIBounds.height());
     }
@@ -318,8 +219,7 @@
         SkPath path;
         draw.fShape.asPath(&path);
 
-        auto doEvenOddFill = DoEvenOddFill(draw.fShape.style().strokeRec().isFillStyle() &&
-                                           SkPath::kEvenOdd_FillType == path.getFillType());
+        auto doEvenOddFill = DoEvenOddFill(SkPath::kEvenOdd_FillType == path.getFillType());
         SkASSERT(SkPath::kEvenOdd_FillType == path.getFillType() ||
                  SkPath::kWinding_FillType == path.getFillType());
 
@@ -370,9 +270,9 @@
         SkRect devBounds, devBounds45;
         SkIRect devIBounds;
         SkIVector devToAtlasOffset;
-        if (auto atlas = resources->renderShapeInAtlas(
-                    draw.fMaskDevIBounds, draw.fMatrix, draw.fShape, draw.fStrokeDevWidth,
-                    &devBounds, &devBounds45, &devIBounds, &devToAtlasOffset)) {
+        if (auto atlas = resources->renderPathInAtlas(draw.fMaskDevIBounds, draw.fMatrix, path,
+                                                      &devBounds, &devBounds45, &devIBounds,
+                                                      &devToAtlasOffset)) {
             this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
             resources->appendDrawPathInstance().set(devBounds, devBounds45, devToAtlasOffset,
                                                     draw.fColor, doEvenOddFill);
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.h b/src/gpu/ccpr/GrCCDrawPathsOp.h
index 2716d59..40d9df4 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.h
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.h
@@ -30,7 +30,8 @@
     SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCDrawPathsOp);
 
     static std::unique_ptr<GrCCDrawPathsOp> Make(GrContext*, const SkIRect& clipIBounds,
-                                                 const SkMatrix&, const GrShape&, GrPaint&&);
+                                                 const SkMatrix&, const GrShape&,
+                                                 const SkRect& devBounds, GrPaint&&);
     ~GrCCDrawPathsOp() override;
 
     const char* name() const override { return "GrCCDrawPathsOp"; }
@@ -69,35 +70,28 @@
 private:
     friend class GrOpMemoryPool;
 
-    static std::unique_ptr<GrCCDrawPathsOp> InternalMake(GrContext*, const SkIRect& clipIBounds,
-                                                         const SkMatrix&, const GrShape&,
-                                                         float strokeDevWidth,
-                                                         const SkRect& conservativeDevBounds,
-                                                         GrPaint&&);
     enum class Visibility {
         kPartial,
         kMostlyComplete,  // (i.e., can we cache the whole path mask if we think it will be reused?)
         kComplete
     };
 
-    GrCCDrawPathsOp(const SkMatrix&, const GrShape&, float strokeDevWidth,
-                    const SkIRect& shapeConservativeIBounds, const SkIRect& maskDevIBounds,
-                    Visibility maskVisibility, const SkRect& conservativeDevBounds, GrPaint&&);
+    GrCCDrawPathsOp(const SkMatrix&, const GrShape&, const SkIRect& shapeDevIBounds,
+                    const SkIRect& maskDevIBounds, Visibility maskVisibility,
+                    const SkRect& devBounds, GrPaint&&);
 
     void recordInstance(GrTextureProxy* atlasProxy, int instanceIdx);
 
     const SkMatrix fViewMatrixIfUsingLocalCoords;
 
     struct SingleDraw {
-        SingleDraw(const SkMatrix&, const GrShape&, float strokeDevWidth,
-                   const SkIRect& shapeConservativeIBounds, const SkIRect& maskDevIBounds,
-                   Visibility maskVisibility, GrColor);
+        SingleDraw(const SkMatrix&, const GrShape&, const SkIRect& shapeDevIBounds,
+                   const SkIRect& maskDevIBounds, Visibility maskVisibility, GrColor);
         ~SingleDraw();
 
         SkMatrix fMatrix;
-        GrShape fShape;
-        float fStrokeDevWidth;
-        const SkIRect fShapeConservativeIBounds;
+        const GrShape fShape;
+        const SkIRect fShapeDevIBounds;
         SkIRect fMaskDevIBounds;
         Visibility fMaskVisibility;
         GrColor fColor;
diff --git a/src/gpu/ccpr/GrCCFiller.cpp b/src/gpu/ccpr/GrCCFiller.cpp
index 1460077..cdace98 100644
--- a/src/gpu/ccpr/GrCCFiller.cpp
+++ b/src/gpu/ccpr/GrCCFiller.cpp
@@ -20,8 +20,9 @@
 using TriPointInstance = GrCCCoverageProcessor::TriPointInstance;
 using QuadPointInstance = GrCCCoverageProcessor::QuadPointInstance;
 
-GrCCFiller::GrCCFiller(int numPaths, int numSkPoints, int numSkVerbs, int numConicWeights)
-        : fGeometry(numSkPoints, numSkVerbs, numConicWeights)
+GrCCFiller::GrCCFiller(int numPaths, const PathStats& pathStats)
+        : fGeometry(pathStats.fNumTotalSkPoints, pathStats.fNumTotalSkVerbs,
+                    pathStats.fNumTotalConicWeights)
         , fPathInfos(numPaths)
         , fScissorSubBatches(numPaths)
         , fTotalPrimitiveCounts{PrimitiveTallies(), PrimitiveTallies()} {
diff --git a/src/gpu/ccpr/GrCCFiller.h b/src/gpu/ccpr/GrCCFiller.h
index 45a03a4..40dc657 100644
--- a/src/gpu/ccpr/GrCCFiller.h
+++ b/src/gpu/ccpr/GrCCFiller.h
@@ -9,6 +9,7 @@
 #define GrCCPathParser_DEFINED
 
 #include "GrMesh.h"
+#include "SkPath.h"
 #include "SkPathPriv.h"
 #include "SkRect.h"
 #include "SkRefCnt.h"
@@ -27,7 +28,16 @@
  */
 class GrCCFiller {
 public:
-    GrCCFiller(int numPaths, int numSkPoints, int numSkVerbs, int numConicWeights);
+    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.
@@ -112,4 +122,11 @@
     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 7d3fe2a..01781b0 100644
--- a/src/gpu/ccpr/GrCCPathCache.cpp
+++ b/src/gpu/ccpr/GrCCPathCache.cpp
@@ -45,47 +45,27 @@
 // Produces a key that accounts both for a shape's path geometry, as well as any stroke/style.
 class WriteStyledKey {
 public:
-    static constexpr int kStyledKeySizeInBytesIdx = 0;
-    static constexpr int kStrokeWidthIdx = 1;
-    static constexpr int kStrokeMiterIdx = 2;
-    static constexpr int kStrokeCapJoinIdx = 3;
-    static constexpr int kShapeUnstyledKeyIdx = 4;
-
-    static constexpr int kStrokeKeyCount = 3;  // [width, miterLimit, cap|join].
-
-    WriteStyledKey(const GrShape& shape) : fShapeUnstyledKeyCount(shape.unstyledKeySize()) {}
+    WriteStyledKey(const GrShape& shape)
+        : fShapeUnstyledKeyCount(shape.unstyledKeySize())
+        , fStyleKeyCount(
+                GrStyle::KeySize(shape.style(), GrStyle::Apply::kPathEffectAndStrokeRec)) {}
 
     // Returns the total number of uint32_t's to allocate for the key.
-    int allocCountU32() const { return kShapeUnstyledKeyIdx + fShapeUnstyledKeyCount; }
+    int allocCountU32() const { return 2 + fShapeUnstyledKeyCount + fStyleKeyCount; }
 
     // Writes the key to out[].
     void write(const GrShape& shape, uint32_t* out) {
-        out[kStyledKeySizeInBytesIdx] =
-                (kStrokeKeyCount + fShapeUnstyledKeyCount) * sizeof(uint32_t);
-
-        // Stroke key.
-        // We don't use GrStyle::WriteKey() because it does not account for hairlines.
-        // http://skbug.com/8273
-        SkASSERT(!shape.style().hasPathEffect());
-        const SkStrokeRec& stroke = shape.style().strokeRec();
-        if (stroke.isFillStyle()) {
-            // Use a value for width that won't collide with a valid fp32 value >= 0.
-            out[kStrokeWidthIdx] = ~0;
-            out[kStrokeMiterIdx] = out[kStrokeCapJoinIdx] = 0;
-        } else {
-            float width = stroke.getWidth(), miterLimit = stroke.getMiter();
-            memcpy(&out[kStrokeWidthIdx], &width, sizeof(float));
-            memcpy(&out[kStrokeMiterIdx], &miterLimit, sizeof(float));
-            out[kStrokeCapJoinIdx] = (stroke.getCap() << 16) | stroke.getJoin();
-            GR_STATIC_ASSERT(sizeof(out[kStrokeWidthIdx]) == sizeof(float));
-        }
-
-        // Shape unstyled key.
-        shape.writeUnstyledKey(&out[kShapeUnstyledKeyIdx]);
+        // How many bytes remain in the key, beginning on out[1]?
+        out[0] = (1 + fShapeUnstyledKeyCount + fStyleKeyCount)  * sizeof(uint32_t);
+        out[1] = fStyleKeyCount;
+        shape.writeUnstyledKey(&out[2]);
+        GrStyle::WriteKey(&out[2 + fShapeUnstyledKeyCount], shape.style(),
+                          GrStyle::Apply::kPathEffectAndStrokeRec, 1);
     }
 
 private:
     int fShapeUnstyledKeyCount;
+    int fStyleKeyCount;
 };
 
 }
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index e761141..0ab4e5d 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -12,17 +12,12 @@
 #include "GrOnFlushResourceProvider.h"
 #include "GrSurfaceContextPriv.h"
 #include "GrRenderTargetContext.h"
-#include "GrShape.h"
 #include "SkMakeUnique.h"
 #include "ccpr/GrCCPathCache.h"
 
 using FillBatchID = GrCCFiller::BatchID;
-using StrokeBatchID = GrCCStroker::BatchID;
 using PathInstance = GrCCPathProcessor::Instance;
 
-static constexpr int kFillIdx = GrCCPerFlushResourceSpecs::kFillIdx;
-static constexpr int kStrokeIdx = GrCCPerFlushResourceSpecs::kStrokeIdx;
-
 namespace {
 
 // Base class for an Op that renders a CCPR atlas.
@@ -106,35 +101,30 @@
 
     static std::unique_ptr<GrDrawOp> Make(GrContext* context,
                                           sk_sp<const GrCCPerFlushResources> resources,
-                                          FillBatchID fillBatchID, StrokeBatchID strokeBatchID,
-                                          const SkISize& drawBounds) {
+                                          FillBatchID batchID, const SkISize& drawBounds) {
         GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
 
-        return pool->allocate<RenderAtlasOp>(std::move(resources), fillBatchID, strokeBatchID,
-                                             drawBounds);
+        return pool->allocate<RenderAtlasOp>(std::move(resources), batchID, drawBounds);
     }
 
     // GrDrawOp interface.
     const char* name() const override { return "RenderAtlasOp (CCPR)"; }
 
     void onExecute(GrOpFlushState* flushState) override {
-        fResources->filler().drawFills(flushState, fFillBatchID, fDrawBounds);
-        fResources->stroker().drawStrokes(flushState, fStrokeBatchID, fDrawBounds);
+        fResources->filler().drawFills(flushState, fBatchID, fDrawBounds);
     }
 
 private:
     friend class ::GrOpMemoryPool; // for ctor
 
-    RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, FillBatchID fillBatchID,
-                  StrokeBatchID strokeBatchID, const SkISize& drawBounds)
+    RenderAtlasOp(sk_sp<const GrCCPerFlushResources> resources, FillBatchID batchID,
+                  const SkISize& drawBounds)
             : AtlasOp(ClassID(), std::move(resources), drawBounds)
-            , fFillBatchID(fillBatchID)
-            , fStrokeBatchID(strokeBatchID)
+            , fBatchID(batchID)
             , fDrawBounds(SkIRect::MakeWH(drawBounds.width(), drawBounds.height())) {
     }
 
-    const FillBatchID fFillBatchID;
-    const StrokeBatchID fStrokeBatchID;
+    const FillBatchID fBatchID;
     const SkIRect fDrawBounds;
 };
 
@@ -142,10 +132,8 @@
 
 static int inst_buffer_count(const GrCCPerFlushResourceSpecs& specs) {
     return specs.fNumCachedPaths +
-           // Copies get two instances per draw: 1 copy + 1 draw.
-           (specs.fNumCopiedPaths[kFillIdx] + specs.fNumCopiedPaths[kStrokeIdx]) * 2 +
-           specs.fNumRenderedPaths[kFillIdx] + specs.fNumRenderedPaths[kStrokeIdx];
-           // No clips in instance buffers.
+           specs.fNumCopiedPaths*2 +  // 1 copy + 1 draw.
+           specs.fNumRenderedPaths;
 }
 
 GrCCPerFlushResources::GrCCPerFlushResources(GrOnFlushResourceProvider* onFlushRP,
@@ -153,15 +141,8 @@
           // 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(SkTMax(specs.fRenderedPathStats[kFillIdx].fMaxPointsPerPath,
-                                    specs.fRenderedPathStats[kStrokeIdx].fMaxPointsPerPath) + 1)
-        , fFiller(specs.fNumRenderedPaths[kFillIdx] + specs.fNumClipPaths,
-                  specs.fRenderedPathStats[kFillIdx].fNumTotalSkPoints,
-                  specs.fRenderedPathStats[kFillIdx].fNumTotalSkVerbs,
-                  specs.fRenderedPathStats[kFillIdx].fNumTotalConicWeights)
-        , fStroker(specs.fNumRenderedPaths[kStrokeIdx],
-                   specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkPoints,
-                   specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkVerbs)
+        : 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())
@@ -170,8 +151,7 @@
         , fInstanceBuffer(onFlushRP->makeBuffer(kVertex_GrBufferType,
                                                 inst_buffer_count(specs) * sizeof(PathInstance)))
         , fNextCopyInstanceIdx(0)
-        , fNextPathInstanceIdx(specs.fNumCopiedPaths[kFillIdx] +
-                               specs.fNumCopiedPaths[kStrokeIdx]) {
+        , fNextPathInstanceIdx(specs.fNumCopiedPaths) {
     if (!fIndexBuffer) {
         SkDebugf("WARNING: failed to allocate CCPR index buffer. No paths will be drawn.\n");
         return;
@@ -186,8 +166,7 @@
     }
     fPathInstanceData = static_cast<PathInstance*>(fInstanceBuffer->map());
     SkASSERT(fPathInstanceData);
-    SkDEBUGCODE(fEndCopyInstance =
-                        specs.fNumCopiedPaths[kFillIdx] + specs.fNumCopiedPaths[kStrokeIdx]);
+    SkDEBUGCODE(fEndCopyInstance = specs.fNumCopiedPaths);
     SkDEBUGCODE(fEndPathInstance = inst_buffer_count(specs));
 }
 
@@ -201,7 +180,7 @@
     if (GrCCAtlas* retiredAtlas = fCopyAtlasStack.addRect(entry.devIBounds(), newAtlasOffset)) {
         // We did not fit in the previous copy atlas and it was retired. We will render the copies
         // up until fNextCopyInstanceIdx into the retired atlas during finalize().
-        retiredAtlas->setFillBatchID(fNextCopyInstanceIdx);
+        retiredAtlas->setUserBatchID(fNextCopyInstanceIdx);
     }
 
     fPathInstanceData[fNextCopyInstanceIdx++].set(entry, *newAtlasOffset, GrColor_WHITE, evenOdd);
@@ -258,29 +237,20 @@
                          bottomRightPts[1].y());
 }
 
-const GrCCAtlas* GrCCPerFlushResources::renderShapeInAtlas(
-        const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape, float strokeDevWidth,
-        SkRect* devBounds, SkRect* devBounds45, SkIRect* devIBounds, SkIVector* devToAtlasOffset) {
+const GrCCAtlas* GrCCPerFlushResources::renderPathInAtlas(const SkIRect& clipIBounds,
+                                                          const SkMatrix& m, const SkPath& path,
+                                                          SkRect* devBounds, SkRect* devBounds45,
+                                                          SkIRect* devIBounds,
+                                                          SkIVector* devToAtlasOffset) {
     SkASSERT(this->isMapped());
     SkASSERT(fNextPathInstanceIdx < fEndPathInstance);
 
-    SkPath path;
-    shape.asPath(&path);
     if (path.isEmpty()) {
         SkDEBUGCODE(--fEndPathInstance);
         return nullptr;
     }
-    transform_path_pts(m, path, fLocalDevPtsBuffer, devBounds, devBounds45);
 
-    const SkStrokeRec& stroke = shape.style().strokeRec();
-    if (!stroke.isFillStyle()) {
-        float r = SkStrokeRec::GetInflationRadius(stroke.getJoin(), stroke.getMiter(),
-                                                  stroke.getCap(), strokeDevWidth);
-        devBounds->outset(r, r);
-        // devBounds45 is in (| 1 -1 | * devCoords) space.
-        //                    | 1  1 |
-        devBounds45->outset(r*SK_ScalarSqrt2, r*SK_ScalarSqrt2);
-    }
+    transform_path_pts(m, path, fLocalDevPtsBuffer, devBounds, devBounds45);
     devBounds->roundOut(devIBounds);
 
     GrScissorTest scissorTest;
@@ -291,17 +261,8 @@
         return nullptr;  // Path was degenerate or clipped away.
     }
 
-    if (stroke.isFillStyle()) {
-        SkASSERT(0 == strokeDevWidth);
-        fFiller.parseDeviceSpaceFill(path, fLocalDevPtsBuffer.begin(), scissorTest,
-                                     clippedPathIBounds, *devToAtlasOffset);
-    } else {
-        // Stroke-and-fill is not yet supported.
-        SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle() || stroke.isHairlineStyle());
-        SkASSERT(!stroke.isHairlineStyle() || 1 == strokeDevWidth);
-        fStroker.parseDeviceSpaceStroke(path, fLocalDevPtsBuffer.begin(), stroke, strokeDevWidth,
-                                        scissorTest, clippedPathIBounds, *devToAtlasOffset);
-    }
+    fFiller.parseDeviceSpaceFill(path, fLocalDevPtsBuffer.begin(), scissorTest, clippedPathIBounds,
+                                 *devToAtlasOffset);
     return &fRenderedAtlasStack.current();
 }
 
@@ -345,8 +306,8 @@
         // 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().
-        retiredAtlas->setFillBatchID(fFiller.closeCurrentBatch());
-        retiredAtlas->setStrokeBatchID(fStroker.closeCurrentBatch());
+        FillBatchID batchID = fFiller.closeCurrentBatch();
+        retiredAtlas->setUserBatchID(batchID);
     }
     return true;
 }
@@ -362,26 +323,23 @@
     fPathInstanceData = nullptr;
 
     if (!fCopyAtlasStack.empty()) {
-        fCopyAtlasStack.current().setFillBatchID(fNextCopyInstanceIdx);
+        fCopyAtlasStack.current().setUserBatchID(fNextCopyInstanceIdx);
     }
     if (!fRenderedAtlasStack.empty()) {
-        fRenderedAtlasStack.current().setFillBatchID(fFiller.closeCurrentBatch());
-        fRenderedAtlasStack.current().setStrokeBatchID(fStroker.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 calls to fFiller/fStroker.closeCurrentBatch().)
+    // final call to fPathParser.closeCurrentBatch().)
     if (!fFiller.prepareToDraw(onFlushRP)) {
         return false;
     }
-    if (!fStroker.prepareToDraw(onFlushRP)) {
-        return false;
-    }
 
     // Draw the copies from the stashed atlas into 8-bit cached atlas(es).
     int baseCopyInstance = 0;
     for (GrCCAtlasStack::Iter atlas(fCopyAtlasStack); atlas.next();) {
-        int endCopyInstance = atlas->getFillBatchID();
+        int endCopyInstance = atlas->getUserBatchID();
         if (endCopyInstance <= baseCopyInstance) {
             SkASSERT(endCopyInstance == baseCopyInstance);
             continue;
@@ -409,8 +367,7 @@
 
         if (auto rtc = atlas->makeRenderTargetContext(onFlushRP, std::move(backingTexture))) {
             auto op = RenderAtlasOp::Make(rtc->surfPriv().getContext(), sk_ref_sp(this),
-                                          atlas->getFillBatchID(), atlas->getStrokeBatchID(),
-                                          atlas->drawBounds());
+                                          atlas->getUserBatchID(), atlas->drawBounds());
             rtc->addDrawOp(GrNoClip(), std::move(op));
             out->push_back(std::move(rtc));
         }
@@ -420,17 +377,8 @@
 }
 
 void GrCCPerFlushResourceSpecs::convertCopiesToRenders() {
-    for (int i = 0; i < 2; ++i) {
-        fNumRenderedPaths[i] += fNumCopiedPaths[i];
-        fNumCopiedPaths[i] = 0;
-
-        fRenderedPathStats[i].fMaxPointsPerPath =
-               SkTMax(fRenderedPathStats[i].fMaxPointsPerPath, fCopyPathStats[i].fMaxPointsPerPath);
-        fRenderedPathStats[i].fNumTotalSkPoints += fCopyPathStats[i].fNumTotalSkPoints;
-        fRenderedPathStats[i].fNumTotalSkVerbs += fCopyPathStats[i].fNumTotalSkVerbs;
-        fRenderedPathStats[i].fNumTotalConicWeights += fCopyPathStats[i].fNumTotalConicWeights;
-        fCopyPathStats[i] = GrCCRenderedPathStats();
-    }
+    fNumRenderedPaths += fNumCopiedPaths;
+    fNumCopiedPaths = 0;
 
     fRenderedAtlasSpecs.fApproxNumPixels += fCopyAtlasSpecs.fApproxNumPixels;
     fRenderedAtlasSpecs.fMinWidth =
@@ -438,4 +386,11 @@
     fRenderedAtlasSpecs.fMinHeight =
             SkTMax(fRenderedAtlasSpecs.fMinHeight, fCopyAtlasSpecs.fMinHeight);
     fCopyAtlasSpecs = GrCCAtlas::Specs();
+
+    fRenderedPathStats.fMaxPointsPerPath =
+            SkTMax(fRenderedPathStats.fMaxPointsPerPath, fCopyPathStats.fMaxPointsPerPath);
+    fRenderedPathStats.fNumTotalSkPoints += fCopyPathStats.fNumTotalSkPoints;
+    fRenderedPathStats.fNumTotalSkVerbs += fCopyPathStats.fNumTotalSkVerbs;
+    fRenderedPathStats.fNumTotalConicWeights += fCopyPathStats.fNumTotalConicWeights;
+    fCopyPathStats = GrCCFiller::PathStats();
 }
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h
index 132068f..3fa392e 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.h
+++ b/src/gpu/ccpr/GrCCPerFlushResources.h
@@ -11,47 +11,29 @@
 #include "GrNonAtomicRef.h"
 #include "ccpr/GrCCAtlas.h"
 #include "ccpr/GrCCFiller.h"
-#include "ccpr/GrCCStroker.h"
 #include "ccpr/GrCCPathProcessor.h"
 
 class GrCCPathCacheEntry;
 class GrOnFlushResourceProvider;
-class GrShape;
-
-/**
- * This struct counts values that help us preallocate buffers for rendered path geometry.
- */
-struct GrCCRenderedPathStats {
-    int fMaxPointsPerPath = 0;
-    int fNumTotalSkPoints = 0;
-    int fNumTotalSkVerbs = 0;
-    int fNumTotalConicWeights = 0;
-
-    void statPath(const SkPath&);
-};
 
 /**
  * This struct encapsulates the minimum and desired requirements for the GPU resources required by
  * CCPR in a given flush.
  */
 struct GrCCPerFlushResourceSpecs {
-    static constexpr int kFillIdx = 0;
-    static constexpr int kStrokeIdx = 1;
-
     int fNumCachedPaths = 0;
 
-    int fNumCopiedPaths[2] = {0, 0};
-    GrCCRenderedPathStats fCopyPathStats[2];
+    int fNumCopiedPaths = 0;
+    GrCCFiller::PathStats fCopyPathStats;
     GrCCAtlas::Specs fCopyAtlasSpecs;
 
-    int fNumRenderedPaths[2] = {0, 0};
+    int fNumRenderedPaths = 0;
     int fNumClipPaths = 0;
-    GrCCRenderedPathStats fRenderedPathStats[2];
+    GrCCFiller::PathStats fRenderedPathStats;
     GrCCAtlas::Specs fRenderedAtlasSpecs;
 
     bool isEmpty() const {
-        return 0 == fNumCachedPaths + fNumCopiedPaths[kFillIdx] + fNumCopiedPaths[kStrokeIdx] +
-                    fNumRenderedPaths[kFillIdx] + fNumRenderedPaths[kStrokeIdx] + fNumClipPaths;
+        return 0 == fNumCachedPaths + fNumCopiedPaths + fNumRenderedPaths + fNumClipPaths;
     }
     void convertCopiesToRenders();
 };
@@ -73,16 +55,12 @@
     GrCCAtlas* copyPathToCachedAtlas(const GrCCPathCacheEntry&, GrCCPathProcessor::DoEvenOddFill,
                                      SkIVector* newAtlasOffset);
 
-    // These two methods render a path into a temporary coverage count atlas. See
-    // GrCCPathProcessor::Instance for a description of the outputs. The returned atlases are
-    // "const" to prevent the caller from assigning a unique key.
-    //
-    // strokeDevWidth must be 0 for fills, 1 for hairlines, or the stroke width in device-space
-    // pixels for non-hairline strokes (implicitly requiring a rigid-body transform).
-    const GrCCAtlas* renderShapeInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const GrShape&,
-                                        float strokeDevWidth, SkRect* devBounds,
-                                        SkRect* devBounds45, SkIRect* devIBounds,
-                                        SkIVector* devToAtlasOffset);
+    // These two methods render a path into a temporary coverage count atlas. See GrCCPathParser for
+    // a description of the arguments. The returned atlases are "const" to prevent the caller from
+    // assigning a unique key.
+    const GrCCAtlas* renderPathInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const SkPath&,
+                                       SkRect* devBounds, SkRect* devBounds45, SkIRect* devIBounds,
+                                       SkIVector* devToAtlasOffset);
     const GrCCAtlas* renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds, const SkPath& devPath,
                                                   const SkIRect& devPathIBounds,
                                                   SkIVector* devToAtlasOffset);
@@ -108,7 +86,6 @@
 
     // Accessors used by draw calls, once the resources have been finalized.
     const GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; }
-    const GrCCStroker& stroker() const { SkASSERT(!this->isMapped()); return fStroker; }
     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(); }
@@ -136,7 +113,6 @@
 
     const SkAutoSTArray<32, SkPoint> fLocalDevPtsBuffer;
     GrCCFiller fFiller;
-    GrCCStroker fStroker;
     GrCCAtlasStack fCopyAtlasStack;
     GrCCAtlasStack fRenderedAtlasStack;
 
@@ -151,11 +127,4 @@
     SkDEBUGCODE(int fEndPathInstance);
 };
 
-inline void GrCCRenderedPathStats::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/GrCCStrokeGeometry.cpp b/src/gpu/ccpr/GrCCStrokeGeometry.cpp
deleted file mode 100644
index 3fcafec..0000000
--- a/src/gpu/ccpr/GrCCStrokeGeometry.cpp
+++ /dev/null
@@ -1,582 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrCCStrokeGeometry.h"
-
-#include "SkGeometry.h"
-#include "SkMathPriv.h"
-#include "SkNx.h"
-#include "SkStrokeRec.h"
-
-// This is the maximum distance in pixels that we can stray from the edge of a stroke when
-// converting it to flat line segments.
-static constexpr float kMaxErrorFromLinearization = 1/8.f;
-
-static inline float length(const Sk2f& n) {
-    Sk2f nn = n*n;
-    return SkScalarSqrt(nn[0] + nn[1]);
-}
-
-static inline Sk2f normalize(const Sk2f& v) {
-    Sk2f vv = v*v;
-    vv += SkNx_shuffle<1,0>(vv);
-    return v * vv.rsqrt();
-}
-
-static inline void transpose(const Sk2f& a, const Sk2f& b, Sk2f* X, Sk2f* Y) {
-    float transpose[4];
-    a.store(transpose);
-    b.store(transpose+2);
-    Sk2f::Load2(transpose, X, Y);
-}
-
-static inline void normalize2(const Sk2f& v0, const Sk2f& v1, SkPoint out[2]) {
-    Sk2f X, Y;
-    transpose(v0, v1, &X, &Y);
-    Sk2f invlength = (X*X + Y*Y).rsqrt();
-    Sk2f::Store2(out, Y * invlength, -X * invlength);
-}
-
-static inline float calc_curvature_costheta(const Sk2f& leftTan, const Sk2f& rightTan) {
-    Sk2f X, Y;
-    transpose(leftTan, rightTan, &X, &Y);
-    Sk2f invlength = (X*X + Y*Y).rsqrt();
-    Sk2f dotprod = leftTan * rightTan;
-    return (dotprod[0] + dotprod[1]) * invlength[0] * invlength[1];
-}
-
-static GrCCStrokeGeometry::Verb join_verb_from_join(SkPaint::Join join) {
-    using Verb = GrCCStrokeGeometry::Verb;
-    switch (join) {
-        case SkPaint::kBevel_Join:
-            return Verb::kBevelJoin;
-        case SkPaint::kMiter_Join:
-            return Verb::kMiterJoin;
-        case SkPaint::kRound_Join:
-            return Verb::kRoundJoin;
-    }
-    SK_ABORT("Invalid SkPaint::Join.");
-    return Verb::kBevelJoin;
-}
-
-void GrCCStrokeGeometry::beginPath(const SkStrokeRec& stroke, float strokeDevWidth,
-                                   InstanceTallies* tallies) {
-    SkASSERT(!fInsideContour);
-    // Client should have already converted the stroke to device space (i.e. width=1 for hairline).
-    SkASSERT(strokeDevWidth > 0);
-
-    fCurrStrokeRadius = strokeDevWidth/2;
-    fCurrStrokeJoinVerb = join_verb_from_join(stroke.getJoin());
-    fCurrStrokeCapType = stroke.getCap();
-    fCurrStrokeTallies = tallies;
-
-    if (Verb::kMiterJoin == fCurrStrokeJoinVerb) {
-        // We implement miters by placing a triangle-shaped cap on top of a bevel join. Convert the
-        // "miter limit" to how tall that triangle cap can be.
-        float m = stroke.getMiter();
-        fMiterMaxCapHeightOverWidth = .5f * SkScalarSqrt(m*m - 1);
-    }
-
-    // Find the angle of curvature where the arc height above a simple line from point A to point B
-    // is equal to kMaxErrorFromLinearization.
-    float r = SkTMax(1 - kMaxErrorFromLinearization / fCurrStrokeRadius, 0.f);
-    fMaxCurvatureCosTheta = 2*r*r - 1;
-
-    fCurrContourFirstPtIdx = -1;
-    fCurrContourFirstNormalIdx = -1;
-
-    fVerbs.push_back(Verb::kBeginPath);
-}
-
-void GrCCStrokeGeometry::moveTo(SkPoint pt) {
-    SkASSERT(!fInsideContour);
-    fCurrContourFirstPtIdx = fPoints.count();
-    fCurrContourFirstNormalIdx = fNormals.count();
-    fPoints.push_back(pt);
-    SkDEBUGCODE(fInsideContour = true);
-}
-
-void GrCCStrokeGeometry::lineTo(SkPoint pt) {
-    SkASSERT(fInsideContour);
-    this->lineTo(fCurrStrokeJoinVerb, pt);
-}
-
-void GrCCStrokeGeometry::lineTo(Verb leftJoinVerb, SkPoint pt) {
-    Sk2f tan = Sk2f::Load(&pt) - Sk2f::Load(&fPoints.back());
-    if ((tan == 0).allTrue()) {
-        return;
-    }
-
-    tan = normalize(tan);
-    SkVector n = SkVector::Make(tan[1], -tan[0]);
-
-    this->recordLeftJoinIfNotEmpty(leftJoinVerb, n);
-    fNormals.push_back(n);
-
-    this->recordStroke(Verb::kLinearStroke, 0);
-    fPoints.push_back(pt);
-}
-
-void GrCCStrokeGeometry::quadraticTo(const SkPoint P[3]) {
-    SkASSERT(fInsideContour);
-    this->quadraticTo(fCurrStrokeJoinVerb, P, SkFindQuadMaxCurvature(P));
-}
-
-// Wang's formula for quadratics (1985) gives us the number of evenly spaced (in the parametric
-// sense) line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization"
-// from the actual curve.
-static inline float wangs_formula_quadratic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2) {
-    static constexpr float k = 2 / (8 * kMaxErrorFromLinearization);
-    float f = SkScalarSqrt(k * length(p2 - p1*2 + p0));
-    return SkScalarCeilToInt(f);
-}
-
-void GrCCStrokeGeometry::quadraticTo(Verb leftJoinVerb, const SkPoint P[3], float maxCurvatureT) {
-    Sk2f p0 = Sk2f::Load(P);
-    Sk2f p1 = Sk2f::Load(P+1);
-    Sk2f p2 = Sk2f::Load(P+2);
-
-    Sk2f tan0 = p1 - p0;
-    Sk2f tan1 = p2 - p1;
-
-    // Snap to a "lineTo" if the control point is so close to an endpoint that FP error will become
-    // an issue.
-    if ((tan0.abs() < SK_ScalarNearlyZero).allTrue() ||  // p0 ~= p1
-        (tan1.abs() < SK_ScalarNearlyZero).allTrue()) {  // p1 ~= p2
-        this->lineTo(leftJoinVerb, P[2]);
-        return;
-    }
-
-    SkPoint normals[2];
-    normalize2(tan0, tan1, normals);
-
-    // Decide how many flat line segments to chop the curve into.
-    int numSegments = wangs_formula_quadratic(p0, p1, p2);
-    if (numSegments <= 1) {
-        this->rotateTo(leftJoinVerb, normals[0]);
-        this->lineTo(Verb::kInternalRoundJoin, P[2]);
-        this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
-        return;
-    }
-
-    // At + B gives a vector tangent to the quadratic.
-    Sk2f A = p0 - p1*2 + p2;
-    Sk2f B = p1 - p0;
-
-    // Find a line segment that crosses max curvature.
-    float segmentLength = SkScalarInvert(numSegments);
-    float leftT = maxCurvatureT - segmentLength/2;
-    float rightT = maxCurvatureT + segmentLength/2;
-    Sk2f leftTan, rightTan;
-    if (leftT <= 0) {
-        leftT = 0;
-        leftTan = tan0;
-        rightT = segmentLength;
-        rightTan = A*rightT + B;
-    } else if (rightT >= 1) {
-        leftT = 1 - segmentLength;
-        leftTan = A*leftT + B;
-        rightT = 1;
-        rightTan = tan1;
-    } else {
-        leftTan = A*leftT + B;
-        rightTan = A*rightT + B;
-    }
-
-    // Check if curvature is too strong for a triangle strip on the line segment that crosses max
-    // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins.
-    //
-    // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We
-    // would benefit significantly from a quick reject that detects curves that don't need special
-    // treatment for strong curvature.
-    bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta;
-    if (isCurvatureTooStrong) {
-        SkPoint ptsBuffer[5];
-        const SkPoint* currQuadratic = P;
-
-        if (leftT > 0) {
-            SkChopQuadAt(currQuadratic, ptsBuffer, leftT);
-            this->quadraticTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1);
-            if (rightT < 1) {
-                rightT = (rightT - leftT) / (1 - leftT);
-            }
-            currQuadratic = ptsBuffer + 2;
-        } else {
-            this->rotateTo(leftJoinVerb, normals[0]);
-        }
-
-        if (rightT < 1) {
-            SkChopQuadAt(currQuadratic, ptsBuffer, rightT);
-            this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[2]);
-            this->quadraticTo(Verb::kInternalRoundJoin, ptsBuffer + 2, /*maxCurvatureT=*/0);
-        } else {
-            this->lineTo(Verb::kInternalRoundJoin, currQuadratic[2]);
-            this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
-        }
-        return;
-    }
-
-    this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]);
-    fNormals.push_back_n(2, normals);
-
-    this->recordStroke(Verb::kQuadraticStroke, SkNextLog2(numSegments));
-    p1.store(&fPoints.push_back());
-    p2.store(&fPoints.push_back());
-}
-
-void GrCCStrokeGeometry::cubicTo(const SkPoint P[4]) {
-    SkASSERT(fInsideContour);
-    float roots[3];
-    int numRoots = SkFindCubicMaxCurvature(P, roots);
-    this->cubicTo(fCurrStrokeJoinVerb, P,
-                  numRoots > 0 ? roots[numRoots/2] : 0,
-                  numRoots > 1 ? roots[0] : kLeftMaxCurvatureNone,
-                  numRoots > 2 ? roots[2] : kRightMaxCurvatureNone);
-}
-
-// Wang's formula for cubics (1985) gives us the number of evenly spaced (in the parametric sense)
-// line segments that are guaranteed to be within a distance of "kMaxErrorFromLinearization"
-// from the actual curve.
-static inline float wangs_formula_cubic(const Sk2f& p0, const Sk2f& p1, const Sk2f& p2,
-                                        const Sk2f& p3) {
-    static constexpr float k = (3 * 2) / (8 * kMaxErrorFromLinearization);
-    float f = SkScalarSqrt(k * length(Sk2f::Max((p2 - p1*2 + p0).abs(),
-                                                (p3 - p2*2 + p1).abs())));
-    return SkScalarCeilToInt(f);
-}
-
-void GrCCStrokeGeometry::cubicTo(Verb leftJoinVerb, const SkPoint P[4], float maxCurvatureT,
-                                 float leftMaxCurvatureT, float rightMaxCurvatureT) {
-    Sk2f p0 = Sk2f::Load(P);
-    Sk2f p1 = Sk2f::Load(P+1);
-    Sk2f p2 = Sk2f::Load(P+2);
-    Sk2f p3 = Sk2f::Load(P+3);
-
-    Sk2f tan0 = p1 - p0;
-    Sk2f tan1 = p3 - p2;
-
-    // Snap control points to endpoints if they are so close that FP error will become an issue.
-    if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) {  // p0 ~= p1
-        p1 = p0;
-        tan0 = p2 - p0;
-        if ((tan0.abs() < SK_ScalarNearlyZero).allTrue()) {  // p0 ~= p1 ~= p2
-            this->lineTo(leftJoinVerb, P[3]);
-            return;
-        }
-    }
-    if ((tan1.abs() < SK_ScalarNearlyZero).allTrue()) {  // p2 ~= p3
-        p2 = p3;
-        tan1 = p3 - p1;
-        if ((tan1.abs() < SK_ScalarNearlyZero).allTrue() ||  // p1 ~= p2 ~= p3
-            (p0 == p1).allTrue()) {  // p0 ~= p1 AND p2 ~= p3
-            this->lineTo(leftJoinVerb, P[3]);
-            return;
-        }
-    }
-
-    SkPoint normals[2];
-    normalize2(tan0, tan1, normals);
-
-    // Decide how many flat line segments to chop the curve into.
-    int numSegments = wangs_formula_cubic(p0, p1, p2, p3);
-    if (numSegments <= 1) {
-        this->rotateTo(leftJoinVerb, normals[0]);
-        this->lineTo(leftJoinVerb, P[3]);
-        this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
-        return;
-    }
-
-    // At^2 + Bt + C gives a vector tangent to the cubic. (More specifically, it's the derivative
-    // minus an irrelevant scale by 3, since all we care about is the direction.)
-    Sk2f A = p3 + (p1 - p2)*3 - p0;
-    Sk2f B = (p0 - p1*2 + p2)*2;
-    Sk2f C = p1 - p0;
-
-    // Find a line segment that crosses max curvature.
-    float segmentLength = SkScalarInvert(numSegments);
-    float leftT = maxCurvatureT - segmentLength/2;
-    float rightT = maxCurvatureT + segmentLength/2;
-    Sk2f leftTan, rightTan;
-    if (leftT <= 0) {
-        leftT = 0;
-        leftTan = tan0;
-        rightT = segmentLength;
-        rightTan = A*rightT*rightT + B*rightT + C;
-    } else if (rightT >= 1) {
-        leftT = 1 - segmentLength;
-        leftTan = A*leftT*leftT + B*leftT + C;
-        rightT = 1;
-        rightTan = tan1;
-    } else {
-        leftTan = A*leftT*leftT + B*leftT + C;
-        rightTan = A*rightT*rightT + B*rightT + C;
-    }
-
-    // Check if curvature is too strong for a triangle strip on the line segment that crosses max
-    // curvature. If it is, we will chop and convert the segment to a "lineTo" with round joins.
-    //
-    // FIXME: This is quite costly and the vast majority of curves only have moderate curvature. We
-    // would benefit significantly from a quick reject that detects curves that don't need special
-    // treatment for strong curvature.
-    bool isCurvatureTooStrong = calc_curvature_costheta(leftTan, rightTan) < fMaxCurvatureCosTheta;
-    if (isCurvatureTooStrong) {
-        SkPoint ptsBuffer[7];
-        p0.store(ptsBuffer);
-        p1.store(ptsBuffer + 1);
-        p2.store(ptsBuffer + 2);
-        p3.store(ptsBuffer + 3);
-        const SkPoint* currCubic = ptsBuffer;
-
-        if (leftT > 0) {
-            SkChopCubicAt(currCubic, ptsBuffer, leftT);
-            this->cubicTo(leftJoinVerb, ptsBuffer, /*maxCurvatureT=*/1,
-                          (kLeftMaxCurvatureNone != leftMaxCurvatureT)
-                                  ? leftMaxCurvatureT/leftT : kLeftMaxCurvatureNone,
-                          kRightMaxCurvatureNone);
-            if (rightT < 1) {
-                rightT = (rightT - leftT) / (1 - leftT);
-            }
-            if (rightMaxCurvatureT < 1 && kRightMaxCurvatureNone != rightMaxCurvatureT) {
-                rightMaxCurvatureT = (rightMaxCurvatureT - leftT) / (1 - leftT);
-            }
-            currCubic = ptsBuffer + 3;
-        } else {
-            this->rotateTo(leftJoinVerb, normals[0]);
-        }
-
-        if (rightT < 1) {
-            SkChopCubicAt(currCubic, ptsBuffer, rightT);
-            this->lineTo(Verb::kInternalRoundJoin, ptsBuffer[3]);
-            currCubic = ptsBuffer + 3;
-            this->cubicTo(Verb::kInternalRoundJoin, currCubic, /*maxCurvatureT=*/0,
-                          kLeftMaxCurvatureNone, kRightMaxCurvatureNone);
-        } else {
-            this->lineTo(Verb::kInternalRoundJoin, currCubic[3]);
-            this->rotateTo(Verb::kInternalRoundJoin, normals[1]);
-        }
-        return;
-    }
-
-    // Recurse and check the other two points of max curvature, if any.
-    if (kRightMaxCurvatureNone != rightMaxCurvatureT) {
-        this->cubicTo(leftJoinVerb, P, rightMaxCurvatureT, leftMaxCurvatureT,
-                      kRightMaxCurvatureNone);
-        return;
-    }
-    if (kLeftMaxCurvatureNone != leftMaxCurvatureT) {
-        SkASSERT(kRightMaxCurvatureNone == rightMaxCurvatureT);
-        this->cubicTo(leftJoinVerb, P, leftMaxCurvatureT, kLeftMaxCurvatureNone,
-                      kRightMaxCurvatureNone);
-        return;
-    }
-
-    this->recordLeftJoinIfNotEmpty(leftJoinVerb, normals[0]);
-    fNormals.push_back_n(2, normals);
-
-    this->recordStroke(Verb::kCubicStroke, SkNextLog2(numSegments));
-    p1.store(&fPoints.push_back());
-    p2.store(&fPoints.push_back());
-    p3.store(&fPoints.push_back());
-}
-
-void GrCCStrokeGeometry::recordStroke(Verb verb, int numSegmentsLog2) {
-    SkASSERT(Verb::kLinearStroke != verb || 0 == numSegmentsLog2);
-    SkASSERT(numSegmentsLog2 <= kMaxNumLinearSegmentsLog2);
-    fVerbs.push_back(verb);
-    if (Verb::kLinearStroke != verb) {
-        SkASSERT(numSegmentsLog2 > 0);
-        fParams.push_back().fNumLinearSegmentsLog2 = numSegmentsLog2;
-    }
-    ++fCurrStrokeTallies->fStrokes[numSegmentsLog2];
-}
-
-void GrCCStrokeGeometry::rotateTo(Verb leftJoinVerb, SkVector normal) {
-    this->recordLeftJoinIfNotEmpty(leftJoinVerb, normal);
-    fNormals.push_back(normal);
-}
-
-void GrCCStrokeGeometry::recordLeftJoinIfNotEmpty(Verb joinVerb, SkVector nextNormal) {
-    if (fNormals.count() <= fCurrContourFirstNormalIdx) {
-        // The contour is empty. Nothing to join with.
-        SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
-        return;
-    }
-
-    if (Verb::kBevelJoin == joinVerb) {
-        this->recordBevelJoin(Verb::kBevelJoin);
-        return;
-    }
-
-    Sk2f n0 = Sk2f::Load(&fNormals.back());
-    Sk2f n1 = Sk2f::Load(&nextNormal);
-    Sk2f base = n1 - n0;
-    if ((base.abs() * fCurrStrokeRadius < kMaxErrorFromLinearization).allTrue()) {
-        // Treat any join as a bevel when the outside corners of the two adjoining strokes are
-        // close enough to each other. This is important because "miterCapHeightOverWidth" becomes
-        // unstable when n0 and n1 are nearly equal.
-        this->recordBevelJoin(joinVerb);
-        return;
-    }
-
-    // We implement miters and round joins by placing a triangle-shaped cap on top of a bevel join.
-    // (For round joins this triangle cap comprises the conic control points.) Find how tall to make
-    // this triangle cap, relative to its width.
-    //
-    // NOTE: This value would be infinite at 180 degrees, but we clamp miterCapHeightOverWidth at
-    // near-infinity. 180-degree round joins still look perfectly acceptable like this (though
-    // technically not pure arcs).
-    Sk2f cross = base * SkNx_shuffle<1,0>(n0);
-    Sk2f dot = base * n0;
-    float miterCapHeight = SkScalarAbs(dot[0] + dot[1]);
-    float miterCapWidth = SkScalarAbs(cross[0] - cross[1]) * 2;
-
-    if (Verb::kMiterJoin == joinVerb) {
-        if (miterCapHeight > fMiterMaxCapHeightOverWidth * miterCapWidth) {
-            // This join is tighter than the miter limit. Treat it as a bevel.
-            this->recordBevelJoin(Verb::kMiterJoin);
-            return;
-        }
-        this->recordMiterJoin(miterCapHeight / miterCapWidth);
-        return;
-    }
-
-    SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb);
-
-    // Conic arcs become unstable when they approach 180 degrees. When the conic control point
-    // begins shooting off to infinity (i.e., height/width > 32), split the conic into two.
-    static constexpr float kAlmost180Degrees = 32;
-    if (miterCapHeight > kAlmost180Degrees * miterCapWidth) {
-        Sk2f bisect = normalize(n0 - n1);
-        this->rotateTo(joinVerb, SkVector::Make(-bisect[1], bisect[0]));
-        this->recordLeftJoinIfNotEmpty(joinVerb, nextNormal);
-        return;
-    }
-
-    float miterCapHeightOverWidth = miterCapHeight / miterCapWidth;
-
-    // Find the heights of this round join's conic control point as well as the arc itself.
-    Sk2f X, Y;
-    transpose(base * base, n0 * n1, &X, &Y);
-    Sk2f r = Sk2f::Max(X + Y + Sk2f(0, 1), 0.f).sqrt();
-    Sk2f heights = SkNx_fma(r, Sk2f(miterCapHeightOverWidth, -SK_ScalarRoot2Over2), Sk2f(0, 1));
-    float controlPointHeight = SkScalarAbs(heights[0]);
-    float curveHeight = heights[1];
-    if (curveHeight * fCurrStrokeRadius < kMaxErrorFromLinearization) {
-        // Treat round joins as bevels when their curvature is nearly flat.
-        this->recordBevelJoin(joinVerb);
-        return;
-    }
-
-    float w = curveHeight / (controlPointHeight - curveHeight);
-    this->recordRoundJoin(joinVerb, miterCapHeightOverWidth, w);
-}
-
-void GrCCStrokeGeometry::recordBevelJoin(Verb originalJoinVerb) {
-    if (!IsInternalJoinVerb(originalJoinVerb)) {
-        fVerbs.push_back(Verb::kBevelJoin);
-        ++fCurrStrokeTallies->fTriangles;
-    } else {
-        fVerbs.push_back(Verb::kInternalBevelJoin);
-        fCurrStrokeTallies->fTriangles += 2;
-    }
-}
-
-void GrCCStrokeGeometry::recordMiterJoin(float miterCapHeightOverWidth) {
-    fVerbs.push_back(Verb::kMiterJoin);
-    fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth;
-    fCurrStrokeTallies->fTriangles += 2;
-}
-
-void GrCCStrokeGeometry::recordRoundJoin(Verb joinVerb, float miterCapHeightOverWidth,
-                                         float conicWeight) {
-    fVerbs.push_back(joinVerb);
-    fParams.push_back().fConicWeight = conicWeight;
-    fParams.push_back().fMiterCapHeightOverWidth = miterCapHeightOverWidth;
-    if (Verb::kRoundJoin == joinVerb) {
-        ++fCurrStrokeTallies->fTriangles;
-        ++fCurrStrokeTallies->fConics;
-    } else {
-        SkASSERT(Verb::kInternalRoundJoin == joinVerb);
-        fCurrStrokeTallies->fTriangles += 2;
-        fCurrStrokeTallies->fConics += 2;
-    }
-}
-
-void GrCCStrokeGeometry::closeContour() {
-    SkASSERT(fInsideContour);
-    SkASSERT(fPoints.count() > fCurrContourFirstPtIdx);
-    if (fPoints.back() != fPoints[fCurrContourFirstPtIdx]) {
-        // Draw a line back to the beginning.
-        this->lineTo(fCurrStrokeJoinVerb, fPoints[fCurrContourFirstPtIdx]);
-    }
-    if (fNormals.count() > fCurrContourFirstNormalIdx) {
-        // Join the first and last lines.
-        this->rotateTo(fCurrStrokeJoinVerb,fNormals[fCurrContourFirstNormalIdx]);
-    } else {
-        // This contour is empty. Add a bogus normal since the iterator always expects one.
-        SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
-        fNormals.push_back({0, 0});
-    }
-    fVerbs.push_back(Verb::kEndContour);
-    SkDEBUGCODE(fInsideContour = false);
-}
-
-void GrCCStrokeGeometry::capContourAndExit() {
-    SkASSERT(fInsideContour);
-    if (fCurrContourFirstNormalIdx >= fNormals.count()) {
-        // This contour is empty. Add a normal in the direction that caps orient on empty geometry.
-        SkASSERT(fNormals.count() == fCurrContourFirstNormalIdx);
-        fNormals.push_back({1, 0});
-    }
-
-    this->recordCapsIfAny();
-    fVerbs.push_back(Verb::kEndContour);
-
-    SkDEBUGCODE(fInsideContour = false);
-}
-
-void GrCCStrokeGeometry::recordCapsIfAny() {
-    SkASSERT(fInsideContour);
-    SkASSERT(fCurrContourFirstNormalIdx < fNormals.count());
-
-    if (SkPaint::kButt_Cap == fCurrStrokeCapType) {
-        return;
-    }
-
-    Verb capVerb;
-    if (SkPaint::kSquare_Cap == fCurrStrokeCapType) {
-        if (fCurrStrokeRadius * SK_ScalarRoot2Over2 < kMaxErrorFromLinearization) {
-            return;
-        }
-        capVerb = Verb::kSquareCap;
-        fCurrStrokeTallies->fStrokes[0] += 2;
-    } else {
-        SkASSERT(SkPaint::kRound_Cap == fCurrStrokeCapType);
-        if (fCurrStrokeRadius < kMaxErrorFromLinearization) {
-            return;
-        }
-        capVerb = Verb::kRoundCap;
-        fCurrStrokeTallies->fTriangles += 2;
-        fCurrStrokeTallies->fConics += 4;
-    }
-
-    fVerbs.push_back(capVerb);
-    fVerbs.push_back(Verb::kEndContour);
-
-    fVerbs.push_back(capVerb);
-
-    // Reserve the space first, since push_back() takes the point by reference and might
-    // invalidate the reference if the array grows.
-    fPoints.reserve(fPoints.count() + 1);
-    fPoints.push_back(fPoints[fCurrContourFirstPtIdx]);
-
-    // Reserve the space first, since push_back() takes the normal by reference and might
-    // invalidate the reference if the array grows. (Although in this case we should be fine
-    // since there is a negate operator.)
-    fNormals.reserve(fNormals.count() + 1);
-    fNormals.push_back(-fNormals[fCurrContourFirstNormalIdx]);
-}
diff --git a/src/gpu/ccpr/GrCCStrokeGeometry.h b/src/gpu/ccpr/GrCCStrokeGeometry.h
deleted file mode 100644
index 56871f2..0000000
--- a/src/gpu/ccpr/GrCCStrokeGeometry.h
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrGrCCStrokeGeometry_DEFINED
-#define GrGrCCStrokeGeometry_DEFINED
-
-#include "SkPaint.h"
-#include "SkPoint.h"
-#include "SkTArray.h"
-
-class SkStrokeRec;
-
-/**
- * This class converts device-space stroked paths into a set of independent strokes, joins, and caps
- * that map directly to coverage-counted GPU instances. Non-hairline strokes can only be drawn with
- * rigid body transforms; we don't yet support skewing the stroke lines themselves.
- */
-class GrCCStrokeGeometry {
-public:
-    static constexpr int kMaxNumLinearSegmentsLog2 = 15;
-
-    GrCCStrokeGeometry(int numSkPoints = 0, int numSkVerbs = 0)
-            : fVerbs(numSkVerbs * 5/2)  // Reserve for a 2.5x expansion in verbs. (Joins get their
-                                        // own separate verb in our representation.)
-            , fParams(numSkVerbs * 3)  // Somewhere around 1-2 params per verb.
-            , fPoints(numSkPoints * 5/4)  // Reserve for a 1.25x expansion in points and normals.
-            , fNormals(numSkPoints * 5/4) {}
-
-    // A string of verbs and their corresponding, params, points, and normals are a compact
-    // representation of what will eventually be independent instances in GPU buffers. When added
-    // up, the combined coverage of all these instances will make complete stroked paths.
-    enum class Verb : uint8_t {
-        kBeginPath,  // Instructs the iterator to advance its stroke width, atlas offset, etc.
-
-        // Independent strokes of a single line or curve, with (antialiased) butt caps on the ends.
-        kLinearStroke,
-        kQuadraticStroke,
-        kCubicStroke,
-
-        // Joins are a triangles that connect the outer corners of two adjoining strokes. Miters
-        // have an additional triangle cap on top of the bevel, and round joins have an arc on top.
-        kBevelJoin,
-        kMiterJoin,
-        kRoundJoin,
-
-        // We use internal joins when we have to internally break up a stroke because its curvature
-        // is too strong for a triangle strip. They are coverage-counted, self-intersecting
-        // quadrilaterals that tie the four corners of two adjoining strokes together a like a
-        // shoelace. (Coverage is negative on the inside half.) We place an arc on both ends of an
-        // internal round join.
-        kInternalBevelJoin,
-        kInternalRoundJoin,
-
-        kSquareCap,
-        kRoundCap,
-
-        kEndContour  // Instructs the iterator to advance its internal point and normal ptrs.
-    };
-    static bool IsInternalJoinVerb(Verb verb);
-
-    // Some verbs require additional parameters(s).
-    union Parameter {
-        // For cubic and quadratic strokes: How many flat line segments to chop the curve into?
-        int fNumLinearSegmentsLog2;
-        // For miter and round joins: How tall should the triangle cap be on top of the join?
-        // (This triangle is the conic control points for a round join.)
-        float fMiterCapHeightOverWidth;
-        float fConicWeight;  // Round joins only.
-    };
-
-    const SkTArray<Verb, true>& verbs() const { SkASSERT(!fInsideContour); return fVerbs; }
-    const SkTArray<Parameter, true>& params() const { SkASSERT(!fInsideContour); return fParams; }
-    const SkTArray<SkPoint, true>& points() const { SkASSERT(!fInsideContour); return fPoints; }
-    const SkTArray<SkVector, true>& normals() const { SkASSERT(!fInsideContour); return fNormals; }
-
-    // These track the numbers of instances required to draw all the recorded strokes.
-    struct InstanceTallies {
-        int fStrokes[kMaxNumLinearSegmentsLog2 + 1];
-        int fTriangles;
-        int fConics;
-
-        InstanceTallies operator+(const InstanceTallies&) const;
-    };
-
-    void beginPath(const SkStrokeRec&, float strokeDevWidth, InstanceTallies*);
-    void moveTo(SkPoint);
-    void lineTo(SkPoint);
-    void quadraticTo(const SkPoint[3]);
-    void cubicTo(const SkPoint[4]);
-    void closeContour();  // Connect back to the first point in the contour and exit.
-    void capContourAndExit();  // Add endcaps (if any) and exit the contour.
-
-private:
-    void lineTo(Verb leftJoinVerb, SkPoint);
-    void quadraticTo(Verb leftJoinVerb, const SkPoint[3], float maxCurvatureT);
-
-    static constexpr float kLeftMaxCurvatureNone = 1;
-    static constexpr float kRightMaxCurvatureNone = 0;
-    void cubicTo(Verb leftJoinVerb, const SkPoint[4], float maxCurvatureT, float leftMaxCurvatureT,
-                 float rightMaxCurvatureT);
-
-    // Pushes a new normal to fNormals and records a join, without changing the current position.
-    void rotateTo(Verb leftJoinVerb, SkVector normal);
-
-    // Records a stroke in fElememts.
-    void recordStroke(Verb, int numSegmentsLog2);
-
-    // Records a join in fElememts with the previous stroke, if the cuurent contour is not empty.
-    void recordLeftJoinIfNotEmpty(Verb joinType, SkVector nextNormal);
-    void recordBevelJoin(Verb originalJoinVerb);
-    void recordMiterJoin(float miterCapHeightOverWidth);
-    void recordRoundJoin(Verb roundJoinVerb, float miterCapHeightOverWidth, float conicWeight);
-
-    void recordCapsIfAny();
-
-    float fCurrStrokeRadius;
-    Verb fCurrStrokeJoinVerb;
-    SkPaint::Cap fCurrStrokeCapType;
-    InstanceTallies* fCurrStrokeTallies = nullptr;
-
-    // We implement miters by placing a triangle-shaped cap on top of a bevel join. This field tells
-    // us what the miter limit is, restated in terms of how tall that triangle cap can be.
-    float fMiterMaxCapHeightOverWidth;
-
-    // Any curvature on the original curve gets magnified on the outer edge of the stroke,
-    // proportional to how thick the stroke radius is. This field tells us the maximum curvature we
-    // can tolerate using the current stroke radius, before linearization artifacts begin to appear
-    // on the outer edge.
-    //
-    // (Curvature this strong is quite rare in practice, but when it does happen, we decompose the
-    // section with strong curvature into lineTo's with round joins in between.)
-    float fMaxCurvatureCosTheta;
-
-    int fCurrContourFirstPtIdx;
-    int fCurrContourFirstNormalIdx;
-
-    SkDEBUGCODE(bool fInsideContour = false);
-
-    SkSTArray<128, Verb, true> fVerbs;
-    SkSTArray<128, Parameter, true> fParams;
-    SkSTArray<128, SkPoint, true> fPoints;
-    SkSTArray<128, SkVector, true> fNormals;
-};
-
-inline GrCCStrokeGeometry::InstanceTallies GrCCStrokeGeometry::InstanceTallies::operator+(
-        const InstanceTallies& t) const {
-    InstanceTallies ret;
-    for (int i = 0; i <= kMaxNumLinearSegmentsLog2; ++i) {
-        ret.fStrokes[i] = fStrokes[i] + t.fStrokes[i];
-    }
-    ret.fTriangles = fTriangles + t.fTriangles;
-    ret.fConics = fConics + t.fConics;
-    return ret;
-}
-
-inline bool GrCCStrokeGeometry::IsInternalJoinVerb(Verb verb) {
-    switch (verb) {
-        case Verb::kInternalBevelJoin:
-        case Verb::kInternalRoundJoin:
-            return true;
-        case Verb::kBeginPath:
-        case Verb::kLinearStroke:
-        case Verb::kQuadraticStroke:
-        case Verb::kCubicStroke:
-        case Verb::kBevelJoin:
-        case Verb::kMiterJoin:
-        case Verb::kRoundJoin:
-        case Verb::kSquareCap:
-        case Verb::kRoundCap:
-        case Verb::kEndContour:
-            return false;
-    }
-    SK_ABORT("Invalid GrCCStrokeGeometry::Verb.");
-    return false;
-}
-#endif
diff --git a/src/gpu/ccpr/GrCCStroker.cpp b/src/gpu/ccpr/GrCCStroker.cpp
deleted file mode 100644
index ab3906c..0000000
--- a/src/gpu/ccpr/GrCCStroker.cpp
+++ /dev/null
@@ -1,832 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrCCStroker.h"
-
-#include "GrGpuCommandBuffer.h"
-#include "GrOnFlushResourceProvider.h"
-#include "SkPathPriv.h"
-#include "SkStrokeRec.h"
-#include "ccpr/GrCCCoverageProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLVertexGeoBuilder.h"
-
-static constexpr int kMaxNumLinearSegmentsLog2 = GrCCStrokeGeometry::kMaxNumLinearSegmentsLog2;
-using TriangleInstance = GrCCCoverageProcessor::TriPointInstance;
-using ConicInstance = GrCCCoverageProcessor::QuadPointInstance;
-
-namespace {
-
-struct LinearStrokeInstance {
-    float fEndpoints[4];
-    float fStrokeRadius;
-
-    inline void set(const SkPoint[2], float dx, float dy, float strokeRadius);
-};
-
-inline void LinearStrokeInstance::set(const SkPoint P[2], float dx, float dy, float strokeRadius) {
-    Sk2f X, Y;
-    Sk2f::Load2(P, &X, &Y);
-    Sk2f::Store2(fEndpoints, X + dx, Y + dy);
-    fStrokeRadius = strokeRadius;
-}
-
-struct CubicStrokeInstance {
-    float fX[4];
-    float fY[4];
-    float fStrokeRadius;
-    float fNumSegments;
-
-    inline void set(const SkPoint[4], float dx, float dy, float strokeRadius, int numSegments);
-    inline void set(const Sk4f& X, const Sk4f& Y, float dx, float dy, float strokeRadius,
-                    int numSegments);
-};
-
-inline void CubicStrokeInstance::set(const SkPoint P[4], float dx, float dy, float strokeRadius,
-                                     int numSegments) {
-    Sk4f X, Y;
-    Sk4f::Load2(P, &X, &Y);
-    this->set(X, Y, dx, dy, strokeRadius, numSegments);
-}
-
-inline void CubicStrokeInstance::set(const Sk4f& X, const Sk4f& Y, float dx, float dy,
-                                     float strokeRadius, int numSegments) {
-    (X + dx).store(&fX);
-    (Y + dy).store(&fY);
-    fStrokeRadius = strokeRadius;
-    fNumSegments = static_cast<float>(numSegments);
-}
-
-// This class draws stroked lines in post-transform device space (a.k.a. rectangles). Rigid-body
-// transforms can be achieved by transforming the line ahead of time and adjusting the stroke
-// width. Skews of the stroke itself are not yet supported.
-//
-// Corner coverage is AA-correct, meaning, n^2 attenuation along the diagonals. This is important
-// for seamless integration with the connecting geometry.
-class LinearStrokeProcessor : public GrGeometryProcessor {
-public:
-    LinearStrokeProcessor() : GrGeometryProcessor(kLinearStrokeProcessor_ClassID) {
-        this->setInstanceAttributeCnt(2);
-#ifdef SK_DEBUG
-        // Check that instance attributes exactly match the LinearStrokeInstance struct layout.
-        using Instance = LinearStrokeInstance;
-        SkASSERT(!strcmp(this->instanceAttribute(0).name(), "endpts"));
-        SkASSERT(this->debugOnly_instanceAttributeOffset(0) == offsetof(Instance, fEndpoints));
-        SkASSERT(!strcmp(this->instanceAttribute(1).name(), "stroke_radius"));
-        SkASSERT(this->debugOnly_instanceAttributeOffset(1) == offsetof(Instance, fStrokeRadius));
-        SkASSERT(this->debugOnly_instanceStride() == sizeof(Instance));
-#endif
-    }
-
-private:
-    const char* name() const override { return "LinearStrokeProcessor"; }
-    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
-
-    static constexpr Attribute kInstanceAttribs[2] = {
-            {"endpts", kFloat4_GrVertexAttribType},
-            {"stroke_radius", kFloat_GrVertexAttribType}
-    };
-
-    const Attribute& onInstanceAttribute(int i) const override { return kInstanceAttribs[i]; }
-
-    class Impl : public GrGLSLGeometryProcessor {
-        void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
-                     FPCoordTransformIter&&) override {}
-        void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
-    };
-
-    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
-        return new Impl();
-    }
-};
-
-void LinearStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
-    GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
-    GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
-
-    varyingHandler->emitAttributes(args.fGP.cast<LinearStrokeProcessor>());
-
-    GrGLSLVertexBuilder* v = args.fVertBuilder;
-    v->codeAppend ("float2 tan = normalize(endpts.zw - endpts.xy);");
-    v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
-    v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
-
-    // Outset the vertex position for AA butt caps.
-    v->codeAppend ("float2 outset = tan*nwidth/2;");
-    v->codeAppend ("float2 position = (sk_VertexID < 2) "
-                           "? endpts.xy - outset : endpts.zw + outset;");
-
-    // Calculate Manhattan distance from both butt caps, where distance=0 on the actual endpoint and
-    // distance=-.5 on the outset edge.
-    GrGLSLVarying edgeDistances(kFloat4_GrSLType);
-    varyingHandler->addVarying("edge_distances", &edgeDistances);
-    v->codeAppendf("%s.xz = float2(-.5, dot(endpts.zw - endpts.xy, tan) / nwidth + .5);",
-                   edgeDistances.vsOut());
-    v->codeAppendf("%s.xz = (sk_VertexID < 2) ? %s.xz : %s.zx;",
-                   edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
-
-    // Outset the vertex position for stroke radius plus edge AA.
-    v->codeAppend ("outset = n * (stroke_radius + nwidth/2);");
-    v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? +outset : -outset;");
-
-    // Calculate Manhattan distance from both edges, where distance=0 on the actual edge and
-    // distance=-.5 on the outset.
-    v->codeAppendf("%s.yw = float2(-.5, 2*stroke_radius / nwidth + .5);", edgeDistances.vsOut());
-    v->codeAppendf("%s.yw = (0 == (sk_VertexID & 1)) ? %s.yw : %s.wy;",
-                   edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
-
-    gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
-    this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
-                         SkMatrix::I(), args.fFPCoordTransformHandler);
-
-    // Use the 4 edge distances to calculate coverage in the fragment shader.
-    GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
-    f->codeAppendf("half2 coverages = min(%s.xy, .5) + min(%s.zw, .5);",
-                   edgeDistances.fsIn(), edgeDistances.fsIn());
-    f->codeAppendf("%s = half4(coverages.x * coverages.y);", args.fOutputColor);
-
-    // This shader doesn't use the built-in Ganesh coverage.
-    f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
-}
-
-constexpr GrPrimitiveProcessor::Attribute LinearStrokeProcessor::kInstanceAttribs[];
-
-// This class draws stroked cubics in post-transform device space. Rigid-body transforms can be
-// achieved by transforming the curve ahead of time and adjusting the stroke width. Skews of the
-// stroke itself are not yet supported. Quadratics can be drawn by converting them to cubics.
-//
-// This class works by finding stroke-width line segments orthogonal to the curve at a
-// pre-determined number of evenly spaced points along the curve (evenly spaced in the parametric
-// sense). It then connects the segments with a triangle strip. As for common in CCPR, clockwise-
-// winding triangles from the strip emit positive coverage, counter-clockwise triangles emit
-// negative, and we use SkBlendMode::kPlus.
-class CubicStrokeProcessor : public GrGeometryProcessor {
-public:
-    CubicStrokeProcessor() : GrGeometryProcessor(kCubicStrokeProcessor_ClassID) {
-        this->setInstanceAttributeCnt(3);
-#ifdef SK_DEBUG
-        // Check that instance attributes exactly match the CubicStrokeInstance struct layout.
-        using Instance = CubicStrokeInstance;
-        SkASSERT(!strcmp(this->instanceAttribute(0).name(), "X"));
-        SkASSERT(this->debugOnly_instanceAttributeOffset(0) == offsetof(Instance, fX));
-        SkASSERT(!strcmp(this->instanceAttribute(1).name(), "Y"));
-        SkASSERT(this->debugOnly_instanceAttributeOffset(1) == offsetof(Instance, fY));
-        SkASSERT(!strcmp(this->instanceAttribute(2).name(), "stroke_info"));
-        SkASSERT(this->debugOnly_instanceAttributeOffset(2) == offsetof(Instance, fStrokeRadius));
-        SkASSERT(this->debugOnly_instanceStride() == sizeof(Instance));
-#endif
-    }
-
-private:
-    const char* name() const override { return "CubicStrokeProcessor"; }
-    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
-
-    static constexpr Attribute kInstanceAttribs[3] = {
-            {"X", kFloat4_GrVertexAttribType},
-            {"Y", kFloat4_GrVertexAttribType},
-            {"stroke_info", kFloat2_GrVertexAttribType}
-    };
-
-    const Attribute& onInstanceAttribute(int i) const override { return kInstanceAttribs[i]; }
-
-    class Impl : public GrGLSLGeometryProcessor {
-        void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
-                     FPCoordTransformIter&&) override {}
-        void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
-    };
-
-    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
-        return new Impl();
-    }
-};
-
-void CubicStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
-    GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
-    GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
-
-    varyingHandler->emitAttributes(args.fGP.cast<CubicStrokeProcessor>());
-
-    GrGLSLVertexBuilder* v = args.fVertBuilder;
-    v->codeAppend ("float4x2 P = transpose(float2x4(X, Y));");
-    v->codeAppend ("float stroke_radius = stroke_info[0];");
-    v->codeAppend ("float num_segments = stroke_info[1];");
-
-    // Find the parametric T value at which we will emit our orthogonal line segment. We emit two
-    // line segments at T=0 and double at T=1 as well for AA butt caps.
-    v->codeAppend ("float point_id = float(sk_VertexID/2);");
-    v->codeAppend ("float T = max((point_id - 1) / num_segments, 0);");
-    v->codeAppend ("T = (point_id >= num_segments + 1) ? 1 : T;");  // In case x/x !== 1.
-
-    // Use De Casteljau's algorithm to find the position and tangent for our orthogonal line
-    // segment. De Casteljau's is more numerically stable than evaluating the curve and derivative
-    // directly.
-    v->codeAppend ("float2 ab = mix(P[0], P[1], T);");
-    v->codeAppend ("float2 bc = mix(P[1], P[2], T);");
-    v->codeAppend ("float2 cd = mix(P[2], P[3], T);");
-    v->codeAppend ("float2 abc = mix(ab, bc, T);");
-    v->codeAppend ("float2 bcd = mix(bc, cd, T);");
-    v->codeAppend ("float2 position = mix(abc, bcd, T);");
-    v->codeAppend ("float2 tan = bcd - abc;");
-
-    // Find actual tangents for the corner cases when De Casteljau's yields tan=0. (We shouldn't
-    // encounter other numerically unstable cases where tan ~= 0, because GrCCStrokeGeometry snaps
-    // control points to endpoints in curves where they are almost equal.)
-    v->codeAppend ("if (0 == T && P[0] == P[1]) {");
-    v->codeAppend (    "tan = P[2] - P[0];");
-    v->codeAppend ("}");
-    v->codeAppend ("if (1 == T && P[2] == P[3]) {");
-    v->codeAppend (    "tan = P[3] - P[1];");
-    v->codeAppend ("}");
-    v->codeAppend ("tan = normalize(tan);");
-    v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
-    v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
-
-    // Outset the vertex position for stroke radius plus edge AA.
-    v->codeAppend ("float2 outset = n * (stroke_radius + nwidth/2);");
-    v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? -outset : +outset;");
-
-    // Calculate the Manhattan distance from both edges, where distance=0 on the actual edge and
-    // distance=-.5 on the outset.
-    GrGLSLVarying coverages(kFloat3_GrSLType);
-    varyingHandler->addVarying("coverages", &coverages);
-    v->codeAppendf("%s.xy = float2(-.5, 2*stroke_radius / nwidth + .5);", coverages.vsOut());
-    v->codeAppendf("%s.xy = (0 == (sk_VertexID & 1)) ? %s.xy : %s.yx;",
-                   coverages.vsOut(), coverages.vsOut(), coverages.vsOut());
-
-    // Adjust the orthogonal line segments on the endpoints so they straddle the actual endpoint
-    // at a Manhattan distance of .5 on either side.
-    v->codeAppend ("if (0 == point_id || num_segments+1 == point_id) {");
-    v->codeAppend (    "position -= tan*nwidth/2;");
-    v->codeAppend ("}");
-    v->codeAppend ("if (1 == point_id || num_segments+2 == point_id) {");
-    v->codeAppend (    "position += tan*nwidth/2;");
-    v->codeAppend ("}");
-
-    // Interpolate coverage for butt cap AA from 0 on the outer segment to 1 on the inner.
-    v->codeAppendf("%s.z = (0 == point_id || num_segments+2 == point_id) ? 0 : 1;",
-                   coverages.vsOut());
-
-    gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
-    this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
-                         SkMatrix::I(), args.fFPCoordTransformHandler);
-
-    // Use the 2 edge distances and interpolated butt cap AA to calculate fragment coverage.
-    GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
-    f->codeAppendf("half2 edge_coverages = min(%s.xy, .5);", coverages.fsIn());
-    f->codeAppend ("half coverage = edge_coverages.x + edge_coverages.y;");
-    f->codeAppendf("coverage *= %s.z;", coverages.fsIn());  // Butt cap AA.
-
-    // As is common for CCPR, clockwise-winding triangles from the strip emit positive coverage, and
-    // counter-clockwise triangles emit negative.
-    f->codeAppendf("%s = half4(sk_Clockwise ? +coverage : -coverage);", args.fOutputColor);
-
-    // This shader doesn't use the built-in Ganesh coverage.
-    f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
-}
-
-constexpr GrPrimitiveProcessor::Attribute CubicStrokeProcessor::kInstanceAttribs[];
-
-}  // anonymous namespace
-
-void GrCCStroker::parseDeviceSpaceStroke(const SkPath& path, const SkPoint* deviceSpacePts,
-                                         const SkStrokeRec& stroke, float strokeDevWidth,
-                                         GrScissorTest scissorTest,
-                                         const SkIRect& clippedDevIBounds,
-                                         const SkIVector& devToAtlasOffset) {
-    SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle() ||
-             SkStrokeRec::kHairline_Style == stroke.getStyle());
-    SkASSERT(!fInstanceBuffer);
-    SkASSERT(!path.isEmpty());
-
-    if (!fHasOpenBatch) {
-        fBatches.emplace_back(&fTalliesAllocator, *fInstanceCounts[(int)GrScissorTest::kDisabled],
-                              fScissorSubBatches.count());
-        fInstanceCounts[(int)GrScissorTest::kDisabled] = fBatches.back().fNonScissorEndInstances;
-        fHasOpenBatch = true;
-    }
-
-    InstanceTallies* currStrokeEndIndices;
-    if (GrScissorTest::kEnabled == scissorTest) {
-        SkASSERT(fBatches.back().fEndScissorSubBatch == fScissorSubBatches.count());
-        fScissorSubBatches.emplace_back(
-                &fTalliesAllocator, *fInstanceCounts[(int)GrScissorTest::kEnabled],
-                clippedDevIBounds.makeOffset(devToAtlasOffset.x(), devToAtlasOffset.y()));
-        fBatches.back().fEndScissorSubBatch = fScissorSubBatches.count();
-        fInstanceCounts[(int)GrScissorTest::kEnabled] =
-                currStrokeEndIndices = fScissorSubBatches.back().fEndInstances;
-    } else {
-        currStrokeEndIndices = fBatches.back().fNonScissorEndInstances;
-    }
-
-    fGeometry.beginPath(stroke, strokeDevWidth, currStrokeEndIndices);
-
-    fPathInfos.push_back() = {devToAtlasOffset, strokeDevWidth/2, scissorTest};
-
-    int devPtsIdx = 0;
-    SkPath::Verb previousVerb = SkPath::kClose_Verb;
-
-    for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
-        SkASSERT(SkPath::kDone_Verb != previousVerb);
-        const SkPoint* P = &deviceSpacePts[devPtsIdx - 1];
-        switch (verb) {
-            case SkPath::kMove_Verb:
-                if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
-                    fGeometry.capContourAndExit();
-                }
-                fGeometry.moveTo(deviceSpacePts[devPtsIdx]);
-                ++devPtsIdx;
-                break;
-            case SkPath::kClose_Verb:
-                SkASSERT(SkPath::kClose_Verb != previousVerb);
-                fGeometry.closeContour();
-                break;
-            case SkPath::kLine_Verb:
-                SkASSERT(SkPath::kClose_Verb != previousVerb);
-                fGeometry.lineTo(P[1]);
-                ++devPtsIdx;
-                break;
-            case SkPath::kQuad_Verb:
-                SkASSERT(SkPath::kClose_Verb != previousVerb);
-                fGeometry.quadraticTo(P);
-                devPtsIdx += 2;
-                break;
-            case SkPath::kCubic_Verb: {
-                SkASSERT(SkPath::kClose_Verb != previousVerb);
-                fGeometry.cubicTo(P);
-                devPtsIdx += 3;
-                break;
-            }
-            case SkPath::kConic_Verb:
-                SkASSERT(SkPath::kClose_Verb != previousVerb);
-                SK_ABORT("Stroked conics not supported.");
-                break;
-            case SkPath::kDone_Verb:
-                break;
-        }
-        previousVerb = verb;
-    }
-
-    if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
-        fGeometry.capContourAndExit();
-    }
-}
-
-// This class encapsulates the process of expanding ready-to-draw geometry from GrCCStrokeGeometry
-// directly into GPU instance buffers.
-class GrCCStroker::InstanceBufferBuilder {
-public:
-    InstanceBufferBuilder(GrOnFlushResourceProvider* onFlushRP, GrCCStroker* stroker) {
-        memcpy(fNextInstances, stroker->fBaseInstances, sizeof(fNextInstances));
-#ifdef SK_DEBUG
-        fEndInstances[0] = stroker->fBaseInstances[0] + *stroker->fInstanceCounts[0];
-        fEndInstances[1] = stroker->fBaseInstances[1] + *stroker->fInstanceCounts[1];
-#endif
-
-        int endConicsIdx = stroker->fBaseInstances[1].fConics +
-                           stroker->fInstanceCounts[1]->fConics;
-        fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType,
-                                                endConicsIdx * sizeof(ConicInstance));
-        if (!fInstanceBuffer) {
-            SkDebugf("WARNING: failed to allocate CCPR stroke instance buffer.\n");
-            return;
-        }
-        fInstanceBufferData = fInstanceBuffer->map();
-    }
-
-    bool isMapped() const { return SkToBool(fInstanceBufferData); }
-
-    void updateCurrentInfo(const PathInfo& pathInfo) {
-        SkASSERT(this->isMapped());
-        fCurrDX = static_cast<float>(pathInfo.fDevToAtlasOffset.x());
-        fCurrDY = static_cast<float>(pathInfo.fDevToAtlasOffset.y());
-        fCurrStrokeRadius = pathInfo.fStrokeRadius;
-        fCurrNextInstances = &fNextInstances[(int)pathInfo.fScissorTest];
-        SkDEBUGCODE(fCurrEndInstances = &fEndInstances[(int)pathInfo.fScissorTest]);
-    }
-
-    void appendLinearStroke(const SkPoint endpts[2]) {
-        SkASSERT(this->isMapped());
-        this->appendLinearStrokeInstance().set(endpts, fCurrDX, fCurrDY, fCurrStrokeRadius);
-    }
-
-    void appendQuadraticStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
-        SkASSERT(this->isMapped());
-        SkASSERT(numLinearSegmentsLog2 > 0);
-
-        Sk4f ptsT[2];
-        Sk2f p0 = Sk2f::Load(P);
-        Sk2f p1 = Sk2f::Load(P+1);
-        Sk2f p2 = Sk2f::Load(P+2);
-
-        // Convert the quadratic to cubic.
-        Sk2f c1 = SkNx_fma(Sk2f(2/3.f), p1 - p0, p0);
-        Sk2f c2 = SkNx_fma(Sk2f(1/3.f), p2 - p1, p1);
-        Sk2f::Store4(ptsT, p0, c1, c2, p2);
-
-        this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
-                ptsT[0], ptsT[1], fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
-    }
-
-    void appendCubicStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
-        SkASSERT(this->isMapped());
-        SkASSERT(numLinearSegmentsLog2 > 0);
-        this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
-                P, fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
-    }
-
-    void appendJoin(Verb joinVerb, const SkPoint& center, const SkVector& leftNorm,
-                    const SkVector& rightNorm, float miterCapHeightOverWidth, float conicWeight) {
-        SkASSERT(this->isMapped());
-
-        Sk2f offset = Sk2f::Load(&center) + Sk2f(fCurrDX, fCurrDY);
-        Sk2f n0 = Sk2f::Load(&leftNorm);
-        Sk2f n1 = Sk2f::Load(&rightNorm);
-
-        // Identify the outer edge.
-        Sk2f cross = n0 * SkNx_shuffle<1,0>(n1);
-        if (cross[0] < cross[1]) {
-            Sk2f tmp = n0;
-            n0 = -n1;
-            n1 = -tmp;
-        }
-
-        if (!GrCCStrokeGeometry::IsInternalJoinVerb(joinVerb)) {
-            // Normal joins are a triangle that connects the outer corners of two adjoining strokes.
-            this->appendTriangleInstance().set(n1 * fCurrStrokeRadius, Sk2f(0, 0),
-                                               n0 * fCurrStrokeRadius, offset);
-            if (Verb::kBevelJoin == joinVerb) {
-                return;
-            }
-        } else {
-            // Internal joins are coverage-counted, self-intersecting quadrilaterals that tie the
-            // four corners of two adjoining strokes together a like a shoelace. Coverage is
-            // negative on the inside half. We implement this geometry with a pair of triangles.
-            this->appendTriangleInstance().set(-n0 * fCurrStrokeRadius, n0 * fCurrStrokeRadius,
-                                               n1 * fCurrStrokeRadius, offset);
-            this->appendTriangleInstance().set(-n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius,
-                                               -n1 * fCurrStrokeRadius, offset);
-            if (Verb::kInternalBevelJoin == joinVerb) {
-                return;
-            }
-        }
-
-        // For miter and round joins, we place an additional triangle cap on top of the bevel. This
-        // triangle is literal for miters and is conic control points for round joins.
-        SkASSERT(miterCapHeightOverWidth >= 0);
-        Sk2f base = n1 - n0;
-        Sk2f baseNorm = Sk2f(base[1], -base[0]);
-        Sk2f c = (n0 + n1) * .5f + baseNorm * miterCapHeightOverWidth;
-
-        if (Verb::kMiterJoin == joinVerb) {
-            this->appendTriangleInstance().set(n0 * fCurrStrokeRadius, c * fCurrStrokeRadius,
-                                               n1 * fCurrStrokeRadius, offset);
-        } else {
-            SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb);
-            this->appendConicInstance().setW(n0 * fCurrStrokeRadius, c * fCurrStrokeRadius,
-                                             n1 * fCurrStrokeRadius, offset, conicWeight);
-            if (Verb::kInternalRoundJoin == joinVerb) {
-                this->appendConicInstance().setW(-n1 * fCurrStrokeRadius, c * -fCurrStrokeRadius,
-                                                 -n0 * fCurrStrokeRadius, offset, conicWeight);
-            }
-        }
-    }
-
-    void appendCap(Verb capType, const SkPoint& pt, const SkVector& norm) {
-        SkASSERT(this->isMapped());
-
-        Sk2f n = Sk2f::Load(&norm) * fCurrStrokeRadius;
-        Sk2f v = Sk2f(-n[1], n[0]);
-        Sk2f offset = Sk2f::Load(&pt) + Sk2f(fCurrDX, fCurrDY);
-
-        if (Verb::kSquareCap == capType) {
-            SkPoint endPts[2] = {{0, 0}, {v[0], v[1]}};
-            this->appendLinearStrokeInstance().set(endPts, offset[0], offset[1], fCurrStrokeRadius);
-        } else {
-            SkASSERT(Verb::kRoundCap == capType);
-            this->appendTriangleInstance().set(n, v, -n, offset);
-            this->appendConicInstance().setW(n, n + v, v, offset, SK_ScalarRoot2Over2);
-            this->appendConicInstance().setW(v, v - n, -n, offset, SK_ScalarRoot2Over2);
-        }
-    }
-
-    sk_sp<GrBuffer> finish() {
-        SkASSERT(this->isMapped());
-        SkASSERT(!memcmp(fNextInstances, fEndInstances, sizeof(fNextInstances)));
-        fInstanceBuffer->unmap();
-        fInstanceBufferData = nullptr;
-        SkASSERT(!this->isMapped());
-        return std::move(fInstanceBuffer);
-    }
-
-private:
-    LinearStrokeInstance& appendLinearStrokeInstance() {
-        int instanceIdx = fCurrNextInstances->fStrokes[0]++;
-        SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[0]);
-
-        return reinterpret_cast<LinearStrokeInstance*>(fInstanceBufferData)[instanceIdx];
-    }
-
-    CubicStrokeInstance& appendCubicStrokeInstance(int numLinearSegmentsLog2) {
-        SkASSERT(numLinearSegmentsLog2 > 0);
-        SkASSERT(numLinearSegmentsLog2 <= kMaxNumLinearSegmentsLog2);
-
-        int instanceIdx = fCurrNextInstances->fStrokes[numLinearSegmentsLog2]++;
-        SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[numLinearSegmentsLog2]);
-
-        return reinterpret_cast<CubicStrokeInstance*>(fInstanceBufferData)[instanceIdx];
-    }
-
-    TriangleInstance& appendTriangleInstance() {
-        int instanceIdx = fCurrNextInstances->fTriangles++;
-        SkASSERT(instanceIdx < fCurrEndInstances->fTriangles);
-
-        return reinterpret_cast<TriangleInstance*>(fInstanceBufferData)[instanceIdx];
-    }
-
-    ConicInstance& appendConicInstance() {
-        int instanceIdx = fCurrNextInstances->fConics++;
-        SkASSERT(instanceIdx < fCurrEndInstances->fConics);
-
-        return reinterpret_cast<ConicInstance*>(fInstanceBufferData)[instanceIdx];
-    }
-
-    float fCurrDX, fCurrDY;
-    float fCurrStrokeRadius;
-    InstanceTallies* fCurrNextInstances;
-    SkDEBUGCODE(const InstanceTallies* fCurrEndInstances);
-
-    sk_sp<GrBuffer> fInstanceBuffer;
-    void* fInstanceBufferData = nullptr;
-    InstanceTallies fNextInstances[2];
-    SkDEBUGCODE(InstanceTallies fEndInstances[2]);
-};
-
-GrCCStroker::BatchID GrCCStroker::closeCurrentBatch() {
-    if (!fHasOpenBatch) {
-        return kEmptyBatchID;
-    }
-    int start = (fBatches.count() < 2) ? 0 : fBatches[fBatches.count() - 2].fEndScissorSubBatch;
-    int end = fBatches.back().fEndScissorSubBatch;
-    fMaxNumScissorSubBatches = SkTMax(fMaxNumScissorSubBatches, end - start);
-    fHasOpenBatch = false;
-    return fBatches.count() - 1;
-}
-
-bool GrCCStroker::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) {
-    SkASSERT(!fInstanceBuffer);
-    SkASSERT(!fHasOpenBatch);  // Call closeCurrentBatch() first.
-
-    // Here we layout a single instance buffer to share with every internal batch.
-    //
-    // Rather than place each instance array in its own GPU buffer, we allocate a single
-    // megabuffer and lay them all out side-by-side. We can offset the "baseInstance" parameter in
-    // our draw calls to direct the GPU to the applicable elements within a given array.
-    fBaseInstances[0].fStrokes[0] = 0;
-    fBaseInstances[1].fStrokes[0] = fInstanceCounts[0]->fStrokes[0];
-    int endLinearStrokesIdx = fBaseInstances[1].fStrokes[0] + fInstanceCounts[1]->fStrokes[0];
-
-    int cubicStrokesIdx = GR_CT_DIV_ROUND_UP(endLinearStrokesIdx * sizeof(LinearStrokeInstance),
-                                             sizeof(CubicStrokeInstance));
-    for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
-        for (int j = 0; j < kNumScissorModes; ++j) {
-            fBaseInstances[j].fStrokes[i] = cubicStrokesIdx;
-            cubicStrokesIdx += fInstanceCounts[j]->fStrokes[i];
-        }
-    }
-
-    int trianglesIdx = GR_CT_DIV_ROUND_UP(cubicStrokesIdx * sizeof(CubicStrokeInstance),
-                                          sizeof(TriangleInstance));
-    fBaseInstances[0].fTriangles = trianglesIdx;
-    fBaseInstances[1].fTriangles =
-            fBaseInstances[0].fTriangles + fInstanceCounts[0]->fTriangles;
-    int endTrianglesIdx =
-            fBaseInstances[1].fTriangles + fInstanceCounts[1]->fTriangles;
-
-    int conicsIdx = GR_CT_DIV_ROUND_UP(endTrianglesIdx * sizeof(TriangleInstance),
-                                       sizeof(ConicInstance));
-    fBaseInstances[0].fConics = conicsIdx;
-    fBaseInstances[1].fConics = fBaseInstances[0].fConics + fInstanceCounts[0]->fConics;
-
-    InstanceBufferBuilder builder(onFlushRP, this);
-    if (!builder.isMapped()) {
-        return false;  // Buffer allocation failed.
-    }
-
-    // Now parse the GrCCStrokeGeometry and expand it into the instance buffer.
-    int pathIdx = 0;
-    int ptsIdx = 0;
-    int paramsIdx = 0;
-    int normalsIdx = 0;
-
-    const SkTArray<GrCCStrokeGeometry::Parameter, true>& params = fGeometry.params();
-    const SkTArray<SkPoint, true>& pts = fGeometry.points();
-    const SkTArray<SkVector, true>& normals = fGeometry.normals();
-
-    float miterCapHeightOverWidth=0, conicWeight=0;
-
-    for (Verb verb : fGeometry.verbs()) {
-        switch (verb) {
-            case Verb::kBeginPath:
-                builder.updateCurrentInfo(fPathInfos[pathIdx]);
-                ++pathIdx;
-                continue;
-
-            case Verb::kLinearStroke:
-                builder.appendLinearStroke(&pts[ptsIdx]);
-                ++ptsIdx;
-                continue;
-            case Verb::kQuadraticStroke:
-                builder.appendQuadraticStroke(&pts[ptsIdx],
-                                              params[paramsIdx++].fNumLinearSegmentsLog2);
-                ptsIdx += 2;
-                ++normalsIdx;
-                continue;
-            case Verb::kCubicStroke:
-                builder.appendCubicStroke(&pts[ptsIdx], params[paramsIdx++].fNumLinearSegmentsLog2);
-                ptsIdx += 3;
-                ++normalsIdx;
-                continue;
-
-            case Verb::kRoundJoin:
-            case Verb::kInternalRoundJoin:
-                conicWeight = params[paramsIdx++].fConicWeight;
-                // fallthru
-            case Verb::kMiterJoin:
-                miterCapHeightOverWidth = params[paramsIdx++].fMiterCapHeightOverWidth;
-                // fallthru
-            case Verb::kBevelJoin:
-            case Verb::kInternalBevelJoin:
-                builder.appendJoin(verb, pts[ptsIdx], normals[normalsIdx], normals[normalsIdx + 1],
-                                   miterCapHeightOverWidth, conicWeight);
-                ++normalsIdx;
-                continue;
-
-            case Verb::kSquareCap:
-            case Verb::kRoundCap:
-                builder.appendCap(verb, pts[ptsIdx], normals[normalsIdx]);
-                continue;
-
-            case Verb::kEndContour:
-                ++ptsIdx;
-                ++normalsIdx;
-                continue;
-        }
-        SK_ABORT("Invalid CCPR stroke element.");
-    }
-
-    fInstanceBuffer = builder.finish();
-    SkASSERT(fPathInfos.count() == pathIdx);
-    SkASSERT(pts.count() == ptsIdx);
-    SkASSERT(normals.count() == normalsIdx);
-
-    fMeshesBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2);
-    fScissorsBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2);
-    return true;
-}
-
-void GrCCStroker::drawStrokes(GrOpFlushState* flushState, BatchID batchID,
-                              const SkIRect& drawBounds) const {
-    using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
-    SkASSERT(fInstanceBuffer);
-
-    if (kEmptyBatchID == batchID) {
-        return;
-    }
-    const Batch& batch = fBatches[batchID];
-    int startScissorSubBatch = (!batchID) ? 0 : fBatches[batchID - 1].fEndScissorSubBatch;
-
-    const InstanceTallies* startIndices[2];
-    startIndices[(int)GrScissorTest::kDisabled] = (!batchID)
-            ? &fZeroTallies : fBatches[batchID - 1].fNonScissorEndInstances;
-    startIndices[(int)GrScissorTest::kEnabled] = (!startScissorSubBatch)
-            ? &fZeroTallies : fScissorSubBatches[startScissorSubBatch - 1].fEndInstances;
-
-    GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrScissorTest::kEnabled,
-                        SkBlendMode::kPlus);
-
-    // Draw linear strokes.
-    this->appendStrokeMeshesToBuffers(0, batch, startIndices, startScissorSubBatch, drawBounds);
-    if (!fMeshesBuffer.empty()) {
-        LinearStrokeProcessor linearProc;
-        this->flushBufferedMeshesAsStrokes(linearProc, flushState, pipeline, drawBounds);
-    }
-
-    // Draw cubic strokes. (Quadratics were converted to cubics for GPU processing.)
-    for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
-        this->appendStrokeMeshesToBuffers(i, batch, startIndices, startScissorSubBatch, drawBounds);
-    }
-    if (!fMeshesBuffer.empty()) {
-        CubicStrokeProcessor cubicProc;
-        this->flushBufferedMeshesAsStrokes(cubicProc, flushState, pipeline, drawBounds);
-    }
-
-    // Draw triangles.
-    GrCCCoverageProcessor triProc(flushState->resourceProvider(), PrimitiveType::kTriangles);
-    this->drawConnectingGeometry<&InstanceTallies::fTriangles>(
-            flushState, pipeline, triProc, batch, startIndices, startScissorSubBatch, drawBounds);
-
-    // Draw conics.
-    GrCCCoverageProcessor conicProc(flushState->resourceProvider(), PrimitiveType::kConics);
-    this->drawConnectingGeometry<&InstanceTallies::fConics>(
-            flushState, pipeline, conicProc, batch, startIndices, startScissorSubBatch, drawBounds);
-}
-
-void GrCCStroker::appendStrokeMeshesToBuffers(int numSegmentsLog2, const Batch& batch,
-                                              const InstanceTallies* startIndices[2],
-                                              int startScissorSubBatch,
-                                              const SkIRect& drawBounds) const {
-    // Linear strokes draw a quad. Cubic strokes emit a strip with normals at "numSegments"
-    // evenly-spaced points along the curve, plus one more for the final endpoint, plus two more for
-    // AA butt caps. (i.e., 2 vertices * (numSegments + 3).)
-    int numStripVertices = (0 == numSegmentsLog2) ? 4 : ((1 << numSegmentsLog2) + 3) * 2;
-
-    // Append non-scissored meshes.
-    int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].fStrokes[numSegmentsLog2];
-    int startIdx = startIndices[(int)GrScissorTest::kDisabled]->fStrokes[numSegmentsLog2];
-    int endIdx = batch.fNonScissorEndInstances->fStrokes[numSegmentsLog2];
-    SkASSERT(endIdx >= startIdx);
-    if (int instanceCount = endIdx - startIdx) {
-        GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip);
-        mesh.setInstanced(fInstanceBuffer.get(), instanceCount, baseInstance + startIdx,
-                          numStripVertices);
-        fScissorsBuffer.push_back(drawBounds);
-    }
-
-    // Append scissored meshes.
-    baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].fStrokes[numSegmentsLog2];
-    startIdx = startIndices[(int)GrScissorTest::kEnabled]->fStrokes[numSegmentsLog2];
-    for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
-        const ScissorSubBatch& subBatch = fScissorSubBatches[i];
-        endIdx = subBatch.fEndInstances->fStrokes[numSegmentsLog2];
-        SkASSERT(endIdx >= startIdx);
-        if (int instanceCount = endIdx - startIdx) {
-            GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip);
-            mesh.setInstanced(fInstanceBuffer.get(), instanceCount, baseInstance + startIdx,
-                              numStripVertices);
-            fScissorsBuffer.push_back(subBatch.fScissor);
-            startIdx = endIdx;
-        }
-    }
-}
-
-void GrCCStroker::flushBufferedMeshesAsStrokes(const GrPrimitiveProcessor& processor,
-                                               GrOpFlushState* flushState,
-                                               const GrPipeline& pipeline,
-                                               const SkIRect& drawBounds) const {
-    SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count());
-    GrPipeline::DynamicStateArrays dynamicStateArrays;
-    dynamicStateArrays.fScissorRects = fScissorsBuffer.begin();
-    flushState->rtCommandBuffer()->draw(processor, pipeline, nullptr, &dynamicStateArrays,
-                                        fMeshesBuffer.begin(), fMeshesBuffer.count(),
-                                        SkRect::Make(drawBounds));
-    // Don't call reset(), as that also resets the reserve count.
-    fMeshesBuffer.pop_back_n(fMeshesBuffer.count());
-    fScissorsBuffer.pop_back_n(fScissorsBuffer.count());
-}
-
-template<int GrCCStrokeGeometry::InstanceTallies::* InstanceType>
-void GrCCStroker::drawConnectingGeometry(GrOpFlushState* flushState, const GrPipeline& pipeline,
-                                         const GrCCCoverageProcessor& processor,
-                                         const Batch& batch, const InstanceTallies* startIndices[2],
-                                         int startScissorSubBatch,
-                                         const SkIRect& drawBounds) const {
-    // Append non-scissored meshes.
-    int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].*InstanceType;
-    int startIdx = startIndices[(int)GrScissorTest::kDisabled]->*InstanceType;
-    int endIdx = batch.fNonScissorEndInstances->*InstanceType;
-    SkASSERT(endIdx >= startIdx);
-    if (int instanceCount = endIdx - startIdx) {
-        processor.appendMesh(fInstanceBuffer.get(), instanceCount, baseInstance + startIdx,
-                             &fMeshesBuffer);
-        fScissorsBuffer.push_back(drawBounds);
-    }
-
-    // Append scissored meshes.
-    baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].*InstanceType;
-    startIdx = startIndices[(int)GrScissorTest::kEnabled]->*InstanceType;
-    for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
-        const ScissorSubBatch& subBatch = fScissorSubBatches[i];
-        endIdx = subBatch.fEndInstances->*InstanceType;
-        SkASSERT(endIdx >= startIdx);
-        if (int instanceCount = endIdx - startIdx) {
-            processor.appendMesh(fInstanceBuffer.get(), instanceCount, baseInstance + startIdx,
-                                 &fMeshesBuffer);
-            fScissorsBuffer.push_back(subBatch.fScissor);
-            startIdx = endIdx;
-        }
-    }
-
-    // Flush the geometry.
-    if (!fMeshesBuffer.empty()) {
-        SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count());
-        processor.draw(flushState, pipeline, fScissorsBuffer.begin(), fMeshesBuffer.begin(),
-                       fMeshesBuffer.count(), SkRect::Make(drawBounds));
-        // Don't call reset(), as that also resets the reserve count.
-        fMeshesBuffer.pop_back_n(fMeshesBuffer.count());
-        fScissorsBuffer.pop_back_n(fScissorsBuffer.count());
-    }
-}
diff --git a/src/gpu/ccpr/GrCCStroker.h b/src/gpu/ccpr/GrCCStroker.h
deleted file mode 100644
index ac71011..0000000
--- a/src/gpu/ccpr/GrCCStroker.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrCCStroker_DEFINED
-#define GrCCStroker_DEFINED
-
-#include "GrAllocator.h"
-#include "GrMesh.h"
-#include "SkNx.h"
-#include "ccpr/GrCCStrokeGeometry.h"
-
-class GrBuffer;
-class GrCCCoverageProcessor;
-class GrOnFlushResourceProvider;
-class GrOpFlushState;
-class GrPipeline;
-class GrPrimitiveProcessor;
-class SkMatrix;
-class SkPath;
-class SkStrokeRec;
-
-/**
- * This class parses stroked SkPaths into a GPU instance buffer, then issues calls to draw their
- * coverage counts.
- */
-class GrCCStroker {
-public:
-    GrCCStroker(int numPaths, int numSkPoints, int numSkVerbs)
-            : fGeometry(numSkPoints, numSkVerbs), fPathInfos(numPaths) {}
-
-    // Parses a device-space SkPath into the current batch, using the SkPath's original verbs with
-    // 'deviceSpacePts', and the SkStrokeRec's original settings with 'strokeDevWidth'. Accepts an
-    // optional post-device-space translate for placement in an atlas.
-    //
-    // Strokes intended as hairlines must have a strokeDevWidth of 1. Non-hairline strokes can only
-    // be drawn with rigid body transforms; affine transformation of the stroke lines themselves is
-    // not yet supported.
-    void parseDeviceSpaceStroke(const SkPath&, const SkPoint* deviceSpacePts, const SkStrokeRec&,
-                                float strokeDevWidth, 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 strokes in the future.
-    BatchID closeCurrentBatch();
-
-    // Builds an internal GPU buffer and prepares for calls to drawStrokes(). 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 strokes.
-    void drawStrokes(GrOpFlushState*, BatchID, const SkIRect& drawBounds) const;
-
-private:
-    static constexpr int kNumScissorModes = 2;
-    static constexpr BatchID kEmptyBatchID = -1;
-    using Verb = GrCCStrokeGeometry::Verb;
-    using InstanceTallies = GrCCStrokeGeometry::InstanceTallies;
-
-    // Every kBeginPath verb has a corresponding PathInfo entry.
-    struct PathInfo {
-        SkIVector fDevToAtlasOffset;
-        float fStrokeRadius;
-        GrScissorTest fScissorTest;
-    };
-
-    // Defines a sub-batch of stroke instances that have a scissor test and the same scissor rect.
-    // Start indices are deduced by looking at the previous ScissorSubBatch.
-    struct ScissorSubBatch {
-        ScissorSubBatch(GrTAllocator<InstanceTallies>* alloc, const InstanceTallies& startIndices,
-                        const SkIRect& scissor)
-                : fEndInstances(&alloc->emplace_back(startIndices)), fScissor(scissor) {}
-        InstanceTallies* fEndInstances;
-        SkIRect fScissor;
-    };
-
-    // Defines a batch of stroke instances that can be drawn with drawStrokes(). Start indices are
-    // deduced by looking at the previous Batch in the list.
-    struct Batch {
-        Batch(GrTAllocator<InstanceTallies>* alloc, const InstanceTallies& startNonScissorIndices,
-              int startScissorSubBatch)
-                : fNonScissorEndInstances(&alloc->emplace_back(startNonScissorIndices))
-                , fEndScissorSubBatch(startScissorSubBatch) {}
-        InstanceTallies* fNonScissorEndInstances;
-        int fEndScissorSubBatch;
-    };
-
-    class InstanceBufferBuilder;
-
-    void appendStrokeMeshesToBuffers(int numSegmentsLog2, const Batch&,
-                                     const InstanceTallies* startIndices[2],
-                                     int startScissorSubBatch, const SkIRect& drawBounds) const;
-    void flushBufferedMeshesAsStrokes(const GrPrimitiveProcessor&, GrOpFlushState*, const
-                                      GrPipeline&, const SkIRect& drawBounds) const;
-
-    template<int GrCCStrokeGeometry::InstanceTallies::* InstanceType>
-    void drawConnectingGeometry(GrOpFlushState*, const GrPipeline&,
-                                const GrCCCoverageProcessor&, const Batch&,
-                                const InstanceTallies* startIndices[2], int startScissorSubBatch,
-                                const SkIRect& drawBounds) const;
-
-    GrCCStrokeGeometry fGeometry;
-    SkSTArray<32, PathInfo> fPathInfos;
-    SkSTArray<32, Batch> fBatches;
-    SkSTArray<32, ScissorSubBatch> fScissorSubBatches;
-    int fMaxNumScissorSubBatches = 0;
-    bool fHasOpenBatch = false;
-
-    const InstanceTallies fZeroTallies = InstanceTallies();
-    GrSTAllocator<128, InstanceTallies> fTalliesAllocator;
-    const InstanceTallies* fInstanceCounts[kNumScissorModes] = {&fZeroTallies, &fZeroTallies};
-
-    sk_sp<GrBuffer> fInstanceBuffer;
-    // The indices stored in batches are relative to these base instances.
-    InstanceTallies fBaseInstances[kNumScissorModes];
-
-    mutable SkSTArray<32, GrMesh> fMeshesBuffer;
-    mutable SkSTArray<32, SkIRect> fScissorsBuffer;
-};
-
-#endif
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index 0899690..f783259 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -18,6 +18,21 @@
 
 using PathInstance = GrCCPathProcessor::Instance;
 
+// If a path spans more pixels than this, we need to crop it or else analytic AA can run out of fp32
+// precision.
+static constexpr float kPathCropThreshold = 1 << 16;
+
+static void crop_path(const SkPath& path, const SkIRect& cropbox, SkPath* out) {
+    SkPath cropboxPath;
+    cropboxPath.addRect(SkRect::Make(cropbox));
+    if (!Op(cropboxPath, path, kIntersect_SkPathOp, out)) {
+        // This can fail if the PathOps encounter NaN or infinities.
+        out->reset();
+    }
+    out->setIsVolatile(true);
+}
+
+
 GrCCPerOpListPaths::~GrCCPerOpListPaths() {
     // Ensure there are no surviving DrawPathsOps with a dangling pointer into this class.
     if (!fDrawOps.isEmpty()) {
@@ -69,69 +84,45 @@
 
 GrPathRenderer::CanDrawPath GrCoverageCountingPathRenderer::onCanDrawPath(
         const CanDrawPathArgs& args) const {
-    const GrShape& shape = *args.fShape;
-    if (GrAAType::kCoverage != args.fAAType || shape.style().hasPathEffect() ||
-        args.fViewMatrix->hasPerspective() || shape.inverseFilled()) {
+    if (!args.fShape->style().isSimpleFill() || args.fShape->inverseFilled() ||
+        args.fViewMatrix->hasPerspective() || GrAAType::kCoverage != args.fAAType) {
         return CanDrawPath::kNo;
     }
 
     SkPath path;
-    shape.asPath(&path);
+    args.fShape->asPath(&path);
 
-    switch (shape.style().strokeRec().getStyle()) {
-        case SkStrokeRec::kFill_Style: {
-            SkRect devBounds;
-            args.fViewMatrix->mapRect(&devBounds, path.getBounds());
+    SkRect devBounds;
+    args.fViewMatrix->mapRect(&devBounds, path.getBounds());
 
-            SkIRect clippedIBounds;
-            devBounds.roundOut(&clippedIBounds);
-            if (!clippedIBounds.intersect(*args.fClipConservativeBounds)) {
-                // The path is completely clipped away. Our code will eventually notice this before
-                // doing any real work.
-                return CanDrawPath::kYes;
-            }
-
-            int64_t numPixels = sk_64_mul(clippedIBounds.height(), clippedIBounds.width());
-            if (path.countVerbs() > 1000 && path.countPoints() > numPixels) {
-                // This is a complicated path that has more vertices than pixels! Let's let the SW
-                // renderer have this one: It will probably be faster and a bitmap will require less
-                // total memory on the GPU than CCPR instance buffers would for the raw path data.
-                return CanDrawPath::kNo;
-            }
-
-            if (numPixels > 256 * 256) {
-                // Large paths can blow up the atlas fast. And they are not ideal for a two-pass
-                // rendering algorithm. Give the simpler direct renderers a chance before we commit
-                // to drawing it.
-                return CanDrawPath::kAsBackup;
-            }
-
-            if (args.fShape->hasUnstyledKey() && path.countVerbs() > 50) {
-                // Complex paths do better cached in an SDF, if the renderer will accept them.
-                return CanDrawPath::kAsBackup;
-            }
-
-            return CanDrawPath::kYes;
-        }
-
-        case SkStrokeRec::kStroke_Style:
-            if (!args.fViewMatrix->isSimilarity()) {
-                // The stroker currently only supports rigid-body transfoms for the stroke lines
-                // themselves. This limitation doesn't affect hairlines since their stroke lines are
-                // defined relative to device space.
-                return CanDrawPath::kNo;
-            }
-            // fallthru
-        case SkStrokeRec::kHairline_Style:
-            // The stroker does not support conics yet.
-            return !SkPathPriv::ConicWeightCnt(path) ? CanDrawPath::kYes : CanDrawPath::kNo;
-
-        case SkStrokeRec::kStrokeAndFill_Style:
-            return CanDrawPath::kNo;
+    SkIRect clippedIBounds;
+    devBounds.roundOut(&clippedIBounds);
+    if (!clippedIBounds.intersect(*args.fClipConservativeBounds)) {
+        // Path is completely clipped away. Our code will eventually notice this before doing any
+        // real work.
+        return CanDrawPath::kYes;
     }
 
-    SK_ABORT("Invalid stroke style.");
-    return CanDrawPath::kNo;
+    int64_t numPixels = sk_64_mul(clippedIBounds.height(), clippedIBounds.width());
+    if (path.countVerbs() > 1000 && path.countPoints() > numPixels) {
+        // This is a complicated path that has more vertices than pixels! Let's let the SW renderer
+        // have this one: It will probably be faster and a bitmap will require less total memory on
+        // the GPU than CCPR instance buffers would for the raw path data.
+        return CanDrawPath::kNo;
+    }
+
+    if (numPixels > 256 * 256) {
+        // Large paths can blow up the atlas fast. And they are not ideal for a two-pass rendering
+        // algorithm. Give the simpler direct renderers a chance before we commit to drawing it.
+        return CanDrawPath::kAsBackup;
+    }
+
+    if (args.fShape->hasUnstyledKey() && path.countVerbs() > 50) {
+        // Complex paths do better cached in an SDF, if the renderer will accept them.
+        return CanDrawPath::kAsBackup;
+    }
+
+    return CanDrawPath::kYes;
 }
 
 bool GrCoverageCountingPathRenderer::onDrawPath(const DrawPathArgs& args) {
@@ -141,8 +132,24 @@
     GrRenderTargetContext* rtc = args.fRenderTargetContext;
     args.fClip->getConservativeBounds(rtc->width(), rtc->height(), &clipIBounds, nullptr);
 
-    auto op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, *args.fViewMatrix, *args.fShape,
-                                    std::move(args.fPaint));
+    SkRect devBounds;
+    args.fViewMatrix->mapRect(&devBounds, args.fShape->bounds());
+
+    std::unique_ptr<GrCCDrawPathsOp> op;
+    if (SkTMax(devBounds.height(), devBounds.width()) > kPathCropThreshold) {
+        // The path is too large. Crop it or analytic AA can run out of fp32 precision.
+        SkPath croppedPath;
+        args.fShape->asPath(&croppedPath);
+        croppedPath.transform(*args.fViewMatrix, &croppedPath);
+        crop_path(croppedPath, clipIBounds, &croppedPath);
+        // FIXME: This breaks local coords: http://skbug.com/8003
+        op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, SkMatrix::I(), GrShape(croppedPath),
+                                   croppedPath.getBounds(), std::move(args.fPaint));
+    } else {
+        op = GrCCDrawPathsOp::Make(args.fContext, clipIBounds, *args.fViewMatrix, *args.fShape,
+                                   devBounds, std::move(args.fPaint));
+    }
+
     this->recordOp(std::move(op), args);
     return true;
 }
@@ -173,7 +180,7 @@
             // The path is too large. Crop it or analytic AA can run out of fp32 precision.
             SkPath croppedPath;
             int maxRTSize = caps.maxRenderTargetSize();
-            CropPath(deviceSpacePath, SkIRect::MakeWH(maxRTSize, maxRTSize), &croppedPath);
+            crop_path(deviceSpacePath, SkIRect::MakeWH(maxRTSize, maxRTSize), &croppedPath);
             clipPath.init(croppedPath, accessRect, rtWidth, rtHeight, caps);
         } else {
             clipPath.init(deviceSpacePath, accessRect, rtWidth, rtHeight, caps);
@@ -249,14 +256,11 @@
 
     // Determine if there are enough reusable paths from last flush for it to be worth our time to
     // copy them to cached atlas(es).
-    int numCopies = specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kFillIdx] +
-                    specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kStrokeIdx];
-    DoCopiesToCache doCopies = DoCopiesToCache(numCopies > 100 ||
+    DoCopiesToCache doCopies = DoCopiesToCache(specs.fNumCopiedPaths > 100 ||
                                                specs.fCopyAtlasSpecs.fApproxNumPixels > 256 * 256);
-    if (numCopies && DoCopiesToCache::kNo == doCopies) {
+    if (specs.fNumCopiedPaths && DoCopiesToCache::kNo == doCopies) {
         specs.convertCopiesToRenders();
-        SkASSERT(!specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kFillIdx]);
-        SkASSERT(!specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kStrokeIdx]);
+        SkASSERT(!specs.fNumCopiedPaths);
     }
 
     auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, specs);
@@ -312,14 +316,3 @@
 
     SkDEBUGCODE(fFlushing = false);
 }
-
-void GrCoverageCountingPathRenderer::CropPath(const SkPath& path, const SkIRect& cropbox,
-                                              SkPath* out) {
-    SkPath cropboxPath;
-    cropboxPath.addRect(SkRect::Make(cropbox));
-    if (!Op(cropboxPath, path, kIntersect_SkPathOp, out)) {
-        // This can fail if the PathOps encounter NaN or infinities.
-        out->reset();
-    }
-    out->setIsVolatile(true);
-}
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
index bc336a5..0ec9aa0 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
@@ -70,12 +70,6 @@
     void testingOnly_drawPathDirectly(const DrawPathArgs&);
     const GrUniqueKey& testingOnly_getStashedAtlasKey() const;
 
-    // If a path spans more pixels than this, we need to crop it or else analytic AA can run out of
-    // fp32 precision.
-    static constexpr float kPathCropThreshold = 1 << 16;
-
-    static void CropPath(const SkPath&, const SkIRect& cropbox, SkPath* out);
-
 private:
     GrCoverageCountingPathRenderer(AllowCaching);