Revert "ccpr: Simplify triangle corners"

This reverts commit 622650a1949f9a68793ac895d9fbadee7177d860.

Reason for revert: Going to try to improve AAA quality on curve corners

Original change's description:
> ccpr: Simplify triangle corners
> 
> Modifies triangle corner shaders to just approximate their coverage with
> linear values that ramp to zero at bloat vertices outside the triangle.
> 
> For the vertex backend, since corners now have the same fragment shader
> as the rest of the triangle, we fold them in with the other steps and
> draw triangles in a single pass.
> 
> The geometry backend still draws triangles in two passes, as there is
> not an apparent performance advantage in combining them.
> 
> Bug: skia:
> Change-Id: Ib4a89d793a3c706f734d0271875c8a3e5c87c49b
> Reviewed-on: https://skia-review.googlesource.com/112632
> Commit-Queue: Chris Dalton <csmartdalton@google.com>
> Reviewed-by: Brian Salomon <bsalomon@google.com>

TBR=bsalomon@google.com,csmartdalton@google.com

Change-Id: I45e7b9d7d7f8452b28bd54ca1e90a1f046cb2462
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: skia:
Reviewed-on: https://skia-review.googlesource.com/113180
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.cpp b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
index 3a9eb7b..76ca8f5 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
@@ -7,21 +7,17 @@
 
 #include "GrCCCoverageProcessor.h"
 
-#include "GrGpuCommandBuffer.h"
-#include "GrOpFlushState.h"
 #include "SkMakeUnique.h"
 #include "ccpr/GrCCCubicShader.h"
 #include "ccpr/GrCCQuadraticShader.h"
