Add GrOpsRenderPass::bindPipeline()

Clients now must call bindPipeline() before drawing.

Also renames GrOpsRenderPass::draw() to drawMeshes(), in order to
ensure every call site gets updated. drawMeshes() will soon be
replaced by individual calls for each draw type (indexed, instanced,
indexed-patterned, indirect, etc.).

Change-Id: I93ef579ded7d0048c5aa1bf1d7c0eb7bc1cd27b2
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/270424
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/gm/clockwise.cpp b/gm/clockwise.cpp
index 07892ac..25ff881 100644
--- a/gm/clockwise.cpp
+++ b/gm/clockwise.cpp
@@ -213,8 +213,8 @@
         mesh.setNonIndexedNonInstanced(4);
         mesh.setVertexData(std::move(fVertexBuffer));
 
-        flushState->opsRenderPass()->draw(*fProgramInfo, &mesh, 1,
-                                          SkRect::MakeXYWH(0, fY, 100, 100));
+        flushState->opsRenderPass()->bindPipeline(*fProgramInfo, SkRect::MakeXYWH(0, fY, 100, 100));
+        flushState->opsRenderPass()->drawMeshes(*fProgramInfo, &mesh, 1);
     }
 
     sk_sp<GrBuffer> fVertexBuffer;
diff --git a/gm/fwidth_squircle.cpp b/gm/fwidth_squircle.cpp
index 76aa830..97a88ae 100644
--- a/gm/fwidth_squircle.cpp
+++ b/gm/fwidth_squircle.cpp
@@ -221,8 +221,8 @@
         mesh.setNonIndexedNonInstanced(4);
         mesh.setVertexData(std::move(fVertexBuffer));
 
-        flushState->opsRenderPass()->draw(*fProgramInfo, &mesh, 1,
-                                          SkRect::MakeIWH(kWidth, kHeight));
+        flushState->opsRenderPass()->bindPipeline(*fProgramInfo, SkRect::MakeIWH(kWidth, kHeight));
+        flushState->opsRenderPass()->drawMeshes(*fProgramInfo, &mesh, 1);
 
     }
 
diff --git a/gm/samplelocations.cpp b/gm/samplelocations.cpp
index e131852..2bba40c 100644
--- a/gm/samplelocations.cpp
+++ b/gm/samplelocations.cpp
@@ -285,7 +285,8 @@
         GrMesh mesh;
         mesh.setInstanced(nullptr, 200*200, 0, 4);
 
-        flushState->opsRenderPass()->draw(*fProgramInfo, &mesh, 1, SkRect::MakeIWH(200, 200));
+        flushState->opsRenderPass()->bindPipeline(*fProgramInfo, SkRect::MakeIWH(200, 200));
+        flushState->opsRenderPass()->drawMeshes(*fProgramInfo, &mesh, 1);
     }
 
     const GradType fGradType;
diff --git a/gm/tessellation.cpp b/gm/tessellation.cpp
index 0eec638..e390b0d 100644
--- a/gm/tessellation.cpp
+++ b/gm/tessellation.cpp
@@ -337,7 +337,8 @@
                                   &pipeline, shader.get(), &fixedDynamicState, nullptr, 0,
                                   GrPrimitiveType::kPatches, tessellationPatchVertexCount);
 
-        state->opsRenderPass()->draw(programInfo, &mesh, 1, SkRect::MakeIWH(kWidth, kHeight));
+        state->opsRenderPass()->bindPipeline(programInfo, SkRect::MakeIWH(kWidth, kHeight));
+        state->opsRenderPass()->drawMeshes(programInfo, &mesh, 1);
     }
 
     const SkMatrix fViewMatrix;
diff --git a/src/gpu/GrOpFlushState.cpp b/src/gpu/GrOpFlushState.cpp
index cb8b13b..5364bf1 100644
--- a/src/gpu/GrOpFlushState.cpp
+++ b/src/gpu/GrOpFlushState.cpp
@@ -54,8 +54,8 @@
                                   fCurrDraw->fMeshCnt,
                                   fCurrDraw->fPrimitiveType);
 
-        this->opsRenderPass()->draw(programInfo, fCurrDraw->fMeshes,
-                                    fCurrDraw->fMeshCnt, chainBounds);
+        this->opsRenderPass()->bindPipeline(programInfo, chainBounds);
+        this->opsRenderPass()->drawMeshes(programInfo, fCurrDraw->fMeshes, fCurrDraw->fMeshCnt);
         fTokenTracker->flushToken();
         ++fCurrDraw;
     }
diff --git a/src/gpu/GrOpsRenderPass.cpp b/src/gpu/GrOpsRenderPass.cpp
index 8626ada..dd511f7 100644
--- a/src/gpu/GrOpsRenderPass.cpp
+++ b/src/gpu/GrOpsRenderPass.cpp
@@ -26,41 +26,53 @@
     // be redirected to draws instead
     SkASSERT(!this->gpu()->caps()->performColorClearsAsDraws());
     SkASSERT(!clip.scissorEnabled() || !this->gpu()->caps()->performPartialClearsAsDraws());
+    fDrawPipelineStatus = DrawPipelineStatus::kNotConfigured;
     this->onClear(clip, color);
 }
 
 void GrOpsRenderPass::clearStencilClip(const GrFixedClip& clip, bool insideStencilMask) {
     // As above, make sure the stencil clear wasn't supposed to be a draw rect with stencil settings
     SkASSERT(!this->gpu()->caps()->performStencilClearsAsDraws());
+    fDrawPipelineStatus = DrawPipelineStatus::kNotConfigured;
     this->onClearStencilClip(clip, insideStencilMask);
 }
 
