Rewrite the path tessellation sample

Use the GrPathTessellators directly instead of going through
GrPathStencilFillOp. Add keyboard shortcuts to toggle between the
different path tessellators.

Bug: skia:10419
Change-Id: I5d80731d26c9a77fb0eca07a7023c50848e29f7c
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/409556
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Tyler Denniston <tdenniston@google.com>
diff --git a/gn/samples.gni b/gn/samples.gni
index a248613..2e7362f 100644
--- a/gn/samples.gni
+++ b/gn/samples.gni
@@ -54,6 +54,7 @@
   "$_samplecode/SamplePathClip.cpp",
   "$_samplecode/SamplePathEffects.cpp",
   "$_samplecode/SamplePathOverstroke.cpp",
+  "$_samplecode/SamplePathTessellators.cpp",
   "$_samplecode/SamplePathText.cpp",
   "$_samplecode/SamplePolyToPoly.cpp",
   "$_samplecode/SampleQuadStroker.cpp",
@@ -71,7 +72,6 @@
   "$_samplecode/SampleStrokePath.cpp",
   "$_samplecode/SampleStrokeRect.cpp",
   "$_samplecode/SampleStrokeVerb.cpp",
-  "$_samplecode/SampleTessellatedWedge.cpp",
   "$_samplecode/SampleTextBox.cpp",
   "$_samplecode/SampleTextEffects.cpp",
   "$_samplecode/SampleTextureUpload.cpp",