+#include "ccpr/GrCCTriangleShader.h"
 #include "glsl/GrGLSLVertexGeoBuilder.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLVertexGeoBuilder.h"
 
 void GrCCCoverageProcessor::getGLSLProcessorKey(const GrShaderCaps&,
                                                 GrProcessorKeyBuilder* b) const {
-    int key = ((int)fRenderPass << 3);
-    if (GSTriangleSubpass::kCorners == fGSTriangleSubpass) {
-        key |= 4;
-    }
+    int key = (int)fRenderPass << 2;
     if (WindMethod::kInstanceData == fWindMethod) {
         key |= 2;
     }
@@ -40,7 +36,10 @@
     std::unique_ptr<Shader> shader;
     switch (fRenderPass) {
         case RenderPass::kTriangles:
-            shader = skstd::make_unique<Shader>();
+            shader = skstd::make_unique<GrCCTriangleShader>();
+            break;
+        case RenderPass::kTriangleCorners:
+            shader = skstd::make_unique<GrCCTriangleCornerShader>();
             break;
         case RenderPass::kQuadratics:
             shader = skstd::make_unique<GrCCQuadraticShader>();
@@ -53,45 +52,12 @@
                                           : this->createVSImpl(std::move(shader));
 }
 
-void GrCCCoverageProcessor::draw(GrOpFlushState* flushState, const GrPipeline& pipeline,
-                                 const GrMesh meshes[],
-                                 const GrPipeline::DynamicState dynamicStates[], int meshCount,
-                                 const SkRect& drawBounds) const {
-    GrGpuRTCommandBuffer* cmdBuff = flushState->rtCommandBuffer();
-    cmdBuff->draw(pipeline, *this, meshes, dynamicStates, meshCount, drawBounds);
-
-    // Geometry shader backend draws triangles in two subpasses.
-    if (RenderPass::kTriangles == fRenderPass && Impl::kGeometryShader == fImpl) {
-        SkASSERT(GSTriangleSubpass::kHullsAndEdges == fGSTriangleSubpass);
-        GrCCCoverageProcessor cornerProc(*this, GSTriangleSubpass::kCorners);
-        cmdBuff->draw(pipeline, cornerProc, meshes, dynamicStates, meshCount, drawBounds);
-    }
-}
-
-void GrCCCoverageProcessor::Shader::emitVaryings(GrGLSLVaryingHandler* varyingHandler,
-                                                 GrGLSLVarying::Scope scope, SkString* code,
-                                                 const char* position, const char* coverage,
-                                                 const char* wind) {
-    SkASSERT(GrGLSLVarying::Scope::kVertToGeo != scope);
-    code->appendf("half coverageTimesWind = %s * %s;", coverage, wind);
-    CoverageHandling coverageHandling = this->onEmitVaryings(varyingHandler, scope, code, position,
-                                                             "coverageTimesWind");
-    if (CoverageHandling::kNotHandled == coverageHandling) {
-        fCoverageTimesWind.reset(kHalf_GrSLType, scope);
-        varyingHandler->addVarying("coverage_times_wind", &fCoverageTimesWind);
-        code->appendf("%s = coverageTimesWind;", OutName(fCoverageTimesWind));
-    }
-}
-
 void GrCCCoverageProcessor::Shader::emitFragmentCode(const GrCCCoverageProcessor& proc,
                                                      GrGLSLFPFragmentBuilder* f,
                                                      const char* skOutputColor,
                                                      const char* skOutputCoverage) const {
-    f->codeAppendf("half coverage = +1;");
+    f->codeAppendf("half coverage = 0;");
     this->onEmitFragmentCode(proc, f, "coverage");
-    if (fCoverageTimesWind.fsIn()) {
-        f->codeAppendf("coverage *= %s;", fCoverageTimesWind.fsIn());
-    }
     f->codeAppendf("%s.a = coverage;", skOutputColor);
     f->codeAppendf("%s = half4(1);", skOutputCoverage);
 #ifdef SK_DEBUG
@@ -136,20 +102,3 @@
     // GPU divides by multiplying by the reciprocal?) It also guards against NaN when nwidth=0.
     s->codeAppendf("%s = (abs(t) != nwidth ? t / nwidth : sign(t)) * -.5 - .5;", outputCoverage);
 }
-
-void GrCCCoverageProcessor::Shader::CalcEdgeCoveragesAtBloatVertices(GrGLSLVertexGeoBuilder* s,
-                                                                     const char* leftPt,
-                                                                     const char* rightPt,
-                                                                     const char* bloatDir1,
-                                                                     const char* bloatDir2,
-                                                                     const char* outputCoverages) {
-    // See comments in CalcEdgeCoverageAtBloatVertex.
-    s->codeAppendf("float2 n = float2(%s.y - %s.y, %s.x - %s.x);",
-                   rightPt, leftPt, leftPt, rightPt);
-    s->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
-    s->codeAppendf("float2 t = n * float2x2(%s, %s);", bloatDir1, bloatDir2);
-    s->codeAppendf("for (int i = 0; i < 2; ++i) {");
-    s->codeAppendf(    "%s[i] = (abs(t[i]) != nwidth ? t[i] / nwidth : sign(t[i])) * -.5 - .5;",
-                       outputCoverages);
-    s->codeAppendf("}");
-}
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.h b/src/gpu/ccpr/GrCCCoverageProcessor.h
index a8ad18c..7db424e 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.h
@@ -10,7 +10,6 @@
 
 #include "GrCaps.h"
 #include "GrGeometryProcessor.h"
-#include "GrPipeline.h"
 #include "GrShaderCaps.h"
 #include "SkNx.h"
 #include "glsl/GrGLSLGeometryProcessor.h"
@@ -19,7 +18,6 @@
 class GrGLSLFPFragmentBuilder;
 class GrGLSLVertexGeoBuilder;
 class GrMesh;
-class GrOpFlushState;
 
 /**
  * This is the geometry processor for the simple convex primitive shapes (triangles and closed,
@@ -56,9 +54,11 @@
         void set(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& trans, float w);
     };
     // 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.
+    // mask. Triangles require two render passes: One to draw a rough outline, and a second pass to
+    // touch up the corners. This is an exhaustive list of all ccpr coverage shaders.
     enum class RenderPass {
         kTriangles,
+        kTriangleCorners,
         kQuadratics,
         kCubics,
     };
@@ -83,6 +83,18 @@
         }
     }
 
+    // Appends a GrMesh that will draw the provided instances. The instanceBuffer must be an array
+    // of either TriPointInstance or QuadPointInstance, depending on this processor's RendererPass,
+    // with coordinates in the desired shape's final atlas-space position.
+    void appendMesh(GrBuffer* instanceBuffer, int instanceCount, int baseInstance,
+                    SkTArray<GrMesh>* out) {
+        if (Impl::kGeometryShader == fImpl) {
+            this->appendGSMesh(instanceBuffer, instanceCount, baseInstance, out);
+        } else {
+            this->appendVSMesh(instanceBuffer, instanceCount, baseInstance, out);
+        }
+    }
+
     // GrPrimitiveProcessor overrides.
     const char* name() const override { return RenderPassName(fRenderPass); }
     SkString dumpInfo() const override {
@@ -99,38 +111,38 @@
     float debugBloat() const { SkASSERT(this->debugVisualizationsEnabled()); return fDebugBloat; }
 #endif
 
-    // Appends a GrMesh that will draw the provided instances. The instanceBuffer must be an array
-    // of either TriPointInstance or QuadPointInstance, depending on this processor's RendererPass,
-    // with coordinates in the desired shape's final atlas-space position.
-    void appendMesh(GrBuffer* instanceBuffer, int instanceCount, int baseInstance,
-                    SkTArray<GrMesh>* out) const {
-        if (Impl::kGeometryShader == fImpl) {
-            this->appendGSMesh(instanceBuffer, instanceCount, baseInstance, out);
-        } else {
-            this->appendVSMesh(instanceBuffer, instanceCount, baseInstance, out);
-        }
-    }
-
-    void draw(GrOpFlushState*, const GrPipeline&, const GrMesh[], const GrPipeline::DynamicState[],
-              int meshCount, const SkRect& drawBounds) const;
-
-    // The Shader provides code to calculate a pixel's coverage.
+    // The Shader provides code to calculate each pixel's coverage in a RenderPass. It also
+    // provides details about shape-specific geometry.
     class Shader {
     public:
-        // Called before generating geometry. Subclasses may use this opportunity to setup internal
-        // member variables that will be needed during onEmitVaryings (e.g. transformation
-        // matrices).
-        //
-        // Returns the name of a newly defined list of points around which the Impl should generate
-        // its geometry, or null if it should just use the input points. (Regardless, the size of
-        // whatever list of points indicated should match the size expected by the Impl: 3 points
-        // for triangles, and 4 for quadratics and cubics.)
-        virtual const char* emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts) const {
-            return nullptr;
-        }
+        union GeometryVars {
+            struct {
+                const char* fAlternatePoints; // floatNx2 (if left null, will use input points).
+            } fHullVars;
 
-        void emitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
-                          const char* position, const char* coverage, const char* wind);
+            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).
+        //
+        // repetitionID is a 0-based index and indicates which edge or corner is being generated.
+        // It will be null when generating a hull.
+        virtual void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts,
+                                   const char* repetitionID, const char* wind,
+                                   GeometryVars*) const {}
+
+        void emitVaryings(GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope,
+                          SkString* code, const char* position, const char* inputCoverage,
+                          const char* wind) {
+            SkASSERT(GrGLSLVarying::Scope::kVertToGeo != scope);
+            this->onEmitVaryings(varyingHandler, scope, code, position, inputCoverage, wind);
+        }
 
         void emitFragmentCode(const GrCCCoverageProcessor&, GrGLSLFPFragmentBuilder*,
                               const char* skOutputColor, const char* skOutputCoverage) const;
@@ -145,37 +157,20 @@
                                                   const char* rightPt, const char* rasterVertexDir,
                                                   const char* outputCoverage);
 
-        // Calculates an edge's coverage at two conservative raster vertices.
-        // (See CalcEdgeCoverageAtBloatVertex).
-        static void CalcEdgeCoveragesAtBloatVertices(GrGLSLVertexGeoBuilder*, const char* leftPt,
-                                                     const char* rightPt, const char* bloatDir1,
-                                                     const char* bloatDir2,
-                                                     const char* outputCoverages);
-
         virtual ~Shader() {}
 
     protected:
-        enum class CoverageHandling : bool {
-            kHandled,
-            kNotHandled
-        };
-
         // Here the subclass adds its internal varyings to the handler and produces code to
-        // initialize those varyings from a given position and coverage/wind.
+        // initialize those varyings from a given position, input coverage value, and wind.
         //
-        // Returns whether the subclass will handle coverage modulation or if this base class should
-        // take charge of multiplying the final coverage output by 'coverageTimesWind'.
-        virtual CoverageHandling onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope,
-                                                SkString* code, const char* position,
-                                                const char* coverageTimesWind) {
-            return CoverageHandling::kNotHandled;
-        }
+        // NOTE: the coverage input is only relevant for triangles. Otherwise it is null.
+        virtual void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
+                                    const char* position, const char* inputCoverage,
+                                    const char* wind) = 0;
 
-        // Emits the fragment code that calculates a pixel's coverage value. If using
-        // CoverageHandling::kHandled, this value must be signed and modulated appropriately by
-        // coverage.
+        // Emits the fragment code that calculates a pixel's signed coverage value.
         virtual void onEmitFragmentCode(const GrCCCoverageProcessor&, GrGLSLFPFragmentBuilder*,
-                                        const char* outputCoverage) const {}
+                                        const char* outputCoverage) const = 0;
 
         // Returns the name of a Shader's internal varying at the point where where its value is
         // assigned. This is intended to work whether called for a vertex or a geometry shader.
@@ -184,9 +179,6 @@
             SkASSERT(Scope::kVertToGeo != varying.scope());
             return Scope::kGeoToFrag == varying.scope() ? varying.gsOut() : varying.vsOut();
         }
-
-    private:
-        GrGLSLVarying fCoverageTimesWind;
     };
 
     class GSImpl;
@@ -205,24 +197,6 @@
         kVertexShader
     };
 
-    // Geometry shader backend draws triangles in two subpasses.
-    enum class GSTriangleSubpass : bool {
-        kHullsAndEdges,
-        kCorners
-    };
-
-    GrCCCoverageProcessor(const GrCCCoverageProcessor& proc, GSTriangleSubpass subpass)
-            : INHERITED(kGrCCCoverageProcessor_ClassID)
-            , fRenderPass(RenderPass::kTriangles)
-            , fWindMethod(proc.fWindMethod)
-            , fImpl(Impl::kGeometryShader)
-            SkDEBUGCODE(, fDebugBloat(proc.fDebugBloat))
-            , fGSTriangleSubpass(subpass) {
-        SkASSERT(RenderPass::kTriangles == proc.fRenderPass);
-        SkASSERT(Impl::kGeometryShader == proc.fImpl);
-        this->initGS();
-    }
-
     void initGS();
     void initVS(GrResourceProvider*);
 
@@ -239,9 +213,6 @@
     const Impl fImpl;
     SkDEBUGCODE(float fDebugBloat = 0);
 
-    // Used by GSImpl.
-    const GSTriangleSubpass fGSTriangleSubpass = GSTriangleSubpass::kHullsAndEdges;
-
     // Used by VSImpl.
     sk_sp<const GrBuffer> fVertexBuffer;
     sk_sp<const GrBuffer> fIndexBuffer;
@@ -283,6 +254,7 @@
 inline const char* GrCCCoverageProcessor::RenderPassName(RenderPass pass) {
     switch (pass) {
         case RenderPass::kTriangles: return "kTriangles";
+        case RenderPass::kTriangleCorners: return "kTriangleCorners";
         case RenderPass::kQuadratics: return "kQuadratics";
         case RenderPass::kCubics: return "kCubics";
     }
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp b/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
index 1fef1c3..e64b8c0 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
@@ -75,7 +75,10 @@
         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();
+        const char* coverage = nullptr;
+        if (RenderPass::kTriangleCorners != proc.fRenderPass) {
+            coverage = emitArgs.emplace_back("coverage", kHalf_GrSLType).c_str();
+        }
         g->emitFunction(kVoid_GrSLType, "emitVertex", emitArgs.count(), emitArgs.begin(), [&]() {
             SkString fnBody;
             fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kGeoToFrag, &fnBody,
@@ -113,7 +116,7 @@
  * coverage ramp from -1 to 0. These edge coverage values convert jagged conservative raster edges
  * into smooth, antialiased ones.
  *
- * The final corners get touched up in a later step by GSTriangleCornerImpl.
+ * The final corners get touched up in a later step by GSCornerImpl.
  */
 class GSTriangleImpl : public GrCCCoverageProcessor::GSImpl {
 public:
@@ -121,8 +124,6 @@
 
     void onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
                               const char* emitVertexFn) const override {
-        SkAssertResult(!fShader->emitSetupCode(g, "pts"));
-
         // Visualize the input triangle as upright and equilateral, with a flat base. Paying special
         // attention to wind, we can identify the points as top, bottom-left, and bottom-right.
         //
@@ -211,69 +212,19 @@
 };
 
 /**
- * Generates conservative rasters around triangle corners (aka pixel-size boxes) and calculates
- * coverage ramps that fix up the coverage values written by GSTriangleImpl.
- */
-class GSTriangleCornerImpl : public GrCCCoverageProcessor::GSImpl {
-public:
-    GSTriangleCornerImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
-
-    void onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
-                              const char* emitVertexFn) const override {
-        SkAssertResult(!fShader->emitSetupCode(g, "pts"));
-
-        g->codeAppendf("float2 corner = pts[sk_InvocationID];");
-        g->codeAppendf("float2 left = pts[(sk_InvocationID + (%s > 0 ? 2 : 1)) %% 3];",
-                       wind.c_str());
-        g->codeAppendf("float2 right = pts[(sk_InvocationID + (%s > 0 ? 1 : 2)) %% 3];",
-                       wind.c_str());
-
-        // Find "outbloat" and "crossbloat" at our corner. The outbloat points diagonally out of the
-        // triangle, in the direction that should ramp to zero coverage. The crossbloat runs
-        // perpindicular to outbloat, and ramps from left-edge coverage to right-edge coverage.
-        g->codeAppend ("float2 leftdir = normalize(corner - left);");
-        g->codeAppend ("float2 rightdir = normalize(right - corner);");
-        g->codeAppend ("float2 outbloat = float2(leftdir.x > rightdir.x ? +1 : -1, "
-                                                "leftdir.y > rightdir.y ? +1 : -1);");
-        g->codeAppend ("float2 crossbloat = float2(-outbloat.y, +outbloat.x);");
-
-        g->codeAppend ("half2 left_coverages; {");
-        Shader::CalcEdgeCoveragesAtBloatVertices(g, "left", "corner", "outbloat", "crossbloat",
-                                                 "left_coverages");
-        g->codeAppend ("}");
-
-        g->codeAppend ("half2 right_coverages; {");
-        Shader::CalcEdgeCoveragesAtBloatVertices(g, "corner", "right", "outbloat", "-crossbloat",
-                                                 "right_coverages");
-        g->codeAppend ("}");
-
-        // Emit a corner box that erases whatever coverage was written previously, and replaces it
-        // using linearly-interpolated values that ramp to zero in bloat vertices that fall outside
-        // the triangle.
-        //
-        // NOTE: Since this is not a linear mapping, it is important that the box's diagonal shared
-        // edge points out of the triangle as much as possible.
-        g->codeAppendf("%s(corner - crossbloat * bloat, -right_coverages[1]);", emitVertexFn);
-        g->codeAppendf("%s(corner + outbloat * bloat, "
-                          "-1 - left_coverages[0] - right_coverages[0]);", emitVertexFn);
-        g->codeAppendf("%s(corner - outbloat * bloat, 0);", emitVertexFn);
-        g->codeAppendf("%s(corner + crossbloat * bloat, -left_coverages[1]);", emitVertexFn);
-
-        g->configure(InputType::kLines, OutputType::kTriangleStrip, 4, 3);
-    }
-};
-
-/**
  * Generates a conservative raster hull around a convex quadrilateral that encloses a cubic or
  * quadratic, as well as its shared edge.
  */
