ccpr: Replace curve corner MSAA with analytic attenuation

Begins using the new triangle corner algorithm on curves as well.
Updates the vertex backend to render curves in a single pass.
Simplifies the cubic and quadratic shaders. Removes all code related to
sample locations.

Bug: skia:
Change-Id: I96c6c401be765e96a8fe087deb7f84760e68dcf0
Reviewed-on: https://skia-review.googlesource.com/115746
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.cpp b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
index 10b6736..8e7249e9 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
@@ -127,26 +127,6 @@
                    outputAttenuation);
 }
 
-int GrCCCoverageProcessor::Shader::DefineSoftSampleLocations(GrGLSLFPFragmentBuilder* f,
-                                                             const char* samplesName) {
-    // Standard DX11 sample locations.
-#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_IOS)
-    f->defineConstant("float2[8]", samplesName, "float2[8]("
-        "float2(+1, -3)/16, float2(-1, +3)/16, float2(+5, +1)/16, float2(-3, -5)/16, "
-        "float2(-5, +5)/16, float2(-7, -1)/16, float2(+3, +7)/16, float2(+7, -7)/16."
-    ")");
-    return 8;
-#else
-    f->defineConstant("float2[16]", samplesName, "float2[16]("
-        "float2(+1, +1)/16, float2(-1, -3)/16, float2(-3, +2)/16, float2(+4, -1)/16, "
-        "float2(-5, -2)/16, float2(+2, +5)/16, float2(+5, +3)/16, float2(+3, -5)/16, "
-        "float2(-2, +6)/16, float2( 0, -7)/16, float2(-4, -6)/16, float2(-6, +4)/16, "
-        "float2(-8,  0)/16, float2(+7, -4)/16, float2(+6, +7)/16, float2(-7, -8)/16."
-    ")");
-    return 16;
-#endif
-}
-
 void GrCCCoverageProcessor::getGLSLProcessorKey(const GrShaderCaps&,
                                                 GrProcessorKeyBuilder* b) const {
     int key = (int)fRenderPass << 2;
@@ -172,16 +152,12 @@
             shader = skstd::make_unique<GrCCTriangleShader>();
             break;
         case RenderPass::kQuadratics:
-            shader = skstd::make_unique<GrCCQuadraticHullShader>();
-            break;
         case RenderPass::kQuadraticCorners:
-            shader = skstd::make_unique<GrCCQuadraticCornerShader>();
+            shader = skstd::make_unique<GrCCQuadraticShader>();
             break;
         case RenderPass::kCubics:
-            shader = skstd::make_unique<GrCCCubicHullShader>();
-            break;
         case RenderPass::kCubicCorners:
-            shader = skstd::make_unique<GrCCCubicCornerShader>();
+            shader = skstd::make_unique<GrCCCubicShader>();
             break;
     }
     return Impl::kGeometryShader == fImpl ? this->createGSImpl(std::move(shader))
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.h b/src/gpu/ccpr/GrCCCoverageProcessor.h
index d826b81..8b2b003 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.h
@@ -70,9 +70,19 @@
     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();
+    static bool DoesRenderPass(RenderPass renderPass, const GrCaps& caps) {
+        switch (renderPass) {
+            case RenderPass::kTriangles:
+            case RenderPass::kQuadratics:
+            case RenderPass::kCubics:
+                return true;
+            case RenderPass::kTriangleCorners:
+            case RenderPass::kQuadraticCorners:
+            case RenderPass::kCubicCorners:
+                return caps.shaderCaps()->geometryShaderSupport();
+        }
+        SK_ABORT("Invalid RenderPass");
+        return false;
     }
 
     enum class WindMethod : bool {
@@ -126,34 +136,20 @@
     // provides details about shape-specific geometry.
     class Shader {
     public:
-        union GeometryVars {
-            struct {
-                const char* fAlternatePoints; // floatNx2 (if left null, will use input points).
-            } fHullVars;
-
-            struct {
-                const char* fPoint; // float2
-            } fCornerVars;
-
-            GeometryVars() { memset(this, 0, sizeof(*this)); }
-        };
-
-        // Called before generating geometry. Subclasses must fill out the applicable fields in
-        // GeometryVars (if any), and may also use this opportunity to setup internal member
-        // variables that will be needed during onEmitVaryings (e.g. transformation matrices).
+        // Called before generating geometry. Subclasses may set up internal member variables during
+        // this time 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 {}
+        // If the optional 'tighterHull' parameter is not null and gets filled out by the subclass,
+        // the the Impl will generate geometry around those points rather than the input points.
+        virtual void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* wind,
+                                   const char** tighterHull = nullptr) const {}
 
         void emitVaryings(GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope,
                           SkString* code, const char* position, const char* coverage,
-                          const char* attenuatedCoverage, const char* wind) {
+                          const char* attenuatedCoverage) {
             SkASSERT(GrGLSLVarying::Scope::kVertToGeo != scope);
             this->onEmitVaryings(varyingHandler, scope, code, position, coverage,
-                                 attenuatedCoverage, wind);
+                                 attenuatedCoverage);
         }
 
         void emitFragmentCode(const GrCCCoverageProcessor&, GrGLSLFPFragmentBuilder*,
@@ -195,12 +191,13 @@
 
     protected:
         // Here the subclass adds its internal varyings to the handler and produces code to
-        // initialize those varyings from a given position, input coverage value, and wind.
+        // initialize those varyings from a given position and coverage values.
         //
-        // NOTE: the coverage inputs are only relevant for triangles. Otherwise they are null.
+        // NOTE: the coverage values are signed appropriately for wind.
+        //       'coverage' will only be +1 or -1 on curves.
         virtual void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
                                     const char* position, const char* coverage,
-                                    const char* attenuatedCoverage, const char* wind) = 0;
+                                    const char* attenuatedCoverage) = 0;
 
         // Emits the fragment code that calculates a pixel's signed coverage value.
         virtual void onEmitFragmentCode(GrGLSLFPFragmentBuilder*,
@@ -213,15 +210,12 @@
             SkASSERT(Scope::kVertToGeo != varying.scope());
             return Scope::kGeoToFrag == varying.scope() ? varying.gsOut() : varying.vsOut();
         }
-
-        // Defines a global float2 array that contains MSAA sample locations as offsets from pixel
-        // center. Subclasses can use this for software multisampling.
-        //
-        // Returns the number of samples.
-        static int DefineSoftSampleLocations(GrGLSLFPFragmentBuilder* f, const char* samplesName);
     };
 
     class GSImpl;
+    class GSTriangleHullImpl;
+    class GSCurveHullImpl;
+    class GSCornerImpl;
     class VSImpl;
 
 private:
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp b/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
index 82a292c..907de46 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor_GSImpl.cpp
@@ -12,7 +12,6 @@
 
 using InputType = GrGLSLGeometryBuilder::InputType;
 using OutputType = GrGLSLGeometryBuilder::OutputType;