diff --git a/samplecode/SamplePathTessellators.cpp b/samplecode/SamplePathTessellators.cpp
new file mode 100644
index 0000000..8a499a4
--- /dev/null
+++ b/samplecode/SamplePathTessellators.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/core/SkCanvas.h"
+#include "samplecode/Sample.h"
+#include "src/core/SkPathPriv.h"
+
+#if SK_SUPPORT_GPU
+
+#include "src/core/SkCanvasPriv.h"
+#include "src/gpu/GrRecordingContextPriv.h"
+#include "src/gpu/GrSurfaceDrawContext.h"
+#include "src/gpu/tessellate/GrPathTessellator.h"
+#include "src/gpu/tessellate/GrStencilPathShader.h"
+
+namespace {
+
+enum class Mode {
+    kCurveMiddleOut,
+    kWedgeTessellate,
+    kCurveTessellate
+};
+
+static const char* ModeName(Mode mode) {
+    switch (mode) {
+        case Mode::kCurveMiddleOut:
+            return "GrCurveMiddleOutShader";
+        case Mode::kWedgeTessellate:
+            return "GrWedgeTessellateShader";
+        case Mode::kCurveTessellate:
+            return "GrCurveTessellateShader";
+    }
+    SkUNREACHABLE;
+}
+
+// Draws a path directly to the screen using a specific tessellator.
+class SamplePathTessellatorOp : public GrDrawOp {
+private:
+    DEFINE_OP_CLASS_ID
+
+    SamplePathTessellatorOp(const SkRect& drawBounds, const SkPath& path, const SkMatrix& m,
+                            GrPipeline::InputFlags pipelineFlags, Mode mode)
+            : GrDrawOp(ClassID())
+            , fPath(path)
+            , fMatrix(m)
+            , fPipelineFlags(pipelineFlags)
+            , fMode(mode) {
+        this->setBounds(drawBounds, HasAABloat::kNo, IsHairline::kNo);
+    }
+    const char* name() const override { return "SamplePathTessellatorOp"; }
+    void visitProxies(const VisitProxyFunc& fn) const override {}
+    FixedFunctionFlags fixedFunctionFlags() const override {
+        return FixedFunctionFlags::kUsesHWAA;
+    }
+    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
+                                      GrClampType clampType) override {
+        SkPMColor4f color;
+        return fProcessors.finalize(SK_PMColor4fWHITE, GrProcessorAnalysisCoverage::kNone, clip,
+                                    nullptr, caps, clampType, &color);
+    }
+    void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
+                      const GrXferProcessor::DstProxyView&, GrXferBarrierFlags,
+                      GrLoadOp colorLoadOp) override {}
+    void onPrepare(GrOpFlushState* flushState) override {
+        auto alloc = flushState->allocator();
+        GrPathShader* shader;
+        switch (fMode) {
+            case Mode::kCurveMiddleOut:
+                fTessellator = alloc->make<GrPathIndirectTessellator>(
+                        fMatrix, fPath, GrPathIndirectTessellator::DrawInnerFan::kYes);
+                shader = alloc->make<GrCurveMiddleOutShader>(fMatrix);
+                break;
+            case Mode::kWedgeTessellate:
+                fTessellator = alloc->make<GrPathWedgeTessellator>();
+                shader = alloc->make<GrWedgeTessellateShader>(fMatrix);
+                break;
+            case Mode::kCurveTessellate:
+                fTessellator = alloc->make<GrPathOuterCurveTessellator>();
+                shader = alloc->make<GrCurveTessellateShader>(fMatrix);
+                break;
+        }
+        fTessellator->prepare(flushState, fMatrix, fPath);
+        auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors),
+                                                                 fPipelineFlags);
+        fProgram = GrPathShader::MakeProgram({alloc, flushState->writeView(),
+                                             &flushState->dstProxyView(),
+                                             flushState->renderPassBarriers(), GrLoadOp::kClear,
+                                             &flushState->caps()}, shader, pipeline,
+                                             &GrUserStencilSettings::kUnused);
+    }
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        flushState->bindPipeline(*fProgram, chainBounds);
+        fTessellator->draw(flushState);
+    }
+
+    const SkPath fPath;
+    const SkMatrix fMatrix;
+    const GrPipeline::InputFlags fPipelineFlags;
+    const Mode fMode;
+    GrPathTessellator* fTessellator = nullptr;
+    GrProgramInfo* fProgram;
+    GrProcessorSet fProcessors{SkBlendMode::kSrcOver};
+
+    friend class GrOp;  // For ctor.
+};
+
+}  // namespace
+
+// This sample enables wireframe and visualizes the triangles generated by path tessellators.
+class SamplePathTessellators : public Sample {
+public:
+    SamplePathTessellators() {
+#if 0
+        // For viewing middle-out triangulations of the inner fan.
+        fPath.moveTo(1, 0);
+        int numSides = 32 * 3;
+        for (int i = 1; i < numSides; ++i) {
+            float theta = 2*3.1415926535897932384626433832785 * i / numSides;
+            fPath.lineTo(std::cos(theta), std::sin(theta));
+        }
+        fPath.transform(SkMatrix::Scale(200, 200));
+        fPath.transform(SkMatrix::Translate(300, 300));
+#else
+        fPath.moveTo(100, 500);
+        fPath.cubicTo(300, 400, -100, 300, 100, 200);
+        fPath.quadTo(250, 0, 400, 200);
+        fPath.conicTo(600, 350, 400, 500, fConicWeight);
+        fPath.close();
+#endif
+    }
+
+private:
+    void onDrawContent(SkCanvas*) override;
+    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override;
+    bool onClick(Sample::Click*) override;
+    bool onChar(SkUnichar) override;
+
+    SkString name() override { return SkString("PathTessellators"); }
+
+    SkPath fPath;
+    GrPipeline::InputFlags fPipelineFlags = GrPipeline::InputFlags::kHWAntialias |
+                                            GrPipeline::InputFlags::kWireframe;
+    Mode fMode = Mode::kCurveMiddleOut;
+
+    float fConicWeight = .5;
+
+    class Click;
+};
+
+void SamplePathTessellators::onDrawContent(SkCanvas* canvas) {
+    canvas->clear(SK_ColorBLACK);
+
+    auto ctx = canvas->recordingContext();
+    GrSurfaceDrawContext* sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
+
+    SkString error;
+    if (!sdc || !ctx) {
+        error = "GPU Only.";
+    } else if (!GrTessellationPathRenderer::IsSupported(*ctx->priv().caps())) {
+        error = "GrTessellationPathRenderer not supported.";
+    } else if (fMode >= Mode::kWedgeTessellate &&
+               !ctx->priv().caps()->shaderCaps()->tessellationSupport()) {
+        error.printf("%s requires hardware tessellation support.", ModeName(fMode));
+    }
+    if (!error.isEmpty()) {
+        canvas->clear(SK_ColorRED);
+        SkFont font(nullptr, 20);
+        SkPaint captionPaint;
+        captionPaint.setColor(SK_ColorWHITE);
+        canvas->drawString(error.c_str(), 10, 30, font, captionPaint);
+        return;
+    }
+
+    sdc->addDrawOp(GrOp::Make<SamplePathTessellatorOp>(ctx,
+                                                       sdc->asRenderTargetProxy()->getBoundsRect(),
+                                                       fPath, canvas->getTotalMatrix(),
+                                                       fPipelineFlags, fMode));
+
+    // Draw the path points.
+    SkPaint pointsPaint;
+    pointsPaint.setColor(SK_ColorBLUE);
+    pointsPaint.setStrokeWidth(8);
+    SkPath devPath = fPath;
+    devPath.transform(canvas->getTotalMatrix());
+    {
+        SkAutoCanvasRestore acr(canvas, true);
+        canvas->setMatrix(SkMatrix::I());
+        SkString caption(ModeName(fMode));
+        caption.appendf(" (w=%g)", fConicWeight);
+        SkFont font(nullptr, 20);
+        SkPaint captionPaint;
+        captionPaint.setColor(SK_ColorWHITE);
+        canvas->drawString(caption, 10, 30, font, captionPaint);
+        canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(),
+                           SkPathPriv::PointData(devPath), pointsPaint);
+    }
+}
+
+class SamplePathTessellators::Click : public Sample::Click {
+public:
+    Click(int ptIdx) : fPtIdx(ptIdx) {}
+
+    void doClick(SkPath* path) {
+        SkPoint pt = path->getPoint(fPtIdx);
+        SkPathPriv::UpdatePathPoint(path, fPtIdx, pt + fCurr - fPrev);
+    }
+
+private:
+    int fPtIdx;
+};
+
+Sample::Click* SamplePathTessellators::onFindClickHandler(SkScalar x, SkScalar y,
+                                                          skui::ModifierKey) {
+    const SkPoint* pts = SkPathPriv::PointData(fPath);
+    float fuzz = 30;
+    for (int i = 0; i < fPath.countPoints(); ++i) {
+        if (fabs(x - pts[i].x()) < fuzz && fabsf(y - pts[i].y()) < fuzz) {
+            return new Click(i);
+        }
+    }
+    return nullptr;
+}
+
+bool SamplePathTessellators::onClick(Sample::Click* click) {
+    Click* myClick = (Click*)click;
+    myClick->doClick(&fPath);
+    return true;
+}
+
+static SkPath update_weight(const SkPath& path, float w) {
+    SkPath path_;
+    for (auto [verb, pts, _] : SkPathPriv::Iterate(path)) {
+        switch (verb) {
+            case SkPathVerb::kMove:
+                path_.moveTo(pts[0]);
+                break;
+            case SkPathVerb::kLine:
+                path_.lineTo(pts[1]);
+                break;
+            case SkPathVerb::kQuad:
+                path_.quadTo(pts[1], pts[2]);
+                break;
+            case SkPathVerb::kCubic:
+                path_.cubicTo(pts[1], pts[2], pts[3]);
+                break;
+            case SkPathVerb::kConic:
+                path_.conicTo(pts[1], pts[2], (w != 1) ? w : .99f);
+                break;
+            case SkPathVerb::kClose:
+                break;
+        }
+    }
+    return path_;
+}
+
+bool SamplePathTessellators::onChar(SkUnichar unichar) {
+    switch (unichar) {
+        case 'w':
+            fPipelineFlags = (GrPipeline::InputFlags)(
+                    (int)fPipelineFlags ^ (int)GrPipeline::InputFlags::kWireframe);
+            return true;
+        case 'D': {
+            fPath.dump();
+            return true;
+        }
+        case '+':
+            fConicWeight *= 2;
+            fPath = update_weight(fPath, fConicWeight);
+            return true;
+        case '=':
+            fConicWeight *= 5/4.f;
+            fPath = update_weight(fPath, fConicWeight);
+            return true;
+        case '_':
+            fConicWeight *= .5f;
+            fPath = update_weight(fPath, fConicWeight);
+            return true;
+        case '-':
+            fConicWeight *= 4/5.f;
+            fPath = update_weight(fPath, fConicWeight);
+            return true;
+        case '1':
+        case '2':
+        case '3':
+            fMode = (Mode)(unichar - '1');
+            return true;
+    }
+    return false;
+}
+
+Sample* MakeTessellatedPathSample() { return new SamplePathTessellators; }
+static SampleRegistry gTessellatedPathSample(MakeTessellatedPathSample);
+
+#endif  // SK_SUPPORT_GPU
diff --git a/samplecode/SampleTessellatedWedge.cpp b/samplecode/SampleTessellatedWedge.cpp
deleted file mode 100644
index bb40896..0000000
--- a/samplecode/SampleTessellatedWedge.cpp
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright 2019 Google LLC.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "include/core/SkCanvas.h"
-#include "samplecode/Sample.h"
-#include "src/core/SkGeometry.h"
-#include "src/core/SkPathPriv.h"
-#include "tools/ToolUtils.h"
-
-#if SK_SUPPORT_GPU
-
-#include "include/gpu/GrRecordingContext.h"
-#include "src/core/SkCanvasPriv.h"
-#include "src/gpu/GrClip.h"
-#include "src/gpu/GrMemoryPool.h"
-#include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/GrSurfaceDrawContext.h"
-#include "src/gpu/geometry/GrWangsFormula.h"
-#include "src/gpu/tessellate/GrPathStencilFillOp.h"
-
-static float kConicWeight = .5;
-
-// This sample enables wireframe and visualizes the triangulation generated by
-// GrTessellateWedgeShader.
-class TessellatedWedge : public Sample {
-public:
-    TessellatedWedge() {
-#if 0
-        fPath.moveTo(1, 0);
-        int numSides = 32 * 3;
-        for (int i = 1; i < numSides; ++i) {
-            float theta = 2*3.1415926535897932384626433832785 * i / numSides;
-            fPath.lineTo(std::cos(theta), std::sin(theta));
-        }
-        fPath.transform(SkMatrix::Scale(200, 200));
-        fPath.transform(SkMatrix::Translate(300, 300));
-#else
-        fPath.moveTo(100, 300);
-        fPath.conicTo(300, 100, 500, 300, kConicWeight);
-        fPath.cubicTo(433, 366, 366, 433, 300, 500);
-#endif
-    }
-
-private:
-    void onDrawContent(SkCanvas*) override;
-    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override;
-    bool onClick(Sample::Click*) override;
-    bool onChar(SkUnichar) override;
-
-    SkString name() override { return SkString("TessellatedWedge"); }
-
-    SkMatrix fLastViewMatrix = SkMatrix::I();
-    SkPath fPath;
-    GrTessellationPathRenderer::OpFlags fOpFlags = GrTessellationPathRenderer::OpFlags::kWireframe;
-
-    class Click;
-};
-
-void TessellatedWedge::onDrawContent(SkCanvas* canvas) {
-    canvas->clear(SK_ColorBLACK);
-
-    auto ctx = canvas->recordingContext();
-    GrSurfaceDrawContext* sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas);
-
-    SkString error;
-    if (!sdc || !ctx) {
-        error = "GPU Only.";
-    } else if (!ctx->priv().caps()->drawInstancedSupport()) {
-        error = "Instanced rendering not supported.";
-    }
-    if (!error.isEmpty()) {
-        SkFont font(nullptr, 20);
-        SkPaint captionPaint;
-        captionPaint.setColor(SK_ColorWHITE);
-        canvas->drawString(error.c_str(), 10, 30, font, captionPaint);
-        return;
-    }
-
-    GrPaint paint;
-    paint.setColor4f({1,0,1,1});
-
-    GrAAType aa;
-    if (sdc->numSamples() > 1) {
-        aa = GrAAType::kMSAA;
-    } else {
-        aa = GrAAType::kNone;
-    }
-
-    sdc->addDrawOp(GrOp::Make<GrPathStencilFillOp>(ctx, canvas->getTotalMatrix(), fPath,
-                                                   std::move(paint), aa, fOpFlags));
-
-    // Draw the path points.
-    SkPaint pointsPaint;
-    pointsPaint.setColor(SK_ColorBLUE);
-    pointsPaint.setStrokeWidth(8);
-    SkPath devPath = fPath;
-    devPath.transform(canvas->getTotalMatrix());
-    {
-        SkAutoCanvasRestore acr(canvas, true);
-        canvas->setMatrix(SkMatrix::I());
-        canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(),
-                           SkPathPriv::PointData(devPath), pointsPaint);
-    }
-
-    fLastViewMatrix = canvas->getTotalMatrix();
-
-
-    SkString caption;
-    caption.printf("w=%f  (=/- and +/_ to change)", kConicWeight);
-    SkFont font(nullptr, 20);
-    SkPaint captionPaint;
-    captionPaint.setColor(SK_ColorWHITE);
-    canvas->drawString(caption, 10, 30, font, captionPaint);
-}
-
-class TessellatedWedge::Click : public Sample::Click {
-public:
-    Click(int ptIdx) : fPtIdx(ptIdx) {}
-
-    void doClick(SkPath* path) {
-        if (fPtIdx >= 0) {
-            SkPoint pt = path->getPoint(fPtIdx);
-            SkPathPriv::UpdatePathPoint(path, fPtIdx, pt + fCurr - fPrev);
-        } else {
-            path->transform(
-                    SkMatrix::Translate(fCurr.x() - fPrev.x(), fCurr.y() - fPrev.y()), path);
-        }
-    }
-
-private:
-    int fPtIdx;
-};
-
-Sample::Click* TessellatedWedge::onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) {
-    const SkPoint* pts = SkPathPriv::PointData(fPath);
-    float fuzz = 20 / fLastViewMatrix.getMaxScale();
-    for (int i = 0; i < fPath.countPoints(); ++i) {
-        SkPoint screenPoint = pts[i];
-        if (fabs(x - screenPoint.x()) < fuzz && fabsf(y - screenPoint.y()) < fuzz) {
-            return new Click(i);
-        }
-    }
-    return new Click(-1);
-}
-
-static float find_conic_max_error(const SkConic& conic, int numChops) {
-    if (numChops > 1) {
-        int leftChops = numChops / 2;
-        SkConic halves[2];
-        if (conic.chopAt((float)leftChops/numChops, halves)) {
-            return std::max(find_conic_max_error(halves[0], leftChops),
-                            find_conic_max_error(halves[1], numChops - leftChops));
-        }
-    }
-
-    const SkPoint* p = conic.fPts;
-    float w = conic.fW;
-    SkVector n = {p[2].fY - p[0].fY, p[0].fX - p[2].fX};
-    float h1 = (p[1] - p[0]).dot(n) / n.length();
-    float h = h1*w / (1 + w);
-    return h;
-}
-
-static void dump_conic_max_errors(const SkPath& path) {
-    SkPath path_;
-    for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
-        if (verb == SkPathVerb::kConic) {
-            int n = GrWangsFormula::quadratic(4, pts);
-            float err = find_conic_max_error(SkConic(pts, *w), n);
-            SkDebugf("CONIC MAX ERROR:  %f\n", err);
-        }
-    }
-}
-
-bool TessellatedWedge::onClick(Sample::Click* click) {
-    Click* myClick = (Click*)click;
-    myClick->doClick(&fPath);
-    dump_conic_max_errors(fPath);
-    return true;
-}
-
-static SkPath update_weight(const SkPath& path) {
-    SkPath path_;
-    for (auto [verb, pts, _] : SkPathPriv::Iterate(path)) {
-        switch (verb) {
-            case SkPathVerb::kMove:
-                path_.moveTo(pts[0]);
-                break;
-            case SkPathVerb::kLine:
-                path_.lineTo(pts[1]);
-                break;
-            case SkPathVerb::kQuad:
-                path_.quadTo(pts[1], pts[2]);
-                break;
-            case SkPathVerb::kCubic:
-                path_.cubicTo(pts[1], pts[2], pts[3]);
-                break;
-            case SkPathVerb::kConic:
-                path_.conicTo(pts[1], pts[2], (kConicWeight != 1) ? kConicWeight : .99f);
-                break;
-            default:
-                SkUNREACHABLE;
-        }
-    }
-    dump_conic_max_errors(path);
-    return path_;
-}
-
-bool TessellatedWedge::onChar(SkUnichar unichar) {
-    switch (unichar) {
-        case 'w':
-            fOpFlags = (GrTessellationPathRenderer::OpFlags)(
-                    (int)fOpFlags ^ (int)GrTessellationPathRenderer::OpFlags::kWireframe);
-            return true;
-        case 'D': {
-            fPath.dump();
-            return true;
-        }
-        case '+':
-            kConicWeight *= 2;
-            fPath = update_weight(fPath);
-            return true;
-        case '=':
-            kConicWeight *= 5/4.f;
-            fPath = update_weight(fPath);
-            return true;
-        case '_':
-            kConicWeight *= .5f;
-            fPath = update_weight(fPath);
-            return true;
-        case '-':
-            kConicWeight *= 4/5.f;
-            fPath = update_weight(fPath);
-            return true;
-    }
-    return false;
-}
-
-Sample* MakeTessellatedWedgeSample() { return new TessellatedWedge; }
-static SampleRegistry gTessellatedWedgeSample(MakeTessellatedWedgeSample);
-
-#endif  // SK_SUPPORT_GPU
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index ddf69ea..5e1f1f3 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -130,11 +130,11 @@
         kStencilResolveProcessor_ClassID,
         kFwidthSquircleTestProcessor_ClassID,
         kSwizzleFragmentProcessor_ClassID,
