Replace the indirect patch tessellator with fixed count

Bug: skia:10419
Change-Id: Icb3395565060d736624d03ba1465926bd9188e44
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/416078
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/tessellate/GrPathCurveTessellator.cpp b/src/gpu/tessellate/GrPathCurveTessellator.cpp
index d4b3304..706f9a0 100644
--- a/src/gpu/tessellate/GrPathCurveTessellator.cpp
+++ b/src/gpu/tessellate/GrPathCurveTessellator.cpp
@@ -21,16 +21,11 @@
 // supported by the hardware.
 class CurveWriter {
 public:
-    CurveWriter(const SkRect& cullBounds, const SkMatrix& viewMatrix,
-                const GrShaderCaps& shaderCaps)
+    CurveWriter(const SkRect& cullBounds, const SkMatrix& viewMatrix, int maxSegments)
             : fCullTest(cullBounds, viewMatrix)
-            , fVectorXform(viewMatrix) {
-        // GrCurveTessellateShader tessellates T=0..(1/2) on the first side of the triangle and
-        // T=(1/2)..1 on the second side. This means we get double the max tessellation segments
-        // for the range T=0..1.
-        float maxSegments = shaderCaps.maxTessellationSegments() * 2;
-        fMaxSegments_pow2 = maxSegments * maxSegments;
-        fMaxSegments_pow4 = fMaxSegments_pow2 * fMaxSegments_pow2;
+            , fVectorXform(viewMatrix)
+            , fMaxSegments_pow2(maxSegments * maxSegments)
+            , fMaxSegments_pow4(fMaxSegments_pow2 * fMaxSegments_pow2) {
     }
 
     SK_ALWAYS_INLINE void writeQuadratic(GrVertexChunkBuilder* chunker, const SkPoint p[3]) {
@@ -43,6 +38,7 @@
             if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
                 GrPathUtils::writeQuadAsCubic(p, &vertexWriter);
             }
+            fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4);
         }
     }
 
@@ -56,6 +52,8 @@
             if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
                 GrTessellationShader::WriteConicPatch(p, w, &vertexWriter);
             }
+            fNumFixedSegments_pow4 = std::max(numSegments_pow2 * numSegments_pow2,
+                                              fNumFixedSegments_pow4);
         }
     }
 
@@ -69,9 +67,12 @@
             if (GrVertexWriter vertexWriter = chunker->appendVertex()) {
                 vertexWriter.writeArray(p, 4);
             }
+            fNumFixedSegments_pow4 = std::max(numSegments_pow4, fNumFixedSegments_pow4);
         }
     }
 
+    int numFixedSegments_pow4() const { return fNumFixedSegments_pow4; }
+
 private:
     void chopAndWriteQuadratic(GrVertexChunkBuilder* chunker, const SkPoint p[3]) {
         SkPoint chops[5];
@@ -123,31 +124,58 @@
 
     GrCullTest fCullTest;
     GrVectorXform fVectorXform;
-    float fMaxSegments_pow2;
-    float fMaxSegments_pow4;
+    const float fMaxSegments_pow2;
+    const float fMaxSegments_pow4;
+
+    // If using fixed count, this is the number of segments we need to emit per instance. Always
+    // emit at least 2 segments so we can support triangles.
+    float fNumFixedSegments_pow4 = 2*2*2*2;
 };
 
 }  // namespace
 
 