-using Shader = GrCCCoverageProcessor::Shader;
 
 /**
  * This class and its subclasses implement the coverage processor with geometry shaders.
@@ -21,6 +20,9 @@
 protected:
     GSImpl(std::unique_ptr<Shader> shader) : fShader(std::move(shader)) {}
 
+    virtual bool hasCoverage() const { return false; }
+    virtual bool hasAttenuatedCoverage() const { return false; }
+
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
                  FPCoordTransformIter&& transformIter) final {
         this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
@@ -76,19 +78,24 @@
         SkSTArray<2, GrShaderVar> emitArgs;
         const char* position = emitArgs.emplace_back("position", kFloat2_GrSLType).c_str();
         const char* coverage = nullptr;
-        if (RenderPass::kTriangles == proc.fRenderPass ||
-            RenderPass::kTriangleCorners == proc.fRenderPass) {
+        if (this->hasCoverage()) {
             coverage = emitArgs.emplace_back("coverage", kHalf_GrSLType).c_str();
         }
         const char* attenuatedCoverage = nullptr;
-        if (RenderPass::kTriangleCorners == proc.fRenderPass) {
+        if (this->hasAttenuatedCoverage()) {
             attenuatedCoverage = emitArgs.emplace_back("attenuated_coverage",
                                                        kHalf2_GrSLType).c_str();
         }
         g->emitFunction(kVoid_GrSLType, "emitVertex", emitArgs.count(), emitArgs.begin(), [&]() {
             SkString fnBody;
+            if (coverage) {
+                fnBody.appendf("%s *= %s;", coverage, wind.c_str());
+            }
+            if (attenuatedCoverage) {
+                fnBody.appendf("%s.x *= %s;", attenuatedCoverage, wind.c_str());
+            }
             fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kGeoToFrag, &fnBody,
-                                  position, coverage, attenuatedCoverage, wind.c_str());
+                                  position, coverage ? coverage : wind.c_str(), attenuatedCoverage);
             g->emitVertex(&fnBody, position, rtAdjust);
             return fnBody;
         }().c_str(), &emitVertexFn);
@@ -101,11 +108,11 @@
 #endif
         g->defineConstant("bloat", bloat);
 
-        this->onEmitGeometryShader(g, wind, emitVertexFn.c_str());
+        this->onEmitGeometryShader(proc, g, wind, emitVertexFn.c_str());
     }
 
-    virtual void onEmitGeometryShader(GrGLSLGeometryBuilder*, const GrShaderVar& wind,
-                                      const char* emitVertexFn) const = 0;
+    virtual void onEmitGeometryShader(const GrCCCoverageProcessor&, GrGLSLGeometryBuilder*,
+                                      const GrShaderVar& wind, const char* emitVertexFn) const = 0;
 
     virtual ~GSImpl() {}
 
@@ -124,15 +131,15 @@
  *
  * The final corners get touched up in a later step by GSTriangleCornerImpl.
  */