-        kTessellate_GrCubicTessellateShader_ClassID,
+        kTessellate_GrCurveMiddleOutShader_ClassID,
+        kTessellate_GrCurveTessellateShader_ClassID,
         kTessellate_GrFillBoundingBoxShader_ClassID,
         kTessellate_GrFillCubicHullShader_ClassID,
         kTessellate_GrFillTriangleShader_ClassID,
-        kTessellate_GrMiddleOutCubicShader_ClassID,
         kTessellate_GrStencilTriangleShader_ClassID,
         kTessellate_GrStrokeShader_ClassID,
         kTessellate_GrWedgeTessellateShader_ClassID,
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
index 98074c7..c3924d8 100644
--- a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
@@ -102,7 +102,7 @@
         using DrawInnerFan = GrPathIndirectTessellator::DrawInnerFan;
         fTessellator = args.fArena->make<GrPathIndirectTessellator>(fViewMatrix, fPath,
                                                                     DrawInnerFan::kNo);
-        fStencilCurvesProgram = GrStencilPathShader::MakeStencilProgram<GrMiddleOutCubicShader>(
+        fStencilCurvesProgram = GrStencilPathShader::MakeStencilProgram<GrCurveMiddleOutShader>(
                 args, fViewMatrix, pipelineForStencils, fPath.getFillType());
     }
 