-class GSCurveImpl : public GrCCCoverageProcessor::GSImpl {
+class GSHull4Impl : public GrCCCoverageProcessor::GSImpl {
 public:
-    GSCurveImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
+    GSHull4Impl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
 
     void onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
                              const char* emitVertexFn) const override {
-        const char* hullPts = fShader->emitSetupCode(g, "pts");
+        Shader::GeometryVars vars;
+        fShader->emitSetupCode(g, "pts", nullptr, wind.c_str(), &vars);
+
+        const char* hullPts = vars.fHullVars.fAlternatePoints;
         if (!hullPts) {
             hullPts = "pts";
         }
@@ -363,6 +314,34 @@
     }
 };
 
+/**
+ * Generates conservative rasters around corners. (See comments for RenderPass)
+ */
+class GSCornerImpl : public GrCCCoverageProcessor::GSImpl {
+public:
+    GSCornerImpl(std::unique_ptr<Shader> shader, int numCorners)
+            : GSImpl(std::move(shader)), fNumCorners(numCorners) {}
+
+    void onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
+                              const char* emitVertexFn) const override {
+        Shader::GeometryVars vars;
+        fShader->emitSetupCode(g, "pts", "sk_InvocationID", wind.c_str(), &vars);
+
+        const char* corner = vars.fCornerVars.fPoint;
+        SkASSERT(corner);
+
+        g->codeAppendf("%s(%s + float2(-bloat, -bloat));", emitVertexFn, corner);
+        g->codeAppendf("%s(%s + float2(-bloat, +bloat));", emitVertexFn, corner);
+        g->codeAppendf("%s(%s + float2(+bloat, -bloat));", emitVertexFn, corner);
+        g->codeAppendf("%s(%s + float2(+bloat, +bloat));", emitVertexFn, corner);
+
+        g->configure(InputType::kLines, OutputType::kTriangleStrip, 4, fNumCorners);
+    }
+
+private:
+    const int fNumCorners;
+};
+
 void GrCCCoverageProcessor::initGS() {
     SkASSERT(Impl::kGeometryShader == fImpl);
     if (RenderPass::kCubics == fRenderPass || WindMethod::kInstanceData == fWindMethod) {
@@ -393,12 +372,12 @@
 GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createGSImpl(std::unique_ptr<Shader> shadr) const {
     switch (fRenderPass) {
         case RenderPass::kTriangles:
-            return (GSTriangleSubpass::kHullsAndEdges == fGSTriangleSubpass)
-                    ? (GSImpl*) new GSTriangleImpl(std::move(shadr))
-                    : (GSImpl*) new GSTriangleCornerImpl(std::move(shadr));
+            return new GSTriangleImpl(std::move(shadr));
+        case RenderPass::kTriangleCorners:
+            return new GSCornerImpl(std::move(shadr), 3);
         case RenderPass::kQuadratics:
         case RenderPass::kCubics:
-            return new GSCurveImpl(std::move(shadr));
+            return new GSHull4Impl(std::move(shadr));
     }
     SK_ABORT("Invalid RenderPass");
     return nullptr;
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp b/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
index c64ee75..08398e1 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
@@ -10,15 +10,89 @@
 #include "GrMesh.h"
 #include "glsl/GrGLSLVertexGeoBuilder.h"
 
+using Shader = GrCCCoverageProcessor::Shader;
+
 static constexpr int kAttribIdx_X = 0;
 static constexpr int kAttribIdx_Y = 1;
 static constexpr int kAttribIdx_VertexData = 2;
 
-static constexpr int kVertexData_LeftNeighborIdShift = 10;
-static constexpr int kVertexData_RightNeighborIdShift = 8;
-static constexpr int kVertexData_BloatIdxShift = 6;
-static constexpr int kVertexData_InvertNegativeCoverageBit = 1 << 5;
-static constexpr int kVertexData_IsCornerBit = 1 << 4;
+/**
+ * This class and its subclasses implement the coverage processor with vertex shaders.
+ */
+class GrCCCoverageProcessor::VSImpl : public GrGLSLGeometryProcessor {
+protected:
+    VSImpl(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 GrCCCoverageProcessor& proc = args.fGP.cast<GrCCCoverageProcessor>();
+
+        // Vertex shader.
+        GrGLSLVertexBuilder* v = args.fVertBuilder;
+        int numInputPoints = proc.numInputPoints();
+
+        const char* swizzle = (4 == numInputPoints) ? "xyzw" : "xyz";
+        v->codeAppendf("float%ix2 pts = transpose(float2x%i(%s.%s, %s.%s));",
+                       numInputPoints, numInputPoints, proc.getAttrib(kAttribIdx_X).fName, swizzle,
+                       proc.getAttrib(kAttribIdx_Y).fName, swizzle);
+
+        if (WindMethod::kCrossProduct == proc.fWindMethod) {
+            v->codeAppend ("float area_x2 = determinant(float2x2(pts[0] - pts[1], "
+                                                                "pts[0] - pts[2]));");
+            if (4 == numInputPoints) {
+                v->codeAppend ("area_x2 += determinant(float2x2(pts[0] - pts[2], "
+                                                               "pts[0] - pts[3]));");
+            }
+            v->codeAppend ("half wind = sign(area_x2);");
+        } else {
+            SkASSERT(WindMethod::kInstanceData == proc.fWindMethod);
+            SkASSERT(3 == numInputPoints);
+            SkASSERT(kFloat4_GrVertexAttribType == proc.getAttrib(kAttribIdx_X).fType);
+            v->codeAppendf("half wind = %s.w;", proc.getAttrib(kAttribIdx_X).fName);
+        }
+
+        float bloat = kAABloatRadius;
+#ifdef SK_DEBUG
+        if (proc.debugVisualizationsEnabled()) {
+            bloat *= proc.debugBloat();
+        }
+#endif
+        v->defineConstant("bloat", bloat);
+
+        const char* coverage = this->emitVertexPosition(proc, v, gpArgs);
+        SkASSERT(kFloat2_GrSLType == gpArgs->fPositionVar.getType());
+
+        GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
+        SkString varyingCode;
+        fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag, &varyingCode,
+                              gpArgs->fPositionVar.c_str(), coverage, "wind");
+        v->codeAppend(varyingCode.c_str());
+
+        varyingHandler->emitAttributes(proc);
+        SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform());
+
+        // Fragment shader.
+        fShader->emitFragmentCode(proc, args.fFragBuilder, args.fOutputColor, args.fOutputCoverage);
+    }
+
+    virtual const char* emitVertexPosition(const GrCCCoverageProcessor&, GrGLSLVertexBuilder*,
+                                           GrGPArgs*) const = 0;
+
+    virtual ~VSImpl() {}
+
+    const std::unique_ptr<Shader> fShader;
+
+    typedef GrGLSLGeometryProcessor INHERITED;
+};
+
+static constexpr int kVertexData_LeftNeighborIdShift = 9;
+static constexpr int kVertexData_RightNeighborIdShift = 7;
+static constexpr int kVertexData_BloatIdxShift = 5;
+static constexpr int kVertexData_InvertNegativeCoverageBit = 1 << 4;
 static constexpr int kVertexData_IsEdgeBit = 1 << 3;
 static constexpr int kVertexData_IsHullBit = 1 << 2;
 
@@ -45,12 +119,8 @@
     return pack_vertex_data(leftID, leftID, bloatIdx, rightID, kVertexData_IsEdgeBit | extraData);
 }
 
