Lift calls for setting dynamic state to Ganesh level

Previously, all backends were responsible to check for dynamic state
and update it during their internal draw calls. This CL adds
setScissor() and bindTextures() methods to GrOpsRenderTask, and places
this responsibility to update dynamic state on Ganesh instead. It also
replaces the backend-specific "onDrawMeshes" call, with a singular
"onDrawMesh" call that gets called for each mesh.

For now we keep drawMeshes() on GrOpsRenderPass for convenience, but
it will soon be removed and each call site will be responsible to set
its own dynamic state.

Change-Id: If141feae857b22cbf04416557d0c89986eda2a62
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/271976
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/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 5551e6e..6073bb4 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -625,6 +625,9 @@
         SK_ABORT("Manual framebuffer barrier not supported.");
     }
 
+    // Called before certain draws in order to guarantee coherent results from dst reads.
+    virtual void xferBarrier(GrRenderTarget*, GrXferBarrierType) = 0;
+
 protected:
     static bool MipMapsAreCorrect(SkISize dimensions, GrMipMapped, const BackendTextureData*);
     static bool CompressedDataIsCorrect(SkISize dimensions, SkImage::CompressionType,
@@ -664,9 +667,6 @@
     // and queries the individual sample locations.
     virtual void querySampleLocations(GrRenderTarget*, SkTArray<SkPoint>*) = 0;
 
-    // Called before certain draws in order to guarantee coherent results from dst reads.
-    virtual void xferBarrier(GrRenderTarget*, GrXferBarrierType) = 0;
-
     // overridden by backend-specific derived class to create objects.
     // Texture size, renderablility, format support, sample count will have already been validated
     // in base class before onCreateTexture is called.
diff --git a/src/gpu/GrOpsRenderPass.cpp b/src/gpu/GrOpsRenderPass.cpp
index dd511f7..ed82228 100644
--- a/src/gpu/GrOpsRenderPass.cpp
+++ b/src/gpu/GrOpsRenderPass.cpp
@@ -81,35 +81,90 @@
         SkASSERT(this->gpu()->findOrAssignSamplePatternKey(fRenderTarget)
                          == fRenderTarget->renderTargetPriv().getSamplePatternKey());
     }
+    fScissorStatus = (programInfo.pipeline().isScissorTestEnabled()) ?
+            DynamicStateStatus::kUninitialized : DynamicStateStatus::kDisabled;
+    bool hasTextures = (programInfo.primProc().numTextureSamplers() > 0);
+    if (!hasTextures) {
+        programInfo.pipeline().visitProxies([&hasTextures](GrSurfaceProxy*, GrMipMapped) {
+            hasTextures = true;
+        });
+    }
+    fTextureBindingStatus = (hasTextures) ?
+            DynamicStateStatus::kUninitialized : DynamicStateStatus::kDisabled;
+    fHasVertexAttributes = programInfo.primProc().hasVertexAttributes();
+    fHasInstanceAttributes = programInfo.primProc().hasInstanceAttributes();
 #endif
 
     fDrawPipelineStatus = DrawPipelineStatus::kOk;
