Refactor GrPipeline dynamic state.

Remove scissor rect from GrPipeline.

Draws can specify "fixed dynamic state" which doesn't use the dynamism at
all or can specify dynamic state arrays with an entry per GrMesh.

When we state other than scissor rects this will allow the caller to
use a mix of truly dynamic and fixed dynamic state. So a caller that
only has dynamic scissor rects doesn't need to store its remaining
unvarying state in an array.

Change-Id: I8fcc07eb600c72a26cc712b185755c2116021a8a
Reviewed-on: https://skia-review.googlesource.com/137223
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/gm/beziereffects.cpp b/gm/beziereffects.cpp
index 26da791..9f3bcd7 100644
--- a/gm/beziereffects.cpp
+++ b/gm/beziereffects.cpp
@@ -50,7 +50,7 @@
         this->setBounds(rect, HasAABloat::kYes, IsZeroArea::kNo);
     }
 
-    const GrPipeline* makePipeline(Target* target) {
+    Target::PipelineAndFixedDynamicState makePipeline(Target* target) {
         return target->makePipeline(0, std::move(fProcessorSet), target->detachAppliedClip());
     }
 
@@ -100,7 +100,8 @@
         }
         SkRect rect = this->rect();
         SkPointPriv::SetRectTriStrip(pts, rect, sizeof(SkPoint));