-GrPathTessellator* GrPathCurveTessellator::Make(SkArenaAlloc* arena,
-                                                     const SkMatrix& viewMatrix,
-                                                     const SkPMColor4f& color,
-                                                     DrawInnerFan drawInnerFan) {
-    auto shader = GrPathTessellationShader::MakeHardwareCurveShader(arena, viewMatrix, color);
-    return arena->make<GrPathCurveTessellator>(shader, drawInnerFan);
+GrPathTessellator* GrPathCurveTessellator::Make(SkArenaAlloc* arena, const SkMatrix& viewMatrix,
+                                                const SkPMColor4f& color, DrawInnerFan drawInnerFan,
+                                                int numPathVerbs, const GrCaps& caps) {
+    if (caps.shaderCaps()->tessellationSupport() &&
+        numPathVerbs >= caps.minPathVerbsForHwTessellation()) {
+        return Make(arena, viewMatrix, color, drawInnerFan, ShaderType::kHardwareTessellation);
+    } else {
+        return Make(arena, viewMatrix, color, drawInnerFan, ShaderType::kFixedCountMiddleOut);
+    }
+}
+
+GrPathTessellator* GrPathCurveTessellator::Make(SkArenaAlloc* arena, const SkMatrix& viewMatrix,
+                                                const SkPMColor4f& color, DrawInnerFan drawInnerFan,
+                                                ShaderType shaderType) {
+    GrPathTessellationShader* shader;
+    switch (shaderType) {
+        case ShaderType::kFixedCountMiddleOut:
+            shader = GrPathTessellationShader::MakeMiddleOutInstancedShader(arena, viewMatrix,
+                                                                            color);
+            break;
+        case ShaderType::kHardwareTessellation:
+            shader = GrPathTessellationShader::MakeHardwareCurveShader(arena, viewMatrix, color);
+            break;
+    }
+    return arena->make([=](void* objStart) {
+        return new(objStart) GrPathCurveTessellator(shader, drawInnerFan);
+    });
 }
 
 void GrPathCurveTessellator::prepare(GrMeshDrawOp::Target* target, const SkRect& cullBounds,
-                                          const SkPath& path,
-                                          const BreadcrumbTriangleList* breadcrumbTriangleList) {
-    SkASSERT(target->caps().shaderCaps()->tessellationSupport());
+                                     const SkPath& path,
+                                     const BreadcrumbTriangleList* breadcrumbTriangleList) {
     SkASSERT(fVertexChunkArray.empty());
 
     // Determine how many triangles to allocate.
     int maxTriangles = 0;
     if (fDrawInnerFan) {
-        maxTriangles += GrPathTessellator::MaxTrianglesInInnerFan(path);
+        // An n-sided polygon is fanned by n-2 triangles.
+        int maxEdgesInFan = GrPathTessellator::MaxSegmentsInPath(path);
+        int maxTrianglesInFan = std::max(maxEdgesInFan - 2, 0);
+        maxTriangles += maxTrianglesInFan;
     }
     if (breadcrumbTriangleList) {
         maxTriangles += breadcrumbTriangleList->count();
@@ -174,14 +202,43 @@
             numRemainingTriangles -= numWritten;
         }
         if (breadcrumbTriangleList) {
-            int numWritten = GrPathTessellator::WriteBreadcrumbTriangles(&vertexWriter,
-                                                                         breadcrumbTriangleList);
+            int numWritten = 0;
+            SkDEBUGCODE(int count = 0;)
+            for (const auto* tri = breadcrumbTriangleList->head(); tri; tri = tri->fNext) {
+                SkDEBUGCODE(++count;)
+                auto p0 = grvx::float2::Load(tri->fPts);
+                auto p1 = grvx::float2::Load(tri->fPts + 1);
+                auto p2 = grvx::float2::Load(tri->fPts + 2);
+                if (skvx::any((p0 == p1) & (p1 == p2))) {
+                    // Cull completely horizontal or vertical triangles. GrTriangulator can't always
+                    // get these breadcrumb edges right when they run parallel to the sweep
+                    // direction because their winding is undefined by its current definition.
+                    // FIXME(skia:12060): This seemed safe, but if there is a view matrix it will
+                    // introduce T-junctions.
+                    continue;
+                }
+                vertexWriter.writeArray(tri->fPts, 3);
+                // Mark this instance as a triangle by setting it to a conic with w=Inf.
+                vertexWriter.fill(GrVertexWriter::kIEEE_32_infinity, 2);
+                ++numWritten;
+            }
+            SkASSERT(count == breadcrumbTriangleList->count());
             numRemainingTriangles -= numWritten;
         }
         chunker.popVertices(numRemainingTriangles);
     }
 
-    CurveWriter curveWriter(cullBounds, fShader->viewMatrix(), *target->caps().shaderCaps());
+    int maxSegments;
+    if (fShader->willUseTessellationShaders()) {
+        // The curve shader tessellates T=0..(1/2) on the first side of the canonical triangle and
+        // T=(1/2)..1 on the second side. This means we get double the max tessellation segments
+        // for the range T=0..1.
+        maxSegments = target->caps().shaderCaps()->maxTessellationSegments() * 2;
+    } else {
+        maxSegments = kMaxFixedCountSegments;
+    }
+
+    CurveWriter curveWriter(cullBounds, fShader->viewMatrix(), maxSegments);
     for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
         switch (verb) {
             case SkPathVerb::kQuad:
@@ -197,11 +254,27 @@
                 break;
         }
     }
+
+    if (!fShader->willUseTessellationShaders()) {
+        // log2(n) == log16(n^4).
+        int fixedResolveLevel = GrWangsFormula::nextlog16(curveWriter.numFixedSegments_pow4());
+        fFixedVertexCount =
+                GrPathTessellationShader::NumTrianglesAtResolveLevel(fixedResolveLevel) * 3;
+    }
 }