+    fXferBarrierType = programInfo.pipeline().xferBarrierType(fRenderTarget->asTexture(),
+                                                              *this->gpu()->caps());
+}
+
+void GrOpsRenderPass::setScissorRect(const SkIRect& scissor) {
+    if (DrawPipelineStatus::kOk != fDrawPipelineStatus) {
+        SkASSERT(DrawPipelineStatus::kNotConfigured != fDrawPipelineStatus);
+        return;
+    }
+    SkASSERT(DynamicStateStatus::kDisabled != fScissorStatus);
+    this->onSetScissorRect(scissor);
+    SkDEBUGCODE(fScissorStatus = DynamicStateStatus::kConfigured);
+}
+
+void GrOpsRenderPass::bindTextures(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
+                                   const GrSurfaceProxy* const primProcTextures[]) {
+    if (DrawPipelineStatus::kOk != fDrawPipelineStatus) {
+        SkASSERT(DrawPipelineStatus::kNotConfigured != fDrawPipelineStatus);
+        return;
+    }
+    SkASSERT((primProc.numTextureSamplers() > 0) == SkToBool(primProcTextures));
+    // Don't assert on fTextureBindingStatus. onBindTextures() just turns into a no-op when there
+    // aren't any textures, and it's hard to tell from the GrPipeline whether there are any. For
+    // many clients it is easier to just always call this method.
+    if (!this->onBindTextures(primProc, pipeline, primProcTextures)) {
+        fDrawPipelineStatus = DrawPipelineStatus::kFailedToBind;
+        return;
+    }
+    SkDEBUGCODE(fTextureBindingStatus = DynamicStateStatus::kConfigured);
 }
 
 void GrOpsRenderPass::drawMeshes(const GrProgramInfo& programInfo, const GrMesh meshes[],
                                  int meshCount) {
+    if (programInfo.hasFixedScissor()) {
+        this->setScissorRect(programInfo.fixedScissor());
+    }
+    if (!programInfo.hasDynamicPrimProcTextures()) {
+        auto primProcTextures = (programInfo.hasFixedPrimProcTextures()) ?
+                programInfo.fixedPrimProcTextures() : nullptr;
+        this->bindTextures(programInfo.primProc(), programInfo.pipeline(), primProcTextures);
+    }
+    for (int i = 0; i < meshCount; ++i) {
+        if (programInfo.hasDynamicScissors()) {
+            this->setScissorRect(programInfo.dynamicScissor(i));
+        }
+        if (programInfo.hasDynamicPrimProcTextures()) {
+            this->bindTextures(programInfo.primProc(), programInfo.pipeline(),
+                               programInfo.dynamicPrimProcTextures(i));
+        }
+        this->drawMesh(programInfo.primitiveType(), meshes[i]);
+    }
+}
+
+void GrOpsRenderPass::drawMesh(GrPrimitiveType primitiveType, const GrMesh& mesh) {
     if (DrawPipelineStatus::kOk != fDrawPipelineStatus) {
         SkASSERT(DrawPipelineStatus::kNotConfigured != fDrawPipelineStatus);
         this->gpu()->stats()->incNumFailedDraws();
         return;
     }
 
-#ifdef SK_DEBUG
-    if (int numDynamicStateArrays = programInfo.numDynamicStateArrays()) {
-        SkASSERT(meshCount == numDynamicStateArrays);
-    }
-    for (int i = 0; i < meshCount; ++i) {
-        SkASSERT(programInfo.primProc().hasVertexAttributes() ==
-                 SkToBool(meshes[i].vertexBuffer()));
-        SkASSERT(programInfo.primProc().hasInstanceAttributes() ==
-                 SkToBool(meshes[i].instanceBuffer()));
-        if (GrPrimitiveRestart::kYes == meshes[i].primitiveRestart()) {
-             SkASSERT(this->gpu()->caps()->usePrimitiveRestart());
-        }
-    }
-#endif
+    SkASSERT(DynamicStateStatus::kUninitialized != fScissorStatus);
+    SkASSERT(DynamicStateStatus::kUninitialized != fTextureBindingStatus);
+    SkASSERT(SkToBool(mesh.vertexBuffer()) == fHasVertexAttributes);
+    SkASSERT(SkToBool(mesh.instanceBuffer()) == fHasInstanceAttributes);
+    SkASSERT(GrPrimitiveRestart::kNo == mesh.primitiveRestart() ||
+             this->gpu()->caps()->usePrimitiveRestart());
 
-    if (meshCount) {
-        this->onDrawMeshes(programInfo, meshes, meshCount);
+    if (kNone_GrXferBarrierType != fXferBarrierType) {
+        this->gpu()->xferBarrier(fRenderTarget, fXferBarrierType);
     }
+    this->onDrawMesh(primitiveType, mesh);
 }
diff --git a/src/gpu/GrOpsRenderPass.h b/src/gpu/GrOpsRenderPass.h
index 0652b26..0bea996 100644
--- a/src/gpu/GrOpsRenderPass.h
+++ b/src/gpu/GrOpsRenderPass.h
@@ -51,16 +51,35 @@
     virtual void end() = 0;
 
     // Updates the internal pipeline state for drawing with the provided GrProgramInfo.
-    // Returns false if the state could not be set.
+    // Enters an internal "bad" state if the pipeline could not be set.
     void bindPipeline(const GrProgramInfo&, const SkRect& drawBounds);
 
+    // The scissor rect is always dynamic state and therefore not stored on GrPipeline. If scissor
+    // test is enabled on the current pipeline, then the client must call setScissorRect() before
+    // drawing. The scissor rect may also be updated between draws without having to bind a new
+    // pipeline.
+    void setScissorRect(const SkIRect&);
+
+    // Texture bindings are dynamic state and therefore not set during bindPipeline(). If the
+    // current program uses textures, then the client must call bindTextures() before drawing.
+    // The primitive processor textures may also be updated between draws by calling bindTextures()
+    // again with a different array for primProcTextures. (On subsequent calls, if the backend is
+    // capable of updating the primitive processor textures independently, then it will
+    // automatically skip binding textures from GrPipeline.)
+    void bindTextures(const GrPrimitiveProcessor&, const GrPipeline&,
+                      const GrSurfaceProxy* const primProcTextures[]);
+
     // Draws the given array of meshes using the current pipeline state. The client must call
     // bindPipeline() before using this method.
     //
-    // NOTE: This method will soon be replaced by individual calls for each draw type (indexed,
-    // instanced, indexed-patterned, indirect, etc.).
+    // NOTE: This method will soon be deleted. While it continues to exist, it takes care of calling
+    // setScissor() and bindTextures() on the client's behalf.
     void drawMeshes(const GrProgramInfo&, const GrMesh[], int meshCount);
 
+    // Draws the given mesh using the current pipeline state. The client must call bindPipeline(),
+    // followed setScissor() and/or bindTextures() if necessary, before using this method.
+    void drawMesh(GrPrimitiveType, const GrMesh&);
+
     // Performs an upload of vertex data in the middle of a set of a set of draws
     virtual void inlineUpload(GrOpFlushState*, GrDeferredTextureUploadFn&) = 0;
 
@@ -99,7 +118,10 @@
 
     // overridden by backend-specific derived class to perform the rendering command.
     virtual bool onBindPipeline(const GrProgramInfo&, const SkRect& drawBounds) = 0;
-    virtual void onDrawMeshes(const GrProgramInfo&, const GrMesh[], int meshCount) = 0;
+    virtual void onSetScissorRect(const SkIRect&) = 0;
+    virtual bool onBindTextures(const GrPrimitiveProcessor&, const GrPipeline&,
+                                const GrSurfaceProxy* const primProcTextures[] = nullptr) = 0;
+    virtual void onDrawMesh(GrPrimitiveType, const GrMesh&) = 0;
     virtual void onClear(const GrFixedClip&, const SkPMColor4f&) = 0;
     virtual void onClearStencilClip(const GrFixedClip&, bool insideStencilMask) = 0;
     virtual void onExecuteDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>) {}
@@ -111,6 +133,20 @@
     };
 
     DrawPipelineStatus fDrawPipelineStatus = DrawPipelineStatus::kNotConfigured;
+    GrXferBarrierType fXferBarrierType;
+
+#ifdef SK_DEBUG
+    enum class DynamicStateStatus {
+        kDisabled,
+        kUninitialized,
+        kConfigured
+    };
+
+    DynamicStateStatus fScissorStatus = DynamicStateStatus::kDisabled;
+    DynamicStateStatus fTextureBindingStatus = DynamicStateStatus::kDisabled;
+    bool fHasVertexAttributes = false;
+    bool fHasInstanceAttributes = false;
+#endif
 
     typedef GrOpsRenderPass INHERITED;
 };
