CCPR: Optimize Hull geometry

Conceptualizes hulls a as single entity, rather than a set of "wedges"
fanned around the midpoint. Optimizes GSImpl hulls to use less, better
triangles and not use the midpoint. This refactoring also paves the
way for VSImpl hulls.

Bug: skia:
Change-Id: I6a03c7b64811edfd1589f11a5d102e6ee56ea2d2
Reviewed-on: https://skia-review.googlesource.com/83962
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp b/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp
index c7b8039..68166c0 100644
--- a/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp
@@ -12,6 +12,7 @@
 #include "ccpr/GrCCPRQuadraticShader.h"
 #include "ccpr/GrCCPRTriangleShader.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
 
 void GrCCPRCoverageProcessor::Shader::emitVaryings(GrGLSLVaryingHandler* varyingHandler,
                                                    SkString* code, const char* position,
@@ -44,7 +45,7 @@
 #endif
 }
 
-void GrCCPRCoverageProcessor::Shader::EmitEdgeDistanceEquation(GrGLSLShaderBuilder* s,
+void GrCCPRCoverageProcessor::Shader::EmitEdgeDistanceEquation(GrGLSLVertexGeoBuilder* s,
                                                                const char* leftPt,
                                                                const char* rightPt,
                                                                const char* outputDistanceEquation) {
@@ -107,5 +108,5 @@
             shader = skstd::make_unique<GrCCPRCubicCornerShader>();
             break;
     }
-    return CreateGSImpl(std::move(shader));
+    return this->createGSImpl(std::move(shader));
 }
diff --git a/src/gpu/ccpr/GrCCPRCoverageProcessor.h b/src/gpu/ccpr/GrCCPRCoverageProcessor.h
index dfd2e24..6bc3fad 100644
--- a/src/gpu/ccpr/GrCCPRCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCPRCoverageProcessor.h
@@ -14,13 +14,13 @@
 #include "glsl/GrGLSLVarying.h"
 
 class GrGLSLPPFragmentBuilder;
