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.

Updates SampleCCPRGeometry to better visualize this new geometry by
clearing to black and drawing with SkBlendMode::kPlus.

Bug: skia:
Change-Id: Idf8df8ff715dfab7ac91a07b914f65c08e46010b
Reviewed-on: https://skia-review.googlesource.com/113287
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 457b405..ac52fe2 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -318,7 +318,6 @@
   "$_src/gpu/ccpr/GrCCPathProcessor.h",
   "$_src/gpu/ccpr/GrCCQuadraticShader.cpp",
   "$_src/gpu/ccpr/GrCCQuadraticShader.h",
-  "$_src/gpu/ccpr/GrCCTriangleShader.cpp",
   "$_src/gpu/ccpr/GrCCTriangleShader.h",
   "$_src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp",
   "$_src/gpu/ccpr/GrCoverageCountingPathRenderer.h",
diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp
index a90ece0..4b521de 100644
--- a/samplecode/SampleCCPRGeometry.cpp
+++ b/samplecode/SampleCCPRGeometry.cpp
@@ -32,10 +32,6 @@
 
 static constexpr float kDebugBloat = 40;
 
-static int is_quadratic(RenderPass pass) {
-    return pass == RenderPass::kQuadratics || pass == RenderPass::kQuadraticCorners;
-}
-
 /**
  * This sample visualizes the AA bloat geometry generated by the ccpr geometry shaders. It
  * increases the AA bloat by 50x and outputs color instead of coverage (coverage=+1 -> green,
@@ -91,6 +87,7 @@
     bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
     void onPrepare(GrOpFlushState*) override {}
     void onExecute(GrOpFlushState*) override;
+    void drawRenderPass(GrOpFlushState*, RenderPass);
 
     CCPRGeometryView* fView;
 
@@ -119,14 +116,13 @@
 }
 
 void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
-    SkAutoCanvasRestore acr(canvas, true);
-    canvas->setMatrix(SkMatrix::I());
+    canvas->clear(SK_ColorBLACK);
 
     SkPath outline;
     outline.moveTo(fPoints[0]);
-    if (GrCCCoverageProcessor::RenderPassIsCubic(fRenderPass)) {
+    if (RenderPass::kCubics == fRenderPass) {
         outline.cubicTo(fPoints[1], fPoints[2], fPoints[3]);
-    } else if (is_quadratic(fRenderPass)) {
+    } else if (RenderPass::kQuadratics == fRenderPass) {
         outline.quadTo(fPoints[1], fPoints[3]);
     } else {
         outline.lineTo(fPoints[1]);
@@ -135,7 +131,7 @@
     }
 
     SkPaint outlinePaint;
-    outlinePaint.setColor(0x30000000);
+    outlinePaint.setColor(0x80ffffff);
     outlinePaint.setStyle(SkPaint::kStroke_Style);
     outlinePaint.setStrokeWidth(0);
     outlinePaint.setAntiAlias(true);
@@ -159,7 +155,7 @@
     if (GrRenderTargetContext* rtc = canvas->internal_private_accessTopLayerRenderTargetContext()) {
         rtc->priv().testingOnly_addDrawOp(skstd::make_unique<Op>(this));
         caption.appendf("RenderPass_%s", GrCCCoverageProcessor::RenderPassName(fRenderPass));
-        if (GrCCCoverageProcessor::RenderPassIsCubic(fRenderPass)) {
+        if (RenderPass::kCubics == fRenderPass) {
             caption.appendf(" (%s)", SkCubicTypeName(fCubicType));
         }
     } else {
@@ -171,7 +167,7 @@
     pointsPaint.setStrokeWidth(8);
     pointsPaint.setAntiAlias(true);
 
-    if (GrCCCoverageProcessor::RenderPassIsCubic(fRenderPass)) {
+    if (RenderPass::kCubics == fRenderPass) {
         int w = this->width(), h = this->height();
         canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, fPoints, pointsPaint);
         draw_klm_line(w, h, canvas, &fCubicKLM[0], SK_ColorYELLOW);
@@ -184,7 +180,7 @@
 
     SkPaint captionPaint;
     captionPaint.setTextSize(20);
-    captionPaint.setColor(SK_ColorBLACK);
+    captionPaint.setColor(SK_ColorWHITE);
     captionPaint.setAntiAlias(true);
     canvas->drawText(caption.c_str(), caption.size(), 10, 30, captionPaint);
 }
@@ -193,7 +189,7 @@
     fTriPointInstances.reset();
     fQuadPointInstances.reset();
 
-    if (GrCCCoverageProcessor::RenderPassIsCubic(fRenderPass)) {
+    if (RenderPass::kCubics == fRenderPass) {
         double t[2], s[2];
         fCubicType = GrPathUtils::getCubicKLM(fPoints, &fCubicKLM, t, s);
         GrCCGeometry geometry;
@@ -217,7 +213,7 @@
                     continue;
             }
         }
-    } else if (is_quadratic(fRenderPass)) {
+    } else if (RenderPass::kQuadratics == fRenderPass) {
         GrCCGeometry geometry;
         geometry.beginContour(fPoints[0]);
         geometry.quadraticTo(fPoints[1], fPoints[3]);
@@ -243,18 +239,26 @@
 }
 
 void CCPRGeometryView::Op::onExecute(GrOpFlushState* state) {
+    this->drawRenderPass(state, fView->fRenderPass);
+
+    RenderPass cornerPass = RenderPass((int)fView->fRenderPass + 1);
+    if (GrCCCoverageProcessor::DoesRenderPass(cornerPass, state->caps())) {
+        this->drawRenderPass(state, cornerPass);
+    }
+}
+
+void CCPRGeometryView::Op::drawRenderPass(GrOpFlushState* state, RenderPass renderPass) {
     GrResourceProvider* rp = state->resourceProvider();
     GrContext* context = state->gpu()->getContext();
     GrGLGpu* glGpu = kOpenGL_GrBackend == context->contextPriv().getBackend()
                              ? static_cast<GrGLGpu*>(state->gpu())
                              : nullptr;
 
-    GrCCCoverageProcessor proc(rp, fView->fRenderPass,
-                               GrCCCoverageProcessor::WindMethod::kCrossProduct);
+    GrCCCoverageProcessor proc(rp, renderPass, GrCCCoverageProcessor::WindMethod::kCrossProduct);
     SkDEBUGCODE(proc.enableDebugVisualizations(kDebugBloat));
 
     SkSTArray<1, GrMesh> mesh;
-    if (GrCCCoverageProcessor::RenderPassIsCubic(fView->fRenderPass)) {
+    if (RenderPass::kCubics == renderPass) {
         sk_sp<GrBuffer> instBuff(rp->createBuffer(
                 fView->fQuadPointInstances.count() * sizeof(QuadPointInstance),
                 kVertex_GrBufferType, kDynamic_GrAccessPattern,
@@ -275,17 +279,18 @@
     }
 
     GrPipeline pipeline(state->drawOpArgs().fProxy, GrPipeline::ScissorState::kDisabled,
-                        SkBlendMode::kSrcOver);
+                        SkBlendMode::kPlus);
 
     if (glGpu) {
         glGpu->handleDirtyContext();
-        GR_GL_CALL(glGpu->glInterface(), PolygonMode(GR_GL_FRONT_AND_BACK, GR_GL_LINE));
+        // GR_GL_CALL(glGpu->glInterface(), PolygonMode(GR_GL_FRONT_AND_BACK, GR_GL_LINE));
         GR_GL_CALL(glGpu->glInterface(), Enable(GR_GL_LINE_SMOOTH));
     }
 
     if (!mesh.empty()) {
         SkASSERT(1 == mesh.count());
-        state->rtCommandBuffer()->draw(pipeline, proc, mesh.begin(), nullptr, 1, this->bounds());
+        GrGpuRTCommandBuffer* cmdBuff = state->rtCommandBuffer();
+        cmdBuff->draw(pipeline, proc, mesh.begin(), nullptr, 1, this->bounds());
     }
 
     if (glGpu) {
@@ -318,7 +323,7 @@
 
 SkView::Click* CCPRGeometryView::onFindClickHandler(SkScalar x, SkScalar y, unsigned) {
     for (int i = 0; i < 4; ++i) {
-        if (!GrCCCoverageProcessor::RenderPassIsCubic(fRenderPass) && 2 == i) {
+        if (RenderPass::kCubics != fRenderPass && 2 == i) {
             continue;
         }
         if (fabs(x - fPoints[i].x()) < 20 && fabsf(y - fPoints[i].y()) < 20) {
@@ -342,8 +347,8 @@
     }
     SkUnichar unichar;
     if (SampleCode::CharQ(*evt, &unichar)) {
-        if (unichar >= '1' && unichar <= '6') {
-            fRenderPass = RenderPass(unichar - '1');
+        if (unichar >= '1' && unichar <= '3') {
+            fRenderPass = RenderPass((unichar - '1') * 2);
             this->updateAndInval();
             return true;
         }
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.cpp b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
index 686ab55..efe003d 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
@@ -25,8 +25,8 @@
     f->codeAppendf("%s = half4(1);", skOutputCoverage);
 #ifdef SK_DEBUG
     if (proc.debugVisualizationsEnabled()) {
-        f->codeAppendf("%s = half4(-%s.a, %s.a, 0, 1);",
-                       skOutputColor, skOutputColor, skOutputColor);
+        f->codeAppendf("%s = half4(-%s.a, %s.a, 0, abs(%s.a));",
+                       skOutputColor, skOutputColor, skOutputColor, skOutputColor);
     }
 #endif
 }
@@ -79,6 +79,23 @@
     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("}");
+}
+
 int GrCCCoverageProcessor::Shader::DefineSoftSampleLocations(GrGLSLFPFragmentBuilder* f,
                                                              const char* samplesName) {
     // Standard DX11 sample locations.
@@ -120,10 +137,8 @@
     std::unique_ptr<Shader> shader;
     switch (fRenderPass) {
         case RenderPass::kTriangles:
-            shader = skstd::make_unique<GrCCTriangleShader>();
-            break;
         case RenderPass::kTriangleCorners:
-            shader = skstd::make_unique<GrCCTriangleCornerShader>();
+            shader = skstd::make_unique<GrCCTriangleShader>();
             break;
         case RenderPass::kQuadratics:
             shader = skstd::make_unique<GrCCQuadraticHullShader>();
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.h b/src/gpu/ccpr/GrCCCoverageProcessor.h
index c1f8599..5c10630 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.h
@@ -54,10 +54,11 @@
         void set(const SkPoint&, const SkPoint&, const SkPoint&, const Sk2f& trans, float w);
     };
 
-    // All primitive shapes (triangles and closed, convex bezier curves) require two
-    // render passes: One to draw a rough outline of the shape, and a second pass to touch up the
-    // corners. 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.
+    // All primitive shapes (triangles and closed, convex bezier curves) may require two render
+    // passes: One to draw a rough outline of the shape, and a second pass to touch up the corners.
+    // Check DoesRenderPass() before attempting to draw a given RenderPass. Here we enumerate every
+    // possible 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 {
         kTriangles,
         kTriangleCorners,
@@ -69,6 +70,11 @@
     static bool RenderPassIsCubic(RenderPass);
     static const char* RenderPassName(RenderPass);
 
+    constexpr static bool DoesRenderPass(RenderPass renderPass, const GrCaps& caps) {
+        return RenderPass::kTriangleCorners != renderPass ||
+               caps.shaderCaps()->geometryShaderSupport();
+    }
+
     enum class WindMethod : bool {
         kCrossProduct, // Calculate wind = +/-1 by sign of the cross product.
         kInstanceData // Instance data provides custom, signed wind values of any magnitude.
@@ -81,6 +87,7 @@
             , fWindMethod(windMethod)
             , fImpl(rp->caps()->shaderCaps()->geometryShaderSupport() ? Impl::kGeometryShader
                                                                       : Impl::kVertexShader) {
+        SkASSERT(DoesRenderPass(pass, *rp->caps()));
         if (Impl::kGeometryShader == fImpl) {
             this->initGS();
         } else {
@@ -88,18 +95,6 @@
         }
     }
 
-    // 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 {
@@ -116,6 +111,18 @@
     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);
+        }
+    }
+
     // The Shader provides code to calculate each pixel's coverage in a RenderPass. It also
     // provides details about shape-specific geometry.
     class Shader {
@@ -169,6 +176,13 @@
                                                   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:
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp b/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
index d9febc0..b210aa1 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
@@ -76,7 +76,8 @@
         SkSTArray<2, GrShaderVar> emitArgs;
         const char* position = emitArgs.emplace_back("position", kFloat2_GrSLType).c_str();
         const char* coverage = nullptr;
-        if (RenderPass::kTriangles == proc.fRenderPass) {
+        if (RenderPass::kTriangles == proc.fRenderPass ||
+            RenderPass::kTriangleCorners == proc.fRenderPass) {
             coverage = emitArgs.emplace_back("coverage", kHalf_GrSLType).c_str();
         }
         g->emitFunction(kVoid_GrSLType, "emitVertex", emitArgs.count(), emitArgs.begin(), [&]() {
@@ -116,7 +117,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 GSCornerImpl.
+ * The final corners get touched up in a later step by GSTriangleCornerImpl.
  */
 class GSTriangleImpl : public GrCCCoverageProcessor::GSImpl {
 public:
@@ -124,6 +125,10 @@
 
     void onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
                               const char* emitVertexFn) const override {
+        Shader::GeometryVars vars;
+        fShader->emitSetupCode(g, "pts", nullptr, wind.c_str(), &vars);
+        SkASSERT(!vars.fHullVars.fAlternatePoints);
+
         // 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.
         //
@@ -212,6 +217,61 @@
 };
 
 /**
+ * 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 {
+        Shader::GeometryVars vars;
+        fShader->emitSetupCode(g, "pts", nullptr, wind.c_str(), &vars);
+        SkASSERT(!vars.fHullVars.fAlternatePoints);
+
+        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 around a convex quadrilateral that encloses a cubic or quadratic.
  */
 class GSHull4Impl : public GrCCCoverageProcessor::GSImpl {
@@ -317,12 +377,10 @@
         this->addVertexAttrib("x_or_y_values", kFloat4_GrVertexAttribType);
         SkASSERT(sizeof(QuadPointInstance) == this->getVertexStride() * 2);
         SkASSERT(offsetof(QuadPointInstance, fY) == this->getVertexStride());
-        GR_STATIC_ASSERT(0 == offsetof(QuadPointInstance, fX));
     } else {
         this->addVertexAttrib("x_or_y_values", kFloat3_GrVertexAttribType);
         SkASSERT(sizeof(TriPointInstance) == this->getVertexStride() * 2);
         SkASSERT(offsetof(TriPointInstance, fY) == this->getVertexStride());
-        GR_STATIC_ASSERT(0 == offsetof(TriPointInstance, fX));
     }
     this->setWillUseGeoShader();
 }