-static constexpr int32_t triangle_corner_vertex_data(int32_t cornerID, int32_t bloatIdx) {
-    return pack_vertex_data((cornerID + 2) % 3, (cornerID + 1) % 3, bloatIdx, cornerID,
-                            kVertexData_IsCornerBit);
-}
 
-static constexpr int32_t kTriangleVertices[] = {
+static constexpr int32_t kHull3AndEdgeVertices[] = {
     hull_vertex_data(0, 0, 3),
     hull_vertex_data(0, 1, 3),
     hull_vertex_data(0, 2, 3),
@@ -81,39 +151,21 @@
     edge_vertex_data(0, 2, 0, kVertexData_InvertNegativeCoverageBit),
     edge_vertex_data(0, 2, 1, kVertexData_InvertNegativeCoverageBit),
     edge_vertex_data(0, 2, 2, kVertexData_InvertNegativeCoverageBit),
-
-    triangle_corner_vertex_data(0, 0),
-    triangle_corner_vertex_data(0, 1),
-    triangle_corner_vertex_data(0, 2),
-    triangle_corner_vertex_data(0, 3),
-
-    triangle_corner_vertex_data(1, 0),
-    triangle_corner_vertex_data(1, 1),
-    triangle_corner_vertex_data(1, 2),
-    triangle_corner_vertex_data(1, 3),
-
-    triangle_corner_vertex_data(2, 0),
-    triangle_corner_vertex_data(2, 1),
-    triangle_corner_vertex_data(2, 2),
-    triangle_corner_vertex_data(2, 3),
 };
 
-GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey);
+GR_DECLARE_STATIC_UNIQUE_KEY(gHull3AndEdgeVertexBufferKey);
 
 static constexpr uint16_t kRestartStrip = 0xffff;
 
-static constexpr uint16_t kTriangleIndicesAsStrips[] =  {
+static constexpr uint16_t kHull3AndEdgeIndicesAsStrips[] =  {
     1, 2, 0, 3, 8, kRestartStrip, // First corner and main body of the hull.
     4, 5, 3, 6, 8, 7, kRestartStrip, // Opposite side and corners of the hull.
     10, 9, 11, 14, 12, 13, kRestartStrip, // First edge.
     16, 15, 17, 20, 18, 19, kRestartStrip, // Second edge.
-    22, 21, 23, 26, 24, 25, kRestartStrip, // Third edge.
-    27, 28, 30, 29, kRestartStrip, // First corner.
-    31, 32, 34, 33, kRestartStrip, // Second corner.
-    35, 36, 38, 37 // Third corner.
+    22, 21, 23, 26, 24, 25 // Third edge.
 };
 
-static constexpr uint16_t kTriangleIndicesAsTris[] =  {
+static constexpr uint16_t kHull3AndEdgeIndicesAsTris[] =  {
     // First corner and main body of the hull.
     1, 2, 0,
     2, 3, 0,
@@ -142,21 +194,9 @@
     21, 26, 23,
     23, 26, 24,
     26, 25, 24,
-
-    // First corner.
-    27, 28, 30,
-    28, 29, 30,
-
-    // Second corner.
-    31, 32, 34,
-    32, 33, 34,
-
-    // Third corner.
-    35, 36, 38,
-    36, 37, 38,
 };
 
-GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey);
+GR_DECLARE_STATIC_UNIQUE_KEY(gHull3AndEdgeIndexBufferKey);
 
 static constexpr int32_t kHull4AndEdgeVertices[] = {
     hull_vertex_data(0, 0, 4),
@@ -212,229 +252,162 @@
 
 GR_DECLARE_STATIC_UNIQUE_KEY(gHull4AndEdgeIndexBufferKey);
 
-
-/**
- * This class and its subclasses implement the coverage processor with vertex shaders.
- */
-class GrCCCoverageProcessor::VSImpl : public GrGLSLGeometryProcessor {
-public:
-    VSImpl(std::unique_ptr<Shader> shader) : fShader(std::move(shader)) {}
-
-private:
-    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) override {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
-    }
-
-    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
-
-    const char* emitVertexPosition(const GrCCCoverageProcessor&, GrGLSLVertexBuilder*,
-                                   GrGPArgs*) const;
-
-    const std::unique_ptr<Shader> fShader;
-};
-
-void GrCCCoverageProcessor::VSImpl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
-    const GrCCCoverageProcessor& proc = args.fGP.cast<GrCCCoverageProcessor>();
-
-    // Vertex shader.
-    GrGLSLVertexBuilder* v = args.fVertBuilder;
-    int numInputPoints = proc.numInputPoints();
-
-    const char* swizzle = (4 == numInputPoints) ? "xyzw" : "xyz";
-    v->codeAppendf("float%ix2 pts = transpose(float2x%i(%s.%s, %s.%s));",
-                   numInputPoints, numInputPoints, proc.getAttrib(kAttribIdx_X).fName, swizzle,
-                   proc.getAttrib(kAttribIdx_Y).fName, swizzle);
-
-    if (WindMethod::kCrossProduct == proc.fWindMethod) {
-        v->codeAppend ("float area_x2 = determinant(float2x2(pts[0] - pts[1], "
-                                                            "pts[0] - pts[2]));");
-        if (4 == numInputPoints) {
-            v->codeAppend ("area_x2 += determinant(float2x2(pts[0] - pts[2], "
-                                                           "pts[0] - pts[3]));");
-        }
-        v->codeAppend ("half wind = sign(area_x2);");
-    } else {
-        SkASSERT(WindMethod::kInstanceData == proc.fWindMethod);
-        SkASSERT(3 == numInputPoints);
-        SkASSERT(kFloat4_GrVertexAttribType == proc.getAttrib(kAttribIdx_X).fType);
-        v->codeAppendf("half wind = %s.w;", proc.getAttrib(kAttribIdx_X).fName);
-    }
-
-    float bloat = kAABloatRadius;
-#ifdef SK_DEBUG
-    if (proc.debugVisualizationsEnabled()) {
-        bloat *= proc.debugBloat();
-    }
-#endif
-    v->defineConstant("bloat", bloat);
-
-    const char* coverage = this->emitVertexPosition(proc, v, gpArgs);
-    SkASSERT(kFloat2_GrSLType == gpArgs->fPositionVar.getType());
-
-    GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
-    SkString varyingCode;
-    fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag, &varyingCode,
-                          gpArgs->fPositionVar.c_str(), coverage, "wind");
-    v->codeAppend(varyingCode.c_str());
-
-    varyingHandler->emitAttributes(proc);
-    SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform());
-
-    // Fragment shader.
-    fShader->emitFragmentCode(proc, args.fFragBuilder, args.fOutputColor, args.fOutputCoverage);
-}
-
 /**
  * Generates a conservative raster hull around a triangle or curve. For triangles we generate
- * additional conservative rasters with coverage ramps around the edges. For curves we generate an
- * additional raster with coverage ramps around its shared edge.
+ * additional conservative rasters with coverage ramps around the edges. For curves we
+ * generate an additional raster with coverage ramps around its shared edge.
  *
- * Triangles are drawn in three steps: (1) Draw a conservative raster of the entire triangle, with a
- * coverage of +1. (2) Draw conservative rasters around each edge, with a coverage ramp from -1 to
- * 0. These edge coverage values convert jagged conservative raster edges into smooth, antialiased
- * ones. (3) Draw conservative rasters (aka pixel-size boxes) around each corner, replacing the
- * previous coverage values with ones that ramp to zero in the bloat vertices that fall outside the
- * triangle.
+ * Triangle rough outlines are drawn in two steps: (1) Draw a conservative raster of the entire
+ * triangle, with a coverage of +1. (2) Draw conservative rasters around each edge, with a
+ * coverage ramp from -1 to 0. These edge coverage values convert jagged conservative raster edges
+ * into smooth, antialiased ones. The final corners get touched up in a later step by VSCornerImpl.
  *
  * Curves are drawn in two steps: (1) Draw a conservative raster around the input points, passing
  * coverage=+1 to the Shader. (2) Draw an additional conservative raster around the curve's shared
  * edge, using coverage=-1 at bloat vertices that fall outside the input points. This erases what
  * the hull just wrote and ramps coverage to zero.
  */