-class GrGLSLShaderBuilder;
+class GrGLSLVertexGeoBuilder;
 class GrMesh;
 
 /**
- * This is the geometry processor for the simple convex primitive shapes (triangles and closed curve
- * segments) from which ccpr paths are composed. The output is a single-channel alpha value,
- * positive for clockwise shapes and negative for counter-clockwise, that indicates coverage.
+ * This is the geometry processor for the simple convex primitive shapes (triangles and closed,
+ * convex bezier curves) from which ccpr paths are composed. The output is a single-channel alpha
+ * value, positive for clockwise shapes and negative for counter-clockwise, that indicates coverage.
  *
  * The caller is responsible to execute all render passes for all applicable primitives into a
  * cleared, floating point, alpha-only render target using SkBlendMode::kPlus (see RenderPass
@@ -48,21 +48,36 @@
         void set(const SkPoint[4], float dx, float dy);
     };
 
-    // All primitive shapes (triangles and convex closed curve segments) require more than one
+    // All primitive shapes (triangles and closed, convex bezier curves) require more than one
     // render pass. Here we enumerate every render pass needed in order to produce a complete
     // coverage count mask. This is an exhaustive list of all ccpr coverage shaders.
+    //
+    // During a render pass, the "Impl" (currently only GSImpl) generates conservative geometry for
+    // rasterization, and the Shader decides the coverage value at each pixel.
     enum class RenderPass {
-        // Triangles.
+        // For a Hull, the Impl generates a "conservative raster hull" around the input points. This
+        // is the geometry that causes a pixel to be rasterized if it is touched anywhere by the
+        // input polygon. The initial coverage values sent to the Shader at each vertex are +1 all
+        // around. Logically, the conservative raster hull is equivalent to the convex hull of pixel
+        // size boxes centered on each input point.
         kTriangleHulls,
-        kTriangleEdges,
-        kTriangleCorners,
-
-        // Quadratics.
         kQuadraticHulls,
-        kQuadraticCorners,
-
-        // Cubics.
         kCubicHulls,
+
+        // For Edges, the Impl generates conservative rasters around every input edge (i.e. convex
+        // hulls of two pixel-size boxes centered on both of the edge's endpoints). The initial
+        // coverage values sent to the Shader at each vertex are -1 on the outside border of the
+        // edge geometry and 0 on the inside. This is the only geometry type that associates
+        // coverage values with the output vertices. Interpolated, these coverage values convert
+        // jagged conservative raster edges into a smooth antialiased edge.
+        kTriangleEdges,
+
+        // For Corners, the Impl Generates the conservative rasters of corner points (i.e.
+        // pixel-size boxes). It generates 3 corner boxes for triangles and 2 for curves. The Shader
+        // specifies which corners. The initial coverage values sent to the Shader at each pixel are
+        // +1 all around.
+        kTriangleCorners,
+        kQuadraticCorners,
         kCubicCorners
     };
     static bool RenderPassIsCubic(RenderPass);
@@ -100,46 +115,13 @@
     float debugBloat() const { SkASSERT(this->debugVisualizationsEnabled()); return fDebugBloat; }
 #endif
 
-    // This serves as the base class for each RenderPass's Shader. It indicates what type of
-    // geometry the Impl should generate and provides implementation-independent code to process the
-    // inputs and calculate coverage in the fragment Shader.
+    // The Shader provides code to calculate each pixel's coverage in a RenderPass. It also
+    // provides details about shape-specific geometry.
     class Shader {
     public:
-        using TexelBufferHandle = GrGLSLGeometryProcessor::TexelBufferHandle;
-
-        // This enum specifies the type of geometry that should be generated for a Shader instance.
-        // Subclasses are limited to three built-in types of geometry to choose from:
-        enum class GeometryType {
-            // Generates a conservative raster hull around the input points. This is the geometry
-            // that causes a pixel to be rasterized if it is touched anywhere by the input polygon.
-            // Coverage is +1 all around.
-            //
-            // Logically, the conservative raster hull is equivalent to the convex hull of pixel
-            // size boxes centered around each input point.
-            kHull,
-
-            // Generates the conservative rasters of the input edges (i.e. convex hull of two
-            // pixel-size boxes centered on both endpoints). Coverage is -1 on the outside border of
-            // the edge geometry and 0 on the inside. This is the only geometry type that associates
-            // coverage values with the output points. It effectively converts a jagged conservative
-            // raster edge into a smooth antialiased edge.
-            kEdges,
-
-            // Generates the conservative rasters of the corners specified by the geometry provider
-            // (i.e. pixel-size box centered on the corner point). Coverage is +1 all around.
-            kCorners
-        };
-
-        virtual GeometryType getGeometryType() const = 0;
-
-        // Returns the number of independent geometric segments to generate for the render pass
-        // (number of wedges for a hull, number of edges, or number of corners.)
-        virtual int getNumSegments() const = 0;
-
         union GeometryVars {
             struct {
                 const char* fAlternatePoints; // floatNx2 (if left null, will use input points).
-                const char* fAlternateMidpoint; // float2 (if left null, finds euclidean midpoint).
             } fHullVars;
 
             struct {
@@ -152,8 +134,12 @@
         // Called before generating geometry. Subclasses must fill out the applicable fields in
         // GeometryVars (if any), and may also use this opportunity to setup internal member
         // variables that will be needed during onEmitVaryings (e.g. transformation matrices).
-        virtual void emitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
-                                   const char* wind, GeometryVars*) const {}
+        //
+        // repetitionID is a 0-based index and indicates which edge or corner is being generated.
+        // It will be null when generating a hull.
+        virtual void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts,
+                                   const char* repetitionID, const char* wind,
+                                   GeometryVars*) const {}
 
         void emitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
                           const char* coverage, const char* wind);
@@ -164,16 +150,10 @@
         // Defines an equation ("dot(float3(pt, 1), distance_equation)") that is -1 on the outside
         // border of a conservative raster edge and 0 on the inside. 'leftPt' and 'rightPt' must be
         // ordered clockwise.
-        static void EmitEdgeDistanceEquation(GrGLSLShaderBuilder*, const char* leftPt,
+        static void EmitEdgeDistanceEquation(GrGLSLVertexGeoBuilder*, const char* leftPt,
                                              const char* rightPt,
                                              const char* outputDistanceEquation);
 
-        // Defines a global float2 array that contains MSAA sample locations as offsets from pixel
-        // center. Subclasses can use this for software multisampling.
-        //
-        // Returns the number of samples.
-        static int DefineSoftSampleLocations(GrGLSLPPFragmentBuilder* f, const char* samplesName);
-
         virtual ~Shader() {}
 
     protected:
@@ -188,7 +168,7 @@
         // Returns whether the subclass will handle wind modulation or if this base class should
         // take charge of multiplying the final coverage output by "wind".
         //
-        // NOTE: the coverage parameter is only relevant for edges (see comments in GeometryType).
+        // NOTE: the coverage parameter is only relevant for edges (see comments in RenderPass).
         // Otherwise it is +1 all around.
         virtual WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code,
                                             const char* position, const char* coverage,
@@ -199,6 +179,12 @@
         virtual void onEmitFragmentCode(GrGLSLPPFragmentBuilder*,
                                         const char* outputCoverage) const = 0;
 
+        // Defines a global float2 array that contains MSAA sample locations as offsets from pixel
+        // center. Subclasses can use this for software multisampling.
+        //
+        // Returns the number of samples.
+        static int DefineSoftSampleLocations(GrGLSLPPFragmentBuilder* f, const char* samplesName);
+
     private:
         GrGLSLVarying fWind{kHalf_GrSLType, GrGLSLVarying::Scope::kGeoToFrag};
     };
@@ -210,15 +196,13 @@
     // accidentally bleed into neighbor pixels.
     static constexpr float kAABloatRadius = 0.491111f;
 
+    // Number of bezier points for curves, or 3 for triangles.
+    int numInputPoints() const { return RenderPassIsCubic(fRenderPass) ? 4 : 3; }
+
     void initGS();
     void appendGSMesh(GrBuffer* instanceBuffer, int instanceCount, int baseInstance,
-                      SkTArray<GrMesh, true>* out);
-
-    int numInputPoints() const {
-        return RenderPassIsCubic(fRenderPass) ? 4 : 3;
-    }
-
-    static GrGLSLPrimitiveProcessor* CreateGSImpl(std::unique_ptr<Shader>);
+                      SkTArray<GrMesh, true>* out) const;
+    GrGLSLPrimitiveProcessor* createGSImpl(std::unique_ptr<Shader>) const;
 
     const RenderPass fRenderPass;
     SkDEBUGCODE(float fDebugBloat = 0;)
@@ -257,7 +241,7 @@
         case RenderPass::kCubicCorners:
             return true;
     }
-    SK_ABORT("Invalid GrCCPRCoverageProcessor::RenderPass");
+    SK_ABORT("Invalid RenderPass");
     return false;
 }
 
@@ -271,7 +255,7 @@
         case RenderPass::kCubicHulls: return "kCubicHulls";
         case RenderPass::kCubicCorners: return "kCubicCorners";
     }
-    SK_ABORT("Invalid GrCCPRCoverageProcessor::RenderPass");
+    SK_ABORT("Invalid RenderPass");
     return "";
 }
 
diff --git a/src/gpu/ccpr/GrCCPRCoverageProcessor_GSImpl.cpp b/src/gpu/ccpr/GrCCPRCoverageProcessor_GSImpl.cpp
index 60928b6..a832f62 100644
--- a/src/gpu/ccpr/GrCCPRCoverageProcessor_GSImpl.cpp
+++ b/src/gpu/ccpr/GrCCPRCoverageProcessor_GSImpl.cpp
@@ -10,6 +10,8 @@
 #include "GrMesh.h"
 #include "glsl/GrGLSLVertexGeoBuilder.h"
 
+using InputType = GrGLSLGeometryBuilder::InputType;
+using OutputType = GrGLSLGeometryBuilder::OutputType;
 using Shader = GrCCPRCoverageProcessor::Shader;
 
 /**
@@ -46,9 +48,6 @@
     void emitGeometryShader(const GrCCPRCoverageProcessor& proc,
                             GrGLSLVaryingHandler* varyingHandler, GrGLSLGeometryBuilder* g,
                             const char* rtAdjust) const {
-        using InputType = GrGLSLGeometryBuilder::InputType;
-        using OutputType = GrGLSLGeometryBuilder::OutputType;
-
         int numInputPoints = proc.numInputPoints();
         SkASSERT(3 == numInputPoints || 4 == numInputPoints);
 
@@ -83,16 +82,11 @@
 #endif
         g->defineConstant("bloat", bloat);
 
-        Shader::GeometryVars vars;
-        fShader->emitSetupCode(g, "pts", "sk_InvocationID", wind.c_str(), &vars);
-        int maxPoints = this->onEmitGeometryShader(g, wind, emitVertexFn.c_str(), vars);
-        g->configure(InputType::kLines, OutputType::kTriangleStrip, maxPoints,
-                     fShader->getNumSegments());
+        this->onEmitGeometryShader(g, wind, emitVertexFn.c_str());
     }
 
-    virtual int onEmitGeometryShader(GrGLSLGeometryBuilder*, const GrShaderVar& wind,
-                                     const char* emitVertexFn,
-                                     const Shader::GeometryVars&) const = 0;
+    virtual void onEmitGeometryShader(GrGLSLGeometryBuilder*, const GrShaderVar& wind,
+                                      const char* emitVertexFn) const = 0;
 
     virtual ~GSImpl() {}
 
@@ -101,100 +95,166 @@
     typedef GrGLSLGeometryProcessor INHERITED;
 };
 
-class GSHullImpl : public GrCCPRCoverageProcessor::GSImpl {
+/**
+ * Generates a conservative raster hull around a triangle. (See comments for RenderPass)
+ */
+class GSHull3Impl : public GrCCPRCoverageProcessor::GSImpl {
 public:
-    GSHullImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
+    GSHull3Impl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
 
-    int onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
-                             const char* emitVertexFn,
-                             const Shader::GeometryVars& vars) const override {
-        int numSides = fShader->getNumSegments();
-        SkASSERT(numSides >= 3);
+    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";
         }
 