-bool GrOpsRenderPass::draw(const GrProgramInfo& programInfo,
-                           const GrMesh meshes[], int meshCount, const SkRect& bounds) {
-    if (!meshCount) {
-        return true;
-    }
+void GrOpsRenderPass::executeDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler> drawable) {
+    fDrawPipelineStatus = DrawPipelineStatus::kNotConfigured;
+    this->onExecuteDrawable(std::move(drawable));
+}
 
+void GrOpsRenderPass::bindPipeline(const GrProgramInfo& programInfo, const SkRect& drawBounds) {
 #ifdef SK_DEBUG
-    SkASSERT(GrPrimitiveType::kPatches != programInfo.primitiveType() ||
-             this->gpu()->caps()->shaderCaps()->tessellationSupport());
-    SkASSERT(!programInfo.primProc().hasInstanceAttributes() ||
-             this->gpu()->caps()->instanceAttribSupport());
-    SkASSERT(!programInfo.pipeline().usesConservativeRaster() ||
-             this->gpu()->caps()->conservativeRasterSupport());
-    SkASSERT(!programInfo.pipeline().isWireframe() ||
-             this->gpu()->caps()->wireframeSupport());
-
-    programInfo.compatibleWithMeshes(meshes, meshCount, *this->gpu()->caps());
+    if (programInfo.primProc().hasInstanceAttributes()) {
+         SkASSERT(this->gpu()->caps()->instanceAttribSupport());
+    }
+    if (programInfo.pipeline().usesConservativeRaster()) {
+        SkASSERT(this->gpu()->caps()->conservativeRasterSupport());
+        // Conservative raster, by default, only supports triangles. Implementations can
+        // optionally indicate that they also support points and lines, but we don't currently
+        // query or track that info.
+        SkASSERT(GrIsPrimTypeTris(programInfo.primitiveType()));
+    }
+    if (programInfo.pipeline().isWireframe()) {
+         SkASSERT(this->gpu()->caps()->wireframeSupport());
+    }
+    if (GrPrimitiveType::kPatches == programInfo.primitiveType()) {
+        SkASSERT(this->gpu()->caps()->shaderCaps()->tessellationSupport());
+    }
     programInfo.checkAllInstantiated();
     programInfo.checkMSAAAndMIPSAreResolved();
 #endif
 
     if (programInfo.primProc().numVertexAttributes() > this->gpu()->caps()->maxVertexAttributes()) {
-        this->gpu()->stats()->incNumFailedDraws();
-        return false;
+        fDrawPipelineStatus = DrawPipelineStatus::kFailedToBind;
+        return;
     }
-    this->onDraw(programInfo, meshes, meshCount, bounds);
+
+    if (!this->onBindPipeline(programInfo, drawBounds)) {
+        fDrawPipelineStatus = DrawPipelineStatus::kFailedToBind;
+        return;
+    }
 
 #ifdef SK_DEBUG
     GrProcessor::CustomFeatures processorFeatures = programInfo.requestedFeatures();
@@ -70,5 +82,34 @@
                          == fRenderTarget->renderTargetPriv().getSamplePatternKey());
     }
 #endif
-    return true;
+
+    fDrawPipelineStatus = DrawPipelineStatus::kOk;
+}
+
+void GrOpsRenderPass::drawMeshes(const GrProgramInfo& programInfo, const GrMesh meshes[],
+                                 int meshCount) {
+    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
+
+    if (meshCount) {
+        this->onDrawMeshes(programInfo, meshes, meshCount);
+    }
 }
diff --git a/src/gpu/GrOpsRenderPass.h b/src/gpu/GrOpsRenderPass.h
index 4b2abc1..0652b26 100644
--- a/src/gpu/GrOpsRenderPass.h
+++ b/src/gpu/GrOpsRenderPass.h
@@ -50,11 +50,16 @@
     // Signals the end of recording to the GrOpsRenderPass and that it can now be submitted.
     virtual void end() = 0;
 
-    // We pass in an array of meshCount GrMesh to the draw. The backend should loop over each
-    // GrMesh object and emit a draw for it. Each draw will use the same GrPipeline and
-    // GrPrimitiveProcessor. This may fail if the draw would exceed any resource limits (e.g.
-    // number of vertex attributes is too large).
-    bool draw(const GrProgramInfo&, const GrMesh[], int meshCount, const SkRect& bounds);
+    // Updates the internal pipeline state for drawing with the provided GrProgramInfo.
+    // Returns false if the state could not be set.
+    void bindPipeline(const GrProgramInfo&, const SkRect& drawBounds);
+
+    // 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.).
+    void drawMeshes(const GrProgramInfo&, const GrMesh[], int meshCount);
 
     // Performs an upload of vertex data in the middle of a set of a set of draws
     virtual void inlineUpload(GrOpFlushState*, GrDeferredTextureUploadFn&) = 0;
@@ -69,7 +74,7 @@
     /**
      * Executes the SkDrawable object for the underlying backend.
      */
-    virtual void executeDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>) {}
+    void executeDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>);
 
 protected:
     GrOpsRenderPass() : fOrigin(kTopLeft_GrSurfaceOrigin), fRenderTarget(nullptr) {}
@@ -92,14 +97,20 @@
 private:
     virtual GrGpu* gpu() = 0;
 
-    // overridden by backend-specific derived class to perform the draw call.
-    virtual void onDraw(const GrProgramInfo&, const GrMesh[], int meshCount,
-                        const SkRect& bounds) = 0;
-
-    // overridden by backend-specific derived class to perform the clear.
+    // 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 onClear(const GrFixedClip&, const SkPMColor4f&) = 0;
-
     virtual void onClearStencilClip(const GrFixedClip&, bool insideStencilMask) = 0;