@@ -344,7 +402,7 @@
         case RenderPass::kTriangles:
             return new GSTriangleImpl(std::move(shadr));
         case RenderPass::kTriangleCorners:
-            return new GSCornerImpl(std::move(shadr), 3);
+            return new GSTriangleCornerImpl(std::move(shadr));
         case RenderPass::kQuadratics:
         case RenderPass::kCubics:
             return new GSHull4Impl(std::move(shadr));
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp b/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
index 144a4a5..28ed6fc 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
@@ -89,10 +89,11 @@
     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_InvertCoverageBit = 1 << 4;
+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;
 static constexpr int kVertexData_IsEdgeBit = 1 << 3;
 static constexpr int kVertexData_IsHullBit = 1 << 2;
 
@@ -120,10 +121,15 @@
                             0 == endptIdx ? (edgeID + 1) % n : edgeID,
                             bloatIdx, 0 == endptIdx ? edgeID : (edgeID + 1) % n,
                             kVertexData_IsEdgeBit |
-                            (!endptIdx ? kVertexData_InvertCoverageBit : 0));
+                            (!endptIdx ? kVertexData_InvertNegativeCoverageBit : 0));
 }
 
-static constexpr int32_t kHull3AndEdgeVertices[] = {
+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[] = {
     hull_vertex_data(0, 0, 3),
     hull_vertex_data(0, 1, 3),
     hull_vertex_data(0, 2, 3),
@@ -154,21 +160,39 @@
     edge_vertex_data(2, 1, 0, 3),
     edge_vertex_data(2, 1, 1, 3),
     edge_vertex_data(2, 1, 2, 3),
+
+    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(gHull3AndEdgeVertexBufferKey);
+GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey);
 
 static constexpr uint16_t kRestartStrip = 0xffff;
 
-static constexpr uint16_t kHull3AndEdgeIndicesAsStrips[] =  {
+static constexpr uint16_t kTriangleIndicesAsStrips[] =  {
     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 // Third 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.
 };
 
-static constexpr uint16_t kHull3AndEdgeIndicesAsTris[] =  {
+static constexpr uint16_t kTriangleIndicesAsTris[] =  {
     // First corner and main body of the hull.
     1, 2, 0,
     2, 3, 0,
@@ -197,9 +221,21 @@
     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(gHull3AndEdgeIndexBufferKey);
+GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey);
 
 static constexpr int32_t kHull4Vertices[] = {
     hull_vertex_data(0, 0, 4),
@@ -243,19 +279,21 @@
 
 GR_DECLARE_STATIC_UNIQUE_KEY(gHull4IndexBufferKey);
 
+
 /**
- * Generates a conservative raster hull around a convex polygon. For triangles we generate
- * additional conservative rasters around the edges and calculate coverage ramps.
+ * Generates a conservative raster hull around a triangle or curve. For triangles we generate
+ * additional conservative rasters with coverage ramps around the edges and corners.
  *
- * Triangle rough outlines are drawn in two steps: (1) draw a conservative raster of the entire
- * triangle, with a coverage of +1, and (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.
+ * 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.
  *
- * Curve rough outlines are just the conservative raster of a convex quadrilateral that encloses the
- * curve. The Shader takes care of everything else for now.
- *
- * The final corners get touched up in a later step by VSCornerImpl.
+ * Curves are drawn in two separate passes. Here we just draw a conservative raster around the input
+ * points. The Shader takes care of everything else for now. The final curve corners get touched up
+ * in a later step by VSCornerImpl.
  */
 class VSHullAndEdgeImpl : public GrCCCoverageProcessor::VSImpl {
 public:
@@ -284,10 +322,9 @@
         // 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. For more details on conservative raster, see:
+        // to offset each vertex. Triangle edges and corners are also handled here using the same
+        // concept. For more details on conservative raster, see:
         // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
-        //
-        // Triangle edges are also handled here using the same concept (see kHull3AndEdgeVertices).
         v->codeAppendf("float2 corner = %s[clockwise_indices & 3];", hullPts);
         v->codeAppendf("float2 left = %s[clockwise_indices >> %i];",
                        hullPts, kVertexData_LeftNeighborIdShift);
@@ -304,48 +341,97 @@
 
         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;");
+
+        if (3 == fNumSides) { // 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 this corner's first raster vertex (leftbloat), then
+        // continue rotating 90 degrees clockwise until we reach the desired raster vertex for this
+        // invocation. Corners with less than 3 corresponding raster 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 (3 == fNumSides) { // 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);");
+        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);");
+        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");
+
         // For triangles, we also emit coverage in order to handle edges and corners.
         const char* coverage = nullptr;
         if (3 == fNumSides) {
-            v->codeAppend ("half coverage;");
-            Shader::CalcEdgeCoverageAtBloatVertex(v, "left", "corner", "bloatdir", "coverage");
-            v->codeAppendf("if (0 != (%s & %i)) {", // Are we the opposite endpoint of an edge?
+            // The hull has a coverage of +1 all around.
+            v->codeAppend ("half coverage = +1;");
+
+            v->codeAppendf("if (0 != (%s & %i)) {", // Are we an edge OR corner?
                            proc.getAttrib(kAttribIdx_VertexData).fName,
-                           kVertexData_InvertCoverageBit);
-            v->codeAppend (    "coverage = -1 - coverage;");
+                           kVertexData_IsEdgeBit | kVertexData_IsCornerBit);
+            Shader::CalcEdgeCoverageAtBloatVertex(v, "left", "corner", "bloatdir", "coverage");
             v->codeAppend ("}");
 
-            v->codeAppendf("if (0 != (%s & %i)) {", // Are we a hull vertex?
-                           proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsHullBit);
-            v->codeAppend (    "coverage = +1;"); // Hull coverage is +1 all around.
+            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 ("}");
+
+            v->codeAppendf("if (0 != (%s & %i)) {", // Invert coverage?
+                           proc.getAttrib(kAttribIdx_VertexData).fName,
+                           kVertexData_InvertNegativeCoverageBit);
+            v->codeAppend (    "coverage = -1 - coverage;");
             v->codeAppend ("}");
 
             coverage = "coverage";
         }
 
-        v->codeAppend ("float2 vertex = corner + bloatdir * bloat;");
-        gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
-
         return coverage;
     }
 
@@ -355,8 +441,7 @@
 
 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.
+    4, 5, 6, 7 // Second corner.
 };
 
 static constexpr uint16_t kCornerIndicesAsTris[] =  {
@@ -367,10 +452,6 @@
     // Second corner.
     4,  5,  6,
     5,  7,  6,
-
-    // Third corner.
-    8,  9, 10,
-    9, 11, 10,
 };
 
 GR_DECLARE_STATIC_UNIQUE_KEY(gCornerIndexBufferKey);
@@ -403,28 +484,31 @@
 
     switch (fRenderPass) {
         case RenderPass::kTriangles: {
-            GR_DEFINE_STATIC_UNIQUE_KEY(gHull3AndEdgeVertexBufferKey);
+            GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey);
             fVertexBuffer = rp->findOrMakeStaticBuffer(kVertex_GrBufferType,
-                                                       sizeof(kHull3AndEdgeVertices),
-                                                       kHull3AndEdgeVertices,
-                                                       gHull3AndEdgeVertexBufferKey);
-            GR_DEFINE_STATIC_UNIQUE_KEY(gHull3AndEdgeIndexBufferKey);
+                                                       sizeof(kTriangleVertices),
+                                                       kTriangleVertices,
+                                                       gTriangleVertexBufferKey);
+            GR_DEFINE_STATIC_UNIQUE_KEY(gTriangleIndexBufferKey);
             if (caps.usePrimitiveRestart()) {
                 fIndexBuffer = rp->findOrMakeStaticBuffer(kIndex_GrBufferType,
-                                                          sizeof(kHull3AndEdgeIndicesAsStrips),
-                                                          kHull3AndEdgeIndicesAsStrips,
-                                                          gHull3AndEdgeIndexBufferKey);
-                fNumIndicesPerInstance = SK_ARRAY_COUNT(kHull3AndEdgeIndicesAsStrips);
+                                                          sizeof(kTriangleIndicesAsStrips),
+                                                          kTriangleIndicesAsStrips,
+                                                          gTriangleIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsStrips);
             } else {
                 fIndexBuffer = rp->findOrMakeStaticBuffer(kIndex_GrBufferType,
-                                                          sizeof(kHull3AndEdgeIndicesAsTris),
-                                                          kHull3AndEdgeIndicesAsTris,
-                                                          gHull3AndEdgeIndexBufferKey);
-                fNumIndicesPerInstance = SK_ARRAY_COUNT(kHull3AndEdgeIndicesAsTris);
+                                                          sizeof(kTriangleIndicesAsTris),
+                                                          kTriangleIndicesAsTris,
+                                                          gTriangleIndexBufferKey);
+                fNumIndicesPerInstance = SK_ARRAY_COUNT(kTriangleIndicesAsTris);
             }
             break;
         }
 