-        const char* midpoint = vars.fHullVars.fAlternateMidpoint;
-        if (!midpoint) {
-            g->codeAppendf("float2 midpoint = %s * float%i(%f);", hullPts, numSides, 1.0/numSides);
-            midpoint = "midpoint";
-        }
+        // 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.
+        //
+        // NOTE: We generate the hull in 2 independent invocations, so each invocation designates
+        // the corner it will begin with as the top.
+        g->codeAppendf("int i = %s > 0 ? sk_InvocationID : 1 - sk_InvocationID;", wind.c_str());
+        g->codeAppendf("float2 top = %s[i];", hullPts);
+        g->codeAppendf("float2 left = %s[%s > 0 ? (1 - i) * 2 : i + 1];", hullPts, wind.c_str());
+        g->codeAppendf("float2 right = %s[%s > 0 ? i + 1 : (1 - i) * 2];", hullPts, wind.c_str());
 
-        g->codeAppendf("int previdx = (sk_InvocationID + %i) %% %i, "
-                           "nextidx = (sk_InvocationID + 1) %% %i;",
-                       numSides - 1, numSides, numSides);
+        // Determine how much to outset the conservative raster hull from each of the three edges.
+        g->codeAppend ("float2 leftbloat = float2(top.y > left.y ? +bloat : -bloat, "
+                                                 "top.x > left.x ? -bloat : +bloat);");
+        g->codeAppend ("float2 rightbloat = float2(right.y > top.y ? +bloat : -bloat, "
+                                                  "right.x > top.x ? -bloat : +bloat);");
+        g->codeAppend ("float2 downbloat = float2(left.y > right.y ? +bloat : -bloat, "
+                                                 "left.x > right.x ? -bloat : +bloat);");
 