+    virtual void onExecuteDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>) {}
+
+    enum class DrawPipelineStatus {
+        kOk = 0,
+        kNotConfigured,
+        kFailedToBind
+    };
+
+    DrawPipelineStatus fDrawPipelineStatus = DrawPipelineStatus::kNotConfigured;
 
     typedef GrOpsRenderPass INHERITED;
 };
diff --git a/src/gpu/GrProgramInfo.cpp b/src/gpu/GrProgramInfo.cpp
index 152ee19..636efaa 100644
--- a/src/gpu/GrProgramInfo.cpp
+++ b/src/gpu/GrProgramInfo.cpp
@@ -118,22 +118,4 @@
     }
 }
 
-void GrProgramInfo::compatibleWithMeshes(const GrMesh meshes[], int meshCount,
-                                         const GrCaps& caps) const {
-    SkASSERT(!fNumDynamicStateArrays || meshCount == fNumDynamicStateArrays);
-
-    for (int i = 0; i < meshCount; ++i) {
-        SkASSERT(fPrimProc->hasVertexAttributes() == SkToBool(meshes[i].vertexBuffer()));
-        SkASSERT(fPrimProc->hasInstanceAttributes() == SkToBool(meshes[i].instanceBuffer()));
-        if (fPipeline->usesConservativeRaster()) {
-            // Conservative raster, by default, only supports triangles. Implementations can
-            // optionally indicate that they also support points and lines, but we don't currently
-            // query or track that info.
-            SkASSERT(GrIsPrimTypeTris(fPrimitiveType));
-        }
-        SkASSERT(GrPrimitiveRestart::kNo == meshes[i].primitiveRestart() ||
-                 caps.usePrimitiveRestart());
-    }
-}
-
 #endif
diff --git a/src/gpu/GrProgramInfo.h b/src/gpu/GrProgramInfo.h
index 2bd31c9..7f4a1f0 100644
--- a/src/gpu/GrProgramInfo.h
+++ b/src/gpu/GrProgramInfo.h
@@ -62,6 +62,7 @@
     const GrPipeline& pipeline() const { return *fPipeline; }
     const GrPrimitiveProcessor& primProc() const { return *fPrimProc; }
     const GrPipeline::FixedDynamicState* fixedDynamicState() const { return fFixedDynamicState; }
+    int numDynamicStateArrays() const { return fNumDynamicStateArrays; }
 
     bool hasDynamicScissors() const {
         return fPipeline->isScissorEnabled() &&
@@ -126,7 +127,6 @@
     void validate(bool flushTime) const;
     void checkAllInstantiated() const;
     void checkMSAAAndMIPSAreResolved() const;
-    void compatibleWithMeshes(const GrMesh meshes[], int meshCount, const GrCaps&) const;
 
     bool isNVPR() const {
         return fPrimProc->isPathRendering() && !fPrimProc->willUseGeoShader() &&
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.cpp b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
index bbe0bdd..14d9887 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
@@ -214,5 +214,6 @@
                               &dynamicStateArrays, 0, primitiveType);
 
 
-    renderPass->draw(programInfo, meshes, meshCount, drawBounds);
+    renderPass->bindPipeline(programInfo, drawBounds);
+    renderPass->drawMeshes(programInfo, meshes, meshCount);
 }
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index ed9d4eb..8499726 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -150,7 +150,8 @@
                               fixedDynamicState,
                               nullptr, 0, primitiveType);
 
-    flushState->opsRenderPass()->draw(programInfo, &mesh, 1, bounds);
+    flushState->opsRenderPass()->bindPipeline(programInfo, bounds);
+    flushState->opsRenderPass()->drawMeshes(programInfo, &mesh, 1);
 }
 
 void GrCCPathProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
diff --git a/src/gpu/ccpr/GrCCStroker.cpp b/src/gpu/ccpr/GrCCStroker.cpp
index 171bc55..8d092d6 100644
--- a/src/gpu/ccpr/GrCCStroker.cpp
+++ b/src/gpu/ccpr/GrCCStroker.cpp
@@ -788,9 +788,9 @@
                               nullptr,
                               &dynamicStateArrays, 0, GrPrimitiveType::kTriangleStrip);
 
-    flushState->opsRenderPass()->draw(programInfo,
-                                      fMeshesBuffer.begin(), fMeshesBuffer.count(),
-                                      SkRect::Make(drawBounds));
+    flushState->opsRenderPass()->bindPipeline(programInfo, SkRect::Make(drawBounds));
+    flushState->opsRenderPass()->drawMeshes(programInfo, fMeshesBuffer.begin(),
+                                            fMeshesBuffer.count());
     // Don't call reset(), as that also resets the reserve count.
     fMeshesBuffer.pop_back_n(fMeshesBuffer.count());
     fScissorsBuffer.pop_back_n(fScissorsBuffer.count());
diff --git a/src/gpu/ccpr/GrStencilAtlasOp.cpp b/src/gpu/ccpr/GrStencilAtlasOp.cpp
index 0f5ba47..e56d722 100644
--- a/src/gpu/ccpr/GrStencilAtlasOp.cpp
+++ b/src/gpu/ccpr/GrStencilAtlasOp.cpp
@@ -163,5 +163,6 @@
                               &scissorRectState,
                               nullptr, 0, GrPrimitiveType::kTriangleStrip);
 
