Rewrite GrPathTessellateOp as GrPathInnerTriangulateOp

Simplifies the op to always triangulate the inner fan. We ensure this
will always work by using breadcrumb triangles. Also removes the
fail-on-non-simple mode from GrInnerFanTriangulator since it isn't
being used anymore.

Bug: skia:10419
Change-Id: Idb849712bf2bc4e5ef785bc3f9b8db03119d230e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/359565
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/bench/TessellateBench.cpp b/bench/TessellateBench.cpp
index a8dc005..51223dd 100644
--- a/bench/TessellateBench.cpp
+++ b/bench/TessellateBench.cpp
@@ -12,7 +12,7 @@
 #include "src/gpu/GrOpFlushState.h"
 #include "src/gpu/mock/GrMockOpTarget.h"
 #include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
-#include "src/gpu/tessellate/GrPathTessellateOp.h"
+#include "src/gpu/tessellate/GrPathTessellator.h"
 #include "src/gpu/tessellate/GrStrokeIndirectOp.h"
 #include "src/gpu/tessellate/GrStrokeTessellateOp.h"
 #include "src/gpu/tessellate/GrWangsFormula.h"
@@ -96,17 +96,17 @@
 
 DEF_PATH_TESS_BENCH(GrPathIndirectTessellator, make_cubic_path(), SkMatrix::I()) {
     GrPathIndirectTessellator tess(fMatrix, fPath, GrPathIndirectTessellator::DrawInnerFan::kNo);
-    tess.prepare(fTarget.get(), fMatrix, fPath);
+    tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
 }
 
 DEF_PATH_TESS_BENCH(GrPathOuterCurveTessellator, make_cubic_path(), SkMatrix::I()) {
     GrPathOuterCurveTessellator tess;
-    tess.prepare(fTarget.get(), fMatrix, fPath);
+    tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
 }
 
 DEF_PATH_TESS_BENCH(GrPathWedgeTessellator, make_cubic_path(), SkMatrix::I()) {
     GrPathWedgeTessellator tess;
-    tess.prepare(fTarget.get(), fMatrix, fPath);
+    tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
 }
 
 static void benchmark_wangs_formula_cubic_log2(const SkMatrix& matrix, const SkPath& path) {
diff --git a/gm/largeclippedpath.cpp b/gm/largeclippedpath.cpp
new file mode 100644
index 0000000..9af338c
--- /dev/null
+++ b/gm/largeclippedpath.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm/gm.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkPath.h"
+
+constexpr int kSize = 1000;
+
+// Makes sure GrPathInnerTriangulateOp uses correct stencil settings when there is a clip in the
+// stencil buffer.
+static void draw_clipped_flower(SkCanvas* canvas, SkPathFillType fillType) {
+    canvas->clear(SK_ColorCYAN);
+    SkPath clip;
+    clip.setFillType(SkPathFillType::kWinding);
+    constexpr static int kGridCount = 50;
+    constexpr static float kCellSize = (float)kSize / kGridCount;
+    for (int y = 0; y < kGridCount; ++y) {
+        clip.addRect(0, y * kCellSize, kSize, (y + 1) * kCellSize, SkPathDirection(y & 1));
+    }
+    for (int x = 0; x < kGridCount; ++x) {
+        clip.addRect(x * kCellSize, 0, (x + 1) * kCellSize, kSize, SkPathDirection(x & 1));
+    }
+    canvas->clipPath(clip);
+    SkPath flower;
+    flower.setFillType(fillType);
+    flower.moveTo(1, 0);
+    constexpr static int kNumPetals = 9;
+    for (int i = 1; i <= kNumPetals; ++i) {
+        float c = 2*SK_ScalarPI*(i - .5f) / kNumPetals;
+        float theta = 2*SK_ScalarPI*i / kNumPetals;
+        flower.quadTo(cosf(c)*2, sinf(c)*2, cosf(theta), sinf(theta));
+    }
+    flower.close();
+    flower.addArc(SkRect::MakeLTRB(-.75f, -.75f, .75f, .75f), 0, 360);
+    canvas->translate(kSize/2.f, kSize/2.f);
+    canvas->scale(kSize/3.f, kSize/3.f);
+    SkPaint p;
+    p.setAntiAlias(true);
+    p.setColor(SK_ColorMAGENTA);
+    canvas->drawPath(flower, p);
+}
+
+DEF_SIMPLE_GM(largeclippedpath_winding, canvas, kSize, kSize) {
+    draw_clipped_flower(canvas, SkPathFillType::kWinding);
+}
+
+DEF_SIMPLE_GM(largeclippedpath_evenodd, canvas, kSize, kSize) {
+    draw_clipped_flower(canvas, SkPathFillType::kEvenOdd);
+}
diff --git a/gn/gm.gni b/gn/gm.gni
index 0c05e95..d6cfe85 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -236,6 +236,7 @@
   "$_gm/inversepaths.cpp",
   "$_gm/jpg_color_cube.cpp",
   "$_gm/labyrinth.cpp",
+  "$_gm/largeclippedpath.cpp",
   "$_gm/largeglyphblur.cpp",
   "$_gm/lattice.cpp",
   "$_gm/lazytiling.cpp",
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 620772f..24c0f02 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -472,9 +472,9 @@
   "$_src/gpu/tessellate/GrFillPathShader.h",
   "$_src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h",
   "$_src/gpu/tessellate/GrMidpointContourParser.h",
+  "$_src/gpu/tessellate/GrPathInnerTriangulateOp.cpp",
+  "$_src/gpu/tessellate/GrPathInnerTriangulateOp.h",
   "$_src/gpu/tessellate/GrPathShader.h",
-  "$_src/gpu/tessellate/GrPathTessellateOp.cpp",
-  "$_src/gpu/tessellate/GrPathTessellateOp.h",
   "$_src/gpu/tessellate/GrPathTessellator.cpp",
   "$_src/gpu/tessellate/GrPathTessellator.h",
   "$_src/gpu/tessellate/GrStencilPathShader.cpp",
diff --git a/src/gpu/GrAATriangulator.cpp b/src/gpu/GrAATriangulator.cpp
index a058fad..9659548 100644
--- a/src/gpu/GrAATriangulator.cpp
+++ b/src/gpu/GrAATriangulator.cpp
@@ -617,10 +617,8 @@
     this->mergeCoincidentVertices(&innerMesh, c, nullptr);
     bool was_complex = this->mergeCoincidentVertices(&fOuterMesh, c, nullptr);
     auto result = this->simplify(&innerMesh, c, nullptr);
-    SkASSERT(SimplifyResult::kAbort != result);
     was_complex = (SimplifyResult::kFoundSelfIntersection == result) || was_complex;
     result = this->simplify(&fOuterMesh, c, nullptr);
-    SkASSERT(SimplifyResult::kAbort != result);
     was_complex = (SimplifyResult::kFoundSelfIntersection == result) || was_complex;
     TESS_LOG("\ninner mesh before:\n");
     DUMP_MESH(innerMesh);
@@ -642,7 +640,6 @@
         SortedMerge(&innerMesh, &fOuterMesh, &aaMesh, c);
         this->mergeCoincidentVertices(&aaMesh, c, nullptr);
         result = this->simplify(&aaMesh, c, nullptr);
-        SkASSERT(SimplifyResult::kAbort != result);
         TESS_LOG("combined and simplified mesh:\n");
         DUMP_MESH(aaMesh);
         fOuterMesh.fHead = fOuterMesh.fTail = nullptr;
diff --git a/src/gpu/GrInnerFanTriangulator.h b/src/gpu/GrInnerFanTriangulator.h
index 2f98a87..fd1aeaa 100644
--- a/src/gpu/GrInnerFanTriangulator.h
+++ b/src/gpu/GrInnerFanTriangulator.h
@@ -17,10 +17,8 @@
 public:
     using GrTriangulator::BreadcrumbTriangleList;
 
-    GrInnerFanTriangulator(const SkPath& path, SkArenaAlloc* alloc,
-                           bool disallowSelfIntersection = false)
+    GrInnerFanTriangulator(const SkPath& path, SkArenaAlloc* alloc)
             : GrTriangulator(path, alloc) {
-        fDisallowSelfIntersection = disallowSelfIntersection;
         fCullCollinearVertices = false;
     }
 
diff --git a/src/gpu/GrTriangulator.cpp b/src/gpu/GrTriangulator.cpp
index 00f275d..4c73d08 100644
--- a/src/gpu/GrTriangulator.cpp
+++ b/src/gpu/GrTriangulator.cpp
@@ -1197,9 +1197,6 @@
                             leftEnclosingEdge, edge, &activeEdges, &v, mesh, c, breadcrumbList) ||
                         this->checkForIntersection(
                             edge, rightEnclosingEdge, &activeEdges, &v, mesh, c, breadcrumbList)) {
-                        if (fDisallowSelfIntersection) {
-                            return SimplifyResult::kAbort;
-                        }
                         result = SimplifyResult::kFoundSelfIntersection;
                         restartChecks = true;
                         break;
@@ -1208,9 +1205,6 @@
             } else {
                 if (this->checkForIntersection(leftEnclosingEdge, rightEnclosingEdge, &activeEdges,
                                                &v, mesh, c, breadcrumbList)) {
-                    if (fDisallowSelfIntersection) {
-                        return SimplifyResult::kAbort;
-                    }
                     result = SimplifyResult::kFoundSelfIntersection;
                     restartChecks = true;
                 }
@@ -1237,10 +1231,6 @@
 
 Poly* GrTriangulator::tessellate(const VertexList& vertices, const Comparator&) const {
     TESS_LOG("\ntessellating simple polygons\n");
-    int maxWindMagnitude = std::numeric_limits<int>::max();
-    if (fDisallowSelfIntersection && !SkPathFillType_IsEvenOdd(fPath.getFillType())) {
-        maxWindMagnitude = 1;
-    }
     EdgeList activeEdges;
     Poly* polys = nullptr;
     for (Vertex* v = vertices.fHead; v != nullptr; v = v->fNext) {
@@ -1333,9 +1323,6 @@
                 int winding = leftEdge->fLeftPoly ? leftEdge->fLeftPoly->fWinding : 0;
                 winding += leftEdge->fWinding;
                 if (winding != 0) {
-                    if (abs(winding) > maxWindMagnitude) {
-                        return nullptr;  // We can't have weighted wind in kSimpleInnerPolygons mode
-                    }
                     Poly* poly = this->makePoly(&polys, v, winding);
                     leftEdge->fRightPoly = rightEdge->fLeftPoly = poly;
                 }
@@ -1403,9 +1390,7 @@
     this->contoursToMesh(contours, contourCnt, &mesh, c, breadcrumbList);
     SortMesh(&mesh, c);
     this->mergeCoincidentVertices(&mesh, c, breadcrumbList);
-    if (SimplifyResult::kAbort == this->simplify(&mesh, c, breadcrumbList)) {
-        return nullptr;
-    }
+    this->simplify(&mesh, c, breadcrumbList);
     TESS_LOG("\nsimplified mesh:\n");
     DUMP_MESH(mesh);
     return this->tessellate(mesh, c);
diff --git a/src/gpu/GrTriangulator.h b/src/gpu/GrTriangulator.h
index 33f30657..1cc31e2 100644
--- a/src/gpu/GrTriangulator.h
+++ b/src/gpu/GrTriangulator.h
@@ -135,10 +135,9 @@
     static void SortMesh(VertexList* vertices, const Comparator&);
 
     // 4) Simplify the mesh by inserting new vertices at intersecting edges:
-    enum class SimplifyResult {
+    enum class SimplifyResult : bool {
         kAlreadySimple,
-        kFoundSelfIntersection,
-        kAbort
+        kFoundSelfIntersection
     };
 
     SimplifyResult simplify(VertexList* mesh, const Comparator&, BreadcrumbTriangleList*) const;
@@ -249,7 +248,6 @@
     bool fRoundVerticesToQuarterPixel = false;
     bool fEmitCoverage = false;
     bool fCullCollinearVertices = true;
-    bool fDisallowSelfIntersection = false;
 };
 
 /**
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
new file mode 100644
index 0000000..a47a3cb
--- /dev/null
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
@@ -0,0 +1,279 @@
+/*
+ * 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 "src/gpu/tessellate/GrPathInnerTriangulateOp.h"
+
+#include "src/gpu/GrEagerVertexAllocator.h"
+#include "src/gpu/GrInnerFanTriangulator.h"
+#include "src/gpu/GrOpFlushState.h"
+#include "src/gpu/GrRecordingContextPriv.h"
+#include "src/gpu/tessellate/GrFillPathShader.h"
+#include "src/gpu/tessellate/GrPathTessellator.h"
+#include "src/gpu/tessellate/GrStencilPathShader.h"
+#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
+
+using OpFlags = GrTessellationPathRenderer::OpFlags;
+
+void GrPathInnerTriangulateOp::visitProxies(const VisitProxyFunc& fn) const {
+    if (fPipelineForFills) {
+        fPipelineForFills->visitProxies(fn);
+    } else {
+        fProcessors.visitProxies(fn);
+    }
+}
+
+GrDrawOp::FixedFunctionFlags GrPathInnerTriangulateOp::fixedFunctionFlags() const {
+    auto flags = FixedFunctionFlags::kUsesStencil;
+    if (GrAAType::kNone != fAAType) {
+        flags |= FixedFunctionFlags::kUsesHWAA;
+    }
+    return flags;
+}
+
+GrProcessorSet::Analysis GrPathInnerTriangulateOp::finalize(const GrCaps& caps,
+                                                            const GrAppliedClip* clip,
+                                                            bool hasMixedSampledCoverage,
+                                                            GrClampType clampType) {
+    return fProcessors.finalize(fColor, GrProcessorAnalysisCoverage::kNone, clip, nullptr,
+                                hasMixedSampledCoverage, caps, clampType, &fColor);
+}
+
+void GrPathInnerTriangulateOp::pushFanStencilProgram(const GrPathShader::ProgramArgs& args,
+                                                     const GrPipeline* pipelineForStencils,
+                                                     const GrUserStencilSettings* stencil) {
+    SkASSERT(pipelineForStencils);
+    fFanPrograms.push_back(GrStencilPathShader::MakeStencilProgram<GrStencilTriangleShader>(
+            args, fViewMatrix, pipelineForStencils, stencil));
+}
+
+void GrPathInnerTriangulateOp::pushFanFillProgram(const GrPathShader::ProgramArgs& args,
+                                                  const GrUserStencilSettings* stencil) {
+    SkASSERT(fPipelineForFills);
+    auto* shader = args.fArena->make<GrFillTriangleShader>(fViewMatrix, fColor);
+    fFanPrograms.push_back(GrPathShader::MakeProgram(args, shader, fPipelineForFills, stencil));
+}
+
+void GrPathInnerTriangulateOp::prePreparePrograms(const GrPathShader::ProgramArgs& args,
+                                                  GrAppliedClip&& appliedClip) {
+    SkASSERT(!fFanTriangulator);
+    SkASSERT(!fFanPolys);
+    SkASSERT(!fPipelineForFills);
+    SkASSERT(!fTessellator);
+    SkASSERT(!fStencilCurvesProgram);
+    SkASSERT(fFanPrograms.empty());
+    SkASSERT(!fFillHullsProgram);
+
+    if (fPath.countVerbs() <= 0) {
+        return;
+    }
+
+    // If using wireframe or mixed samples, we have to fall back on a standard Redbook "stencil then
+    // fill" algorithm instead of bypassing the stencil buffer to fill the fan directly.
+    bool forceRedbookStencilPass = (fOpFlags & (OpFlags::kStencilOnly | OpFlags::kWireframe)) ||
+                                   fAAType == GrAAType::kCoverage;  // i.e., mixed samples!
+    bool doFill = !(fOpFlags & OpFlags::kStencilOnly);
+
+    bool isLinear;
+    fFanTriangulator = args.fArena->make<GrInnerFanTriangulator>(fPath, args.fArena);
+    fFanPolys = fFanTriangulator->pathToPolys(&fFanBreadcrumbs, &isLinear);
+
+    // Create a pipeline for stencil passes if needed.
+    const GrPipeline* pipelineForStencils = nullptr;
+    if (forceRedbookStencilPass || !isLinear) {  // Curves always get stencilled.
+        pipelineForStencils = GrStencilPathShader::MakeStencilPassPipeline(args, fAAType, fOpFlags,
+                                                                           appliedClip.hardClip());
+    }
+
+    // Create a pipeline for fill passes if needed.
+    if (doFill) {
+        fPipelineForFills = GrFillPathShader::MakeFillPassPipeline(args, fAAType,
+                                                                   std::move(appliedClip),
+                                                                   std::move(fProcessors));
+    }
+
+    // Pass 1: Tessellate the outer curves into the stencil buffer.
+    if (!isLinear) {
+        // Always use indirect draws for now. Our goal in this op is to maximize GPU performance,
+        // and the middle-out topology used by indirect draws is easier on the rasterizer than what
+        // we can do with hw tessellation. So far we haven't found any platforms where trying to use
+        // hw tessellation here is worth it.
+        using DrawInnerFan = GrPathIndirectTessellator::DrawInnerFan;
+        fTessellator = args.fArena->make<GrPathIndirectTessellator>(fViewMatrix, fPath,
+                                                                    DrawInnerFan::kNo);
+        fStencilCurvesProgram = GrStencilPathShader::MakeStencilProgram<GrMiddleOutCubicShader>(
+                args, fViewMatrix, pipelineForStencils, fPath.getFillType());
+    }
+
+    // Pass 2: Fill the path's inner fan with a stencil test against the curves.
+    if (fFanPolys) {
+        if (forceRedbookStencilPass) {
+            // Use a standard Redbook "stencil then fill" algorithm instead of bypassing the stencil
+            // buffer to fill the fan directly.
+            this->pushFanStencilProgram(
+                    args, pipelineForStencils,
+                    GrStencilPathShader::StencilPassSettings(fPath.getFillType()));
+            if (doFill) {
+                this->pushFanFillProgram(args, GrFillPathShader::TestAndResetStencilSettings());
+            }
+        } else if (isLinear) {
+            // There are no outer curves! Ignore stencil and fill the path directly.
+            SkASSERT(!pipelineForStencils);
+            this->pushFanFillProgram(args, &GrUserStencilSettings::kUnused);
+        } else if (!fPipelineForFills->hasStencilClip()) {
+            // These are a twist on the standard Redbook stencil settings that allow us to fill the
+            // inner polygon directly to the final render target. By the time these programs
+            // execute, the outer curves will already be stencilled in. So if the stencil value is
+            // zero, then it means the sample in question is not affected by any curves and we can
+            // fill it in directly. If the stencil value is nonzero, then we don't fill and instead
+            // continue the standard Redbook counting process.
+            constexpr static GrUserStencilSettings kFillOrIncrDecrStencil(
+                GrUserStencilSettings::StaticInitSeparate<
+                    0x0000,                       0x0000,
+                    GrUserStencilTest::kEqual,    GrUserStencilTest::kEqual,
+                    0xffff,                       0xffff,
+                    GrUserStencilOp::kKeep,       GrUserStencilOp::kKeep,
+                    GrUserStencilOp::kIncWrap,    GrUserStencilOp::kDecWrap,
+                    0xffff,                       0xffff>());
+
+            constexpr static GrUserStencilSettings kFillOrInvertStencil(
+                GrUserStencilSettings::StaticInit<
+                    0x0000,
+                    GrUserStencilTest::kEqual,
+                    0xffff,
+                    GrUserStencilOp::kKeep,
+                    // "Zero" instead of "Invert" because the fan only touches any given pixel once.
+                    GrUserStencilOp::kZero,
+                    0xffff>());
+
+            auto* stencil = (fPath.getFillType() == SkPathFillType::kWinding)
+                    ? &kFillOrIncrDecrStencil
+                    : &kFillOrInvertStencil;
+            this->pushFanFillProgram(args, stencil);
+        } else {
+            // This is the same idea as above, but we use two passes instead of one because there is
+            // a stencil clip. The stencil test isn't expressive enough to do the above tests and
+            // also check the clip bit in a single pass.
+            constexpr static GrUserStencilSettings kFillIfZeroAndInClip(
+                GrUserStencilSettings::StaticInit<
+                    0x0000,
+                    GrUserStencilTest::kEqualIfInClip,
+                    0xffff,
+                    GrUserStencilOp::kKeep,
+                    GrUserStencilOp::kKeep,
+                    0xffff>());
+
+            constexpr static GrUserStencilSettings kIncrDecrStencilIfNonzero(
+                GrUserStencilSettings::StaticInitSeparate<
+                    0x0000,                         0x0000,
+                    // No need to check the clip because the previous stencil pass will have only
+                    // written to samples already inside the clip.
+                    GrUserStencilTest::kNotEqual,   GrUserStencilTest::kNotEqual,
+                    0xffff,                         0xffff,
+                    GrUserStencilOp::kIncWrap,      GrUserStencilOp::kDecWrap,
+                    GrUserStencilOp::kKeep,         GrUserStencilOp::kKeep,
+                    0xffff,                         0xffff>());
+
+            constexpr static GrUserStencilSettings kInvertStencilIfNonZero(
+                GrUserStencilSettings::StaticInit<
+                    0x0000,
+                    // No need to check the clip because the previous stencil pass will have only
+                    // written to samples already inside the clip.
+                    GrUserStencilTest::kNotEqual,
+                    0xffff,
+                    // "Zero" instead of "Invert" because the fan only touches any given pixel once.
+                    GrUserStencilOp::kZero,
+                    GrUserStencilOp::kKeep,
+                    0xffff>());
+
+            // Pass 2a: Directly fill fan samples whose stencil values (from curves) are zero.
+            this->pushFanFillProgram(args, &kFillIfZeroAndInClip);
+
+            // Pass 2b: Redbook counting on fan samples whose stencil values (from curves) != 0.
+            auto* stencil = (fPath.getFillType() == SkPathFillType::kWinding)
+                    ? &kIncrDecrStencilIfNonzero
+                    : &kInvertStencilIfNonZero;
+            this->pushFanStencilProgram(args, pipelineForStencils, stencil);
+        }
+    }
+
+    // Pass 3: Draw convex hulls around each curve.
+    if (doFill && !isLinear) {
+        // By the time this program executes, every pixel will be filled in except the ones touched
+        // by curves. We issue a final cover pass over the curves by drawing their convex hulls.
+        // This will fill in any remaining samples and reset the stencil values back to zero.
+        SkASSERT(fTessellator);
+        auto* hullShader = args.fArena->make<GrFillCubicHullShader>(fViewMatrix, fColor);
+        fFillHullsProgram = GrPathShader::MakeProgram(
+                args, hullShader, fPipelineForFills,
+                GrFillPathShader::TestAndResetStencilSettings());
+    }
+}
+
+void GrPathInnerTriangulateOp::onPrePrepare(GrRecordingContext* context,
+                                            const GrSurfaceProxyView& writeView,
+                                            GrAppliedClip* clip,
+                                            const GrXferProcessor::DstProxyView& dstProxyView,
+                                            GrXferBarrierFlags renderPassXferBarriers,
+                                            GrLoadOp colorLoadOp) {
+    this->prePreparePrograms({context->priv().recordTimeAllocator(), writeView, &dstProxyView,
+                             renderPassXferBarriers, colorLoadOp, context->priv().caps()},
+                             (clip) ? std::move(*clip) : GrAppliedClip::Disabled());
+    if (fStencilCurvesProgram) {
+        context->priv().recordProgramInfo(fStencilCurvesProgram);
+    }
+    for (const GrProgramInfo* fanProgram : fFanPrograms) {
+        context->priv().recordProgramInfo(fanProgram);
+    }
+    if (fFillHullsProgram) {
+        context->priv().recordProgramInfo(fFillHullsProgram);
+    }
+}
+
+void GrPathInnerTriangulateOp::onPrepare(GrOpFlushState* flushState) {
+    if (!fFanTriangulator) {
+        this->prePreparePrograms({flushState->allocator(), flushState->writeView(),
+                                 &flushState->dstProxyView(), flushState->renderPassBarriers(),
+                                 flushState->colorLoadOp(), &flushState->caps()},
+                                 flushState->detachAppliedClip());
+        if (!fFanTriangulator) {
+            return;
+        }
+    }
+
+    if (fFanPolys) {
+        GrEagerDynamicVertexAllocator alloc(flushState, &fFanBuffer, &fBaseFanVertex);
+        fFanVertexCount = fFanTriangulator->polysToTriangles(fFanPolys, &alloc, &fFanBreadcrumbs);
+    }
+
+    if (fTessellator) {
+        // Must be called after polysToTriangles() in order for fFanBreadcrumbs to be complete.
+        fTessellator->prepare(flushState, fViewMatrix, fPath, &fFanBreadcrumbs);
+    }
+}
+
+void GrPathInnerTriangulateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
+    if (fStencilCurvesProgram) {
+        SkASSERT(fTessellator);
+        flushState->bindPipelineAndScissorClip(*fStencilCurvesProgram, this->bounds());
+        fTessellator->draw(flushState);
+    }
+
+    for (const GrProgramInfo* fanProgram : fFanPrograms) {
+        SkASSERT(fFanBuffer);
+        flushState->bindPipelineAndScissorClip(*fanProgram, this->bounds());
+        flushState->bindTextures(fanProgram->primProc(), nullptr, fanProgram->pipeline());
+        flushState->bindBuffers(nullptr, nullptr, fFanBuffer);
+        flushState->draw(fFanVertexCount, fBaseFanVertex);
+    }
+
+    if (fFillHullsProgram) {
+        SkASSERT(fTessellator);
+        flushState->bindPipelineAndScissorClip(*fFillHullsProgram, this->bounds());
+        flushState->bindTextures(fFillHullsProgram->primProc(), nullptr, *fPipelineForFills);
+        fTessellator->drawHullInstances(flushState);
+    }
+}
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.h b/src/gpu/tessellate/GrPathInnerTriangulateOp.h
new file mode 100644
index 0000000..70ffa7f
--- /dev/null
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrPathInnerTriangulateOp_DEFINED
+#define GrPathInnerTriangulateOp_DEFINED
+
+#include "src/gpu/GrInnerFanTriangulator.h"
+#include "src/gpu/ops/GrDrawOp.h"
+#include "src/gpu/tessellate/GrPathShader.h"
+#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
+
+class GrPathTessellator;
+
+// This op is a 3-pass twist on the standard Redbook "stencil then fill" algorithm:
+//
+// 1) Tessellate the path's outer curves into the stencil buffer.
+// 2) Triangulate the path's inner fan and fill it with a stencil test against the curves.
+// 3) Draw convex hulls around each curve that fill in remaining samples.
+//
+// In practice, a path's inner fan takes up a large majority of its pixels. So from a GPU load
+// perspective, this op is effectively as fast as a single-pass algorithm.
+class GrPathInnerTriangulateOp : public GrDrawOp {
+private:
+    DEFINE_OP_CLASS_ID
+
+    GrPathInnerTriangulateOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
+                       GrAAType aaType, GrTessellationPathRenderer::OpFlags opFlags)
+            : GrDrawOp(ClassID())
+            , fOpFlags(opFlags)
+            , fViewMatrix(viewMatrix)
+            , fPath(path)
+            , fAAType(aaType)
+            , fColor(paint.getColor4f())
+            , fProcessors(std::move(paint)) {
+        SkRect devBounds;
+        fViewMatrix.mapRect(&devBounds, path.getBounds());
+        this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType), IsHairline::kNo);
+    }
+
+    const char* name() const override { return "GrPathInnerTriangulateOp"; }
+    void visitProxies(const VisitProxyFunc& fn) const override;
+    FixedFunctionFlags fixedFunctionFlags() const override;
+    GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
+                                      bool hasMixedSampledCoverage, GrClampType) override;
+
+    // These calls set up the stencil & fill programs we will use prior to preparing and executing.
+    void pushFanStencilProgram(const GrPathShader::ProgramArgs&,
+                               const GrPipeline* pipelineForStencils, const GrUserStencilSettings*);
+    void pushFanFillProgram(const GrPathShader::ProgramArgs&, const GrUserStencilSettings*);
+    void prePreparePrograms(const GrPathShader::ProgramArgs&, GrAppliedClip&&);
+
+    void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
+                      const GrXferProcessor::DstProxyView&, GrXferBarrierFlags,
+                      GrLoadOp colorLoadOp) override;
+    void onPrepare(GrOpFlushState*) override;
+    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
+
+    const GrTessellationPathRenderer::OpFlags fOpFlags;
+    const SkMatrix fViewMatrix;
+    const SkPath fPath;
+    const GrAAType fAAType;
+    SkPMColor4f fColor;
+    GrProcessorSet fProcessors;
+
+    // Triangulates the inner fan.
+    GrInnerFanTriangulator* fFanTriangulator = nullptr;
+    GrTriangulator::Poly* fFanPolys = nullptr;
+    GrInnerFanTriangulator::BreadcrumbTriangleList fFanBreadcrumbs;
+
+    // This pipeline is shared by all programs that do filling.
+    const GrPipeline* fPipelineForFills = nullptr;
+
+    // Tessellates the outer curves.
+    GrPathTessellator* fTessellator = nullptr;
+
+    // Pass 1: Tessellate the outer curves into the stencil buffer.
+    const GrProgramInfo* fStencilCurvesProgram = nullptr;
+
+    // Pass 2: Fill the path's inner fan with a stencil test against the curves. (In extenuating
+    // circumstances this might require two separate draws.)
+    SkSTArray<2, const GrProgramInfo*> fFanPrograms;
+
+    // Pass 3: Draw convex hulls around each curve.
+    const GrProgramInfo* fFillHullsProgram = nullptr;
+
+    // This buffer gets created by fFanTriangulator during onPrepare.
+    sk_sp<const GrBuffer> fFanBuffer;
+    int fBaseFanVertex = 0;
+    int fFanVertexCount = 0;
+
+    friend class GrOp;  // For ctor.
+};
+
+#endif
diff --git a/src/gpu/tessellate/GrPathTessellateOp.cpp b/src/gpu/tessellate/GrPathTessellateOp.cpp
deleted file mode 100644
index ce449ea..0000000
--- a/src/gpu/tessellate/GrPathTessellateOp.cpp
+++ /dev/null
@@ -1,457 +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 "src/gpu/tessellate/GrPathTessellateOp.h"
-
-#include "src/gpu/GrEagerVertexAllocator.h"
-#include "src/gpu/GrInnerFanTriangulator.h"
-#include "src/gpu/GrOpFlushState.h"
-#include "src/gpu/GrRecordingContextPriv.h"
-#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
-#include "src/gpu/tessellate/GrFillPathShader.h"
-#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
-#include "src/gpu/tessellate/GrStencilPathShader.h"
-#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
-
-using OpFlags = GrTessellationPathRenderer::OpFlags;
-
-void GrPathTessellateOp::visitProxies(const VisitProxyFunc& fn) const {
-    if (fPipelineForFills) {
-        fPipelineForFills->visitProxies(fn);
-    } else {
-        fProcessors.visitProxies(fn);
-    }
-}
-
-GrPathTessellateOp::FixedFunctionFlags GrPathTessellateOp::fixedFunctionFlags() const {
-    auto flags = FixedFunctionFlags::kUsesStencil;
-    if (GrAAType::kNone != fAAType) {
-        flags |= FixedFunctionFlags::kUsesHWAA;
-    }
-    return flags;
-}
-
-void GrPathTessellateOp::onPrePrepare(GrRecordingContext* context,
-                                      const GrSurfaceProxyView& writeView, GrAppliedClip* clip,
-                                      const GrXferProcessor::DstProxyView& dstProxyView,
-                                      GrXferBarrierFlags renderPassXferBarriers,
-                                      GrLoadOp colorLoadOp) {
-    SkArenaAlloc* recordTimeAllocator = context->priv().recordTimeAllocator();
-    GrAppliedHardClip hardClip = GrAppliedHardClip(
-            (clip) ? clip->hardClip() : GrAppliedHardClip::Disabled());
-    PrePrepareArgs args{recordTimeAllocator, writeView, &hardClip, clip, &dstProxyView,
-                        renderPassXferBarriers, colorLoadOp, context->priv().caps()};
-
-    this->prePreparePrograms(args);
-
-    if (fStencilTrianglesProgram) {
-        context->priv().recordProgramInfo(fStencilTrianglesProgram);
-    }
-    if (fStencilCubicsProgram) {
-        context->priv().recordProgramInfo(fStencilCubicsProgram);
-    }
-    if (fFillTrianglesProgram) {
-        context->priv().recordProgramInfo(fFillTrianglesProgram);
-    }
-    if (fFillPathProgram) {
-        context->priv().recordProgramInfo(fFillPathProgram);
-    }
-}
-
-void GrPathTessellateOp::prePreparePrograms(const PrePrepareArgs& args) {
-    using DrawInnerFan = GrPathIndirectTessellator::DrawInnerFan;
-    int numVerbs = fPath.countVerbs();
-    if (numVerbs <= 0) {
-        return;
-    }
-
-    // First check if the path is large and/or simple enough that we can actually triangulate the
-    // inner polygon(s) on the CPU. This is our fastest approach. It allows us to stencil only the
-    // curves, and then fill the internal polygons directly to the final render target, thus drawing
-    // the majority of pixels in a single render pass.
-    SkScalar scales[2];
-    SkAssertResult(fViewMatrix.getMinMaxScales(scales));  // Will fail if perspective.
-    const SkRect& bounds = fPath.getBounds();
-    float gpuFragmentWork = bounds.height() * scales[0] * bounds.width() * scales[1];
-    float cpuTessellationWork = (float)numVerbs * SkNextLog2(numVerbs);  // N log N.
-    if (cpuTessellationWork * 500 + (256 * 256) < gpuFragmentWork) {  // Don't try below 256x256.
-        bool isLinear;
-        // This will fail if the inner triangles do not form a simple polygon (e.g., self
-        // intersection, double winding).
-        if (this->prePrepareInnerPolygonTriangulation(args, &isLinear)) {
-            if (!isLinear) {
-                // Always use indirect draws for cubics instead of tessellation here. Our goal in
-                // this mode is to maximize GPU performance, and the middle-out topology used by our
-                // indirect draws is easier on the rasterizer than a tessellated fan. There also
-                // seems to be a small amount of fixed tessellation overhead that this avoids.
-                this->prePrepareStencilCubicsProgram<GrMiddleOutCubicShader>(args);
-                // We will need one final pass to cover the convex hulls of the cubics after
-                // drawing the inner triangles.
-                this->prePrepareFillCubicHullsProgram(args);
-                fTessellator = args.fArena->make<GrPathIndirectTessellator>(fViewMatrix, fPath,
-                                                                            DrawInnerFan::kNo);
-            }
-            return;
-        }
-    }
-
-    // If we didn't triangulate the inner fan then the fill program will be a simple bounding box.
-    this->prePrepareFillBoundingBoxProgram(args);
-
-    // When there are only a few verbs, it seems to always be fastest to make a single indirect draw
-    // that contains both the inner triangles and the outer cubics, instead of using hardware
-    // tessellation. Also take this path if tessellation is not supported.
-    bool drawTrianglesAsIndirectCubicDraw = (numVerbs < 50);
-    if (drawTrianglesAsIndirectCubicDraw || (fOpFlags & OpFlags::kDisableHWTessellation)) {
-        if (!drawTrianglesAsIndirectCubicDraw) {
-            this->prePrepareStencilTrianglesProgram(args);
-        }
-        this->prePrepareStencilCubicsProgram<GrMiddleOutCubicShader>(args);
-        fTessellator = args.fArena->make<GrPathIndirectTessellator>(
-                fViewMatrix, fPath, DrawInnerFan(drawTrianglesAsIndirectCubicDraw));
-        return;
-    }
-
-    // The caller should have sent Flags::kDisableHWTessellation if it was not supported.
-    SkASSERT(args.fCaps->shaderCaps()->tessellationSupport());
-
-    // Next see if we can split up the inner triangles and outer cubics into two draw calls. This
-    // allows for a more efficient inner triangle topology that can reduce the rasterizer load by a
-    // large margin on complex paths, but also causes greater CPU overhead due to the extra shader
-    // switches and draw calls.
-    // NOTE: Raster-edge work is 1-dimensional, so we sum height and width instead of multiplying.
-    float rasterEdgeWork = (bounds.height() + bounds.width()) * scales[1] * fPath.countVerbs();
-    if (rasterEdgeWork > 300 * 300) {
-        this->prePrepareStencilTrianglesProgram(args);
-        this->prePrepareStencilCubicsProgram<GrCubicTessellateShader>(args);
-        fTessellator = args.fArena->make<GrPathOuterCurveTessellator>();
-        return;
-    }
-
-    // Fastest CPU approach: emit one cubic wedge per verb, fanning out from the center.
-    this->prePrepareStencilCubicsProgram<GrWedgeTessellateShader>(args);
-    fTessellator = args.fArena->make<GrPathWedgeTessellator>();
-}
-
-bool GrPathTessellateOp::prePrepareInnerPolygonTriangulation(const PrePrepareArgs& args,
-                                                             bool* isLinear) {
-    SkASSERT(!fTriangleBuffer);
-    SkASSERT(fTriangleVertexCount == 0);
-    SkASSERT(!fStencilTrianglesProgram);
-    SkASSERT(!fFillTrianglesProgram);
-    fInnerFanTriangulator = args.fArena->make<GrInnerFanTriangulator>(fPath, args.fArena, true);
-    fInnerFanPolys = fInnerFanTriangulator->pathToPolys(nullptr, isLinear);
-    if (!fInnerFanPolys) {
-        // pathToPolys will fail if the inner polygon(s) are not simple.
-        return false;
-    }
-    if ((fOpFlags & (OpFlags::kStencilOnly | OpFlags::kWireframe)) ||
-        GrAAType::kCoverage == fAAType ||
-        (args.fClip && args.fClip->hasStencilClip())) {
-        // If we have certain flags, mixed samples, or a stencil clip then we unfortunately
-        // can't fill the inner polygon directly. Indicate that these triangles need to be
-        // stencilled.
-        this->prePrepareStencilTrianglesProgram(args);
-    }
-    this->prePrepareFillTrianglesProgram(args, *isLinear);
-    return true;
-}
-
-// Increments clockwise triangles and decrements counterclockwise. Used for "winding" fill.
-constexpr static GrUserStencilSettings kIncrDecrStencil(
-    GrUserStencilSettings::StaticInitSeparate<
-        0x0000,                                0x0000,
-        GrUserStencilTest::kAlwaysIfInClip,    GrUserStencilTest::kAlwaysIfInClip,
-        0xffff,                                0xffff,
-        GrUserStencilOp::kIncWrap,             GrUserStencilOp::kDecWrap,
-        GrUserStencilOp::kKeep,                GrUserStencilOp::kKeep,
-        0xffff,                                0xffff>());
-
-// Inverts the bottom stencil bit. Used for "even/odd" fill.
-constexpr static GrUserStencilSettings kInvertStencil(
-    GrUserStencilSettings::StaticInit<
-        0x0000,
-        GrUserStencilTest::kAlwaysIfInClip,
-        0xffff,
-        GrUserStencilOp::kInvert,
-        GrUserStencilOp::kKeep,
-        0x0001>());
-
-constexpr static const GrUserStencilSettings* stencil_pass_settings(SkPathFillType fillType) {
-    return (fillType == SkPathFillType::kWinding) ? &kIncrDecrStencil : &kInvertStencil;
-}
-
-void GrPathTessellateOp::prePrepareStencilTrianglesProgram(const PrePrepareArgs& args) {
-    SkASSERT(!fStencilTrianglesProgram);
-
-    this->prePreparePipelineForStencils(args);
-
-    auto* shader = args.fArena->make<GrStencilTriangleShader>(fViewMatrix);
-    fStencilTrianglesProgram = GrPathShader::MakeProgramInfo(
-            shader, args.fArena, args.fWriteView, fPipelineForStencils, *args.fDstProxyView,
-            args.fXferBarrierFlags, args.fColorLoadOp, stencil_pass_settings(fPath.getFillType()),
-            *args.fCaps);
-}
-
-template<typename ShaderType>
-void GrPathTessellateOp::prePrepareStencilCubicsProgram(const PrePrepareArgs& args) {
-    SkASSERT(!fStencilCubicsProgram);
-
-    this->prePreparePipelineForStencils(args);
-
-    auto* shader = args.fArena->make<ShaderType>(fViewMatrix);
-    fStencilCubicsProgram = GrPathShader::MakeProgramInfo(
-            shader, args.fArena, args.fWriteView, fPipelineForStencils, *args.fDstProxyView,
-            args.fXferBarrierFlags, args.fColorLoadOp, stencil_pass_settings(fPath.getFillType()),
-            *args.fCaps);
-}
-
-void GrPathTessellateOp::prePreparePipelineForStencils(const PrePrepareArgs& args) {
-    if (fPipelineForStencils) {
-        return;
-    }
-
-    GrPipeline::InitArgs initArgs;
-    if (GrAAType::kNone != fAAType) {
-        initArgs.fInputFlags |= GrPipeline::InputFlags::kHWAntialias;
-    }
-    if (args.fCaps->wireframeSupport() && (OpFlags::kWireframe & fOpFlags)) {
-        initArgs.fInputFlags |= GrPipeline::InputFlags::kWireframe;
-    }
-    SkASSERT(SkPathFillType::kWinding == fPath.getFillType() ||
-             SkPathFillType::kEvenOdd == fPath.getFillType());
-    initArgs.fCaps = args.fCaps;
-    fPipelineForStencils = args.fArena->make<GrPipeline>(
-            initArgs, GrDisableColorXPFactory::MakeXferProcessor(), *args.fHardClip);
-}
-
-// Allows non-zero stencil values to pass and write a color, and resets the stencil value back to
-// zero; discards immediately on stencil values of zero.
-// NOTE: It's ok to not check the clip here because the previous stencil pass will have only written
-// to samples already inside the clip.
-constexpr static GrUserStencilSettings kTestAndResetStencil(
-    GrUserStencilSettings::StaticInit<
-        0x0000,
-        GrUserStencilTest::kNotEqual,
-        0xffff,
-        GrUserStencilOp::kZero,
-        GrUserStencilOp::kKeep,
-        0xffff>());
-
-void GrPathTessellateOp::prePrepareFillTrianglesProgram(const PrePrepareArgs& args, bool isLinear) {
-    SkASSERT(!fFillTrianglesProgram);
-
-    if (fOpFlags & OpFlags::kStencilOnly) {
-        return;
-    }
-
-    // These are a twist on the standard red book stencil settings that allow us to fill the inner
-    // polygon directly to the final render target. At this point, the curves are already stencilled
-    // in. So if the stencil value is zero, then it means the path at our sample is not affected by
-    // any curves and we fill the path in directly. If the stencil value is nonzero, then we don't
-    // fill and instead continue the standard red book stencil process.
-    //
-    // NOTE: These settings are currently incompatible with a stencil clip.
-    constexpr static GrUserStencilSettings kFillOrIncrDecrStencil(
-        GrUserStencilSettings::StaticInitSeparate<
-            0x0000,                        0x0000,
-            GrUserStencilTest::kEqual,     GrUserStencilTest::kEqual,
-            0xffff,                        0xffff,
-            GrUserStencilOp::kKeep,        GrUserStencilOp::kKeep,
-            GrUserStencilOp::kIncWrap,     GrUserStencilOp::kDecWrap,
-            0xffff,                        0xffff>());
-
-    constexpr static GrUserStencilSettings kFillOrInvertStencil(
-        GrUserStencilSettings::StaticInit<
-            0x0000,
-            GrUserStencilTest::kEqual,
-            0xffff,
-            GrUserStencilOp::kKeep,
-            GrUserStencilOp::kZero,
-            0xffff>());
-
-    this->prePreparePipelineForFills(args);
-
-    const GrUserStencilSettings* stencil;
-    if (fStencilTrianglesProgram) {
-        // The path was already stencilled. Here we just need to do a cover pass.
-        stencil = &kTestAndResetStencil;
-    } else if (isLinear) {
-        // There are no stencilled curves. We can ignore stencil and fill the path directly.
-        stencil = &GrUserStencilSettings::kUnused;
-    } else if (SkPathFillType::kWinding == fPath.getFillType()) {
-        // Fill in the path pixels not touched by curves, incr/decr stencil otherwise.
-        SkASSERT(!fPipelineForFills->hasStencilClip());
-        stencil = &kFillOrIncrDecrStencil;
-    } else {
-        // Fill in the path pixels not touched by curves, invert stencil otherwise.
-        SkASSERT(!fPipelineForFills->hasStencilClip());
-        stencil = &kFillOrInvertStencil;
-    }
-
-    auto* fillTriangleShader = args.fArena->make<GrFillTriangleShader>(fViewMatrix, fColor);
-    fFillTrianglesProgram = GrPathShader::MakeProgramInfo(
-            fillTriangleShader, args.fArena, args.fWriteView, fPipelineForFills,
-            *args.fDstProxyView, args.fXferBarrierFlags, args.fColorLoadOp, stencil, *args.fCaps);
-}
-
-void GrPathTessellateOp::prePrepareFillCubicHullsProgram(const PrePrepareArgs& args) {
-    SkASSERT(!fFillPathProgram);
-
-    if (fOpFlags & OpFlags::kStencilOnly) {
-        return;
-    }
-
-    this->prePreparePipelineForFills(args);
-
-    auto* fillCubicHullsShader = args.fArena->make<GrFillCubicHullShader>(fViewMatrix, fColor);
-    fFillPathProgram = GrPathShader::MakeProgramInfo(
-            fillCubicHullsShader, args.fArena, args.fWriteView, fPipelineForFills,
-            *args.fDstProxyView, args.fXferBarrierFlags, args.fColorLoadOp, &kTestAndResetStencil,
-            *args.fCaps);
-}
-
-void GrPathTessellateOp::prePrepareFillBoundingBoxProgram(const PrePrepareArgs& args) {
-    SkASSERT(!fFillPathProgram);
-
-    if (fOpFlags & OpFlags::kStencilOnly) {
-        return;
-    }
-
-    this->prePreparePipelineForFills(args);
-
-    auto* fillBoundingBoxShader = args.fArena->make<GrFillBoundingBoxShader>(fViewMatrix, fColor,
-                                                                             fPath.getBounds());
-    fFillPathProgram = GrPathShader::MakeProgramInfo(
-            fillBoundingBoxShader, args.fArena, args.fWriteView, fPipelineForFills,
-            *args.fDstProxyView, args.fXferBarrierFlags, args.fColorLoadOp, &kTestAndResetStencil,
-            *args.fCaps);
-}
-
-void GrPathTessellateOp::prePreparePipelineForFills(const PrePrepareArgs& args) {
-    SkASSERT(!(fOpFlags & OpFlags::kStencilOnly));
-
-    if (fPipelineForFills) {
-        return;
-    }
-
-    auto pipelineFlags = GrPipeline::InputFlags::kNone;
-    if (GrAAType::kNone != fAAType) {
-        if (args.fWriteView.asRenderTargetProxy()->numSamples() == 1) {
-            // We are mixed sampled. We need to either enable conservative raster (preferred) or
-            // disable MSAA in order to avoid double blend artifacts. (Even if we disable MSAA for
-            // the cover geometry, the stencil test is still multisampled and will still produce
-            // smooth results.)
-            SkASSERT(GrAAType::kCoverage == fAAType);
-            if (args.fCaps->conservativeRasterSupport()) {
-                pipelineFlags |= GrPipeline::InputFlags::kHWAntialias;
-                pipelineFlags |= GrPipeline::InputFlags::kConservativeRaster;
-            }
-        } else {
-            // We are standard MSAA. Leave MSAA enabled for the cover geometry.
-            pipelineFlags |= GrPipeline::InputFlags::kHWAntialias;
-        }
-    }
-
-    fPipelineForFills = GrSimpleMeshDrawOpHelper::CreatePipeline(
-            args.fCaps, args.fArena, args.fWriteView.swizzle(),
-            (args.fClip) ? std::move(*args.fClip) : GrAppliedClip::Disabled(), *args.fDstProxyView,
-            std::move(fProcessors), pipelineFlags);
-}
-
-void GrPathTessellateOp::onPrepare(GrOpFlushState* flushState) {
-    int numVerbs = fPath.countVerbs();
-    if (numVerbs <= 0) {
-        return;
-    }
-
-    if (!fPipelineForStencils && !fPipelineForFills) {
-        // Nothing has been prePrepared yet. Do it now.
-        GrAppliedHardClip hardClip = GrAppliedHardClip(flushState->appliedHardClip());
-        GrAppliedClip clip = flushState->detachAppliedClip();
-        PrePrepareArgs args{flushState->allocator(), flushState->writeView(), &hardClip,
-                            &clip, &flushState->dstProxyView(),
-                            flushState->renderPassBarriers(), flushState->colorLoadOp(),
-                            &flushState->caps()};
-        this->prePreparePrograms(args);
-    }
-
-    if (fInnerFanPolys) {
-        // prePreparePrograms was able to generate an inner polygon triangulation. It will exist in
-        // either fOffThreadInnerTriangulation or fTriangleBuffer exclusively.
-        SkASSERT(fInnerFanTriangulator);
-        GrEagerDynamicVertexAllocator alloc(flushState, &fTriangleBuffer, &fBaseTriangleVertex);
-        fTriangleVertexCount = fInnerFanTriangulator->polysToTriangles(fInnerFanPolys, &alloc,
-                                                                       nullptr);
-    } else if (fStencilTrianglesProgram) {
-        // The inner fan isn't built into the tessellator. Generate a standard Redbook fan with a
-        // middle-out topology.
-        GrEagerDynamicVertexAllocator vertexAlloc(flushState, &fTriangleBuffer,
-                                                  &fBaseTriangleVertex);
-        // No initial moveTo, plus an implicit close at the end; n-2 triangles fill an n-gon.
-        int maxInnerTriangles = fPath.countVerbs() - 1;
-        auto* triangleVertexData = vertexAlloc.lock<SkPoint>(maxInnerTriangles * 3);
-        fTriangleVertexCount = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
-                triangleVertexData, 3/*perTriangleVertexAdvance*/, fPath) * 3;
-        vertexAlloc.unlock(fTriangleVertexCount);
-    }
-
-    if (fTessellator) {
-        fTessellator->prepare(flushState, fViewMatrix, fPath);
-    }
-}
-
-void GrPathTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
-    this->drawStencilPass(flushState);
-    this->drawCoverPass(flushState);
-}
-
-void GrPathTessellateOp::drawStencilPass(GrOpFlushState* flushState) {
-    if (fStencilTrianglesProgram && fTriangleVertexCount > 0) {
-        SkASSERT(fTriangleBuffer);
-        flushState->bindPipelineAndScissorClip(*fStencilTrianglesProgram, this->bounds());
-        flushState->bindBuffers(nullptr, nullptr, fTriangleBuffer);
-        flushState->draw(fTriangleVertexCount, fBaseTriangleVertex);
-    }
-
-    if (fTessellator) {
-        flushState->bindPipelineAndScissorClip(*fStencilCubicsProgram, this->bounds());
-        fTessellator->draw(flushState);
-    }
-}
-
-void GrPathTessellateOp::drawCoverPass(GrOpFlushState* flushState) {
-    if (fFillTrianglesProgram) {
-        SkASSERT(fTriangleBuffer);
-
-        // We have a triangulation of the path's inner polygon. This is the fast path. Fill those
-        // triangles directly to the screen.
-        if (fTriangleVertexCount > 0) {
-            flushState->bindPipelineAndScissorClip(*fFillTrianglesProgram, this->bounds());
-            flushState->bindTextures(fFillTrianglesProgram->primProc(), nullptr,
-                                     *fPipelineForFills);
-            flushState->bindBuffers(nullptr, nullptr, fTriangleBuffer);
-            flushState->draw(fTriangleVertexCount, fBaseTriangleVertex);
-        }
-
-        if (fTessellator) {
-            // At this point, every pixel is filled in except the ones touched by curves.
-            // fFillPathProgram will issue a final cover pass over the curves by drawing their
-            // convex hulls. This will fill in any remaining samples and reset the stencil buffer.
-            SkASSERT(fFillPathProgram);
-            flushState->bindPipelineAndScissorClip(*fFillPathProgram, this->bounds());
-            flushState->bindTextures(fFillPathProgram->primProc(), nullptr, *fPipelineForFills);
-            fTessellator->drawHullInstances(flushState);
-        }
-    } else if (fFillPathProgram) {
-        // There are no triangles to fill. Just draw a bounding box.
-        flushState->bindPipelineAndScissorClip(*fFillPathProgram, this->bounds());
-        flushState->bindTextures(fFillPathProgram->primProc(), nullptr, *fPipelineForFills);
-        flushState->bindBuffers(nullptr, nullptr, nullptr);
-        flushState->draw(4, 0);
-    }
-}
diff --git a/src/gpu/tessellate/GrPathTessellateOp.h b/src/gpu/tessellate/GrPathTessellateOp.h
deleted file mode 100644
index a94ca26..0000000
--- a/src/gpu/tessellate/GrPathTessellateOp.h
+++ /dev/null
@@ -1,153 +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.
- */
-
-#ifndef GrPathTessellateOp_DEFINED
-#define GrPathTessellateOp_DEFINED
-
-#include "src/gpu/GrInnerFanTriangulator.h"
-#include "src/gpu/ops/GrMeshDrawOp.h"
-#include "src/gpu/tessellate/GrPathTessellator.h"
-#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
-
-class GrAppliedHardClip;
-class GrEagerVertexAllocator;
-class GrStencilPathShader;
-class GrResolveLevelCounter;
-
-// Renders paths using a hybrid "Red Book" (stencil, then cover) method. Curves get linearized by
-// either GPU tessellation shaders or indirect draws. This Op doesn't apply analytic AA, so it
-// requires a render target that supports either MSAA or mixed samples if AA is desired.
-class GrPathTessellateOp : public GrDrawOp {
-private:
-    DEFINE_OP_CLASS_ID
-
-    GrPathTessellateOp(const SkMatrix& viewMatrix, const SkPath& path, GrPaint&& paint,
-                       GrAAType aaType, GrTessellationPathRenderer::OpFlags opFlags)
-            : GrDrawOp(ClassID())
-            , fOpFlags(opFlags)
-            , fViewMatrix(viewMatrix)
-            , fPath(path)
-            , fAAType(aaType)
-            , fColor(paint.getColor4f())
-            , fProcessors(std::move(paint)) {
-        SkRect devBounds;
-        fViewMatrix.mapRect(&devBounds, path.getBounds());
-        this->setBounds(devBounds, HasAABloat(GrAAType::kCoverage == fAAType), IsHairline::kNo);
-    }
-
-    const char* name() const override { return "GrPathTessellateOp"; }
-    void visitProxies(const VisitProxyFunc& fn) const override;
-    GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip,
-                                      bool hasMixedSampledCoverage,
-                                      GrClampType clampType) override {
-        return fProcessors.finalize(
-                fColor, GrProcessorAnalysisCoverage::kNone, clip, &GrUserStencilSettings::kUnused,
-                hasMixedSampledCoverage, caps, clampType, &fColor);
-    }
-
-    FixedFunctionFlags fixedFunctionFlags() const override;
-
-    void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*,
-                      const GrXferProcessor::DstProxyView&, GrXferBarrierFlags,
-                      GrLoadOp colorLoadOp) override;
-
-    struct PrePrepareArgs {
-        SkArenaAlloc* fArena;
-        const GrSurfaceProxyView& fWriteView;
-        const GrAppliedHardClip* fHardClip;
-        GrAppliedClip* fClip;
-        const GrXferProcessor::DstProxyView* fDstProxyView;
-        GrXferBarrierFlags fXferBarrierFlags;
-        GrLoadOp fColorLoadOp;
-        const GrCaps* fCaps;
-    };
-
-    // Chooses the rendering method we will use and creates the corresponding stencil and fill
-    // programs up front.
-    void prePreparePrograms(const PrePrepareArgs&);
-
-    // Produces a non-overlapping triangulation of the path's inner polygon(s). The inner polygons
-    // connect the endpoints of each verb. (i.e., they are the path that would result from
-    // collapsing all curves to single lines.) If this succeeds, then we will be able to fill the
-    // triangles directly and bypass stencilling them.
-    //
-    // Returns false if the inner triangles do not form a simple polygon (e.g., self intersection,
-    // double winding). Non-simple polygons would need to split edges in order to avoid overlap,
-    // and this is not an option as it would introduce T-junctions with the outer cubics.
-    bool prePrepareInnerPolygonTriangulation(const PrePrepareArgs&, bool* isLinear);
-
-    void prePrepareStencilTrianglesProgram(const PrePrepareArgs&);
-    template<typename ShaderType> void prePrepareStencilCubicsProgram(const PrePrepareArgs&);
-    void prePreparePipelineForStencils(const PrePrepareArgs&);
-
-    void prePrepareFillTrianglesProgram(const PrePrepareArgs&, bool isLinear);
-    void prePrepareFillCubicHullsProgram(const PrePrepareArgs&);
-    void prePrepareFillBoundingBoxProgram(const PrePrepareArgs&);
-    void prePreparePipelineForFills(const PrePrepareArgs&);
-
-    void onPrepare(GrOpFlushState*) override;
-
-    void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
-    void drawStencilPass(GrOpFlushState*);
-    void drawCoverPass(GrOpFlushState*);
-
-    const GrTessellationPathRenderer::OpFlags fOpFlags;
-    const SkMatrix fViewMatrix;
-    const SkPath fPath;
-    const GrAAType fAAType;
-    SkPMColor4f fColor;
-    GrProcessorSet fProcessors;
-
-    GrInnerFanTriangulator* fInnerFanTriangulator = nullptr;
-    GrTriangulator::Poly* fInnerFanPolys = nullptr;
-    sk_sp<const GrBuffer> fTriangleBuffer;
-    int fBaseTriangleVertex = 0;
-    int fTriangleVertexCount = 0;
-
-    // This pipeline is used by all programInfos in the stencil step.
-    const GrPipeline* fPipelineForStencils = nullptr;
-
-    // This pipeline is used by all programInfos in the fill step.
-    const GrPipeline* fPipelineForFills = nullptr;
-
-    // These switches specify how the above fTriangleBuffer should be drawn (if at all).
-    //
-    // If stencil and !fill:
-    //
-    //     We just stencil the triangles (with cubics) during the stencil step. The path gets filled
-    //     later using a simple bounding box if needed.
-    //
-    // If stencil and fill:
-    //
-    //     We still stencil the triangles and cubics normally, but during the *fill* step we fill
-    //     the triangles plus local convex hulls around each cubic instead of a bounding box.
-    //
-    // If !stencil and fill:
-    //
-    //     This means that fTriangleBuffer contains non-overlapping geometry that can be filled
-    //     directly to the final render target. We only need to stencil *curves*, and during the
-    //     fill step we draw the triangles directly with a stencil test that accounts for curves
-    //     (see drawCoverPass()), and then finally fill the curves with local convex hulls.
-    const GrProgramInfo* fStencilTrianglesProgram = nullptr;
-    const GrProgramInfo* fFillTrianglesProgram = nullptr;
-
-    const GrProgramInfo* fStencilCubicsProgram = nullptr;
-
-    // This will draw either a bounding box, or if fFillTrianglesProgram exists, individual convex
-    // hulls covering each cubic.
-    const GrProgramInfo* fFillPathProgram = nullptr;
-
-    GrPathTessellator* fTessellator = nullptr;
-
-    friend class GrOp;  // For ctor.
-
-public:
-    // This serves as a base class for benchmarking individual methods on GrPathTessellateOp.
-    class TestingOnly_Benchmark;
-};
-
-#endif
diff --git a/src/gpu/tessellate/GrPathTessellator.cpp b/src/gpu/tessellate/GrPathTessellator.cpp
index 91b3409..adeceb9 100644
--- a/src/gpu/tessellate/GrPathTessellator.cpp
+++ b/src/gpu/tessellate/GrPathTessellator.cpp
@@ -47,7 +47,8 @@
 }
 
 void GrPathIndirectTessellator::prepare(GrMeshDrawOp::Target* target, const SkMatrix& viewMatrix,
-                                        const SkPath& path) {
+                                        const SkPath& path,
+                                        const BreadcrumbTriangleList* breadcrumbTriangleList) {
     SkASSERT(fTotalInstanceCount == 0);
     SkASSERT(fIndirectDrawCount == 0);
     SkASSERT(target->caps().drawInstancedSupport());
@@ -58,6 +59,9 @@
         int maxInnerTriangles = path.countVerbs() - 1;
         instanceLockCount += maxInnerTriangles;
     }