-        g->codeAppendf("float2 self = %s[sk_InvocationID];"
-                       "int leftidx = %s > 0 ? previdx : nextidx;"
-                       "int rightidx = %s > 0 ? nextidx : previdx;",
-                       hullPts, wind.c_str(), wind.c_str());
-
-        // Which quadrant does the vector from self -> right fall into?
-        g->codeAppendf("float2 right = %s[rightidx];", hullPts);
-        if (3 == numSides) {
-            // TODO: evaluate perf gains.
-            g->codeAppend ("float2 qsr = sign(right - self);");
-        } else {
-            SkASSERT(4 == numSides);
-            g->codeAppendf("float2 diag = %s[(sk_InvocationID + 2) %% 4];", hullPts);
-            g->codeAppend ("float2 qsr = sign((right != self ? right : diag) - self);");
-        }
-
-        // Which quadrant does the vector from left -> self fall into?
-        g->codeAppendf("float2 qls = sign(self - %s[leftidx]);", hullPts);
-
-        // d2 just helps us reduce triangle counts with orthogonal, axis-aligned lines.
-        // TODO: evaluate perf gains.
-        const char* dr2 = "dr";
-        if (3 == numSides) {
-            // TODO: evaluate perf gains.
-            g->codeAppend ("float2 dr = float2(qsr.y != 0 ? +qsr.y : +qsr.x, "
-                                              "qsr.x != 0 ? -qsr.x : +qsr.y);");
-            g->codeAppend ("float2 dr2 = float2(qsr.y != 0 ? +qsr.y : -qsr.x, "
-                                               "qsr.x != 0 ? -qsr.x : -qsr.y);");
-            g->codeAppend ("float2 dl = float2(qls.y != 0 ? +qls.y : +qls.x, "
-                                              "qls.x != 0 ? -qls.x : +qls.y);");
-            dr2 = "dr2";
-        } else {
-            g->codeAppend ("float2 dr = float2(qsr.y != 0 ? +qsr.y : 1, "
-                                              "qsr.x != 0 ? -qsr.x : 1);");
-            g->codeAppend ("float2 dl = (qls == float2(0)) ? dr : "
-                                       "float2(qls.y != 0 ? +qls.y : 1, qls.x != 0 ? -qls.x : 1);");
-        }
-        g->codeAppendf("bool2 dnotequal = notEqual(%s, dl);", dr2);
-
-        // Emit one third of what is the convex hull of pixel-size boxes centered on the vertices.
-        // Each invocation emits a different third.
-        g->codeAppendf("%s(right + bloat * dr, 1);", emitVertexFn);
-        g->codeAppendf("%s(%s, 1);", emitVertexFn, midpoint);
-        g->codeAppendf("%s(self + bloat * %s, 1);", emitVertexFn, dr2);
-        g->codeAppend ("if (any(dnotequal)) {");
-        g->codeAppendf(    "%s(self + bloat * dl, 1);", emitVertexFn);
+        // Here we generate the conservative raster geometry. It is the convex hull of 3 pixel-size
+        // boxes centered on the input points, split between two invocations. This translates to a
+        // polygon with either one, two, or three vertices at each input point, depending on how
+        // sharp the corner is. For more details on conservative raster, see:
+        // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
+        g->codeAppendf("bool2 left_right_notequal = notEqual(leftbloat, rightbloat);");
+        g->codeAppend ("if (all(left_right_notequal)) {");
+                           // The top corner will have three conservative raster vertices. Emit the
+                           // middle one first to the triangle strip.
+        g->codeAppendf(    "%s(top + float2(-leftbloat.y, leftbloat.x), 1);", emitVertexFn);
         g->codeAppend ("}");
-        g->codeAppend ("if (all(dnotequal)) {");
-        g->codeAppendf(    "%s(self + bloat * float2(-dl.y, dl.x), 1);", emitVertexFn);
+        g->codeAppend ("if (any(left_right_notequal)) {");
+                           // Second conservative raster vertex for the top corner.
+        g->codeAppendf(    "%s(top + rightbloat, 1);", emitVertexFn);
         g->codeAppend ("}");
-        g->endPrimitive();
 
-        return 5;
+        // Main interior body of the triangle.
+        g->codeAppendf("%s(top + leftbloat, 1);", emitVertexFn);
+        g->codeAppendf("%s(right + rightbloat, 1);", emitVertexFn);
+
+        // Here the two invocations diverge. We can't symmetrically divide three triangle points
+        // between two invocations, so each does the following:
+        //
+        // sk_InvocationID=0: Finishes the main interior body of the triangle.
+        // sk_InvocationID=1: Remaining two conservative raster vertices for the third corner.
+        g->codeAppendf("bool2 right_down_notequal = notEqual(rightbloat, downbloat);");
+        g->codeAppend ("if (any(right_down_notequal) || 0 == sk_InvocationID) {");
+        g->codeAppendf(    "%s(sk_InvocationID == 0 ? left + leftbloat : right + downbloat, 1);",
+                           emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("if (all(right_down_notequal) && 0 != sk_InvocationID) {");
+        g->codeAppendf(    "%s(right + float2(-rightbloat.y, rightbloat.x), 1);", emitVertexFn);
+        g->codeAppend ("}");
+
+        g->configure(InputType::kLines, OutputType::kTriangleStrip, 6, 2);
     }
 };
 