-const char* GrCCCoverageProcessor::VSImpl::emitVertexPosition(const GrCCCoverageProcessor& proc,
-                                                              GrGLSLVertexBuilder* v,
-                                                              GrGPArgs* gpArgs) const {
-    int numSides = (RenderPass::kTriangles == proc.fRenderPass) ? 3 : 4;
-    const char* hullPts = fShader->emitSetupCode(v, "pts");
-    if (!hullPts) {
-        SkASSERT(numSides == proc.numInputPoints());
-        hullPts = "pts";
-    }
+class VSHullAndEdgeImpl : public GrCCCoverageProcessor::VSImpl {
+public:
+    VSHullAndEdgeImpl(std::unique_ptr<Shader> shader, int numSides)
+            : VSImpl(std::move(shader)), fNumSides(numSides) {}
 
-    // Reverse all indices if the wind is counter-clockwise: [0, 1, 2] -> [2, 1, 0].
-    v->codeAppendf("int clockwise_indices = wind > 0 ? %s : 0x%x - %s;",
-                   proc.getAttrib(kAttribIdx_VertexData).fName,
-                   ((numSides - 1) << kVertexData_LeftNeighborIdShift) |
-                   ((numSides - 1) << kVertexData_RightNeighborIdShift) |
-                   (((1 << kVertexData_RightNeighborIdShift) - 1) ^ 3) |
-                   (numSides - 1),
-                   proc.getAttrib(kAttribIdx_VertexData).fName);
+    const char* emitVertexPosition(const GrCCCoverageProcessor& proc, GrGLSLVertexBuilder* v,
+                                   GrGPArgs* gpArgs) const override {
+        Shader::GeometryVars vars;
+        fShader->emitSetupCode(v, "pts", nullptr, "wind", &vars);
 
-    // Here we generate conservative raster geometry for the input polygon. It is the convex hull of
-    // N pixel-size boxes, one centered on each the input points. Each corner has three vertices,
-    // where one or two may cause degenerate triangles. The vertex data tells us how to offset each
-    // vertex. Edges are also handled here using the same concept. For more details on conservative
-    // raster, see: https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
-    v->codeAppendf("float2 corner = %s[clockwise_indices & 3];", hullPts);
-    v->codeAppendf("float2 left = %s[clockwise_indices >> %i];",
-                   hullPts, kVertexData_LeftNeighborIdShift);
-    v->codeAppendf("float2 right = %s[(clockwise_indices >> %i) & 3];",
-                   hullPts, kVertexData_RightNeighborIdShift);
+        const char* hullPts = vars.fHullVars.fAlternatePoints;
+        if (!hullPts) {
+            hullPts = "pts";
+        }
 
-    v->codeAppend ("float2 leftbloat = sign(corner - left);");
-    v->codeAppend ("leftbloat = float2(0 != leftbloat.y ? leftbloat.y : leftbloat.x, "
-                                      "0 != leftbloat.x ? -leftbloat.x : -leftbloat.y);");
-
-    v->codeAppend ("float2 rightbloat = sign(right - corner);");
-    v->codeAppend ("rightbloat = float2(0 != rightbloat.y ? rightbloat.y : rightbloat.x, "
-                                       "0 != rightbloat.x ? -rightbloat.x : -rightbloat.y);");
-
-    v->codeAppend ("bool2 left_right_notequal = notEqual(leftbloat, rightbloat);");
-
-    v->codeAppend ("float2 bloatdir = leftbloat;");
-
-    if (RenderPass::kTriangles == proc.fRenderPass) { // Only triangles emit corner boxes.
-        v->codeAppendf("if (0 != (%s & %i)) {", // Are we a corner?
-                       proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsCornerBit);
-
-                           // For corner boxes, we hack 'left_right_notequal' to [true, true].  This
-                           // causes the upcoming code to always rotate, which is the right thing
-                           // for corners.
-        v->codeAppendf(    "left_right_notequal = bool2(true, true);");
-
-                           // In corner boxes, all 4 coverage values will not map linearly, so it is
-                           // important to rotate the box so its diagonal shared edge points out of
-                           // the triangle, in the direction that ramps to zero.
-        v->codeAppend (    "float2 bisect = normalize(corner - right) + normalize(corner - left);");
-        v->codeAppend (    "if (sign(bisect) == sign(leftbloat)) {");
-        v->codeAppend (        "bloatdir = float2(+bloatdir.y, -bloatdir.x);");
-        v->codeAppend (    "}");
-        v->codeAppend ("}");
-    }
-
-    // At each corner of the polygon, our hull will have either 1, 2, or 3 vertices (or 4 if it's a
-    // corner box). We begin with the first hull vertex (leftbloat), then continue rotating 90
-    // degrees clockwise until we reach the desired vertex for this invocation.  Corners with less
-    // than 3 corresponding hull vertices will result in redundant vertices and degenerate
-    // triangles.
-    v->codeAppendf("int bloatidx = (%s >> %i) & 3;",
-                   proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_BloatIdxShift);
-    v->codeAppend ("switch (bloatidx) {");
-    if (RenderPass::kTriangles == proc.fRenderPass) { // Only triangles emit corner boxes.
-        v->codeAppend (    "case 3:");
-                                // Only corners will have bloatidx=3, and corners always rotate.
-        v->codeAppend (        "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW.
-                               // fallthru.
-    }
-    v->codeAppend (    "case 2:");
-    v->codeAppendf(        "if (all(left_right_notequal)) {");
-    v->codeAppend (            "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW.
-    v->codeAppend (        "}");
-                           // fallthru.
-    v->codeAppend (    "case 1:");
-    v->codeAppendf(        "if (any(left_right_notequal)) {");
-    v->codeAppend (            "bloatdir = float2(-bloatdir.y, +bloatdir.x);"); // 90 deg CW.
-    v->codeAppend (        "}");
-                           // fallthru.
-    v->codeAppend ("}");
-
-    v->codeAppend ("float2 vertex = corner + bloatdir * bloat;");
-    gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
-
-    // The hull has a coverage of +1 all around.
-    v->codeAppend ("half coverage = +1;");
-
-    if (RenderPass::kTriangles == proc.fRenderPass) {
-        v->codeAppendf("if (0 != (%s & %i)) {", // Are we an edge OR corner?
+        // Reverse all indices if the wind is counter-clockwise: [0, 1, 2] -> [2, 1, 0].
+        v->codeAppendf("int clockwise_indices = wind > 0 ? %s : 0x%x - %s;",
                        proc.getAttrib(kAttribIdx_VertexData).fName,
-                       kVertexData_IsEdgeBit | kVertexData_IsCornerBit);
-        Shader::CalcEdgeCoverageAtBloatVertex(v, "left", "corner", "bloatdir", "coverage");
+                       ((fNumSides - 1) << kVertexData_LeftNeighborIdShift) |
+                       ((fNumSides - 1) << kVertexData_RightNeighborIdShift) |
+                       (((1 << kVertexData_RightNeighborIdShift) - 1) ^ 3) |
+                       (fNumSides - 1),
+                       proc.getAttrib(kAttribIdx_VertexData).fName);
+
+        // Here we generate conservative raster geometry for the input polygon. It is the convex
+        // hull of N pixel-size boxes, one centered on each the input points. Each corner has three
+        // vertices, where one or two may cause degenerate triangles. The vertex data tells us how
+        // to offset each vertex. Edges are also handled here using the same concept. For more
+        // details on conservative raster, see:
+        // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
+        v->codeAppendf("float2 corner = %s[clockwise_indices & 3];", hullPts);
+        v->codeAppendf("float2 left = %s[clockwise_indices >> %i];",
+                       hullPts, kVertexData_LeftNeighborIdShift);
+        v->codeAppendf("float2 right = %s[(clockwise_indices >> %i) & 3];",
+                       hullPts, kVertexData_RightNeighborIdShift);
+
+        v->codeAppend ("float2 leftbloat = sign(corner - left);");
+        v->codeAppend ("leftbloat = float2(0 != leftbloat.y ? leftbloat.y : leftbloat.x, "
+                                          "0 != leftbloat.x ? -leftbloat.x : -leftbloat.y);");
+
+        v->codeAppend ("float2 rightbloat = sign(right - corner);");
+        v->codeAppend ("rightbloat = float2(0 != rightbloat.y ? rightbloat.y : rightbloat.x, "
+                                           "0 != rightbloat.x ? -rightbloat.x : -rightbloat.y);");
+
+        v->codeAppend ("bool2 left_right_notequal = notEqual(leftbloat, rightbloat);");
+
+        // At each corner of the polygon, our hull will have either 1, 2, or 3 vertices. We begin
+        // with the first hull vertex (leftbloat), then continue rotating 90 degrees clockwise until
+        // we reach the desired vertex for this invocation. Corners with less than 3 corresponding
+        // hull vertices will result in redundant vertices and degenerate triangles.
+        v->codeAppend ("float2 bloatdir = leftbloat;");
+        v->codeAppendf("int bloatidx = (%s >> %i) & 3;",
+                       proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_BloatIdxShift);
+        v->codeAppend ("switch (bloatidx) {");
+        v->codeAppend (    "case 2:");
+        v->codeAppendf(        "if (all(left_right_notequal)) {");
+        v->codeAppend (            "bloatdir = float2(-bloatdir.y, +bloatdir.x);");
+        v->codeAppend (        "}");
+                               // fallthru.
+        v->codeAppend (    "case 1:");
+        v->codeAppendf(        "if (any(left_right_notequal)) {");
+        v->codeAppend (            "bloatdir = float2(-bloatdir.y, +bloatdir.x);");
+        v->codeAppend (        "}");
+                               // fallthru.
         v->codeAppend ("}");
 
-        v->codeAppendf("if (0 != (%s & %i)) {", // Are we a corner?
-                       proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsCornerBit);
-                           // Corner boxes erase whatever coverage was written previously, and
-                           // replace it with linearly-interpolated values that ramp to zero in the
-                           // diagonal that points out of the triangle, and ramp from left-edge
-                           // coverage to right-edge coverage in the other diagonal.
-        v->codeAppend (    "half left_coverage = coverage;");
-        v->codeAppend (    "half right_coverage;");
-        Shader::CalcEdgeCoverageAtBloatVertex(v, "corner", "right", "bloatdir",
-                                              "right_coverage");
-        v->codeAppend (    "coverage = (1 == bloatidx) ? -1 : 0;");
-        v->codeAppend (    "if (((bloatidx + 3) & 3) < 2) {");
-        v->codeAppend (        "coverage -= left_coverage;");
-        v->codeAppend (    "}");
-        v->codeAppend (    "if (bloatidx < 2) {");
-        v->codeAppend (        "coverage -= right_coverage;");
-        v->codeAppend (    "}");
+        v->codeAppend ("float2 vertex = corner + bloatdir * bloat;");
+        gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
+
+        // The hull has a coverage of +1 all around.
+        v->codeAppend ("half coverage = +1;");
+
+        if (3 == fNumSides) {
+            v->codeAppendf("if (0 != (%s & %i)) {", // Are we an edge?
+                           proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsEdgeBit);
+            Shader::CalcEdgeCoverageAtBloatVertex(v, "left", "corner", "bloatdir", "coverage");
+            v->codeAppend ("}");
+        } else {
+            SkASSERT(4 == fNumSides);
+            v->codeAppendf("if (0 != (%s & %i)) {", // Are we an edge?
+                           proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsEdgeBit);
+            v->codeAppend (    "coverage = -1;");
+            v->codeAppend ("}");
+        }
+
+        v->codeAppendf("if (0 != (%s & %i)) {", // Invert coverage?
+                       proc.getAttrib(kAttribIdx_VertexData).fName,
+                       kVertexData_InvertNegativeCoverageBit);
+        v->codeAppend (    "coverage = -1 - coverage;");
         v->codeAppend ("}");
-    } else {
-        v->codeAppendf("if (0 != (%s & %i)) {", // Are we an edge?
-                       proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsEdgeBit);
-        v->codeAppend (    "coverage = -1;");
-        v->codeAppend ("}");
+
+        return "coverage";
     }
 
-    v->codeAppendf("if (0 != (%s & %i)) {", // Invert coverage?
-                   proc.getAttrib(kAttribIdx_VertexData).fName,
-                   kVertexData_InvertNegativeCoverageBit);
-    v->codeAppend (    "coverage = -1 - coverage;");
-    v->codeAppend ("}");
+private:
+    const int fNumSides;
+};
 
-    return "coverage";
-}
+static constexpr uint16_t kCornerIndicesAsStrips[] =  {
+    0, 1, 2, 3, kRestartStrip, // First corner.
+    4, 5, 6, 7, kRestartStrip, // Second corner.
+    8, 9, 10, 11 // Third corner.
+};
+
+static constexpr uint16_t kCornerIndicesAsTris[] =  {
+    // First corner.
+    0,  1,  2,
+    1,  3,  2,
+
+    // Second corner.
+    4,  5,  6,
+    5,  7,  6,
+
+    // Third corner.
+    8,  9, 10,
+    9, 11, 10,
+};
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gCornerIndexBufferKey);
+
+/**
+ * Generates conservative rasters around corners. (See comments for RenderPass)
+ */
+class VSCornerImpl : public GrCCCoverageProcessor::VSImpl {
+public:
+    VSCornerImpl(std::unique_ptr<Shader> shader) : VSImpl(std::move(shader)) {}
+
+    const char* emitVertexPosition(const GrCCCoverageProcessor&, GrGLSLVertexBuilder* v,
+                                   GrGPArgs* gpArgs) const override {
+        Shader::GeometryVars vars;
+        v->codeAppend ("int corner_id = sk_VertexID / 4;");
+        fShader->emitSetupCode(v, "pts", "corner_id", "wind", &vars);
+
+        v->codeAppendf("float2 vertex = %s;", vars.fCornerVars.fPoint);
+        v->codeAppend ("vertex.x += (0 == (sk_VertexID & 2)) ? -bloat : +bloat;");
+        v->codeAppend ("vertex.y += (0 == (sk_VertexID & 1)) ? -bloat : +bloat;");
+
+        gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
+        return nullptr; // Corner vertices don't have an initial coverage value.
+    }
+};
 
 void GrCCCoverageProcessor::initVS(GrResourceProvider* rp) {
     SkASSERT(Impl::kVertexShader == fImpl);
@@ -442,24 +415,42 @@
 
     switch (fRenderPass) {
         case RenderPass::kTriangles: {
-            GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey);
+            GR_DEFINE_STATIC_UNIQUE_KEY(gHull3AndEdgeVertexBufferKey);
             fVertexBuffer = rp->findOrMakeStaticBuffer(kVertex_GrBufferType,
-                                                       sizeof(kTriangleVertices),
-                                                       kTriangleVertices,
-                                                       gTriangleVertexBufferKey);
-            GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey);
+                                                       sizeof(kHull3AndEdgeVertices),
+                                                       kHull3AndEdgeVertices,
+                                                       gHull3AndEdgeVertexBufferKey);
+            GR_DEFINE_STATIC_UNIQUE_KEY(gHull3AndEdgeIndexBufferKey);
             if (caps.usePrimitiveRestart()) {
                 fIndexBuffer = rp->findOrMakeStaticBuffer(kIndex_GrBufferType,
-                                                          sizeof(kTriangleIndicesAsStrips),
-                                                          kTriangleIndicesAsStrips,
-                                                          gTriangleIndexBufferKey);
-                fNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsStrips);
+                                                          sizeof(kHull3AndEdgeIndicesAsStrips),
+                                                          kHull3AndEdgeIndicesAsStrips,
+                                                          gHull3AndEdgeIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kHull3AndEdgeIndicesAsStrips);
             } else {
                 fIndexBuffer = rp->findOrMakeStaticBuffer(kIndex_GrBufferType,
-                                                          sizeof(kTriangleIndicesAsTris),
-                                                          kTriangleIndicesAsTris,
-                                                          gTriangleIndexBufferKey);
-                fNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsTris);
+                                                          sizeof(kHull3AndEdgeIndicesAsTris),
+                                                          kHull3AndEdgeIndicesAsTris,
+                                                          gHull3AndEdgeIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kHull3AndEdgeIndicesAsTris);
+            }
+            break;
+        }
+
+        case RenderPass::kTriangleCorners: {
+            GR_DEFINE_STATIC_UNIQUE_KEY(gCornerIndexBufferKey);
+            if (caps.usePrimitiveRestart()) {
+                fIndexBuffer = rp->findOrMakeStaticBuffer(kIndex_GrBufferType,
+                                                          sizeof(kCornerIndicesAsStrips),
+                                                          kCornerIndicesAsStrips,
+                                                          gCornerIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kCornerIndicesAsStrips);
+            } else {
+                fIndexBuffer = rp->findOrMakeStaticBuffer(kIndex_GrBufferType,
+                                                          sizeof(kCornerIndicesAsTris),
+                                                          kCornerIndicesAsTris,
+                                                          gCornerIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kCornerIndicesAsTris);
             }
             break;
         }