diff --git a/src/gpu/GrPipeline.cpp b/src/gpu/GrPipeline.cpp
index 4985a6e..3e18d5b 100644
--- a/src/gpu/GrPipeline.cpp
+++ b/src/gpu/GrPipeline.cpp
@@ -23,7 +23,7 @@
         fFlags |= Flags::kHasStencilClip;
     }
     if (hardClip.scissorState().enabled()) {
-        fFlags |= Flags::kScissorEnabled;
+        fFlags |= Flags::kScissorTestEnabled;
     }
 
     fWindowRectsState = hardClip.windowRectsState();
@@ -76,7 +76,7 @@
         , fXferProcessor(std::move(xp))
         , fOutputSwizzle(outputSwizzle) {
     if (GrScissorTest::kEnabled == scissorTest) {
-        fFlags |= Flags::kScissorEnabled;
+        fFlags |= Flags::kScissorTestEnabled;
     }
     this->setUserStencil(userStencil);
 }
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index a444c74..0f8eaa9 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -194,8 +194,8 @@
         }
     }
 
-    bool isScissorEnabled() const {
-        return SkToBool(fFlags & Flags::kScissorEnabled);
+    bool isScissorTestEnabled() const {
+        return SkToBool(fFlags & Flags::kScissorTestEnabled);
     }
 
     const GrWindowRectsState& getWindowRectsState() const { return fWindowRectsState; }
@@ -243,7 +243,7 @@
     enum class Flags : uint8_t {
         kHasStencilClip = (kLastInputFlag << 1),
         kStencilEnabled = (kLastInputFlag << 2),
-        kScissorEnabled = (kLastInputFlag << 3),
+        kScissorTestEnabled = (kLastInputFlag << 3),
     };
 
     GR_DECL_BITFIELD_CLASS_OPS_FRIENDS(Flags);
diff --git a/src/gpu/GrProgramInfo.cpp b/src/gpu/GrProgramInfo.cpp
index 636efaa..c3ac9de 100644
--- a/src/gpu/GrProgramInfo.cpp
+++ b/src/gpu/GrProgramInfo.cpp
@@ -39,7 +39,7 @@
         SkASSERT(!fPrimProc->numTextureSamplers());
     }
 
-    SkASSERT(!fPipeline->isScissorEnabled() || this->hasFixedScissor() ||
+    SkASSERT(!fPipeline->isScissorTestEnabled() || this->hasFixedScissor() ||
              this->hasDynamicScissors());
 
     if (this->hasDynamicPrimProcTextures()) {
diff --git a/src/gpu/GrProgramInfo.h b/src/gpu/GrProgramInfo.h
index 7f4a1f0..6e48e7b 100644
--- a/src/gpu/GrProgramInfo.h
+++ b/src/gpu/GrProgramInfo.h
@@ -65,7 +65,7 @@
     int numDynamicStateArrays() const { return fNumDynamicStateArrays; }
 
     bool hasDynamicScissors() const {
-        return fPipeline->isScissorEnabled() &&
+        return fPipeline->isScissorTestEnabled() &&
                fDynamicStateArrays && fDynamicStateArrays->fScissorRects;
     }
 
@@ -75,7 +75,7 @@
         return fDynamicStateArrays->fScissorRects[i];
     }
 
-    bool hasFixedScissor() const { return fPipeline->isScissorEnabled() && fFixedDynamicState; }
+    bool hasFixedScissor() const { return fPipeline->isScissorTestEnabled() && fFixedDynamicState; }
 
     const SkIRect& fixedScissor() const {
         SkASSERT(this->hasFixedScissor());
diff --git a/src/gpu/ccpr/GrCCFiller.cpp b/src/gpu/ccpr/GrCCFiller.cpp
index 5295104..6c6d897 100644
--- a/src/gpu/ccpr/GrCCFiller.cpp
+++ b/src/gpu/ccpr/GrCCFiller.cpp
@@ -519,7 +519,7 @@
 void GrCCFiller::drawPrimitives(
         GrOpFlushState* flushState, const GrCCCoverageProcessor& proc, const GrPipeline& pipeline,
         BatchID batchID, int PrimitiveTallies::*instanceType, const SkIRect& drawBounds) const {
-    SkASSERT(pipeline.isScissorEnabled());
+    SkASSERT(pipeline.isScissorTestEnabled());
 
     // Don't call reset(), as that also resets the reserve count.
     fMeshesScratchBuffer.pop_back_n(fMeshesScratchBuffer.count());
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index bc5ca03..b6c3fd4 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -1373,7 +1373,7 @@
             }
         } else if (this->glCaps().canFormatBeFBOColorAttachment(format.asGLFormat()) &&
                    !this->glCaps().performColorClearsAsDraws()) {
-            this->disableScissor();
+            this->flushScissorTest(GrScissorTest::kDisabled);
             this->disableWindowRectangles();
             this->flushColorWrite(true);
             this->flushClearColor(SK_PMColor4fTRANSPARENT);
@@ -1763,23 +1763,28 @@
     return GrGLBuffer::Make(this, size, intendedType, accessPattern, data);
 }
 
-void GrGLGpu::flushScissor(const GrScissorState& scissorState, int rtWidth, int rtHeight,
-                           GrSurfaceOrigin rtOrigin) {
-    if (scissorState.enabled()) {
-        auto scissor = GrNativeRect::MakeRelativeTo(rtOrigin, rtHeight, scissorState.rect());
-        if (fHWScissorSettings.fRect != scissor) {
-            GL_CALL(Scissor(scissor.fX, scissor.fY, scissor.fWidth, scissor.fHeight));
-            fHWScissorSettings.fRect = scissor;
-        }
+void GrGLGpu::flushScissorTest(GrScissorTest scissorTest) {
+    if (GrScissorTest::kEnabled == scissorTest) {
         if (kYes_TriState != fHWScissorSettings.fEnabled) {
             GL_CALL(Enable(GR_GL_SCISSOR_TEST));
             fHWScissorSettings.fEnabled = kYes_TriState;
         }
-        return;
+    } else {
+        if (kNo_TriState != fHWScissorSettings.fEnabled) {
+            GL_CALL(Disable(GR_GL_SCISSOR_TEST));
+            fHWScissorSettings.fEnabled = kNo_TriState;
+        }
     }
+}
 
-    // See fall through note above
-    this->disableScissor();
+void GrGLGpu::flushScissorRect(const SkIRect& scissor, int rtWidth, int rtHeight,
+                               GrSurfaceOrigin rtOrigin) {
+    auto nativeScissor = GrNativeRect::MakeRelativeTo(rtOrigin, rtHeight, scissor);
+    if (fHWScissorSettings.fRect != nativeScissor) {
+        GL_CALL(Scissor(nativeScissor.fX, nativeScissor.fY, nativeScissor.fWidth,
+                        nativeScissor.fHeight));
+        fHWScissorSettings.fRect = nativeScissor;
+    }
 }
 
 void GrGLGpu::flushWindowRectangles(const GrWindowRectsState& windowState,
@@ -1842,11 +1847,6 @@
                                   programInfo.pipeline().outputSwizzle());
 
     fHWProgram->updateUniforms(renderTarget, programInfo);
-    if (!programInfo.hasDynamicPrimProcTextures()) {
-        auto* primProcTextures = (programInfo.hasFixedPrimProcTextures())
-                ? programInfo.fixedPrimProcTextures() : nullptr;
-        fHWProgram->bindTextures(programInfo.primProc(), programInfo.pipeline(), primProcTextures);
-    }
 
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(renderTarget);
     GrStencilSettings stencil;
@@ -1857,14 +1857,7 @@
                       glRT->renderTargetPriv().numStencilBits());
     }
     this->flushStencil(stencil, programInfo.origin());
-    if (programInfo.pipeline().isScissorEnabled()) {
-        static constexpr SkIRect kBogusScissor{0, 0, 1, 1};
-        GrScissorState state(programInfo.fixedDynamicState() ? programInfo.fixedScissor()
-                                                             : kBogusScissor);
-        this->flushScissor(state, glRT->width(), glRT->height(), programInfo.origin());
-    } else {
-        this->disableScissor();
-    }
+    this->flushScissorTest(GrScissorTest(programInfo.pipeline().isScissorTestEnabled()));
     this->flushWindowRectangles(programInfo.pipeline().getWindowRectsState(),
                                 glRT, programInfo.origin());
     this->flushHWAAState(glRT, programInfo.pipeline().isHWAntialiasState());
@@ -1979,13 +1972,6 @@
 
     return bufferState->fGLTarget;
 }