-    flushState->opsRenderPass()->draw(programInfo, &mesh, 1, SkRect::Make(drawBoundsRect));
+    flushState->opsRenderPass()->bindPipeline(programInfo, SkRect::Make(drawBoundsRect));
+    flushState->opsRenderPass()->drawMeshes(programInfo, &mesh, 1);
 }
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 41f448b..3827733 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -1809,6 +1809,11 @@
 }
 
 bool GrGLGpu::flushGLState(GrRenderTarget* renderTarget, const GrProgramInfo& programInfo) {
+    this->handleDirtyContext();
+
+    if (GrPrimitiveType::kPatches == programInfo.primitiveType()) {
+        this->flushPatchVertexCount(programInfo.tessellationPatchVertexCount());
+    }
 
     sk_sp<GrGLProgram> program(fProgramCache->findOrCreateProgram(renderTarget, programInfo));
     if (!program) {
@@ -2350,22 +2355,10 @@
     #endif
 #endif
 
-void GrGLGpu::draw(GrRenderTarget* renderTarget,
-                   const GrProgramInfo& programInfo,
-                   const GrMesh meshes[],
-                   int meshCount) {
-    this->handleDirtyContext();
-
+void GrGLGpu::drawMeshes(GrRenderTarget* renderTarget, const GrProgramInfo& programInfo,
+                         const GrMesh meshes[], int meshCount) {
     SkASSERT(meshCount); // guaranteed by GrOpsRenderPass::draw
 
-    if (!this->flushGLState(renderTarget, programInfo)) {
-        return;
-    }
-
-    if (GrPrimitiveType::kPatches == programInfo.primitiveType()) {
-        this->flushPatchVertexCount(programInfo.tessellationPatchVertexCount());
-    }
-
     bool hasDynamicScissors = programInfo.hasDynamicScissors();
     bool hasDynamicPrimProcTextures = programInfo.hasDynamicPrimProcTextures();
 
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 45a1ad8..c067d1e 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -73,10 +73,15 @@
     // If the caller wishes to bind an index buffer to a specific VAO, it can call glBind directly.
     GrGLenum bindBuffer(GrGpuBufferType type, const GrBuffer*);
 
+    // Flushes state from GrProgramInfo to GL. Returns false if the state couldn't be set.
+    bool flushGLState(GrRenderTarget*, const GrProgramInfo&);
+
     // 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.
-    void draw(GrRenderTarget*, const GrProgramInfo&, const GrMesh[], int meshCount);
+    //
+    // The client must call flushGLState before this method.
+    void drawMeshes(GrRenderTarget*, const GrProgramInfo&, const GrMesh[], int meshCount);
 
     // GrMesh::SendToGpuImpl methods. These issue the actual GL draw calls.
     // Marked final as a hint to the compiler to not use virtual dispatch.
@@ -284,9 +289,6 @@
     // binds texture unit in GL
     void setTextureUnit(int unitIdx);
 
-    // Flushes state from GrPipeline to GL. Returns false if the state couldn't be set.
-    bool flushGLState(GrRenderTarget*, const GrProgramInfo&);
-
     void flushProgram(sk_sp<GrGLProgram>);
 
     // Version for programs that aren't GrGLProgram.
diff --git a/src/gpu/gl/GrGLOpsRenderPass.h b/src/gpu/gl/GrGLOpsRenderPass.h
index 0685932..4be4899 100644
--- a/src/gpu/gl/GrGLOpsRenderPass.h
+++ b/src/gpu/gl/GrGLOpsRenderPass.h
@@ -49,9 +49,13 @@
 private:
     GrGpu* gpu() override { return fGpu; }
 
-    void onDraw(const GrProgramInfo& programInfo, const GrMesh mesh[], int meshCount,
-                const SkRect& bounds) override {
-        fGpu->draw(fRenderTarget, programInfo, mesh, meshCount);
+    bool onBindPipeline(const GrProgramInfo& programInfo, const SkRect& drawBounds) override {
+        return fGpu->flushGLState(fRenderTarget, programInfo);
+    }
+
+    void onDrawMeshes(const GrProgramInfo& programInfo, const GrMesh mesh[],
+                      int meshCount) override {
+        fGpu->drawMeshes(fRenderTarget, programInfo, mesh, meshCount);
     }
 
     void onClear(const GrFixedClip& clip, const SkPMColor4f& color) override {
diff --git a/src/gpu/mock/GrMockOpsRenderPass.h b/src/gpu/mock/GrMockOpsRenderPass.h
index d17774f..e392400 100644
--- a/src/gpu/mock/GrMockOpsRenderPass.h
+++ b/src/gpu/mock/GrMockOpsRenderPass.h
@@ -34,8 +34,10 @@
     int numDraws() const { return fNumDraws; }
 
 private:
-    void onDraw(const GrProgramInfo&, const GrMesh[], int meshCount,
-                const SkRect& bounds) override {
+    bool onBindPipeline(const GrProgramInfo&, const SkRect&) override {
+        return true;
+    }
+    void onDrawMeshes(const GrProgramInfo&, const GrMesh[], int meshCount) override {
         this->markRenderTargetDirty();
         ++fNumDraws;
     }
diff --git a/src/gpu/mtl/GrMtlOpsRenderPass.h b/src/gpu/mtl/GrMtlOpsRenderPass.h
index 30f19ca..8792eea 100644
--- a/src/gpu/mtl/GrMtlOpsRenderPass.h
+++ b/src/gpu/mtl/GrMtlOpsRenderPass.h
@@ -39,12 +39,9 @@
 private:
     GrGpu* gpu() override { return fGpu; }
 
-    GrMtlPipelineState* prepareDrawState(const GrProgramInfo&);
+    bool onBindPipeline(const GrProgramInfo&, const SkRect& drawBounds) override;
 
-    void onDraw(const GrProgramInfo& programInfo,
-                const GrMesh mesh[],
-                int meshCount,
-                const SkRect& bounds) override;
+    void onDrawMeshes(const GrProgramInfo&, const GrMesh[], int meshCount) override;
 
     void onClear(const GrFixedClip& clip, const SkPMColor4f& color) override;
 
@@ -76,6 +73,7 @@
     GrMtlGpu*                   fGpu;
 
     id<MTLRenderCommandEncoder> fActiveRenderCmdEncoder;
+    GrMtlPipelineState*         fActivePipelineState = nullptr;
     MTLRenderPassDescriptor*    fRenderPassDesc;
     SkRect                      fBounds;
     size_t                      fCurrentVertexStride;
diff --git a/src/gpu/mtl/GrMtlOpsRenderPass.mm b/src/gpu/mtl/GrMtlOpsRenderPass.mm
index 41cef25..0f887d4 100644
--- a/src/gpu/mtl/GrMtlOpsRenderPass.mm
+++ b/src/gpu/mtl/GrMtlOpsRenderPass.mm
@@ -49,57 +49,38 @@
     fActiveRenderCmdEncoder = nil;
 }
 
-GrMtlPipelineState* GrMtlOpsRenderPass::prepareDrawState(const GrProgramInfo& programInfo) {
-    // TODO: resolve textures and regenerate mipmaps as needed
-
-    GrMtlPipelineState* pipelineState =
-        fGpu->resourceProvider().findOrCreateCompatiblePipelineState(fRenderTarget, programInfo);
-    if (!pipelineState) {
-        return nullptr;
+bool GrMtlOpsRenderPass::onBindPipeline(const GrProgramInfo& programInfo,
+                                        const SkRect& drawBounds) {
+    fActivePipelineState = fGpu->resourceProvider().findOrCreateCompatiblePipelineState(
+            fRenderTarget, programInfo);
+    if (!fActivePipelineState) {
+        return false;
     }
 
-    pipelineState->setData(fRenderTarget, programInfo);
+    fActivePipelineState->setData(fRenderTarget, programInfo);
     fCurrentVertexStride = programInfo.primProc().vertexStride();
 
-    return pipelineState;
-}
-
-void GrMtlOpsRenderPass::onDraw(const GrProgramInfo& programInfo,
-                                const GrMesh meshes[],
-                                int meshCount,
-                                const SkRect& bounds) {
-
-    SkASSERT(meshCount); // guaranteed by GrOpsRenderPass::draw
-
-    GrMtlPipelineState* pipelineState = this->prepareDrawState(programInfo);
-    if (!pipelineState) {
-        return;
-    }
-
     if (!fActiveRenderCmdEncoder) {
         fActiveRenderCmdEncoder =
                 fGpu->commandBuffer()->getRenderCommandEncoder(fRenderPassDesc, nullptr, this);
     }
 
-    [fActiveRenderCmdEncoder setRenderPipelineState:pipelineState->mtlPipelineState()];
-    pipelineState->setDrawState(fActiveRenderCmdEncoder,
-                                programInfo.pipeline().outputSwizzle(),
-                                programInfo.pipeline().getXferProcessor());
+    [fActiveRenderCmdEncoder setRenderPipelineState:fActivePipelineState->mtlPipelineState()];
+    fActivePipelineState->setDrawState(fActiveRenderCmdEncoder,
+                                       programInfo.pipeline().outputSwizzle(),
+                                       programInfo.pipeline().getXferProcessor());
     if (this->gpu()->caps()->wireframeMode() || programInfo.pipeline().isWireframe()) {
         [fActiveRenderCmdEncoder setTriangleFillMode:MTLTriangleFillModeLines];
     } else {
         [fActiveRenderCmdEncoder setTriangleFillMode:MTLTriangleFillModeFill];
     }
 
-    bool hasDynamicScissors = programInfo.hasDynamicScissors();
-    bool hasDynamicTextures = programInfo.hasDynamicPrimProcTextures();
-
     if (!programInfo.pipeline().isScissorEnabled()) {
         GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
                                                        fRenderTarget, fOrigin,
                                                        SkIRect::MakeWH(fRenderTarget->width(),
                                                                        fRenderTarget->height()));
-    } else if (!hasDynamicScissors) {
+    } else if (!programInfo.hasDynamicScissors()) {
         SkASSERT(programInfo.hasFixedScissor());
 
         GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
@@ -107,29 +88,35 @@
                                                        programInfo.fixedScissor());
     }
 
-    if (!hasDynamicTextures) {
-        pipelineState->bindTextures(fActiveRenderCmdEncoder);
+    if (!programInfo.hasDynamicPrimProcTextures()) {
+        fActivePipelineState->bindTextures(fActiveRenderCmdEncoder);
     }
 
+    fBounds.join(drawBounds);
+    return true;
+}
+
+void GrMtlOpsRenderPass::onDrawMeshes(const GrProgramInfo& programInfo, const GrMesh meshes[],
+                                      int meshCount) {
+    SkASSERT(fActivePipelineState);
+
     for (int i = 0; i < meshCount; ++i) {
         const GrMesh& mesh = meshes[i];
         SkASSERT(nil != fActiveRenderCmdEncoder);
 
-        if (hasDynamicScissors) {
+        if (programInfo.hasDynamicScissors()) {
             GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder, fRenderTarget,
                                                            fOrigin,
                                                            programInfo.dynamicScissor(i));
         }
-        if (hasDynamicTextures) {
+        if (programInfo.hasDynamicPrimProcTextures()) {
             auto meshProxies = programInfo.dynamicPrimProcTextures(i);
-            pipelineState->setTextures(programInfo, meshProxies);
-            pipelineState->bindTextures(fActiveRenderCmdEncoder);
+            fActivePipelineState->setTextures(programInfo, meshProxies);
+            fActivePipelineState->bindTextures(fActiveRenderCmdEncoder);
         }
 
         mesh.sendToGpu(programInfo.primitiveType(), this);
     }
-
-    fBounds.join(bounds);
 }
 
 void GrMtlOpsRenderPass::onClear(const GrFixedClip& clip, const SkPMColor4f& color) {
diff --git a/src/gpu/ops/GrFillRRectOp.cpp b/src/gpu/ops/GrFillRRectOp.cpp
index e283813..7941f31 100644
--- a/src/gpu/ops/GrFillRRectOp.cpp
+++ b/src/gpu/ops/GrFillRRectOp.cpp
@@ -815,7 +815,8 @@
                               fBaseInstance, GrPrimitiveRestart::kNo);
     mesh->setVertexData(std::move(fVertexBuffer));
 
-    flushState->opsRenderPass()->draw(*fProgramInfo, mesh, 1, this->bounds());
+    flushState->opsRenderPass()->bindPipeline(*fProgramInfo, this->bounds());
+    flushState->opsRenderPass()->drawMeshes(*fProgramInfo, mesh, 1);
 }
 
 // Will the given corner look good if we use HW derivatives?
diff --git a/src/gpu/tessellate/GrDrawAtlasPathOp.cpp b/src/gpu/tessellate/GrDrawAtlasPathOp.cpp
index 1b2aa8a..34740d1 100644
--- a/src/gpu/tessellate/GrDrawAtlasPathOp.cpp
+++ b/src/gpu/tessellate/GrDrawAtlasPathOp.cpp
@@ -186,5 +186,6 @@
 
     GrMesh mesh;
     mesh.setInstanced(fInstanceBuffer, fInstanceCount, fBaseInstance, 4);
-    state->opsRenderPass()->draw(programInfo, &mesh, 1, this->bounds());
+    state->opsRenderPass()->bindPipeline(programInfo, this->bounds());
+    state->opsRenderPass()->drawMeshes(programInfo, &mesh, 1);
 }
diff --git a/src/gpu/tessellate/GrPathShader.h b/src/gpu/tessellate/GrPathShader.h
index facb9aa..e5a1f0f 100644
--- a/src/gpu/tessellate/GrPathShader.h
+++ b/src/gpu/tessellate/GrPathShader.h
@@ -48,7 +48,8 @@
                                   state->proxy()->backendFormat(), state->view()->origin(),
                                   pipeline, this, fixedDynamicState, nullptr, 0,
                                   fPrimitiveType, fTessellationPatchVertexCount);
-        state->opsRenderPass()->draw(programInfo, &mesh, 1, bounds);
+        state->opsRenderPass()->bindPipeline(programInfo, bounds);
+        state->opsRenderPass()->drawMeshes(programInfo, &mesh, 1);
     }
 
 private:
diff --git a/src/gpu/vk/GrVkOpsRenderPass.cpp b/src/gpu/vk/GrVkOpsRenderPass.cpp
index 08cc282..7de73cc 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.cpp
+++ b/src/gpu/vk/GrVkOpsRenderPass.cpp
@@ -479,50 +479,102 @@
     }
 }
 
-GrVkPipelineState* GrVkOpsRenderPass::prepareDrawState(
-        const GrProgramInfo& programInfo,
-        const SkIRect& renderPassScissorRect) {
+#ifdef SK_DEBUG
+void check_sampled_texture(GrTexture* tex, GrRenderTarget* rt, GrVkGpu* gpu) {
+    SkASSERT(!tex->isProtected() || (rt->isProtected() && gpu->protectedContext()));
+    GrVkTexture* vkTex = static_cast<GrVkTexture*>(tex);
+    SkASSERT(vkTex->currentLayout() == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+}
+
+void check_sampled_textures(const GrProgramInfo& programInfo, GrRenderTarget* rt, GrVkGpu* gpu) {
+    if (programInfo.hasDynamicPrimProcTextures()) {
+        for (int m = 0; m < programInfo.numDynamicStateArrays(); ++m) {
+            auto dynamicPrimProcTextures = programInfo.dynamicPrimProcTextures(m);
+
+            for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
+                auto texture = dynamicPrimProcTextures[s]->peekTexture();
+                check_sampled_texture(texture, rt, gpu);
+            }
+        }
+    } else if (programInfo.hasFixedPrimProcTextures()) {
+        auto fixedPrimProcTextures = programInfo.fixedPrimProcTextures();
+
+        for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
+            auto texture = fixedPrimProcTextures[s]->peekTexture();
+            check_sampled_texture(texture, rt, gpu);
+        }
+    }
+
+    GrFragmentProcessor::PipelineTextureSamplerRange textureSamplerRange(programInfo.pipeline());
+    for (auto [sampler, fp] : textureSamplerRange) {
+        check_sampled_texture(sampler.peekTexture(), rt, gpu);
+    }
+    if (GrTexture* dstTexture = programInfo.pipeline().peekDstTexture()) {
+        check_sampled_texture(dstTexture, rt, gpu);
+    }
+}
+#endif
+
+bool GrVkOpsRenderPass::onBindPipeline(const GrProgramInfo& programInfo, const SkRect& drawBounds) {
+    if (!fCurrentRenderPass) {
+        SkASSERT(fGpu->isDeviceLost());
+        return false;
+    }
+
+#ifdef SK_DEBUG
+    check_sampled_textures(programInfo, fRenderTarget, fGpu);
+
+    // Both the 'programInfo' and this renderPass have an origin. Since they come from the
+    // same place (i.e., the target renderTargetProxy) they had best agree.
+    SkASSERT(programInfo.origin() == fOrigin);
+#endif
+
+    SkRect rtRect = SkRect::Make(fBounds);
+    if (rtRect.intersect(drawBounds)) {
+        rtRect.roundOut(&fCurrentPipelineBounds);
+    } else {
+        fCurrentPipelineBounds.setEmpty();
+    }
+
     GrVkCommandBuffer* currentCB = this->currentCommandBuffer();
     SkASSERT(fCurrentRenderPass);
 
     VkRenderPass compatibleRenderPass = fCurrentRenderPass->vkRenderPass();
 
-    GrVkPipelineState* pipelineState =
-        fGpu->resourceProvider().findOrCreateCompatiblePipelineState(fRenderTarget,
-                                                                     programInfo,
-                                                                     compatibleRenderPass);
-    if (!pipelineState) {
-        return pipelineState;
+    fCurrentPipelineState = fGpu->resourceProvider().findOrCreateCompatiblePipelineState(
+            fRenderTarget, programInfo, compatibleRenderPass);
+    if (!fCurrentPipelineState) {
+        return false;
     }
 
-    pipelineState->bindPipeline(fGpu, currentCB);
+    fCurrentPipelineState->bindPipeline(fGpu, currentCB);
 
     // Both the 'programInfo' and this renderPass have an origin. Since they come from the
     // same place (i.e., the target renderTargetProxy) they had best agree.
     SkASSERT(programInfo.origin() == fOrigin);
 
-    if (!pipelineState->setAndBindUniforms(fGpu, fRenderTarget, programInfo, currentCB)) {
-        return nullptr;
+    if (!fCurrentPipelineState->setAndBindUniforms(fGpu, fRenderTarget, programInfo, currentCB)) {
+        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 (!pipelineState->setAndBindTextures(fGpu, programInfo.primProc(), programInfo.pipeline(),
-                                               proxies, currentCB)) {
-            return nullptr;
+        if (!fCurrentPipelineState->setAndBindTextures(
+                fGpu, programInfo.primProc(), programInfo.pipeline(), proxies, currentCB)) {
+            return false;
         }
     }
 
     if (!programInfo.pipeline().isScissorEnabled()) {
         GrVkPipeline::SetDynamicScissorRectState(fGpu, currentCB, fRenderTarget, fOrigin,
-                                                 renderPassScissorRect);
+                                                 fCurrentPipelineBounds);
     } else if (!programInfo.hasDynamicScissors()) {
         SkASSERT(programInfo.hasFixedScissor());
 
         SkIRect combinedScissorRect;
-        if (!combinedScissorRect.intersect(renderPassScissorRect, programInfo.fixedScissor())) {
+        if (!combinedScissorRect.intersect(fCurrentPipelineBounds, programInfo.fixedScissor())) {
             combinedScissorRect = SkIRect::MakeEmpty();
         }
         GrVkPipeline::SetDynamicScissorRectState(fGpu, currentCB, fRenderTarget, fOrigin,
@@ -533,92 +585,36 @@
                                                programInfo.pipeline().outputSwizzle(),
                                                programInfo.pipeline().getXferProcessor());
 
-    return pipelineState;
+    return true;
 }
 
-#ifdef SK_DEBUG
-void check_sampled_texture(GrTexture* tex, GrRenderTarget* rt, GrVkGpu* gpu) {
-    SkASSERT(!tex->isProtected() || (rt->isProtected() && gpu->protectedContext()));
-    GrVkTexture* vkTex = static_cast<GrVkTexture*>(tex);
-    SkASSERT(vkTex->currentLayout() == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
-}
-#endif
-
-
-void GrVkOpsRenderPass::onDraw(const GrProgramInfo& programInfo,
-                               const GrMesh meshes[], int meshCount,
-                               const SkRect& bounds) {
+void GrVkOpsRenderPass::onDrawMeshes(const GrProgramInfo& programInfo, const GrMesh meshes[],
+                                     int meshCount) {
     if (!fCurrentRenderPass) {
         SkASSERT(fGpu->isDeviceLost());
         return;
     }
 
+    SkASSERT(fCurrentPipelineState);
     SkASSERT(meshCount); // guaranteed by GrOpsRenderPass::draw
 
-#ifdef SK_DEBUG
-    if (programInfo.hasDynamicPrimProcTextures()) {
-        for (int m = 0; m < meshCount; ++m) {
-            auto dynamicPrimProcTextures = programInfo.dynamicPrimProcTextures(m);
-
-            for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
-                auto texture = dynamicPrimProcTextures[s]->peekTexture();
-                check_sampled_texture(texture, fRenderTarget, fGpu);
-            }
-        }
-    } else if (programInfo.hasFixedPrimProcTextures()) {
-        auto fixedPrimProcTextures = programInfo.fixedPrimProcTextures();
-
-        for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
-            auto texture = fixedPrimProcTextures[s]->peekTexture();
-            check_sampled_texture(texture, fRenderTarget, fGpu);
-        }
-    }
-
-    GrFragmentProcessor::PipelineTextureSamplerRange textureSamplerRange(programInfo.pipeline());
-    for (auto [sampler, fp] : textureSamplerRange) {
-        check_sampled_texture(sampler.peekTexture(), fRenderTarget, fGpu);
-    }
-    if (GrTexture* dstTexture = programInfo.pipeline().peekDstTexture()) {
-        check_sampled_texture(dstTexture, fRenderTarget, fGpu);
-    }
-
-    // Both the 'programInfo' and this renderPass have an origin. Since they come from the
-    // same place (i.e., the target renderTargetProxy) they had best agree.
-    SkASSERT(programInfo.origin() == fOrigin);
-#endif
-
-    SkRect scissorRect = SkRect::Make(fBounds);
-    SkIRect renderPassScissorRect = SkIRect::MakeEmpty();
-    if (scissorRect.intersect(bounds)) {
-        scissorRect.roundOut(&renderPassScissorRect);
-    }
-
-    GrVkPipelineState* pipelineState = this->prepareDrawState(programInfo, renderPassScissorRect);
-    if (!pipelineState) {
-        return;
-    }
-
-    bool hasDynamicScissors = programInfo.hasDynamicScissors();
-    bool hasDynamicTextures = programInfo.hasDynamicPrimProcTextures();
-
     for (int i = 0; i < meshCount; ++i) {
         const GrMesh& mesh = meshes[i];
 
-        if (hasDynamicScissors) {
+        if (programInfo.hasDynamicScissors()) {
             SkIRect combinedScissorRect;
-            if (!combinedScissorRect.intersect(renderPassScissorRect,
+            if (!combinedScissorRect.intersect(fCurrentPipelineBounds,
                                                programInfo.dynamicScissor(i))) {
                 combinedScissorRect = SkIRect::MakeEmpty();
             }
             GrVkPipeline::SetDynamicScissorRectState(fGpu, this->currentCommandBuffer(),
-                                                     fRenderTarget, fOrigin,
-                                                     combinedScissorRect);
+                                                     fRenderTarget, fOrigin, combinedScissorRect);
         }
-        if (hasDynamicTextures) {
+        if (programInfo.hasDynamicPrimProcTextures()) {
             auto meshProxies = programInfo.dynamicPrimProcTextures(i);
-            if (!pipelineState->setAndBindTextures(fGpu, programInfo.primProc(),
-                                                   programInfo.pipeline(), meshProxies,
-                                                   this->currentCommandBuffer())) {
+            if (!fCurrentPipelineState->setAndBindTextures(fGpu, programInfo.primProc(),
+                                                           programInfo.pipeline(), meshProxies,
+                                                           this->currentCommandBuffer())) {
                 if (fGpu->isDeviceLost()) {
                     return;
                 } else {
@@ -626,7 +622,6 @@
                 }
             }
         }
-        SkASSERT(pipelineState);
         mesh.sendToGpu(programInfo.primitiveType(), this);
     }
 
@@ -664,7 +659,7 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-void GrVkOpsRenderPass::executeDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler> drawable) {
+void GrVkOpsRenderPass::onExecuteDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler> drawable) {
     if (!fCurrentRenderPass) {
         SkASSERT(fGpu->isDeviceLost());
         return;
diff --git a/src/gpu/vk/GrVkOpsRenderPass.h b/src/gpu/vk/GrVkOpsRenderPass.h
index 64209d9..48e3df7 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.h
+++ b/src/gpu/vk/GrVkOpsRenderPass.h
@@ -34,7 +34,7 @@
 
     void inlineUpload(GrOpFlushState* state, GrDeferredTextureUploadFn& upload) override;
 
-    void executeDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>) override;
+    void onExecuteDrawable(std::unique_ptr<SkDrawable::GpuDrawHandler>) override;
 
     bool set(GrRenderTarget*, GrSurfaceOrigin, const SkIRect& bounds,
              const GrOpsRenderPass::LoadAndStoreInfo&,
@@ -68,10 +68,9 @@
                       const GrGpuBuffer* vertexBuffer,
                       const GrGpuBuffer* instanceBuffer);
 
-    GrVkPipelineState* prepareDrawState(const GrProgramInfo&, const SkIRect& renderPassScissorRect);
+    bool onBindPipeline(const GrProgramInfo&, const SkRect& drawBounds) override;
 
-    void onDraw(const GrProgramInfo&, const GrMesh[], int meshCount,
-                const SkRect& bounds) override;
+    void onDrawMeshes(const GrProgramInfo&, const GrMesh[], int meshCount) override;
 
     // GrMesh::SendToGpuImpl methods. These issue the actual Vulkan draw commands.
     // Marked final as a hint to the compiler to not use virtual dispatch.
@@ -101,6 +100,8 @@
 
     std::unique_ptr<GrVkSecondaryCommandBuffer> fCurrentSecondaryCommandBuffer;
     const GrVkRenderPass*                       fCurrentRenderPass;
+    SkIRect                                     fCurrentPipelineBounds;
+    GrVkPipelineState*                          fCurrentPipelineState = nullptr;
     bool                                        fCurrentCBIsEmpty = true;
     SkIRect                                     fBounds;
     GrVkGpu*                                    fGpu;
diff --git a/tests/GrMeshTest.cpp b/tests/GrMeshTest.cpp
index 8a505a8..8142aa8 100644
--- a/tests/GrMeshTest.cpp
+++ b/tests/GrMeshTest.cpp
@@ -446,8 +446,8 @@
                               mtp,
                               nullptr, nullptr, 0, primitiveType);
 
-    fState->opsRenderPass()->draw(programInfo, &mesh, 1,
-                                  SkRect::MakeIWH(kImageWidth, kImageHeight));
+    fState->opsRenderPass()->bindPipeline(programInfo, SkRect::MakeIWH(kImageWidth, kImageHeight));
+    fState->opsRenderPass()->drawMeshes(programInfo, &mesh, 1);
 }
 
 static void run_test(GrContext* context, const char* testName, skiatest::Reporter* reporter,
diff --git a/tests/GrPipelineDynamicStateTest.cpp b/tests/GrPipelineDynamicStateTest.cpp
index e62269b..a5ce0a2 100644
--- a/tests/GrPipelineDynamicStateTest.cpp
+++ b/tests/GrPipelineDynamicStateTest.cpp
@@ -169,8 +169,9 @@
                                   nullptr,
                                   &dynamicState, 0, GrPrimitiveType::kTriangleStrip);
 
-        flushState->opsRenderPass()->draw(programInfo, meshes.begin(), 4,
-                                          SkRect::MakeIWH(kScreenSize, kScreenSize));
+        flushState->opsRenderPass()->bindPipeline(programInfo,
+                                                  SkRect::MakeIWH(kScreenSize, kScreenSize));
+        flushState->opsRenderPass()->drawMeshes(programInfo, meshes.begin(), 4);
     }
 
     GrScissorTest               fScissorTest;