-class GSTriangleImpl : public GrCCCoverageProcessor::GSImpl {
+class GrCCCoverageProcessor::GSTriangleHullImpl : public GrCCCoverageProcessor::GSImpl {
 public:
-    GSTriangleImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
+    GSTriangleHullImpl(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);
+    bool hasCoverage() const override { return true; }
+
+    void onEmitGeometryShader(const GrCCCoverageProcessor&, GrGLSLGeometryBuilder* g,
+                              const GrShaderVar& wind, const char* emitVertexFn) const override {
+        fShader->emitSetupCode(g, "pts", wind.c_str());
 
         // 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.
@@ -222,24 +229,31 @@
 };
 
 /**
- * Generates conservative rasters around triangle corners (aka pixel-size boxes) and calculates
- * coverage and attenuation ramps to fix up the coverage values written by GSTriangleImpl.
+ * Generates conservative rasters around corners (aka pixel-size boxes) and calculates
+ * coverage and attenuation ramps to fix up the coverage values written by the hulls.
  */
-class GSTriangleCornerImpl : public GrCCCoverageProcessor::GSImpl {
+class GrCCCoverageProcessor::GSCornerImpl : public GrCCCoverageProcessor::GSImpl {
 public:
-    GSTriangleCornerImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
+    GSCornerImpl(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);
+    bool hasCoverage() const override { return true; }
+    bool hasAttenuatedCoverage() const override { return true; }
 
-        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());
+    void onEmitGeometryShader(const GrCCCoverageProcessor& proc, GrGLSLGeometryBuilder* g,
+                              const GrShaderVar& wind, const char* emitVertexFn) const override {
+        fShader->emitSetupCode(g, "pts", wind.c_str());
+
+        bool isTriangle = RenderPass::kTriangleCorners == proc.fRenderPass;
+        g->codeAppendf("int corneridx = sk_InvocationID;");
+        if (!isTriangle) {
+            g->codeAppendf("corneridx *= %i;", proc.numInputPoints() - 1);
+        }
+
+        g->codeAppendf("float2 corner = pts[corneridx];");
+        g->codeAppendf("float2 left = pts[(corneridx + (%s > 0 ? %i : 1)) %% %i];",
+                       wind.c_str(), proc.numInputPoints() - 1, proc.numInputPoints());
+        g->codeAppendf("float2 right = pts[(corneridx + (%s > 0 ? 1 : %i)) %% %i];",
+                       wind.c_str(), proc.numInputPoints() - 1, proc.numInputPoints());
 
         g->codeAppend ("float2 leftdir = corner - left;");
         g->codeAppend ("leftdir = (float2(0) != leftdir) ? normalize(leftdir) : float2(1, 0);");
@@ -275,42 +289,42 @@
         //
         // NOTE: Since this is not a linear mapping, it is important that the box's diagonal shared
         // edge points in the direction of outbloat.
-        g->codeAppendf("%s(corner - crossbloat * bloat, "
-                          "right_coverages[1] - left_coverages[1],"
-                          "half2(1 + left_coverages[1], 1));", emitVertexFn);
+        g->codeAppendf("%s(corner - crossbloat * bloat, %s, half2(1 + left_coverages[1], 1));",
+                       emitVertexFn,
+                       // Erase what the hull wrote previously.
+                       isTriangle ? "right_coverages[1] - left_coverages[1]" : "-1");
 
-        g->codeAppendf("%s(corner + outbloat * bloat, "
-                          "1 + left_coverages[0] + right_coverages[0],"
-                          "half2(0, attenuation));", emitVertexFn);
+        g->codeAppendf("%s(corner + outbloat * bloat, %s, half2(0, attenuation));",
+                       emitVertexFn,
+                       // Erase what the hull wrote previously.
+                       isTriangle ? "1 + left_coverages[0] + right_coverages[0]" : "-1");
 
-        g->codeAppendf("%s(corner - outbloat * bloat, "
-                          "-1 - left_coverages[0] - right_coverages[0],"
-                          "half2(1 + left_coverages[0] + right_coverages[0], 1));", emitVertexFn);
+        g->codeAppendf("%s(corner - outbloat * bloat, %s, "
+                          "half2(1 + left_coverages[0] + right_coverages[0], 1));",
+                       emitVertexFn,
+                       // Erase what the hull wrote previously.
+                       isTriangle ? "-1 - left_coverages[0] - right_coverages[0]" : "-1");
 
-        g->codeAppendf("%s(corner + crossbloat * bloat, "
-                          "left_coverages[1] - right_coverages[1],"
-                          "half2(1 + right_coverages[1], 1));", emitVertexFn);
+        g->codeAppendf("%s(corner + crossbloat * bloat, %s, half2(1 + right_coverages[1], 1));",
+                       emitVertexFn,
+                       // Erase what the hull wrote previously.
+                       isTriangle ? "left_coverages[1] - right_coverages[1]" : "-1");
 
-        g->configure(InputType::kLines, OutputType::kTriangleStrip, 4, 3);
+        g->configure(InputType::kLines, OutputType::kTriangleStrip, 4, isTriangle ? 3 : 2);
     }
 };
 
 /**
  * Generates a conservative raster around a convex quadrilateral that encloses a cubic or quadratic.
  */
-class GSHull4Impl : public GrCCCoverageProcessor::GSImpl {
+class GrCCCoverageProcessor::GSCurveHullImpl : public GrCCCoverageProcessor::GSImpl {
 public:
-    GSHull4Impl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
+    GSCurveHullImpl(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);
-
-        const char* hullPts = vars.fHullVars.fAlternatePoints;
-        if (!hullPts) {
-            hullPts = "pts";
-        }
+    void onEmitGeometryShader(const GrCCCoverageProcessor&, GrGLSLGeometryBuilder* g,
+                              const GrShaderVar& wind, const char* emitVertexFn) const override {
+        const char* hullPts = "pts";
+        fShader->emitSetupCode(g, "pts", wind.c_str(), &hullPts);
 
         // Visualize the input (convex) quadrilateral as a square. Paying special attention to wind,
         // we can identify the points by their corresponding corner.
@@ -366,34 +380,6 @@
     }
 };
 
-/**
- * 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 (RenderPassIsCubic(fRenderPass) || WindMethod::kInstanceData == fWindMethod) {
@@ -424,15 +410,14 @@
 GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createGSImpl(std::unique_ptr<Shader> shadr) const {
     switch (fRenderPass) {
         case RenderPass::kTriangles:
-            return new GSTriangleImpl(std::move(shadr));
-        case RenderPass::kTriangleCorners:
-            return new GSTriangleCornerImpl(std::move(shadr));
+            return new GSTriangleHullImpl(std::move(shadr));
         case RenderPass::kQuadratics:
         case RenderPass::kCubics:
-            return new GSHull4Impl(std::move(shadr));
+            return new GSCurveHullImpl(std::move(shadr));
+        case RenderPass::kTriangleCorners:
         case RenderPass::kQuadraticCorners:
         case RenderPass::kCubicCorners:
-            return new GSCornerImpl(std::move(shadr), 2);
+            return new GSCornerImpl(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 e0ec4d4..d0fc4e0 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor_VSImpl.cpp
@@ -76,7 +76,7 @@
         SkString varyingCode;
         fShader->emitVaryings(varyingHandler, GrGLSLVarying::Scope::kVertToFrag, &varyingCode,
                               gpArgs->fPositionVar.c_str(), coverages.fCoverage,
-                              coverages.fAttenuatedCoverage, "wind");
+                              coverages.fAttenuatedCoverage);
         v->codeAppend(varyingCode.c_str());
 
         varyingHandler->emitAttributes(proc);
@@ -131,9 +131,9 @@
                             (!endptIdx ? kVertexData_InvertNegativeCoverageBit : 0));
 }
 
-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 corner_vertex_data(int32_t leftID, int32_t cornerID, int32_t rightID,
+                                            int32_t bloatIdx) {
+    return pack_vertex_data(leftID, rightID, bloatIdx, cornerID, kVertexData_IsCornerBit);
 }
 
 static constexpr int32_t kTriangleVertices[] = {
@@ -168,20 +168,20 @@
     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),
+    corner_vertex_data(2, 0, 1, 0),
+    corner_vertex_data(2, 0, 1, 1),
+    corner_vertex_data(2, 0, 1, 2),
+    corner_vertex_data(2, 0, 1, 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),
+    corner_vertex_data(0, 1, 2, 0),
+    corner_vertex_data(0, 1, 2, 1),
+    corner_vertex_data(0, 1, 2, 2),
+    corner_vertex_data(0, 1, 2, 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),
+    corner_vertex_data(1, 2, 0, 0),
+    corner_vertex_data(1, 2, 0, 1),
+    corner_vertex_data(1, 2, 0, 2),
+    corner_vertex_data(1, 2, 0, 3),
 };
 
 GR_DECLARE_STATIC_UNIQUE_KEY(gTriangleVertexBufferKey);
@@ -258,14 +258,24 @@
     hull_vertex_data(3, 1, 4),
     hull_vertex_data(3, 2, 4),
 
-    // No edges for now (beziers don't use edges).
+    corner_vertex_data(3, 0, 1, 0),
+    corner_vertex_data(3, 0, 1, 1),
+    corner_vertex_data(3, 0, 1, 2),
+    corner_vertex_data(3, 0, 1, 3),
+
+    corner_vertex_data(2, 3, 0, 0),
+    corner_vertex_data(2, 3, 0, 1),
+    corner_vertex_data(2, 3, 0, 2),
+    corner_vertex_data(2, 3, 0, 3),
 };
 
 GR_DECLARE_STATIC_UNIQUE_KEY(gHull4VertexBufferKey);
 
 static constexpr uint16_t kHull4IndicesAsStrips[] =  {
     1, 0, 2, 11, 3, 5, 4, kRestartStrip, // First half of the hull (split diagonally).
-    7, 6, 8, 5, 9, 11, 10 // Second half of the hull.
+    7, 6, 8, 5, 9, 11, 10, kRestartStrip, // Second half of the hull.
+    13, 12, 14, 15, kRestartStrip, // First corner.
+    17, 16, 18, 19 // Second corner.
 };
 
 static constexpr uint16_t kHull4IndicesAsTris[] =  {
@@ -282,6 +292,14 @@
     8,  5,  9,
     5, 11,  9,
     9, 11, 10,
+
+    // First corner.
+    13, 12, 14,
+    12, 15, 14,
+
+    // Second corner.
+    17, 16, 18,
+    16, 19, 18,
 };
 
 GR_DECLARE_STATIC_UNIQUE_KEY(gHull4IndexBufferKey);
@@ -309,13 +327,8 @@
 
     void emitVertexPosition(const GrCCCoverageProcessor& proc, GrGLSLVertexBuilder* v,
                             GrGPArgs* gpArgs, Coverages* outCoverages) const override {
-        Shader::GeometryVars vars;
-        fShader->emitSetupCode(v, "pts", nullptr, "wind", &vars);
-
-        const char* hullPts = vars.fHullVars.fAlternatePoints;
-        if (!hullPts) {
-            hullPts = "pts";
-        }
+        const char* hullPts = "pts";
+        fShader->emitSetupCode(v, "pts", "wind", &hullPts);
 
         // 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;",
@@ -350,29 +363,27 @@
 
         v->codeAppend ("float2 bloatdir = leftbloat;");
 
-        if (3 == fNumSides) { // Only triangles emit corner boxes.
-            v->codeAppend ("float2 leftdir = corner - left;");
-            v->codeAppend ("leftdir = (float2(0) != leftdir) ? normalize(leftdir) : float2(1, 0);");
+        v->codeAppend ("float2 leftdir = corner - left;");
+        v->codeAppend ("leftdir = (float2(0) != leftdir) ? normalize(leftdir) : float2(1, 0);");
 
-            v->codeAppend ("float2 rightdir = right - corner;");
-            v->codeAppend ("rightdir = (float2(0) != rightdir)"
-                                   "? normalize(rightdir) : float2(1, 0);");
+        v->codeAppend ("float2 rightdir = right - corner;");
+        v->codeAppend ("rightdir = (float2(0) != rightdir)"
+                               "? normalize(rightdir) : float2(1, 0);");
 
-            v->codeAppendf("if (0 != (%s & %i)) {", // Are we a corner?
-                           proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsCornerBit);
+        v->codeAppendf("if (0 != (%s & %i)) {", // Are we a corner?
+                       proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsCornerBit);
 
-                               // In corner boxes, all 4 coverage values will not map linearly.
-                               // Therefore it is important to align the box so its diagonal shared
-                               // edge points out of the triangle, in the direction that ramps to 0.
-            v->codeAppend (    "bloatdir = float2(leftdir.x > rightdir.x ? +1 : -1, "
-                                                 "leftdir.y > rightdir.y ? +1 : -1);");
+                           // In corner boxes, all 4 coverage values will not map linearly.
+                           // Therefore it is important to align the box so its diagonal shared
+                           // edge points out of the triangle, in the direction that ramps to 0.
+        v->codeAppend (    "bloatdir = float2(leftdir.x > rightdir.x ? +1 : -1, "
+                                             "leftdir.y > rightdir.y ? +1 : -1);");
 
-                               // For corner boxes, we hack left_right_notequal to always true. This
-                               // in turn causes the upcoming code to always rotate, generating all
-                               // 4 vertices of the corner box.
-            v->codeAppendf(    "left_right_notequal = bool2(true);");
-            v->codeAppend ("}");
-        }
+                           // For corner boxes, we hack left_right_notequal to always true. This
+                           // in turn causes the upcoming code to always rotate, generating all
+                           // 4 vertices of the corner box.
+        v->codeAppendf(    "left_right_notequal = bool2(true);");
+        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
@@ -382,12 +393,10 @@
         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 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.
@@ -403,48 +412,25 @@
         v->codeAppend ("float2 vertex = corner + bloatdir * bloat;");
         gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
 
-        // For triangles, we also emit coverage and attenuation.
+        v->codeAppend ("half left_coverage; {");
+        Shader::CalcEdgeCoverageAtBloatVertex(v, "left", "corner", "bloatdir", "left_coverage");
+        v->codeAppend ("}");
+
+        v->codeAppend ("half right_coverage; {");
+        Shader::CalcEdgeCoverageAtBloatVertex(v, "corner", "right", "bloatdir", "right_coverage");
+        v->codeAppend ("}");
+
+        v->codeAppend ("half attenuation; {");
+        Shader::CalcCornerCoverageAttenuation(v, "leftdir", "rightdir", "attenuation");
+        v->codeAppend ("}");
+
+        // Hulls have a coverage of +1 all around.
+        v->codeAppend ("half coverage = +1;");
+
         if (3 == fNumSides) {
-            // The hull has a coverage of +1 all around.
-            v->codeAppend ("half coverage = +1;");
-
-            // Corner boxes require attenuation.
-            v->codeAppend ("half2 attenuated_coverage = half2(0);");
-
-            v->codeAppendf("if (0 != (%s & %i)) {", // Are we an edge OR corner?
-                           proc.getAttrib(kAttribIdx_VertexData).fName,
-                           kVertexData_IsEdgeBit | kVertexData_IsCornerBit);
-            Shader::CalcEdgeCoverageAtBloatVertex(v, "left", "corner", "bloatdir", "coverage");
-            v->codeAppend ("}");
-
-            v->codeAppendf("if (0 != (%s & %i)) {", // Are we a corner?
-                           proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsCornerBit);
-            v->codeAppend (    "half left_coverage = coverage;");
-
-            v->codeAppend (    "half right_coverage;");
-            Shader::CalcEdgeCoverageAtBloatVertex(v, "corner", "right", "bloatdir",
-                                                  "right_coverage");
-
-            v->codeAppend (    "half attenuation;");
-            Shader::CalcCornerCoverageAttenuation(v, "leftdir", "rightdir", "attenuation");
-
-                               // For corners, "coverage" erases the values that were written
-                               // previously by the hull and edge geometry.
-            v->codeAppend (    "coverage = -1 - left_coverage - right_coverage;");
-
-                               // The x and y components of "attenuated_coverage" are multiplied
-                               // together by the fragment shader. They ramp to 0 with attenuation
-                               // in the diagonal that points out of the triangle, and linearly from
-                               // left-edge coverage to right in the opposite diagonal. bloatidx=0
-                               // is the outermost vertex; the one that has attenuation.
-            v->codeAppend (    "attenuated_coverage = (0 == bloatidx)"
-                                       "? half2(0, attenuation) : half2(1);");
-            v->codeAppend (    "if (1 == bloatidx || 2 == bloatidx) {");
-            v->codeAppend (        "attenuated_coverage.x += right_coverage;");
-            v->codeAppend (    "}");
-            v->codeAppend (    "if (bloatidx >= 2) {");
-            v->codeAppend (        "attenuated_coverage.x += left_coverage;");
-            v->codeAppend (    "}");
+            v->codeAppendf("if (0 != (%s & %i)) {", // Are we an edge?
+                           proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsEdgeBit);
+            v->codeAppend (    "coverage = left_coverage;");
             v->codeAppend ("}");
 
             v->codeAppendf("if (0 != (%s & %i)) {", // Invert coverage?
@@ -452,54 +438,45 @@
                            kVertexData_InvertNegativeCoverageBit);
             v->codeAppend (    "coverage = -1 - coverage;");
             v->codeAppend ("}");
-
-            outCoverages->fCoverage = "coverage";
-            outCoverages->fAttenuatedCoverage = "attenuated_coverage";
         }
+
+        // Corner boxes require attenuation.
+        v->codeAppend ("half2 attenuated_coverage = half2(0, 1);");
+
+        v->codeAppendf("if (0 != (%s & %i)) {", // Are we a corner?
+                       proc.getAttrib(kAttribIdx_VertexData).fName, kVertexData_IsCornerBit);
+                           // We use coverage=-1 to erase what the hull geometry wrote.
+        v->codeAppend (    "coverage = -1;");
+        if (3 == fNumSides) {
+                           // Triangle corners also have to erase what the edge geometry wrote.
+            v->codeAppend ("coverage -= left_coverage + right_coverage;");
+        }
+                           // The x and y components of "attenuated_coverage" are multiplied
+                           // together by the fragment shader. They ramp to 0 with attenuation in
+                           // the diagonal that points out of the corner, and linearly from
+                           // left-edge coverage to right in the opposite diagonal.
+                           // bloatidx=0 is the outermost vertex; the one that has attenuation.
+        v->codeAppend (    "attenuated_coverage = (0 == bloatidx)"
+                                   "? half2(0, attenuation) : half2(1);");
+        v->codeAppend (    "if (1 == bloatidx || 2 == bloatidx) {");
+        v->codeAppend (        "attenuated_coverage.x += right_coverage;");
+        v->codeAppend (    "}");
+        v->codeAppend (    "if (bloatidx >= 2) {");
+        v->codeAppend (        "attenuated_coverage.x += left_coverage;");
+        v->codeAppend (    "}");
+        v->codeAppend ("}");
+
+        v->codeAppend ("coverage *= wind;");
+        outCoverages->fCoverage = "coverage";
+
+        v->codeAppend ("attenuated_coverage.x *= wind;");
+        outCoverages->fAttenuatedCoverage = "attenuated_coverage";
     }
 
 private:
     const int fNumSides;
 };
 
-static constexpr uint16_t kCornerIndicesAsStrips[] =  {
-    0, 1, 2, 3, kRestartStrip, // First corner.
-    4, 5, 6, 7 // Second corner.
-};
-
-static constexpr uint16_t kCornerIndicesAsTris[] =  {
-    // First corner.
-    0,  1,  2,
-    1,  3,  2,
-
-    // Second corner.
-    4,  5,  6,
-    5,  7,  6,
-};
-
-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)) {}
-
-    void emitVertexPosition(const GrCCCoverageProcessor&, GrGLSLVertexBuilder* v, GrGPArgs* gpArgs,
-                            Coverages* /*outCoverages*/) 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");
-    }
-};
-
 void GrCCCoverageProcessor::initVS(GrResourceProvider* rp) {
     SkASSERT(Impl::kVertexShader == fImpl);
     const GrCaps& caps = *rp->caps();
@@ -528,9 +505,6 @@
             break;
         }
 