-void GrGLGpu::disableScissor() {
-    if (kNo_TriState != fHWScissorSettings.fEnabled) {
-        GL_CALL(Disable(GR_GL_SCISSOR_TEST));
-        fHWScissorSettings.fEnabled = kNo_TriState;
-        return;
-    }
-}
 
 void GrGLGpu::clear(const GrFixedClip& clip, const SkPMColor4f& color,
                     GrRenderTarget* target, GrSurfaceOrigin origin) {
@@ -2023,7 +2009,7 @@
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(target);
     this->flushRenderTargetNoColorWrites(glRT);
 
-    this->disableScissor();
+    this->flushScissorTest(GrScissorTest::kDisabled);
     this->disableWindowRectangles();
 
     GL_CALL(StencilMask(0xffffffff));
@@ -2074,7 +2060,7 @@
         clearMask |= GR_GL_STENCIL_BUFFER_BIT;
     }
     if (clearMask) {
-        this->disableScissor();
+        this->flushScissorTest(GrScissorTest::kDisabled);
         this->disableWindowRectangles();
         GL_CALL(Clear(clearMask));
     }
@@ -2374,38 +2360,17 @@
     #endif
 #endif
 
-void GrGLGpu::drawMeshes(GrRenderTarget* renderTarget, const GrProgramInfo& programInfo,
-                         const GrMesh meshes[], int meshCount) {
-    SkASSERT(meshCount); // guaranteed by GrOpsRenderPass::draw
-
-    bool hasDynamicScissors = programInfo.hasDynamicScissors();
-    bool hasDynamicPrimProcTextures = programInfo.hasDynamicPrimProcTextures();
-
-    for (int m = 0; m < meshCount; ++m) {
-        if (auto barrierType = programInfo.pipeline().xferBarrierType(renderTarget->asTexture(),
-                                                                      *this->caps())) {
-            this->xferBarrier(renderTarget, barrierType);
-        }
-
-        if (hasDynamicScissors) {
-            GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(renderTarget);
-            this->flushScissor(GrScissorState(programInfo.dynamicScissor(m)),
-                               glRT->width(), glRT->height(), programInfo.origin());
-        }
-        if (hasDynamicPrimProcTextures) {
-            fHWProgram->bindTextures(programInfo.primProc(), programInfo.pipeline(),
-                                     programInfo.dynamicPrimProcTextures(m));
-        }
-        if (this->glCaps().requiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines() &&
-            GrIsPrimTypeLines(programInfo.primitiveType()) &&
-            !GrIsPrimTypeLines(fLastPrimitiveType)) {
-            GL_CALL(Enable(GR_GL_CULL_FACE));
-            GL_CALL(Disable(GR_GL_CULL_FACE));
-        }
-        meshes[m].sendToGpu(programInfo.primitiveType(), this);
-        fLastPrimitiveType = programInfo.primitiveType();
+void GrGLGpu::drawMesh(GrRenderTarget* renderTarget, GrPrimitiveType primitiveType,
+                       const GrMesh& mesh) {
+    if (this->glCaps().requiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines() &&
+        GrIsPrimTypeLines(primitiveType) && !GrIsPrimTypeLines(fLastPrimitiveType)) {
+        GL_CALL(Enable(GR_GL_CULL_FACE));
+        GL_CALL(Disable(GR_GL_CULL_FACE));
     }
 
+    mesh.sendToGpu(primitiveType, this);
+    fLastPrimitiveType = primitiveType;
+
 #if SWAP_PER_DRAW
     glFlush();
     #if defined(SK_BUILD_FOR_MAC)
@@ -2550,7 +2515,7 @@
         }
 
         // BlitFrameBuffer respects the scissor, so disable it.
-        this->disableScissor();
+        this->flushScissorTest(GrScissorTest::kDisabled);
         this->disableWindowRectangles();
         GL_CALL(BlitFramebuffer(l, b, r, t, l, b, r, t, GR_GL_COLOR_BUFFER_BIT, GR_GL_NEAREST));
     }
@@ -3539,7 +3504,7 @@
     this->flushHWAAState(nullptr, false);
     this->flushConservativeRasterState(false);
     this->flushWireframeState(false);
-    this->disableScissor();
+    this->flushScissorTest(GrScissorTest::kDisabled);
     this->disableWindowRectangles();
     this->disableStencil();
     if (this->glCaps().srgbWriteControl()) {
@@ -3590,7 +3555,7 @@
     fHWBoundRenderTargetUniqueID.makeInvalid();
 
     // BlitFrameBuffer respects the scissor, so disable it.
-    this->disableScissor();
+    this->flushScissorTest(GrScissorTest::kDisabled);
     this->disableWindowRectangles();
 
     GL_CALL(BlitFramebuffer(srcRect.fLeft,
@@ -3672,7 +3637,7 @@
     // Set "simple" state once:
     this->flushBlendAndColorWrite(GrXferProcessor::BlendInfo(), GrSwizzle::RGBA());
     this->flushHWAAState(nullptr, false);
-    this->disableScissor();
+    this->flushScissorTest(GrScissorTest::kDisabled);
     this->disableWindowRectangles();
     this->disableStencil();
 
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index c067d1e..3d7ad3d 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -75,13 +75,18 @@
 
     // Flushes state from GrProgramInfo to GL. Returns false if the state couldn't be set.
     bool flushGLState(GrRenderTarget*, const GrProgramInfo&);
+    void flushScissorRect(const SkIRect&, int rtWidth, int rtHeight, GrSurfaceOrigin);
+    void bindTextures(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
+                      const GrSurfaceProxy* const primProcTextures[] = nullptr) {
+        fHWProgram->bindTextures(primProc, pipeline, primProcTextures);
+    }
 
     // The GrGLOpsRenderPass does not buffer up draws before submitting them to the gpu.
     // Thus this is the implementation of the draw call for the corresponding passthrough function
     // on GrGLOpsRenderPass.
     //
     // The client must call flushGLState before this method.
-    void drawMeshes(GrRenderTarget*, const GrProgramInfo&, const GrMesh[], int meshCount);
+    void drawMesh(GrRenderTarget*, GrPrimitiveType, const GrMesh&);
 
     // GrMesh::SendToGpuImpl methods. These issue the actual GL draw calls.
     // Marked final as a hint to the compiler to not use virtual dispatch.
@@ -357,10 +362,14 @@
 
     // flushes the scissor. see the note on flushBoundTextureAndParams about
     // flushing the scissor after that function is called.
-    void flushScissor(const GrScissorState&, int rtWidth, int rtHeight, GrSurfaceOrigin rtOrigin);
-
-    // disables the scissor
-    void disableScissor();
+    void flushScissor(const GrScissorState& scissorState, int rtWidth, int rtHeight,
+                      GrSurfaceOrigin rtOrigin) {
+        this->flushScissorTest(GrScissorTest(scissorState.enabled()));
+        if (scissorState.enabled()) {
+            this->flushScissorRect(scissorState.rect(), rtWidth, rtHeight, rtOrigin);
+        }
+    }
+    void flushScissorTest(GrScissorTest);
 
     void flushWindowRectangles(const GrWindowRectsState&, const GrGLRenderTarget*, GrSurfaceOrigin);
     void disableWindowRectangles();
diff --git a/src/gpu/gl/GrGLOpsRenderPass.h b/src/gpu/gl/GrGLOpsRenderPass.h
index 4be4899..9a00aec 100644
--- a/src/gpu/gl/GrGLOpsRenderPass.h
+++ b/src/gpu/gl/GrGLOpsRenderPass.h
@@ -53,9 +53,18 @@
         return fGpu->flushGLState(fRenderTarget, programInfo);
     }
 
-    void onDrawMeshes(const GrProgramInfo& programInfo, const GrMesh mesh[],
-                      int meshCount) override {
-        fGpu->drawMeshes(fRenderTarget, programInfo, mesh, meshCount);
+    void onSetScissorRect(const SkIRect& scissor) override {
+        fGpu->flushScissorRect(scissor, fRenderTarget->width(), fRenderTarget->height(), fOrigin);
+    }
+
+    bool onBindTextures(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
+                        const GrSurfaceProxy* const primProcTextures[]) override {
+        fGpu->bindTextures(primProc, pipeline, primProcTextures);
+        return true;
+    }
+
+    void onDrawMesh(GrPrimitiveType primitiveType, const GrMesh& mesh) override {
+        fGpu->drawMesh(fRenderTarget, primitiveType, mesh);
     }
 
     void onClear(const GrFixedClip& clip, const SkPMColor4f& color) override {
diff --git a/src/gpu/gl/GrGLPathRendering.cpp b/src/gpu/gl/GrGLPathRendering.cpp
index 7d93a11..abbd9d2 100644
--- a/src/gpu/gl/GrGLPathRendering.cpp
+++ b/src/gpu/gl/GrGLPathRendering.cpp
@@ -5,16 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "src/gpu/gl/GrGLGpu.h"
-#include "src/gpu/gl/GrGLPathRendering.h"
-#include "src/gpu/gl/GrGLUtil.h"
-
-#include "src/gpu/GrRenderTargetProxy.h"
-#include "src/gpu/gl/GrGLPath.h"
-#include "src/gpu/gl/GrGLPathRendering.h"
-
 #include "include/core/SkStream.h"
 #include "include/core/SkTypeface.h"
+#include "src/gpu/GrProgramInfo.h"
+#include "src/gpu/GrRenderTargetProxy.h"
+#include "src/gpu/gl/GrGLGpu.h"
+#include "src/gpu/gl/GrGLPath.h"
+#include "src/gpu/gl/GrGLPathRendering.h"
+#include "src/gpu/gl/GrGLUtil.h"
 
 #define GL_CALL(X) GR_GL_CALL(this->gpu()->glInterface(), X)
 #define GL_CALL_RET(RET, X) GR_GL_CALL_RET(this->gpu()->glInterface(), RET, X)
@@ -115,9 +113,16 @@
                                    const GrProgramInfo& programInfo,
                                    const GrStencilSettings& stencilPassSettings,
                                    const GrPath* path) {
+    SkASSERT(!programInfo.hasDynamicScissors());
+    SkASSERT(!programInfo.hasDynamicPrimProcTextures());
     if (!this->gpu()->flushGLState(renderTarget, programInfo)) {
         return;
     }
+    if (programInfo.hasFixedScissor()) {
+        this->gpu()->flushScissorRect(programInfo.fixedScissor(), renderTarget->width(),
+                                      renderTarget->height(), programInfo.origin());
+    }
+    this->gpu()->bindTextures(programInfo.primProc(), programInfo.pipeline());
 
     const GrGLPath* glPath = static_cast<const GrGLPath*>(path);
 
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index 4864343..fd2c4a3 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -101,11 +101,10 @@
 }
 
 void GrGLProgram::bindTextures(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
-                               const GrSurfaceProxy* const primProcTextureOverrides[]) {
+                               const GrSurfaceProxy* const primProcTextures[]) {
     for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-        SkASSERT(primProcTextureOverrides[i]->asTextureProxy());
-        auto* overrideTexture = static_cast<GrGLTexture*>(
-                primProcTextureOverrides[i]->peekTexture());
+        SkASSERT(primProcTextures[i]->asTextureProxy());
+        auto* overrideTexture = static_cast<GrGLTexture*>(primProcTextures[i]->peekTexture());
         fGpu->bindTexture(i, primProc.textureSampler(i).samplerState(),
                           primProc.textureSampler(i).swizzle(), overrideTexture);
     }
diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h
index f372067..83ff0c6 100644
--- a/src/gpu/gl/GrGLProgram.h
+++ b/src/gpu/gl/GrGLProgram.h
@@ -120,12 +120,10 @@
     void updateUniforms(const GrRenderTarget*, const GrProgramInfo&);
 
     /**
-     * Binds all primitive processor and fragment processor textures. We configure primitive
-     * processor samplers based on the proxies on the processor itself, but the actual textures we
-     * bind come from primProcTextureOverrides.
+     * Binds all primitive processor and fragment processor textures.
      */
     void bindTextures(const GrPrimitiveProcessor&, const GrPipeline&,
-                      const GrSurfaceProxy* const primProcTextureOverrides[]);
+                      const GrSurfaceProxy* const primProcTextures[]);
 
     int vertexStride() const { return fVertexStride; }
     int instanceStride() const { return fInstanceStride; }
diff --git a/src/gpu/mock/GrMockOpsRenderPass.h b/src/gpu/mock/GrMockOpsRenderPass.h
index e392400..d5af562 100644
--- a/src/gpu/mock/GrMockOpsRenderPass.h
+++ b/src/gpu/mock/GrMockOpsRenderPass.h
@@ -37,7 +37,12 @@
     bool onBindPipeline(const GrProgramInfo&, const SkRect&) override {
         return true;
     }
-    void onDrawMeshes(const GrProgramInfo&, const GrMesh[], int meshCount) override {
+    void onSetScissorRect(const SkIRect&) override {}
+    bool onBindTextures(const GrPrimitiveProcessor&, const GrPipeline&,
+                        const GrSurfaceProxy* const primProcTextures[]) override {
+        return true;
+    }
+    void onDrawMesh(GrPrimitiveType, const GrMesh&) override {
         this->markRenderTargetDirty();
         ++fNumDraws;
     }
@@ -45,7 +50,6 @@
         this->markRenderTargetDirty();
     }
     void onClearStencilClip(const GrFixedClip&, bool insideStencilMask) override {}
-
     void markRenderTargetDirty() {
         if (auto* tex = fRenderTarget->asTexture()) {
             tex->texturePriv().markMipMapsDirty();
diff --git a/src/gpu/mtl/GrMtlOpsRenderPass.h b/src/gpu/mtl/GrMtlOpsRenderPass.h
index 8792eea..9613405 100644
--- a/src/gpu/mtl/GrMtlOpsRenderPass.h
+++ b/src/gpu/mtl/GrMtlOpsRenderPass.h
@@ -40,8 +40,10 @@
     GrGpu* gpu() override { return fGpu; }
 
     bool onBindPipeline(const GrProgramInfo&, const SkRect& drawBounds) override;
-
-    void onDrawMeshes(const GrProgramInfo&, const GrMesh[], int meshCount) override;
+    void onSetScissorRect(const SkIRect&) override;
+    bool onBindTextures(const GrPrimitiveProcessor&, const GrPipeline&,
+                        const GrSurfaceProxy* const primProcTextures[]) override;
+    void onDrawMesh(GrPrimitiveType, const GrMesh&) override;
 
     void onClear(const GrFixedClip& clip, const SkPMColor4f& color) override;
 
diff --git a/src/gpu/mtl/GrMtlOpsRenderPass.mm b/src/gpu/mtl/GrMtlOpsRenderPass.mm
index 0f887d4..8c902d5 100644
--- a/src/gpu/mtl/GrMtlOpsRenderPass.mm
+++ b/src/gpu/mtl/GrMtlOpsRenderPass.mm
@@ -75,48 +75,39 @@
         [fActiveRenderCmdEncoder setTriangleFillMode:MTLTriangleFillModeFill];
     }
 
-    if (!programInfo.pipeline().isScissorEnabled()) {
+    if (!programInfo.pipeline().isScissorTestEnabled()) {
+        // "Disable" scissor by setting it to the full pipeline bounds.
         GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
                                                        fRenderTarget, fOrigin,
                                                        SkIRect::MakeWH(fRenderTarget->width(),
                                                                        fRenderTarget->height()));
-    } else if (!programInfo.hasDynamicScissors()) {
-        SkASSERT(programInfo.hasFixedScissor());
-
-        GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
-                                                       fRenderTarget, fOrigin,
-                                                       programInfo.fixedScissor());
-    }
-
-    if (!programInfo.hasDynamicPrimProcTextures()) {
-        fActivePipelineState->bindTextures(fActiveRenderCmdEncoder);
     }
 
     fBounds.join(drawBounds);
     return true;
 }
 
-void GrMtlOpsRenderPass::onDrawMeshes(const GrProgramInfo& programInfo, const GrMesh meshes[],
-                                      int meshCount) {
+void GrMtlOpsRenderPass::onSetScissorRect(const SkIRect& scissor) {
     SkASSERT(fActivePipelineState);
+    SkASSERT(fActiveRenderCmdEncoder);
+    GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder, fRenderTarget,
+                                                   fOrigin, scissor);
+}
 
-    for (int i = 0; i < meshCount; ++i) {
-        const GrMesh& mesh = meshes[i];
-        SkASSERT(nil != fActiveRenderCmdEncoder);
+bool GrMtlOpsRenderPass::onBindTextures(const GrPrimitiveProcessor& primProc,
+                                        const GrPipeline& pipeline,
+                                        const GrSurfaceProxy* const primProcTextures[]) {
+    SkASSERT(fActivePipelineState);
+    SkASSERT(fActiveRenderCmdEncoder);
+    fActivePipelineState->setTextures(primProc, pipeline, primProcTextures);
+    fActivePipelineState->bindTextures(fActiveRenderCmdEncoder);
+    return true;
+}
 
-        if (programInfo.hasDynamicScissors()) {
-            GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder, fRenderTarget,
-                                                           fOrigin,
-                                                           programInfo.dynamicScissor(i));
-        }
-        if (programInfo.hasDynamicPrimProcTextures()) {
-            auto meshProxies = programInfo.dynamicPrimProcTextures(i);
-            fActivePipelineState->setTextures(programInfo, meshProxies);
-            fActivePipelineState->bindTextures(fActiveRenderCmdEncoder);
-        }
-
-        mesh.sendToGpu(programInfo.primitiveType(), this);
-    }
+void GrMtlOpsRenderPass::onDrawMesh(GrPrimitiveType primitiveType, const GrMesh& mesh) {
+    SkASSERT(fActivePipelineState);
+    SkASSERT(nil != fActiveRenderCmdEncoder);
+    mesh.sendToGpu(primitiveType, this);
 }
 
 void GrMtlOpsRenderPass::onClear(const GrFixedClip& clip, const SkPMColor4f& color) {
diff --git a/src/gpu/mtl/GrMtlPipelineState.h b/src/gpu/mtl/GrMtlPipelineState.h
index de55756..d0fa9b9 100644
--- a/src/gpu/mtl/GrMtlPipelineState.h
+++ b/src/gpu/mtl/GrMtlPipelineState.h
@@ -48,8 +48,7 @@
 
     void setData(const GrRenderTarget*, const GrProgramInfo&);
 
-    void setTextures(const GrProgramInfo& programInfo,
-                     const GrSurfaceProxy* const primProcTextures[]);
+    void setTextures(const GrPrimitiveProcessor&, const GrPipeline&, const GrSurfaceProxy* const[]);
     void bindTextures(id<MTLRenderCommandEncoder> renderCmdEncoder);
 
     void setDrawState(id<MTLRenderCommandEncoder>, const GrSwizzle& outputSwizzle,
diff --git a/src/gpu/mtl/GrMtlPipelineState.mm b/src/gpu/mtl/GrMtlPipelineState.mm
index 6271f2c..8550fab 100644
--- a/src/gpu/mtl/GrMtlPipelineState.mm
+++ b/src/gpu/mtl/GrMtlPipelineState.mm
@@ -76,11 +76,6 @@
                                 offset);
     }
 
-    if (!programInfo.hasDynamicPrimProcTextures()) {
-        auto proxies = programInfo.hasFixedPrimProcTextures() ? programInfo.fixedPrimProcTextures()
-                                                              : nullptr;
-        this->setTextures(programInfo, proxies);
-    }
     fDataManager.resetDirtyBits();
 
 #ifdef SK_DEBUG
@@ -93,17 +88,18 @@
     fStencil = programInfo.nonGLStencilSettings();
 }
 