+
 void GrPathCurveTessellator::draw(GrOpFlushState* flushState) const {
-    for (const GrVertexChunk& chunk : fVertexChunkArray) {
-        flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
-        flushState->draw(chunk.fCount * 4, chunk.fBase * 4);
+    if (fShader->willUseTessellationShaders()) {
+        for (const GrVertexChunk& chunk : fVertexChunkArray) {
+            flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
+            flushState->draw(chunk.fCount * 4, chunk.fBase * 4);
+        }
+    } else {
+        SkASSERT(fShader->hasInstanceAttributes());
+        for (const GrVertexChunk& chunk : fVertexChunkArray) {
+            flushState->bindBuffers(nullptr, chunk.fBuffer, nullptr);
+            flushState->drawInstanced(chunk.fCount, chunk.fBase, fFixedVertexCount, 0);
+        }
     }
 }
 
diff --git a/src/gpu/tessellate/GrPathCurveTessellator.h b/src/gpu/tessellate/GrPathCurveTessellator.h
index c0785db..10e2d57 100644
--- a/src/gpu/tessellate/GrPathCurveTessellator.h
+++ b/src/gpu/tessellate/GrPathCurveTessellator.h
@@ -16,8 +16,18 @@
 // or a conic. Quadratics are converted to cubics and triangles are converted to conics with w=Inf.
 class GrPathCurveTessellator : public GrPathTessellator {
 public:
-    static GrPathTessellator* Make(SkArenaAlloc*, const SkMatrix&, const SkPMColor4f&,
-                                   DrawInnerFan);
+    // Creates a curve tessellator with the shader type best suited for the given path description.
+    static GrPathTessellator* Make(SkArenaAlloc*, const SkMatrix& viewMatrix, const SkPMColor4f&,
+                                   DrawInnerFan, int numPathVerbs, const GrCaps&);
+
+    enum class ShaderType {
+        kFixedCountMiddleOut,
+        kHardwareTessellation
+    };
+
+    // Creates a curve tessellator with a specific shader type.
+    static GrPathTessellator* Make(SkArenaAlloc*, const SkMatrix& viewMatrix, const SkPMColor4f&,
+                                   DrawInnerFan, ShaderType);
 
     void prepare(GrMeshDrawOp::Target*, const SkRect& cullBounds, const SkPath&,
                  const BreadcrumbTriangleList*) override;
@@ -32,7 +42,8 @@
     const bool fDrawInnerFan;
     GrVertexChunkArray fVertexChunkArray;
 
-    friend class SkArenaAlloc;  // For constructor.
+    // If using fixed count, this is the number of vertices we need to emit per instance.
+    int fFixedVertexCount;
 };
 
 #endif
diff --git a/src/gpu/tessellate/GrPathIndirectTessellator.cpp b/src/gpu/tessellate/GrPathIndirectTessellator.cpp
deleted file mode 100644
index 80aa3ff..0000000
--- a/src/gpu/tessellate/GrPathIndirectTessellator.cpp
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright 2021 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/GrPathIndirectTessellator.h"
-
-#include "src/gpu/GrEagerVertexAllocator.h"
-#include "src/gpu/geometry/GrPathUtils.h"
-#include "src/gpu/geometry/GrWangsFormula.h"
-#include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
-#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
-
-constexpr static float kPrecision = GrTessellationPathRenderer::kLinearizationPrecision;
-
-GrPathTessellator* GrPathIndirectTessellator::Make(SkArenaAlloc* arena, const SkPath& path,
-                                                   const SkMatrix& viewMatrix,
-                                                   const SkPMColor4f& color,
-                                                   DrawInnerFan drawInnerFan) {
-    auto shader = GrPathTessellationShader::MakeMiddleOutInstancedShader(arena, viewMatrix, color);
-    return arena->make<GrPathIndirectTessellator>(shader, path, drawInnerFan);
-}
-
-GrPathIndirectTessellator::GrPathIndirectTessellator(GrPathTessellationShader* shader,
-                                                     const SkPath& path, DrawInnerFan drawInnerFan)
-        : GrPathTessellator(shader)
-        , fDrawInnerFan(drawInnerFan != DrawInnerFan::kNo) {
-    // Count the number of instances at each resolveLevel.
-    GrVectorXform xform(fShader->viewMatrix());
-    for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
-        int level;
-        switch (verb) {
-            case SkPathVerb::kQuad:
-                level = GrWangsFormula::quadratic_log2(kPrecision, pts, xform);
-                break;
-            case SkPathVerb::kConic:
-                level = GrWangsFormula::conic_log2(kPrecision, pts, *w, xform);
-                break;
-            case SkPathVerb::kCubic:
-                level = GrWangsFormula::cubic_log2(kPrecision, pts, xform);
-                break;
-            default:
-                continue;
-        }
-        SkASSERT(level >= 0);
-        // Instances with 2^0=1 segments are empty (zero area). We ignore them completely.
-        if (level > 0) {
-            level = std::min(level, kMaxResolveLevel);
-            ++fResolveLevelCounts[level];
-            ++fOuterCurveInstanceCount;
-        }
-    }
-}
-
-// How many vertices do we need to draw in order to triangulate a curve with 2^resolveLevel line
-// segments?
-constexpr static int num_vertices_at_resolve_level(int resolveLevel) {
-    // resolveLevel=0 -> 0 line segments -> 0 triangles -> 0 vertices
-    // resolveLevel=1 -> 2 line segments -> 1 triangle -> 3 vertices
-    // resolveLevel=2 -> 4 line segments -> 3 triangles -> 9 vertices
-    // resolveLevel=3 -> 8 line segments -> 7 triangles -> 21 vertices
-    // ...
-    return ((1 << resolveLevel) - 1) * 3;
-}
-
-void GrPathIndirectTessellator::prepare(GrMeshDrawOp::Target* target, const SkRect& /*cullBounds*/,
-                                        const SkPath& path,
-                                        const BreadcrumbTriangleList* breadcrumbTriangleList) {
-    SkASSERT(fTotalInstanceCount == 0);
-    SkASSERT(fIndirectDrawCount == 0);
-    SkASSERT(target->caps().drawInstancedSupport());
-
-    int instanceLockCount = fOuterCurveInstanceCount;
-    if (fDrawInnerFan) {
-        instanceLockCount += GrPathTessellator::MaxTrianglesInInnerFan(path);
-    }
-    if (breadcrumbTriangleList) {
-        instanceLockCount += breadcrumbTriangleList->count();
-    }
-    if (instanceLockCount == 0) {
-        return;
-    }
-
-    // Allocate a buffer to store the instance data.
-    GrEagerDynamicVertexAllocator vertexAlloc(target, &fInstanceBuffer, &fBaseInstance);
-    GrVertexWriter instanceWriter = static_cast<SkPoint*>(vertexAlloc.lock(sizeof(SkPoint) * 4,
-                                                                           instanceLockCount));
-    if (!instanceWriter) {
-        return;
-    }
-
-    // Write out any triangles at the beginning of the cubic data. Since this shader draws curves,
-    // output the triangles as conics with w=infinity (which is equivalent to a triangle).
-    int numTrianglesAtBeginningOfData = 0;
-    if (fDrawInnerFan) {
-        numTrianglesAtBeginningOfData = GrMiddleOutPolygonTriangulator::WritePathInnerFan(
-                &instanceWriter,
-                GrMiddleOutPolygonTriangulator::OutputType::kConicsWithInfiniteWeight, path);
-    }
-    if (breadcrumbTriangleList) {
-        numTrianglesAtBeginningOfData += GrPathTessellator::WriteBreadcrumbTriangles(
-                &instanceWriter, breadcrumbTriangleList);
-    }
-
-    // Allocate space for the GrDrawIndexedIndirectCommand structs. Allocate enough for each
-    // possible resolve level (kMaxResolveLevel; resolveLevel=0 never has any instances), plus one
-    // more for the optional inner fan triangles.
-    int indirectLockCnt = kMaxResolveLevel + 1;
-    GrDrawIndirectWriter indirectWriter = target->makeDrawIndirectSpace(indirectLockCnt,
-                                                                        &fIndirectDrawBuffer,
-                                                                        &fIndirectDrawOffset);
-    if (!indirectWriter) {
-        SkASSERT(!fIndirectDrawBuffer);
-        vertexAlloc.unlock(0);
-        return;
-    }
-
-    // Fill out the GrDrawIndexedIndirectCommand structs and determine the starting instance data
-    // location at each resolve level.
-    GrVertexWriter instanceLocations[kMaxResolveLevel + 1];
-    int currentBaseInstance = fBaseInstance;
-    SkASSERT(fResolveLevelCounts[0] == 0);
-    for (int resolveLevel=1, numExtraInstances=numTrianglesAtBeginningOfData;
-         resolveLevel <= kMaxResolveLevel;
-         ++resolveLevel, numExtraInstances=0) {
-        int instanceCountAtCurrLevel = fResolveLevelCounts[resolveLevel];
-        if (!(instanceCountAtCurrLevel + numExtraInstances)) {
-            SkDEBUGCODE(instanceLocations[resolveLevel] = nullptr;)
-            continue;
-        }
-        instanceLocations[resolveLevel] = instanceWriter.makeOffset(0);
-        SkASSERT(fIndirectDrawCount < indirectLockCnt);
-        // The vertex shader determines the T value at which to draw each vertex. Since the
-        // triangles are arranged in "middle-out" order, we can conveniently control the
-        // resolveLevel by changing only the vertexCount.
-        indirectWriter.write(instanceCountAtCurrLevel + numExtraInstances, currentBaseInstance,
-                             num_vertices_at_resolve_level(resolveLevel), 0);
-        ++fIndirectDrawCount;
-        currentBaseInstance += instanceCountAtCurrLevel + numExtraInstances;
-        instanceWriter = instanceWriter.makeOffset(instanceCountAtCurrLevel * 4 * sizeof(SkPoint));
-    }
-
-    target->putBackIndirectDraws(indirectLockCnt - fIndirectDrawCount);
-
-#ifdef SK_DEBUG
-    SkASSERT(currentBaseInstance ==
-             fBaseInstance + numTrianglesAtBeginningOfData + fOuterCurveInstanceCount);
-
-    GrVertexWriter endLocations[kMaxResolveLevel + 1];
-    int lastResolveLevel = 0;
-    for (int resolveLevel = 1; resolveLevel <= kMaxResolveLevel; ++resolveLevel) {
-        if (!instanceLocations[resolveLevel]) {
-            endLocations[resolveLevel] = nullptr;
-            continue;
-        }
-        endLocations[lastResolveLevel] = instanceLocations[resolveLevel].makeOffset(0);
-        lastResolveLevel = resolveLevel;
-    }
-    endLocations[lastResolveLevel] = instanceWriter.makeOffset(0);
-#endif
-
-    fTotalInstanceCount = numTrianglesAtBeginningOfData;
-
-    // Write out the cubic instances.
-    if (fOuterCurveInstanceCount) {
-        GrVectorXform xform(fShader->viewMatrix());
-        for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
-            int level;
-            switch (verb) {
-                default:
-                    continue;
-                case SkPathVerb::kQuad:
-                    level = GrWangsFormula::quadratic_log2(kPrecision, pts, xform);
-                    break;
-                case SkPathVerb::kConic:
-                    level = GrWangsFormula::conic_log2(kPrecision, pts, *w, xform);
-                    break;
-                case SkPathVerb::kCubic:
-                    level = GrWangsFormula::cubic_log2(kPrecision, pts, xform);
-                    break;
-            }
-            if (level == 0) {
-                continue;
-            }
-            level = std::min(level, kMaxResolveLevel);
-            switch (verb) {
-                case SkPathVerb::kQuad:
-                    GrPathUtils::writeQuadAsCubic(pts, &instanceLocations[level]);
-                    break;
-                case SkPathVerb::kCubic:
-                    instanceLocations[level].writeArray(pts, 4);
-                    break;
-                case SkPathVerb::kConic:
-                    GrTessellationShader::WriteConicPatch(pts, *w, &instanceLocations[level]);
-                    break;
-                default:
-                    SkUNREACHABLE;
-            }
-            ++fTotalInstanceCount;
-        }
-    }
-
-#ifdef SK_DEBUG
-    for (int i = 1; i <= kMaxResolveLevel; ++i) {
-        SkASSERT(instanceLocations[i] == endLocations[i]);
-    }
-    SkASSERT(fTotalInstanceCount == numTrianglesAtBeginningOfData + fOuterCurveInstanceCount);
-#endif
-
-    vertexAlloc.unlock(fTotalInstanceCount);
-}
-
-void GrPathIndirectTessellator::draw(GrOpFlushState* flushState) const {
-    if (fIndirectDrawCount) {
-        flushState->bindBuffers(nullptr, fInstanceBuffer, nullptr);
-        flushState->drawIndirect(fIndirectDrawBuffer.get(), fIndirectDrawOffset,
-                                 fIndirectDrawCount);
-    }
-}
-
-void GrPathIndirectTessellator::drawHullInstances(GrOpFlushState* flushState) const {
-    if (fTotalInstanceCount) {
-        flushState->bindBuffers(nullptr, fInstanceBuffer, nullptr);
-        flushState->drawInstanced(fTotalInstanceCount, fBaseInstance, 4, 0);
-    }
-}
diff --git a/src/gpu/tessellate/GrPathIndirectTessellator.h b/src/gpu/tessellate/GrPathIndirectTessellator.h
deleted file mode 100644
index c871737..0000000
--- a/src/gpu/tessellate/GrPathIndirectTessellator.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2021 Google LLC.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrPathIndirectTessellator_DEFINED
-#define GrPathIndirectTessellator_DEFINED
-
-#include "src/gpu/tessellate/GrPathTessellator.h"
-#include "src/gpu/tessellate/GrTessellationPathRenderer.h"
-
-// Prepares patches of the path's outer curves and, optionally, inner fan triangles using indirect
-// draw commands. Quadratics are converted to cubics and triangles are converted to conics with
-// w=Inf. An outer curve is an independent, 4-point closed contour that represents either a cubic or
-// a conic.
-class GrPathIndirectTessellator : public GrPathTessellator {
-public:
-    static GrPathTessellator* Make(SkArenaAlloc*, const SkPath&, const SkMatrix&,
-                                   const SkPMColor4f&, DrawInnerFan);
-
-    void prepare(GrMeshDrawOp::Target*, const SkRect& cullBounds, const SkPath&,
-                 const BreadcrumbTriangleList*) override;
-    void draw(GrOpFlushState*) const override;
-    void drawHullInstances(GrOpFlushState*) const override;
-
-private:
-    constexpr static int kMaxResolveLevel = GrTessellationPathRenderer::kMaxResolveLevel;
-
-    GrPathIndirectTessellator(GrPathTessellationShader*, const SkPath&, DrawInnerFan);
-
-    const bool fDrawInnerFan;
-    int fResolveLevelCounts[kMaxResolveLevel + 1] = {0};
-    int fOuterCurveInstanceCount = 0;
-
-    sk_sp<const GrBuffer> fInstanceBuffer;
-    int fBaseInstance = 0;
-    int fTotalInstanceCount = 0;
-
-    sk_sp<const GrBuffer> fIndirectDrawBuffer;
-    size_t fIndirectDrawOffset = 0;
-    int fIndirectDrawCount = 0;
-
-    friend class SkArenaAlloc;  // For constructor.
-};
-
-#endif
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
index c340ae9..37b4c75 100644
--- a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
@@ -14,7 +14,6 @@
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
 #include "src/gpu/tessellate/GrPathCurveTessellator.h"