@@ -540,5 +531,15 @@
 }
 
 GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createVSImpl(std::unique_ptr<Shader> shadr) const {
-    return new VSImpl(std::move(shadr));
+    switch (fRenderPass) {
+        case RenderPass::kTriangles:
+            return new VSHullAndEdgeImpl(std::move(shadr), 3);
+        case RenderPass::kTriangleCorners:
+            return new VSCornerImpl(std::move(shadr));
+        case RenderPass::kQuadratics:
+        case RenderPass::kCubics:
+            return new VSHullAndEdgeImpl(std::move(shadr), 4);
+    }
+    SK_ABORT("Invalid RenderPass");
+    return nullptr;
 }
diff --git a/src/gpu/ccpr/GrCCCubicShader.cpp b/src/gpu/ccpr/GrCCCubicShader.cpp
index 7066fbd..76d1646 100644
--- a/src/gpu/ccpr/GrCCCubicShader.cpp
+++ b/src/gpu/ccpr/GrCCCubicShader.cpp
@@ -12,7 +12,9 @@
 
 using Shader = GrCCCoverageProcessor::Shader;
 
-const char* GrCCCubicShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts) const {
+void GrCCCubicShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
+                                    const char* /*repetitionID*/, const char* /*wind*/,
+                                    GeometryVars*) const {
     // Find the cubic's power basis coefficients.
     s->codeAppendf("float2x4 C = float4x4(-1,  3, -3,  1, "
                                          " 3, -6,  3,  0, "
@@ -62,28 +64,24 @@
     s->codeAppendf("%s *= float3x3(orientation[0] * orientation[1], 0, 0, "
                                   "0, orientation[0], 0, "
                                   "0, 0, orientation[1]);", fKLMMatrix.c_str());
-
-    return nullptr;
 }
 
-Shader::CoverageHandling GrCCCubicShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
-                                                         GrGLSLVarying::Scope scope, SkString* code,
-                                                         const char* position,
-                                                         const char* coverageTimesWind) {
+void GrCCCubicShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
+                                     GrGLSLVarying::Scope scope, SkString* code,
+                                     const char* position, const char* inputCoverage,
+                                     const char* wind) {
     code->appendf("float3 klm = float3(%s, 1) * %s;", position, fKLMMatrix.c_str());
 
     fKLMW.reset(kFloat4_GrSLType, scope);
     varyingHandler->addVarying("klmw", &fKLMW);
     code->appendf("%s.xyz = klm;", OutName(fKLMW));
-    code->appendf("%s.w = %s;", OutName(fKLMW), coverageTimesWind);
+    code->appendf("%s.w = %s * %s;", OutName(fKLMW), inputCoverage, wind);
 
     fGradMatrix.reset(kFloat2x2_GrSLType, scope);
     varyingHandler->addVarying("grad_matrix", &fGradMatrix);
     code->appendf("%s[0] = 3 * klm[0] * %s[0].xy;", OutName(fGradMatrix), fKLMMatrix.c_str());
     code->appendf("%s[1] = -klm[1] * %s[2].xy - klm[2] * %s[1].xy;",
                     OutName(fGradMatrix), fKLMMatrix.c_str(), fKLMMatrix.c_str());
-
-    return CoverageHandling::kHandled;
 }
 
 void GrCCCubicShader::onEmitFragmentCode(const GrCCCoverageProcessor& proc,
diff --git a/src/gpu/ccpr/GrCCCubicShader.h b/src/gpu/ccpr/GrCCCubicShader.h
index 3ffce65..70d3300 100644
--- a/src/gpu/ccpr/GrCCCubicShader.h
+++ b/src/gpu/ccpr/GrCCCubicShader.h
@@ -23,10 +23,11 @@
  */
 class GrCCCubicShader : public GrCCCoverageProcessor::Shader {
 protected:
-    const char* emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts) const override;
+    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
+                       const char* wind, GeometryVars*) const override;
 