+    if (breadcrumbTriangleList) {
+        instanceLockCount += breadcrumbTriangleList->count();
+    }
     if (instanceLockCount == 0) {
         return;
     }
@@ -74,7 +78,26 @@
     int numTrianglesAtBeginningOfData = 0;
     if (fDrawInnerFan) {
         numTrianglesAtBeginningOfData = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
-                instanceData, 4/*perTriangleVertexAdvance*/, path);
+                instanceData + numTrianglesAtBeginningOfData * 4, 4/*stride*/, path);
+    }
+    if (breadcrumbTriangleList) {
+        SkDEBUGCODE(int count = 0;)
+        for (const auto* tri = breadcrumbTriangleList->head(); tri; tri = tri->fNext) {
+            SkDEBUGCODE(++count;)
+            const SkPoint* p = tri->fPts;
+            if ((p[0].fX == p[1].fX && p[1].fX == p[2].fX) ||
+                (p[0].fY == p[1].fY && p[1].fY == p[2].fY)) {
+                // Completely degenerate triangles have undefined winding. And T-junctions shouldn't
+                // happen on axis-aligned edges.
+                continue;
+            }
+            SkPoint* breadcrumbData = instanceData + numTrianglesAtBeginningOfData * 4;
+            memcpy(breadcrumbData, p, sizeof(SkPoint) * 3);
+            // Duplicate the final point since it will also be used by the convex hull shader.
+            breadcrumbData[3] = p[2];
+            ++numTrianglesAtBeginningOfData;
+        }
+        SkASSERT(count == breadcrumbTriangleList->count());
     }
 
     fIndirectIndexBuffer = GrMiddleOutCubicShader::FindOrMakeMiddleOutIndexBuffer(
@@ -209,8 +232,10 @@
 }
 
 void GrPathOuterCurveTessellator::prepare(GrMeshDrawOp::Target* target, const SkMatrix& matrix,
-                                          const SkPath& path) {
+                                          const SkPath& path,
+                                          const BreadcrumbTriangleList* breadcrumbTriangleList) {
     SkASSERT(target->caps().shaderCaps()->tessellationSupport());
+    SkASSERT(!breadcrumbTriangleList);
     SkASSERT(!fPatchBuffer);
     SkASSERT(fPatchVertexCount == 0);
 
@@ -245,8 +270,10 @@
 }
 
 void GrPathWedgeTessellator::prepare(GrMeshDrawOp::Target* target, const SkMatrix& matrix,
-                                     const SkPath& path) {
+                                     const SkPath& path,
+                                     const BreadcrumbTriangleList* breadcrumbTriangleList) {
     SkASSERT(target->caps().shaderCaps()->tessellationSupport());
+    SkASSERT(!breadcrumbTriangleList);
     SkASSERT(!fPatchBuffer);
     SkASSERT(fPatchVertexCount == 0);
 
diff --git a/src/gpu/tessellate/GrPathTessellator.h b/src/gpu/tessellate/GrPathTessellator.h
index 461916c..2545e60 100644
--- a/src/gpu/tessellate/GrPathTessellator.h
+++ b/src/gpu/tessellate/GrPathTessellator.h
@@ -8,6 +8,7 @@
 #ifndef GrPathTessellator_DEFINED
 #define GrPathTessellator_DEFINED
 
+#include "src/gpu/GrInnerFanTriangulator.h"
 #include "src/gpu/ops/GrMeshDrawOp.h"
 #include "src/gpu/tessellate/GrTessellationPathRenderer.h"
 
@@ -17,8 +18,13 @@
 // the caller may or may not be required to draw the path's inner fan separately.
 class GrPathTessellator {
 public:
-    // Called before draw(). Prepares GPU buffers containing the geometry to tessellate.
-    virtual void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&) = 0;
+    using BreadcrumbTriangleList = GrInnerFanTriangulator::BreadcrumbTriangleList;
+
+    // Called before draw(). Prepares GPU buffers containing the geometry to tessellate. If the
+    // given BreadcrumbTriangleList is non-null, then this class will also include the breadcrumb
+    // triangles in its draw.
+    virtual void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
+                         const BreadcrumbTriangleList* = nullptr) = 0;
 
     // Issues draw calls for the tessellated geometry. The caller is responsible for binding its
     // desired pipeline ahead of time.