-        helper.recordDraw(target, this->gp(), this->makePipeline(target));
+        auto pipe = this->makePipeline(target);
+        helper.recordDraw(target, this->gp(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     static constexpr int kVertsPerCubic = 4;
@@ -296,7 +297,8 @@
             SkPoint3 pt3 = {verts[v].fPosition.x(), verts[v].fPosition.y(), 1.f};
             fKLM.mapHomogeneousPoints((SkPoint3* ) verts[v].fKLM, &pt3, 1);
         }
-        helper.recordDraw(target, this->gp(), this->makePipeline(target));
+        auto pipe = this->makePipeline(target);
+        helper.recordDraw(target, this->gp(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     SkMatrix fKLM;
@@ -513,7 +515,8 @@
         SkRect rect = this->rect();
         SkPointPriv::SetRectTriStrip(&verts[0].fPosition, rect, sizeof(Vertex));
         fDevToUV.apply<4, sizeof(Vertex), sizeof(SkPoint)>(verts);
-        helper.recordDraw(target, this->gp(), this->makePipeline(target));
+        auto pipe = this->makePipeline(target);
+        helper.recordDraw(target, this->gp(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     GrPathUtils::QuadUVMatrix fDevToUV;
diff --git a/gm/convexpolyeffect.cpp b/gm/convexpolyeffect.cpp
index 496b6b1..3c825dc 100644
--- a/gm/convexpolyeffect.cpp
+++ b/gm/convexpolyeffect.cpp
@@ -92,9 +92,8 @@
 
         SkPointPriv::SetRectTriStrip(verts, fRect, sizeof(SkPoint));
 
-        helper.recordDraw(
-                target, gp.get(),
-                target->makePipeline(0, std::move(fProcessors), target->detachAppliedClip()));
+        auto pipe = target->makePipeline(0, std::move(fProcessors), target->detachAppliedClip());
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* op, const GrCaps& caps) override { return false; }
diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp
index f2f88bf..13cc0a0 100644
--- a/samplecode/SampleCCPRGeometry.cpp
+++ b/samplecode/SampleCCPRGeometry.cpp
@@ -359,7 +359,7 @@
 
     if (!mesh.empty()) {
         SkASSERT(1 == mesh.count());
-        proc.draw(state, pipeline, mesh.begin(), nullptr, 1, this->bounds());
+        proc.draw(state, pipeline, nullptr, mesh.begin(), 1, this->bounds());
     }
 
     if (glGpu) {
diff --git a/src/gpu/GrGpuCommandBuffer.cpp b/src/gpu/GrGpuCommandBuffer.cpp
index 617ed75..42e00f3 100644
--- a/src/gpu/GrGpuCommandBuffer.cpp
+++ b/src/gpu/GrGpuCommandBuffer.cpp
@@ -26,12 +26,10 @@
     this->onClearStencilClip(clip, insideStencilMask);
 }
 
-bool GrGpuRTCommandBuffer::draw(const GrPrimitiveProcessor& primProc,
-                                const GrPipeline& pipeline,
-                                const GrMesh meshes[],
-                                const GrPipeline::DynamicState dynamicStates[],
-                                int meshCount,
-                                const SkRect& bounds) {
+bool GrGpuRTCommandBuffer::draw(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
+                                const GrPipeline::FixedDynamicState* fixedDynamicState,
+                                const GrPipeline::DynamicStateArrays* dynamicStateArrays,
+                                const GrMesh meshes[], int meshCount, const SkRect& bounds) {
 #ifdef SK_DEBUG
     SkASSERT(!primProc.hasInstanceAttributes() || this->gpu()->caps()->instanceAttribSupport());
     for (int i = 0; i < meshCount; ++i) {
@@ -41,6 +39,9 @@
         SkASSERT(primProc.hasInstanceAttributes() == meshes[i].isInstanced());
     }
 #endif
+    SkASSERT(!pipeline.isScissorEnabled() || fixedDynamicState ||
+             (dynamicStateArrays && dynamicStateArrays->fScissorRects));
+
     auto resourceProvider = this->gpu()->getContext()->contextPriv().resourceProvider();
 
     if (pipeline.isBad() || !primProc.instantiate(resourceProvider)) {
@@ -51,6 +52,7 @@
         this->gpu()->stats()->incNumFailedDraws();
         return false;
     }
-    this->onDraw(primProc, pipeline, meshes, dynamicStates, meshCount, bounds);
+    this->onDraw(primProc, pipeline, fixedDynamicState, dynamicStateArrays, meshes, meshCount,
+                 bounds);
     return true;
 }
diff --git a/src/gpu/GrGpuCommandBuffer.h b/src/gpu/GrGpuCommandBuffer.h
index 4fae620..fe0edc9 100644
--- a/src/gpu/GrGpuCommandBuffer.h
+++ b/src/gpu/GrGpuCommandBuffer.h
@@ -99,8 +99,9 @@
     // number of vertex attributes is too large).
     bool draw(const GrPrimitiveProcessor&,
               const GrPipeline&,
+              const GrPipeline::FixedDynamicState*,
+              const GrPipeline::DynamicStateArrays*,
               const GrMesh[],
-              const GrPipeline::DynamicState[],
               int meshCount,
               const SkRect& bounds);
 
@@ -134,8 +135,9 @@
     // overridden by backend-specific derived class to perform the draw call.
     virtual void onDraw(const GrPrimitiveProcessor&,
                         const GrPipeline&,
+                        const GrPipeline::FixedDynamicState*,
+                        const GrPipeline::DynamicStateArrays*,
                         const GrMesh[],
-                        const GrPipeline::DynamicState[],
                         int meshCount,
                         const SkRect& bounds) = 0;
 
diff --git a/src/gpu/GrOpFlushState.cpp b/src/gpu/GrOpFlushState.cpp
index b24e89a..d0a8538 100644
--- a/src/gpu/GrOpFlushState.cpp
+++ b/src/gpu/GrOpFlushState.cpp
@@ -44,8 +44,8 @@
         }
         SkASSERT(fCurrDraw->fPipeline->proxy() == this->drawOpArgs().fProxy);
         this->rtCommandBuffer()->draw(*fCurrDraw->fGeometryProcessor, *fCurrDraw->fPipeline,
-                                      fMeshes.begin() + fCurrMesh, nullptr, fCurrDraw->fMeshCnt,
-                                      opBounds);
+                                      fCurrDraw->fFixedDynamicState, fCurrDraw->fDynamicStateArrays,
+                                      fMeshes.begin() + fCurrMesh, fCurrDraw->fMeshCnt, opBounds);
         fCurrMesh += fCurrDraw->fMeshCnt;
         fTokenTracker->flushToken();
         ++fCurrDraw;
@@ -105,6 +105,7 @@
 }
 
 void GrOpFlushState::draw(const GrGeometryProcessor* gp, const GrPipeline* pipeline,
+                          const GrPipeline::FixedDynamicState* fixedDynamicState,
                           const GrMesh& mesh) {
     SkASSERT(fOpArgs);
     SkASSERT(fOpArgs->fOp);
@@ -114,7 +115,10 @@
         Draw& lastDraw = *fDraws.begin();
         // If the last draw shares a geometry processor and pipeline and there are no intervening
         // uploads, add this mesh to it.
-        if (lastDraw.fGeometryProcessor == gp && lastDraw.fPipeline == pipeline) {
+        // Note, we could attempt to convert fixed dynamic states into dynamic state arrays here
+        // if everything else is equal. Maybe it's better to rely on Ops to do that?
+        if (lastDraw.fGeometryProcessor == gp && lastDraw.fPipeline == pipeline &&
+            lastDraw.fFixedDynamicState == fixedDynamicState) {
             if (fInlineUploads.begin() == fInlineUploads.end() ||
                 fInlineUploads.tail()->fUploadBeforeToken != fTokenTracker->nextDrawToken()) {
                 ++lastDraw.fMeshCnt;
@@ -127,6 +131,8 @@
 
     draw.fGeometryProcessor.reset(gp);
     draw.fPipeline = pipeline;
+    draw.fFixedDynamicState = fixedDynamicState;
+    draw.fDynamicStateArrays = nullptr;
     draw.fMeshCnt = 1;
     draw.fOpID = fOpArgs->fOp->uniqueID();
     if (firstDraw) {
diff --git a/src/gpu/GrOpFlushState.h b/src/gpu/GrOpFlushState.h
index a6f0c26..28e0f68 100644
--- a/src/gpu/GrOpFlushState.h
+++ b/src/gpu/GrOpFlushState.h
@@ -74,7 +74,8 @@
 
     /** Overrides of GrMeshDrawOp::Target. */
 
-    void draw(const GrGeometryProcessor*, const GrPipeline*, const GrMesh&) final;
+    void draw(const GrGeometryProcessor*, const GrPipeline*, const GrPipeline::FixedDynamicState*,
+              const GrMesh&) final;
     void* makeVertexSpace(size_t vertexSize, int vertexCount, const GrBuffer**,
                           int* startVertex) final;
     uint16_t* makeIndexSpace(int indexCount, const GrBuffer**, int* startIndex) final;
@@ -120,6 +121,8 @@
         int fMeshCnt = 0;
         GrPendingProgramElement<const GrGeometryProcessor> fGeometryProcessor;
         const GrPipeline* fPipeline;
+        const GrPipeline::FixedDynamicState* fFixedDynamicState;
+        const GrPipeline::DynamicStateArrays* fDynamicStateArrays;
         uint32_t fOpID;
     };
 
diff --git a/src/gpu/GrPathRendering.cpp b/src/gpu/GrPathRendering.cpp
index 1af220c..614d014 100644
--- a/src/gpu/GrPathRendering.cpp
+++ b/src/gpu/GrPathRendering.cpp
@@ -51,6 +51,7 @@
 
 void GrPathRendering::drawPath(const GrPrimitiveProcessor& primProc,
                                const GrPipeline& pipeline,
+                               const GrPipeline::FixedDynamicState& fixedDynamicState,
                                // Cover pass settings in pipeline.
                                const GrStencilSettings& stencilPassSettings,
                                const GrPath* path) {
@@ -58,5 +59,5 @@
     if (GrXferBarrierType barrierType = pipeline.xferBarrierType(*fGpu->caps())) {
         fGpu->xferBarrier(pipeline.renderTarget(), barrierType);
     }
-    this->onDrawPath(primProc, pipeline, stencilPassSettings, path);
+    this->onDrawPath(primProc, pipeline, fixedDynamicState, stencilPassSettings, path);
 }
diff --git a/src/gpu/GrPathRendering.h b/src/gpu/GrPathRendering.h
index ab13674..3ae52e9 100644
--- a/src/gpu/GrPathRendering.h
+++ b/src/gpu/GrPathRendering.h
@@ -111,6 +111,7 @@
 
     void drawPath(const GrPrimitiveProcessor& primProc,
                   const GrPipeline& pipeline,
+                  const GrPipeline::FixedDynamicState&,
                   const GrStencilSettings& stencilPassSettings,  // Cover pass settings in pipeline.
                   const GrPath* path);
 
@@ -120,6 +121,7 @@
     virtual void onStencilPath(const StencilPathArgs&, const GrPath*) = 0;
     virtual void onDrawPath(const GrPrimitiveProcessor&,
                             const GrPipeline&,
+                            const GrPipeline::FixedDynamicState&,
                             const GrStencilSettings&,
                             const GrPath*) = 0;
 
diff --git a/src/gpu/GrPipeline.cpp b/src/gpu/GrPipeline.cpp
index a0eceb3..6608d7c 100644
--- a/src/gpu/GrPipeline.cpp
+++ b/src/gpu/GrPipeline.cpp
@@ -16,7 +16,8 @@
 
 #include "ops/GrOp.h"
 
-GrPipeline::GrPipeline(const InitArgs& args, GrProcessorSet&& processors,
+GrPipeline::GrPipeline(const InitArgs& args,
+                       GrProcessorSet&& processors,
                        GrAppliedClip&& appliedClip) {
     SkASSERT(args.fProxy);
     SkASSERT(processors.isFinalized());
@@ -24,10 +25,13 @@
     fProxy.reset(args.fProxy);
 
     fFlags = args.fFlags;
-    fScissorState = appliedClip.scissorState();
     if (appliedClip.hasStencilClip()) {
         fFlags |= kHasStencilClip_Flag;
     }
+    if (appliedClip.scissorState().enabled()) {
+        fFlags |= kScissorEnabled_Flag;
+    }
+
     fWindowRectsState = appliedClip.windowRectsState();
     if (!args.fUserStencil->isDisabled(fFlags & kHasStencilClip_Flag)) {
         fFlags |= kStencilEnabled_Flag;
@@ -97,7 +101,6 @@
 
 GrPipeline::GrPipeline(GrRenderTargetProxy* proxy, ScissorState scissorState, SkBlendMode blendmode)
         : fProxy(proxy)
-        , fScissorState()
         , fWindowRectsState()
         , fUserStencilSettings(&GrUserStencilSettings::kUnused)
         , fFlags()
@@ -105,7 +108,7 @@
         , fFragmentProcessors()
         , fNumColorProcessors(0) {
     SkASSERT(proxy);
-    if (ScissorState::kEnabled == scissorState) {
-        fScissorState.set({0, 0, 0, 0}); // caller will use the DynamicState struct.
+    if (scissorState) {
+        fFlags |= kScissorEnabled_Flag;
     }
 }
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index 3ace0f0..f60a6d4 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -79,16 +79,26 @@
     };
 
     /**
-     *  Graphics state that can change dynamically without creating a new pipeline.
+     * Some state can be changed between GrMeshes without changing GrPipelines. This is generally
+     * less expensive then using multiple pipelines. Such state is called "dynamic state". It can
+     * be specified in two ways:
+     * 1) FixedDynamicState - use this to specify state that does not vary between GrMeshes.
+     * 2) DynamicStateArrays - use this to specify per mesh values for dynamic state.
      **/
-    struct DynamicState {
-        // Overrides the scissor rectangle (if scissor is enabled in the pipeline).
-        // TODO: eventually this should be the only way to specify a scissor rectangle, as is the
-        // case with the simple constructor.
+    struct FixedDynamicState {
+        FixedDynamicState(const SkIRect& scissorRect) : fScissorRect(scissorRect) {}
         SkIRect fScissorRect;
     };
 
     /**
+     * Any non-null array overrides the FixedDynamicState on a mesh-by-mesh basis. Arrays must
+     * have one entry for each GrMesh.
+     */
+    struct DynamicStateArrays {
+        const SkIRect* fScissorRects = nullptr;
+    };
+
+    /**
      * Creates a simple pipeline with default settings and no processors. The provided blend mode
      * must be "Porter Duff" (<= kLastCoeffMode). If using ScissorState::kEnabled, the caller must
      * specify a scissor rectangle through the DynamicState struct.
@@ -169,7 +179,9 @@
 
     const GrUserStencilSettings* getUserStencil() const { return fUserStencilSettings; }
 
-    const GrScissorState& getScissorState() const { return fScissorState; }
+    ScissorState isScissorEnabled() const {
+        return ScissorState(SkToBool(fFlags & kScissorEnabled_Flag));
+    }
 
     const GrWindowRectsState& getWindowRectsState() const { return fWindowRectsState; }
 
@@ -214,7 +226,8 @@
     enum PrivateFlags {
         kHasStencilClip_Flag = 0x10,
         kStencilEnabled_Flag = 0x20,
-        kIsBad_Flag = 0x40,
+        kScissorEnabled_Flag = 0x40,
+        kIsBad_Flag = 0x80,
     };
 
     using RenderTargetProxy = GrPendingIOResource<GrRenderTargetProxy, kWrite_GrIOType>;
@@ -225,7 +238,6 @@
     SkIPoint fDstTextureOffset;
     // MDB TODO: do we still need the destination proxy here?
     RenderTargetProxy fProxy;
-    GrScissorState fScissorState;
     GrWindowRectsState fWindowRectsState;
     const GrUserStencilSettings* fUserStencilSettings;
     uint16_t fFlags;
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.cpp b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
index 94b027a..e972021 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
@@ -224,16 +224,18 @@
 }
 
 void GrCCCoverageProcessor::draw(GrOpFlushState* flushState, const GrPipeline& pipeline,
-                                 const GrMesh meshes[],
-                                 const GrPipeline::DynamicState dynamicStates[], int meshCount,
+                                 const SkIRect scissorRects[], const GrMesh meshes[], int meshCount,
                                  const SkRect& drawBounds) const {
+    GrPipeline::DynamicStateArrays dynamicStateArrays;
+    dynamicStateArrays.fScissorRects = scissorRects;
     GrGpuRTCommandBuffer* cmdBuff = flushState->rtCommandBuffer();
-    cmdBuff->draw(*this, pipeline, meshes, dynamicStates, meshCount, drawBounds);
+    cmdBuff->draw(*this, pipeline, nullptr, &dynamicStateArrays, meshes, meshCount, drawBounds);
 
     // Geometry shader backend draws primitives in two subpasses.
     if (Impl::kGeometryShader == fImpl) {
         SkASSERT(GSSubpass::kHulls == fGSSubpass);
         GrCCCoverageProcessor cornerProc(*this, GSSubpass::kCorners);
-        cmdBuff->draw(cornerProc, pipeline, meshes, dynamicStates, meshCount, drawBounds);
+        cmdBuff->draw(cornerProc, pipeline, nullptr, &dynamicStateArrays, meshes, meshCount,
+                      drawBounds);
     }
 }
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.h b/src/gpu/ccpr/GrCCCoverageProcessor.h
index 8989048..e5b6bc1 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.h
@@ -105,7 +105,7 @@
         }
     }
 
-    void draw(GrOpFlushState*, const GrPipeline&, const GrMesh[], const GrPipeline::DynamicState[],
+    void draw(GrOpFlushState*, const GrPipeline&, const SkIRect scissorRects[], const GrMesh[],
               int meshCount, const SkRect& drawBounds) const;
 
     // The Shader provides code to calculate each pixel's coverage in a RenderPass. It also
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index db5ea35..bbe3df2 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -337,7 +337,9 @@
     initArgs.fCaps = &flushState->caps();
     initArgs.fResourceProvider = flushState->resourceProvider();
     initArgs.fDstProxy = flushState->drawOpArgs().fDstProxy;
-    GrPipeline pipeline(initArgs, std::move(fProcessors), flushState->detachAppliedClip());
+    auto clip = flushState->detachAppliedClip();
+    GrPipeline::FixedDynamicState fixedDynamicState(clip.scissorState().rect());
+    GrPipeline pipeline(initArgs, std::move(fProcessors), std::move(clip));
 
     int baseInstance = fBaseInstance;
     SkASSERT(baseInstance >= 0);  // Make sure setupResources() has been called.
@@ -347,8 +349,8 @@
 
         GrCCPathProcessor pathProc(flushState->resourceProvider(), sk_ref_sp(range.fAtlasProxy),
                                    fViewMatrixIfUsingLocalCoords);
-        pathProc.drawPaths(flushState, pipeline, *resources, baseInstance, range.fEndInstanceIdx,
-                           this->bounds());
+        pathProc.drawPaths(flushState, pipeline, &fixedDynamicState, *resources, baseInstance,
+                           range.fEndInstanceIdx, this->bounds());
 
         baseInstance = range.fEndInstanceIdx;
     }
diff --git a/src/gpu/ccpr/GrCCPathParser.cpp b/src/gpu/ccpr/GrCCPathParser.cpp
index 6f9b224..1935385 100644
--- a/src/gpu/ccpr/GrCCPathParser.cpp
+++ b/src/gpu/ccpr/GrCCPathParser.cpp
@@ -517,7 +517,7 @@
     SkASSERT(instanceIndices[1].fConics == quadEndIdx);
 
     fMeshesScratchBuffer.reserve(fMaxMeshesPerDraw);
-    fDynamicStatesScratchBuffer.reserve(fMaxMeshesPerDraw);
+    fScissorRectScratchBuffer.reserve(fMaxMeshesPerDraw);
 
     return true;
 }
@@ -564,11 +564,11 @@
                                     GrCCCoverageProcessor::PrimitiveType primitiveType,
                                     int PrimitiveTallies::*instanceType,
                                     const SkIRect& drawBounds) const {
-    SkASSERT(pipeline.getScissorState().enabled());
+    SkASSERT(pipeline.isScissorEnabled());
 
     // Don't call reset(), as that also resets the reserve count.
     fMeshesScratchBuffer.pop_back_n(fMeshesScratchBuffer.count());
-    fDynamicStatesScratchBuffer.pop_back_n(fDynamicStatesScratchBuffer.count());
+    fScissorRectScratchBuffer.pop_back_n(fScissorRectScratchBuffer.count());
 
     GrCCCoverageProcessor proc(flushState->resourceProvider(), primitiveType);
 
@@ -584,8 +584,8 @@
         int baseInstance = fBaseInstances[(int)ScissorMode::kNonScissored].*instanceType +
                            previousBatch.fEndNonScissorIndices.*instanceType;
         proc.appendMesh(fInstanceBuffer.get(), instanceCount, baseInstance, &fMeshesScratchBuffer);
-        fDynamicStatesScratchBuffer.push_back().fScissorRect.setXYWH(0, 0, drawBounds.width(),
-                                                                     drawBounds.height());
+        fScissorRectScratchBuffer.push_back().setXYWH(0, 0, drawBounds.width(),
+                                                      drawBounds.height());
         SkDEBUGCODE(totalInstanceCount += instanceCount);
     }
 
@@ -603,17 +603,17 @@
         SkASSERT(instanceCount > 0);
         proc.appendMesh(fInstanceBuffer.get(), instanceCount,
                         baseScissorInstance + startIndex, &fMeshesScratchBuffer);
-        fDynamicStatesScratchBuffer.push_back().fScissorRect = scissorSubBatch.fScissor;
+        fScissorRectScratchBuffer.push_back() = scissorSubBatch.fScissor;
         SkDEBUGCODE(totalInstanceCount += instanceCount);
     }
 
-    SkASSERT(fMeshesScratchBuffer.count() == fDynamicStatesScratchBuffer.count());
+    SkASSERT(fMeshesScratchBuffer.count() == fScissorRectScratchBuffer.count());
     SkASSERT(fMeshesScratchBuffer.count() <= fMaxMeshesPerDraw);
     SkASSERT(totalInstanceCount == batch.fTotalPrimitiveCounts.*instanceType);
 
     if (!fMeshesScratchBuffer.empty()) {
-        proc.draw(flushState, pipeline, fMeshesScratchBuffer.begin(),
-                  fDynamicStatesScratchBuffer.begin(), fMeshesScratchBuffer.count(),
+        proc.draw(flushState, pipeline, fScissorRectScratchBuffer.begin(),
+                  fMeshesScratchBuffer.begin(), fMeshesScratchBuffer.count(),
                   SkRect::Make(drawBounds));
     }
 }
diff --git a/src/gpu/ccpr/GrCCPathParser.h b/src/gpu/ccpr/GrCCPathParser.h
index 1c09b98..57b1ce6 100644
--- a/src/gpu/ccpr/GrCCPathParser.h
+++ b/src/gpu/ccpr/GrCCPathParser.h
@@ -157,7 +157,7 @@
     sk_sp<GrBuffer> fInstanceBuffer;
     PrimitiveTallies fBaseInstances[kNumScissorModes];
     mutable SkSTArray<32, GrMesh> fMeshesScratchBuffer;
-    mutable SkSTArray<32, GrPipeline::DynamicState> fDynamicStatesScratchBuffer;
+    mutable SkSTArray<32, SkIRect> fScissorRectScratchBuffer;
 };
 
 inline void GrCCPathParser::PathStats::statPath(const SkPath& path) {
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index e1a5086..450b74c 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -130,6 +130,7 @@
 }
 
 void GrCCPathProcessor::drawPaths(GrOpFlushState* flushState, const GrPipeline& pipeline,
+                                  const GrPipeline::FixedDynamicState* fixedDynamicState,
                                   const GrCCPerFlushResources& resources, int baseInstance,
                                   int endInstance, const SkRect& bounds) const {
     const GrCaps& caps = flushState->caps();
@@ -147,7 +148,8 @@
                              enablePrimitiveRestart);
     mesh.setVertexData(resources.vertexBuffer());
 
-    flushState->rtCommandBuffer()->draw(*this, pipeline, &mesh, nullptr, 1, bounds);
+    flushState->rtCommandBuffer()->draw(*this, pipeline, fixedDynamicState, nullptr, &mesh, 1,
+                                        bounds);
 }
 
 void GLSLPathProcessor::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index 54c1b7e..878cd1c 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -8,16 +8,16 @@
 #ifndef GrCCPathProcessor_DEFINED
 #define GrCCPathProcessor_DEFINED
 
+#include <array>
 #include "GrCaps.h"
 #include "GrGeometryProcessor.h"
+#include "GrPipeline.h"
 #include "SkPath.h"
-#include <array>
 
 class GrCCPathCacheEntry;
 class GrCCPerFlushResources;
 class GrOnFlushResourceProvider;
 class GrOpFlushState;
-class GrPipeline;
 
 /**
  * This class draws AA paths using the coverage count masks produced by GrCCCoverageProcessor.
@@ -86,8 +86,9 @@
     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
 
-    void drawPaths(GrOpFlushState*, const GrPipeline&, const GrCCPerFlushResources&,
-                   int baseInstance, int endInstance, const SkRect& bounds) const;
+    void drawPaths(GrOpFlushState*, const GrPipeline&, const GrPipeline::FixedDynamicState*,
+                   const GrCCPerFlushResources&, int baseInstance, int endInstance,
+                   const SkRect& bounds) const;
 
 private:
     const Attribute& onVertexAttribute(int i) const override { return kEdgeNormsAttrib; }
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index 362ae77..dd0bf1b 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -67,7 +67,7 @@
         GrPipeline pipeline(flushState->proxy(), GrPipeline::ScissorState::kDisabled,
                             SkBlendMode::kSrc);
         GrCCPathProcessor pathProc(flushState->resourceProvider(), std::move(fStashedAtlasProxy));
-        pathProc.drawPaths(flushState, pipeline, *fResources, fBaseInstance, fEndInstance,
+        pathProc.drawPaths(flushState, pipeline, nullptr, *fResources, fBaseInstance, fEndInstance,
                            this->bounds());
         // Ensure we released the stashed atlas proxy. This allows its underlying texture to be
         // reused as the current flush's mainline CCPR atlas if needed.
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 97dc3d1..6f34912 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -1683,7 +1683,9 @@
     }
 }
 
-bool GrGLGpu::flushGLState(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
+bool GrGLGpu::flushGLState(const GrPrimitiveProcessor& primProc,
+                           const GrPipeline& pipeline,
+                           const GrPipeline::FixedDynamicState* fixedDynamicState,
                            bool willDrawPoints) {
     sk_sp<GrGLProgram> program(fProgramCache->refProgram(this, primProc, pipeline, willDrawPoints));
     if (!program) {
@@ -1719,7 +1721,13 @@
                       glRT->renderTargetPriv().numStencilBits());
     }
     this->flushStencil(stencil);
-    this->flushScissor(pipeline.getScissorState(), glRT->getViewport(), pipeline.proxy()->origin());
+    if (pipeline.isScissorEnabled()) {
+        static constexpr SkIRect kBogusScissor{0, 0, 1, 1};
+        GrScissorState state(fixedDynamicState ? fixedDynamicState->fScissorRect : kBogusScissor);
+        this->flushScissor(state, glRT->getViewport(), pipeline.proxy()->origin());
+    } else {
+        this->disableScissor();
+    }
     this->flushWindowRectangles(pipeline.getWindowRectsState(), glRT, pipeline.proxy()->origin());
     this->flushHWAAState(glRT, pipeline.isHWAntialiasState(), !stencil.isDisabled());
 
@@ -2250,8 +2258,9 @@
 
 void GrGLGpu::draw(const GrPrimitiveProcessor& primProc,
                    const GrPipeline& pipeline,
+                   const GrPipeline::FixedDynamicState* fixedDynamicState,
+                   const GrPipeline::DynamicStateArrays* dynamicStateArrays,
                    const GrMesh meshes[],
-                   const GrPipeline::DynamicState dynamicStates[],
                    int meshCount) {
     this->handleDirtyContext();
 
@@ -2262,21 +2271,21 @@
             break;
         }
     }
-    if (!this->flushGLState(primProc, pipeline, hasPoints)) {
+    if (!this->flushGLState(primProc, pipeline, fixedDynamicState, hasPoints)) {
         return;
     }
 
+    bool dynamicScissor =
+            pipeline.isScissorEnabled() && dynamicStateArrays && dynamicStateArrays->fScissorRects;
     for (int i = 0; i < meshCount; ++i) {
         if (GrXferBarrierType barrierType = pipeline.xferBarrierType(*this->caps())) {
             this->xferBarrier(pipeline.renderTarget(), barrierType);
         }
 
-        if (dynamicStates) {
-            if (pipeline.getScissorState().enabled()) {
-                GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(pipeline.renderTarget());
-                this->flushScissor(dynamicStates[i].fScissorRect,
-                                   glRT->getViewport(), pipeline.proxy()->origin());
-            }
+        if (dynamicScissor) {
+            GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(pipeline.renderTarget());
+            this->flushScissor(GrScissorState(dynamicStateArrays->fScissorRects[i]),
+                               glRT->getViewport(), pipeline.proxy()->origin());
         }
         if (this->glCaps().requiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines() &&
             GrIsPrimTypeLines(meshes[i].primitiveType()) &&
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 782b0b0..765d5f6 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -84,8 +84,9 @@
     // on GrGLRTGpuCommandBuffer.
     void draw(const GrPrimitiveProcessor&,
               const GrPipeline&,
+              const GrPipeline::FixedDynamicState*,
+              const GrPipeline::DynamicStateArrays*,
               const GrMesh[],
-              const GrPipeline::DynamicState[],
               int meshCount);
 
     // GrMesh::SendToGpuImpl methods. These issue the actual GL draw calls.
@@ -253,7 +254,8 @@
 
     // Flushes state from GrPipeline to GL. Returns false if the state couldn't be set.
     // willDrawPoints must be true if point primitives will be rendered after setting the GL state.
-    bool flushGLState(const GrPrimitiveProcessor&, const GrPipeline&, bool willDrawPoints);
+    bool flushGLState(const GrPrimitiveProcessor&, const GrPipeline&,
+                      const GrPipeline::FixedDynamicState*, bool willDrawPoints);
 
     void flushProgram(sk_sp<GrGLProgram>);
 
diff --git a/src/gpu/gl/GrGLGpuCommandBuffer.h b/src/gpu/gl/GrGLGpuCommandBuffer.h
index 84e05a8..fd3417a 100644
--- a/src/gpu/gl/GrGLGpuCommandBuffer.h
+++ b/src/gpu/gl/GrGLGpuCommandBuffer.h
@@ -86,12 +86,13 @@
 
     void onDraw(const GrPrimitiveProcessor& primProc,
                 const GrPipeline& pipeline,
+                const GrPipeline::FixedDynamicState* fixedDynamicState,
+                const GrPipeline::DynamicStateArrays* dynamicStateArrays,
                 const GrMesh mesh[],
-                const GrPipeline::DynamicState dynamicStates[],
                 int meshCount,
                 const SkRect& bounds) override {
         SkASSERT(pipeline.renderTarget() == fRenderTarget);
-        fGpu->draw(primProc, pipeline, mesh, dynamicStates, meshCount);
+        fGpu->draw(primProc, pipeline, fixedDynamicState, dynamicStateArrays, mesh, meshCount);
     }
 
     void onClear(const GrFixedClip& clip, GrColor color) override {
diff --git a/src/gpu/gl/GrGLPathRendering.cpp b/src/gpu/gl/GrGLPathRendering.cpp
index 7948129..85c129f 100644
--- a/src/gpu/gl/GrGLPathRendering.cpp
+++ b/src/gpu/gl/GrGLPathRendering.cpp
@@ -113,9 +113,10 @@
 
 void GrGLPathRendering::onDrawPath(const GrPrimitiveProcessor& primProc,
                                    const GrPipeline& pipeline,
+                                   const GrPipeline::FixedDynamicState& fixedDynamicState,
                                    const GrStencilSettings& stencilPassSettings,
                                    const GrPath* path) {
-    if (!this->gpu()->flushGLState(primProc, pipeline, false)) {
+    if (!this->gpu()->flushGLState(primProc, pipeline, &fixedDynamicState, false)) {
         return;
     }
     const GrGLPath* glPath = static_cast<const GrGLPath*>(path);
diff --git a/src/gpu/gl/GrGLPathRendering.h b/src/gpu/gl/GrGLPathRendering.h
index 1dfb756..74c5fce 100644
--- a/src/gpu/gl/GrGLPathRendering.h
+++ b/src/gpu/gl/GrGLPathRendering.h
@@ -67,6 +67,7 @@
     void onStencilPath(const StencilPathArgs&, const GrPath*) override;
     void onDrawPath(const GrPrimitiveProcessor&,
                     const GrPipeline&,
+                    const GrPipeline::FixedDynamicState&,
                     const GrStencilSettings&,
                     const GrPath*) override;
 
diff --git a/src/gpu/mock/GrMockGpuCommandBuffer.h b/src/gpu/mock/GrMockGpuCommandBuffer.h
index 7a94e9a..d3e26fa 100644
--- a/src/gpu/mock/GrMockGpuCommandBuffer.h
+++ b/src/gpu/mock/GrMockGpuCommandBuffer.h
@@ -50,8 +50,9 @@
     void submit() override { fGpu->submitCommandBuffer(this); }
 
 private:
-    void onDraw(const GrPrimitiveProcessor&, const GrPipeline&, const GrMesh[],
-                const GrPipeline::DynamicState[], int meshCount, const SkRect& bounds) override {
+    void onDraw(const GrPrimitiveProcessor&, const GrPipeline&,
+                const GrPipeline::FixedDynamicState*, const GrPipeline::DynamicStateArrays*,
+                const GrMesh[], int meshCount, const SkRect& bounds) override {
         ++fNumDraws;
     }
     void onClear(const GrFixedClip&, GrColor) override {}
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index 566eebd..37cb23b 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -807,7 +807,7 @@
         GrAAConvexTessellator tess;
 
         int instanceCount = fPaths.count();
-        const GrPipeline* pipeline = fHelper.makePipeline(target);
+        auto pipe = fHelper.makePipeline(target);
         for (int i = 0; i < instanceCount; i++) {
             tess.rewind();
 
@@ -843,7 +843,7 @@
             mesh.setIndexed(indexBuffer, tess.numIndices(), firstIndex, 0, tess.numPts() - 1,
                             GrPrimitiveRestart::kNo);
             mesh.setVertexData(vertexBuffer, firstVertex);
-            target->draw(gp.get(), pipeline, mesh);
+            target->draw(gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
         }
     }
 
@@ -854,7 +854,7 @@
             return;
         }
 #endif
-        const GrPipeline* pipeline = fHelper.makePipeline(target);
+        auto pipe = fHelper.makePipeline(target);
         int instanceCount = fPaths.count();
 
         SkMatrix invert;
@@ -931,7 +931,7 @@
                 mesh.setIndexed(indexBuffer, draw.fIndexCnt, firstIndex, 0, draw.fVertexCnt - 1,
                                 GrPrimitiveRestart::kNo);
                 mesh.setVertexData(vertexBuffer, firstVertex);
-                target->draw(quadProcessor.get(), pipeline, mesh);
+                target->draw(quadProcessor.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
                 firstIndex += draw.fIndexCnt;
                 firstVertex += draw.fVertexCnt;
             }
diff --git a/src/gpu/ops/GrAAFillRectOp.cpp b/src/gpu/ops/GrAAFillRectOp.cpp
index 76bc812..cbd1ad5 100644
--- a/src/gpu/ops/GrAAFillRectOp.cpp
+++ b/src/gpu/ops/GrAAFillRectOp.cpp
@@ -291,7 +291,8 @@
                                            fHelper.compatibleWithAlphaAsCoverage(), localMatrix);
             info = this->next(info);
         }
-        helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+        auto pipe = fHelper.makePipeline(target);
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
diff --git a/src/gpu/ops/GrAAHairLinePathRenderer.cpp b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
index 89dedc4..af5cb3d 100644
--- a/src/gpu/ops/GrAAHairLinePathRenderer.cpp
+++ b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
@@ -939,7 +939,7 @@
         return;
     }
 
-    const GrPipeline* pipeline = fHelper.makePipeline(target);
+    auto pipe = fHelper.makePipeline(target);
     // do lines first
     if (lineCount) {
         sk_sp<GrGeometryProcessor> lineGP;
@@ -977,7 +977,7 @@
         mesh.setIndexedPatterned(linesIndexBuffer.get(), kIdxsPerLineSeg, kLineSegNumVertices,
                                  lineCount, kLineSegsNumInIdxBuffer);
         mesh.setVertexData(vertexBuffer, firstVertex);
-        target->draw(lineGP.get(), pipeline, mesh);
+        target->draw(lineGP.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
     }
 
     if (quadCount || conicCount) {
@@ -1032,7 +1032,7 @@
             mesh.setIndexedPatterned(quadsIndexBuffer.get(), kIdxsPerQuad, kQuadNumVertices,
                                      quadCount, kQuadsNumInIdxBuffer);
             mesh.setVertexData(vertexBuffer, firstVertex);
-            target->draw(quadGP.get(), pipeline, mesh);
+            target->draw(quadGP.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
             firstVertex += quadCount * kQuadNumVertices;
         }
 
@@ -1041,7 +1041,7 @@
             mesh.setIndexedPatterned(quadsIndexBuffer.get(), kIdxsPerQuad, kQuadNumVertices,
                                      conicCount, kQuadsNumInIdxBuffer);
             mesh.setVertexData(vertexBuffer, firstVertex);
-            target->draw(conicGP.get(), pipeline, mesh);
+            target->draw(conicGP.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
         }
     }
 }
diff --git a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
index b6b7077..1876d6c 100644
--- a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
@@ -206,9 +206,9 @@
     }
 
 private:
-    void draw(GrMeshDrawOp::Target* target, const GrGeometryProcessor* gp,
-              const GrPipeline* pipeline, int vertexCount, size_t vertexStride, void* vertices,
-              int indexCount, uint16_t* indices) const {
+    void draw(Target* target, const GrGeometryProcessor* gp, const GrPipeline* pipeline,
+              const GrPipeline::FixedDynamicState* fixedDynamicState, int vertexCount,
+              size_t vertexStride, void* vertices, int indexCount, uint16_t* indices) const {
         if (vertexCount == 0 || indexCount == 0) {
             return;
         }
@@ -234,12 +234,11 @@
         mesh.setIndexed(indexBuffer, indexCount, firstIndex, 0, vertexCount - 1,
                         GrPrimitiveRestart::kNo);
         mesh.setVertexData(vertexBuffer, firstVertex);
-        target->draw(gp, pipeline, mesh);
+        target->draw(gp, pipeline, fixedDynamicState, mesh);
     }
 
     void onPrepareDraws(Target* target) override {
-        const GrPipeline* pipeline = fHelper.makePipeline(target);
-
+        auto pipe = fHelper.makePipeline(target);
         // Setup GrGeometryProcessor
         sk_sp<GrGeometryProcessor> gp(create_lines_only_gp(fHelper.compatibleWithAlphaAsCoverage(),
                                                            this->viewMatrix(),
@@ -275,8 +274,8 @@
             if (vertexCount + currentVertices > static_cast<int>(UINT16_MAX)) {
                 // if we added the current instance, we would overflow the indices we can store in a
                 // uint16_t. Draw what we've got so far and reset.
-                this->draw(target, gp.get(), pipeline, vertexCount, vertexStride, vertices,
-                           indexCount, indices);
+                this->draw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, vertexCount,
+                           vertexStride, vertices, indexCount, indices);
                 vertexCount = 0;
                 indexCount = 0;
             }
@@ -307,8 +306,8 @@
             indexCount += currentIndices;
         }
         if (vertexCount <= SK_MaxS32 && indexCount <= SK_MaxS32) {
-            this->draw(target, gp.get(), pipeline, vertexCount, vertexStride, vertices, indexCount,
-                       indices);
+            this->draw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, vertexCount,
+                       vertexStride, vertices, indexCount, indices);
         }
         sk_free(vertices);
         sk_free(indices);
diff --git a/src/gpu/ops/GrAAStrokeRectOp.cpp b/src/gpu/ops/GrAAStrokeRectOp.cpp
index 3347227..651f839 100644
--- a/src/gpu/ops/GrAAStrokeRectOp.cpp
+++ b/src/gpu/ops/GrAAStrokeRectOp.cpp
@@ -304,7 +304,8 @@
                                            info.fDegenerate,
                                            fHelper.compatibleWithAlphaAsCoverage());
     }
-    helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+    auto pipe = fHelper.makePipeline(target);
+    helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
 }
 
 sk_sp<const GrBuffer> AAStrokeRectOp::GetIndexBuffer(GrResourceProvider* resourceProvider,
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index fc5e580..29a5938 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -297,9 +297,13 @@
     }
     SkASSERT(proxies[0]);
 
-    FlushInfo flushInfo;
-    flushInfo.fPipeline =
+    auto pipe =
             target->makePipeline(fSRGBFlags, std::move(fProcessors), target->detachAppliedClip());
+
+    FlushInfo flushInfo;
+    flushInfo.fPipeline = pipe.fPipeline;
+    flushInfo.fFixedDynamicState = pipe.fFixedDynamicState;
+
     bool vmPerspective = fGeoData[0].fViewMatrix.hasPerspective();
     if (this->usesDistanceFields()) {
         flushInfo.fGeometryProcessor = this->setupDfProcessor(proxies, numActiveProxies);
@@ -419,7 +423,8 @@
     mesh.setIndexedPatterned(flushInfo->fIndexBuffer.get(), kIndicesPerGlyph, kVerticesPerGlyph,
                              flushInfo->fGlyphsToFlush, maxGlyphsPerDraw);
     mesh.setVertexData(flushInfo->fVertexBuffer.get(), flushInfo->fVertexOffset);
-    target->draw(flushInfo->fGeometryProcessor.get(), flushInfo->fPipeline, mesh);
+    target->draw(flushInfo->fGeometryProcessor.get(), flushInfo->fPipeline,
+                 flushInfo->fFixedDynamicState, mesh);
     flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush;
     flushInfo->fGlyphsToFlush = 0;
 }
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 0318a90..a1f8f89 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -109,6 +109,7 @@
         sk_sp<const GrBuffer> fIndexBuffer;
         sk_sp<GrGeometryProcessor> fGeometryProcessor;
         const GrPipeline* fPipeline;
+        const GrPipeline::FixedDynamicState* fFixedDynamicState;
         int fGlyphsToFlush;
         int fVertexOffset;
     };
diff --git a/src/gpu/ops/GrDashOp.cpp b/src/gpu/ops/GrDashOp.cpp
index 968d169..d38bb9f 100644
--- a/src/gpu/ops/GrDashOp.cpp
+++ b/src/gpu/ops/GrDashOp.cpp
@@ -697,9 +697,9 @@
         if (fDisableSRGBOutputConversion) {
             pipelineFlags |= GrPipeline::kDisableOutputConversionToSRGB_Flag;
         }
-        const GrPipeline* pipeline = target->makePipeline(pipelineFlags, std::move(fProcessorSet),
-                                                          target->detachAppliedClip());
-        helper.recordDraw(target, gp.get(), pipeline);
+        auto pipe = target->makePipeline(pipelineFlags, std::move(fProcessorSet),
+                                         target->detachAppliedClip());
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
diff --git a/src/gpu/ops/GrDefaultPathRenderer.cpp b/src/gpu/ops/GrDefaultPathRenderer.cpp
index aa0963b..af4546c 100644
--- a/src/gpu/ops/GrDefaultPathRenderer.cpp
+++ b/src/gpu/ops/GrDefaultPathRenderer.cpp
@@ -63,12 +63,14 @@
 class PathGeoBuilder {
 public:
     PathGeoBuilder(GrPrimitiveType primitiveType, GrMeshDrawOp::Target* target,
-                   GrGeometryProcessor* geometryProcessor, const GrPipeline* pipeline)
+                   GrGeometryProcessor* geometryProcessor, const GrPipeline* pipeline,
+                   const GrPipeline::FixedDynamicState* fixedDynamicState)
             : fMesh(primitiveType)
             , fTarget(target)
             , fVertexStride(sizeof(SkPoint))
             , fGeometryProcessor(geometryProcessor)
             , fPipeline(pipeline)
+            , fFixedDynamicState(fixedDynamicState)
             , fIndexBuffer(nullptr)
             , fFirstIndex(0)
             , fIndicesInChunk(0)
@@ -274,7 +276,7 @@
                                  GrPrimitiveRestart::kNo);
             }
             fMesh.setVertexData(fVertexBuffer, fFirstVertex);
-            fTarget->draw(fGeometryProcessor, fPipeline, fMesh);
+            fTarget->draw(fGeometryProcessor, fPipeline, fFixedDynamicState, fMesh);
         }
 
         fTarget->putBackIndices((size_t)(fIndicesInChunk - indexCount));
@@ -312,6 +314,7 @@
     size_t fVertexStride;
     GrGeometryProcessor* fGeometryProcessor;
     const GrPipeline* fPipeline;
+    const GrPipeline::FixedDynamicState* fFixedDynamicState;
 
     const GrBuffer* fVertexBuffer;
     int fFirstVertex;
@@ -419,9 +422,9 @@
         } else {
             primitiveType = GrPrimitiveType::kTriangles;
         }
-
-        PathGeoBuilder pathGeoBuilder(primitiveType, target, gp.get(),
-                                      fHelper.makePipeline(target));
+        auto pipe = fHelper.makePipeline(target);
+        PathGeoBuilder pathGeoBuilder(primitiveType, target, gp.get(), pipe.fPipeline,
+                                      pipe.fFixedDynamicState);
 
         // fill buffers
         for (int i = 0; i < instanceCount; i++) {
diff --git a/src/gpu/ops/GrDrawAtlasOp.cpp b/src/gpu/ops/GrDrawAtlasOp.cpp
index 47bad8b..4c25edc 100644
--- a/src/gpu/ops/GrDrawAtlasOp.cpp
+++ b/src/gpu/ops/GrDrawAtlasOp.cpp
@@ -142,7 +142,8 @@
         memcpy(vertPtr, args.fVerts.begin(), allocSize);
         vertPtr += allocSize;
     }
-    helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+    auto pipe = fHelper.makePipeline(target);
+    helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
 }
 
 bool GrDrawAtlasOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
diff --git a/src/gpu/ops/GrDrawPathOp.cpp b/src/gpu/ops/GrDrawPathOp.cpp
index 185b3c0..98a4775 100644
--- a/src/gpu/ops/GrDrawPathOp.cpp
+++ b/src/gpu/ops/GrDrawPathOp.cpp
@@ -75,13 +75,16 @@
 }
 
 void GrDrawPathOp::onExecute(GrOpFlushState* state) {
+    GrAppliedClip appliedClip = state->detachAppliedClip();
+    GrPipeline::FixedDynamicState fixedDynamicState(appliedClip.scissorState().rect());
     GrPipeline pipeline(this->pipelineInitArgs(*state), this->detachProcessors(),
-                        state->detachAppliedClip());
+                        std::move(appliedClip));
     sk_sp<GrPathProcessor> pathProc(GrPathProcessor::Create(this->color(), this->viewMatrix()));
 
     GrStencilSettings stencil;
     init_stencil_pass_settings(*state, this->fillType(), &stencil);
-    state->gpu()->pathRendering()->drawPath(*pathProc, pipeline, stencil, fPath.get());
+    state->gpu()->pathRendering()->drawPath(*pathProc, pipeline, fixedDynamicState, stencil,
+                                            fPath.get());
 }
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index 8f6ad97..ad5aba2 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -245,7 +245,8 @@
                         GrPrimitiveRestart::kNo);
     }
     mesh.setVertexData(vertexBuffer, firstVertex);
-    target->draw(gp.get(), fHelper.makePipeline(target), mesh);
+    auto pipe = fHelper.makePipeline(target);
+    target->draw(gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
 }
 
 bool GrDrawVerticesOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index 7bfa72e..92bcb0c 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -280,7 +280,8 @@
                                                   kVertsPerRect * patch.fIter->numRectsToDraw());
             }
         }
-        helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+        auto pipe = fHelper.makePipeline(target);
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
diff --git a/src/gpu/ops/GrMeshDrawOp.cpp b/src/gpu/ops/GrMeshDrawOp.cpp
index 0bc782c..349f832 100644
--- a/src/gpu/ops/GrMeshDrawOp.cpp
+++ b/src/gpu/ops/GrMeshDrawOp.cpp
@@ -40,9 +40,10 @@
     return vertices;
 }
 
-void GrMeshDrawOp::PatternHelper::recordDraw(Target* target, const GrGeometryProcessor* gp,
-                                             const GrPipeline* pipeline) {
-    target->draw(gp, pipeline, fMesh);
+void GrMeshDrawOp::PatternHelper::recordDraw(
+        Target* target, const GrGeometryProcessor* gp, const GrPipeline* pipeline,
+        const GrPipeline::FixedDynamicState* fixedDynamicState) {
+    target->draw(gp, pipeline, fixedDynamicState, fMesh);
 }
 
 void* GrMeshDrawOp::QuadHelper::init(Target* target, size_t vertexStride, int quadsToDraw) {
diff --git a/src/gpu/ops/GrMeshDrawOp.h b/src/gpu/ops/GrMeshDrawOp.h
index cb2c332..ddc29f1 100644
--- a/src/gpu/ops/GrMeshDrawOp.h
+++ b/src/gpu/ops/GrMeshDrawOp.h
@@ -8,6 +8,7 @@
 #ifndef GrMeshDrawOp_DEFINED
 #define GrMeshDrawOp_DEFINED
 
+#include "GrAppliedClip.h"
 #include "GrDrawOp.h"
 #include "GrGeometryProcessor.h"
 #include "GrMesh.h"
@@ -40,7 +41,8 @@
                    int indicesPerRepetition, int repeatCount);
 
         /** Call after init() to issue draws to the GrMeshDrawOp::Target.*/
-        void recordDraw(Target*, const GrGeometryProcessor*, const GrPipeline*);
+        void recordDraw(Target*, const GrGeometryProcessor*, const GrPipeline*,
+                        const GrPipeline::FixedDynamicState*);
 
     private:
         GrMesh fMesh;
@@ -76,7 +78,8 @@
     virtual ~Target() {}
 
     /** Adds a draw of a mesh. */
-    virtual void draw(const GrGeometryProcessor*, const GrPipeline*, const GrMesh&) = 0;
+    virtual void draw(const GrGeometryProcessor*, const GrPipeline*,
+                      const GrPipeline::FixedDynamicState*, const GrMesh&) = 0;
 
     /**
      * Makes space for vertex data. The returned pointer is the location where vertex data
@@ -129,19 +132,35 @@
         return this->pipelineArena()->make<GrPipeline>(std::forward<Args>(args)...);
     }
 
+    template <typename... Args>
+    GrPipeline::FixedDynamicState* allocFixedDynamicState(Args&... args) {
+        return this->pipelineArena()->make<GrPipeline::FixedDynamicState>(
+                std::forward<Args>(args)...);
+    }
+
+    // Once we have C++17 structured bindings make this just be a tuple because then we can do:
+    //      auto [pipeline, fixedDynamicState] = target->makePipeline(...);
+    // in addition to:
+    //      std::tie(flushInfo.fPipeline, flushInfo.fFixedState) = target->makePipeline(...);
+    struct PipelineAndFixedDynamicState {
+        const GrPipeline* fPipeline;
+        const GrPipeline::FixedDynamicState* fFixedDynamicState;
+    };
+
     /**
      * Helper that makes a pipeline targeting the op's render target that incorporates the op's
-     * GrAppliedClip.
+     * GrAppliedClip and uses a fixed dynamic state.
      */
-    GrPipeline* makePipeline(uint32_t pipelineFlags, GrProcessorSet&& processorSet,
-                             GrAppliedClip&& clip) {
+    PipelineAndFixedDynamicState makePipeline(uint32_t pipelineFlags, GrProcessorSet&& processorSet,
+                                              GrAppliedClip&& clip) {
         GrPipeline::InitArgs pipelineArgs;
         pipelineArgs.fFlags = pipelineFlags;
         pipelineArgs.fProxy = this->proxy();
         pipelineArgs.fDstProxy = this->dstProxy();
         pipelineArgs.fCaps = &this->caps();
         pipelineArgs.fResourceProvider = this->resourceProvider();
-        return this->allocPipeline(pipelineArgs, std::move(processorSet), std::move(clip));
+        const auto* state = this->allocFixedDynamicState(clip.scissorState().rect());
+        return {this->allocPipeline(pipelineArgs, std::move(processorSet), std::move(clip)), state};
     }
 
     virtual GrRenderTargetProxy* proxy() const = 0;
diff --git a/src/gpu/ops/GrNonAAFillRectOp.cpp b/src/gpu/ops/GrNonAAFillRectOp.cpp
index d4ebedc..8af9651 100644
--- a/src/gpu/ops/GrNonAAFillRectOp.cpp
+++ b/src/gpu/ops/GrNonAAFillRectOp.cpp
@@ -203,7 +203,8 @@
             tesselate(verts, kVertexStride, fRects[i].fColor, &fRects[i].fViewMatrix,
                       fRects[i].fRect, &fRects[i].fLocalQuad);
         }
-        helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+        auto pipe = fHelper.makePipeline(target);
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -339,7 +340,8 @@
                 tesselate(verts, vertexStride, info.fColor, nullptr, info.fRect, nullptr);
             }
         }
-        helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+        auto pipe = fHelper.makePipeline(target);
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
diff --git a/src/gpu/ops/GrNonAAStrokeRectOp.cpp b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
index 11c2f8c..4090e34 100644
--- a/src/gpu/ops/GrNonAAStrokeRectOp.cpp
+++ b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
@@ -191,7 +191,8 @@
         GrMesh mesh(primType);
         mesh.setNonIndexedNonInstanced(vertexCount);
         mesh.setVertexData(vertexBuffer, firstVertex);
-        target->draw(gp.get(), fHelper.makePipeline(target), mesh);
+        auto pipe = fHelper.makePipeline(target);
+        target->draw(gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps&) override {
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index 1655b76..ca209e0 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -1445,7 +1445,8 @@
         mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1,
                         GrPrimitiveRestart::kNo);
         mesh.setVertexData(vertexBuffer, firstVertex);
-        target->draw(gp.get(),  fHelper.makePipeline(target), mesh);
+        auto pipe = fHelper.makePipeline(target);
+        target->draw(gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -1761,7 +1762,8 @@
         mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1,
                         GrPrimitiveRestart::kNo);
         mesh.setVertexData(vertexBuffer, firstVertex);
-        target->draw(gp.get(), fHelper.makePipeline(target), mesh);
+        auto pipe = fHelper.makePipeline(target);
+        target->draw(gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -2005,7 +2007,8 @@
 
             verts += kVerticesPerQuad;
         }
-        helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+        auto pipe = fHelper.makePipeline(target);
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -2239,7 +2242,8 @@
 
             verts += kVerticesPerQuad;
         }
-        helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+        auto pipe = fHelper.makePipeline(target);
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -2695,7 +2699,8 @@
         mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1,
                         GrPrimitiveRestart::kNo);
         mesh.setVertexData(vertexBuffer, firstVertex);
-        target->draw(gp.get(), fHelper.makePipeline(target), mesh);
+        auto pipe = fHelper.makePipeline(target);
+        target->draw(gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
@@ -2953,7 +2958,8 @@
                 verts++;
             }
         }
-        helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+        auto pipe = fHelper.makePipeline(target);
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
diff --git a/src/gpu/ops/GrRegionOp.cpp b/src/gpu/ops/GrRegionOp.cpp
index 9a9814e..cd51152 100644
--- a/src/gpu/ops/GrRegionOp.cpp
+++ b/src/gpu/ops/GrRegionOp.cpp
@@ -142,7 +142,8 @@
             int numRectsInRegion = fRegions[i].fRegion.computeRegionComplexity();
             verts += numRectsInRegion * kVertsPerInstance * kVertexStride;
         }
-        helper.recordDraw(target, gp.get(), fHelper.makePipeline(target));
+        auto pipe = fHelper.makePipeline(target);
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
diff --git a/src/gpu/ops/GrShadowRRectOp.cpp b/src/gpu/ops/GrShadowRRectOp.cpp
index afb084d..220556f 100644
--- a/src/gpu/ops/GrShadowRRectOp.cpp
+++ b/src/gpu/ops/GrShadowRRectOp.cpp
@@ -625,14 +625,14 @@
         }
 
         static const uint32_t kPipelineFlags = 0;
-        const GrPipeline* pipeline = target->makePipeline(
-                kPipelineFlags, GrProcessorSet::MakeEmptySet(), target->detachAppliedClip());
+        auto pipe = target->makePipeline(kPipelineFlags, GrProcessorSet::MakeEmptySet(),
+                                         target->detachAppliedClip());
 
         GrMesh mesh(GrPrimitiveType::kTriangles);
         mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1,
                         GrPrimitiveRestart::kNo);
         mesh.setVertexData(vertexBuffer, firstVertex);
-        target->draw(gp.get(), pipeline, mesh);
+        target->draw(gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
diff --git a/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp b/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp
index 8b83271..fc54475 100644
--- a/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp
+++ b/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp
@@ -134,17 +134,21 @@
     return args;
 }
 
-GrPipeline* GrSimpleMeshDrawOpHelper::internalMakePipeline(GrMeshDrawOp::Target* target,
-                                                           const GrPipeline::InitArgs& args) {
+auto GrSimpleMeshDrawOpHelper::internalMakePipeline(GrMeshDrawOp::Target* target,
+                                                    const GrPipeline::InitArgs& args)
+        -> PipelineAndFixedDynamicState {
     // A caller really should only call this once as the processor set and applied clip get
     // moved into the GrPipeline.
     SkASSERT(!fMadePipeline);
     SkDEBUGCODE(fMadePipeline = true);
+    auto clip = target->detachAppliedClip();
+    auto* dynamicState = target->allocFixedDynamicState(clip.scissorState().rect());
     if (fProcessors) {
-        return target->allocPipeline(args, std::move(*fProcessors), target->detachAppliedClip());
+        return {target->allocPipeline(args, std::move(*fProcessors), std::move(clip)),
+                dynamicState};
     } else {
-        return target->allocPipeline(args, GrProcessorSet::MakeEmptySet(),
-                                     target->detachAppliedClip());
+        return {target->allocPipeline(args, GrProcessorSet::MakeEmptySet(), std::move(clip)),
+                dynamicState};
     }
 }
 
@@ -169,8 +173,8 @@
            fStencilSettings == that.fStencilSettings;
 }
 
-const GrPipeline* GrSimpleMeshDrawOpHelperWithStencil::makePipeline(
-        GrMeshDrawOp::Target* target) {
+auto GrSimpleMeshDrawOpHelperWithStencil::makePipeline(GrMeshDrawOp::Target* target)
+        -> PipelineAndFixedDynamicState {
     auto args = INHERITED::pipelineInitArgs(target);
     args.fUserStencil = fStencilSettings;
     return this->internalMakePipeline(target, args);
diff --git a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
index e5229a7..cbf41a7 100644
--- a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
+++ b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
@@ -88,8 +88,9 @@
 
     bool compatibleWithAlphaAsCoverage() const { return fCompatibleWithAlphaAsCoveage; }
 
+    using PipelineAndFixedDynamicState = GrOpFlushState::PipelineAndFixedDynamicState;
     /** Makes a pipeline that consumes the processor set and the op's applied clip. */
-    GrPipeline* makePipeline(GrMeshDrawOp::Target* target) {
+    PipelineAndFixedDynamicState makePipeline(GrMeshDrawOp::Target* target) {
         return this->internalMakePipeline(target, this->pipelineInitArgs(target));
     }
 
@@ -117,7 +118,8 @@
 
     GrPipeline::InitArgs pipelineInitArgs(GrMeshDrawOp::Target* target) const;
 
-    GrPipeline* internalMakePipeline(GrMeshDrawOp::Target*, const GrPipeline::InitArgs&);
+    PipelineAndFixedDynamicState internalMakePipeline(GrMeshDrawOp::Target*,
+                                                      const GrPipeline::InitArgs&);
 
 private:
     GrProcessorSet* fProcessors;
@@ -139,6 +141,8 @@
 public:
     using MakeArgs = GrSimpleMeshDrawOpHelper::MakeArgs;
     using Flags = GrSimpleMeshDrawOpHelper::Flags;
+    using PipelineAndFixedDynamicState = GrOpFlushState::PipelineAndFixedDynamicState;
+
     using GrSimpleMeshDrawOpHelper::visitProxies;
 
     // using declarations can't be templated, so this is a pass through function instead.
@@ -161,7 +165,7 @@
     bool isCompatible(const GrSimpleMeshDrawOpHelperWithStencil& that, const GrCaps&,
                       const SkRect& thisBounds, const SkRect& thatBounds) const;
 
-    const GrPipeline* makePipeline(GrMeshDrawOp::Target*);
+    PipelineAndFixedDynamicState makePipeline(GrMeshDrawOp::Target*);
 
     SkString dumpInfo() const;
 
diff --git a/src/gpu/ops/GrSmallPathRenderer.cpp b/src/gpu/ops/GrSmallPathRenderer.cpp
index a0c9f9c..0cd99d7 100644
--- a/src/gpu/ops/GrSmallPathRenderer.cpp
+++ b/src/gpu/ops/GrSmallPathRenderer.cpp
@@ -303,6 +303,7 @@
         sk_sp<const GrBuffer> fIndexBuffer;
         sk_sp<GrGeometryProcessor>   fGeometryProcessor;
         const GrPipeline* fPipeline;
+        const GrPipeline::FixedDynamicState* fFixedDynamicState;
         int fVertexOffset;
         int fInstancesToFlush;
     };
@@ -310,8 +311,12 @@
     void onPrepareDraws(Target* target) override {
         int instanceCount = fShapes.count();
 
+        auto pipe = fHelper.makePipeline(target);
+
         FlushInfo flushInfo;
-        flushInfo.fPipeline = fHelper.makePipeline(target);
+        flushInfo.fPipeline = pipe.fPipeline;
+        flushInfo.fFixedDynamicState = pipe.fFixedDynamicState;
+
         // Setup GrGeometryProcessor
         const SkMatrix& ctm = fShapes[0].fViewMatrix;
         if (fUsesDistanceField) {
@@ -821,7 +826,8 @@
                                      kVerticesPerQuad, flushInfo->fInstancesToFlush,
                                      maxInstancesPerDraw);
             mesh.setVertexData(flushInfo->fVertexBuffer.get(), flushInfo->fVertexOffset);
-            target->draw(flushInfo->fGeometryProcessor.get(), flushInfo->fPipeline, mesh);
+            target->draw(flushInfo->fGeometryProcessor.get(), flushInfo->fPipeline,
+                         flushInfo->fFixedDynamicState, mesh);
             flushInfo->fVertexOffset += kVerticesPerQuad * flushInfo->fInstancesToFlush;
             flushInfo->fInstancesToFlush = 0;
         }
diff --git a/src/gpu/ops/GrTessellatingPathRenderer.cpp b/src/gpu/ops/GrTessellatingPathRenderer.cpp
index 4c436c2..ed59834 100644
--- a/src/gpu/ops/GrTessellatingPathRenderer.cpp
+++ b/src/gpu/ops/GrTessellatingPathRenderer.cpp
@@ -358,7 +358,8 @@
         GrMesh mesh(TESSELLATOR_WIREFRAME ? GrPrimitiveType::kLines : GrPrimitiveType::kTriangles);
         mesh.setNonIndexedNonInstanced(count);
         mesh.setVertexData(vb, firstVertex);
-        target->draw(gp, fHelper.makePipeline(target), mesh);
+        auto pipe = fHelper.makePipeline(target);
+        target->draw(gp, pipe.fPipeline, pipe.fFixedDynamicState, mesh);
     }
 
     bool onCombineIfPossible(GrOp*, const GrCaps&) override { return false; }
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index a895230..bced31d 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -793,8 +793,10 @@
             args.fFlags |= GrPipeline::kHWAntialias_Flag;
         }
 
-        const GrPipeline* pipeline = target->allocPipeline(args, GrProcessorSet::MakeEmptySet(),
-                                                           target->detachAppliedClip());
+        auto clip = target->detachAppliedClip();
+        const auto* fixedDynamicState = target->allocFixedDynamicState(clip.scissorState().rect());
+        const auto* pipeline =
+                target->allocPipeline(args, GrProcessorSet::MakeEmptySet(), std::move(clip));
         using TessFn =
                 decltype(&TextureOp::tess<SkPoint, MultiTexture::kNo, Domain::kNo, GrAA::kNo>);
 #define TESS_FN_AND_VERTEX_SIZE(Point, MT, Domain, AA)                          \
@@ -866,7 +868,7 @@
             mesh.setNonIndexedNonInstanced(4);
         }
         mesh.setVertexData(vbuffer, vstart);
-        target->draw(gp.get(), pipeline, mesh);
+        target->draw(gp.get(), pipeline, fixedDynamicState, mesh);
     }
 
     bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.cpp b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