-#include "src/gpu/tessellate/GrPathIndirectTessellator.h"
 #include "src/gpu/tessellate/GrTessellationPathRenderer.h"
 #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
 
@@ -189,16 +188,10 @@
 
     // Pass 1: Tessellate the outer curves into the stencil buffer.
     if (!isLinear) {
-        if (args.fCaps->shaderCaps()->tessellationSupport() &&
-            fPath.countVerbs() >= args.fCaps->minPathVerbsForHwTessellation()) {
-            fTessellator = GrPathCurveTessellator::Make(args.fArena, fViewMatrix,
-                                                        SK_PMColor4fTRANSPARENT,
-                                                        GrPathTessellator::DrawInnerFan::kNo);
-        } else {
-            fTessellator = GrPathIndirectTessellator::Make(args.fArena, fPath, fViewMatrix,
-                                                           SK_PMColor4fTRANSPARENT,
-                                                           GrPathTessellator::DrawInnerFan::kNo);
-        }
+        fTessellator = GrPathCurveTessellator::Make(args.fArena, fViewMatrix,
+                                                    SK_PMColor4fTRANSPARENT,
+                                                    GrPathTessellator::DrawInnerFan::kNo,
+                                                    fPath.countVerbs(), *args.fCaps);
         const GrUserStencilSettings* stencilPathSettings =
                 GrPathTessellationShader::StencilPathSettings(fPath.getFillType());
         fStencilCurvesProgram = GrTessellationShader::MakeProgram(args, fTessellator->shader(),
diff --git a/src/gpu/tessellate/GrPathStencilFillOp.cpp b/src/gpu/tessellate/GrPathStencilFillOp.cpp
index 7fb97f6..06b1c05 100644
--- a/src/gpu/tessellate/GrPathStencilFillOp.cpp
+++ b/src/gpu/tessellate/GrPathStencilFillOp.cpp
@@ -14,7 +14,6 @@
 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
 #include "src/gpu/tessellate/GrMiddleOutPolygonTriangulator.h"
 #include "src/gpu/tessellate/GrPathCurveTessellator.h"
-#include "src/gpu/tessellate/GrPathIndirectTessellator.h"
 #include "src/gpu/tessellate/GrPathWedgeTessellator.h"
 #include "src/gpu/tessellate/GrTessellationPathRenderer.h"
 #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
@@ -115,13 +114,13 @@
     }
     if (!args.fCaps->shaderCaps()->tessellationSupport() ||
         fPath.countVerbs() < args.fCaps->minPathVerbsForHwTessellation()) {
-        fTessellator = GrPathIndirectTessellator::Make(args.fArena, fPath, fViewMatrix,
-                                                       SK_PMColor4fTRANSPARENT,
-                                                       drawFanWithTessellator);
+        fTessellator = GrPathCurveTessellator::Make(
+                args.fArena, fViewMatrix, SK_PMColor4fTRANSPARENT, drawFanWithTessellator,
+                GrPathCurveTessellator::ShaderType::kFixedCountMiddleOut);
     } else if (drawFanWithTessellator == GrPathTessellator::DrawInnerFan::kNo) {
-        fTessellator = GrPathCurveTessellator::Make(args.fArena, fViewMatrix,
-                                                    SK_PMColor4fTRANSPARENT,
-                                                    GrPathTessellator::DrawInnerFan::kNo);
+        fTessellator = GrPathCurveTessellator::Make(
+                args.fArena, fViewMatrix, SK_PMColor4fTRANSPARENT, drawFanWithTessellator,
+                GrPathCurveTessellator::ShaderType::kHardwareTessellation);
     } else {
         fTessellator = GrPathWedgeTessellator::Make(args.fArena, fViewMatrix,
                                                     SK_PMColor4fTRANSPARENT);
diff --git a/src/gpu/tessellate/GrPathTessellator.h b/src/gpu/tessellate/GrPathTessellator.h
index c03701c..b8bb392 100644
--- a/src/gpu/tessellate/GrPathTessellator.h
+++ b/src/gpu/tessellate/GrPathTessellator.h
@@ -23,6 +23,10 @@
 public:
     using BreadcrumbTriangleList = GrInnerFanTriangulator::BreadcrumbTriangleList;
 
+    // For fixed count tessellators, this is the largest number of segments we can stuff into a
+    // single instance before we need to chop.
+    constexpr static int kMaxFixedCountSegments = 32;
+
     // For subclasses that use this enum, if DrawInnerFan is kNo, the class only emits the path's
     // outer curves. In that case the caller is responsible to handle the path's inner fan.
     enum class DrawInnerFan : bool {
@@ -30,10 +34,6 @@
         kYes
     };
 
-    // Creates the tessellator best suited to draw the given path.
-    static GrPathTessellator* Make(SkArenaAlloc*, const SkPath&, const SkMatrix&,
-                                   const SkPMColor4f&, DrawInnerFan, const GrCaps&);
-
     const GrPathTessellationShader* shader() const { return fShader; }
 
     // Called before draw(). Prepares GPU buffers containing the geometry to tessellate. If the
@@ -53,9 +53,6 @@
 
     virtual ~GrPathTessellator() {}
 
-protected:
-    GrPathTessellator(GrPathTessellationShader* shader) : fShader(shader) {}
-
     // Returns an upper bound on the number of segments (lineTo, quadTo, conicTo, cubicTo) in a
     // path, also accounting for any implicit lineTos from closing contours.
     static int MaxSegmentsInPath(const SkPath& path) {
@@ -65,40 +62,8 @@
         return path.countVerbs();
     }
 
-    // Returns an upper bound on the number of triangles it would require to fan a path's inner
-    // polygon, in the case where no additional vertices are introduced.
-    static int MaxTrianglesInInnerFan(const SkPath& path) {
-        int maxEdgesInFan = MaxSegmentsInPath(path);
-        return std::max(maxEdgesInFan - 2, 0);  // An n-sided polygon is fanned by n-2 triangles.
-    }
-
-    // Writes out the non-degenerate triangles from 'breadcrumbTriangleList' as 4-point conic
-    // patches with w=Infinity.
-    static int WriteBreadcrumbTriangles(GrVertexWriter* writer,
-                                        const BreadcrumbTriangleList* breadcrumbTriangleList) {
-        int numWritten = 0;
-        SkDEBUGCODE(int count = 0;)
-        for (const auto* tri = breadcrumbTriangleList->head(); tri; tri = tri->fNext) {
-            SkDEBUGCODE(++count;)
-            auto p0 = grvx::float2::Load(tri->fPts);
-            auto p1 = grvx::float2::Load(tri->fPts + 1);
-            auto p2 = grvx::float2::Load(tri->fPts + 2);
-            if (skvx::any((p0 == p1) & (p1 == p2))) {
-                // Cull completely horizontal or vertical triangles. GrTriangulator can't always get
-                // these breadcrumb edges right when they run parallel to the sweep direction
-                // because their winding is undefined by its current definition.
-                // FIXME(skia:12060): This seemed safe, but if there is a view matrix it will
-                // introduce T-junctions.
-                continue;
-            }
-            writer->writeArray(tri->fPts, 3);
-            // Mark this instance as a triangle by setting it to a conic with w=Inf.
-            writer->fill(GrVertexWriter::kIEEE_32_infinity, 2);
-            ++numWritten;
-        }
-        SkASSERT(count == breadcrumbTriangleList->count());
-        return numWritten;
-    }
+protected:
+    GrPathTessellator(GrPathTessellationShader* shader) : fShader(shader) {}
 
     GrPathTessellationShader* fShader;
 };
diff --git a/src/gpu/tessellate/shaders/GrPathTessellationShader.h b/src/gpu/tessellate/shaders/GrPathTessellationShader.h
index d4ac447..4a20aa9 100644
--- a/src/gpu/tessellate/shaders/GrPathTessellationShader.h
+++ b/src/gpu/tessellate/shaders/GrPathTessellationShader.h
@@ -20,6 +20,16 @@
                                                               const SkMatrix& viewMatrix,
                                                               const SkPMColor4f&);
 