@@ -44,7 +50,8 @@
     enum class DrawInnerFan : bool { kNo = false, kYes };
     GrPathIndirectTessellator(const SkMatrix&, const SkPath&, DrawInnerFan);
 
-    void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&) override;
+    void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
+                 const BreadcrumbTriangleList*) override;
     void draw(GrOpFlushState*) const override;
     void drawHullInstances(GrOpFlushState*) const override;
 
@@ -87,7 +94,8 @@
 public:
     GrPathOuterCurveTessellator() = default;
 
-    void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&) override;
+    void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
+                 const BreadcrumbTriangleList*) override;
 };
 
 // Draws an array of "wedge" patches for GrWedgeTessellateShader. A wedge is an independent, 5-point
@@ -98,7 +106,8 @@
 public:
     GrPathWedgeTessellator() = default;
 
-    void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&) override;
+    void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
+                 const BreadcrumbTriangleList*) override;
 };
 
 #endif
diff --git a/src/gpu/tessellate/GrTessellationPathRenderer.cpp b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
index 1055d14..b525c38 100644
--- a/src/gpu/tessellate/GrTessellationPathRenderer.cpp
+++ b/src/gpu/tessellate/GrTessellationPathRenderer.cpp
@@ -17,7 +17,7 @@
 #include "src/gpu/geometry/GrStyledShape.h"
 #include "src/gpu/ops/GrFillRectOp.h"
 #include "src/gpu/tessellate/GrDrawAtlasPathOp.h"
-#include "src/gpu/tessellate/GrPathTessellateOp.h"
+#include "src/gpu/tessellate/GrPathInnerTriangulateOp.h"
 #include "src/gpu/tessellate/GrStrokeIndirectOp.h"
 #include "src/gpu/tessellate/GrStrokeTessellateOp.h"
 #include "src/gpu/tessellate/GrTessellatingStencilFillOp.h"
@@ -247,8 +247,8 @@
             constexpr static float kCpuWeight = 512;
             constexpr static float kMinNumPixelsToTriangulate = 256 * 256;
             if (cpuTessellationWork * kCpuWeight + kMinNumPixelsToTriangulate < gpuFragmentWork) {
-                return GrOp::Make<GrPathTessellateOp>(rContext, viewMatrix, path, std::move(paint),
-                                                      aaType, opFlags);
+                return GrOp::Make<GrPathInnerTriangulateOp>(rContext, viewMatrix, path,
+                                                            std::move(paint), aaType, opFlags);
             }
         }
         return GrOp::Make<GrTessellatingStencilFillOp>(rContext, viewMatrix, path, std::move(paint),