+/**
+ * Generates a conservative raster hull around a convex quadrilateral. (See comments for RenderPass)
+ */
+class GSHull4Impl : public GrCCPRCoverageProcessor::GSImpl {
+public:
+    GSHull4Impl(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";
+        }
+
+        // Visualize the input (convex) quadrilateral as a square. Paying special attention to wind,
+        // we can identify the points by their corresponding corner.
+        //
+        // NOTE: We split the square down the diagonal from top-right to bottom-left, and generate
+        // the hull in two independent invocations. Each invocation designates the corner it will
+        // begin with as top-left.
+        g->codeAppend ("int i = sk_InvocationID * 2;");
+        g->codeAppendf("float2 topleft = %s[i];", hullPts);
+        g->codeAppendf("float2 topright = %s[%s > 0 ? i + 1 : 3 - i];", hullPts, wind.c_str());
+        g->codeAppendf("float2 bottomleft = %s[%s > 0 ? 3 - i : i + 1];", hullPts, wind.c_str());
+        g->codeAppendf("float2 bottomright = %s[2 - i];", hullPts);
+
+        // Determine how much to outset the conservative raster hull from the relevant edges.
+        g->codeAppend ("float2 leftbloat = float2(topleft.y > bottomleft.y ? +bloat : -bloat, "
+                                                 "topleft.x > bottomleft.x ? -bloat : bloat);");
+        g->codeAppend ("float2 upbloat = float2(topright.y > topleft.y ? +bloat : -bloat, "
+                                               "topright.x > topleft.x ? -bloat : +bloat);");
+        g->codeAppend ("float2 rightbloat = float2(bottomright.y > topright.y ? +bloat : -bloat, "
+                                                  "bottomright.x > topright.x ? -bloat : +bloat);");
+
+        // Here we generate the conservative raster geometry. It is the convex hull of 4 pixel-size
+        // boxes centered on the input points, split evenly between two invocations. This translates
+        // to a polygon with either one, two, or three vertices at each input point, depending on
+        // how sharp the corner is. For more details on conservative raster, see:
+        // https://developer.nvidia.com/gpugems/GPUGems2/gpugems2_chapter42.html
+        g->codeAppendf("bool2 left_up_notequal = notEqual(leftbloat, upbloat);");
+        g->codeAppend ("if (all(left_up_notequal)) {");
+                           // The top-left corner will have three conservative raster vertices.
+                           // Emit the middle one first to the triangle strip.
+        g->codeAppendf(    "%s(topleft + float2(-leftbloat.y, leftbloat.x), 1);",
+                           emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("if (any(left_up_notequal)) {");
+                           // Second conservative raster vertex for the top-left corner.
+        g->codeAppendf(    "%s(topleft + leftbloat, 1);", emitVertexFn);
+        g->codeAppend ("}");
+
+        // Main interior body of this invocation's half of the hull.
+        g->codeAppendf("%s(topleft + upbloat, 1);", emitVertexFn);
+        g->codeAppendf("%s(bottomleft + leftbloat, 1);", emitVertexFn);
+        g->codeAppendf("%s(topright + upbloat, 1);", emitVertexFn);
+
+        // Remaining two conservative raster vertices for the top-right corner.
+        g->codeAppendf("bool2 up_right_notequal = notEqual(upbloat, rightbloat);");
+        g->codeAppend ("if (any(up_right_notequal)) {");
+        g->codeAppendf(    "%s(topright + rightbloat, 1);", emitVertexFn);
+        g->codeAppend ("}");
+        g->codeAppend ("if (all(up_right_notequal)) {");
+        g->codeAppendf(    "%s(topright + float2(-upbloat.y, upbloat.x), 1);",
+                           emitVertexFn);
+        g->codeAppend ("}");
+
+        g->configure(InputType::kLines, OutputType::kTriangleStrip, 7, 2);
+    }
+};
+
+/**
+ * Generates conservatives around each edge of a triangle. (See comments for RenderPass)
+ */
 class GSEdgeImpl : public GrCCPRCoverageProcessor::GSImpl {
 public:
     GSEdgeImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
 
-    int onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
-                             const char* emitVertexFn,
-                             const Shader::GeometryVars&) const override {
-        int numSides = fShader->getNumSegments();
+    void onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
+                              const char* emitVertexFn) const override {
+        fShader->emitSetupCode(g, "pts", "sk_InvocationID", wind.c_str(), nullptr);
 
-        g->codeAppendf("int nextidx = (sk_InvocationID + 1) %% %i;", numSides);
-        g->codeAppendf("float2 left = pts[%s > 0 ? sk_InvocationID : nextidx], "
-                                     "right = pts[%s > 0 ? nextidx : sk_InvocationID];",
-                                     wind.c_str(), wind.c_str());
+        g->codeAppend ("int nextidx = 2 != sk_InvocationID ? sk_InvocationID + 1 : 0;");
+        g->codeAppendf("float2 left = pts[%s > 0 ? sk_InvocationID : nextidx];", wind.c_str());
+        g->codeAppendf("float2 right = pts[%s > 0 ? nextidx : sk_InvocationID];", wind.c_str());
 
         Shader::EmitEdgeDistanceEquation(g, "left", "right", "float3 edge_distance_equation");
 
@@ -225,19 +285,24 @@
         g->codeAppend ("if (!aligned) {");
         g->codeAppendf(    "%s(outer_pts[1], outer_coverage[1]);", emitVertexFn);
         g->codeAppend ("}");
-        g->endPrimitive();
 
-        return 6;
+        g->configure(InputType::kLines, OutputType::kTriangleStrip, 6, 3);
     }
 };
 