-    CoverageHandling onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
-                                    const char* position, const char* coverageTimesWind) override;
+    void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
+                        const char* position, const char* inputCoverage, const char* wind) override;
 
     void onEmitFragmentCode(const GrCCCoverageProcessor&, GrGLSLFPFragmentBuilder*,
                             const char* outputCoverage) const override;
diff --git a/src/gpu/ccpr/GrCCPathParser.cpp b/src/gpu/ccpr/GrCCPathParser.cpp
index 42f1010..e625c43 100644
--- a/src/gpu/ccpr/GrCCPathParser.cpp
+++ b/src/gpu/ccpr/GrCCPathParser.cpp
@@ -514,12 +514,17 @@
     if (batchTotalCounts.fTriangles) {
         this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangles,
                              WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles, drawBounds);
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
+                             WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles, drawBounds);
     }
 
     if (batchTotalCounts.fWoundTriangles) {
         this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangles,
                              WindMethod::kInstanceData, &PrimitiveTallies::fWoundTriangles,
                              drawBounds);
+        this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
+                             WindMethod::kInstanceData, &PrimitiveTallies::fWoundTriangles,
+                             drawBounds);
     }
 
     if (batchTotalCounts.fQuadratics) {
@@ -587,8 +592,9 @@
     SkASSERT(totalInstanceCount == batch.fTotalPrimitiveCounts.*instanceType);
 
     if (!fMeshesScratchBuffer.empty()) {
-        proc.draw(flushState, pipeline, fMeshesScratchBuffer.begin(),
-                  fDynamicStatesScratchBuffer.begin(), fMeshesScratchBuffer.count(),
-                  SkRect::Make(drawBounds));
+        SkASSERT(flushState->rtCommandBuffer());
+        flushState->rtCommandBuffer()->draw(pipeline, proc, fMeshesScratchBuffer.begin(),
+                                            fDynamicStatesScratchBuffer.begin(),
+                                            fMeshesScratchBuffer.count(), SkRect::Make(drawBounds));
     }
 }
diff --git a/src/gpu/ccpr/GrCCQuadraticShader.cpp b/src/gpu/ccpr/GrCCQuadraticShader.cpp
index e164cff..baa10fd 100644
--- a/src/gpu/ccpr/GrCCQuadraticShader.cpp
+++ b/src/gpu/ccpr/GrCCQuadraticShader.cpp
@@ -13,7 +13,9 @@
 
 using Shader = GrCCCoverageProcessor::Shader;
 
-const char* GrCCQuadraticShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts) const {
+void GrCCQuadraticShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
+                                        const char* /*repetitionID*/, const char* /*wind*/,
+                                        GeometryVars* vars) const {
     s->declareGlobal(fCanonicalMatrix);
     s->codeAppendf("%s = float3x3(0.0, 0, 1, "
                                  "0.5, 0, 1, "
@@ -36,20 +38,23 @@
                                                       "%s[0] + tan0 * t, "
                                                       "%s[1] + tan1 * t, "
                                                       "%s[2]);", pts, pts, pts, pts);
-    return "quadratic_hull";
+    vars->fHullVars.fAlternatePoints = "quadratic_hull";
 }
 
