Refactor CCPR coverage shaders for a vertex impl

Decouples geometry generation and analytic coverage. This paves the
way for a vertex shader implementation.

Bug: skia:
Change-Id: I23b79d4397db22bd8fc063b8dfca58ab00037292
Reviewed-on: https://skia-review.googlesource.com/59200
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/src/gpu/ccpr/GrCCPRCoverageOp.cpp b/src/gpu/ccpr/GrCCPRCoverageOp.cpp
index f37fcbe..34e0410 100644
--- a/src/gpu/ccpr/GrCCPRCoverageOp.cpp
+++ b/src/gpu/ccpr/GrCCPRCoverageOp.cpp
@@ -385,7 +385,7 @@
 }
 
 void GrCCPRCoverageOp::onExecute(GrOpFlushState* flushState) {
-    using Mode = GrCCPRCoverageProcessor::Mode;
+    using RenderPass = GrCCPRCoverageProcessor::RenderPass;
 
     SkDEBUGCODE(GrCCPRCoverageProcessor::Validate(flushState->drawOpArgs().fProxy));
     SkASSERT(fPointsBuffer);
@@ -399,34 +399,34 @@
 
     // Triangles.
     auto constexpr kTrianglesGrPrimitiveType = GrCCPRCoverageProcessor::kTrianglesGrPrimitiveType;
-    this->drawMaskPrimitives(flushState, pipeline, Mode::kTriangleHulls,
+    this->drawMaskPrimitives(flushState, pipeline, RenderPass::kTriangleHulls,
                              kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles);
-    this->drawMaskPrimitives(flushState, pipeline, Mode::kTriangleEdges,
+    this->drawMaskPrimitives(flushState, pipeline, RenderPass::kTriangleEdges,
                              kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles);
-    this->drawMaskPrimitives(flushState, pipeline, Mode::kTriangleCorners,
+    this->drawMaskPrimitives(flushState, pipeline, RenderPass::kTriangleCorners,
                              kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles);
 
     // Quadratics.
     auto constexpr kQuadraticsGrPrimitiveType = GrCCPRCoverageProcessor::kQuadraticsGrPrimitiveType;
-    this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticHulls,
+    this->drawMaskPrimitives(flushState, pipeline, RenderPass::kQuadraticHulls,
                              kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics);
-    this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticCorners,
+    this->drawMaskPrimitives(flushState, pipeline, RenderPass::kQuadraticCorners,
                              kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics);
 
     // Cubics.
     auto constexpr kCubicsGrPrimitiveType = GrCCPRCoverageProcessor::kCubicsGrPrimitiveType;
-    this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineHulls,
+    this->drawMaskPrimitives(flushState, pipeline, RenderPass::kSerpentineHulls,
                              kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines);
-    this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopHulls,
+    this->drawMaskPrimitives(flushState, pipeline, RenderPass::kLoopHulls,
                              kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops);
-    this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineCorners,
+    this->drawMaskPrimitives(flushState, pipeline, RenderPass::kSerpentineCorners,
                              kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines);
-    this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopCorners,
+    this->drawMaskPrimitives(flushState, pipeline, RenderPass::kLoopCorners,
                              kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops);
 }
 
 void GrCCPRCoverageOp::drawMaskPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline,
-                                          GrCCPRCoverageProcessor::Mode mode,
+                                          GrCCPRCoverageProcessor::RenderPass renderPass,
                                           GrPrimitiveType primType, int vertexCount,
                                           int PrimitiveTallies::* instanceType) const {
     using ScissorMode = GrCCPRCoverageOpsBuilder::ScissorMode;
@@ -463,7 +463,7 @@
     SkASSERT(fMeshesScratchBuffer.count() == fDynamicStatesScratchBuffer.count());
 
     if (!fMeshesScratchBuffer.empty()) {
-        GrCCPRCoverageProcessor proc(mode, fPointsBuffer.get());
+        GrCCPRCoverageProcessor proc(renderPass, fPointsBuffer.get());
         SkASSERT(flushState->rtCommandBuffer());
         flushState->rtCommandBuffer()->draw(pipeline, proc, fMeshesScratchBuffer.begin(),
                                             fDynamicStatesScratchBuffer.begin(),
diff --git a/src/gpu/ccpr/GrCCPRCoverageOp.h b/src/gpu/ccpr/GrCCPRCoverageOp.h
index 7d457fe..80b4cf9 100644
--- a/src/gpu/ccpr/GrCCPRCoverageOp.h
+++ b/src/gpu/ccpr/GrCCPRCoverageOp.h
@@ -149,9 +149,9 @@
                     const PrimitiveTallies baseInstances[kNumScissorModes],
                     const PrimitiveTallies endInstances[kNumScissorModes]);
 
-    void drawMaskPrimitives(GrOpFlushState*, const GrPipeline&, const GrCCPRCoverageProcessor::Mode,
-                            GrPrimitiveType, int vertexCount,
-                            int PrimitiveTallies::* instanceType) const;
+    void drawMaskPrimitives(GrOpFlushState*, const GrPipeline&,
+                            const GrCCPRCoverageProcessor::RenderPass, GrPrimitiveType,
+                            int vertexCount, int PrimitiveTallies::* instanceType) const;
 
     sk_sp<GrBuffer>                      fPointsBuffer;
     sk_sp<GrBuffer>                      fInstanceBuffer;
diff --git a/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp b/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp
index d27ea4c..6fa2987 100644
--- a/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp
@@ -7,323 +7,91 @@
 
 #include "GrCCPRCoverageProcessor.h"
 
-#include "GrRenderTargetProxy.h"
-#include "ccpr/GrCCPRTriangleProcessor.h"
-#include "ccpr/GrCCPRQuadraticProcessor.h"
-#include "ccpr/GrCCPRCubicProcessor.h"
+#include "SkMakeUnique.h"
+#include "ccpr/GrCCPRCubicShader.h"
+#include "ccpr/GrCCPRQuadraticShader.h"
+#include "ccpr/GrCCPRTriangleShader.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLGeometryShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "glsl/GrGLSLVertexShaderBuilder.h"
 
-const char* GrCCPRCoverageProcessor::GetProcessorName(Mode mode) {
-    switch (mode) {
-        case Mode::kTriangleHulls:
-            return "GrCCPRTriangleHullAndEdgeProcessor (hulls)";
-        case Mode::kTriangleEdges:
-            return "GrCCPRTriangleHullAndEdgeProcessor (edges)";
-        case Mode::kTriangleCorners:
-            return "GrCCPRTriangleCornerProcessor";
-        case Mode::kQuadraticHulls:
-            return "GrCCPRQuadraticHullProcessor";
-        case Mode::kQuadraticCorners:
-            return "GrCCPRQuadraticCornerProcessor";
-        case Mode::kSerpentineHulls:
-            return "GrCCPRCubicHullProcessor (serpentine)";
-        case Mode::kLoopHulls:
-            return "GrCCPRCubicHullProcessor (loop)";
-        case Mode::kSerpentineCorners:
-            return "GrCCPRCubicCornerProcessor (serpentine)";
-        case Mode::kLoopCorners:
-            return "GrCCPRCubicCornerProcessor (loop)";
+static GrVertexAttribType instance_array_format(GrCCPRCoverageProcessor::RenderPass renderPass) {
+    switch (renderPass) {
+        case GrCCPRCoverageProcessor::RenderPass::kTriangleHulls:
+        case GrCCPRCoverageProcessor::RenderPass::kTriangleEdges:
+        case GrCCPRCoverageProcessor::RenderPass::kTriangleCorners:
+            return kInt4_GrVertexAttribType;
+        case GrCCPRCoverageProcessor::RenderPass::kQuadraticHulls:
+        case GrCCPRCoverageProcessor::RenderPass::kQuadraticCorners:
+        case GrCCPRCoverageProcessor::RenderPass::kSerpentineHulls:
+        case GrCCPRCoverageProcessor::RenderPass::kLoopHulls:
+        case GrCCPRCoverageProcessor::RenderPass::kSerpentineCorners:
+        case GrCCPRCoverageProcessor::RenderPass::kLoopCorners:
+            return kInt2_GrVertexAttribType;
     }
-    SK_ABORT("Unexpected ccpr coverage processor mode.");
-    return nullptr;
+    SK_ABORT("Unexpected GrCCPRCoverageProcessor::RenderPass.");
+    return kInt4_GrVertexAttribType;
 }
 
-GrCCPRCoverageProcessor::GrCCPRCoverageProcessor(Mode mode, GrBuffer* pointsBuffer)
+GrCCPRCoverageProcessor::GrCCPRCoverageProcessor(RenderPass renderPass, GrBuffer* pointsBuffer)
         : INHERITED(kGrCCPRCoverageProcessor_ClassID)
-        , fMode(mode)
-        , fInstanceAttrib(this->addInstanceAttrib("instance", InstanceArrayFormat(mode))) {
+        , fRenderPass(renderPass)
+        , fInstanceAttrib(this->addInstanceAttrib("instance", instance_array_format(fRenderPass))) {
     fPointsBufferAccess.reset(kRG_float_GrPixelConfig, pointsBuffer, kVertex_GrShaderFlag);
     this->addBufferAccess(&fPointsBufferAccess);
 
     this->setWillUseGeoShader();
 }
 
-void GrCCPRCoverageProcessor::getGLSLProcessorKey(const GrShaderCaps&,
-                                                  GrProcessorKeyBuilder* b) const {
-    b->add32(int(fMode));
-}
-
-GrGLSLPrimitiveProcessor* GrCCPRCoverageProcessor::createGLSLInstance(const GrShaderCaps&) const {
-    switch (fMode) {
-        using GeometryType = GrCCPRTriangleHullAndEdgeProcessor::GeometryType;
-
-        case Mode::kTriangleHulls:
-            return new GrCCPRTriangleHullAndEdgeProcessor(GeometryType::kHulls);
-        case Mode::kTriangleEdges:
-            return new GrCCPRTriangleHullAndEdgeProcessor(GeometryType::kEdges);
-        case Mode::kTriangleCorners:
-            return new GrCCPRTriangleCornerProcessor();
-        case Mode::kQuadraticHulls:
-            return new GrCCPRQuadraticHullProcessor();
-        case Mode::kQuadraticCorners:
-            return new GrCCPRQuadraticCornerProcessor();
-        case Mode::kSerpentineHulls:
-            return new GrCCPRCubicHullProcessor(GrCCPRCubicProcessor::CubicType::kSerpentine);
-        case Mode::kLoopHulls:
-            return new GrCCPRCubicHullProcessor(GrCCPRCubicProcessor::CubicType::kLoop);
-        case Mode::kSerpentineCorners:
-            return new GrCCPRCubicCornerProcessor(GrCCPRCubicProcessor::CubicType::kSerpentine);
-        case Mode::kLoopCorners:
-            return new GrCCPRCubicCornerProcessor(GrCCPRCubicProcessor::CubicType::kLoop);
+void GrCCPRCoverageProcessor::Shader::emitVaryings(GrGLSLVaryingHandler* varyingHandler,
+                                                   SkString* code, const char* position,
+                                                   const char* coverage, const char* wind) {
+    WindHandling windHandling = this->onEmitVaryings(varyingHandler, code, position, coverage,
+                                                     wind);
+    if (WindHandling::kNotHandled == windHandling) {
+        varyingHandler->addFlatVarying("wind", &fWind, kLow_GrSLPrecision);
+        code->appendf("%s = %s;", fWind.gsOut(), wind);
     }
-    SK_ABORT("Unexpected ccpr coverage processor mode.");
-    return nullptr;
 }
 
-using PrimitiveProcessor = GrCCPRCoverageProcessor::PrimitiveProcessor;
-
-void PrimitiveProcessor::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
-    const GrCCPRCoverageProcessor& proc = args.fGP.cast<GrCCPRCoverageProcessor>();
-
-    GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
-    switch (fCoverageType) {
-        case CoverageType::kOne:
-        case CoverageType::kShader:
-            varyingHandler->addFlatVarying("wind", &fFragWind, kLow_GrSLPrecision);
-            break;
-        case CoverageType::kInterpolated:
-            varyingHandler->addVarying("coverage_times_wind", &fFragCoverageTimesWind,
-                                       kMedium_GrSLPrecision);
-            break;
+void GrCCPRCoverageProcessor::Shader::emitFragmentCode(const GrCCPRCoverageProcessor& proc,
+                                                       GrGLSLPPFragmentBuilder* f,
+                                                       const char* skOutputColor,
+                                                       const char* skOutputCoverage) const {
+    f->codeAppendf("half coverage = 0;");
+    this->onEmitFragmentCode(f, "coverage");
+    if (fWind.fsIn()) {
+        f->codeAppendf("%s.a = coverage * %s;", skOutputColor, fWind.fsIn());
+    } else {
+        f->codeAppendf("%s.a = coverage;", skOutputColor);
     }
-    this->resetVaryings(varyingHandler);
-
-    varyingHandler->emitAttributes(proc);
-
-    this->emitVertexShader(proc, args.fVertBuilder, args.fTexelBuffers[0], args.fRTAdjustName,
-                           gpArgs);
-    this->emitGeometryShader(proc, args.fGeomBuilder, args.fRTAdjustName);
-    this->emitCoverage(proc, args.fFragBuilder, args.fOutputColor, args.fOutputCoverage);
-
-    SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform());
-}
-
-void PrimitiveProcessor::emitVertexShader(const GrCCPRCoverageProcessor& proc,
-                                          GrGLSLVertexBuilder* v,
-                                          const TexelBufferHandle& pointsBuffer,
-                                          const char* rtAdjust, GrGPArgs* gpArgs) const {
-    v->codeAppendf("int packedoffset = %s[%i];", proc.instanceAttrib(), proc.atlasOffsetIdx());
-    v->codeAppend ("float2 atlasoffset = float2((packedoffset<<16) >> 16, "
-                                                       "packedoffset >> 16);");
-
-    this->onEmitVertexShader(proc, v, pointsBuffer, "atlasoffset", rtAdjust, gpArgs);
-}
-
-void PrimitiveProcessor::emitGeometryShader(const GrCCPRCoverageProcessor& proc,
-                                            GrGLSLGeometryBuilder* g, const char* rtAdjust) const {
-    g->declareGlobal(fGeomWind);
-    this->emitWind(g, rtAdjust, fGeomWind.c_str());
-
-    SkString emitVertexFn;
-    SkSTArray<2, GrShaderVar> emitArgs;
-    const char* position = emitArgs.emplace_back("position", kFloat2_GrSLType,
-                                                 GrShaderVar::kNonArray).c_str();
-    const char* coverage = emitArgs.emplace_back("coverage", kFloat_GrSLType,
-                                                 GrShaderVar::kNonArray).c_str();
-    g->emitFunction(kVoid_GrSLType, "emitVertex", emitArgs.count(), emitArgs.begin(), [&]() {
-        SkString fnBody;
-        this->emitPerVertexGeometryCode(&fnBody, position, coverage, fGeomWind.c_str());
-        if (fFragWind.gsOut()) {
-            fnBody.appendf("%s = %s;", fFragWind.gsOut(), fGeomWind.c_str());
-        }
-        if (fFragCoverageTimesWind.gsOut()) {
-            fnBody.appendf("%s = %s * %s;",
-                           fFragCoverageTimesWind.gsOut(), coverage, fGeomWind.c_str());
-        }
-        fnBody.append ("sk_Position = float4(position, 0, 1);");
-        fnBody.append ("EmitVertex();");
-        return fnBody;
-    }().c_str(), &emitVertexFn);
-
-    g->codeAppendf("float2 bloat = %f * abs(%s.xz);", kAABloatRadius, rtAdjust);
-
+    f->codeAppendf("%s = half4(1);", skOutputCoverage);
 #ifdef SK_DEBUG
     if (proc.debugVisualizationsEnabled()) {
-        g->codeAppendf("bloat *= %f;", proc.debugBloat());
+        f->codeAppendf("%s = half4(-%s.a, %s.a, 0, 1);",
+                       skOutputColor, skOutputColor, skOutputColor);
     }
 #endif
-
-    return this->onEmitGeometryShader(g, emitVertexFn.c_str(), fGeomWind.c_str(), rtAdjust);
 }
 
-int PrimitiveProcessor::emitHullGeometry(GrGLSLGeometryBuilder* g, const char* emitVertexFn,
-                                         const char* polygonPts, int numSides,
-                                         const char* wedgeIdx, const char* midpoint) const {
-    SkASSERT(numSides >= 3);
-
-    if (!midpoint) {
-        g->codeAppendf("float2 midpoint = %s * float%i(%f);",
-                       polygonPts, numSides, 1.0 / numSides);
-        midpoint = "midpoint";
-    }
-
-    g->codeAppendf("int previdx = (%s + %i) %% %i, "
-                       "nextidx = (%s + 1) %% %i;",
-                   wedgeIdx, numSides - 1, numSides, wedgeIdx, numSides);
-
-    g->codeAppendf("float2 self = %s[%s];"
-                   "int leftidx = %s > 0 ? previdx : nextidx;"
-                   "int rightidx = %s > 0 ? nextidx : previdx;",
-                   polygonPts, wedgeIdx, fGeomWind.c_str(), fGeomWind.c_str());
-
-    // Which quadrant does the vector from self -> right fall into?
-    g->codeAppendf("float2 right = %s[rightidx];", polygonPts);
-    if (3 == numSides) {
-        // TODO: evaluate perf gains.
-        g->codeAppend ("float2 qsr = sign(right - self);");
-    } else {
-        SkASSERT(4 == numSides);
-        g->codeAppendf("float2 diag = %s[(%s + 2) %% 4];", polygonPts, wedgeIdx);
-        g->codeAppend ("float2 qsr = sign((right != self ? right : diag) - self);");
-    }
-
-    // Which quadrant does the vector from left -> self fall into?
-    g->codeAppendf("float2 qls = sign(self - %s[leftidx]);", polygonPts);
-
-    // d2 just helps us reduce triangle counts with orthogonal, axis-aligned lines.
-    // TODO: evaluate perf gains.
-    const char* dr2 = "dr";
-    if (3 == numSides) {
-        // TODO: evaluate perf gains.
-        g->codeAppend ("float2 dr = float2(qsr.y != 0 ? +qsr.y : +qsr.x, "
-                                                  "qsr.x != 0 ? -qsr.x : +qsr.y);");
-        g->codeAppend ("float2 dr2 = float2(qsr.y != 0 ? +qsr.y : -qsr.x, "
-                                                   "qsr.x != 0 ? -qsr.x : -qsr.y);");
-        g->codeAppend ("float2 dl = float2(qls.y != 0 ? +qls.y : +qls.x, "
-                                                  "qls.x != 0 ? -qls.x : +qls.y);");
-        dr2 = "dr2";
-    } else {
-        g->codeAppend ("float2 dr = float2(qsr.y != 0 ? +qsr.y : 1, "
-                                                  "qsr.x != 0 ? -qsr.x : 1);");
-        g->codeAppend ("float2 dl = (qls == float2(0)) ? dr : "
-                                   "float2(qls.y != 0 ? +qls.y : 1, qls.x != 0 ? -qls.x : 1);");
-    }
-    g->codeAppendf("bool2 dnotequal = notEqual(%s, dl);", dr2);
-
-    // Emit one third of what is the convex hull of pixel-size boxes centered on the vertices.
-    // Each invocation emits a different third.
-    g->codeAppendf("%s(right + bloat * dr, 1);", emitVertexFn);
-    g->codeAppendf("%s(%s, 1);", emitVertexFn, midpoint);
-    g->codeAppendf("%s(self + bloat * %s, 1);", emitVertexFn, dr2);
-    g->codeAppend ("if (any(dnotequal)) {");
-    g->codeAppendf(    "%s(self + bloat * dl, 1);", emitVertexFn);
-    g->codeAppend ("}");
-    g->codeAppend ("if (all(dnotequal)) {");
-    g->codeAppendf(    "%s(self + bloat * float2(-dl.y, dl.x), 1);", emitVertexFn);
-    g->codeAppend ("}");
-    g->codeAppend ("EndPrimitive();");
-
-    return 5;
-}
-
-int PrimitiveProcessor::emitEdgeGeometry(GrGLSLGeometryBuilder* g, const char* emitVertexFn,
-                                         const char* leftPt, const char* rightPt,
-                                         const char* distanceEquation) const {
-    if (!distanceEquation) {
-        this->emitEdgeDistanceEquation(g, leftPt, rightPt, "float3 edge_distance_equation");
-        distanceEquation = "edge_distance_equation";
-    }
-
-    // qlr is defined in emitEdgeDistanceEquation.
-    g->codeAppendf("float2x2 endpts = float2x2(%s - bloat * qlr, %s + bloat * qlr);",
-                   leftPt, rightPt);
-    g->codeAppendf("half2 endpts_coverage = %s.xy * endpts + %s.z;",
-                   distanceEquation, distanceEquation);
-
-    // d1 is defined in emitEdgeDistanceEquation.
-    g->codeAppend ("float2 d2 = d1;");
-    g->codeAppend ("bool aligned = qlr.x == 0 || qlr.y == 0;");
-    g->codeAppend ("if (aligned) {");
-    g->codeAppend (    "d1 -= qlr;");
-    g->codeAppend (    "d2 += qlr;");
-    g->codeAppend ("}");
-
-    // Emit the convex hull of 2 pixel-size boxes centered on the endpoints of the edge. Each
-    // invocation emits a different edge. Emit negative coverage that subtracts the appropiate
-    // amount back out from the hull we drew above.
-    g->codeAppend ("if (!aligned) {");
-    g->codeAppendf(    "%s(endpts[0], endpts_coverage[0]);", emitVertexFn);
-    g->codeAppend ("}");
-    g->codeAppendf("%s(%s + bloat * d1, -1);", emitVertexFn, leftPt);
-    g->codeAppendf("%s(%s - bloat * d2, 0);", emitVertexFn, leftPt);
-    g->codeAppendf("%s(%s + bloat * d2, -1);", emitVertexFn, rightPt);
-    g->codeAppendf("%s(%s - bloat * d1, 0);", emitVertexFn, rightPt);
-    g->codeAppend ("if (!aligned) {");
-    g->codeAppendf(    "%s(endpts[1], endpts_coverage[1]);", emitVertexFn);
-    g->codeAppend ("}");
-    g->codeAppend ("EndPrimitive();");
-
-    return 6;
-}
-
-void PrimitiveProcessor::emitEdgeDistanceEquation(GrGLSLGeometryBuilder* g,
-                                                  const char* leftPt, const char* rightPt,
-                                                  const char* outputDistanceEquation) const {
+void GrCCPRCoverageProcessor::Shader::EmitEdgeDistanceEquation(GrGLSLShaderBuilder* s,
+                                                               const char* leftPt,
+                                                               const char* rightPt,
+                                                               const char* outputDistanceEquation) {
     // Which quadrant does the vector from left -> right fall into?
-    g->codeAppendf("float2 qlr = sign(%s - %s);", rightPt, leftPt);
-    g->codeAppend ("float2 d1 = float2(qlr.y, -qlr.x);");
+    s->codeAppendf("float2 qlr = sign(%s - %s);", rightPt, leftPt);
+    s->codeAppend ("float2 d1 = float2(qlr.y, -qlr.x);");
 
-    g->codeAppendf("float2 n = float2(%s.y - %s.y, %s.x - %s.x);",
+    s->codeAppendf("float2 n = float2(%s.y - %s.y, %s.x - %s.x);",
                    rightPt, leftPt, leftPt, rightPt);
-    g->codeAppendf("float2 kk = n * float2x2(%s + bloat * d1, %s - bloat * d1);",
+    s->codeAppendf("float2 kk = n * float2x2(%s + bloat * d1, %s - bloat * d1);",
                    leftPt, leftPt);
     // Clamp for when n=0. wind=0 when n=0 so as long as we don't get Inf or NaN we are fine.
-    g->codeAppendf("float scale = 1 / max(kk[0] - kk[1], 1e-30);");
+    s->codeAppendf("float scale = 1 / max(kk[0] - kk[1], 1e-30);");
 
-    g->codeAppendf("%s = half3(-n, kk[1]) * scale;", outputDistanceEquation);
+    s->codeAppendf("%s = half3(-n, kk[1]) * scale;", outputDistanceEquation);
 }
 
-int PrimitiveProcessor::emitCornerGeometry(GrGLSLGeometryBuilder* g, const char* emitVertexFn,
-                                           const char* pt) const {
-    g->codeAppendf("%s(%s + float2(-bloat.x, -bloat.y), 1);", emitVertexFn, pt);
-    g->codeAppendf("%s(%s + float2(-bloat.x, +bloat.y), 1);", emitVertexFn, pt);
-    g->codeAppendf("%s(%s + float2(+bloat.x, -bloat.y), 1);", emitVertexFn, pt);
-    g->codeAppendf("%s(%s + float2(+bloat.x, +bloat.y), 1);", emitVertexFn, pt);
-    g->codeAppend ("EndPrimitive();");
-
-    return 4;
-}
-
-void PrimitiveProcessor::emitCoverage(const GrCCPRCoverageProcessor& proc, GrGLSLFragmentBuilder* f,
-                                      const char* outputColor, const char* outputCoverage) const {
-    switch (fCoverageType) {
-        case CoverageType::kOne:
-            f->codeAppendf("%s.a = %s;", outputColor, fFragWind.fsIn());
-            break;
-        case CoverageType::kInterpolated:
-            f->codeAppendf("%s.a = %s;", outputColor, fFragCoverageTimesWind.fsIn());
-            break;
-        case CoverageType::kShader:
-            f->codeAppendf("half coverage = 0;");
-            this->emitShaderCoverage(f, "coverage");
-            f->codeAppendf("%s.a = coverage * %s;", outputColor, fFragWind.fsIn());
-            break;
-    }
-
-    f->codeAppendf("%s = half4(1);", outputCoverage);
-
-#ifdef SK_DEBUG
-    if (proc.debugVisualizationsEnabled()) {
-        f->codeAppendf("%s = half4(-%s.a, %s.a, 0, 1);", outputColor, outputColor, outputColor);
-    }
-#endif
-}
-
-int PrimitiveProcessor::defineSoftSampleLocations(GrGLSLFragmentBuilder* f,
-                                                  const char* samplesName) const {
+int GrCCPRCoverageProcessor::Shader::DefineSoftSampleLocations(GrGLSLPPFragmentBuilder* f,
+                                                               const char* samplesName) {
     // Standard DX11 sample locations.
 #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_IOS)
     f->defineConstant("float2[8]", samplesName, "float2[8]("
@@ -342,9 +110,74 @@
 #endif
 }
 
+void GrCCPRCoverageProcessor::getGLSLProcessorKey(const GrShaderCaps&,
+                                                  GrProcessorKeyBuilder* b) const {
+    b->add32((int)fRenderPass);
+}
+
+GrGLSLPrimitiveProcessor* GrCCPRCoverageProcessor::createGLSLInstance(const GrShaderCaps&) const {
+    std::unique_ptr<Shader> shader;
+    switch (fRenderPass) {
+        using CubicType = GrCCPRCubicShader::CubicType;
+        case RenderPass::kTriangleHulls:
+            shader = skstd::make_unique<GrCCPRTriangleHullShader>();
+            break;
+        case RenderPass::kTriangleEdges:
+            shader = skstd::make_unique<GrCCPRTriangleEdgeShader>();
+            break;
+        case RenderPass::kTriangleCorners:
+            shader = skstd::make_unique<GrCCPRTriangleCornerShader>();
+            break;
+        case RenderPass::kQuadraticHulls:
+            shader = skstd::make_unique<GrCCPRQuadraticHullShader>();
+            break;
+        case RenderPass::kQuadraticCorners:
+            shader = skstd::make_unique<GrCCPRQuadraticCornerShader>();
+            break;
+        case RenderPass::kSerpentineHulls:
+            shader = skstd::make_unique<GrCCPRCubicHullShader>(CubicType::kSerpentine);
+            break;
+        case RenderPass::kLoopHulls:
+            shader = skstd::make_unique<GrCCPRCubicHullShader>(CubicType::kLoop);
+            break;
+        case RenderPass::kSerpentineCorners:
+            shader = skstd::make_unique<GrCCPRCubicCornerShader>(CubicType::kSerpentine);
+            break;
+        case RenderPass::kLoopCorners:
+            shader = skstd::make_unique<GrCCPRCubicCornerShader>(CubicType::kLoop);
+            break;
+    }
+    return CreateGSImpl(std::move(shader));
+}
+
+const char* GrCCPRCoverageProcessor::GetRenderPassName(RenderPass renderPass) {
+    switch (renderPass) {
+        case RenderPass::kTriangleHulls:
+            return "RenderPass::kTriangleHulls";
+        case RenderPass::kTriangleEdges:
+            return "RenderPass::kTriangleEdges";
+        case RenderPass::kTriangleCorners:
+            return "RenderPass::kTriangleCorners";
+        case RenderPass::kQuadraticHulls:
+            return "RenderPass::kQuadraticHulls";
+        case RenderPass::kQuadraticCorners:
+            return "RenderPass::kQuadraticCorners";
+        case RenderPass::kSerpentineHulls:
+            return "RenderPass::kSerpentineHulls";
+        case RenderPass::kLoopHulls:
+            return "RenderPass::kLoopHulls";
+        case RenderPass::kSerpentineCorners:
+            return "RenderPass::kSerpentineCorners";
+        case RenderPass::kLoopCorners:
+            return "RenderPass::kLoopCorners";
+    }
+    SK_ABORT("Unexpected GrCCPRCoverageProcessor::RenderPass.");
+    return nullptr;
+}
+
 #ifdef SK_DEBUG
 
-#include "GrRenderTarget.h"
+#include "GrRenderTargetProxy.h"
 
 void GrCCPRCoverageProcessor::Validate(GrRenderTargetProxy* atlasProxy) {
     SkASSERT(kAtlasOrigin == atlasProxy->origin());
diff --git a/src/gpu/ccpr/GrCCPRCoverageProcessor.h b/src/gpu/ccpr/GrCCPRCoverageProcessor.h
index f02b742..f2c5f43 100644
--- a/src/gpu/ccpr/GrCCPRCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCPRCoverageProcessor.h
@@ -12,21 +12,22 @@
 #include "glsl/GrGLSLGeometryProcessor.h"
 #include "glsl/GrGLSLVarying.h"
 
-class GrGLSLFragmentBuilder;
+class GrGLSLPPFragmentBuilder;
+class GrGLSLShaderBuilder;
 
 /**
  * This is the geometry processor for the simple convex primitive shapes (triangles and closed curve
  * segments) from which ccpr paths are composed. The output is a single-channel alpha value,
- * positive for clockwise primitives and negative for counter-clockwise, that indicates coverage.
+ * positive for clockwise shapes and negative for counter-clockwise, that indicates coverage.
  *
- * The caller is responsible to render all modes for all applicable primitives into a cleared,
- * floating point, alpha-only render target using SkBlendMode::kPlus. Once all of a path's
- * primitives have been drawn, the render target contains a composite coverage count that can then
- * be used to draw the path (see GrCCPRPathProcessor).
+ * The caller is responsible to execute all render passes for all applicable primitives into a
+ * cleared, floating point, alpha-only render target using SkBlendMode::kPlus (see RenderPass
+ * below). Once all of a path's primitives have been drawn, the render target contains a composite
+ * coverage count that can then be used to draw the path (see GrCCPRPathProcessor).
  *
- * Caller provides the primitives' (x,y) points in an fp32x2 (RG) texel buffer, and an instance
- * buffer with a single int32x4 attrib (for triangles) or int32x2 (for curves) defined below. There
- * are no vertex attribs.
+ * Caller provides the primitives' (x,y) input points in an fp32x2 (RG) texel buffer, and an
+ * instance buffer with a single int32x4 attrib (for triangles) or int32x2 (for curves) defined
+ * below. There are no vertex attribs.
  *
  * Draw calls are instanced, with one vertex per bezier point (3 for triangles). They use the
  * corresponding GrPrimitiveType as defined below.
@@ -56,7 +57,12 @@
 
     GR_STATIC_ASSERT(2 * 4 == sizeof(CurveInstance));
 
-    enum class Mode {
+    /**
+     * All primitive shapes (triangles and convex closed curve segments) require more than one
+     * render pass. Here we enumerate every render pass needed in order to produce a complete
+     * coverage count mask. This is an exhaustive list of all ccpr coverage shaders.
+     */
+    enum class RenderPass {
         // Triangles.
         kTriangleHulls,
         kTriangleEdges,
@@ -72,22 +78,132 @@
         kSerpentineCorners,
         kLoopCorners
     };
-    static constexpr GrVertexAttribType InstanceArrayFormat(Mode mode) {
-        return mode < Mode::kQuadraticHulls ? kInt4_GrVertexAttribType : kInt2_GrVertexAttribType;
-    }
-    static const char* GetProcessorName(Mode);
 
-    GrCCPRCoverageProcessor(Mode, GrBuffer* pointsBuffer);
+    static const char* GetRenderPassName(RenderPass);
+
+    /**
+     * This serves as the base class for each RenderPass's Shader. It indicates what type of
+     * geometry the Impl should generate and provides implementation-independent code to process
+     * the inputs and calculate coverage in the fragment Shader.
+     */
+    class Shader {
+    public:
+        using TexelBufferHandle = GrGLSLGeometryProcessor::TexelBufferHandle;
+
+        // This enum specifies the type of geometry that should be generated for a Shader instance.
+        // Subclasses are limited to three built-in types of geometry to choose from:
+        enum class GeometryType {
+            // Generates a conservative raster hull around the input points. This is the geometry
+            // that causes a pixel to be rasterized if it is touched anywhere by the input polygon.
+            // Coverage is +1 all around.
+            //
+            // Logically, the conservative raster hull is equivalent to the convex hull of pixel
+            // size boxes centered around each input point.
+            kHull,
+
+            // Generates the conservative rasters of the input edges (i.e. convex hull of two
+            // pixel-size boxes centered on both endpoints). Coverage is -1 on the outside border of
+            // the edge geometry and 0 on the inside. This is the only geometry type that associates
+            // coverage values with the output points. It effectively converts a jagged conservative
+            // raster edge into a smooth antialiased edge.
+            kEdges,
+
+            // Generates the conservative rasters of the corners specified by the geometry provider
+            // (i.e. pixel-size box centered on the corner point). Coverage is +1 all around.
+            kCorners
+        };
+
+        virtual GeometryType getGeometryType() const = 0;
+        virtual int getNumInputPoints() const = 0;
+
+        // Returns the number of independent geometric segments to generate for the render pass
+        // (number of wedges for a hull, number of edges, or number of corners.)
+        virtual int getNumSegments() const = 0;
+
+        // Appends an expression that fetches input point # "pointId" from the texel buffer.
+        virtual void appendInputPointFetch(const GrCCPRCoverageProcessor&, GrGLSLShaderBuilder*,
+                                           const TexelBufferHandle& pointsBuffer,
+                                           const char* pointId) const = 0;
+
+        // Determines the winding direction of the primitive. The subclass must write a value of
+        // either -1, 0, or +1 to "outputWind" (e.g. "sign(area)"). Fractional values are not valid.
+        virtual void emitWind(GrGLSLShaderBuilder*, const char* pts, const char* rtAdjust,
+                              const char* outputWind) const = 0;
+
+        union GeometryVars {
+            struct {
+                const char* fAlternatePoints; // floatNx2 (if left null, will use input points).
+                const char* fAlternateMidpoint; // float2 (if left null, finds euclidean midpoint).
+            } fHullVars;
+
+            struct {
+                const char* fPoint; // float2
+            } fCornerVars;
+
+            GeometryVars() { memset(this, 0, sizeof(*this)); }
+        };
+
+        // Called before generating geometry. Subclasses must fill out the applicable fields in
+        // GeometryVars (if any), and may also use this opportunity to setup internal member
+        // variables that will be needed during onEmitVaryings (e.g. transformation matrices).
+        virtual void emitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
+                                   const char* bloat, const char* wind, const char* rtAdjust,
+                                   GeometryVars*) const {}
+
+        void emitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
+                          const char* coverage, const char* wind);
+
+        void emitFragmentCode(const GrCCPRCoverageProcessor& proc, GrGLSLPPFragmentBuilder*,
+                              const char* skOutputColor, const char* skOutputCoverage) const;
+
+        // Defines an equation ("dot(float3(pt, 1), distance_equation)") that is -1 on the outside
+        // border of a conservative raster edge and 0 on the inside (see emitEdgeGeometry).
+        static void EmitEdgeDistanceEquation(GrGLSLShaderBuilder*, const char* leftPt,
+                                             const char* rightPt,
+                                             const char* outputDistanceEquation);
+
+        // Defines a global float2 array that contains MSAA sample locations as offsets from pixel
+        // center. Subclasses can use this for software multisampling.
+        //
+        // Returns the number of samples.
+        static int DefineSoftSampleLocations(GrGLSLPPFragmentBuilder* f, const char* samplesName);
+
+        virtual ~Shader() {}
+
+    protected:
+        enum class WindHandling : bool {
+            kHandled,
+            kNotHandled
+        };
+
+        // Here the subclass adds its internal varyings to the handler and produces code to
+        // initialize those varyings from a given position, coverage value, and wind.
+        //
+        // Returns whether the subclass will handle wind modulation or if this base class should
+        // take charge of multiplying the final coverage output by "wind".
+        //
+        // NOTE: the coverage parameter is only relevant for edges (see comments in GeometryType).
+        // Otherwise it is +1 all around.
+        virtual WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code,
+                                            const char* position, const char* coverage,
+                                            const char* wind) = 0;
+
+        // Emits the fragment code that calculates a pixel's coverage value. If using
+        // WindHandling::kHandled, this value must be signed appropriately.
+        virtual void onEmitFragmentCode(GrGLSLPPFragmentBuilder*,
+                                        const char* outputCoverage) const = 0;
+
+    private:
+        GrGLSLGeoToFrag fWind{kHalf_GrSLType};
+    };
+
+    GrCCPRCoverageProcessor(RenderPass, GrBuffer* pointsBuffer);
 
     const char* instanceAttrib() const { return fInstanceAttrib.fName; }
-    int atlasOffsetIdx() const {
-        return kInt4_GrVertexAttribType == InstanceArrayFormat(fMode) ? 3 : 1;
-    }
-    const char* name() const override { return GetProcessorName(fMode); }
+    const char* name() const override { return GetRenderPassName(fRenderPass); }
     SkString dumpInfo() const override {
         return SkStringPrintf("%s\n%s", this->name(), this->INHERITED::dumpInfo().c_str());
     }
-
     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
 
@@ -101,152 +217,27 @@
     static void Validate(GrRenderTargetProxy* atlasProxy);
 #endif
 
-    class PrimitiveProcessor;
+    class GSImpl;
 
 private:
-    const Mode          fMode;
-    const Attribute&    fInstanceAttrib;
-    BufferAccess        fPointsBufferAccess;
-    SkDEBUGCODE(float   fDebugBloat = false;)
-
-    typedef GrGeometryProcessor INHERITED;
-};
-
-/**
- * This class represents the actual SKSL implementation for the various primitives and modes of
- * GrCCPRCoverageProcessor.
- */
-class GrCCPRCoverageProcessor::PrimitiveProcessor : public GrGLSLGeometryProcessor {
-protected:
     // Slightly undershoot a bloat radius of 0.5 so vertices that fall on integer boundaries don't
     // accidentally bleed into neighbor pixels.
     static constexpr float kAABloatRadius = 0.491111f;
 
-    // Specifies how the fragment shader should calculate sk_FragColor.a.
-    enum class CoverageType {
-        kOne, // Output +1 all around, modulated by wind.
-        kInterpolated, // Interpolate the coverage values that the geometry shader associates with
-                       // each point, modulated by wind.
-        kShader // Call emitShaderCoverage and let the subclass decide, then a modulate by wind.
-    };
+    static GrGLSLPrimitiveProcessor* CreateGSImpl(std::unique_ptr<Shader>);
 
-    PrimitiveProcessor(CoverageType coverageType)
-            : fCoverageType(coverageType)
-            , fGeomWind("wind", kHalf_GrSLType, GrShaderVar::kNonArray, kLow_GrSLPrecision)
-            , fFragWind(kHalf_GrSLType)
-            , fFragCoverageTimesWind(kHalf_GrSLType) {}
-
-    // Called before generating shader code. Subclass should add its custom varyings to the handler
-    // and update its corresponding internal member variables.
-    virtual void resetVaryings(GrGLSLVaryingHandler*) {}
-
-    // Here the subclass fetches its vertex from the texel buffer, translates by atlasOffset, and
-    // sets "fPositionVar" in the GrGPArgs.
-    virtual void onEmitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*,
-                                    const TexelBufferHandle& pointsBuffer, const char* atlasOffset,
-                                    const char* rtAdjust, GrGPArgs*) const = 0;
-
-    // Here the subclass determines the winding direction of its primitive. It must write a value of
-    // either -1, 0, or +1 to "outputWind" (e.g. "sign(area)"). Fractional values are not valid.
-    virtual void emitWind(GrGLSLGeometryBuilder*, const char* rtAdjust,
-                          const char* outputWind) const = 0;
-
-    // This is where the subclass generates the actual geometry to be rasterized by hardware:
-    //
-    //   emitVertexFn(point1, coverage);
-    //   emitVertexFn(point2, coverage);
-    //   ...
-    //   EndPrimitive();
-    //
-    // Generally a subclass will want to use emitHullGeometry and/or emitEdgeGeometry rather than
-    // calling emitVertexFn directly.
-    //
-    // Subclass must also call GrGLSLGeometryBuilder::configure.
-    virtual void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn,
-                                      const char* wind, const char* rtAdjust) const = 0;
-
-    // This is a hook to inject code in the geometry shader's "emitVertex" function. Subclass
-    // should use this to write values to its custom varyings.
-    // NOTE: even flat varyings should be rewritten at each vertex.
-    virtual void emitPerVertexGeometryCode(SkString* fnBody, const char* position,
-                                           const char* coverage, const char* wind) const {}
-
-    // Called when the subclass has selected CoverageType::kShader. Primitives should produce
-    // coverage values between +0..1. Base class modulates the sign for wind.
-    // TODO: subclasses might have good spots to stuff the winding information without burning a
-    // whole new varying slot. Consider requiring them to generate the correct coverage sign.
-    virtual void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const {
-        SK_ABORT("Shader coverage not implemented when using CoverageType::kShader.");
+    int atlasOffsetIdx() const {
+        SkASSERT(kInt2_GrVertexAttribType == fInstanceAttrib.fType ||
+                 kInt4_GrVertexAttribType == fInstanceAttrib.fType);
+        return kInt4_GrVertexAttribType == fInstanceAttrib.fType ? 3 : 1;
     }
 
-    // Emits one wedge of the conservative raster hull of a convex polygon. The complete hull has
-    // one wedge for each side of the polygon (i.e. call this N times, generally from different
-    // geometry shader invocations). Coverage is +1 all around.
-    //
-    // Logically, the conservative raster hull is equivalent to the convex hull of pixel-size boxes
-    // centered on the vertices.
-    //
-    // Geometry shader must be configured to output triangle strips.
-    //
-    // Returns the maximum number of vertices that will be emitted.
-    int emitHullGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* polygonPts,
-                         int numSides, const char* wedgeIdx, const char* midpoint = nullptr) const;
+    const RenderPass    fRenderPass;
+    const Attribute&    fInstanceAttrib;
+    BufferAccess        fPointsBufferAccess;
+    SkDEBUGCODE(float   fDebugBloat = 0;)
 
-    // Emits the conservative raster of an edge (i.e. convex hull of two pixel-size boxes centered
-    // on the endpoints). Coverage is -1 on the outside border of the edge geometry and 0 on the
-    // inside. This effectively converts a jagged conservative raster edge into a smooth antialiased
-    // edge when using CoverageType::kInterpolated.
-    //
-    // If the subclass has already called emitEdgeDistanceEquation, then provide the distance
-    // equation. Otherwise this function will call emitEdgeDistanceEquation implicitly.
-    //
-    // Geometry shader must be configured to output triangle strips.
-    //
-    // Returns the maximum number of vertices that will be emitted.
-    int emitEdgeGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* leftPt,
-                         const char* rightPt, const char* distanceEquation = nullptr) const;
-
-    // Defines an equation ("dot(float3(pt, 1), distance_equation)") that is -1 on the outside
-    // border of a conservative raster edge and 0 on the inside (see emitEdgeGeometry).
-    void emitEdgeDistanceEquation(GrGLSLGeometryBuilder*, const char* leftPt, const char* rightPt,
-                                  const char* outputDistanceEquation) const;
-
-    // Emits the conservative raster of a single point (i.e. pixel-size box centered on the point).
-    // Coverage is +1 all around.
-    //
-    // Geometry shader must be configured to output triangle strips.
-    //
-    // Returns the number of vertices that were emitted.
-    int emitCornerGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* pt) const;
-
-    // Defines a global float2 array that contains MSAA sample locations as offsets from pixel
-    // center. Subclasses can use this for software multisampling.
-    //
-    // Returns the number of samples.
-    int defineSoftSampleLocations(GrGLSLFragmentBuilder*, const char* samplesName) const;
-
-private:
-    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) final {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
-    }
-
-    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final;
-
-    void emitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*,
-                          const TexelBufferHandle& pointsBuffer, const char* rtAdjust,
-                          GrGPArgs* gpArgs) const;
-    void emitGeometryShader(const GrCCPRCoverageProcessor&, GrGLSLGeometryBuilder*,
-                            const char* rtAdjust) const;
-    void emitCoverage(const GrCCPRCoverageProcessor&, GrGLSLFragmentBuilder*,
-                      const char* outputColor, const char* outputCoverage) const;
-
-    const CoverageType   fCoverageType;
-    GrShaderVar          fGeomWind;
-    GrGLSLGeoToFrag      fFragWind;
-    GrGLSLGeoToFrag      fFragCoverageTimesWind;
-
-    typedef GrGLSLGeometryProcessor INHERITED;
+    typedef GrGeometryProcessor INHERITED;
 };
 
 #endif
diff --git a/src/gpu/ccpr/GrCCPRCoverageProcessor_GSImpl.cpp b/src/gpu/ccpr/GrCCPRCoverageProcessor_GSImpl.cpp
new file mode 100644
index 0000000..b9a38e7
--- /dev/null
+++ b/src/gpu/ccpr/GrCCPRCoverageProcessor_GSImpl.cpp
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrCCPRCoverageProcessor.h"
+
+#include "glsl/GrGLSLGeometryShaderBuilder.h"
+#include "glsl/GrGLSLVertexShaderBuilder.h"
+
+using Shader = GrCCPRCoverageProcessor::Shader;
+
+/**
+ * This class and its subclasses implement the coverage processor with geometry shaders.
+ */
+class GrCCPRCoverageProcessor::GSImpl : public GrGLSLGeometryProcessor {
+protected:
+    GSImpl(std::unique_ptr<Shader> shader) : fShader(std::move(shader)) {}
+
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
+                 FPCoordTransformIter&& transformIter) final {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+    }
+
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
+        const GrCCPRCoverageProcessor& proc = args.fGP.cast<GrCCPRCoverageProcessor>();
+
+        // Vertex shader.
+        GrGLSLVertexBuilder* v = args.fVertBuilder;
+        // The Intel GLSL compiler hits an internal assertion if we index the input attrib itself
+        // with sk_VertexID.
+        v->codeAppendf("int pointID = sk_VertexID;");
+        v->codeAppend ("float2 self = ");
+        fShader->appendInputPointFetch(proc, v, args.fTexelBuffers[0], "pointID");
+        v->codeAppend (".xy;");
+        v->codeAppendf("int packedoffset = %s[%i];",
+                       proc.fInstanceAttrib.fName, proc.atlasOffsetIdx());
+        v->codeAppend ("float2 atlasoffset = float2((packedoffset << 16) >> 16, "
+                                                   "packedoffset >> 16);");
+        v->codeAppend ("self += atlasoffset;");
+        gpArgs->fPositionVar.set(kFloat2_GrSLType, "self");
+
+        // Geometry shader.
+        GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
+        this->emitGeometryShader(proc, varyingHandler, args.fGeomBuilder, args.fRTAdjustName);
+        varyingHandler->emitAttributes(proc);
+        SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform());
+
+        // Fragment shader.
+        fShader->emitFragmentCode(proc, args.fFragBuilder, args.fOutputColor, args.fOutputCoverage);
+    }
+
+    void emitGeometryShader(const GrCCPRCoverageProcessor& proc,
+                            GrGLSLVaryingHandler* varyingHandler, GrGLSLGeometryBuilder* g,
+                            const char* rtAdjust) const {
+        using InputType = GrGLSLGeometryBuilder::InputType;
+        using OutputType = GrGLSLGeometryBuilder::OutputType;
+
+        int numPts = fShader->getNumInputPoints();
+        SkASSERT(3 == numPts || 4 == numPts);
+
+        g->codeAppendf("float%ix2 pts = float%ix2(", numPts, numPts);
+        for (int i = 0; i < numPts; ++i) {
+            g->codeAppend (i ? ", " : "");
+            g->codeAppendf("sk_in[%i].sk_Position.xy", i);
+        }
+        g->codeAppend (");");
+
+        GrShaderVar wind("wind", kHalf_GrSLType);
+        g->declareGlobal(wind);
+        fShader->emitWind(g, "pts", rtAdjust, wind.c_str());
+
+        SkString emitVertexFn;
+        SkSTArray<2, GrShaderVar> emitArgs;
+        const char* position = emitArgs.emplace_back("position", kFloat2_GrSLType).c_str();
+        const char* coverage = emitArgs.emplace_back("coverage", kHalf_GrSLType).c_str();
+        g->emitFunction(kVoid_GrSLType, "emitVertex", emitArgs.count(), emitArgs.begin(), [&]() {
+            SkString fnBody;
+            fShader->emitVaryings(varyingHandler, &fnBody, position, coverage, wind.c_str());
+            fnBody.append("sk_Position = float4(position, 0, 1);");
+            fnBody.append("EmitVertex();");
+            return fnBody;
+        }().c_str(), &emitVertexFn);
+
+        g->codeAppendf("float2 bloat = %f * abs(%s.xz);", kAABloatRadius, rtAdjust);
+#ifdef SK_DEBUG
+        if (proc.debugVisualizationsEnabled()) {
+            g->codeAppendf("bloat *= %f;", proc.debugBloat());
+        }
+#endif
+
+        Shader::GeometryVars vars;
+        fShader->emitSetupCode(g, "pts", "sk_InvocationID", "bloat", wind.c_str(), rtAdjust, &vars);
+        int maxPoints = this->onEmitGeometryShader(g, wind, emitVertexFn.c_str(), rtAdjust, vars);
+
+        int numInputPoints = fShader->getNumInputPoints();
+        SkASSERT(3 == numInputPoints || 4 == numInputPoints);
+        InputType inputType = (3 == numInputPoints) ? InputType::kTriangles
+                                                    : InputType::kLinesAdjacency;
+
+        g->configure(inputType, OutputType::kTriangleStrip, maxPoints, fShader->getNumSegments());
+    }
+
+    virtual int onEmitGeometryShader(GrGLSLGeometryBuilder*, const GrShaderVar& wind,
+                                     const char* emitVertexFn, const char* rtAdjust,
+                                     const Shader::GeometryVars&) const = 0;
+
+    virtual ~GSImpl() {}
+
+    const std::unique_ptr<Shader> fShader;
+
+    typedef GrGLSLGeometryProcessor INHERITED;
+};
+
+class GSHullImpl : public GrCCPRCoverageProcessor::GSImpl {
+public:
+    GSHullImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
+
+    int onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
+                             const char* emitVertexFn, const char* rtAdjust,
+                             const Shader::GeometryVars& vars) const override {
+        int numSides = fShader->getNumSegments();
+        SkASSERT(numSides >= 3);
+
+        const char* hullPts = vars.fHullVars.fAlternatePoints;
+        if (!hullPts) {
+            SkASSERT(fShader->getNumInputPoints() == numSides);
+            hullPts = "pts";
+        }
+
+        const char* midpoint = vars.fHullVars.fAlternateMidpoint;
+        if (!midpoint) {
+            g->codeAppendf("float2 midpoint = %s * float%i(%f);", hullPts, numSides, 1.0/numSides);
+            midpoint = "midpoint";
+        }
+
+        g->codeAppendf("int previdx = (sk_InvocationID + %i) %% %i, "
+                           "nextidx = (sk_InvocationID + 1) %% %i;",
+                       numSides - 1, numSides, numSides);
+
+        g->codeAppendf("float2 self = %s[sk_InvocationID];"
+                       "int leftidx = %s > 0 ? previdx : nextidx;"
+                       "int rightidx = %s > 0 ? nextidx : previdx;",
+                       hullPts, wind.c_str(), wind.c_str());
+
+        // Which quadrant does the vector from self -> right fall into?
+        g->codeAppendf("float2 right = %s[rightidx];", hullPts);
+        if (3 == numSides) {
+            // TODO: evaluate perf gains.
+            g->codeAppend ("float2 qsr = sign(right - self);");
+        } else {
+            SkASSERT(4 == numSides);
+            g->codeAppendf("float2 diag = %s[(sk_InvocationID + 2) %% 4];", hullPts);
+            g->codeAppend ("float2 qsr = sign((right != self ? right : diag) - self);");
+        }
+
+        // Which quadrant does the vector from left -> self fall into?
+        g->codeAppendf("float2 qls = sign(self - %s[leftidx]);", hullPts);
+
+        // d2 just helps us reduce triangle counts with orthogonal, axis-aligned lines.
+        // TODO: evaluate perf gains.
+        const char* dr2 = "dr";
+        if (3 == numSides) {
+            // TODO: evaluate perf gains.
+            g->codeAppend ("float2 dr = float2(qsr.y != 0 ? +qsr.y : +qsr.x, "
+                                              "qsr.x != 0 ? -qsr.x : +qsr.y);");
+            g->codeAppend ("float2 dr2 = float2(qsr.y != 0 ? +qsr.y : -qsr.x, "
+                                               "qsr.x != 0 ? -qsr.x : -qsr.y);");
+            g->codeAppend ("float2 dl = float2(qls.y != 0 ? +qls.y : +qls.x, "
+                                              "qls.x != 0 ? -qls.x : +qls.y);");
+            dr2 = "dr2";
+        } else {
+            g->codeAppend ("float2 dr = float2(qsr.y != 0 ? +qsr.y : 1, "
+                                              "qsr.x != 0 ? -qsr.x : 1);");
+            g->codeAppend ("float2 dl = (qls == float2(0)) ? dr : "
+                                       "float2(qls.y != 0 ? +qls.y : 1, qls.x != 0 ? -qls.x : 1);");
+        }
+        g->codeAppendf("bool2 dnotequal = notEqual(%s, dl);", dr2);
+
+        // Emit one third of what is the convex hull of pixel-size boxes centered on the vertices.
+        // Each invocation emits a different third.
+        g->codeAppendf("%s(right + bloat * dr, 1);", emitVertexFn);
+        g->codeAppendf("%s(%s, 1);", emitVertexFn, midpoint);
+        g->codeAppendf("%s(self + bloat * %s, 1);", emitVertexFn, dr2);
+        g->codeAppend ("if (any(dnotequal)) {");
+        g->codeAppendf(    "%s(self + bloat * dl, 1);", emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("if (all(dnotequal)) {");
+        g->codeAppendf(    "%s(self + bloat * float2(-dl.y, dl.x), 1);", emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("EndPrimitive();");
+
+        return 5;
+    }
+};
+
+class GSEdgeImpl : public GrCCPRCoverageProcessor::GSImpl {
+public:
+    GSEdgeImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
+
+    int onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
+                             const char* emitVertexFn, const char* rtAdjust,
+                             const Shader::GeometryVars&) const override {
+        int numSides = fShader->getNumSegments();
+
+        g->codeAppendf("int nextidx = (sk_InvocationID + 1) %% %i;", numSides);
+        g->codeAppendf("float2 left = pts[%s > 0 ? sk_InvocationID : nextidx], "
+                                     "right = pts[%s > 0 ? nextidx : sk_InvocationID];",
+                                     wind.c_str(), wind.c_str());
+
+        Shader::EmitEdgeDistanceEquation(g, "left", "right", "float3 edge_distance_equation");
+
+        // qlr is defined in EmitEdgeDistanceEquation. TODO: address in a followup CL!
+        g->codeAppend ("float2x2 outer_pts = float2x2(left - bloat * qlr, right + bloat * qlr);");
+        g->codeAppend ("half2 outer_coverage = edge_distance_equation.xy * outer_pts + "
+                                              "edge_distance_equation.z;");
+
+        // d1 is defined in EmitEdgeDistanceEquation. TODO: address in a followup CL!
+        g->codeAppend ("float2 d2 = d1;");
+        g->codeAppend ("bool aligned = qlr.x == 0 || qlr.y == 0;");
+        g->codeAppend ("if (aligned) {");
+        g->codeAppend (    "d1 -= qlr;");
+        g->codeAppend (    "d2 += qlr;");
+        g->codeAppend ("}");
+
+        // Emit the convex hull of 2 pixel-size boxes centered on the endpoints of the edge. Each
+        // invocation emits a different edge. Emit negative coverage that subtracts the appropiate
+        // amount back out from the hull we drew above.
+        g->codeAppend ("if (!aligned) {");
+        g->codeAppendf(    "%s(outer_pts[0], outer_coverage[0]);", emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppendf("%s(left + bloat * d1, -1);", emitVertexFn);
+        g->codeAppendf("%s(left - bloat * d2, 0);", emitVertexFn);
+        g->codeAppendf("%s(right + bloat * d2, -1);", emitVertexFn);
+        g->codeAppendf("%s(right - bloat * d1, 0);", emitVertexFn);
+        g->codeAppend ("if (!aligned) {");
+        g->codeAppendf(    "%s(outer_pts[1], outer_coverage[1]);", emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("EndPrimitive();");
+
+        return 6;
+    }
+};
+
+class GSCornerImpl : public GrCCPRCoverageProcessor::GSImpl {
+public:
+    GSCornerImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
+
+    int onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
+                             const char* emitVertexFn, const char* rtAdjust,
+                             const Shader::GeometryVars& vars) const override {
+        const char* corner = vars.fCornerVars.fPoint;
+        SkASSERT(corner);
+
+        g->codeAppendf("%s(%s + float2(-bloat.x, -bloat.y), 1);", emitVertexFn, corner);
+        g->codeAppendf("%s(%s + float2(-bloat.x, +bloat.y), 1);", emitVertexFn, corner);
+        g->codeAppendf("%s(%s + float2(+bloat.x, -bloat.y), 1);", emitVertexFn, corner);
+        g->codeAppendf("%s(%s + float2(+bloat.x, +bloat.y), 1);", emitVertexFn, corner);
+        g->codeAppend ("EndPrimitive();");
+
+        return 4;
+    }
+};
+
+GrGLSLPrimitiveProcessor* GrCCPRCoverageProcessor::CreateGSImpl(std::unique_ptr<Shader> shader) {
+    switch (shader->getGeometryType()) {
+        case Shader::GeometryType::kHull:
+            return new GSHullImpl(std::move(shader));
+        case Shader::GeometryType::kEdges:
+            return new GSEdgeImpl(std::move(shader));
+        case Shader::GeometryType::kCorners:
+            return new GSCornerImpl(std::move(shader));
+    }
+    SK_ABORT("Unexpected Shader::GeometryType.");
+    return nullptr;
+}
diff --git a/src/gpu/ccpr/GrCCPRCubicProcessor.cpp b/src/gpu/ccpr/GrCCPRCubicProcessor.cpp
deleted file mode 100644
index 8b4c405..0000000
--- a/src/gpu/ccpr/GrCCPRCubicProcessor.cpp
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrCCPRCubicProcessor.h"
-
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLGeometryShaderBuilder.h"
-#include "glsl/GrGLSLVertexShaderBuilder.h"
-
-void GrCCPRCubicProcessor::onEmitVertexShader(const GrCCPRCoverageProcessor& proc,
-                                              GrGLSLVertexBuilder* v,
-                                              const TexelBufferHandle& pointsBuffer,
-                                              const char* atlasOffset, const char* rtAdjust,
-                                              GrGPArgs* gpArgs) const {
-    v->codeAppend ("float2 self = ");
-    v->appendTexelFetch(pointsBuffer,
-                        SkStringPrintf("%s.x + sk_VertexID", proc.instanceAttrib()).c_str());
-    v->codeAppendf(".xy + %s;", atlasOffset);
-    gpArgs->fPositionVar.set(kFloat2_GrSLType, "self");
-}
-
-void GrCCPRCubicProcessor::emitWind(GrGLSLGeometryBuilder* g, const char* rtAdjust,
-                                    const char* outputWind) const {
-    // We will define bezierpts in onEmitGeometryShader.
-    g->codeAppend ("float area_times_2 = "
-                                      "determinant(float3x3(1, bezierpts[0], "
-                                                               "1, bezierpts[2], "
-                                                               "0, bezierpts[3] - bezierpts[1]));");
-    // Drop curves that are nearly flat. The KLM  math becomes unstable in this case.
-    g->codeAppendf("if (2 * abs(area_times_2) < length((bezierpts[3] - bezierpts[0]) * %s.zx)) {",
-                   rtAdjust);
-#ifndef SK_BUILD_FOR_MAC
-    g->codeAppend (    "return;");
-#else
-    // Returning from this geometry shader makes Mac very unhappy. Instead we make wind 0.
-    g->codeAppend (    "area_times_2 = 0;");
-#endif
-    g->codeAppend ("}");
-    g->codeAppendf("%s = sign(area_times_2);", outputWind);
-}
-
-void GrCCPRCubicProcessor::onEmitGeometryShader(GrGLSLGeometryBuilder* g, const char* emitVertexFn,
-                                                const char* wind, const char* rtAdjust) const {
-    // Prepend bezierpts at the start of the shader.
-    g->codePrependf("float4x2 bezierpts = float4x2(sk_in[0].sk_Position.xy, "
-                                                          "sk_in[1].sk_Position.xy, "
-                                                          "sk_in[2].sk_Position.xy, "
-                                                          "sk_in[3].sk_Position.xy);");
-
-    // Evaluate the cubic at T=.5 for an mid-ish point.
-    g->codeAppendf("float2 midpoint = bezierpts * float4(.125, .375, .375, .125);");
-
-    // Find the cubic's power basis coefficients.
-    g->codeAppend ("float2x4 C = float4x4(-1,  3, -3,  1, "
-                                                 " 3, -6,  3,  0, "
-                                                 "-3,  3,  0,  0, "
-                                                 " 1,  0,  0,  0) * transpose(bezierpts);");
-
-    // Find the cubic's inflection function.
-    g->codeAppend ("float D3 = +determinant(float2x2(C[0].yz, C[1].yz));");
-    g->codeAppend ("float D2 = -determinant(float2x2(C[0].xz, C[1].xz));");
-    g->codeAppend ("float D1 = +determinant(float2x2(C));");
-
-    // Calculate the KLM matrix.
-    g->declareGlobal(fKLMMatrix);
-    g->codeAppend ("float4 K, L, M;");
-    g->codeAppend ("float2 l, m;");
-    g->codeAppend ("float discr = 3*D2*D2 - 4*D1*D3;");
-    if (CubicType::kSerpentine == fCubicType) {
-        // This math also works out for the "cusp" and "cusp at infinity" cases.
-        g->codeAppend ("float q = sqrt(max(3*discr, 0));");
-        g->codeAppend ("q = 3*D2 + (D2 >= 0 ? q : -q);");
-        g->codeAppend ("l.ts = normalize(float2(q, 6*D1));");
-        g->codeAppend ("m.ts = discr <= 0 ? l.ts : normalize(float2(2*D3, q));");
-        g->codeAppend ("K = float4(0, l.s * m.s, -l.t * m.s - m.t * l.s, l.t * m.t);");
-        g->codeAppend ("L = float4(-1,3,-3,1) * l.ssst * l.sstt * l.sttt;");
-        g->codeAppend ("M = float4(-1,3,-3,1) * m.ssst * m.sstt * m.sttt;");
-    } else {
-        g->codeAppend ("float q = sqrt(max(-discr, 0));");
-        g->codeAppend ("q = D2 + (D2 >= 0 ? q : -q);");
-        g->codeAppend ("l.ts = normalize(float2(q, 2*D1));");
-        g->codeAppend ("m.ts = discr >= 0 ? l.ts : normalize(float2(2 * (D2*D2 - D3*D1), D1*q));");
-        g->codeAppend ("float4 lxm = float4(l.s * m.s, l.s * m.t, l.t * m.s, l.t * m.t);");
-        g->codeAppend ("K = float4(0, lxm.x, -lxm.y - lxm.z, lxm.w);");
-        g->codeAppend ("L = float4(-1,1,-1,1) * l.sstt * (lxm.xyzw + float4(0, 2*lxm.zy, 0));");
-        g->codeAppend ("M = float4(-1,1,-1,1) * m.sstt * (lxm.xzyw + float4(0, 2*lxm.yz, 0));");
-    }
-    g->codeAppend ("short middlerow = abs(D2) > abs(D1) ? 2 : 1;");
-    g->codeAppend ("float3x3 CI = inverse(float3x3(C[0][0], C[0][middlerow], C[0][3], "
-                                                          "C[1][0], C[1][middlerow], C[1][3], "
-                                                          "      0,               0,       1));");
-    g->codeAppendf("%s = CI * float3x3(K[0], K[middlerow], K[3], "
-                                          "L[0], L[middlerow], L[3], "
-                                          "M[0], M[middlerow], M[3]);", fKLMMatrix.c_str());
-
-    // Orient the KLM matrix so we fill the correct side of the curve.
-    g->codeAppendf("float2 orientation = sign(float3(midpoint, 1) * float2x3(%s[1], %s[2]));",
-                   fKLMMatrix.c_str(), fKLMMatrix.c_str());
-    g->codeAppendf("%s *= float3x3(orientation[0] * orientation[1], 0, 0, "
-                                      "0, orientation[0], 0, "
-                                      "0, 0, orientation[1]);", fKLMMatrix.c_str());
-
-    g->declareGlobal(fKLMDerivatives);
-    g->codeAppendf("%s[0] = %s[0].xy * %s.xz;",
-                   fKLMDerivatives.c_str(), fKLMMatrix.c_str(), rtAdjust);
-    g->codeAppendf("%s[1] = %s[1].xy * %s.xz;",
-                   fKLMDerivatives.c_str(), fKLMMatrix.c_str(), rtAdjust);
-    g->codeAppendf("%s[2] = %s[2].xy * %s.xz;",
-                   fKLMDerivatives.c_str(), fKLMMatrix.c_str(), rtAdjust);
-
-    // Determine the amount of additional coverage to subtract out for the flat edge (P3 -> P0).
-    g->declareGlobal(fEdgeDistanceEquation);
-    g->codeAppendf("short edgeidx0 = %s > 0 ? 3 : 0;", wind);
-    g->codeAppendf("float2 edgept0 = bezierpts[edgeidx0];");
-    g->codeAppendf("float2 edgept1 = bezierpts[3 - edgeidx0];");
-    this->emitEdgeDistanceEquation(g, "edgept0", "edgept1", fEdgeDistanceEquation.c_str());
-
-    this->emitCubicGeometry(g, emitVertexFn, wind, rtAdjust);
-}
-
-void GrCCPRCubicProcessor::emitPerVertexGeometryCode(SkString* fnBody, const char* position,
-                                                     const char* /*coverage*/,
-                                                     const char* /*wind*/) const {
-    fnBody->appendf("float3 klm = float3(%s, 1) * %s;", position, fKLMMatrix.c_str());
-    fnBody->appendf("float d = dot(float3(%s, 1), %s);",
-                    position, fEdgeDistanceEquation.c_str());
-    fnBody->appendf("%s = float4(klm, d);", fKLMD.gsOut());
-    this->onEmitPerVertexGeometryCode(fnBody);
-}
-
-void GrCCPRCubicHullProcessor::emitCubicGeometry(GrGLSLGeometryBuilder* g, const char* emitVertexFn,
-                                                 const char* wind, const char* rtAdjust) const {
-    // FIXME: we should clip this geometry at the tip of the curve.
-    int maxVertices = this->emitHullGeometry(g, emitVertexFn, "bezierpts", 4, "sk_InvocationID",
-                                             "midpoint");
-
-    g->configure(GrGLSLGeometryBuilder::InputType::kLinesAdjacency,
-                 GrGLSLGeometryBuilder::OutputType::kTriangleStrip,
-                 maxVertices, 4);
-}
-
-void GrCCPRCubicHullProcessor::onEmitPerVertexGeometryCode(SkString* fnBody) const {
-    // "klm" was just defined by the base class.
-    fnBody->appendf("%s[0] = 3 * klm[0] * %s[0];", fGradMatrix.gsOut(), fKLMDerivatives.c_str());
-    fnBody->appendf("%s[1] = -klm[1] * %s[2].xy - klm[2] * %s[1].xy;",
-                    fGradMatrix.gsOut(), fKLMDerivatives.c_str(), fKLMDerivatives.c_str());
-}
-
-void GrCCPRCubicHullProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f,
-                                                  const char* outputCoverage) const {
-    f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z, d = %s.w;",
-                   fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn());
-    f->codeAppend ("float f = k*k*k - l*m;");
-    f->codeAppendf("float2 grad_f = %s * float2(k, 1);", fGradMatrix.fsIn());
-    f->codeAppendf("%s = clamp(0.5 - f * inversesqrt(dot(grad_f, grad_f)), 0, 1);", outputCoverage);
-    f->codeAppendf("%s += min(d, 0);", outputCoverage); // Flat closing edge.
-}
-
-void GrCCPRCubicCornerProcessor::emitCubicGeometry(GrGLSLGeometryBuilder* g,
-                                                   const char* emitVertexFn, const char* wind,
-                                                   const char* rtAdjust) const {
-    // We defined bezierpts in onEmitGeometryShader.
-    g->declareGlobal(fEdgeDistanceDerivatives);
-    g->codeAppendf("%s = %s.xy * %s.xz;",
-                   fEdgeDistanceDerivatives.c_str(), fEdgeDistanceEquation.c_str(), rtAdjust);
-
-    g->codeAppendf("float2 corner = bezierpts[sk_InvocationID * 3];");
-    int numVertices = this->emitCornerGeometry(g, emitVertexFn, "corner");
-
-    g->configure(GrGLSLGeometryBuilder::InputType::kLinesAdjacency,
-                 GrGLSLGeometryBuilder::OutputType::kTriangleStrip, numVertices, 2);
-}
-
-void GrCCPRCubicCornerProcessor::onEmitPerVertexGeometryCode(SkString* fnBody) const {
-    fnBody->appendf("%s = float4(%s[0].x, %s[1].x, %s[2].x, %s.x);",
-                    fdKLMDdx.gsOut(), fKLMDerivatives.c_str(), fKLMDerivatives.c_str(),
-                    fKLMDerivatives.c_str(), fEdgeDistanceDerivatives.c_str());
-    fnBody->appendf("%s = float4(%s[0].y, %s[1].y, %s[2].y, %s.y);",
-                    fdKLMDdy.gsOut(), fKLMDerivatives.c_str(), fKLMDerivatives.c_str(),
-                    fKLMDerivatives.c_str(), fEdgeDistanceDerivatives.c_str());
-
-    // Otherwise, fEdgeDistances = fEdgeDistances * sign(wind * rtAdjust.x * rdAdjust.z).
-    GR_STATIC_ASSERT(kTopLeft_GrSurfaceOrigin == GrCCPRCoverageProcessor::kAtlasOrigin);
-}
-
-void GrCCPRCubicCornerProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f,
-                                                    const char* outputCoverage) const {
-    f->codeAppendf("float2x4 grad_klmd = float2x4(%s, %s);",
-                   fdKLMDdx.fsIn(), fdKLMDdy.fsIn());
-
-    // Erase what the previous hull shader wrote. We don't worry about the two corners falling on
-    // the same pixel because those cases should have been weeded out by this point.
-    f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z, d = %s.w;",
-                   fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn());
-    f->codeAppend ("float f = k*k*k - l*m;");
-    f->codeAppend ("float2 grad_f = float3(3*k*k, -m, -l) * float2x3(grad_klmd);");
-    f->codeAppendf("%s = -clamp(0.5 - f * inversesqrt(dot(grad_f, grad_f)), 0, 1);",
-                   outputCoverage);
-    f->codeAppendf("%s -= d;", outputCoverage);
-
-    // Use software msaa to estimate actual coverage at the corner pixels.
-    const int sampleCount = this->defineSoftSampleLocations(f, "samples");
-    f->codeAppendf("float4 klmd_center = float4(%s.xyz, %s.w + 0.5);",
-                   fKLMD.fsIn(), fKLMD.fsIn());
-    f->codeAppendf("for (int i = 0; i < %i; ++i) {", sampleCount);
-    f->codeAppend (    "float4 klmd = grad_klmd * samples[i] + klmd_center;");
-    f->codeAppend (    "half f = klmd.y * klmd.z - klmd.x * klmd.x * klmd.x;");
-    f->codeAppendf(    "%s += all(greaterThan(half4(f, klmd.y, klmd.z, klmd.w), "
-                                             "half4(0))) ? %f : 0;",
-                       outputCoverage, 1.0 / sampleCount);
-    f->codeAppend ("}");
-}
diff --git a/src/gpu/ccpr/GrCCPRCubicProcessor.h b/src/gpu/ccpr/GrCCPRCubicProcessor.h
deleted file mode 100644
index e44d8fb..0000000
--- a/src/gpu/ccpr/GrCCPRCubicProcessor.h
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrCCPRCubicProcessor_DEFINED
-#define GrCCPRCubicProcessor_DEFINED
-
-#include "ccpr/GrCCPRCoverageProcessor.h"
-
-class GrGLSLGeometryBuilder;
-
-/**
- * This class renders the coverage of convex closed cubic segments using the techniques outlined in
- * "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
- * Jim Blinn:
- *
- * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
- *
- * The provided curves must be convex, monotonic with respect to the vector of their closing edge
- * [P3 - P0], and must not contain or be near any inflection points or loop intersections.
- * (Use GrCCPRGeometry.)
- */
-class GrCCPRCubicProcessor : public GrCCPRCoverageProcessor::PrimitiveProcessor {
-public:
-    enum class CubicType {
-        kSerpentine,
-        kLoop
-    };
-
-    GrCCPRCubicProcessor(CubicType cubicType)
-            : INHERITED(CoverageType::kShader)
-            , fCubicType(cubicType)
-            , fKLMMatrix("klm_matrix", kFloat3x3_GrSLType, GrShaderVar::kNonArray,
-                         kHigh_GrSLPrecision)
-            , fKLMDerivatives("klm_derivatives", kFloat2_GrSLType, 3, kHigh_GrSLPrecision)
-            , fEdgeDistanceEquation("edge_distance_equation", kFloat3_GrSLType,
-                                    GrShaderVar::kNonArray, kHigh_GrSLPrecision)
-            , fKLMD(kFloat4_GrSLType) {}
-
-    void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override {
-        varyingHandler->addVarying("klmd", &fKLMD, kHigh_GrSLPrecision);
-    }
-
-    void onEmitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*,
-                            const TexelBufferHandle& pointsBuffer, const char* atlasOffset,
-                            const char* rtAdjust, GrGPArgs*) const override;
-    void emitWind(GrGLSLGeometryBuilder*, const char* rtAdjust, const char* outputWind) const final;
-    void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* wind,
-                              const char* rtAdjust) const final;
-    void emitPerVertexGeometryCode(SkString* fnBody, const char* position, const char* coverage,
-                                   const char* wind) const final;
-
-protected:
-    virtual void emitCubicGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn,
-                                   const char* wind, const char* rtAdjust) const = 0;
-    virtual void onEmitPerVertexGeometryCode(SkString* fnBody) const = 0;
-
-    const CubicType   fCubicType;
-    GrShaderVar       fKLMMatrix;
-    GrShaderVar       fKLMDerivatives;
-    GrShaderVar       fEdgeDistanceEquation;
-    GrGLSLGeoToFrag   fKLMD;
-
-    typedef GrCCPRCoverageProcessor::PrimitiveProcessor INHERITED;
-};
-
-class GrCCPRCubicHullProcessor : public GrCCPRCubicProcessor {
-public:
-    GrCCPRCubicHullProcessor(CubicType cubicType)
-            : INHERITED(cubicType)
-            , fGradMatrix(kFloat2x2_GrSLType) {}
-
-    void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override {
-        this->INHERITED::resetVaryings(varyingHandler);
-        varyingHandler->addVarying("grad_matrix", &fGradMatrix, kHigh_GrSLPrecision);
-    }
-
-    void emitCubicGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn,
-                           const char* wind, const char* rtAdjust) const override;
-    void onEmitPerVertexGeometryCode(SkString* fnBody) const override;
-    void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const override;
-
-protected:
-    GrGLSLGeoToFrag   fGradMatrix;
-
-    typedef GrCCPRCubicProcessor INHERITED;
-};
-
-class GrCCPRCubicCornerProcessor : public GrCCPRCubicProcessor {
-public:
-    GrCCPRCubicCornerProcessor(CubicType cubicType)
-            : INHERITED(cubicType)
-            , fEdgeDistanceDerivatives("edge_distance_derivatives", kFloat2_GrSLType,
-                                        GrShaderVar::kNonArray, kHigh_GrSLPrecision)
-            , fdKLMDdx(kFloat4_GrSLType)
-            , fdKLMDdy(kFloat4_GrSLType) {}
-
-    void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override {
-        this->INHERITED::resetVaryings(varyingHandler);
-        varyingHandler->addFlatVarying("dklmddx", &fdKLMDdx, kHigh_GrSLPrecision);
-        varyingHandler->addFlatVarying("dklmddy", &fdKLMDdy, kHigh_GrSLPrecision);
-    }
-
-    void emitCubicGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn,
-                           const char* wind, const char* rtAdjust) const override;
-    void onEmitPerVertexGeometryCode(SkString* fnBody) const override;
-    void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const override;
-
-protected:
-    GrShaderVar        fEdgeDistanceDerivatives;
-    GrGLSLGeoToFrag    fdKLMDdx;
-    GrGLSLGeoToFrag    fdKLMDdy;
-
-    typedef GrCCPRCubicProcessor INHERITED;
-};
-
-#endif
diff --git a/src/gpu/ccpr/GrCCPRCubicShader.cpp b/src/gpu/ccpr/GrCCPRCubicShader.cpp
new file mode 100644
index 0000000..3a30cee
--- /dev/null
+++ b/src/gpu/ccpr/GrCCPRCubicShader.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrCCPRCubicShader.h"
+
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+
+void GrCCPRCubicShader::appendInputPointFetch(const GrCCPRCoverageProcessor& proc,
+                                              GrGLSLShaderBuilder* s,
+                                              const TexelBufferHandle& pointsBuffer,
+                                              const char* pointId) const {
+    s->appendTexelFetch(pointsBuffer,
+                        SkStringPrintf("%s.x + %s", proc.instanceAttrib(), pointId).c_str());
+}
+
+void GrCCPRCubicShader::emitWind(GrGLSLShaderBuilder* s, const char* pts,
+                                           const char* rtAdjust, const char* outputWind) const {
+
+    s->codeAppendf("float area_times_2 = determinant(float3x3(1, %s[0], "
+                                                             "1, %s[2], "
+                                                             "0, %s[3] - %s[1]));",
+                                                             pts, pts, pts, pts);
+    // Drop curves that are nearly flat. The KLM  math becomes unstable in this case.
+    s->codeAppendf("if (2 * abs(area_times_2) < length((%s[3] - %s[0]) * %s.zx)) {",
+                   pts, pts, rtAdjust);
+#ifndef SK_BUILD_FOR_MAC
+    s->codeAppend (    "return;");
+#else
+    // Returning from this geometry shader makes Mac very unhappy. Instead we make wind 0.
+    s->codeAppend (    "area_times_2 = 0;");
+#endif
+    s->codeAppend ("}");
+    s->codeAppendf("%s = sign(area_times_2);", outputWind);
+}
+
+void GrCCPRCubicShader::emitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
+                                      const char* segmentId, const char* bloat, const char* wind,
+                                      const char* rtAdjust, GeometryVars* vars) const {
+    // Evaluate the cubic at T=.5 for an mid-ish point.
+    s->codeAppendf("float2 midpoint = %s * float4(.125, .375, .375, .125);", pts);
+
+    // Find the cubic's power basis coefficients.
+    s->codeAppendf("float2x4 C = float4x4(-1,  3, -3,  1, "
+                                         " 3, -6,  3,  0, "
+                                         "-3,  3,  0,  0, "
+                                         " 1,  0,  0,  0) * transpose(%s);", pts);
+
+    // Find the cubic's inflection function.
+    s->codeAppend ("float D3 = +determinant(float2x2(C[0].yz, C[1].yz));");
+    s->codeAppend ("float D2 = -determinant(float2x2(C[0].xz, C[1].xz));");
+    s->codeAppend ("float D1 = +determinant(float2x2(C));");
+
+    // Calculate the KLM matrix.
+    s->declareGlobal(fKLMMatrix);
+    s->codeAppend ("float4 K, L, M;");
+    s->codeAppend ("float2 l, m;");
+    s->codeAppend ("float discr = 3*D2*D2 - 4*D1*D3;");
+    if (CubicType::kSerpentine == fCubicType) {
+        // This math also works out for the "cusp" and "cusp at infinity" cases.
+        s->codeAppend ("float q = sqrt(max(3*discr, 0));");
+        s->codeAppend ("q = 3*D2 + (D2 >= 0 ? q : -q);");
+        s->codeAppend ("l.ts = normalize(float2(q, 6*D1));");
+        s->codeAppend ("m.ts = discr <= 0 ? l.ts : normalize(float2(2*D3, q));");
+        s->codeAppend ("K = float4(0, l.s * m.s, -l.t * m.s - m.t * l.s, l.t * m.t);");
+        s->codeAppend ("L = float4(-1,3,-3,1) * l.ssst * l.sstt * l.sttt;");
+        s->codeAppend ("M = float4(-1,3,-3,1) * m.ssst * m.sstt * m.sttt;");
+    } else {
+        s->codeAppend ("float q = sqrt(max(-discr, 0));");
+        s->codeAppend ("q = D2 + (D2 >= 0 ? q : -q);");
+        s->codeAppend ("l.ts = normalize(float2(q, 2*D1));");
+        s->codeAppend ("m.ts = discr >= 0 ? l.ts : normalize(float2(2 * (D2*D2 - D3*D1), D1*q));");
+        s->codeAppend ("float4 lxm = float4(l.s * m.s, l.s * m.t, l.t * m.s, l.t * m.t);");
+        s->codeAppend ("K = float4(0, lxm.x, -lxm.y - lxm.z, lxm.w);");
+        s->codeAppend ("L = float4(-1,1,-1,1) * l.sstt * (lxm.xyzw + float4(0, 2*lxm.zy, 0));");
+        s->codeAppend ("M = float4(-1,1,-1,1) * m.sstt * (lxm.xzyw + float4(0, 2*lxm.yz, 0));");
+    }
+    s->codeAppend ("short middlerow = abs(D2) > abs(D1) ? 2 : 1;");
+    s->codeAppend ("float3x3 CI = inverse(float3x3(C[0][0], C[0][middlerow], C[0][3], "
+                                                  "C[1][0], C[1][middlerow], C[1][3], "
+                                                  "      0,               0,       1));");
+    s->codeAppendf("%s = CI * float3x3(K[0], K[middlerow], K[3], "
+                                      "L[0], L[middlerow], L[3], "
+                                      "M[0], M[middlerow], M[3]);", fKLMMatrix.c_str());
+
+    // Orient the KLM matrix so we fill the correct side of the curve.
+    s->codeAppendf("float2 orientation = sign(float3(midpoint, 1) * float2x3(%s[1], %s[2]));",
+                   fKLMMatrix.c_str(), fKLMMatrix.c_str());
+    s->codeAppendf("%s *= float3x3(orientation[0] * orientation[1], 0, 0, "
+                                  "0, orientation[0], 0, "
+                                  "0, 0, orientation[1]);", fKLMMatrix.c_str());
+
+    s->declareGlobal(fKLMDerivatives);
+    s->codeAppendf("%s[0] = %s[0].xy * %s.xz;",
+                   fKLMDerivatives.c_str(), fKLMMatrix.c_str(), rtAdjust);
+    s->codeAppendf("%s[1] = %s[1].xy * %s.xz;",
+                   fKLMDerivatives.c_str(), fKLMMatrix.c_str(), rtAdjust);
+    s->codeAppendf("%s[2] = %s[2].xy * %s.xz;",
+                   fKLMDerivatives.c_str(), fKLMMatrix.c_str(), rtAdjust);
+
+    // Determine the amount of additional coverage to subtract out for the flat edge (P3 -> P0).
+    s->declareGlobal(fEdgeDistanceEquation);
+    s->codeAppendf("short edgeidx0 = %s > 0 ? 3 : 0;", wind);
+    s->codeAppendf("float2 edgept0 = %s[edgeidx0];", pts);
+    s->codeAppendf("float2 edgept1 = %s[3 - edgeidx0];", pts);
+    Shader::EmitEdgeDistanceEquation(s, "edgept0", "edgept1", fEdgeDistanceEquation.c_str());
+
+    this->onEmitSetupCode(s, pts, segmentId, rtAdjust, vars);
+}
+
+GrCCPRCubicShader::WindHandling
+GrCCPRCubicShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler, SkString* code,
+                                  const char* position, const char* /*coverage*/,
+                                  const char* /*wind*/) {
+    varyingHandler->addVarying("klmd", &fKLMD, kHigh_GrSLPrecision);
+    code->appendf("float3 klm = float3(%s, 1) * %s;", position, fKLMMatrix.c_str());
+    code->appendf("float d = dot(float3(%s, 1), %s);", position, fEdgeDistanceEquation.c_str());
+    code->appendf("%s = float4(klm, d);", fKLMD.gsOut());
+
+    this->onEmitVaryings(varyingHandler, code);
+    return WindHandling::kNotHandled;
+}
+
+void GrCCPRCubicHullShader::onEmitSetupCode(GrGLSLShaderBuilder* s, const char* /*pts*/,
+                                            const char* /*wedgeId*/, const char* /*rtAdjust*/,
+                                            GeometryVars* vars) const {
+    // "midpoint" was just defined by the base class.
+    vars->fHullVars.fAlternateMidpoint = "midpoint";
+}
+
+void GrCCPRCubicHullShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler, SkString* code) {
+    // "klm" was just defined by the base class.
+    varyingHandler->addVarying("grad_matrix", &fGradMatrix, kHigh_GrSLPrecision);
+    code->appendf("%s[0] = 3 * klm[0] * %s[0];", fGradMatrix.gsOut(), fKLMDerivatives.c_str());
+    code->appendf("%s[1] = -klm[1] * %s[2].xy - klm[2] * %s[1].xy;",
+                    fGradMatrix.gsOut(), fKLMDerivatives.c_str(), fKLMDerivatives.c_str());
+}
+
+void GrCCPRCubicHullShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
+                                               const char* outputCoverage) const {
+    f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z, d = %s.w;",
+                   fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn());
+    f->codeAppend ("float f = k*k*k - l*m;");
+    f->codeAppendf("float2 grad_f = %s * float2(k, 1);", fGradMatrix.fsIn());
+    f->codeAppendf("%s = clamp(0.5 - f * inversesqrt(dot(grad_f, grad_f)), 0, 1);", outputCoverage);
+    f->codeAppendf("%s += min(d, 0);", outputCoverage); // Flat closing edge.
+}
+
+void GrCCPRCubicCornerShader::onEmitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
+                                              const char* cornerId, const char* rtAdjust,
+                                              GeometryVars* vars) const {
+    s->declareGlobal(fEdgeDistanceDerivatives);
+    s->codeAppendf("%s = %s.xy * %s.xz;",
+                   fEdgeDistanceDerivatives.c_str(), fEdgeDistanceEquation.c_str(), rtAdjust);
+
+    s->codeAppendf("float2 corner = %s[%s * 3];", pts, cornerId);
+    vars->fCornerVars.fPoint = "corner";
+}
+
+void GrCCPRCubicCornerShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler, SkString* code) {
+    varyingHandler->addFlatVarying("dklmddx", &fdKLMDdx, kHigh_GrSLPrecision);
+    code->appendf("%s = float4(%s[0].x, %s[1].x, %s[2].x, %s.x);",
+                    fdKLMDdx.gsOut(), fKLMDerivatives.c_str(), fKLMDerivatives.c_str(),
+                    fKLMDerivatives.c_str(), fEdgeDistanceDerivatives.c_str());
+
+    varyingHandler->addFlatVarying("dklmddy", &fdKLMDdy, kHigh_GrSLPrecision);
+    code->appendf("%s = float4(%s[0].y, %s[1].y, %s[2].y, %s.y);",
+                    fdKLMDdy.gsOut(), fKLMDerivatives.c_str(), fKLMDerivatives.c_str(),
+                    fKLMDerivatives.c_str(), fEdgeDistanceDerivatives.c_str());
+
+    // Otherwise, fEdgeDistances = fEdgeDistances * sign(wind * rtAdjust.x * rdAdjust.z).
+    GR_STATIC_ASSERT(kTopLeft_GrSurfaceOrigin == GrCCPRCoverageProcessor::kAtlasOrigin);
+}
+
+void GrCCPRCubicCornerShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
+                                                 const char* outputCoverage) const {
+    f->codeAppendf("float2x4 grad_klmd = float2x4(%s, %s);", fdKLMDdx.fsIn(), fdKLMDdy.fsIn());
+
+    // Erase what the previous hull shader wrote. We don't worry about the two corners falling on
+    // the same pixel because those cases should have been weeded out by this point.
+    f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z, d = %s.w;",
+                   fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn());
+    f->codeAppend ("float f = k*k*k - l*m;");
+    f->codeAppend ("float2 grad_f = float3(3*k*k, -m, -l) * float2x3(grad_klmd);");
+    f->codeAppendf("%s = -clamp(0.5 - f * inversesqrt(dot(grad_f, grad_f)), 0, 1);",
+                   outputCoverage);
+    f->codeAppendf("%s -= d;", outputCoverage);
+
+    // Use software msaa to estimate actual coverage at the corner pixels.
+    const int sampleCount = Shader::DefineSoftSampleLocations(f, "samples");
+    f->codeAppendf("float4 klmd_center = float4(%s.xyz, %s.w + 0.5);",
+                   fKLMD.fsIn(), fKLMD.fsIn());
+    f->codeAppendf("for (int i = 0; i < %i; ++i) {", sampleCount);
+    f->codeAppend (    "float4 klmd = grad_klmd * samples[i] + klmd_center;");
+    f->codeAppend (    "half f = klmd.y * klmd.z - klmd.x * klmd.x * klmd.x;");
+    f->codeAppendf(    "%s += all(greaterThan(half4(f, klmd.y, klmd.z, klmd.w), "
+                                             "half4(0))) ? %f : 0;",
+                       outputCoverage, 1.0 / sampleCount);
+    f->codeAppend ("}");
+}
diff --git a/src/gpu/ccpr/GrCCPRCubicShader.h b/src/gpu/ccpr/GrCCPRCubicShader.h
new file mode 100644
index 0000000..f9655be
--- /dev/null
+++ b/src/gpu/ccpr/GrCCPRCubicShader.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrCCPRCubicShader_DEFINED
+#define GrCCPRCubicShader_DEFINED
+
+#include "ccpr/GrCCPRCoverageProcessor.h"
+
+/**
+ * This class renders the coverage of convex closed cubic segments using the techniques outlined in
+ * "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
+ * Jim Blinn:
+ *
+ * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
+ *
+ * The provided curve segments must be convex, monotonic with respect to the vector of their closing
+ * edge [P3 - P0], and must not contain or be near any inflection points or loop intersections.
+ * (Use GrCCPRGeometry.)
+ */
+class GrCCPRCubicShader : public GrCCPRCoverageProcessor::Shader {
+public:
+    enum class CubicType {
+        kSerpentine,
+        kLoop
+    };
+
+protected:
+    GrCCPRCubicShader(CubicType cubicType) : fCubicType(cubicType) {}
+
+    int getNumInputPoints() const final { return 4; }
+
+    void appendInputPointFetch(const GrCCPRCoverageProcessor&, GrGLSLShaderBuilder*,
+                               const TexelBufferHandle& pointsBuffer,
+                               const char* pointId) const final;
+
+    void emitWind(GrGLSLShaderBuilder*, const char* pts, const char* rtAdjust,
+                  const char* outputWind) const final;
+
+    void emitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
+                       const char* bloat, const char* wind, const char* rtAdjust,
+                       GeometryVars*) const final;
+
+    virtual void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
+                                 const char* rtAdjust, GeometryVars*) const = 0;
+
+    WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
+                                const char* coverage, const char* wind) final;
+
+    virtual void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) = 0;
+
+    const CubicType   fCubicType;
+    GrShaderVar       fKLMMatrix{"klm_matrix", kFloat3x3_GrSLType};
+    GrShaderVar       fKLMDerivatives{"klm_derivatives", kFloat2_GrSLType, 3};
+    GrShaderVar       fEdgeDistanceEquation{"edge_distance_equation", kFloat3_GrSLType};
+    GrGLSLGeoToFrag   fKLMD{kFloat4_GrSLType};
+};
+
+class GrCCPRCubicHullShader : public GrCCPRCubicShader {
+public:
+    GrCCPRCubicHullShader(CubicType cubicType) : GrCCPRCubicShader(cubicType) {}
+
+private:
+    GeometryType getGeometryType() const override { return GeometryType::kHull; }
+    int getNumSegments() const override { return 4; } // 4 wedges.
+    void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* wedgeId,
+                         const char* rtAdjust, GeometryVars*) const override;
+    void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
+
+    GrGLSLGeoToFrag fGradMatrix{kFloat2x2_GrSLType};
+};
+
+class GrCCPRCubicCornerShader : public GrCCPRCubicShader {
+public:
+    GrCCPRCubicCornerShader(CubicType cubicType) : GrCCPRCubicShader(cubicType) {}
+
+private:
+    GeometryType getGeometryType() const override { return GeometryType::kCorners; }
+    int getNumSegments() const override { return 2; } // 2 corners.
+    void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* cornerId,
+                         const char* rtAdjust, GeometryVars*) const override;
+    void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
+
+    GrShaderVar        fEdgeDistanceDerivatives{"edge_distance_derivatives", kFloat2_GrSLType};
+    GrGLSLGeoToFrag    fdKLMDdx{kFloat4_GrSLType};
+    GrGLSLGeoToFrag    fdKLMDdy{kFloat4_GrSLType};
+};
+
+#endif
diff --git a/src/gpu/ccpr/GrCCPRQuadraticProcessor.cpp b/src/gpu/ccpr/GrCCPRQuadraticProcessor.cpp
deleted file mode 100644
index 2a56b22..0000000
--- a/src/gpu/ccpr/GrCCPRQuadraticProcessor.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrCCPRQuadraticProcessor.h"
-
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLGeometryShaderBuilder.h"
-#include "glsl/GrGLSLVertexShaderBuilder.h"
-
-void GrCCPRQuadraticProcessor::onEmitVertexShader(const GrCCPRCoverageProcessor& proc,
-                                                  GrGLSLVertexBuilder* v,
-                                                  const TexelBufferHandle& pointsBuffer,
-                                                  const char* atlasOffset, const char* rtAdjust,
-                                                  GrGPArgs* gpArgs) const {
-    v->codeAppend ("float2 self = ");
-    v->appendTexelFetch(pointsBuffer,
-                        SkStringPrintf("%s.x + sk_VertexID", proc.instanceAttrib()).c_str());
-    v->codeAppendf(".xy + %s;", atlasOffset);
-    gpArgs->fPositionVar.set(kFloat2_GrSLType, "self");
-}
-
-void GrCCPRQuadraticProcessor::emitWind(GrGLSLGeometryBuilder* g, const char* rtAdjust,
-                                        const char* outputWind) const {
-    // We will define bezierpts in onEmitGeometryShader.
-    g->codeAppend ("float area_times_2 = determinant(float2x2(bezierpts[1] - bezierpts[0], "
-                                                             "bezierpts[2] - bezierpts[0]));");
-    // Drop curves that are nearly flat, in favor of the higher quality triangle antialiasing.
-    g->codeAppendf("if (2 * abs(area_times_2) < length((bezierpts[2] - bezierpts[0]) * %s.zx)) {",
-                   rtAdjust);
-#ifndef SK_BUILD_FOR_MAC
-    g->codeAppend (    "return;");
-#else
-    // Returning from this geometry shader makes Mac very unhappy. Instead we make wind 0.
-    g->codeAppend (    "area_times_2 = 0;");
-#endif
-    g->codeAppend ("}");
-    g->codeAppendf("%s = sign(area_times_2);", outputWind);
-}
-
-void GrCCPRQuadraticProcessor::onEmitGeometryShader(GrGLSLGeometryBuilder* g,
-                                                    const char* emitVertexFn, const char* wind,
-                                                    const char* rtAdjust) const {
-    // Prepend bezierpts at the start of the shader.
-    g->codePrependf("float3x2 bezierpts = float3x2(sk_in[0].sk_Position.xy, "
-                                                  "sk_in[1].sk_Position.xy, "
-                                                  "sk_in[2].sk_Position.xy);");
-
-    g->declareGlobal(fCanonicalMatrix);
-    g->codeAppendf("%s = float3x3(0.0, 0, 1, "
-                                 "0.5, 0, 1, "
-                                 "1.0, 1, 1) * "
-                        "inverse(float3x3(bezierpts[0], 1, "
-                                         "bezierpts[1], 1, "
-                                         "bezierpts[2], 1));",
-                   fCanonicalMatrix.c_str());
-
-    g->declareGlobal(fCanonicalDerivatives);
-    g->codeAppendf("%s = float2x2(%s) * float2x2(%s.x, 0, 0, %s.z);",
-                   fCanonicalDerivatives.c_str(), fCanonicalMatrix.c_str(), rtAdjust, rtAdjust);
-
-    g->declareGlobal(fEdgeDistanceEquation);
-    g->codeAppendf("float2 edgept0 = bezierpts[%s > 0 ? 2 : 0];", wind);
-    g->codeAppendf("float2 edgept1 = bezierpts[%s > 0 ? 0 : 2];", wind);
-    this->emitEdgeDistanceEquation(g, "edgept0", "edgept1", fEdgeDistanceEquation.c_str());
-
-    this->emitQuadraticGeometry(g, emitVertexFn, rtAdjust);
-}
-
-void GrCCPRQuadraticProcessor::emitPerVertexGeometryCode(SkString* fnBody, const char* position,
-                                                         const char* /*coverage*/,
-                                                         const char* /*wind*/) const {
-    fnBody->appendf("%s.xy = (%s * float3(%s, 1)).xy;",
-                    fXYD.gsOut(), fCanonicalMatrix.c_str(), position);
-    fnBody->appendf("%s.z = dot(%s.xy, %s) + %s.z;",
-                    fXYD.gsOut(), fEdgeDistanceEquation.c_str(), position,
-                    fEdgeDistanceEquation.c_str());
-    this->onEmitPerVertexGeometryCode(fnBody);
-}
-
-void GrCCPRQuadraticHullProcessor::emitQuadraticGeometry(GrGLSLGeometryBuilder* g,
-                                                         const char* emitVertexFn,
-                                                         const char* /*rtAdjust*/) const {
-    // Find the t value whose tangent is halfway between the tangents at the endpionts.
-    // (We defined bezierpts in onEmitGeometryShader.)
-    g->codeAppend ("float2 tan0 = bezierpts[1] - bezierpts[0];");
-    g->codeAppend ("float2 tan1 = bezierpts[2] - bezierpts[1];");
-    g->codeAppend ("float2 midnorm = normalize(tan0) - normalize(tan1);");
-    g->codeAppend ("float2 T = midnorm * float2x2(tan0 - tan1, tan0);");
-    g->codeAppend ("float t = clamp(T.t / T.s, 0, 1);"); // T.s=0 is weeded out by this point.
-
-    // Clip the bezier triangle by the tangent at our new t value. This is a simple application for
-    // De Casteljau's algorithm.
-    g->codeAppendf("float4x2 quadratic_hull = float4x2(bezierpts[0], "
-                                                      "bezierpts[0] + tan0 * t, "
-                                                      "bezierpts[1] + tan1 * t, "
-                                                      "bezierpts[2]);");
-
-    int maxVerts = this->emitHullGeometry(g, emitVertexFn, "quadratic_hull", 4, "sk_InvocationID");
-
-    g->configure(GrGLSLGeometryBuilder::InputType::kTriangles,
-                 GrGLSLGeometryBuilder::OutputType::kTriangleStrip,
-                 maxVerts, 4);
-}
-
-void GrCCPRQuadraticHullProcessor::onEmitPerVertexGeometryCode(SkString* fnBody) const {
-    fnBody->appendf("%s = float2(2 * %s.x, -1) * %s;",
-                    fGradXY.gsOut(), fXYD.gsOut(), fCanonicalDerivatives.c_str());
-}
-
-void GrCCPRQuadraticHullProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f,
-                                                      const char* outputCoverage) const {
-    f->codeAppendf("float d = (%s.x * %s.x - %s.y) * inversesqrt(dot(%s, %s));",
-                   fXYD.fsIn(), fXYD.fsIn(), fXYD.fsIn(), fGradXY.fsIn(), fGradXY.fsIn());
-    f->codeAppendf("%s = clamp(0.5 - d, 0, 1);", outputCoverage);
-    f->codeAppendf("%s += min(%s.z, 0);", outputCoverage, fXYD.fsIn()); // Flat closing edge.
-}
-
-void GrCCPRQuadraticCornerProcessor::emitQuadraticGeometry(GrGLSLGeometryBuilder* g,
-                                                           const char* emitVertexFn,
-                                                           const char* rtAdjust) const {
-    g->declareGlobal(fEdgeDistanceDerivatives);
-    g->codeAppendf("%s = %s.xy * %s.xz;",
-                   fEdgeDistanceDerivatives.c_str(), fEdgeDistanceEquation.c_str(), rtAdjust);
-
-    g->codeAppendf("float2 corner = bezierpts[sk_InvocationID * 2];");
-    int numVertices = this->emitCornerGeometry(g, emitVertexFn, "corner");
-
-    g->configure(GrGLSLGeometryBuilder::InputType::kTriangles,
-                 GrGLSLGeometryBuilder::OutputType::kTriangleStrip, numVertices, 2);
-}
-
-void GrCCPRQuadraticCornerProcessor::onEmitPerVertexGeometryCode(SkString* fnBody) const {
-    fnBody->appendf("%s = float3(%s[0].x, %s[0].y, %s.x);",
-                    fdXYDdx.gsOut(), fCanonicalDerivatives.c_str(), fCanonicalDerivatives.c_str(),
-                    fEdgeDistanceDerivatives.c_str());
-    fnBody->appendf("%s = float3(%s[1].x, %s[1].y, %s.y);",
-                    fdXYDdy.gsOut(), fCanonicalDerivatives.c_str(), fCanonicalDerivatives.c_str(),
-                    fEdgeDistanceDerivatives.c_str());
-}
-
-void GrCCPRQuadraticCornerProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f,
-                                                        const char* outputCoverage) const {
-    f->codeAppendf("float x = %s.x, y = %s.y, d = %s.z;",
-                   fXYD.fsIn(), fXYD.fsIn(), fXYD.fsIn());
-    f->codeAppendf("float2x3 grad_xyd = float2x3(%s, %s);", fdXYDdx.fsIn(), fdXYDdy.fsIn());
-
-    // Erase what the previous hull shader wrote. We don't worry about the two corners falling on
-    // the same pixel because those cases should have been weeded out by this point.
-    f->codeAppend ("float f = x*x - y;");
-    f->codeAppend ("float2 grad_f = float2(2*x, -1) * float2x2(grad_xyd);");
-    f->codeAppendf("%s = -(0.5 - f * inversesqrt(dot(grad_f, grad_f)));", outputCoverage);
-    f->codeAppendf("%s -= d;", outputCoverage);
-
-    // Use software msaa to approximate coverage at the corner pixels.
-    int sampleCount = this->defineSoftSampleLocations(f, "samples");
-    f->codeAppendf("float3 xyd_center = float3(%s.xy, %s.z + 0.5);",
-                   fXYD.fsIn(), fXYD.fsIn());
-    f->codeAppendf("for (int i = 0; i < %i; ++i) {", sampleCount);
-    f->codeAppend (    "float3 xyd = grad_xyd * samples[i] + xyd_center;");
-    f->codeAppend (    "half f = xyd.y - xyd.x * xyd.x;"); // f > 0 -> inside curve.
-    f->codeAppendf(    "%s += all(greaterThan(float2(f,xyd.z), float2(0))) ? %f : 0;",
-                       outputCoverage, 1.0 / sampleCount);
-    f->codeAppendf("}");
-}
diff --git a/src/gpu/ccpr/GrCCPRQuadraticProcessor.h b/src/gpu/ccpr/GrCCPRQuadraticProcessor.h
deleted file mode 100644
index 8975923..0000000
--- a/src/gpu/ccpr/GrCCPRQuadraticProcessor.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrCCPRQuadraticProcessor_DEFINED
-#define GrCCPRQuadraticProcessor_DEFINED
-
-#include "ccpr/GrCCPRCoverageProcessor.h"
-
-/**
- * This class renders the coverage of closed quadratic curves using the techniques outlined in
- * "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
- * Jim Blinn:
- *
- * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
- *
- * The provided curves must be monotonic with respect to the vector of their closing edge [P2 - P0].
- * (Use GrCCPRGeometry.)
- */
-class GrCCPRQuadraticProcessor : public GrCCPRCoverageProcessor::PrimitiveProcessor {
-public:
-    GrCCPRQuadraticProcessor()
-            : INHERITED(CoverageType::kShader)
-            , fCanonicalMatrix("canonical_matrix", kFloat3x3_GrSLType, GrShaderVar::kNonArray)
-            , fCanonicalDerivatives("canonical_derivatives", kFloat2x2_GrSLType,
-                                    GrShaderVar::kNonArray)
-            , fEdgeDistanceEquation("edge_distance_equation", kFloat3_GrSLType,
-                                    GrShaderVar::kNonArray)
-            , fXYD(kFloat3_GrSLType) {}
-
-    void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override {
-        varyingHandler->addVarying("xyd", &fXYD, kHigh_GrSLPrecision);
-    }
-
-    void onEmitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*,
-                            const TexelBufferHandle& pointsBuffer, const char* atlasOffset,
-                            const char* rtAdjust, GrGPArgs*) const override;
-    void emitWind(GrGLSLGeometryBuilder*, const char* rtAdjust, const char* outputWind) const final;
-    void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* wind,
-                              const char* rtAdjust) const final;
-    void emitPerVertexGeometryCode(SkString* fnBody, const char* position, const char* coverage,
-                                   const char* wind) const final;
-
-protected:
-    virtual void emitQuadraticGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn,
-                                       const char* rtAdjust) const = 0;
-    virtual void onEmitPerVertexGeometryCode(SkString* fnBody) const = 0;
-
-    GrShaderVar       fCanonicalMatrix;
-    GrShaderVar       fCanonicalDerivatives;
-    GrShaderVar       fEdgeDistanceEquation;
-    GrGLSLGeoToFrag   fXYD;
-
-    typedef GrCCPRCoverageProcessor::PrimitiveProcessor INHERITED;
-};
-
-/**
- * This pass draws a conservative raster hull around the quadratic bezier curve, computes the
- * curve's coverage using the gradient-based AA technique outlined in the Loop/Blinn paper, and
- * uses simple distance-to-edge to subtract out coverage for the flat closing edge [P2 -> P0]. Since
- * the provided curves are monotonic, this will get every pixel right except the two corners.
- */
-class GrCCPRQuadraticHullProcessor : public GrCCPRQuadraticProcessor {
-public:
-    GrCCPRQuadraticHullProcessor()
-            : fGradXY(kFloat2_GrSLType) {}
-
-    void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override {
-        this->INHERITED::resetVaryings(varyingHandler);
-        varyingHandler->addVarying("grad_xy", &fGradXY, kHigh_GrSLPrecision);
-    }
-
-    void emitQuadraticGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn,
-                               const char* rtAdjust) const override;
-    void onEmitPerVertexGeometryCode(SkString* fnBody) const override;
-    void emitShaderCoverage(GrGLSLFragmentBuilder* f, const char* outputCoverage) const override;
-
-private:
-    GrGLSLGeoToFrag   fGradXY;
-
-    typedef GrCCPRQuadraticProcessor INHERITED;
-};
-
-/**
- * This pass fixes the corners of a closed quadratic segment with soft MSAA.
- */
-class GrCCPRQuadraticCornerProcessor : public GrCCPRQuadraticProcessor {
-public:
-    GrCCPRQuadraticCornerProcessor()
-            : fEdgeDistanceDerivatives("edge_distance_derivatives", kFloat2_GrSLType,
-                                       GrShaderVar::kNonArray)
-            , fdXYDdx(kFloat3_GrSLType)
-            , fdXYDdy(kFloat3_GrSLType) {}
-
-    void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override {
-        this->INHERITED::resetVaryings(varyingHandler);
-        varyingHandler->addFlatVarying("dXYDdx", &fdXYDdx, kHigh_GrSLPrecision);
-        varyingHandler->addFlatVarying("dXYDdy", &fdXYDdy, kHigh_GrSLPrecision);
-    }
-
-    void emitQuadraticGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn,
-                               const char* rtAdjust) const override;
-    void onEmitPerVertexGeometryCode(SkString* fnBody) const override;
-    void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const override;
-
-private:
-    GrShaderVar       fEdgeDistanceDerivatives;
-    GrGLSLGeoToFrag   fdXYDdx;
-    GrGLSLGeoToFrag   fdXYDdy;
-
-    typedef GrCCPRQuadraticProcessor INHERITED;
-};
-
-#endif
diff --git a/src/gpu/ccpr/GrCCPRQuadraticShader.cpp b/src/gpu/ccpr/GrCCPRQuadraticShader.cpp
new file mode 100644
index 0000000..9dc0c72
--- /dev/null
+++ b/src/gpu/ccpr/GrCCPRQuadraticShader.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrCCPRQuadraticShader.h"
+
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+
+void GrCCPRQuadraticShader::appendInputPointFetch(const GrCCPRCoverageProcessor& proc,
+                                                  GrGLSLShaderBuilder* s,
+                                                  const TexelBufferHandle& pointsBuffer,
+                                                  const char* pointId) const {
+    s->appendTexelFetch(pointsBuffer,
+                        SkStringPrintf("%s.x + %s", proc.instanceAttrib(), pointId).c_str());
+}
+
+void GrCCPRQuadraticShader::emitWind(GrGLSLShaderBuilder* s, const char* pts, const char* rtAdjust,
+                                     const char* outputWind) const {
+    s->codeAppendf("float area_times_2 = determinant(float2x2(%s[1] - %s[0], %s[2] - %s[0]));",
+                                                     pts, pts, pts, pts);
+    // Drop curves that are nearly flat, in favor of the higher quality triangle antialiasing.
+    s->codeAppendf("if (2 * abs(area_times_2) < length((%s[2] - %s[0]) * %s.zx)) {",
+                   pts, pts, rtAdjust);
+#ifndef SK_BUILD_FOR_MAC
+    s->codeAppend (    "return;");
+#else
+    // Returning from this geometry shader makes Mac very unhappy. Instead we make wind 0.
+    s->codeAppend (    "area_times_2 = 0;");
+#endif
+    s->codeAppend ("}");
+    s->codeAppendf("%s = sign(area_times_2);", outputWind);
+}
+
+void GrCCPRQuadraticShader::emitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
+                                          const char* segmentId, const char* bloat,
+                                          const char* wind, const char* rtAdjust,
+                                          GeometryVars* vars) const {
+    s->declareGlobal(fCanonicalMatrix);
+    s->codeAppendf("%s = float3x3(0.0, 0, 1, "
+                                 "0.5, 0, 1, "
+                                 "1.0, 1, 1) * "
+                        "inverse(float3x3(%s[0], 1, "
+                                         "%s[1], 1, "
+                                         "%s[2], 1));",
+                   fCanonicalMatrix.c_str(), pts, pts, pts);
+
+    s->declareGlobal(fCanonicalDerivatives);
+    s->codeAppendf("%s = float2x2(%s) * float2x2(%s.x, 0, 0, %s.z);",
+                   fCanonicalDerivatives.c_str(), fCanonicalMatrix.c_str(), rtAdjust, rtAdjust);
+
+    s->declareGlobal(fEdgeDistanceEquation);
+    s->codeAppendf("float2 edgept0 = %s[%s > 0 ? 2 : 0];", pts, wind);
+    s->codeAppendf("float2 edgept1 = %s[%s > 0 ? 0 : 2];", pts, wind);
+    Shader::EmitEdgeDistanceEquation(s, "edgept0", "edgept1", fEdgeDistanceEquation.c_str());
+
+    this->onEmitSetupCode(s, pts, segmentId, rtAdjust, vars);
+}
+
+GrCCPRQuadraticShader::WindHandling
+GrCCPRQuadraticShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler, SkString* code,
+                                      const char* position, const char* /*coverage*/,
+                                      const char* /*wind*/) {
+    varyingHandler->addVarying("xyd", &fXYD, kHigh_GrSLPrecision);
+    code->appendf("%s.xy = (%s * float3(%s, 1)).xy;",
+                  fXYD.gsOut(), fCanonicalMatrix.c_str(), position);
+    code->appendf("%s.z = dot(%s.xy, %s) + %s.z;",
+                  fXYD.gsOut(), fEdgeDistanceEquation.c_str(), position,
+                  fEdgeDistanceEquation.c_str());
+
+    this->onEmitVaryings(varyingHandler, code);
+    return WindHandling::kNotHandled;
+}
+
+void GrCCPRQuadraticHullShader::onEmitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
+                                                const char* /*wedgeId*/, const char* /*rtAdjust*/,
+                                                GeometryVars* vars) const {
+    // Find the T value whose tangent is halfway between the tangents at the endpionts.
+    s->codeAppendf("float2 tan0 = %s[1] - %s[0];", pts, pts);
+    s->codeAppendf("float2 tan1 = %s[2] - %s[1];", pts, pts);
+    s->codeAppend ("float2 midnorm = normalize(tan0) - normalize(tan1);");
+    s->codeAppend ("float2 T = midnorm * float2x2(tan0 - tan1, tan0);");
+    s->codeAppend ("float t = clamp(T.t / T.s, 0, 1);"); // T.s != 0; we cull flat curves on CPU.
+
+    // Clip the bezier triangle by the tangent at our new t value. This is a simple application for
+    // De Casteljau's algorithm.
+    s->codeAppendf("float4x2 quadratic_hull = float4x2(%s[0], "
+                                                      "%s[0] + tan0 * t, "
+                                                      "%s[1] + tan1 * t, "
+                                                      "%s[2]);", pts, pts, pts, pts);
+    vars->fHullVars.fAlternatePoints = "quadratic_hull";
+}
+
+void GrCCPRQuadraticHullShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
+                                               SkString* code) {
+    varyingHandler->addVarying("grad", &fGrad, kHigh_GrSLPrecision);
+    code->appendf("%s = float2(2 * %s.x, -1) * %s;",
+                  fGrad.gsOut(), fXYD.gsOut(), fCanonicalDerivatives.c_str());
+}
+
+void GrCCPRQuadraticHullShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
+                                                   const char* outputCoverage) const {
+    f->codeAppendf("float d = (%s.x * %s.x - %s.y) * inversesqrt(dot(%s, %s));",
+                   fXYD.fsIn(), fXYD.fsIn(), fXYD.fsIn(), fGrad.fsIn(), fGrad.fsIn());
+    f->codeAppendf("%s = clamp(0.5 - d, 0, 1);", outputCoverage);
+    f->codeAppendf("%s += min(%s.z, 0);", outputCoverage, fXYD.fsIn()); // Flat closing edge.
+}
+
+void GrCCPRQuadraticCornerShader::onEmitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
+                                                  const char* cornerId, const char* rtAdjust,
+                                                  GeometryVars* vars) const {
+    s->declareGlobal(fEdgeDistanceDerivatives);
+    s->codeAppendf("%s = %s.xy * %s.xz;",
+                   fEdgeDistanceDerivatives.c_str(), fEdgeDistanceEquation.c_str(), rtAdjust);
+
+    s->codeAppendf("float2 corner = %s[%s * 2];", pts, cornerId);
+    vars->fCornerVars.fPoint = "corner";
+}
+
+void GrCCPRQuadraticCornerShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
+                                                 SkString* code) {
+    varyingHandler->addFlatVarying("dXYDdx", &fdXYDdx, kHigh_GrSLPrecision);
+    code->appendf("%s = float3(%s[0].x, %s[0].y, %s.x);",
+                  fdXYDdx.gsOut(), fCanonicalDerivatives.c_str(), fCanonicalDerivatives.c_str(),
+                  fEdgeDistanceDerivatives.c_str());
+
+    varyingHandler->addFlatVarying("dXYDdy", &fdXYDdy, kHigh_GrSLPrecision);
+    code->appendf("%s = float3(%s[1].x, %s[1].y, %s.y);",
+                  fdXYDdy.gsOut(), fCanonicalDerivatives.c_str(), fCanonicalDerivatives.c_str(),
+                  fEdgeDistanceDerivatives.c_str());
+}
+
+void GrCCPRQuadraticCornerShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
+                                                     const char* outputCoverage) const {
+    f->codeAppendf("float x = %s.x, y = %s.y, d = %s.z;",
+                   fXYD.fsIn(), fXYD.fsIn(), fXYD.fsIn());
+    f->codeAppendf("float2x3 grad_xyd = float2x3(%s, %s);", fdXYDdx.fsIn(), fdXYDdy.fsIn());
+
+    // Erase what the previous hull shader wrote. We don't worry about the two corners falling on
+    // the same pixel because those cases should have been weeded out by this point.
+    f->codeAppend ("float f = x*x - y;");
+    f->codeAppend ("float2 grad_f = float2(2*x, -1) * float2x2(grad_xyd);");
+    f->codeAppendf("%s = -(0.5 - f * inversesqrt(dot(grad_f, grad_f)));", outputCoverage);
+    f->codeAppendf("%s -= d;", outputCoverage);
+
+    // Use software msaa to approximate coverage at the corner pixels.
+    int sampleCount = Shader::DefineSoftSampleLocations(f, "samples");
+    f->codeAppendf("float3 xyd_center = float3(%s.xy, %s.z + 0.5);", fXYD.fsIn(), fXYD.fsIn());
+    f->codeAppendf("for (int i = 0; i < %i; ++i) {", sampleCount);
+    f->codeAppend (    "float3 xyd = grad_xyd * samples[i] + xyd_center;");
+    f->codeAppend (    "half f = xyd.y - xyd.x * xyd.x;"); // f > 0 -> inside curve.
+    f->codeAppendf(    "%s += all(greaterThan(float2(f,xyd.z), float2(0))) ? %f : 0;",
+                       outputCoverage, 1.0 / sampleCount);
+    f->codeAppendf("}");
+}
diff --git a/src/gpu/ccpr/GrCCPRQuadraticShader.h b/src/gpu/ccpr/GrCCPRQuadraticShader.h
new file mode 100644
index 0000000..7441266
--- /dev/null
+++ b/src/gpu/ccpr/GrCCPRQuadraticShader.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrCCPRQuadraticShader_DEFINED
+#define GrCCPRQuadraticShader_DEFINED
+
+#include "ccpr/GrCCPRCoverageProcessor.h"
+
+/**
+ * This class renders the coverage of closed quadratic curves using the techniques outlined in
+ * "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and
+ * Jim Blinn:
+ *
+ * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
+ *
+ * The provided curves must be monotonic with respect to the vector of their closing edge [P2 - P0].
+ * (Use GrCCPRGeometry.)
+ */
+class GrCCPRQuadraticShader : public GrCCPRCoverageProcessor::Shader {
+protected:
+    int getNumInputPoints() const final { return 3; }
+
+    void appendInputPointFetch(const GrCCPRCoverageProcessor&, GrGLSLShaderBuilder*,
+                               const TexelBufferHandle& pointsBuffer,
+                               const char* pointId) const override;
+
+    void emitWind(GrGLSLShaderBuilder*, const char* pts, const char* rtAdjust,
+                  const char* outputWind) const final;
+
+    void emitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
+                       const char* bloat, const char* wind, const char* rtAdjust,
+                       GeometryVars*) const final;
+
+    virtual void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
+                                 const char* rtAdjust, GeometryVars*) const = 0;
+
+    WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
+                                const char* coverage, const char* wind) final;
+
+    virtual void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) = 0;
+
+    const GrShaderVar   fCanonicalMatrix{"canonical_matrix", kFloat3x3_GrSLType};
+    const GrShaderVar   fCanonicalDerivatives{"canonical_derivatives", kFloat2x2_GrSLType};
+    const GrShaderVar   fEdgeDistanceEquation{"edge_distance_equation", kFloat3_GrSLType};
+    GrGLSLGeoToFrag     fXYD{kFloat3_GrSLType};
+};
+
+/**
+ * This pass draws a conservative raster hull around the quadratic bezier curve, computes the
+ * curve's coverage using the gradient-based AA technique outlined in the Loop/Blinn paper, and
+ * uses simple distance-to-edge to subtract out coverage for the flat closing edge [P2 -> P0]. Since
+ * the provided curves are monotonic, this will get every pixel right except the two corners.
+ */
+class GrCCPRQuadraticHullShader : public GrCCPRQuadraticShader {
+    int getNumSegments() const final { return 4; } // 4 wedges.
+
+    GeometryType getGeometryType() const override { return GeometryType::kHull; }
+    void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* wedgeId,
+                         const char* rtAdjust, GeometryVars*) const override;
+    void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
+
+    GrGLSLGeoToFrag fGrad{kFloat2_GrSLType};
+};
+
+/**
+ * This pass fixes the corners of a closed quadratic segment with soft MSAA.
+ */
+class GrCCPRQuadraticCornerShader : public GrCCPRQuadraticShader {
+    int getNumSegments() const final { return 2; } // 2 corners.
+
+    GeometryType getGeometryType() const override { return GeometryType::kCorners; }
+    void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* cornerId,
+                         const char* rtAdjust, GeometryVars*) const override;
+    void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
+
+    const GrShaderVar   fEdgeDistanceDerivatives{"edge_distance_derivatives", kFloat2_GrSLType};
+    GrGLSLGeoToFrag     fdXYDdx{kFloat3_GrSLType};
+    GrGLSLGeoToFrag     fdXYDdy{kFloat3_GrSLType};
+};
+
+#endif
diff --git a/src/gpu/ccpr/GrCCPRTriangleProcessor.cpp b/src/gpu/ccpr/GrCCPRTriangleProcessor.cpp
deleted file mode 100644
index 83b5c7b..0000000
--- a/src/gpu/ccpr/GrCCPRTriangleProcessor.cpp
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrCCPRTriangleProcessor.h"
-
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLGeometryShaderBuilder.h"
-#include "glsl/GrGLSLVertexShaderBuilder.h"
-
-void GrCCPRTriangleProcessor::onEmitVertexShader(const GrCCPRCoverageProcessor& proc,
-                                                 GrGLSLVertexBuilder* v,
-                                                 const TexelBufferHandle& pointsBuffer,
-                                                 const char* atlasOffset, const char* rtAdjust,
-                                                 GrGPArgs* gpArgs) const {
-    // Copy the input attrib to an intermediate array. The Intel GLSL compiler hits an internal
-    // assertion if we index the input attrib itself with sk_VertexID.
-    v->codeAppendf("int indices[3] = int[3](%s.x, %s.y, %s.z);",
-                   proc.instanceAttrib(), proc.instanceAttrib(), proc.instanceAttrib());
-    v->codeAppend ("float2 self = ");
-    v->appendTexelFetch(pointsBuffer, "indices[sk_VertexID]");
-    v->codeAppendf(".xy + %s;", atlasOffset);
-    gpArgs->fPositionVar.set(kFloat2_GrSLType, "self");
-}
-
-void GrCCPRTriangleProcessor::defineInputVertices(GrGLSLGeometryBuilder* g) const {
-    // Prepend in_vertices at the start of the shader.
-    g->codePrependf("float3x2 in_vertices = float3x2(sk_in[0].sk_Position.xy, "
-                                                    "sk_in[1].sk_Position.xy, "
-                                                    "sk_in[2].sk_Position.xy);");
-}
-
-void GrCCPRTriangleProcessor::emitWind(GrGLSLGeometryBuilder* g, const char* /*rtAdjust*/,
-                                       const char* outputWind) const {
-    // We will define in_vertices in defineInputVertices.
-    g->codeAppendf("%s = sign(determinant(float2x2(in_vertices[1] - in_vertices[0], "
-                                                  "in_vertices[2] - in_vertices[0])));",
-                   outputWind);
-}
-
-void GrCCPRTriangleHullAndEdgeProcessor::onEmitGeometryShader(GrGLSLGeometryBuilder* g,
-                                                              const char* emitVertexFn,
-                                                              const char* wind,
-                                                              const char* rtAdjust) const {
-    this->defineInputVertices(g);
-    int maxOutputVertices = 0;
-
-    if (GeometryType::kEdges != fGeometryType) {
-        maxOutputVertices += this->emitHullGeometry(g, emitVertexFn, "in_vertices", 3,
-                                                    "sk_InvocationID");
-    }
-
-    if (GeometryType::kHulls != fGeometryType) {
-        g->codeAppend ("int edgeidx0 = sk_InvocationID, "
-                           "edgeidx1 = (edgeidx0 + 1) % 3;");
-        g->codeAppendf("float2 edgept0 = in_vertices[%s > 0 ? edgeidx0 : edgeidx1];", wind);
-        g->codeAppendf("float2 edgept1 = in_vertices[%s > 0 ? edgeidx1 : edgeidx0];", wind);
-
-        maxOutputVertices += this->emitEdgeGeometry(g, emitVertexFn, "edgept0", "edgept1");
-    }
-
-    g->configure(GrGLSLGeometryBuilder::InputType::kTriangles,
-                 GrGLSLGeometryBuilder::OutputType::kTriangleStrip,
-                 maxOutputVertices, 3);
-}
-
-void GrCCPRTriangleCornerProcessor::onEmitGeometryShader(GrGLSLGeometryBuilder* g,
-                                                         const char* emitVertexFn, const char* wind,
-                                                         const char* rtAdjust) const {
-    this->defineInputVertices(g);
-
-    g->codeAppend ("float2 corner = in_vertices[sk_InvocationID];");
-    g->codeAppend ("float2x2 vectors = float2x2(corner - in_vertices[(sk_InvocationID + 2) % 3], "
-                                               "corner - in_vertices[(sk_InvocationID + 1) % 3]);");
-
-    // Make sure neither vector is 0 in order to avoid a divide-by-zero. Wind will be zero anyway if
-    // this is the case, so whatever we output won't have any effect as long it isn't NaN or Inf.
-    g->codeAppendf("for (int i = 0; i < 2; ++i) {");
-    g->codeAppendf(    "vectors[i] = any(notEqual(vectors[i], float2(0))) ? "
-                                    "vectors[i] : float2(1);");
-    g->codeAppendf("}");
-
-    // Find the vector that bisects the region outside the incoming edges. Each edge is responsible
-    // to subtract the outside region on its own the side of the bisector.
-    g->codeAppendf("float2 leftdir = normalize(vectors[%s > 0 ? 0 : 1]);", wind);
-    g->codeAppendf("float2 rightdir = normalize(vectors[%s > 0 ? 1 : 0]);", wind);
-    g->codeAppendf("float2 bisect = dot(leftdir, rightdir) >= 0 ? leftdir + rightdir : "
-                                        "float2(leftdir.y - rightdir.y, rightdir.x - leftdir.x);");
-
-    // In ccpr we don't calculate exact geometric pixel coverage. What the distance-to-edge method
-    // actually finds is coverage inside a logical "AA box", one that is rotated inline with the
-    // edge, and in our case, up-scaled to circumscribe the actual pixel. Below we set up
-    // transformations into normalized logical AA box space for both incoming edges. These will tell
-    // the fragment shader where the corner is located within each edge's AA box.
-    g->declareGlobal(fAABoxMatrices);
-    g->declareGlobal(fAABoxTranslates);
-    g->declareGlobal(fGeoShaderBisects);
-    g->codeAppendf("for (int i = 0; i < 2; ++i) {");
-    // The X component runs parallel to the edge (i.e. distance to the corner).
-    g->codeAppendf(    "float2 n = -vectors[%s > 0 ? i : 1 - i];", wind);
-    g->codeAppendf(    "float nwidth = dot(abs(n), bloat) * 2;");
-    g->codeAppendf(    "n /= nwidth;"); // nwidth != 0 because both vectors != 0.
-    g->codeAppendf(    "%s[i][0] = n;", fAABoxMatrices.c_str());
-    g->codeAppendf(    "%s[i][0] = -dot(n, corner) + .5;", fAABoxTranslates.c_str());
-
-    // The Y component runs perpendicular to the edge (i.e. distance-to-edge).
-    // NOTE: once we are back in device space and bloat.x == bloat.y, we will not need to find and
-    // divide by nwidth a second time.
-    g->codeAppendf(    "n = (i == 0) ? float2(-n.y, n.x) : float2(n.y, -n.x);");
-    g->codeAppendf(    "nwidth = dot(abs(n), bloat) * 2;");
-    g->codeAppendf(    "n /= nwidth;");
-    g->codeAppendf(    "%s[i][1] = n;", fAABoxMatrices.c_str());
-    g->codeAppendf(    "%s[i][1] = -dot(n, corner) + .5;", fAABoxTranslates.c_str());
-
-    // Translate the bisector into logical AA box space.
-    // NOTE: Since the region outside two edges of a convex shape is in [180 deg, 360 deg], the
-    // bisector will therefore be in [90 deg, 180 deg]. Or, x >= 0 and y <= 0 in AA box space.
-    g->codeAppendf(    "%s[i] = -bisect * %s[i];",
-                       fGeoShaderBisects.c_str(), fAABoxMatrices.c_str());
-    g->codeAppendf("}");
-
-    int numVertices = this->emitCornerGeometry(g, emitVertexFn, "corner");
-
-    g->configure(GrGLSLGeometryBuilder::InputType::kTriangles,
-                 GrGLSLGeometryBuilder::OutputType::kTriangleStrip,
-                 numVertices, 3);
-}
-
-void GrCCPRTriangleCornerProcessor::emitPerVertexGeometryCode(SkString* fnBody,
-                                                              const char* position,
-                                                              const char* /*coverage*/,
-                                                              const char* wind) const {
-    fnBody->appendf("for (int i = 0; i < 2; ++i) {");
-    fnBody->appendf(    "%s[i] = %s * %s[i] + %s[i];",
-                        fCornerLocationInAABoxes.gsOut(), position, fAABoxMatrices.c_str(),
-                        fAABoxTranslates.c_str());
-    fnBody->appendf(    "%s[i] = %s[i];", fBisectInAABoxes.gsOut(), fGeoShaderBisects.c_str());
-    fnBody->appendf("}");
-}
-
-void GrCCPRTriangleCornerProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f,
-                                                       const char* outputCoverage) const {
-    // By the time we reach this shader, the pixel is in the following state:
-    //
-    //   1. The hull shader has emitted a coverage of 1.
-    //   2. Both edges have subtracted the area on their outside.
-    //
-    // This generally works, but it is a problem for corner pixels. There is a region within corner
-    // pixels that is outside both edges at the same time. This means the region has been double
-    // subtracted (once by each edge). The purpose of this shader is to fix these corner pixels.
-    //
-    // More specifically, each edge redoes its coverage analysis so that it only subtracts the
-    // outside area that falls on its own side of the bisector line.
-    //
-    // NOTE: unless the edges fall on multiples of 90 deg from one another, they will have different
-    // AA boxes. (For an explanation of AA boxes, see comments in onEmitGeometryShader.) This means
-    // the coverage analysis will only be approximate. It seems acceptable, but if we want exact
-    // coverage we will need to switch to a more expensive model.
-    f->codeAppendf("%s = 0;", outputCoverage);
-
-    // Loop through both edges.
-    f->codeAppendf("for (int i = 0; i < 2; ++i) {");
-    f->codeAppendf(    "half2 corner = %s[i];", fCornerLocationInAABoxes.fsIn());
-    f->codeAppendf(    "half2 bisect = %s[i];", fBisectInAABoxes.fsIn());
-
-    // Find the point at which the bisector exits the logical AA box.
-    // (The inequality works because bisect.x is known >= 0 and bisect.y is known <= 0.)
-    f->codeAppendf(    "half2 d = half2(1 - corner.x, -corner.y);");
-    f->codeAppendf(    "half T = d.y * bisect.x >= d.x * bisect.y ? d.y / bisect.y "
-                                                                 ": d.x / bisect.x;");
-    f->codeAppendf(    "half2 exit = corner + bisect * T;");
-
-    // These lines combined (and the final multiply by .5) accomplish the following:
-    //   1. Add back the area beyond the corner that was subtracted out previously.
-    //   2. Subtract out the area beyond the corner, but under the bisector.
-    // The other edge will take care of the area on its own side of the bisector.
-    f->codeAppendf(    "%s += (2 - corner.x - exit.x) * corner.y;", outputCoverage);
-    f->codeAppendf(    "%s += (corner.x - 1) * exit.y;", outputCoverage);
-    f->codeAppendf("}");
-
-    f->codeAppendf("%s *= .5;", outputCoverage);
-}
diff --git a/src/gpu/ccpr/GrCCPRTriangleProcessor.h b/src/gpu/ccpr/GrCCPRTriangleProcessor.h
deleted file mode 100644
index ca143d1..0000000
--- a/src/gpu/ccpr/GrCCPRTriangleProcessor.h
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrCCPRTriangleProcessor_DEFINED
-#define GrCCPRTriangleProcessor_DEFINED
-
-#include "ccpr/GrCCPRCoverageProcessor.h"
-
-/**
- * This class renders the coverage of triangles.
- *
- * Triangles are rendered in three passes:
- *
- * Pass 1: Draw the triangle's conservative raster hull with a coverage of 1. (Conservative raster
- *         is drawn by considering 3 pixel size boxes, one centered at each vertex, and drawing the
- *         convex hull of those boxes.)
- *
- * Pass 2: Smooth the edges that were over-rendered during Pass 1. Draw the conservative raster of
- *         each edge (i.e. convex hull of two pixel-size boxes at the endpoints), interpolating from
- *         coverage=-1 on the outside edge to coverage=0 on the inside edge.
- *
- * Pass 3: Touch up the corner pixels to have the correct coverage.
- */
-class GrCCPRTriangleProcessor : public GrCCPRCoverageProcessor::PrimitiveProcessor {
-public:
-    GrCCPRTriangleProcessor(CoverageType initialCoverage) : INHERITED(initialCoverage) {}
-
-    void onEmitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*,
-                            const TexelBufferHandle& pointsBuffer, const char* atlasOffset,
-                            const char* rtAdjust, GrGPArgs*) const override;
-    void emitWind(GrGLSLGeometryBuilder*, const char* rtAdjust, const char* outputWind) const final;
-
-protected:
-    void defineInputVertices(GrGLSLGeometryBuilder*) const;
-
-private:
-    typedef GrCCPRCoverageProcessor::PrimitiveProcessor INHERITED;
-};
-
-class GrCCPRTriangleHullAndEdgeProcessor : public GrCCPRTriangleProcessor {
-public:
-    enum class GeometryType {
-        kHulls,
-        kEdges,
-        kHullsAndEdges
-    };
-
-    GrCCPRTriangleHullAndEdgeProcessor(GeometryType geometryType)
-            : INHERITED(GeometryType::kHulls == geometryType ?
-                        CoverageType::kOne : CoverageType::kInterpolated)
-            , fGeometryType(geometryType) {}
-
-    void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* wind,
-                              const char* rtAdjust) const override;
-
-private:
-    const GeometryType fGeometryType;
-
-    typedef GrCCPRTriangleProcessor INHERITED;
-};
-
-/**
- * This pass fixes the corner pixels of a triangle. It touches up the simple distance-to-edge
- * coverage analysis done previously so that it takes into account the region that is outside both
- * edges at the same time.
- */
-class GrCCPRTriangleCornerProcessor : public GrCCPRTriangleProcessor {
-public:
-    GrCCPRTriangleCornerProcessor()
-            : INHERITED(CoverageType::kShader)
-            , fAABoxMatrices("aa_box_matrices", kFloat2x2_GrSLType, 2)
-            , fAABoxTranslates("aa_box_translates", kFloat2_GrSLType, 2)
-            , fGeoShaderBisects("bisects", kFloat2_GrSLType, 2)
-            , fCornerLocationInAABoxes(kFloat2x2_GrSLType)
-            , fBisectInAABoxes(kFloat2x2_GrSLType) {}
-
-    void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override {
-        this->INHERITED::resetVaryings(varyingHandler);
-        varyingHandler->addVarying("corner_location_in_aa_boxes", &fCornerLocationInAABoxes);
-        varyingHandler->addFlatVarying("bisect_in_aa_boxes", &fBisectInAABoxes);
-    }
-
-    void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* wind,
-                              const char* rtAdjust) const override;
-    void emitPerVertexGeometryCode(SkString* fnBody, const char* position, const char* coverage,
-                                   const char* wind) const override;
-    void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const override;
-
-private:
-    GrShaderVar       fAABoxMatrices;
-    GrShaderVar       fAABoxTranslates;
-    GrShaderVar       fGeoShaderBisects;
-    GrGLSLGeoToFrag   fCornerLocationInAABoxes;
-    GrGLSLGeoToFrag   fBisectInAABoxes;
-
-    typedef GrCCPRTriangleProcessor INHERITED;
-};
-
-#endif
diff --git a/src/gpu/ccpr/GrCCPRTriangleShader.cpp b/src/gpu/ccpr/GrCCPRTriangleShader.cpp
new file mode 100644
index 0000000..0bb85ac
--- /dev/null
+++ b/src/gpu/ccpr/GrCCPRTriangleShader.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrCCPRTriangleShader.h"
+
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLGeometryShaderBuilder.h"
+#include "glsl/GrGLSLVertexShaderBuilder.h"
+
+void GrCCPRTriangleShader::appendInputPointFetch(const GrCCPRCoverageProcessor& proc,
+                                                 GrGLSLShaderBuilder* s,
+                                                 const TexelBufferHandle& pointsBuffer,
+                                                 const char* pointId) const {
+    s->appendTexelFetch(pointsBuffer,
+                        SkStringPrintf("%s[%s]", proc.instanceAttrib(), pointId).c_str());
+}
+
+void GrCCPRTriangleShader::emitWind(GrGLSLShaderBuilder* s, const char* pts, const char* rtAdjust,
+              const char* outputWind) const {
+    s->codeAppendf("%s = sign(determinant(float2x2(%s[1] - %s[0], %s[2] - %s[0])));",
+                   outputWind, pts, pts, pts, pts);
+}
+
+GrCCPRTriangleHullShader::WindHandling
+GrCCPRTriangleHullShader::onEmitVaryings(GrGLSLVaryingHandler*, SkString* code,
+                                         const char* /*position*/, const char* /*coverage*/,
+                                         const char* /*wind*/) {
+    return WindHandling::kNotHandled; // No varyings.Let the base class handle wind.
+}
+
+void GrCCPRTriangleHullShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
+                                                  const char* outputCoverage) const {
+    f->codeAppendf("%s = 1;", outputCoverage);
+}
+
+GrCCPRTriangleEdgeShader::WindHandling
+GrCCPRTriangleEdgeShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler, SkString* code,
+                                         const char* position, const char* coverage,
+                                         const char* wind) {
+    varyingHandler->addVarying("coverage_times_wind", &fCoverageTimesWind, kLow_GrSLPrecision);
+    code->appendf("%s = %s * %s;", fCoverageTimesWind.gsOut(), coverage, wind);
+    return WindHandling::kHandled;
+}
+
+void GrCCPRTriangleEdgeShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
+                                                  const char* outputCoverage) const {
+    f->codeAppendf("%s = %s;", outputCoverage, fCoverageTimesWind.fsIn());
+}
+
+void GrCCPRTriangleCornerShader::emitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
+                                               const char* cornerId, const char* bloat,
+                                               const char* wind, const char* rtAdjust,
+                                               GeometryVars* vars) const {
+    s->codeAppendf("float2 corner = %s[sk_InvocationID];", pts);
+    vars->fCornerVars.fPoint = "corner";
+
+    s->codeAppendf("float2x2 vectors = float2x2(corner - %s[(sk_InvocationID + 2) %% 3], "
+                                               "corner - %s[(sk_InvocationID + 1) %% 3]);",
+                                               pts, pts);
+
+    // Make sure neither vector is 0 to avoid a divide-by-zero. Wind will be zero anyway if this
+    // is the case, so whatever we output won't have any effect as long it isn't NaN or Inf.
+    s->codeAppend ("for (int i = 0; i < 2; ++i) {");
+    s->codeAppend (    "vectors[i] = (vectors[i] != float2(0)) ? vectors[i] : float2(1);");
+    s->codeAppend ("}");
+
+    // Find the vector that bisects the region outside the incoming edges. Each edge is
+    // responsible to subtract the outside region on its own the side of the bisector.
+    s->codeAppendf("float2 leftdir = normalize(vectors[%s > 0 ? 0 : 1]);", wind);
+    s->codeAppendf("float2 rightdir = normalize(vectors[%s > 0 ? 1 : 0]);", wind);
+    s->codeAppend ("float2 bisect = dot(leftdir, rightdir) >= 0 ? "
+                                   "leftdir + rightdir : "
+                                   "float2(leftdir.y - rightdir.y, rightdir.x - leftdir.x);");
+
+    // In ccpr we don't calculate exact geometric pixel coverage. What the distance-to-edge
+    // method actually finds is coverage inside a logical "AA box", one that is rotated inline
+    // with the edge, and in our case, up-scaled to circumscribe the actual pixel. Below we set
+    // up transformations into normalized logical AA box space for both incoming edges. These
+    // will tell the fragment shader where the corner is located within each edge's AA box.
+    s->declareGlobal(fAABoxMatrices);
+    s->declareGlobal(fAABoxTranslates);
+    s->declareGlobal(fGeoShaderBisects);
+    s->codeAppendf("for (int i = 0; i < 2; ++i) {");
+    // The X component runs parallel to the edge (i.e. distance to the corner).
+    s->codeAppendf(    "float2 n = -vectors[%s > 0 ? i : 1 - i];", wind);
+    s->codeAppend (    "float nwidth = dot(abs(n), bloat) * 2;");
+    s->codeAppend (    "n /= nwidth;"); // nwidth != 0 because both vectors != 0.
+    s->codeAppendf(    "%s[i][0] = n;", fAABoxMatrices.c_str());
+    s->codeAppendf(    "%s[i][0] = -dot(n, corner) + .5;", fAABoxTranslates.c_str());
+
+    // The Y component runs perpendicular to the edge (i.e. distance-to-edge).
+    // NOTE: if we are back in device space and bloat.x == bloat.y, we will not need to find and
+    // divide by nwidth a second time.
+    s->codeAppend (    "n = (i == 0) ? float2(-n.y, n.x) : float2(n.y, -n.x);");
+    s->codeAppend (    "nwidth = dot(abs(n), bloat) * 2;");
+    s->codeAppend (    "n /= nwidth;");
+    s->codeAppendf(    "%s[i][1] = n;", fAABoxMatrices.c_str());
+    s->codeAppendf(    "%s[i][1] = -dot(n, corner) + .5;", fAABoxTranslates.c_str());
+
+    // Translate the bisector into logical AA box space.
+    // NOTE: Since the region outside two edges of a convex shape is in [180 deg, 360 deg], the
+    // bisector will therefore be in [90 deg, 180 deg]. Or, x >= 0 and y <= 0 in AA box space.
+    s->codeAppendf(    "%s[i] = -bisect * %s[i];",
+                       fGeoShaderBisects.c_str(), fAABoxMatrices.c_str());
+    s->codeAppend ("}");
+}
+
+GrCCPRTriangleCornerShader::WindHandling
+GrCCPRTriangleCornerShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler, SkString* code,
+                                           const char* position, const char* /*coverage*/,
+                                           const char* /*wind*/) {
+    varyingHandler->addVarying("corner_location_in_aa_boxes", &fCornerLocationInAABoxes);
+    varyingHandler->addFlatVarying("bisect_in_aa_boxes", &fBisectInAABoxes);
+    code->appendf("for (int i = 0; i < 2; ++i) {");
+    code->appendf(    "%s[i] = %s * %s[i] + %s[i];",
+                      fCornerLocationInAABoxes.gsOut(), position, fAABoxMatrices.c_str(),
+                      fAABoxTranslates.c_str());
+    code->appendf(    "%s[i] = %s[i];", fBisectInAABoxes.gsOut(), fGeoShaderBisects.c_str());
+    code->appendf("}");
+
+    return WindHandling::kNotHandled;
+}
+
+void GrCCPRTriangleCornerShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
+                                                    const char* outputCoverage) const {
+    // By the time we reach this shader, the pixel is in the following state:
+    //
+    //   1. The hull shader has emitted a coverage of 1.
+    //   2. Both edges have subtracted the area on their outside.
+    //
+    // This generally works, but it is a problem for corner pixels. There is a region within
+    // corner pixels that is outside both edges at the same time. This means the region has been
+    // double subtracted (once by each edge). The purpose of this shader is to fix these corner
+    // pixels.
+    //
+    // More specifically, each edge redoes its coverage analysis so that it only subtracts the
+    // outside area that falls on its own side of the bisector line.
+    //
+    // NOTE: unless the edges fall on multiples of 90 deg from one another, they will have
+    // different AA boxes. (For an explanation of AA boxes, see comments in
+    // onEmitGeometryShader.) This means the coverage analysis will only be approximate. It
+    // seems acceptable, but if we want exact coverage we will need to switch to a more
+    // expensive model.
+    f->codeAppendf("for (int i = 0; i < 2; ++i) {"); // Loop through both edges.
+    f->codeAppendf(    "half2 corner = %s[i];", fCornerLocationInAABoxes.fsIn());
+    f->codeAppendf(    "half2 bisect = %s[i];", fBisectInAABoxes.fsIn());
+
+    // Find the point at which the bisector exits the logical AA box.
+    // (The inequality works because bisect.x is known >= 0 and bisect.y is known <= 0.)
+    f->codeAppendf(    "half2 d = half2(1 - corner.x, -corner.y);");
+    f->codeAppendf(    "half T = d.y * bisect.x >= d.x * bisect.y ? d.y / bisect.y "
+                                                                 ": d.x / bisect.x;");
+    f->codeAppendf(    "half2 exit = corner + bisect * T;");
+
+    // These lines combined (and the final multiply by .5) accomplish the following:
+    //   1. Add back the area beyond the corner that was subtracted out previously.
+    //   2. Subtract out the area beyond the corner, but under the bisector.
+    // The other edge will take care of the area on its own side of the bisector.
+    f->codeAppendf(    "%s += (2 - corner.x - exit.x) * corner.y;", outputCoverage);
+    f->codeAppendf(    "%s += (corner.x - 1) * exit.y;", outputCoverage);
+    f->codeAppendf("}");
+
+    f->codeAppendf("%s *= .5;", outputCoverage);
+}
diff --git a/src/gpu/ccpr/GrCCPRTriangleShader.h b/src/gpu/ccpr/GrCCPRTriangleShader.h
new file mode 100644
index 0000000..efc2521
--- /dev/null
+++ b/src/gpu/ccpr/GrCCPRTriangleShader.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrCCPRTriangleShader_DEFINED
+#define GrCCPRTriangleShader_DEFINED
+
+#include "ccpr/GrCCPRCoverageProcessor.h"
+
+/**
+ * This class renders the coverage of triangles. Triangles are rendered in three passes, as
+ * described below.
+ */
+class GrCCPRTriangleShader : public GrCCPRCoverageProcessor::Shader {
+public:
+    int getNumInputPoints() const final { return 3; }
+    int getNumSegments() const final { return 3; } // 3 wedges, 3 edges, 3 corners.
+
+    void appendInputPointFetch(const GrCCPRCoverageProcessor&, GrGLSLShaderBuilder*,
+                               const TexelBufferHandle& pointsBuffer,
+                               const char* pointId) const final;
+
+    void emitWind(GrGLSLShaderBuilder* s, const char* pts, const char* rtAdjust,
+                  const char* outputWind) const final;
+};
+
+/**
+ * Pass 1: Draw the triangle's conservative raster hull with a coverage of 1. (Conservative raster
+ *         is drawn by considering 3 pixel size boxes, one centered at each vertex, and drawing the
+ *         convex hull of those boxes.)
+ */
+class GrCCPRTriangleHullShader : public GrCCPRTriangleShader {
+    GeometryType getGeometryType() const override { return GeometryType::kHull; }
+
+    WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
+                                const char* coverage, const char* wind) override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder* f, const char* outputCoverage) const override;
+};
+
+/**
+ * Pass 2: Smooth the edges that were over-rendered during Pass 1. Draw the conservative raster of
+ *         each edge (i.e. convex hull of two pixel-size boxes at the endpoints), interpolating from
+ *         coverage=-1 on the outside edge to coverage=0 on the inside edge.
+ */
+class GrCCPRTriangleEdgeShader : public GrCCPRTriangleShader {
+    GeometryType getGeometryType() const override { return GeometryType::kEdges; }
+
+    WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
+                                const char* coverage, const char* wind) override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
+
+    GrGLSLGeoToFrag fCoverageTimesWind{kHalf_GrSLType};
+};
+
+/**
+ * Pass 3: Touch up the corner pixels. Here we fix the simple distance-to-edge coverage analysis
+ *         done previously so that it takes into account the region that is outside both edges at
+ *         the same time.
+ */
+class GrCCPRTriangleCornerShader : public GrCCPRTriangleShader {
+    GeometryType getGeometryType() const override { return GeometryType::kCorners; }
+
+    void emitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* cornerId,
+                       const char* bloat, const char* wind, const char* rtAdjust,
+                       GeometryVars*) const override;
+    WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
+                                const char* coverage, const char* wind) override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder* f, const char* outputCoverage) const override;
+
+    GrShaderVar       fAABoxMatrices{"aa_box_matrices", kFloat2x2_GrSLType, 2};
+    GrShaderVar       fAABoxTranslates{"aa_box_translates", kFloat2_GrSLType, 2};
+    GrShaderVar       fGeoShaderBisects{"bisects", kFloat2_GrSLType, 2};
+    GrGLSLGeoToFrag   fCornerLocationInAABoxes{kFloat2x2_GrSLType};
+    GrGLSLGeoToFrag   fBisectInAABoxes{kFloat2x2_GrSLType};
+};
+
+#endif