+/**
+ * Generates conservatives around corners. (See comments for RenderPass)
+ */
 class GSCornerImpl : public GrCCPRCoverageProcessor::GSImpl {
 public:
-    GSCornerImpl(std::unique_ptr<Shader> shader) : GSImpl(std::move(shader)) {}
+    GSCornerImpl(std::unique_ptr<Shader> shader, int numCorners)
+            : GSImpl(std::move(shader)), fNumCorners(numCorners) {}
 
-    int onEmitGeometryShader(GrGLSLGeometryBuilder* g, const GrShaderVar& wind,
-                             const char* emitVertexFn,
-                             const Shader::GeometryVars& vars) const override {
+    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);
 
@@ -245,10 +310,12 @@
         g->codeAppendf("%s(%s + float2(-bloat, +bloat), 1);", emitVertexFn, corner);
         g->codeAppendf("%s(%s + float2(+bloat, -bloat), 1);", emitVertexFn, corner);
         g->codeAppendf("%s(%s + float2(+bloat, +bloat), 1);", emitVertexFn, corner);
-        g->endPrimitive();
 
-        return 4;
+        g->configure(InputType::kLines, OutputType::kTriangleStrip, 4, fNumCorners);
     }
+
+private:
+    const int fNumCorners;
 };
 
 void GrCCPRCoverageProcessor::initGS() {
@@ -262,21 +329,8 @@
     this->setWillUseGeoShader();
 }
 
-GrGLSLPrimitiveProcessor* GrCCPRCoverageProcessor::CreateGSImpl(std::unique_ptr<Shader> shader) {
-    switch (shader->getGeometryType()) {
-        case Shader::GeometryType::kHull:
-            return new GSHullImpl(std::move(shader));
-        case Shader::GeometryType::kEdges:
-            return new GSEdgeImpl(std::move(shader));
-        case Shader::GeometryType::kCorners:
-            return new GSCornerImpl(std::move(shader));
-    }
-    SK_ABORT("Unexpected Shader::GeometryType.");
-    return nullptr;
-}
-
 void GrCCPRCoverageProcessor::appendGSMesh(GrBuffer* instanceBuffer, int instanceCount,
-                                           int baseInstance, SkTArray<GrMesh, true>* out) {
+                                           int baseInstance, SkTArray<GrMesh, true>* out) const {
     // GSImpl doesn't actually make instanced draw calls. Instead, we feed transposed x,y point
     // values to the GPU in a regular vertex array and draw kLines (see initGS). Then, each vertex
     // invocation receives either the shape's x or y values as inputs, which it forwards to the
@@ -285,3 +339,23 @@
     mesh.setNonIndexedNonInstanced(instanceCount * 2);
     mesh.setVertexData(instanceBuffer, baseInstance * 2);
 }
+
+GrGLSLPrimitiveProcessor*
+GrCCPRCoverageProcessor::createGSImpl(std::unique_ptr<Shader> shader) const {
+    switch (fRenderPass) {
+        case RenderPass::kTriangleHulls:
+            return new GSHull3Impl(std::move(shader));
+        case RenderPass::kQuadraticHulls:
+        case RenderPass::kCubicHulls:
+            return new GSHull4Impl(std::move(shader));
+        case RenderPass::kTriangleEdges:
+            return new GSEdgeImpl(std::move(shader));
+        case RenderPass::kTriangleCorners:
+            return new GSCornerImpl(std::move(shader), 3);
+        case RenderPass::kQuadraticCorners:
+        case RenderPass::kCubicCorners:
+            return new GSCornerImpl(std::move(shader), 2);
+    }
+    SK_ABORT("Invalid RenderPass");
+    return nullptr;
+}
diff --git a/src/gpu/ccpr/GrCCPRCubicShader.cpp b/src/gpu/ccpr/GrCCPRCubicShader.cpp
index 4d63e98..0d34d37 100644
--- a/src/gpu/ccpr/GrCCPRCubicShader.cpp
+++ b/src/gpu/ccpr/GrCCPRCubicShader.cpp
@@ -8,13 +8,11 @@
 #include "GrCCPRCubicShader.h"
 
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
 