+    // How many triangles are in a curve with 2^resolveLevel line segments?
+    constexpr static int NumTrianglesAtResolveLevel(int resolveLevel) {
+        // resolveLevel=0 -> 0 line segments -> 0 triangles
+        // resolveLevel=1 -> 2 line segments -> 1 triangle
+        // resolveLevel=2 -> 4 line segments -> 3 triangles
+        // resolveLevel=3 -> 8 line segments -> 7 triangles
+        // ...
+        return (1 << resolveLevel) - 1;
+    }
+
     // Uses instanced draws to triangulate standalone closed curves with a "middle-out" topology.
     // Middle-out draws a triangle with vertices at T=[0, 1/2, 1] and then recurses breadth first:
     //
@@ -28,9 +38,10 @@
     //   depth=2: T=[0, 1/8, 2/8], T=[2/8, 3/8, 4/8], T=[4/8, 5/8, 6/8], T=[6/8, 7/8, 1]
     //   ...
     //
-    // 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.
+    // The shader determines how many segments are required to render each individual curve
+    // smoothly, and emits empty triangles at any vertices whose sk_VertexIDs are higher than
+    // necessary. It is the caller's responsibility to draw enough vertices per instance for the
+    // most complex curve in the batch to render smoothly (i.e., NumTrianglesAtResolveLevel() * 3).
     static GrPathTessellationShader* MakeMiddleOutInstancedShader(SkArenaAlloc*,
                                                                   const SkMatrix& viewMatrix,
                                                                   const SkPMColor4f&);