-        case RenderPass::kTriangleCorners:
-            SK_ABORT("RenderPass::kTriangleCorners is unused by VSImpl.");
-
         case RenderPass::kQuadratics:
         case RenderPass::kCubics: {
             GR_DEFINE_STATIC_UNIQUE_KEY(gHull4VertexBufferKey);
@@ -553,24 +527,10 @@
             break;
         }
 
+        case RenderPass::kTriangleCorners:
         case RenderPass::kQuadraticCorners:
-        case RenderPass::kCubicCorners: {
-            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;
-         }
+        case RenderPass::kCubicCorners:
+            SK_ABORT("Corners are not used by VSImpl.");
     }
 
     if (RenderPassIsCubic(fRenderPass) || WindMethod::kInstanceData == fWindMethod) {
@@ -597,12 +557,9 @@
         SkASSERT(sizeof(TriPointInstance) == this->getInstanceStride());
     }
 
-    if (fVertexBuffer) {
-        SkASSERT(kAttribIdx_VertexData == this->numAttribs());
-        this->addVertexAttrib("vertexdata", kInt_GrVertexAttribType);
-
-        SkASSERT(sizeof(int32_t) == this->getVertexStride());
-    }
+    SkASSERT(kAttribIdx_VertexData == this->numAttribs());
+    this->addVertexAttrib("vertexdata", kInt_GrVertexAttribType);
+    SkASSERT(sizeof(int32_t) == this->getVertexStride());
 
     if (caps.usePrimitiveRestart()) {
         this->setWillUsePrimitiveRestart();
@@ -618,9 +575,7 @@
     GrMesh& mesh = out->emplace_back(fPrimitiveType);
     mesh.setIndexedInstanced(fIndexBuffer.get(), fNumIndicesPerInstance, instanceBuffer,
                              instanceCount, baseInstance);
-    if (fVertexBuffer) {
-        mesh.setVertexData(fVertexBuffer.get(), 0);
-    }
+    mesh.setVertexData(fVertexBuffer.get(), 0);
 }
 
 GrGLSLPrimitiveProcessor* GrCCCoverageProcessor::createVSImpl(std::unique_ptr<Shader> shadr) const {
@@ -633,7 +588,7 @@
         case RenderPass::kTriangleCorners:
         case RenderPass::kQuadraticCorners:
         case RenderPass::kCubicCorners:
-            return new VSCornerImpl(std::move(shadr));
+            SK_ABORT("Corners are not used by VSImpl.");
     }
     SK_ABORT("Invalid RenderPass");
     return nullptr;