-void GrMtlPipelineState::setTextures(const GrProgramInfo& programInfo,
+void GrMtlPipelineState::setTextures(const GrPrimitiveProcessor& primProc,
+                                     const GrPipeline& pipeline,
                                      const GrSurfaceProxy* const primProcTextures[]) {
     fSamplerBindings.reset();
-    for (int i = 0; i < programInfo.primProc().numTextureSamplers(); ++i) {
+    for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
         SkASSERT(primProcTextures[i]->asTextureProxy());
-        const auto& sampler = programInfo.primProc().textureSampler(i);
+        const auto& sampler = primProc.textureSampler(i);
         auto texture = static_cast<GrMtlTexture*>(primProcTextures[i]->peekTexture());
         fSamplerBindings.emplace_back(sampler.samplerState(), texture, fGpu);
     }
 
-    GrFragmentProcessor::CIter fpIter(programInfo.pipeline());
+    GrFragmentProcessor::CIter fpIter(pipeline);
     for (; fpIter; ++fpIter) {
         for (int i = 0; i < fpIter->numTextureSamplers(); ++i) {
             const auto& sampler = fpIter->textureSampler(i);
@@ -111,7 +107,7 @@
         }
     }
 
-    if (GrTextureProxy* dstTextureProxy = programInfo.pipeline().dstProxyView().asTextureProxy()) {
+    if (GrTextureProxy* dstTextureProxy = pipeline.dstProxyView().asTextureProxy()) {
         fSamplerBindings.emplace_back(
                 GrSamplerState::Filter::kNearest, dstTextureProxy->peekTexture(), fGpu);
     }
diff --git a/src/gpu/vk/GrVkOpsRenderPass.cpp b/src/gpu/vk/GrVkOpsRenderPass.cpp
index 7de73cc..92319a5 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.cpp
+++ b/src/gpu/vk/GrVkOpsRenderPass.cpp
@@ -557,28 +557,10 @@
         return false;
     }
 
-    // Check whether we need to bind textures between each GrMesh. If not we can bind them all now.
-    if (!programInfo.hasDynamicPrimProcTextures()) {
-        auto proxies = programInfo.hasFixedPrimProcTextures() ? programInfo.fixedPrimProcTextures()
-                                                              : nullptr;
-        if (!fCurrentPipelineState->setAndBindTextures(
-                fGpu, programInfo.primProc(), programInfo.pipeline(), proxies, currentCB)) {
-            return false;
-        }
-    }
-
-    if (!programInfo.pipeline().isScissorEnabled()) {
+    if (!programInfo.pipeline().isScissorTestEnabled()) {
+        // "Disable" scissor by setting it to the full pipeline bounds.
         GrVkPipeline::SetDynamicScissorRectState(fGpu, currentCB, fRenderTarget, fOrigin,
                                                  fCurrentPipelineBounds);
-    } else if (!programInfo.hasDynamicScissors()) {
-        SkASSERT(programInfo.hasFixedScissor());
-
-        SkIRect combinedScissorRect;
-        if (!combinedScissorRect.intersect(fCurrentPipelineBounds, programInfo.fixedScissor())) {
-            combinedScissorRect = SkIRect::MakeEmpty();
-        }
-        GrVkPipeline::SetDynamicScissorRectState(fGpu, currentCB, fRenderTarget, fOrigin,
-                                                 combinedScissorRect);
     }
     GrVkPipeline::SetDynamicViewportState(fGpu, currentCB, fRenderTarget);
     GrVkPipeline::SetDynamicBlendConstantState(fGpu, currentCB,
@@ -588,50 +570,36 @@
     return true;
 }
 
-void GrVkOpsRenderPass::onDrawMeshes(const GrProgramInfo& programInfo, const GrMesh meshes[],
-                                     int meshCount) {
+void GrVkOpsRenderPass::onSetScissorRect(const SkIRect& scissor) {
+    SkIRect combinedScissorRect;
+    if (!combinedScissorRect.intersect(fCurrentPipelineBounds, scissor)) {
+        combinedScissorRect = SkIRect::MakeEmpty();
+    }
+    GrVkPipeline::SetDynamicScissorRectState(fGpu, this->currentCommandBuffer(), fRenderTarget,
+                                             fOrigin, combinedScissorRect);
+}
+
+bool GrVkOpsRenderPass::onBindTextures(const GrPrimitiveProcessor& primProc,
+                                       const GrPipeline& pipeline,
+                                       const GrSurfaceProxy* const primProcTextures[]) {
+    return fCurrentPipelineState->setAndBindTextures(fGpu, primProc, pipeline, primProcTextures,
+                                                     this->currentCommandBuffer());
+}
+
+void GrVkOpsRenderPass::onDrawMesh(GrPrimitiveType primitiveType, const GrMesh& mesh) {
     if (!fCurrentRenderPass) {
         SkASSERT(fGpu->isDeviceLost());
         return;
     }
 
     SkASSERT(fCurrentPipelineState);
-    SkASSERT(meshCount); // guaranteed by GrOpsRenderPass::draw
-
-    for (int i = 0; i < meshCount; ++i) {
-        const GrMesh& mesh = meshes[i];
-
-        if (programInfo.hasDynamicScissors()) {
-            SkIRect combinedScissorRect;
-            if (!combinedScissorRect.intersect(fCurrentPipelineBounds,
-                                               programInfo.dynamicScissor(i))) {
-                combinedScissorRect = SkIRect::MakeEmpty();
-            }
-            GrVkPipeline::SetDynamicScissorRectState(fGpu, this->currentCommandBuffer(),
-                                                     fRenderTarget, fOrigin, combinedScissorRect);
-        }
-        if (programInfo.hasDynamicPrimProcTextures()) {
-            auto meshProxies = programInfo.dynamicPrimProcTextures(i);
-            if (!fCurrentPipelineState->setAndBindTextures(fGpu, programInfo.primProc(),
-                                                           programInfo.pipeline(), meshProxies,
-                                                           this->currentCommandBuffer())) {
-                if (fGpu->isDeviceLost()) {
-                    return;
-                } else {
-                    continue;
-                }
-            }
-        }
-        mesh.sendToGpu(programInfo.primitiveType(), this);
-    }
-
+    mesh.sendToGpu(primitiveType, this);
     fCurrentCBIsEmpty = false;
 }
 
 void GrVkOpsRenderPass::sendInstancedMeshToGpu(GrPrimitiveType, const GrMesh& mesh, int vertexCount,
                                                int baseVertex, int instanceCount,
-                                               int baseInstance)
-{
+                                               int baseInstance) {
     SkASSERT(!mesh.vertexBuffer() || !mesh.vertexBuffer()->isCpuBuffer());
     SkASSERT(!mesh.instanceBuffer() || !mesh.instanceBuffer()->isCpuBuffer());
     auto gpuVertexBuffer = static_cast<const GrGpuBuffer*>(mesh.vertexBuffer());
diff --git a/src/gpu/vk/GrVkOpsRenderPass.h b/src/gpu/vk/GrVkOpsRenderPass.h
index 48e3df7..96bbe56 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.h
+++ b/src/gpu/vk/GrVkOpsRenderPass.h
@@ -69,8 +69,10 @@
                       const GrGpuBuffer* instanceBuffer);
 
     bool onBindPipeline(const GrProgramInfo&, const SkRect& drawBounds) override;
-
-    void onDrawMeshes(const GrProgramInfo&, const GrMesh[], int meshCount) override;
+    void onSetScissorRect(const SkIRect&) override;
+    bool onBindTextures(const GrPrimitiveProcessor&, const GrPipeline&,
+                        const GrSurfaceProxy* const primProcTextures[]) override;
+    void onDrawMesh(GrPrimitiveType, const GrMesh&) override;
 
     // GrMesh::SendToGpuImpl methods. These issue the actual Vulkan draw commands.
     // Marked final as a hint to the compiler to not use virtual dispatch.