Extract a GrVertexChunkArray class with a builder

This will be used by the new stroke tessellator. All the other
tessellators should start chopping and chunking too. That will allow us
to quit cropping paths if we are afraid they might need more segments
than are supported.

Bug: chromium:1172543
Change-Id: I30f0ebb581f56cac099d8c05e0e181c4657c3db8
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/390096
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/GrDrawIndirectCommand.h b/src/gpu/GrDrawIndirectCommand.h
index 1119757..17cd709 100644
--- a/src/gpu/GrDrawIndirectCommand.h
+++ b/src/gpu/GrDrawIndirectCommand.h
@@ -87,7 +87,7 @@
 
     bool operator==(const GrDrawIndexedIndirectWriter& that) { return fData == that.fData; }
 
-    bool isValid() const { return fData != nullptr; }
+    operator bool() const { return fData != nullptr; }
 
     GrDrawIndexedIndirectWriter makeOffset(int drawCount) const { return {fData + drawCount}; }
 
diff --git a/src/gpu/GrVertexChunkArray.h b/src/gpu/GrVertexChunkArray.h
new file mode 100644
index 0000000..febde51
--- /dev/null
+++ b/src/gpu/GrVertexChunkArray.h
@@ -0,0 +1,102 @@
+/*
+ * 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 GrVertexChunkArray_DEFINED
+#define GrVertexChunkArray_DEFINED
+
+#include "include/private/SkNoncopyable.h"
+#include "include/private/SkTArray.h"
+#include "src/gpu/GrBuffer.h"
+#include "src/gpu/GrVertexWriter.h"
+#include "src/gpu/ops/GrMeshDrawOp.h"
+
+// Represents a chunk of vertex data. Use with GrVertexChunkArray and GrVertexChunkBuilder. We write
+// the data out in chunks when we don't start out knowing exactly how many vertices (or instances)
+// we will end up writing.
+struct GrVertexChunk {
+    sk_sp<const GrBuffer> fBuffer;
+    int fVertexCount = 0;
+    int fBaseVertex;
+};
+
+// Represents an array of GrVertexChunks.
+//
+// We only preallocate 1 chunk because if the array needs to grow, then we're also allocating a
+// brand new GPU buffer anyway.
+using GrVertexChunkArray = SkSTArray<1, GrVertexChunk>;
+
+// Builds a GrVertexChunkArray. The provided Target must not be used externally throughout the
+// entire lifetime of this object.
+class GrVertexChunkBuilder : SkNoncopyable {
+public:
+    GrVertexChunkBuilder(GrMeshDrawOp::Target* target, GrVertexChunkArray* chunks, size_t stride,
+                         int minVerticesPerChunk)
+            : fTarget(target)
+            , fChunks(chunks)
+            , fStride(stride)
+            , fMinVerticesPerChunk(minVerticesPerChunk) {
+        SkASSERT(fMinVerticesPerChunk > 0);
+    }
+
+    ~GrVertexChunkBuilder() {
+        if (!fChunks->empty()) {
+            fTarget->putBackVertices(fCurrChunkVertexCapacity - fCurrChunkVertexCount, fStride);
+            fChunks->back().fVertexCount = fCurrChunkVertexCount;
+        }
+    }
+
+    // Appends 'count' contiguous vertices. These vertices are not guaranteed to be contiguous with
+    // previous or future calls to appendVertices.
+    SK_ALWAYS_INLINE GrVertexWriter appendVertices(int count) {
+        SkASSERT(count > 0);
+        if (fCurrChunkVertexCount + count > fCurrChunkVertexCapacity && !this->allocChunk(count)) {
+            return {nullptr};
+        }
+        SkASSERT(fCurrChunkVertexCount + count <= fCurrChunkVertexCapacity);
+        fCurrChunkVertexCount += count;
+        return std::exchange(fCurrChunkVertexWriter,
+                             fCurrChunkVertexWriter.makeOffset(fStride * count));
+    }
+
+    SK_ALWAYS_INLINE GrVertexWriter appendVertex() { return this->appendVertices(1); }
+
+private:
+    bool allocChunk(int minCount) {
+        if (!fChunks->empty()) {
+            // No need to put back vertices; the buffer is full.
+            fChunks->back().fVertexCount = fCurrChunkVertexCount;
+        }
+        fCurrChunkVertexCount = 0;
+        GrVertexChunk* chunk = &fChunks->push_back();
+        fCurrChunkVertexWriter = {fTarget->makeVertexSpaceAtLeast(fStride,
+                                                                  fMinVerticesPerChunk * minCount,
+                                                                  fMinVerticesPerChunk * minCount,
+                                                                  &chunk->fBuffer,
+                                                                  &chunk->fBaseVertex,
+                                                                  &fCurrChunkVertexCapacity)};
+        if (!fCurrChunkVertexWriter || !chunk->fBuffer || fCurrChunkVertexCapacity < minCount) {
+            SkDebugf("WARNING: Failed to allocate vertex buffer for GrVertexChunk.\n");
+            fChunks->pop_back();
+            SkASSERT(fCurrChunkVertexCount == 0);
+            fCurrChunkVertexCapacity = 0;
+            return false;
+        }
+        fMinVerticesPerChunk *= 2;
+        return true;
+    }
+
+    GrMeshDrawOp::Target* const fTarget;
+    GrVertexChunkArray* const fChunks;
+    const size_t fStride;
+    size_t fMinVerticesPerChunk;
+
+    GrVertexWriter fCurrChunkVertexWriter;
+    int fCurrChunkVertexCount = 0;
+    int fCurrChunkVertexCapacity = 0;
+};
+
+#endif
diff --git a/src/gpu/GrVertexWriter.h b/src/gpu/GrVertexWriter.h
index e5f6685..192b862 100644
--- a/src/gpu/GrVertexWriter.h
+++ b/src/gpu/GrVertexWriter.h
@@ -37,11 +37,8 @@
         return *this;
     }
 
-    bool operator==(const GrVertexWriter& that) {
-        return fPtr == that.fPtr;
-    }
-
-    bool isValid() const { return fPtr != nullptr; }
+    bool operator==(const GrVertexWriter& that) const { return fPtr == that.fPtr; }
+    operator bool() const { return fPtr != nullptr; }
 
     GrVertexWriter makeOffset(size_t offsetInBytes) const {
         return {SkTAddOffset<void>(fPtr, offsetInBytes)};
diff --git a/src/gpu/mock/GrMockOpTarget.h b/src/gpu/mock/GrMockOpTarget.h
index 9564cf3..e3cb2f1 100644
--- a/src/gpu/mock/GrMockOpTarget.h
+++ b/src/gpu/mock/GrMockOpTarget.h
@@ -10,13 +10,20 @@
 
 #include "include/gpu/GrDirectContext.h"
 #include "src/gpu/GrDirectContextPriv.h"
+#include "src/gpu/GrGpu.h"
 #include "src/gpu/ops/GrMeshDrawOp.h"
 
 // This is a mock GrMeshDrawOp::Target implementation that just gives back pointers into
 // pre-allocated CPU buffers, rather than allocating and mapping GPU buffers.
 class GrMockOpTarget : public GrMeshDrawOp::Target {
 public:
-    GrMockOpTarget(sk_sp<GrDirectContext> mockContext) : fMockContext(std::move(mockContext)) {}
+    GrMockOpTarget(sk_sp<GrDirectContext> mockContext) : fMockContext(std::move(mockContext)) {
+        fStaticVertexBuffer = fMockContext->priv().getGpu()->createBuffer(
+                sizeof(fStaticVertexData), GrGpuBufferType::kVertex, kDynamic_GrAccessPattern);
+        fStaticIndirectBuffer = fMockContext->priv().getGpu()->createBuffer(
+                sizeof(fStaticIndirectData), GrGpuBufferType::kDrawIndirect,
+                kDynamic_GrAccessPattern);
+    }
     const GrDirectContext* mockContext() const { return fMockContext.get(); }
     const GrCaps& caps() const override { return *fMockContext->priv().caps(); }
     GrThreadSafeCache* threadSafeCache() const override {
@@ -34,23 +41,25 @@
     GrXferBarrierFlags renderPassBarriers() const override { return GrXferBarrierFlags::kNone; }
     GrLoadOp colorLoadOp() const override { return GrLoadOp::kLoad; }
 
-    void* makeVertexSpace(size_t vertexSize, int vertexCount, sk_sp<const GrBuffer>*,
+    void* makeVertexSpace(size_t vertexSize, int vertexCount, sk_sp<const GrBuffer>* buffer,
                           int* startVertex) override {
         if (vertexSize * vertexCount > sizeof(fStaticVertexData)) {
             SK_ABORT("FATAL: wanted %zu bytes of static vertex data; only have %zu.\n",
                      vertexSize * vertexCount, sizeof(fStaticVertexData));
         }
+        *buffer = fStaticVertexBuffer;
         *startVertex = 0;
         return fStaticVertexData;
     }
 
     void* makeVertexSpaceAtLeast(size_t vertexSize, int minVertexCount, int fallbackVertexCount,
-                                 sk_sp<const GrBuffer>*, int* startVertex,
+                                 sk_sp<const GrBuffer>* buffer, int* startVertex,
                                  int* actualVertexCount) override {
         if (vertexSize * minVertexCount > sizeof(fStaticVertexData)) {
             SK_ABORT("FATAL: wanted %zu bytes of static vertex data; only have %zu.\n",
                      vertexSize * minVertexCount, sizeof(fStaticVertexData));
         }
+        *buffer = fStaticVertexBuffer;
         *startVertex = 0;
         *actualVertexCount = sizeof(fStaticVertexData) / vertexSize;
         return fStaticVertexData;
@@ -62,6 +71,7 @@
             SK_ABORT("FATAL: wanted %zu bytes of static indirect data; only have %zu.\n",
                      sizeof(GrDrawIndirectCommand) * drawCount, sizeof(fStaticIndirectData));
         }
+        *buffer = fStaticIndirectBuffer;
         *offsetInBytes = 0;
         return fStaticIndirectData;
     }
@@ -75,6 +85,7 @@
             SK_ABORT("FATAL: wanted %zu bytes of static indirect data; only have %zu.\n",
                      sizeof(GrDrawIndexedIndirectCommand) * drawCount, sizeof(fStaticIndirectData));
         }
+        *buffer = fStaticIndirectBuffer;
         *offsetInBytes = 0;
         return fStaticIndirectData;
     }
@@ -103,7 +114,9 @@
 private:
     sk_sp<GrDirectContext> fMockContext;
     char fStaticVertexData[6 * 1024 * 1024];
+    sk_sp<GrGpuBuffer> fStaticVertexBuffer;
     char fStaticIndirectData[sizeof(GrDrawIndexedIndirectCommand) * 32];
+    sk_sp<GrGpuBuffer> fStaticIndirectBuffer;
     SkSTArenaAllocWithReset<1024 * 1024> fAllocator;
     GrXferProcessor::DstProxyView fDstProxyView;
 };
diff --git a/src/gpu/tessellate/GrPathTessellator.cpp b/src/gpu/tessellate/GrPathTessellator.cpp
index 126df211..756e65f 100644
--- a/src/gpu/tessellate/GrPathTessellator.cpp
+++ b/src/gpu/tessellate/GrPathTessellator.cpp
@@ -102,6 +102,7 @@
     fIndirectIndexBuffer = GrMiddleOutCubicShader::FindOrMakeMiddleOutIndexBuffer(
             target->resourceProvider());
     if (!fIndirectIndexBuffer) {
+        vertexAlloc.unlock(0);
         return;
     }
 
@@ -111,8 +112,9 @@
     int indirectLockCnt = kMaxResolveLevel + 1;
     GrDrawIndexedIndirectWriter indirectWriter = target->makeDrawIndexedIndirectSpace(
             indirectLockCnt, &fIndirectDrawBuffer, &fIndirectDrawOffset);
-    if (!indirectWriter.isValid()) {
+    if (!indirectWriter) {
         SkASSERT(!fIndirectDrawBuffer);
+        vertexAlloc.unlock(0);
         return;
     }
 
diff --git a/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp b/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp
index ab9e5f9..2aea611 100644
--- a/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp
+++ b/src/gpu/tessellate/GrStrokeHardwareTessellator.cpp
@@ -42,7 +42,6 @@
 class PatchWriter {
 public:
     using ShaderFlags = GrStrokeTessellator::ShaderFlags;
-    using PatchChunk = GrStrokeHardwareTessellator::PatchChunk;
 
     enum class JoinType {
         kMiter = SkPaint::kMiter_Join,
@@ -52,27 +51,15 @@
     };
 
     PatchWriter(ShaderFlags shaderFlags, GrMeshDrawOp::Target* target, float matrixMaxScale,
-                SkTArray<PatchChunk>* patchChunks, int totalCombinedVerbCnt)
+                GrVertexChunkArray* patchChunks, int minPatchesPerChunk)
             : fShaderFlags(shaderFlags)
-            , fTarget(target)
-            , fPatchChunks(patchChunks)
-            , fPatchStride(GrStrokeTessellateShader::PatchStride(fShaderFlags))
+            , fChunkBuilder(target, patchChunks,
+                            GrStrokeTessellateShader::PatchStride(fShaderFlags), minPatchesPerChunk)
             // Subtract 2 because the tessellation shader chops every cubic at two locations, and
             // each chop has the potential to introduce an extra segment.
             , fMaxTessellationSegments(target->caps().shaderCaps()->maxTessellationSegments() - 2)
             , fParametricIntolerance(GrStrokeTolerances::CalcParametricIntolerance(
                     matrixMaxScale)) {
-        // Pre-allocate at least enough vertex space for 1 in 4 strokes to chop, and for 8 caps.
-        int strokePreallocCount = totalCombinedVerbCnt * 5/4;
-        int capPreallocCount = 8;
-        fNextChunkMinPatchAllocCount = strokePreallocCount + capPreallocCount;
-    }
-
-    ~PatchWriter() {
-        if (!fPatchChunks->empty()) {
-            fTarget->putBackVertices(fCurrChunkPatchCapacity - fCurrChunkPatchCount, fPatchStride);
-            fPatchChunks->back().fPatchCount = fCurrChunkPatchCount;
-        }
     }
 
     // This is the intolerance value, adjusted for the view matrix, to use with Wang's formulas when
@@ -239,10 +226,10 @@
             fLastControlPoint = p[0];  // Disables the join section of this patch.
         }
 
-        if (this->allocPatch()) {
-            fPatchWriter.write(fLastControlPoint);
-            fPatchWriter.writeArray(p, 4);
-            this->writeDynamicAttribs();
+        if (GrVertexWriter patchWriter = fChunkBuilder.appendVertex()) {
+            patchWriter.write(fLastControlPoint);
+            patchWriter.writeArray(p, 4);
+            this->writeDynamicAttribs(&patchWriter);
         }
 
         fLastControlPoint = endControlPoint;
@@ -578,71 +565,37 @@
         // We should never write out joins before the first curve.
         SkASSERT(fHasLastControlPoint);
 
-        if (this->allocPatch()) {
-            fPatchWriter.write(fLastControlPoint, junctionPoint);
+        if (GrVertexWriter patchWriter = fChunkBuilder.appendVertex()) {
+            patchWriter.write(fLastControlPoint, junctionPoint);
             if (joinType == JoinType::kBowtie) {
                 // {prevControlPoint, [p0, p0, p0, p3]} is a reserved patch pattern that means this
                 // patch is a bowtie. The bowtie is anchored on p0 and its tangent angles go from
                 // (p0 - prevControlPoint) to (p3 - p0).
-                fPatchWriter.write(junctionPoint, junctionPoint);
+                patchWriter.write(junctionPoint, junctionPoint);
             } else {
                 // {prevControlPoint, [p0, p3, p3, p3]} is a reserved patch pattern that means this
                 // patch is a join only (no curve sections in the patch). The join is anchored on p0
                 // and its tangent angles go from (p0 - prevControlPoint) to (p3 - p0).
-                fPatchWriter.write(nextControlPoint, nextControlPoint);
+                patchWriter.write(nextControlPoint, nextControlPoint);
             }
-            fPatchWriter.write(nextControlPoint);
-            this->writeDynamicAttribs();
+            patchWriter.write(nextControlPoint);
+            this->writeDynamicAttribs(&patchWriter);
         }
 
         fLastControlPoint = nextControlPoint;
     }
 
-    SK_ALWAYS_INLINE void writeDynamicAttribs() {
+    SK_ALWAYS_INLINE void writeDynamicAttribs(GrVertexWriter* patchWriter) {
         if (fShaderFlags & ShaderFlags::kDynamicStroke) {
-            fPatchWriter.write(fDynamicStroke);
+            patchWriter->write(fDynamicStroke);
         }
         if (fShaderFlags & ShaderFlags::kDynamicColor) {
-            fPatchWriter.write(fDynamicColor);
+            patchWriter->write(fDynamicColor);
         }
     }
 
-    SK_ALWAYS_INLINE bool allocPatch() {
-        if (fCurrChunkPatchCount == fCurrChunkPatchCapacity && !this->allocPatchChunk()) {
-            return false;
-        }
-        SkASSERT(fCurrChunkPatchCount < fCurrChunkPatchCapacity);
-        ++fCurrChunkPatchCount;
-        return true;
-    }
-
-    bool allocPatchChunk() {
-        if (!fPatchChunks->empty()) {
-            fPatchChunks->back().fPatchCount = fCurrChunkPatchCount;
-            // No need to put back vertices; the buffer is full.
-        }
-        fCurrChunkPatchCount = 0;
-        PatchChunk* chunk = &fPatchChunks->push_back();
-        fPatchWriter = {fTarget->makeVertexSpaceAtLeast(fPatchStride, fNextChunkMinPatchAllocCount,
-                                                        fNextChunkMinPatchAllocCount,
-                                                        &chunk->fPatchBuffer, &chunk->fBasePatch,
-                                                        &fCurrChunkPatchCapacity)};
-        if (!fPatchWriter.isValid()) {
-            SkDebugf("WARNING: Failed to allocate vertex buffer for tessellated stroke.\n");
-            fPatchChunks->pop_back();
-            fCurrChunkPatchCapacity = 0;
-            return false;
-        }
-        fNextChunkMinPatchAllocCount *= 2;
-        return true;
-    }
-
     const ShaderFlags fShaderFlags;
-    GrMeshDrawOp::Target* const fTarget;
-    SkTArray<PatchChunk>* const fPatchChunks;
-
-    // Size in bytes of a tessellation patch with our shader flags.
-    const size_t fPatchStride;
+    GrVertexChunkBuilder fChunkBuilder;
 
     // The maximum number of tessellation segments the hardware can emit for a single patch.
     const int fMaxTessellationSegments;
@@ -671,12 +624,6 @@
     bool fSoloRoundJoinAlwaysFitsInPatch;
     JoinType fStrokeJoinType;
 
-    // Variables related to the patch chunk that we are currently writing out during prepareBuffers.
-    int fCurrChunkPatchCount = 0;
-    int fCurrChunkPatchCapacity = 0;
-    int fNextChunkMinPatchAllocCount;
-    GrVertexWriter fPatchWriter;
-
     // Variables related to the specific contour that we are currently iterating during
     // prepareBuffers().
     bool fHasLastControlPoint = false;
@@ -735,7 +682,7 @@
 }  // namespace
 
 void GrStrokeHardwareTessellator::prepare(GrMeshDrawOp::Target* target,
-                                          const SkMatrix& viewMatrix) {
+                                          const SkMatrix& viewMatrix, int totalCombinedVerbCnt) {
     using JoinType = PatchWriter::JoinType;
 
     std::array<float, 2> matrixMinMaxScales;
@@ -743,8 +690,13 @@
         matrixMinMaxScales.fill(1);
     }
 
+    // Over-allocate enough patches for 1 in 4 strokes to chop and for 8 extra caps.
+    int strokePreallocCount = totalCombinedVerbCnt * 5/4;
+    int capPreallocCount = 8;
+    int minPatchesPerChunk = strokePreallocCount + capPreallocCount;
     PatchWriter patchWriter(fShaderFlags, target, matrixMinMaxScales[1], &fPatchChunks,
-                            fTotalCombinedVerbCnt);
+                            minPatchesPerChunk);
+
     if (!(fShaderFlags & ShaderFlags::kDynamicStroke)) {
         // Strokes are static. Calculate tolerances once.
         const SkStrokeRec& stroke = fPathStrokeList->fStroke;
@@ -918,9 +870,7 @@
 
 void GrStrokeHardwareTessellator::draw(GrOpFlushState* flushState) const {
     for (const auto& chunk : fPatchChunks) {
-        if (chunk.fPatchBuffer) {
-            flushState->bindBuffers(nullptr, nullptr, std::move(chunk.fPatchBuffer));
-            flushState->draw(chunk.fPatchCount, chunk.fBasePatch);
-        }
+        flushState->bindBuffers(nullptr, nullptr, chunk.fBuffer);
+        flushState->draw(chunk.fVertexCount, chunk.fBaseVertex);
     }
 }
diff --git a/src/gpu/tessellate/GrStrokeHardwareTessellator.h b/src/gpu/tessellate/GrStrokeHardwareTessellator.h
index 76f1c66..2f7b68a 100644
--- a/src/gpu/tessellate/GrStrokeHardwareTessellator.h
+++ b/src/gpu/tessellate/GrStrokeHardwareTessellator.h
@@ -8,8 +8,7 @@
 #ifndef GrStrokeHardwareTessellator_DEFINED
 #define GrStrokeHardwareTessellator_DEFINED
 
-#include "include/core/SkStrokeRec.h"
-#include "src/gpu/GrVertexWriter.h"
+#include "src/gpu/GrVertexChunkArray.h"
 #include "src/gpu/tessellate/GrStrokeTessellator.h"
 
 // Renders opaque, constant-color strokes by decomposing them into standalone tessellation patches.
@@ -17,30 +16,17 @@
 // MSAA if antialiasing is desired.
 class GrStrokeHardwareTessellator : public GrStrokeTessellator {
 public:
-    // We generate and store patch buffers in chunks. Normally there will only be one chunk, but in
-    // rare cases the first can run out of space if too many cubics needed to be subdivided.
-    struct PatchChunk {
-        sk_sp<const GrBuffer> fPatchBuffer;
-        int fPatchCount = 0;
-        int fBasePatch;
-    };
-
     GrStrokeHardwareTessellator(ShaderFlags shaderFlags, PathStrokeList* pathStrokeList,
-                                int totalCombinedVerbCnt, const GrShaderCaps& shaderCaps)
-            : GrStrokeTessellator(shaderFlags, std::move(pathStrokeList))
-            , fTotalCombinedVerbCnt(totalCombinedVerbCnt) {
+                                const GrShaderCaps&)
+            : GrStrokeTessellator(shaderFlags, std::move(pathStrokeList)) {
     }
 
-    void prepare(GrMeshDrawOp::Target*, const SkMatrix&) override;
+    void prepare(GrMeshDrawOp::Target*, const SkMatrix& viewMatrix,
+                 int totalCombinedVerbCnt) override;
     void draw(GrOpFlushState*) const override;
 
 private:
-    // The combined number of path verbs from all paths in fPathStrokeList.
-    const int fTotalCombinedVerbCnt;
-
-    SkSTArray<1, PatchChunk> fPatchChunks;
-
-    friend class GrOp;  // For ctor.
+    GrVertexChunkArray fPatchChunks;
 
 public:
     // This class is used to benchmark prepareBuffers().
diff --git a/src/gpu/tessellate/GrStrokeIndirectTessellator.cpp b/src/gpu/tessellate/GrStrokeIndirectTessellator.cpp
index d01c618..f306ad3 100644
--- a/src/gpu/tessellate/GrStrokeIndirectTessellator.cpp
+++ b/src/gpu/tessellate/GrStrokeIndirectTessellator.cpp
@@ -719,7 +719,7 @@
 #ifdef SK_DEBUG
     ~BinningInstanceWriter() {
         for (int i = 0; i < kNumBins; ++i) {
-            if (fInstanceWriters[i].isValid()) {
+            if (fInstanceWriters[i]) {
                 SkASSERT(fInstanceWriters[i] == fEndWriters[i]);
             }
         }
@@ -749,7 +749,8 @@
 }  // namespace
 
 void GrStrokeIndirectTessellator::prepare(GrMeshDrawOp::Target* target,
-                                          const SkMatrix& viewMatrix) {
+                                          const SkMatrix& viewMatrix,
+                                          int /*totalCombinedVerbCnt*/) {
     SkASSERT(fResolveLevels);
     SkASSERT(!fDrawIndirectBuffer);
     SkASSERT(!fInstanceBuffer);
@@ -775,7 +776,7 @@
     size_t instanceStride = GrStrokeTessellateShader::IndirectInstanceStride(fShaderFlags);
     GrVertexWriter instanceWriter = {target->makeVertexSpace(instanceStride, fChainedInstanceCount,
                                                              &fInstanceBuffer, &baseInstance)};
-    if (!instanceWriter.isValid()) {
+    if (!instanceWriter) {
         SkASSERT(!fInstanceBuffer);
         fDrawIndirectBuffer.reset();
         return;
diff --git a/src/gpu/tessellate/GrStrokeIndirectTessellator.h b/src/gpu/tessellate/GrStrokeIndirectTessellator.h
index 38700fd..b4c873f 100644
--- a/src/gpu/tessellate/GrStrokeIndirectTessellator.h
+++ b/src/gpu/tessellate/GrStrokeIndirectTessellator.h
@@ -22,14 +22,15 @@
     // become an issue if we try to draw a stroke with an astronomically wide width.
     constexpr static int8_t kMaxResolveLevel = 15;
 
-    GrStrokeIndirectTessellator(ShaderFlags, const SkMatrix&, PathStrokeList*,
+    GrStrokeIndirectTessellator(ShaderFlags, const SkMatrix& viewMatrix, PathStrokeList*,
                                 int totalCombinedVerbCnt, SkArenaAlloc*);
 
     // Adds the given tessellator to our chain. The chained tessellators all append to a shared
     // indirect draw list during prepare().
     void addToChain(GrStrokeIndirectTessellator*);
 
-    void prepare(GrMeshDrawOp::Target*, const SkMatrix&) override;
+    void prepare(GrMeshDrawOp::Target*, const SkMatrix& viewMatrix,
+                 int totalCombinedVerbCnt) override;
 
     void draw(GrOpFlushState*) const override;
 
diff --git a/src/gpu/tessellate/GrStrokeTessellateOp.cpp b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
index 3eda3f5..1c8acec 100644
--- a/src/gpu/tessellate/GrStrokeTessellateOp.cpp
+++ b/src/gpu/tessellate/GrStrokeTessellateOp.cpp
@@ -174,7 +174,6 @@
         ((fShaderFlags & ShaderFlags::kDynamicColor) || fTotalCombinedVerbCnt > 50)) {
         SkASSERT(!this->nextInChain());  // We never chain when hw tessellation is an option.
         fTessellator = arena->make<GrStrokeHardwareTessellator>(fShaderFlags, &fPathStrokeList,
-                                                                fTotalCombinedVerbCnt,
                                                                 *caps.shaderCaps());
         shaderMode = GrStrokeTessellateShader::Mode::kTessellation;
     } else {
@@ -259,7 +258,7 @@
                                     flushState->detachAppliedClip());
     }
     SkASSERT(fTessellator);
-    fTessellator->prepare(flushState, fViewMatrix);
+    fTessellator->prepare(flushState, fViewMatrix, fTotalCombinedVerbCnt);
 }
 
 void GrStrokeTessellateOp::onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) {
diff --git a/src/gpu/tessellate/GrStrokeTessellator.h b/src/gpu/tessellate/GrStrokeTessellator.h
index 4d80501..8d0ec4e 100644
--- a/src/gpu/tessellate/GrStrokeTessellator.h
+++ b/src/gpu/tessellate/GrStrokeTessellator.h
@@ -29,7 +29,8 @@
             : fShaderFlags(shaderFlags), fPathStrokeList(pathStrokeList) {}
 
     // Called before draw(). Prepares GPU buffers containing the geometry to tessellate.
-    virtual void prepare(GrMeshDrawOp::Target*, const SkMatrix& viewMatrix) = 0;
+    virtual void prepare(GrMeshDrawOp::Target*, const SkMatrix& viewMatrix,
+                         int totalCombinedVerbCnt) = 0;
 
     // Issues draw calls for the tessellated stroke. The caller is responsible for binding its
     // desired pipeline ahead of time.