+        case RenderPass::kTriangleCorners:
+            SK_ABORT("RenderPass::kTriangleCorners is unused by VSImpl.");
+
         case RenderPass::kQuadratics:
         case RenderPass::kCubics: {
             GR_DEFINE_STATIC_UNIQUE_KEY(gHull4VertexBufferKey);
@@ -447,7 +531,6 @@
             break;
         }
 
-        case RenderPass::kTriangleCorners:
         case RenderPass::kQuadraticCorners:
         case RenderPass::kCubicCorners: {
             GR_DEFINE_STATIC_UNIQUE_KEY(gCornerIndexBufferKey);
@@ -463,12 +546,9 @@
                                                           kCornerIndicesAsTris,
                                                           gCornerIndexBufferKey);
                 fNumIndicesPerInstance = SK_ARRAY_COUNT(kCornerIndicesAsTris);
-            }
-            if (RenderPass::kTriangleCorners != fRenderPass) {
-                fNumIndicesPerInstance = fNumIndicesPerInstance * 2/3;
-            }
-            break;
-        }
+             }
+             break;
+         }
     }
 
     if (RenderPassIsCubic(fRenderPass) || WindMethod::kInstanceData == fWindMethod) {
diff --git a/src/gpu/ccpr/GrCCPathParser.cpp b/src/gpu/ccpr/GrCCPathParser.cpp
index cb5311f..1114367 100644
--- a/src/gpu/ccpr/GrCCPathParser.cpp
+++ b/src/gpu/ccpr/GrCCPathParser.cpp
@@ -514,7 +514,8 @@
         this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangles,
                              WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles, drawBounds);
         this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