diff --git a/src/gpu/ccpr/GrCCCubicShader.cpp b/src/gpu/ccpr/GrCCCubicShader.cpp
index 4dc100b..edff5ff 100644
--- a/src/gpu/ccpr/GrCCCubicShader.cpp
+++ b/src/gpu/ccpr/GrCCCubicShader.cpp
@@ -13,8 +13,7 @@
 using Shader = GrCCCoverageProcessor::Shader;
 
 void GrCCCubicShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
-                                    const char* repetitionID, const char* wind,
-                                    GeometryVars* vars) const {
+                                    const char* wind, const char** /*tighterHull*/) const {
     // Find the cubic's power basis coefficients.
     s->codeAppendf("float2x4 C = float4x4(-1,  3, -3,  1, "
                                          " 3, -6,  3,  0, "
@@ -58,14 +57,12 @@
     // Evaluate the cubic at T=.5 for a mid-ish point.
     s->codeAppendf("float2 midpoint = %s * float4(.125, .375, .375, .125);", pts);
 
-    // Orient the KLM matrix so L & M have matching signs on the side of the curve we wish to fill.
-    // We give L & M both the same sign as wind, in order to pass this value to the fragment shader.
-    // (Cubics are pre-chopped such that L & M do not change sign within any individual segment).
+    // Orient the KLM matrix so L & M are both positive on the side of the curve we wish to fill.
     s->codeAppendf("float2 orientation = sign(float3(midpoint, 1) * float2x3(%s[1], %s[2]));",
                    fKLMMatrix.c_str(), fKLMMatrix.c_str());
     s->codeAppendf("%s *= float3x3(orientation[0] * orientation[1], 0, 0, "
-                                  "0, orientation[0] * %s, 0, "
-                                  "0, 0, orientation[1] * %s);", fKLMMatrix.c_str(), wind, wind);
+                                  "0, orientation[0], 0, "
+                                  "0, 0, orientation[1]);", fKLMMatrix.c_str());
 
     // Determine the amount of additional coverage to subtract out for the flat edge (P3 -> P0).
     s->declareGlobal(fEdgeDistanceEquation);
@@ -73,105 +70,54 @@
     s->codeAppendf("float2 edgept0 = %s[edgeidx0];", pts);
     s->codeAppendf("float2 edgept1 = %s[3 - edgeidx0];", pts);
     Shader::EmitEdgeDistanceEquation(s, "edgept0", "edgept1", fEdgeDistanceEquation.c_str());
-
-    this->onEmitSetupCode(s, pts, repetitionID, vars);
 }
 
 void GrCCCubicShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
                                      GrGLSLVarying::Scope scope, SkString* code,
                                      const char* position, const char* coverage,
-                                     const char* attenuatedCoverage, const char* /*wind*/) {
-    SkASSERT(!coverage);
-    SkASSERT(!attenuatedCoverage);
-
+                                     const char* attenuatedCoverage) {
     fKLMD.reset(kFloat4_GrSLType, scope);
     varyingHandler->addVarying("klmd", &fKLMD);
     code->appendf("float3 klm = float3(%s, 1) * %s;", position, fKLMMatrix.c_str());
-    code->appendf("float d = dot(float3(%s, 1), %s);", position, fEdgeDistanceEquation.c_str());
-    code->appendf("%s = float4(klm, d);", OutName(fKLMD));
+    // We give L & M both the same sign as wind, in order to pass this value to the fragment shader.
+    // (Cubics are pre-chopped such that L & M do not change sign within any individual segment.)
+    code->appendf("%s.xyz = klm * float3(1, %s, %s);",
+                  OutName(fKLMD), coverage, coverage); // coverage == wind on curves.
+    code->appendf("%s.w = dot(float3(%s, 1), %s);", // Flat edge opposite the curve.
+                  OutName(fKLMD), position, fEdgeDistanceEquation.c_str());
 
-    this->onEmitVaryings(varyingHandler, scope, code);
+    fGradMatrix.reset(kFloat2x2_GrSLType, scope);
+    varyingHandler->addVarying("grad_matrix", &fGradMatrix);
+    code->appendf("%s[0] = 2*bloat * 3 * klm[0] * %s[0].xy;",
+                  OutName(fGradMatrix), fKLMMatrix.c_str());
+    code->appendf("%s[1] = -2*bloat * (klm[1] * %s[2].xy + klm[2] * %s[1].xy);",
+                    OutName(fGradMatrix), fKLMMatrix.c_str(), fKLMMatrix.c_str());
+
+    if (attenuatedCoverage) {
+        fCornerCoverage.reset(kHalf2_GrSLType, scope);
+        varyingHandler->addVarying("corner_coverage", &fCornerCoverage);
+        code->appendf("%s = %s;", // Attenuated corner coverage.
+                      OutName(fCornerCoverage), attenuatedCoverage);
+    }
 }
 
 void GrCCCubicShader::onEmitFragmentCode(GrGLSLFPFragmentBuilder* f,
                                          const char* outputCoverage) const {
-    f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z, d = %s.w;",
-                   fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn());
+    f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z;", fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn());
+    f->codeAppend ("float f = k*k*k - l*m;");
+    f->codeAppendf("float2 grad_f = %s * float2(k, 1);", fGradMatrix.fsIn());
+    f->codeAppendf("%s = clamp(0.5 - f * inversesqrt(dot(grad_f, grad_f)), 0, 1);", outputCoverage);
 
-    this->emitCoverage(f, outputCoverage);
-
+    f->codeAppendf("half d = min(%s.w, 0);", fKLMD.fsIn()); // Flat edge opposite the curve.
     // Wind is the sign of both L and/or M. Take the sign of whichever has the larger magnitude.
     // (In reality, either would be fine because we chop cubics with more than a half pixel of
     // padding around the L & M lines, so neither should approach zero.)
     f->codeAppend ("half wind = sign(l + m);");
-    f->codeAppendf("%s *= wind;", outputCoverage);
-}
+    f->codeAppendf("%s = (%s + d) * wind;", outputCoverage, outputCoverage);
 