index 95bcb13..91adb5a 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
@@ -560,10 +560,12 @@
     }
 }
 
-GrVkPipelineState* GrVkGpuRTCommandBuffer::prepareDrawState(const GrPrimitiveProcessor& primProc,
-                                                            const GrPipeline& pipeline,
-                                                            GrPrimitiveType primitiveType,
-                                                            bool hasDynamicState) {
+GrVkPipelineState* GrVkGpuRTCommandBuffer::prepareDrawState(
+        const GrPrimitiveProcessor& primProc,
+        const GrPipeline& pipeline,
+        const GrPipeline::FixedDynamicState* fixedDynamicState,
+        const GrPipeline::DynamicStateArrays* dynamicStateArrays,
+        GrPrimitiveType primitiveType) {
     CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
     SkASSERT(cbInfo.fRenderPass);
 
@@ -589,14 +591,15 @@
 
     GrRenderTarget* rt = pipeline.renderTarget();
 
-    if (!pipeline.getScissorState().enabled()) {
+    if (!pipeline.isScissorEnabled()) {
         GrVkPipeline::SetDynamicScissorRectState(fGpu, cbInfo.currentCmdBuf(),
                                                  rt, pipeline.proxy()->origin(),
                                                  SkIRect::MakeWH(rt->width(), rt->height()));
-    } else if (!hasDynamicState) {
-        GrVkPipeline::SetDynamicScissorRectState(fGpu, cbInfo.currentCmdBuf(),
-                                                 rt, pipeline.proxy()->origin(),
-                                                 pipeline.getScissorState().rect());
+    } else if (!dynamicStateArrays || !dynamicStateArrays->fScissorRects) {
+        SkASSERT(fixedDynamicState);
+        GrVkPipeline::SetDynamicScissorRectState(fGpu, cbInfo.currentCmdBuf(), rt,
+                                                 pipeline.proxy()->origin(),
+                                                 fixedDynamicState->fScissorRect);
     }
     GrVkPipeline::SetDynamicViewportState(fGpu, cbInfo.currentCmdBuf(), rt);
     GrVkPipeline::SetDynamicBlendConstantState(fGpu, cbInfo.currentCmdBuf(), rt->config(),
@@ -631,8 +634,9 @@
 
 void GrVkGpuRTCommandBuffer::onDraw(const GrPrimitiveProcessor& primProc,
                                     const GrPipeline& pipeline,
+                                    const GrPipeline::FixedDynamicState* fixedDynamicState,
+                                    const GrPipeline::DynamicStateArrays* dynamicStateArrays,
                                     const GrMesh meshes[],
-                                    const GrPipeline::DynamicState dynamicStates[],
                                     int meshCount,
                                     const SkRect& bounds) {
     SkASSERT(pipeline.renderTarget() == fRenderTarget);
@@ -653,12 +657,15 @@
     }
 
     GrPrimitiveType primitiveType = meshes[0].primitiveType();
-    GrVkPipelineState* pipelineState =
-            this->prepareDrawState(primProc, pipeline, primitiveType, SkToBool(dynamicStates));
+    GrVkPipelineState* pipelineState = this->prepareDrawState(primProc, pipeline, fixedDynamicState,
+                                                              dynamicStateArrays, primitiveType);
     if (!pipelineState) {
         return;
     }
 
+    bool dynamicScissor =
+            pipeline.isScissorEnabled() && dynamicStateArrays && dynamicStateArrays->fScissorRects;
+
     for (int i = 0; i < meshCount; ++i) {
         const GrMesh& mesh = meshes[i];
         if (mesh.primitiveType() != primitiveType) {
@@ -668,19 +675,17 @@
             pipelineState->freeTempResources(fGpu);
             SkDEBUGCODE(pipelineState = nullptr);
             primitiveType = mesh.primitiveType();
-            pipelineState = this->prepareDrawState(
-                    primProc, pipeline, primitiveType, SkToBool(dynamicStates));
+            pipelineState = this->prepareDrawState(primProc, pipeline, fixedDynamicState,
+                                                   dynamicStateArrays, primitiveType);
             if (!pipelineState) {
                 return;
             }
         }
 
-        if (dynamicStates) {
-            if (pipeline.getScissorState().enabled()) {
-                GrVkPipeline::SetDynamicScissorRectState(fGpu, cbInfo.currentCmdBuf(),
-                                                         fRenderTarget, pipeline.proxy()->origin(),
-                                                         dynamicStates[i].fScissorRect);
-            }
+        if (dynamicScissor) {
+            GrVkPipeline::SetDynamicScissorRectState(fGpu, cbInfo.currentCmdBuf(), fRenderTarget,
+                                                     pipeline.proxy()->origin(),
+                                                     dynamicStateArrays->fScissorRects[i]);
         }
 
         SkASSERT(pipelineState);
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.h b/src/gpu/vk/GrVkGpuCommandBuffer.h
index b56d157..01b2693 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.h
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.h
@@ -88,13 +88,15 @@
 
     GrVkPipelineState* prepareDrawState(const GrPrimitiveProcessor&,
                                         const GrPipeline&,
-                                        GrPrimitiveType,
-                                        bool hasDynamicState);
+                                        const GrPipeline::FixedDynamicState*,
+                                        const GrPipeline::DynamicStateArrays*,
+                                        GrPrimitiveType);
 
-    void onDraw(const GrPrimitiveProcessor& primProc,
-                const GrPipeline& pipeline,
-                const GrMesh mesh[],
-                const GrPipeline::DynamicState[],
+    void onDraw(const GrPrimitiveProcessor&,
+                const GrPipeline&,
+                const GrPipeline::FixedDynamicState*,
+                const GrPipeline::DynamicStateArrays*,
+                const GrMesh[],
                 int meshCount,
                 const SkRect& bounds) override;
 
diff --git a/src/gpu/vk/GrVkPipeline.cpp b/src/gpu/vk/GrVkPipeline.cpp
index 385cc8b..91f42ad 100644
--- a/src/gpu/vk/GrVkPipeline.cpp
+++ b/src/gpu/vk/GrVkPipeline.cpp
@@ -441,15 +441,11 @@
     dynamicInfo->pDynamicStates = dynamicStates;
 }
 
-GrVkPipeline* GrVkPipeline::Create(GrVkGpu* gpu,
-                                   const GrPrimitiveProcessor& primProc,
-                                   const GrPipeline& pipeline,
-                                   const GrStencilSettings& stencil,
+GrVkPipeline* GrVkPipeline::Create(GrVkGpu* gpu, const GrPrimitiveProcessor& primProc,
+                                   const GrPipeline& pipeline, const GrStencilSettings& stencil,
                                    VkPipelineShaderStageCreateInfo* shaderStageInfo,
-                                   int shaderStageCount,
-                                   GrPrimitiveType primitiveType,
-                                   const GrVkRenderPass& renderPass,
-                                   VkPipelineLayout layout,
+                                   int shaderStageCount, GrPrimitiveType primitiveType,
+                                   const GrVkRenderPass& renderPass, VkPipelineLayout layout,
                                    VkPipelineCache cache) {
     VkPipelineVertexInputStateCreateInfo vertexInputInfo;
     SkSTArray<2, VkVertexInputBindingDescription, true> bindingDescs;
diff --git a/tests/GrMeshTest.cpp b/tests/GrMeshTest.cpp
index af6633f..aa951c0 100644
--- a/tests/GrMeshTest.cpp
+++ b/tests/GrMeshTest.cpp
@@ -394,7 +394,7 @@
     GrRenderTargetProxy* proxy = fState->drawOpArgs().fProxy;
     GrPipeline pipeline(proxy, GrPipeline::ScissorState::kDisabled, SkBlendMode::kSrc);
     GrMeshTestProcessor mtp(mesh.isInstanced(), mesh.hasVertexData());
-    fState->rtCommandBuffer()->draw(mtp, pipeline, &mesh, nullptr, 1,
+    fState->rtCommandBuffer()->draw(mtp, pipeline, nullptr, nullptr, &mesh, 1,
                                     SkRect::MakeIWH(kImageWidth, kImageHeight));
 }
 
diff --git a/tests/GrPipelineDynamicStateTest.cpp b/tests/GrPipelineDynamicStateTest.cpp
index b8e893b..b2aaecc 100644
--- a/tests/GrPipelineDynamicStateTest.cpp
+++ b/tests/GrPipelineDynamicStateTest.cpp
@@ -35,11 +35,11 @@
 static constexpr int kScreenSplitX = kScreenSize/2;
 static constexpr int kScreenSplitY = kScreenSize/2;
 
-static const GrPipeline::DynamicState kDynamicStates[kNumMeshes] = {
-    {SkIRect::MakeLTRB(0,              0,              kScreenSplitX,  kScreenSplitY)},
-    {SkIRect::MakeLTRB(0,              kScreenSplitY,  kScreenSplitX,  kScreenSize)},
-    {SkIRect::MakeLTRB(kScreenSplitX,  0,              kScreenSize,    kScreenSplitY)},
-    {SkIRect::MakeLTRB(kScreenSplitX,  kScreenSplitY,  kScreenSize,    kScreenSize)},
+static const SkIRect kDynamicScissors[kNumMeshes] = {
+    SkIRect::MakeLTRB(0,              0,              kScreenSplitX,  kScreenSplitY),
+    SkIRect::MakeLTRB(0,              kScreenSplitY,  kScreenSplitX,  kScreenSize),
+    SkIRect::MakeLTRB(kScreenSplitX,  0,              kScreenSize,    kScreenSplitY),
+    SkIRect::MakeLTRB(kScreenSplitX,  kScreenSplitY,  kScreenSize,    kScreenSize),
 };
 
 static const GrColor kMeshColors[kNumMeshes] {
@@ -148,8 +148,10 @@
             mesh.setNonIndexedNonInstanced(4);
             mesh.setVertexData(fVertexBuffer.get(), 4 * i);
         }
-        state->rtCommandBuffer()->draw(GrPipelineDynamicStateTestProcessor(), pipeline,
-                                       meshes.begin(), kDynamicStates, 4,
+        GrPipeline::DynamicStateArrays dynamicState;
+        dynamicState.fScissorRects = kDynamicScissors;
+        state->rtCommandBuffer()->draw(GrPipelineDynamicStateTestProcessor(), pipeline, nullptr,
+                                       &dynamicState, meshes.begin(), 4,
                                        SkRect::MakeIWH(kScreenSize, kScreenSize));
     }
 
diff --git a/tests/OnFlushCallbackTest.cpp b/tests/OnFlushCallbackTest.cpp
index a875205..c60ae7c 100644
--- a/tests/OnFlushCallbackTest.cpp
+++ b/tests/OnFlushCallbackTest.cpp
@@ -163,7 +163,8 @@
         mesh.setIndexed(indexBuffer, 6, firstIndex, 0, 3, GrPrimitiveRestart::kNo);
         mesh.setVertexData(vertexBuffer, firstVertex);
 
-        target->draw(gp.get(), fHelper.makePipeline(target), mesh);
+        auto pipe = fHelper.makePipeline(target);
+        target->draw(gp.get(), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
     }
 
     Helper fHelper;
diff --git a/tests/PrimitiveProcessorTest.cpp b/tests/PrimitiveProcessorTest.cpp
index 60b2205..e9fbfd1 100644
--- a/tests/PrimitiveProcessorTest.cpp
+++ b/tests/PrimitiveProcessorTest.cpp
@@ -111,9 +111,9 @@
         SkASSERT(vertexStride == gp->debugOnly_vertexStride());
         SkPoint* vertices = reinterpret_cast<SkPoint*>(helper.init(target, vertexStride, 1));
         SkPointPriv::SetRectTriStrip(vertices, 0.f, 0.f, 1.f, 1.f, vertexStride);
-        helper.recordDraw(target, gp.get(),
-                          target->makePipeline(0, GrProcessorSet::MakeEmptySet(),
-                                               target->detachAppliedClip()));
+        auto pipe = target->makePipeline(0, GrProcessorSet::MakeEmptySet(),
+                                         target->detachAppliedClip());
+        helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
     int fNumAttribs;