diff --git a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp
index ee1d162..5dc5c63 100644
--- a/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp
+++ b/src/gpu/tessellate/shaders/GrPathTessellationShader_MiddleOut.cpp
@@ -7,6 +7,7 @@
 
 #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h"
 
+#include "src/gpu/geometry/GrWangsFormula.h"
 #include "src/gpu/glsl/GrGLSLProgramBuilder.h"
 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
 
@@ -20,9 +21,10 @@
 //   depth=2: T=[0, 1/8, 2/8], T=[2/8, 3/8, 4/8], T=[4/8, 5/8, 6/8], T=[6/8, 7/8, 1]
 //   ...
 //
-// 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.
+// The shader determines how many segments are required to render each individual curve smoothly,
+// and emits empty triangles at any vertices whose sk_VertexIDs are higher than necessary. It is the
+// caller's responsibility to draw enough vertices per instance for the most complex curve in the
+// batch to render smoothly (i.e., NumTrianglesAtResolveLevel() * 3).
 class MiddleOutShader : public GrPathTessellationShader {
 public:
     MiddleOutShader(const SkMatrix& viewMatrix, const SkPMColor4f& color)
@@ -43,13 +45,20 @@
 GrGLSLGeometryProcessor* MiddleOutShader::createGLSLInstance(const GrShaderCaps&) const {
     class Impl : public GrPathTessellationShader::Impl {
         void emitVertexCode(GrGLSLVertexBuilder* v, GrGPArgs* gpArgs) override {
+            v->defineConstant("PRECISION", GrTessellationPathRenderer::kLinearizationPrecision);
+            v->insertFunction(GrWangsFormula::as_sksl().c_str());
             if (v->getProgramBuilder()->shaderCaps()->bitManipulationSupport()) {
                 // Determines the T value at which to place the given vertex in a "middle-out"
                 // topology.
                 v->insertFunction(R"(
-                float find_middle_out_T() {
+                float find_middle_out_T(float maxResolveLevel) {
                     int totalTriangleIdx = sk_VertexID/3 + 1;
                     int resolveLevel = findMSB(totalTriangleIdx) + 1;
+                    if (resolveLevel > int(maxResolveLevel)) {
+                        // This vertex is at a higher resolve level than we need. Emit a degenerate
+                        // triangle at T=1.
+                        return 1;
+                    }
                     int firstTriangleAtDepth = (1 << (resolveLevel - 1));
                     int triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
                     int vertexIdxWithinDepth = triangleIdxWithinDepth * 2 + sk_VertexID % 3;
@@ -59,9 +68,14 @@
                 // Determines the T value at which to place the given vertex in a "middle-out"
                 // topology.
                 v->insertFunction(R"(
-                float find_middle_out_T() {
+                float find_middle_out_T(float maxResolveLevel) {
                     float totalTriangleIdx = float(sk_VertexID/3) + 1;
                     float resolveLevel = floor(log2(totalTriangleIdx)) + 1;
+                    if (resolveLevel > maxResolveLevel) {
+                        // This vertex is at a higher resolve level than we need. Emit a degenerate
+                        // triangle at T=1.
+                        return 1;
+                    }
                     float firstTriangleAtDepth = exp2(resolveLevel - 1);
                     float triangleIdxWithinDepth = totalTriangleIdx - firstTriangleAtDepth;
                     float vertexIdxWithinDepth = triangleIdxWithinDepth*2 + float(sk_VertexID % 3);
@@ -78,13 +92,19 @@
             } else {
                 float w = -1;  // w < 0 tells us to treat the instance as an integral cubic.
                 float4x2 P = float4x2(inputPoints_0_1, inputPoints_2_3);
+                float maxResolveLevel;
                 if (isinf(P[3].y)) {
                     // The patch is a conic.
                     w = P[3].x;
+                    maxResolveLevel =
+                            wangs_formula_conic_log2(PRECISION, AFFINE_MATRIX * float3x2(P), w);
                     P[3] = P[2];  // Duplicate the endpoint.
                     P[1] *= w;  // Unproject p1.
+                } else {
+                    // The patch is an integral cubic.
+                    maxResolveLevel = wangs_formula_cubic_log2(PRECISION, P, AFFINE_MATRIX);
                 }
-                float T = find_middle_out_T();
+                float T = find_middle_out_T(maxResolveLevel);
                 if (0 < T && T < 1) {
                     // Evaluate at T. Use De Casteljau's for its accuracy and stability.
                     float2 ab = mix(P[0], P[1], T);