diff --git a/src/gpu/tessellate/GrPathStencilFillOp.cpp b/src/gpu/tessellate/GrPathStencilFillOp.cpp
index 1e3d7cd..4544494 100644
--- a/src/gpu/tessellate/GrPathStencilFillOp.cpp
+++ b/src/gpu/tessellate/GrPathStencilFillOp.cpp
@@ -66,7 +66,7 @@
             fStencilFanProgram = GrStencilPathShader::MakeStencilProgram<GrStencilTriangleShader>(
                     args, fViewMatrix, stencilPassPipeline, fPath.getFillType());
         }
-        fStencilPathProgram = GrStencilPathShader::MakeStencilProgram<GrMiddleOutCubicShader>(
+        fStencilPathProgram = GrStencilPathShader::MakeStencilProgram<GrCurveMiddleOutShader>(
                 args, fViewMatrix, stencilPassPipeline, fPath.getFillType());
     } else {
         // The caller should have sent Flags::kDisableHWTessellation if it was not supported.
@@ -85,7 +85,7 @@
             fTessellator = args.fArena->make<GrPathOuterCurveTessellator>();
             fStencilFanProgram = GrStencilPathShader::MakeStencilProgram<GrStencilTriangleShader>(
                     args, fViewMatrix, stencilPassPipeline, fPath.getFillType());
-            fStencilPathProgram = GrStencilPathShader::MakeStencilProgram<GrCubicTessellateShader>(
+            fStencilPathProgram = GrStencilPathShader::MakeStencilProgram<GrCurveTessellateShader>(
                     args, fViewMatrix, stencilPassPipeline, fPath.getFillType());
         } else {
             // Fastest CPU approach: emit one cubic wedge per verb, fanning out from the center.
diff --git a/src/gpu/tessellate/GrPathTessellator.cpp b/src/gpu/tessellate/GrPathTessellator.cpp
index 6c8a060..d511f62 100644
--- a/src/gpu/tessellate/GrPathTessellator.cpp
+++ b/src/gpu/tessellate/GrPathTessellator.cpp
@@ -128,7 +128,7 @@
         }
         instanceLocations[resolveLevel] = instanceWriter.makeOffset(0);
         SkASSERT(fIndirectDrawCount < indirectLockCnt);
-        GrMiddleOutCubicShader::WriteDrawIndirectCmd(&indirectWriter, resolveLevel,
+        GrCurveMiddleOutShader::WriteDrawIndirectCmd(&indirectWriter, resolveLevel,
                                                      instanceCountAtCurrLevel + numExtraInstances,
                                                      currentBaseInstance);
         ++fIndirectDrawCount;
diff --git a/src/gpu/tessellate/GrStencilPathShader.cpp b/src/gpu/tessellate/GrStencilPathShader.cpp
index b827b6c..0ff4619 100644
--- a/src/gpu/tessellate/GrStencilPathShader.cpp
+++ b/src/gpu/tessellate/GrStencilPathShader.cpp
@@ -90,7 +90,9 @@
             v->codeAppendf("vsPt = %s;", vertexPos.c_str());
         }
 
-        // No fragment shader.
+        // The fragment shader is normally disabled, but output fully opaque white.
+        args.fFragBuilder->codeAppendf("const half4 %s = half4(1);", args.fOutputColor);
+        args.fFragBuilder->codeAppendf("const half4 %s = half4(1);", args.fOutputCoverage);
     }
 
     void setData(const GrGLSLProgramDataManager& pdman,
@@ -109,8 +111,8 @@
     return new Impl;
 }
 