-void GrCCCubicHullShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
-                                         GrGLSLVarying::Scope scope, SkString* code) {
-    fGradMatrix.reset(kFloat2x2_GrSLType, scope);
-    varyingHandler->addVarying("grad_matrix", &fGradMatrix);
-    // "klm" was just defined by the base class.
-    code->appendf("%s[0] = 2*bloat * 3 * klm[0] * %s[0].xy;",
-                  OutName(fGradMatrix), fKLMMatrix.c_str());
-    code->appendf("%s[1] = -2*bloat * (klm[1] * %s[2].xy + klm[2] * %s[1].xy);",
-                  OutName(fGradMatrix), fKLMMatrix.c_str(), fKLMMatrix.c_str());
-}
-
-void GrCCCubicHullShader::emitCoverage(GrGLSLFPFragmentBuilder* f,
-                                       const char* outputCoverage) const {
-    // k,l,m,d are defined by the base class.
-    f->codeAppend ("float f = k*k*k - l*m;");
-    f->codeAppendf("float2 grad_f = %s * float2(k, 1);", fGradMatrix.fsIn());
-    f->codeAppendf("%s = clamp(0.5 - f * inversesqrt(dot(grad_f, grad_f)), 0, 1);", outputCoverage);
-    f->codeAppendf("%s += min(d, 0);", outputCoverage); // Flat edge opposite the curve.
-}
-
-void GrCCCubicCornerShader::onEmitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
-                                            const char* repetitionID, GeometryVars* vars) const {
-    s->codeAppendf("float2 corner = %s[%s * 3];", pts, repetitionID);
-    vars->fCornerVars.fPoint = "corner";
-}
-
-void GrCCCubicCornerShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
-                                           GrGLSLVarying::Scope scope, SkString* code) {
-    using Interpolation = GrGLSLVaryingHandler::Interpolation;
-
-    fdKLMDdx.reset(kFloat4_GrSLType, scope);
-    varyingHandler->addVarying("dklmddx", &fdKLMDdx, Interpolation::kCanBeFlat);
-    code->appendf("%s = 2*bloat * float4(%s[0].x, %s[1].x, %s[2].x, %s.x);",
-                  OutName(fdKLMDdx), fKLMMatrix.c_str(), fKLMMatrix.c_str(),
-                  fKLMMatrix.c_str(), fEdgeDistanceEquation.c_str());
-
-    fdKLMDdy.reset(kFloat4_GrSLType, scope);
-    varyingHandler->addVarying("dklmddy", &fdKLMDdy, Interpolation::kCanBeFlat);
-    code->appendf("%s = 2*bloat * float4(%s[0].y, %s[1].y, %s[2].y, %s.y);",
-                  OutName(fdKLMDdy), fKLMMatrix.c_str(), fKLMMatrix.c_str(),
-                  fKLMMatrix.c_str(), fEdgeDistanceEquation.c_str());
-}
-
-void GrCCCubicCornerShader::emitCoverage(GrGLSLFPFragmentBuilder* f,
-                                         const char* outputCoverage) const {
-    f->codeAppendf("float2x4 grad_klmd = float2x4(%s, %s);", fdKLMDdx.fsIn(), fdKLMDdy.fsIn());
-
-    // Erase what the previous hull shader wrote. We don't worry about the two corners falling on
-    // the same pixel because those cases should have been weeded out by this point.
-    // k,l,m,d are defined by the base class.
-    f->codeAppend ("float f = k*k*k - l*m;");
-    f->codeAppend ("float2 grad_f = float3(3*k*k, -m, -l) * float2x3(grad_klmd);");
-    f->codeAppendf("%s = -clamp(0.5 - f * inversesqrt(dot(grad_f, grad_f)), 0, 1);",
-                   outputCoverage);
-    f->codeAppendf("%s -= d;", outputCoverage);
-
-    // Use software msaa to estimate actual coverage at the corner pixels.
-    const int sampleCount = Shader::DefineSoftSampleLocations(f, "samples");
-    f->codeAppendf("float4 klmd_center = float4(%s.xyz, %s.w + 0.5);",
-                   fKLMD.fsIn(), fKLMD.fsIn());
-    f->codeAppendf("for (int i = 0; i < %i; ++i) {", sampleCount);
-    f->codeAppend (    "float4 klmd = grad_klmd * samples[i] + klmd_center;");
-    f->codeAppend (    "half f = klmd.y * klmd.z - klmd.x * klmd.x * klmd.x;");
-    f->codeAppendf(    "%s += all(greaterThan(half4(f, klmd.y, klmd.z, klmd.w), "
-                                             "half4(0))) ? %f : 0;",
-                       outputCoverage, 1.0 / sampleCount);
-    f->codeAppend ("}");
+    if (fCornerCoverage.fsIn()) {
+        f->codeAppendf("%s = %s.x * %s.y + %s;", // Attenuated corner coverage.
+                       outputCoverage, fCornerCoverage.fsIn(), fCornerCoverage.fsIn(),
+                       outputCoverage);
+    }
 }
diff --git a/src/gpu/ccpr/GrCCCubicShader.h b/src/gpu/ccpr/GrCCCubicShader.h
index dff7f6b..39ca55f 100644
--- a/src/gpu/ccpr/GrCCCubicShader.h
+++ b/src/gpu/ccpr/GrCCCubicShader.h
@@ -19,43 +19,23 @@
  *
  * The provided curve segments must be convex, monotonic with respect to the vector of their closing
  * edge [P3 - P0], and must not contain or be near any inflection points or loop intersections.
- * (Use GrCCGeometry.)
+ * (Use GrCCGeometry::cubicTo().)
  */
 class GrCCCubicShader : public GrCCCoverageProcessor::Shader {
-protected:
-    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
-                       const char* wind, GeometryVars*) const final;
-    virtual void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
-                                 GeometryVars*) const {}
+    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* wind,
+                       const char** tighterHull) const override;
 
     void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
-                        const char* position, const char* coverage, const char* attenuatedCoverage,
-                        const char* wind) final;
-    virtual void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) = 0;
+                        const char* position, const char* coverage,
+                        const char* attenuatedCoverage) override;
 
-    void onEmitFragmentCode(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const final;
-    virtual void emitCoverage(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const = 0;
+    void onEmitFragmentCode(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override;
 
-    GrShaderVar fKLMMatrix{"klm_matrix", kFloat3x3_GrSLType};
-    GrShaderVar fEdgeDistanceEquation{"edge_distance_equation", kFloat3_GrSLType};
+    const GrShaderVar fKLMMatrix{"klm_matrix", kFloat3x3_GrSLType};
+    const GrShaderVar fEdgeDistanceEquation{"edge_distance_equation", kFloat3_GrSLType};
     GrGLSLVarying fKLMD;
-};
-
-class GrCCCubicHullShader : public GrCCCubicShader {
-    void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) override;
-    void emitCoverage(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override;
-
     GrGLSLVarying fGradMatrix;
-};
-
-class GrCCCubicCornerShader : public GrCCCubicShader {
-    void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
-                         GeometryVars*) const override;
-    void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) override;
-    void emitCoverage(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override;
-
-    GrGLSLVarying fdKLMDdx;
-    GrGLSLVarying fdKLMDdy;
+    GrGLSLVarying fCornerCoverage;
 };
 
 #endif
diff --git a/src/gpu/ccpr/GrCCQuadraticShader.cpp b/src/gpu/ccpr/GrCCQuadraticShader.cpp
index 5bee85e..287e63e 100644
--- a/src/gpu/ccpr/GrCCQuadraticShader.cpp
+++ b/src/gpu/ccpr/GrCCQuadraticShader.cpp
@@ -14,8 +14,7 @@
 using Shader = GrCCCoverageProcessor::Shader;
 
 void GrCCQuadraticShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
-                                        const char* repetitionID, const char* wind,
-                                        GeometryVars* vars) const {
+                                        const char* wind, const char** tighterHull) const {
     s->declareGlobal(fQCoordMatrix);
     s->codeAppendf("%s = float2x2(1, 1, .5, 0) * inverse(float2x2(%s[2] - %s[0], %s[1] - %s[0]));",
                    fQCoordMatrix.c_str(), pts, pts, pts, pts);
@@ -28,113 +27,60 @@
     s->codeAppendf("float2 edgept1 = %s[%s > 0 ? 0 : 2];", pts, wind);
     Shader::EmitEdgeDistanceEquation(s, "edgept0", "edgept1", fEdgeDistanceEquation.c_str());
 
-    this->onEmitSetupCode(s, pts, repetitionID, vars);
+    if (tighterHull) {
+        // Find the T value whose tangent is halfway between the tangents at the endpionts.
+        s->codeAppendf("float2 tan0 = %s[1] - %s[0];", pts, pts);
+        s->codeAppendf("float2 tan1 = %s[2] - %s[1];", pts, pts);
+        s->codeAppend ("float2 midnorm = normalize(tan0) - normalize(tan1);");
+        s->codeAppend ("float2 T = midnorm * float2x2(tan0 - tan1, tan0);");
+        s->codeAppend ("float t = clamp(T.t / T.s, 0, 1);"); // T.s!=0; we cull flat curves on CPU.
+
+        // Clip the bezier triangle by the tangent at our new t value. This is a simple application
+        // for De Casteljau's algorithm.
+        s->codeAppendf("float4x2 quadratic_hull = float4x2(%s[0], "
+                                                          "%s[0] + tan0 * t, "
+                                                          "%s[1] + tan1 * t, "
+                                                          "%s[2]);", pts, pts, pts, pts);
+        *tighterHull = "quadratic_hull";
+    }
 }
 
 void GrCCQuadraticShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
                                          GrGLSLVarying::Scope scope, SkString* code,
                                          const char* position, const char* coverage,