-void GrCCPRCubicShader::emitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
-                                      const char* segmentId, const char* wind,
+void GrCCPRCubicShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
+                                      const char* repetitionID, const char* wind,
                                       GeometryVars* vars) const {
-    // Evaluate the cubic at T=.5 for an mid-ish point.
-    s->codeAppendf("float2 midpoint = %s * float4(.125, .375, .375, .125);", pts);
-
     // Find the cubic's power basis coefficients.
     s->codeAppendf("float2x4 C = float4x4(-1,  3, -3,  1, "
                                          " 3, -6,  3,  0, "
@@ -55,6 +53,9 @@
                                       "L[0], L[middlerow], L[3], "
                                       "M[0], M[middlerow], M[3]);", fKLMMatrix.c_str());
 
+    // 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 we fill the correct side of the curve.
     s->codeAppendf("float2 orientation = sign(float3(midpoint, 1) * float2x3(%s[1], %s[2]));",
                    fKLMMatrix.c_str(), fKLMMatrix.c_str());
@@ -69,7 +70,7 @@
     s->codeAppendf("float2 edgept1 = %s[3 - edgeidx0];", pts);
     Shader::EmitEdgeDistanceEquation(s, "edgept0", "edgept1", fEdgeDistanceEquation.c_str());
 
-    this->onEmitSetupCode(s, pts, segmentId, vars);
+    this->onEmitSetupCode(s, pts, repetitionID, vars);
 }
 
 GrCCPRCubicShader::WindHandling
@@ -85,12 +86,6 @@
     return WindHandling::kNotHandled;
 }
 
-void GrCCPRCubicHullShader::onEmitSetupCode(GrGLSLShaderBuilder* s, const char* /*pts*/,
-                                            const char* /*wedgeId*/, GeometryVars* vars) const {
-    // "midpoint" was just defined by the base class.
-    vars->fHullVars.fAlternateMidpoint = "midpoint";
-}
-
 void GrCCPRCubicHullShader::onEmitVaryings(GrGLSLVaryingHandler* varyingHandler, SkString* code) {
     // "klm" was just defined by the base class.
     varyingHandler->addVarying("grad_matrix", &fGradMatrix);
@@ -109,9 +104,9 @@
     f->codeAppendf("%s += min(d, 0);", outputCoverage); // Flat closing edge.
 }
 
-void GrCCPRCubicCornerShader::onEmitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
-                                              const char* cornerId, GeometryVars* vars) const {
-    s->codeAppendf("float2 corner = %s[%s * 3];", pts, cornerId);
+void GrCCPRCubicCornerShader::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";
 }
 
diff --git a/src/gpu/ccpr/GrCCPRCubicShader.h b/src/gpu/ccpr/GrCCPRCubicShader.h
index be7635e..10f4dff 100644
--- a/src/gpu/ccpr/GrCCPRCubicShader.h
+++ b/src/gpu/ccpr/GrCCPRCubicShader.h
@@ -23,11 +23,11 @@
  */
 class GrCCPRCubicShader : public GrCCPRCoverageProcessor::Shader {
 protected:
-    void emitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
+    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                        const char* wind, GeometryVars*) const final;
 
-    virtual void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
-                                 GeometryVars*) const = 0;
+    virtual void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
+                                 GeometryVars*) const {}
 
     WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
                                 const char* coverage, const char* wind) final;
@@ -40,10 +40,6 @@
 };
 
 class GrCCPRCubicHullShader : public GrCCPRCubicShader {
-    GeometryType getGeometryType() const override { return GeometryType::kHull; }
-    int getNumSegments() const override { return 4; } // 4 wedges.
-    void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* wedgeId,
-                         GeometryVars*) const override;
     void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) override;
     void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
 
@@ -51,9 +47,7 @@
 };
 
 class GrCCPRCubicCornerShader : public GrCCPRCubicShader {
-    GeometryType getGeometryType() const override { return GeometryType::kCorners; }
-    int getNumSegments() const override { return 2; } // 2 corners.
-    void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* cornerId,
+    void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                          GeometryVars*) const override;
     void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) override;
     void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
diff --git a/src/gpu/ccpr/GrCCPRQuadraticShader.cpp b/src/gpu/ccpr/GrCCPRQuadraticShader.cpp
index 01527d0..bccba21 100644
--- a/src/gpu/ccpr/GrCCPRQuadraticShader.cpp
+++ b/src/gpu/ccpr/GrCCPRQuadraticShader.cpp
@@ -8,9 +8,10 @@
 #include "GrCCPRQuadraticShader.h"
 
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
 
-void GrCCPRQuadraticShader::emitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
-                                          const char* segmentId, const char* wind,
+void GrCCPRQuadraticShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
+                                          const char* repetitionID, const char* wind,
                                           GeometryVars* vars) const {
     s->declareGlobal(fCanonicalMatrix);
     s->codeAppendf("%s = float3x3(0.0, 0, 1, "
@@ -26,7 +27,7 @@
     s->codeAppendf("float2 edgept1 = %s[%s > 0 ? 0 : 2];", pts, wind);
     Shader::EmitEdgeDistanceEquation(s, "edgept0", "edgept1", fEdgeDistanceEquation.c_str());
 
-    this->onEmitSetupCode(s, pts, segmentId, vars);
+    this->onEmitSetupCode(s, pts, repetitionID, vars);
 }
 
 GrCCPRQuadraticShader::WindHandling
@@ -44,8 +45,9 @@
     return WindHandling::kNotHandled;
 }
 
-void GrCCPRQuadraticHullShader::onEmitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
-                                                const char* /*wedgeId*/, GeometryVars* vars) const {
+void GrCCPRQuadraticHullShader::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);
@@ -77,9 +79,10 @@
     f->codeAppendf("%s += min(%s.z, 0);", outputCoverage, fXYD.fsIn()); // Flat closing edge.
 }
 