-GrGLSLGeometryProcessor* GrCubicTessellateShader::createGLSLInstance(const GrShaderCaps&) const {
-    class CubicImpl : public GrStencilPathShader::Impl {
+GrGLSLGeometryProcessor* GrCurveTessellateShader::createGLSLInstance(const GrShaderCaps&) const {
+    class Impl : public GrStencilPathShader::Impl {
         SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
                                           const char* versionAndExtensionDecls,
                                           const GrGLSLUniformHandler&,
@@ -205,11 +207,11 @@
         }
     };
 
-    return new CubicImpl;
+    return new Impl;
 }
 
 GrGLSLGeometryProcessor* GrWedgeTessellateShader::createGLSLInstance(const GrShaderCaps&) const {
-    class WedgeImpl : public GrStencilPathShader::Impl {
+    class Impl : public GrStencilPathShader::Impl {
         SkString getTessControlShaderGLSL(const GrGeometryProcessor&,
                                           const char* versionAndExtensionDecls,
                                           const GrGLSLUniformHandler&,
@@ -299,12 +301,12 @@
         }
     };
 
-    return new WedgeImpl;
+    return new Impl;
 }
 
-class GrMiddleOutCubicShader::Impl : public GrStencilPathShader::Impl {
+class GrCurveMiddleOutShader::Impl : public GrStencilPathShader::Impl {
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
-        const auto& shader = args.fGeomProc.cast<GrMiddleOutCubicShader>();
+        const auto& shader = args.fGeomProc.cast<GrCurveMiddleOutShader>();
         args.fVaryingHandler->emitAttributes(shader);
         args.fVertBuilder->insertFunction(kUnpackRationalCubicFn);
         args.fVertBuilder->insertFunction(kEvalRationalCubicFn);
@@ -352,10 +354,13 @@
             pos = (%s * float3(pos, 1)).xy;)", viewMatrix);
         }
         gpArgs->fPositionVar.set(kFloat2_GrSLType, "pos");
-        // No fragment shader.
+
+        // The fragment shader is normally disabled, but output fully opaque white.
+        args.fFragBuilder->codeAppendf("const half4 %s = half4(1);", args.fOutputColor);
+        args.fFragBuilder->codeAppendf("const half4 %s = half4(1);", args.fOutputCoverage);
     }
 };
 