-                                         const char* attenuatedCoverage, const char* wind) {
-    SkASSERT(!coverage);
-    SkASSERT(!attenuatedCoverage);
+                                         const char* attenuatedCoverage) {
+    fCoord.reset(kFloat4_GrSLType, scope);
+    varyingHandler->addVarying("coord", &fCoord);
+    code->appendf("%s.xy = %s * (%s - %s);", // Quadratic coords.
+                  OutName(fCoord), fQCoordMatrix.c_str(), position, fQCoord0.c_str());
+    code->appendf("%s.zw = 2*bloat * float2(2 * %s.x, -1) * %s;", // Gradient.
+                  OutName(fCoord), OutName(fCoord), fQCoordMatrix.c_str());
 
-    fXYDW.reset(kFloat4_GrSLType, scope);
-    varyingHandler->addVarying("xydw", &fXYDW);
-    code->appendf("%s.xy = %s * (%s - %s);",
-                  OutName(fXYDW), fQCoordMatrix.c_str(), position, fQCoord0.c_str());
-    code->appendf("%s.z = dot(%s.xy, %s) + %s.z;",
-                  OutName(fXYDW), fEdgeDistanceEquation.c_str(), position,
-                  fEdgeDistanceEquation.c_str());
-    code->appendf("%s.w = %s;", OutName(fXYDW), wind);
-
-    this->onEmitVaryings(varyingHandler, scope, code);
+    // Coverages need full precision since distance to the opposite edge can be large.
+    fCoverages.reset(attenuatedCoverage ? kFloat4_GrSLType : kFloat2_GrSLType, scope);
+    varyingHandler->addVarying("coverages", &fCoverages);
+    code->appendf("%s.x = dot(%s, float3(%s, 1));", // Distance to flat edge opposite the curve.
+                  OutName(fCoverages), fEdgeDistanceEquation.c_str(), position);
+    code->appendf("%s.y = %s;", OutName(fCoverages), coverage); // Wind.
+    if (attenuatedCoverage) {
+        code->appendf("%s.zw = %s;", // Attenuated corner coverage.
+                      OutName(fCoverages), attenuatedCoverage);
+    }
 }
 
 void GrCCQuadraticShader::onEmitFragmentCode(GrGLSLFPFragmentBuilder* f,
                                              const char* outputCoverage) const {
-    this->emitCoverage(f, outputCoverage);
-    f->codeAppendf("%s *= %s.w;", outputCoverage, fXYDW.fsIn()); // Sign by wind.
-}
-
-void GrCCQuadraticHullShader::onEmitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
-                                              const char* /*repetitionID*/,
-                                              GeometryVars* vars) const {
-    // Find the T value whose tangent is halfway between the tangents at the endpionts.
-    s->codeAppendf("float2 tan0 = %s[1] - %s[0];", pts, pts);
-    s->codeAppendf("float2 tan1 = %s[2] - %s[1];", pts, pts);
-    s->codeAppend ("float2 midnorm = normalize(tan0) - normalize(tan1);");
-    s->codeAppend ("float2 T = midnorm * float2x2(tan0 - tan1, tan0);");
-    s->codeAppend ("float t = clamp(T.t / T.s, 0, 1);"); // T.s != 0; we cull flat curves on CPU.
-
-    // Clip the bezier triangle by the tangent at our new t value. This is a simple application for
-    // De Casteljau's algorithm.
-    s->codeAppendf("float4x2 quadratic_hull = float4x2(%s[0], "
-                                                      "%s[0] + tan0 * t, "
-                                                      "%s[1] + tan1 * t, "
-                                                      "%s[2]);", pts, pts, pts, pts);
-    vars->fHullVars.fAlternatePoints = "quadratic_hull";
-}
-
-void GrCCQuadraticHullShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
-                                             GrGLSLVarying::Scope scope, SkString* code) {
-    fGrad.reset(kFloat2_GrSLType, scope);
-    varyingHandler->addVarying("grad", &fGrad);
-    code->appendf("%s = 2*bloat * float2(2 * %s.x, -1) * %s;",
-                  OutName(fGrad), OutName(fXYDW), fQCoordMatrix.c_str());
-}
-
-void GrCCQuadraticHullShader::emitCoverage(GrGLSLFPFragmentBuilder* f,
-                                           const char* outputCoverage) const {
-    f->codeAppendf("float d = (%s.x * %s.x - %s.y) * inversesqrt(dot(%s, %s));",
-                   fXYDW.fsIn(), fXYDW.fsIn(), fXYDW.fsIn(), fGrad.fsIn(), fGrad.fsIn());
-    f->codeAppendf("%s = clamp(0.5 - d, 0, 1);", outputCoverage);
-    f->codeAppendf("%s += min(%s.z, 0);", outputCoverage, fXYDW.fsIn()); // Flat closing edge.
-}
-
-void GrCCQuadraticCornerShader::onEmitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
-                                                const char* repetitionID,
-                                                GeometryVars* vars) const {
-    s->codeAppendf("float2 corner = %s[%s * 2];", pts, repetitionID);
-    vars->fCornerVars.fPoint = "corner";
-}
-
-void GrCCQuadraticCornerShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler,
-                                               GrGLSLVarying::Scope scope, SkString* code) {
-    using Interpolation = GrGLSLVaryingHandler::Interpolation;
-
-    fdXYDdx.reset(kFloat3_GrSLType, scope);
-    varyingHandler->addVarying("dXYDdx", &fdXYDdx, Interpolation::kCanBeFlat);
-    code->appendf("%s = 2*bloat * float3(%s[0].x, %s[0].y, %s.x);",
-                  OutName(fdXYDdx), fQCoordMatrix.c_str(), fQCoordMatrix.c_str(),
-                  fEdgeDistanceEquation.c_str());
-
-    fdXYDdy.reset(kFloat3_GrSLType, scope);
-    varyingHandler->addVarying("dXYDdy", &fdXYDdy, Interpolation::kCanBeFlat);
-    code->appendf("%s = 2*bloat * float3(%s[1].x, %s[1].y, %s.y);",
-                  OutName(fdXYDdy), fQCoordMatrix.c_str(), fQCoordMatrix.c_str(),
-                  fEdgeDistanceEquation.c_str());
-}
-
-void GrCCQuadraticCornerShader::emitCoverage(GrGLSLFPFragmentBuilder* f,
-                                             const char* outputCoverage) const {
-    f->codeAppendf("float x = %s.x, y = %s.y, d = %s.z;",
-                   fXYDW.fsIn(), fXYDW.fsIn(), fXYDW.fsIn());
-    f->codeAppendf("float2x3 grad_xyd = float2x3(%s, %s);", fdXYDdx.fsIn(), fdXYDdy.fsIn());
-
-    // Erase what the previous hull shader wrote. We don't worry about the two corners falling on
-    // the same pixel because those cases should have been weeded out by this point.
+    f->codeAppendf("float x = %s.x, y = %s.y;", fCoord.fsIn(), fCoord.fsIn());
     f->codeAppend ("float f = x*x - y;");
-    f->codeAppend ("float2 grad_f = float2(2*x, -1) * float2x2(grad_xyd);");
-    f->codeAppendf("%s = -(0.5 - f * inversesqrt(dot(grad_f, grad_f)));", outputCoverage);
-    f->codeAppendf("%s -= d;", outputCoverage);
+    f->codeAppendf("float2 grad = %s.zw;", fCoord.fsIn());
+    f->codeAppendf("%s = clamp(0.5 - f * inversesqrt(dot(grad, grad)), 0, 1);", outputCoverage);
 
-    // Use software msaa to approximate coverage at the corner pixels.
-    int sampleCount = Shader::DefineSoftSampleLocations(f, "samples");
-    f->codeAppendf("float3 xyd_center = float3(%s.xy, %s.z + 0.5);", fXYDW.fsIn(), fXYDW.fsIn());
-    f->codeAppendf("for (int i = 0; i < %i; ++i) {", sampleCount);
-    f->codeAppend (    "float3 xyd = grad_xyd * samples[i] + xyd_center;");
-    f->codeAppend (    "half f = xyd.y - xyd.x * xyd.x;"); // f > 0 -> inside curve.
-    f->codeAppendf(    "%s += all(greaterThan(float2(f,xyd.z), float2(0))) ? %f : 0;",
-                       outputCoverage, 1.0 / sampleCount);
-    f->codeAppendf("}");
+    f->codeAppendf("half d = min(%s.x, 0);", fCoverages.fsIn()); // Flat edge opposite the curve.
+    f->codeAppendf("half wind = %s.y;", fCoverages.fsIn());
+    f->codeAppendf("%s = (%s + d) * wind;", outputCoverage, outputCoverage);
+
+    if (kFloat4_GrSLType == fCoverages.type()) {
+        f->codeAppendf("%s = %s.z * %s.w + %s;", // Attenuated corner coverage.
+                       outputCoverage, fCoverages.fsIn(), fCoverages.fsIn(), outputCoverage);
+    }
 }