-                             WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles, drawBounds);
+                             WindMethod::kCrossProduct, &PrimitiveTallies::fTriangles,
+                             drawBounds); // Might get skipped.
     }
 
     if (batchTotalCounts.fWoundTriangles) {
@@ -523,7 +524,7 @@
                              drawBounds);
         this->drawRenderPass(flushState, pipeline, batchID, RenderPass::kTriangleCorners,
                              WindMethod::kInstanceData, &PrimitiveTallies::fWoundTriangles,
-                             drawBounds);
+                             drawBounds); // Might get skipped.
     }
 
     if (batchTotalCounts.fQuadratics) {
@@ -549,6 +550,10 @@
                                     const SkIRect& drawBounds) const {
     SkASSERT(pipeline.getScissorState().enabled());
 
+    if (!GrCCCoverageProcessor::DoesRenderPass(renderPass, flushState->caps())) {
+        return;
+    }
+
     // Don't call reset(), as that also resets the reserve count.
     fMeshesScratchBuffer.pop_back_n(fMeshesScratchBuffer.count());
     fDynamicStatesScratchBuffer.pop_back_n(fDynamicStatesScratchBuffer.count());
diff --git a/src/gpu/ccpr/GrCCTriangleShader.cpp b/src/gpu/ccpr/GrCCTriangleShader.cpp
deleted file mode 100644
index e086201..0000000
--- a/src/gpu/ccpr/GrCCTriangleShader.cpp
+++ /dev/null
@@ -1,149 +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 "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(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(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
index 5f33b07..c7fbefd 100644
--- a/src/gpu/ccpr/GrCCTriangleShader.h
+++ b/src/gpu/ccpr/GrCCTriangleShader.h
@@ -9,39 +9,28 @@
 #define GrCCTriangleShader_DEFINED
 
 #include "ccpr/GrCCCoverageProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.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.
+ * This class renders AA triangles. It relies on the coverage processor to set up the geometry and
+ * provide coverage values at each vertex, then simply interpolates these values in the fragment
+ * shader.
  */
 class GrCCTriangleShader : public GrCCCoverageProcessor::Shader {
-    void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
-                        const char* position, const char* inputCoverage, const char* wind) override;
-    void onEmitFragmentCode(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override;
+    void onEmitVaryings(GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope,
+                        SkString* code, const char* /*position*/, const char* inputCoverage,
+                        const char* wind) override {
+        SkASSERT(inputCoverage);
+        fCoverageTimesWind.reset(kHalf_GrSLType, scope);
+        varyingHandler->addVarying("coverage_times_wind", &fCoverageTimesWind);
+        code->appendf("%s = %s * %s;", OutName(fCoverageTimesWind), inputCoverage, wind);
+    }
+
+    void onEmitFragmentCode(GrGLSLFPFragmentBuilder* f, const char* outputCoverage) const override {
+        f->codeAppendf("%s = %s;", outputCoverage, fCoverageTimesWind.fsIn());
+    }
 
     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(GrGLSLFPFragmentBuilder* 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};
-    GrGLSLVarying fCornerLocationInAABoxes;
-    GrGLSLVarying fBisectInAABoxes;
-    GrGLSLVarying fWindTimesHalf;
-};
-
 #endif