-GrGLSLGeometryProcessor* GrMiddleOutCubicShader::createGLSLInstance(const GrShaderCaps&) const {
+GrGLSLGeometryProcessor* GrCurveMiddleOutShader::createGLSLInstance(const GrShaderCaps&) const {
     return new Impl;
 }
diff --git a/src/gpu/tessellate/GrStencilPathShader.h b/src/gpu/tessellate/GrStencilPathShader.h
index ea12276..a938812 100644
--- a/src/gpu/tessellate/GrStencilPathShader.h
+++ b/src/gpu/tessellate/GrStencilPathShader.h
@@ -105,13 +105,13 @@
 
 // Uses GPU tessellation shaders to linearize, triangulate, and render standalone closed cubics.
 // TODO: Eventually we want to use rational cubic wedges in order to support perspective and conics.
-class GrCubicTessellateShader : public GrStencilPathShader {
+class GrCurveTessellateShader : public GrStencilPathShader {
 public:
-    GrCubicTessellateShader(const SkMatrix& viewMatrix) : GrStencilPathShader(
-            kTessellate_GrCubicTessellateShader_ClassID, viewMatrix, GrPrimitiveType::kPatches, 4) {
+    GrCurveTessellateShader(const SkMatrix& viewMatrix) : GrStencilPathShader(
+            kTessellate_GrCurveTessellateShader_ClassID, viewMatrix, GrPrimitiveType::kPatches, 4) {
         this->setVertexAttributes(&kSinglePointAttrib, 1);
     }
-    const char* name() const override { return "tessellate_GrCubicTessellateShader"; }
+    const char* name() const override { return "tessellate_GrCurveTessellateShader"; }
 
 private:
     GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const override;
@@ -144,7 +144,7 @@
 // The caller may compute each cubic's resolveLevel on the CPU (i.e., the log2 number of line
 // segments it will be divided into; see GrWangsFormula::cubic_log2/quadratic_log2/conic_log2), and
 // then sort the instance buffer by resolveLevel for efficient batching of indirect draws.
-class GrMiddleOutCubicShader : public GrStencilPathShader {
+class GrCurveMiddleOutShader : public GrStencilPathShader {
 public:
     // How many vertices do we need to draw in order to triangulate a cubic with 2^resolveLevel
     // line segments?
@@ -169,8 +169,8 @@
         indirectWriter->write(instanceCount, baseInstance, vertexCount, 0);
     }
 
-    GrMiddleOutCubicShader(const SkMatrix& viewMatrix)
-            : GrStencilPathShader(kTessellate_GrMiddleOutCubicShader_ClassID, viewMatrix,
+    GrCurveMiddleOutShader(const SkMatrix& viewMatrix)
+            : GrStencilPathShader(kTessellate_GrCurveMiddleOutShader_ClassID, viewMatrix,
                                   GrPrimitiveType::kTriangles) {
         constexpr static Attribute kInputPtsAttribs[] = {
                 {"inputPoints_0_1", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
@@ -178,7 +178,7 @@
         this->setInstanceAttributes(kInputPtsAttribs, 2);
     }
 
-    const char* name() const override { return "tessellate_GrMiddleOutCubicShader"; }
+    const char* name() const override { return "tessellate_GrCurveMiddleOutShader"; }
 
 private:
     GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps&) const override;