diff --git a/src/gpu/ccpr/GrCCQuadraticShader.h b/src/gpu/ccpr/GrCCQuadraticShader.h
index 8635ba4..30c5230 100644
--- a/src/gpu/ccpr/GrCCQuadraticShader.h
+++ b/src/gpu/ccpr/GrCCQuadraticShader.h
@@ -18,55 +18,24 @@
  * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf
  *
  * The provided curves must be monotonic with respect to the vector of their closing edge [P2 - P0].
- * (Use GrCCGeometry.)
+ * (Use GrCCGeometry::quadraticTo().)
  */
 class GrCCQuadraticShader : public GrCCCoverageProcessor::Shader {
 protected:
-    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
-                       const char* wind, GeometryVars*) const final;
-    virtual void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
-                                 GeometryVars*) const = 0;
+    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* wind,
+                       const char** tighterHull) const override;
 
     void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
-                        const char* position, const char* coverage, const char* attenuatedCoverage,
-                        const char* wind) final;
-    virtual void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) {}
+                        const char* position, const char* coverage,
+                        const char* attenuatedCoverage) override;
 
-    void onEmitFragmentCode(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const final;
-    virtual void emitCoverage(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const = 0;
+    void onEmitFragmentCode(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override;
 
     const GrShaderVar fQCoordMatrix{"qcoord_matrix", kFloat2x2_GrSLType};
     const GrShaderVar fQCoord0{"qcoord0", kFloat2_GrSLType};
     const GrShaderVar fEdgeDistanceEquation{"edge_distance_equation", kFloat3_GrSLType};
-    GrGLSLVarying fXYDW;
-};
-
-/**
- * This pass draws a conservative raster hull around the quadratic bezier curve, computes the
- * curve's coverage using the gradient-based AA technique outlined in the Loop/Blinn paper, and
- * uses simple distance-to-edge to subtract out coverage for the flat closing edge [P2 -> P0]. Since
- * the provided curves are monotonic, this will get every pixel right except the two corners.
- */
-class GrCCQuadraticHullShader : public GrCCQuadraticShader {
-    void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
-                         GeometryVars*) const override;
-    void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) override;
-    void emitCoverage(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override;
-
-    GrGLSLVarying fGrad;
-};
-
-/**
- * This pass fixes the corners of a closed quadratic segment with soft MSAA.
- */
-class GrCCQuadraticCornerShader : public GrCCQuadraticShader {
-    void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
-                         GeometryVars*) const override;
-    void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) override;
-    void emitCoverage(GrGLSLFPFragmentBuilder*, const char* outputCoverage) const override;
-
-    GrGLSLVarying fdXYDdx;
-    GrGLSLVarying fdXYDdy;
+    GrGLSLVarying fCoord;
+    GrGLSLVarying fCoverages;
 };
 
 #endif
diff --git a/src/gpu/ccpr/GrCCTriangleShader.h b/src/gpu/ccpr/GrCCTriangleShader.h
index 3dcb351..f9af13c 100644
--- a/src/gpu/ccpr/GrCCTriangleShader.h
+++ b/src/gpu/ccpr/GrCCTriangleShader.h
@@ -19,31 +19,28 @@
 class GrCCTriangleShader : public GrCCCoverageProcessor::Shader {
     void onEmitVaryings(GrGLSLVaryingHandler* varyingHandler, GrGLSLVarying::Scope scope,
                         SkString* code, const char* position, const char* coverage,
-                        const char* attenuatedCoverage, const char* wind) override {
+                        const char* attenuatedCoverage) override {
         if (!attenuatedCoverage) {
-            fCoverageTimesWind.reset(kHalf_GrSLType, scope);
-            varyingHandler->addVarying("coverage_times_wind", &fCoverageTimesWind);
-            code->appendf("%s = %s * %s;", OutName(fCoverageTimesWind), coverage, wind);
+            fCoverages.reset(kHalf_GrSLType, scope);
+            varyingHandler->addVarying("coverage", &fCoverages);
+            code->appendf("%s = %s;", OutName(fCoverages), coverage);
         } else {
-            fCoverageTimesWind.reset(kHalf3_GrSLType, scope);
-            varyingHandler->addVarying("coverage_times_wind", &fCoverageTimesWind);
-            code->appendf("%s = half3(%s, %s);",
-                          OutName(fCoverageTimesWind), attenuatedCoverage, coverage);
-            code->appendf("%s.yz *= %s;", OutName(fCoverageTimesWind), wind);
+            fCoverages.reset(kHalf3_GrSLType, scope);
+            varyingHandler->addVarying("coverages", &fCoverages);
+            code->appendf("%s = half3(%s, %s);", OutName(fCoverages), attenuatedCoverage, coverage);
         }
     }
 
     void onEmitFragmentCode(GrGLSLFPFragmentBuilder* f, const char* outputCoverage) const override {
-        if (kHalf_GrSLType == fCoverageTimesWind.type()) {
-            f->codeAppendf("%s = %s;", outputCoverage, fCoverageTimesWind.fsIn());
+        if (kHalf_GrSLType == fCoverages.type()) {
+            f->codeAppendf("%s = %s;", outputCoverage, fCoverages.fsIn());
         } else {
             f->codeAppendf("%s = %s.x * %s.y + %s.z;",
-                           outputCoverage, fCoverageTimesWind.fsIn(), fCoverageTimesWind.fsIn(),
-                           fCoverageTimesWind.fsIn());
+                           outputCoverage, fCoverages.fsIn(), fCoverages.fsIn(), fCoverages.fsIn());
         }
     }
 
-    GrGLSLVarying fCoverageTimesWind;
+    GrGLSLVarying fCoverages;
 };
 
 #endif