-void GrCCPRQuadraticCornerShader::onEmitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
-                                                  const char* cornerId, GeometryVars* vars) const {
-    s->codeAppendf("float2 corner = %s[%s * 2];", pts, cornerId);
+void GrCCPRQuadraticCornerShader::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";
 }
 
diff --git a/src/gpu/ccpr/GrCCPRQuadraticShader.h b/src/gpu/ccpr/GrCCPRQuadraticShader.h
index 5d26a39..14505c6 100644
--- a/src/gpu/ccpr/GrCCPRQuadraticShader.h
+++ b/src/gpu/ccpr/GrCCPRQuadraticShader.h
@@ -22,10 +22,10 @@
  */
 class GrCCPRQuadraticShader : public GrCCPRCoverageProcessor::Shader {
 protected:
-    void emitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
+    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                        const char* wind, GeometryVars*) const final;
 
-    virtual void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* segmentId,
+    virtual void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                                  GeometryVars*) const = 0;
 
     WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
@@ -45,10 +45,7 @@
  * the provided curves are monotonic, this will get every pixel right except the two corners.
  */
 class GrCCPRQuadraticHullShader : public GrCCPRQuadraticShader {
-    int getNumSegments() const final { return 4; } // 4 wedges.
-
-    GeometryType getGeometryType() const override { return GeometryType::kHull; }
-    void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* wedgeId,
+    void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                          GeometryVars*) const override;
     void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) override;
     void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
@@ -60,10 +57,7 @@
  * This pass fixes the corners of a closed quadratic segment with soft MSAA.
  */
 class GrCCPRQuadraticCornerShader : public GrCCPRQuadraticShader {
-    int getNumSegments() const final { return 2; } // 2 corners.
-
-    GeometryType getGeometryType() const override { return GeometryType::kCorners; }
-    void onEmitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* cornerId,
+    void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                          GeometryVars*) const override;
     void onEmitVaryings(GrGLSLVaryingHandler*, SkString* code) override;
     void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
diff --git a/src/gpu/ccpr/GrCCPRTriangleShader.cpp b/src/gpu/ccpr/GrCCPRTriangleShader.cpp
index 836be31..1692b83 100644
--- a/src/gpu/ccpr/GrCCPRTriangleShader.cpp
+++ b/src/gpu/ccpr/GrCCPRTriangleShader.cpp
@@ -36,15 +36,16 @@
     f->codeAppendf("%s = %s;", outputCoverage, fCoverageTimesWind.fsIn());
 }
 
-void GrCCPRTriangleCornerShader::emitSetupCode(GrGLSLShaderBuilder* s, const char* pts,
-                                               const char* cornerId, const char* wind,
+void GrCCPRTriangleCornerShader::emitSetupCode(GrGLSLVertexGeoBuilder* s, const char* pts,
+                                               const char* repetitionID, const char* wind,
                                                GeometryVars* vars) const {
-    s->codeAppendf("float2 corner = %s[sk_InvocationID];", pts);
+    s->codeAppendf("float2 corner = %s[%s];", pts, repetitionID);
     vars->fCornerVars.fPoint = "corner";
 
-    s->codeAppendf("float2x2 vectors = float2x2(corner - %s[(sk_InvocationID + 2) %% 3], "
-                                               "corner - %s[(sk_InvocationID + 1) %% 3]);",
-                                               pts, pts);
+    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.
diff --git a/src/gpu/ccpr/GrCCPRTriangleShader.h b/src/gpu/ccpr/GrCCPRTriangleShader.h
index 6d0e2a2..4f7228a 100644
--- a/src/gpu/ccpr/GrCCPRTriangleShader.h
+++ b/src/gpu/ccpr/GrCCPRTriangleShader.h
@@ -16,9 +16,6 @@
  *         convex hull of those boxes.)
  */
 class GrCCPRTriangleHullShader : public GrCCPRCoverageProcessor::Shader {
-    GeometryType getGeometryType() const override { return GeometryType::kHull; }
-    int getNumSegments() const final { return 3; }
-
     WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
                                 const char* coverage, const char* wind) override;
     void onEmitFragmentCode(GrGLSLPPFragmentBuilder* f, const char* outputCoverage) const override;
@@ -30,9 +27,6 @@
  *         coverage=-1 on the outside edge to coverage=0 on the inside edge.
  */
 class GrCCPRTriangleEdgeShader : public GrCCPRCoverageProcessor::Shader {
-    GeometryType getGeometryType() const override { return GeometryType::kEdges; }
-    int getNumSegments() const final { return 3; }
-
     WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
                                 const char* coverage, const char* wind) override;
     void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
@@ -46,10 +40,7 @@
  *         the same time.
  */
 class GrCCPRTriangleCornerShader : public GrCCPRCoverageProcessor::Shader {
-    GeometryType getGeometryType() const override { return GeometryType::kCorners; }
-    int getNumSegments() const final { return 3; }
-
-    void emitSetupCode(GrGLSLShaderBuilder*, const char* pts, const char* cornerId,
+    void emitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                        const char* wind, GeometryVars*) const override;
     WindHandling onEmitVaryings(GrGLSLVaryingHandler*, SkString* code, const char* position,
                                 const char* coverage, const char* wind) override;