-Shader::CoverageHandling GrCCQuadraticShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
-                                                             GrGLSLVarying::Scope scope,
-                                                             SkString* code, const char* position,
-                                                             const char* coverageTimesWind) {
+void GrCCQuadraticShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
+                                         GrGLSLVarying::Scope scope, SkString* code,
+                                         const char* position, const char* inputCoverage,
+                                         const char* wind) {
     fCoords.reset(kFloat4_GrSLType, scope);
     varyingHandler->addVarying("coords", &fCoords);
     code->appendf("%s.xy = (%s * float3(%s, 1)).xy;",
                   OutName(fCoords), fCanonicalMatrix.c_str(), position);
     code->appendf("%s.zw = float2(2 * %s.x, -1) * float2x2(%s);",
                   OutName(fCoords), OutName(fCoords), fCanonicalMatrix.c_str());
-    return CoverageHandling::kNotHandled;
+
+    fCoverageTimesWind.reset(kHalf_GrSLType, scope);
+    varyingHandler->addVarying("coverage_times_wind", &fCoverageTimesWind);
+    code->appendf("%s = %s * %s;", OutName(fCoverageTimesWind), inputCoverage, wind);
 }
 
 void GrCCQuadraticShader::onEmitFragmentCode(const GrCCCoverageProcessor& proc,
@@ -62,5 +67,5 @@
         f->codeAppendf("d /= %f;", proc.debugBloat());
     }
 #endif
-    f->codeAppendf("%s = clamp(0.5 - d, 0, 1);", outputCoverage);
+    f->codeAppendf("%s = clamp(0.5 - d, 0, 1) * %s;", outputCoverage, fCoverageTimesWind.fsIn());
 }
diff --git a/src/gpu/ccpr/GrCCQuadraticShader.h b/src/gpu/ccpr/GrCCQuadraticShader.h
index a2ac5da..d91f943 100644
--- a/src/gpu/ccpr/GrCCQuadraticShader.h
+++ b/src/gpu/ccpr/GrCCQuadraticShader.h
@@ -22,16 +22,18 @@
  */
 class GrCCQuadraticShader : public GrCCCoverageProcessor::Shader {
 protected:
-    const char* emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts) const override;
+    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
+                       const char* wind, GeometryVars*) const override;
 
-    CoverageHandling onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
-                                    const char* position, const char* coverageTimesWind) override;
+    void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
+                        const char* position, const char* inputCoverage, const char* wind) override;
 
     void onEmitFragmentCode(const GrCCCoverageProcessor&, GrGLSLFPFragmentBuilder*,
                             const char* outputCoverage) const override;
 
     const GrShaderVar fCanonicalMatrix{"canonical_matrix", kFloat3x3_GrSLType};
     GrGLSLVarying fCoords;
+    GrGLSLVarying fCoverageTimesWind;
 };
 
 #endif
diff --git a/src/gpu/ccpr/GrCCTriangleShader.cpp b/src/gpu/ccpr/GrCCTriangleShader.cpp
new file mode 100644
index 0000000..8135313
--- /dev/null
+++ b/src/gpu/ccpr/GrCCTriangleShader.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 "GrCCTriangleShader.h"
+
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
+
+using Shader = GrCCCoverageProcessor::Shader;
+
+void GrCCTriangleShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
+                                        GrGLSLVarying::Scope scope, SkString* code,
+                                        const char* /*position*/, const char* inputCoverage,
+                                        const char* wind) {
+    SkASSERT(inputCoverage);
+    fCoverageTimesWind.reset(kHalf_GrSLType, scope);
+    varyingHandler->addVarying("coverage_times_wind", &fCoverageTimesWind);
+    code->appendf("%s = %s * %s;", OutName(fCoverageTimesWind), inputCoverage, wind);
+}
+
+void GrCCTriangleShader::onEmitFragmentCode(const GrCCCoverageProcessor&,
+                                            GrGLSLFPFragmentBuilder* f,
+                                            const char* outputCoverage) const {
+    f->codeAppendf("%s = %s;", outputCoverage, fCoverageTimesWind.fsIn());
+}
+
+void GrCCTriangleCornerShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
+                                             const char* repetitionID, const char* wind,
+                                             GeometryVars* vars) const {
+    s->codeAppendf("float2 corner = %s[%s];", pts, repetitionID);
+    vars->fCornerVars.fPoint = "corner";
+
+    s->codeAppendf("float2x2 vectors = float2x2(corner - %s[0 != %s ? %s - 1 : 2], "
+                                               "corner - %s[2 != %s ? %s + 1 : 0]);",
+                                               pts, repetitionID, repetitionID, pts, repetitionID,
+                                               repetitionID);
+
+    // 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 = (abs(n.x) + abs(n.y)) * (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).
+    s->codeAppend (    "n = (i == 0) ? float2(-n.y, n.x) : float2(n.y, -n.x);");
+    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 ("}");
+}
+
+void GrCCTriangleCornerShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
+                                              GrGLSLVarying::Scope scope, SkString* code,
+                                              const char* position, const char* inputCoverage,
+                                              const char* wind) {
+    using Interpolation = GrGLSLVaryingHandler::Interpolation;
+    SkASSERT(!inputCoverage);
+
+    fCornerLocationInAABoxes.reset(kFloat2x2_GrSLType, scope);
+    varyingHandler->addVarying("corner_location_in_aa_boxes", &fCornerLocationInAABoxes);
+
+    fBisectInAABoxes.reset(kFloat2x2_GrSLType, scope);
+    varyingHandler->addVarying("bisect_in_aa_boxes", &fBisectInAABoxes, Interpolation::kCanBeFlat);
+
+    code->appendf("for (int i = 0; i < 2; ++i) {");
+    code->appendf(    "%s[i] = %s * %s[i] + %s[i];",
+                      OutName(fCornerLocationInAABoxes), position, fAABoxMatrices.c_str(),
+                      fAABoxTranslates.c_str());
+    code->appendf(    "%s[i] = %s[i];", OutName(fBisectInAABoxes), fGeoShaderBisects.c_str());
+    code->appendf("}");
+
+    fWindTimesHalf.reset(kHalf_GrSLType, scope);
+    varyingHandler->addVarying("wind_times_half", &fWindTimesHalf, Interpolation::kCanBeFlat);
+    code->appendf("%s = %s * .5;", OutName(fWindTimesHalf), wind);
+}
+
+void GrCCTriangleCornerShader::onEmitFragmentCode(const GrCCCoverageProcessor&,
+                                                  GrGLSLFPFragmentBuilder* 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 *= %s;", outputCoverage, fWindTimesHalf.fsIn());
+}
diff --git a/src/gpu/ccpr/GrCCTriangleShader.h b/src/gpu/ccpr/GrCCTriangleShader.h
new file mode 100644
index 0000000..6dae8df
--- /dev/null
+++ b/src/gpu/ccpr/GrCCTriangleShader.h
@@ -0,0 +1,49 @@
+/*
+ * 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 GrCCTriangleShader_DEFINED
+#define GrCCTriangleShader_DEFINED
+
+#include "ccpr/GrCCCoverageProcessor.h"
+
+/**
+ * Steps 1 & 2: Draw the triangle's conservative raster hull with a coverage of +1, then smooth the
+ *              edges by drawing the conservative rasters of all 3 edges and interpolating from
+ *              coverage=-1 on the outside to coverage=0 on the inside. The Impl may choose to
+ *              implement these steps in either one or two actual render passes.
+ */
+class GrCCTriangleShader : public GrCCCoverageProcessor::Shader {
+    void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
+                        const char* position, const char* inputCoverage, const char* wind) override;
+    void onEmitFragmentCode(const GrCCCoverageProcessor&, GrGLSLFPFragmentBuilder*,
+                            const char* outputCoverage) const override;
+
+    GrGLSLVarying fCoverageTimesWind;
+};
+
+/**
+ * Step 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 GrCCTriangleCornerShader : public GrCCCoverageProcessor::Shader {
+    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
+                       const char* wind, GeometryVars*) const override;
+    void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
+                        const char* position, const char* inputCoverage, const char* wind) override;
+    void onEmitFragmentCode(const GrCCCoverageProcessor&, GrGLSLFPFragmentBuilder*,
+                            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};
+    GrGLSLVarying fCornerLocationInAABoxes;
+    GrGLSLVarying fBisectInAABoxes;
+    GrGLSLVarying fWindTimesHalf;
+};
+
+#endif