Introduce dynamic pipeline state

Adds a DynamicState struct to GrPipeline that has a field for the
scissor rect. Eventually this should become the only way to specify a
scissor rectangle and may grow to contain more fields. Adds an array of
DynamicStates to GrGpuCommandBuffer::draw and implements support in GL
and Vulkan.

Bug: skia:
Change-Id: If5aebbf9da5d192acf7e68e7def4674ffc7ec310
Reviewed-on: https://skia-review.googlesource.com/18510
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index 0263dd6..1b438f4 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -92,6 +92,7 @@
   "$_tests/GrDrawTargetTest.cpp",
   "$_tests/GrMemoryPoolTest.cpp",
   "$_tests/GrMeshTest.cpp",
+  "$_tests/GrPipelineDynamicStateTest.cpp",
   "$_tests/GrPorterDuffTest.cpp",
   "$_tests/GrShapeTest.cpp",
   "$_tests/GrSKSLPrettyPrintTest.cpp",
diff --git a/src/gpu/GrGpuCommandBuffer.cpp b/src/gpu/GrGpuCommandBuffer.cpp
index 5570a5a..9659ca3 100644
--- a/src/gpu/GrGpuCommandBuffer.cpp
+++ b/src/gpu/GrGpuCommandBuffer.cpp
@@ -39,6 +39,7 @@
 bool GrGpuCommandBuffer::draw(const GrPipeline& pipeline,
                               const GrPrimitiveProcessor& primProc,
                               const GrMesh meshes[],
+                              const GrPipeline::DynamicState dynamicStates[],
                               int meshCount,
                               const SkRect& bounds) {
 #ifdef SK_DEBUG
@@ -58,7 +59,7 @@
         this->gpu()->stats()->incNumFailedDraws();
         return false;
     }
-    this->onDraw(pipeline, primProc, meshes, meshCount, bounds);
+    this->onDraw(pipeline, primProc, meshes, dynamicStates, meshCount, bounds);
     return true;
 }
 
diff --git a/src/gpu/GrGpuCommandBuffer.h b/src/gpu/GrGpuCommandBuffer.h
index fb5dc00..3a6a3e7 100644
--- a/src/gpu/GrGpuCommandBuffer.h
+++ b/src/gpu/GrGpuCommandBuffer.h
@@ -9,6 +9,7 @@
 #define GrGpuCommandBuffer_DEFINED
 
 #include "GrColor.h"
+#include "GrPipeline.h"
 #include "ops/GrDrawOp.h"
 
 class GrOpFlushState;
@@ -69,6 +70,7 @@
     bool draw(const GrPipeline&,
               const GrPrimitiveProcessor&,
               const GrMesh[],
+              const GrPipeline::DynamicState[],
               int meshCount,
               const SkRect& bounds);
 
@@ -99,7 +101,8 @@
     // overridden by backend-specific derived class to perform the draw call.
     virtual void onDraw(const GrPipeline&,
                         const GrPrimitiveProcessor&,
-                        const GrMesh*,
+                        const GrMesh[],
+                        const GrPipeline::DynamicState[],
                         int meshCount,
                         const SkRect& bounds) = 0;
 
diff --git a/src/gpu/GrPipeline.cpp b/src/gpu/GrPipeline.cpp
index 8d1b926..9d8b2ef 100644
--- a/src/gpu/GrPipeline.cpp
+++ b/src/gpu/GrPipeline.cpp
@@ -112,15 +112,19 @@
     }
 }
 
-GrPipeline::GrPipeline(GrRenderTarget* rt, SkBlendMode blendmode)
-        : fRenderTarget(rt)
-        , fScissorState()
-        , fWindowRectsState()
-        , fUserStencilSettings(&GrUserStencilSettings::kUnused)
-        , fFlags()
-        , fXferProcessor(GrPorterDuffXPFactory::MakeNoCoverageXP(blendmode))
-        , fFragmentProcessors()
-        , fNumColorProcessors(0) {}
+GrPipeline::GrPipeline(GrRenderTarget* rt, ScissorState scissorState, SkBlendMode blendmode)
+    : fRenderTarget(rt)
+    , fScissorState()
+    , fWindowRectsState()
+    , fUserStencilSettings(&GrUserStencilSettings::kUnused)
+    , fFlags()
+    , fXferProcessor(GrPorterDuffXPFactory::MakeNoCoverageXP(blendmode))
+    , fFragmentProcessors()
+    , fNumColorProcessors(0) {
+    if (ScissorState::kEnabled == scissorState) {
+        fScissorState.set({0, 0, 0, 0}); // caller will use the DynamicState struct.
+    }
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index 078cb68..3fd5189 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -68,6 +68,11 @@
         return flags;
     }
 
+    enum ScissorState : bool {
+        kEnabled = true,
+        kDisabled = false
+    };
+
     struct InitArgs {
         uint32_t fFlags = 0;
         const GrProcessorSet* fProcessors = nullptr;  // Must be finalized
@@ -80,16 +85,26 @@
     };
 
     /**
+     *  Graphics state that can change dynamically without creating a new pipeline.
+     **/
+    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.
+        SkIRect fScissorRect;
+    };
+
+    /**
      * A Default constructed pipeline is unusable until init() is called.
      **/
     GrPipeline() = default;
 
     /**
      * Creates a simple pipeline with default settings and no processors. The provided blend mode
-     * must be "Porter Duff" (<= kLastCoeffMode). This pipeline is initialized without requiring
-     * a call to init().
+     * must be "Porter Duff" (<= kLastCoeffMode). If using ScissorState::kEnabled, the caller must
+     * specify a scissor rectangle through the DynamicState struct.
      **/
-    GrPipeline(GrRenderTarget*, SkBlendMode);
+    GrPipeline(GrRenderTarget*, ScissorState, SkBlendMode);
 
     GrPipeline(const InitArgs& args) { this->init(args); }
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index b6c8076..50c0edb 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -2449,6 +2449,7 @@
 void GrGLGpu::draw(const GrPipeline& pipeline,
                    const GrPrimitiveProcessor& primProc,
                    const GrMesh meshes[],
+                   const GrPipeline::DynamicState dynamicStates[],
                    int meshCount) {
     this->handleDirtyContext();
 
@@ -2468,6 +2469,14 @@
             this->xferBarrier(pipeline.getRenderTarget(), barrierType);
         }
 
+        if (dynamicStates) {
+            if (pipeline.getScissorState().enabled()) {
+                GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(pipeline.getRenderTarget());
+                this->flushScissor(dynamicStates[i].fScissorRect,
+                                   glRT->getViewport(), glRT->origin());
+            }
+        }
+
         meshes[i].sendToGpu(primProc, this);
     }
 
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 1785eae..639b589 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -101,7 +101,8 @@
     // on GrGLGpuCommandBuffer.
     void draw(const GrPipeline&,
               const GrPrimitiveProcessor&,
-              const GrMesh*,
+              const GrMesh[],
+              const GrPipeline::DynamicState[],
               int meshCount);
 
 
diff --git a/src/gpu/gl/GrGLGpuCommandBuffer.h b/src/gpu/gl/GrGLGpuCommandBuffer.h
index c7c76a4..c6e7208 100644
--- a/src/gpu/gl/GrGLGpuCommandBuffer.h
+++ b/src/gpu/gl/GrGLGpuCommandBuffer.h
@@ -49,7 +49,8 @@
 
     void onDraw(const GrPipeline& pipeline,
                 const GrPrimitiveProcessor& primProc,
-                const GrMesh* mesh,
+                const GrMesh mesh[],
+                const GrPipeline::DynamicState dynamicStates[],
                 int meshCount,
                 const SkRect& bounds) override {
         GrGLRenderTarget* target = static_cast<GrGLRenderTarget*>(pipeline.getRenderTarget());
@@ -57,7 +58,7 @@
             fRenderTarget = target;
         }
         SkASSERT(target == fRenderTarget);
-        fGpu->draw(pipeline, primProc, mesh, meshCount);
+        fGpu->draw(pipeline, primProc, mesh, dynamicStates, meshCount);
     }
 
     void onClear(GrRenderTarget* rt, const GrFixedClip& clip, GrColor color) override {
diff --git a/src/gpu/ops/GrMeshDrawOp.cpp b/src/gpu/ops/GrMeshDrawOp.cpp
index d6c2ced..d1367e9 100644
--- a/src/gpu/ops/GrMeshDrawOp.cpp
+++ b/src/gpu/ops/GrMeshDrawOp.cpp
@@ -74,7 +74,8 @@
         const QueuedDraw& draw = fQueuedDraws[currDrawIdx];
         SkASSERT(draw.fPipeline->getRenderTarget() == state->drawOpArgs().fRenderTarget);
         state->commandBuffer()->draw(*draw.fPipeline, *draw.fGeometryProcessor.get(),
-                                     fMeshes.begin() + currMeshIdx, draw.fMeshCnt, this->bounds());
+                                     fMeshes.begin() + currMeshIdx, nullptr, draw.fMeshCnt,
+                                     this->bounds());
         currMeshIdx += draw.fMeshCnt;
         state->flushToken();
     }
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.cpp b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
index 6f39cfa..6e9c292 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
@@ -468,7 +468,8 @@
 sk_sp<GrVkPipelineState> GrVkGpuCommandBuffer::prepareDrawState(
                                                                const GrPipeline& pipeline,
                                                                const GrPrimitiveProcessor& primProc,
-                                                               GrPrimitiveType primitiveType) {
+                                                               GrPrimitiveType primitiveType,
+                                                               bool hasDynamicState) {
     CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
     SkASSERT(cbInfo.fRenderPass);
 
@@ -492,7 +493,18 @@
 
     pipelineState->bind(fGpu, cbInfo.currentCmdBuf());
 
-    GrVkPipeline::SetDynamicState(fGpu, cbInfo.currentCmdBuf(), pipeline);
+    GrRenderTarget* rt = pipeline.getRenderTarget();
+
+    if (!pipeline.getScissorState().enabled()) {
+        GrVkPipeline::SetDynamicScissorRectState(fGpu, cbInfo.currentCmdBuf(), rt,
+                                                 SkIRect::MakeWH(rt->width(), rt->height()));
+    } else if (!hasDynamicState) {
+        GrVkPipeline::SetDynamicScissorRectState(fGpu, cbInfo.currentCmdBuf(), rt,
+                                                 pipeline.getScissorState().rect());
+    }
+    GrVkPipeline::SetDynamicViewportState(fGpu, cbInfo.currentCmdBuf(), rt);
+    GrVkPipeline::SetDynamicBlendConstantState(fGpu, cbInfo.currentCmdBuf(), rt->config(),
+                                               pipeline.getXferProcessor());
 
     return pipelineState;
 }
@@ -533,7 +545,8 @@
 
 void GrVkGpuCommandBuffer::onDraw(const GrPipeline& pipeline,
                                   const GrPrimitiveProcessor& primProc,
-                                  const GrMesh* meshes,
+                                  const GrMesh meshes[],
+                                  const GrPipeline::DynamicState dynamicStates[],
                                   int meshCount,
                                   const SkRect& bounds) {
     GrVkRenderTarget* target = static_cast<GrVkRenderTarget*>(pipeline.getRenderTarget());
@@ -557,11 +570,14 @@
     GrPrimitiveType primitiveType = meshes[0].primitiveType();
     sk_sp<GrVkPipelineState> pipelineState = this->prepareDrawState(pipeline,
                                                                     primProc,
-                                                                    primitiveType);
+                                                                    primitiveType,
+                                                                    SkToBool(dynamicStates));
     if (!pipelineState) {
         return;
     }
 
+    CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
+
     for (int i = 0; i < meshCount; ++i) {
         const GrMesh& mesh = meshes[i];
         if (mesh.primitiveType() != primitiveType) {
@@ -573,17 +589,24 @@
             primitiveType = mesh.primitiveType();
             pipelineState = this->prepareDrawState(pipeline,
                                                    primProc,
-                                                   primitiveType);
+                                                   primitiveType,
+                                                   SkToBool(dynamicStates));
             if (!pipelineState) {
                 return;
             }
         }
 
+        if (dynamicStates) {
+            if (pipeline.getScissorState().enabled()) {
+                GrVkPipeline::SetDynamicScissorRectState(fGpu, cbInfo.currentCmdBuf(),
+                                                         target, dynamicStates[i].fScissorRect);
+            }
+        }
+
         SkASSERT(pipelineState);
         mesh.sendToGpu(primProc, this);
     }
 
-    CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
     cbInfo.fBounds.join(bounds);
     cbInfo.fIsEmpty = false;
 
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.h b/src/gpu/vk/GrVkGpuCommandBuffer.h
index 6836fac..9ef166e 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.h
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.h
@@ -53,11 +53,13 @@
 
     sk_sp<GrVkPipelineState> prepareDrawState(const GrPipeline&,
                                               const GrPrimitiveProcessor&,
-                                              GrPrimitiveType);
+                                              GrPrimitiveType,
+                                              bool hasDynamicState);
 
     void onDraw(const GrPipeline& pipeline,
                 const GrPrimitiveProcessor& primProc,
-                const GrMesh* mesh,
+                const GrMesh mesh[],
+                const GrPipeline::DynamicState[],
                 int meshCount,
                 const SkRect& bounds) override;
 
diff --git a/src/gpu/vk/GrVkPipeline.cpp b/src/gpu/vk/GrVkPipeline.cpp
index 2732c6f..0b4289e 100644
--- a/src/gpu/vk/GrVkPipeline.cpp
+++ b/src/gpu/vk/GrVkPipeline.cpp
@@ -481,65 +481,56 @@
     GR_VK_CALL(gpu->vkInterface(), DestroyPipeline(gpu->device(), fPipeline, nullptr));
 }
 
-static void set_dynamic_scissor_state(GrVkGpu* gpu,
-                                      GrVkCommandBuffer* cmdBuffer,
-                                      const GrPipeline& pipeline,
-                                      const GrRenderTarget& target) {
-    // We always use one scissor and if it is disabled we just make it the size of the RT
-    const GrScissorState& scissorState = pipeline.getScissorState();
-    VkRect2D scissor;
-    if (scissorState.enabled() &&
-        !scissorState.rect().contains(0, 0, target.width(), target.height())) {
-        // This all assumes the scissorState has previously been clipped to the device space render
-        // target.
-        scissor.offset.x = SkTMax(scissorState.rect().fLeft, 0);
-        scissor.extent.width = scissorState.rect().width();
-        if (kTopLeft_GrSurfaceOrigin == target.origin()) {
-            scissor.offset.y = scissorState.rect().fTop;
-        } else {
-            SkASSERT(kBottomLeft_GrSurfaceOrigin == target.origin());
-            scissor.offset.y = target.height() - scissorState.rect().fBottom;
-        }
-        scissor.offset.y = SkTMax(scissor.offset.y, 0);
-        scissor.extent.height = scissorState.rect().height();
-
-        SkASSERT(scissor.offset.x >= 0);
-        SkASSERT(scissor.offset.y >= 0);
-    } else {
-        scissor.extent.width = target.width();
-        scissor.extent.height = target.height();
-        scissor.offset.x = 0;
-        scissor.offset.y = 0;
+void GrVkPipeline::SetDynamicScissorRectState(GrVkGpu* gpu,
+                                              GrVkCommandBuffer* cmdBuffer,
+                                              const GrRenderTarget* renderTarget,
+                                              SkIRect scissorRect) {
+    if (!scissorRect.intersect(SkIRect::MakeWH(renderTarget->width(), renderTarget->height()))) {
+        scissorRect.setEmpty();
     }
+
+    VkRect2D scissor;
+    scissor.offset.x = scissorRect.fLeft;
+    scissor.extent.width = scissorRect.width();
+    if (kTopLeft_GrSurfaceOrigin == renderTarget->origin()) {
+        scissor.offset.y = scissorRect.fTop;
+    } else {
+        SkASSERT(kBottomLeft_GrSurfaceOrigin == renderTarget->origin());
+        scissor.offset.y = renderTarget->height() - scissorRect.fBottom;
+    }
+    scissor.extent.height = scissorRect.height();
+
+    SkASSERT(scissor.offset.x >= 0);
+    SkASSERT(scissor.offset.y >= 0);
     cmdBuffer->setScissor(gpu, 0, 1, &scissor);
 }
 
-static void set_dynamic_viewport_state(GrVkGpu* gpu,
-                                       GrVkCommandBuffer* cmdBuffer,
-                                       const GrRenderTarget& target) {
+void GrVkPipeline::SetDynamicViewportState(GrVkGpu* gpu,
+                                           GrVkCommandBuffer* cmdBuffer,
+                                           const GrRenderTarget* renderTarget) {
     // We always use one viewport the size of the RT
     VkViewport viewport;
     viewport.x = 0.0f;
     viewport.y = 0.0f;
-    viewport.width = SkIntToScalar(target.width());
-    viewport.height = SkIntToScalar(target.height());
+    viewport.width = SkIntToScalar(renderTarget->width());
+    viewport.height = SkIntToScalar(renderTarget->height());
     viewport.minDepth = 0.0f;
     viewport.maxDepth = 1.0f;
     cmdBuffer->setViewport(gpu, 0, 1, &viewport);
 }
 
-static void set_dynamic_blend_constant_state(GrVkGpu* gpu,
-                                             GrVkCommandBuffer* cmdBuffer,
-                                             const GrPipeline& pipeline) {
+void GrVkPipeline::SetDynamicBlendConstantState(GrVkGpu* gpu,
+                                                GrVkCommandBuffer* cmdBuffer,
+                                                GrPixelConfig pixelConfig,
+                                                const GrXferProcessor& xferProcessor) {
     GrXferProcessor::BlendInfo blendInfo;
-    pipeline.getXferProcessor().getBlendInfo(&blendInfo);
+    xferProcessor.getBlendInfo(&blendInfo);
     GrBlendCoeff srcCoeff = blendInfo.fSrcBlend;
     GrBlendCoeff dstCoeff = blendInfo.fDstBlend;
     float floatColors[4];
     if (blend_coeff_refs_constant(srcCoeff) || blend_coeff_refs_constant(dstCoeff)) {
         // Swizzle the blend to match what the shader will output.
-        const GrSwizzle& swizzle = gpu->caps()->shaderCaps()->configOutputSwizzle(
-                pipeline.getRenderTarget()->config());
+        const GrSwizzle& swizzle = gpu->caps()->shaderCaps()->configOutputSwizzle(pixelConfig);
         GrColor blendConst = swizzle.applyTo(blendInfo.fBlendConstant);
         GrColorToRGBAFloat(blendConst, floatColors);
     } else {
@@ -547,12 +538,3 @@
     }
     cmdBuffer->setBlendConstants(gpu, floatColors);
 }
-
-void GrVkPipeline::SetDynamicState(GrVkGpu* gpu,
-                                   GrVkCommandBuffer* cmdBuffer,
-                                   const GrPipeline& pipeline) {
-    const GrRenderTarget& target = *pipeline.getRenderTarget();
-    set_dynamic_scissor_state(gpu, cmdBuffer, pipeline, target);
-    set_dynamic_viewport_state(gpu, cmdBuffer, target);
-    set_dynamic_blend_constant_state(gpu, cmdBuffer, pipeline);
-}
diff --git a/src/gpu/vk/GrVkPipeline.h b/src/gpu/vk/GrVkPipeline.h
index 585014e..d05974b 100644
--- a/src/gpu/vk/GrVkPipeline.h
+++ b/src/gpu/vk/GrVkPipeline.h
@@ -16,10 +16,13 @@
 
 class GrPipeline;
 class GrPrimitiveProcessor;
+class GrRenderTarget;
+class GrXferProcessor;
 class GrStencilSettings;
 class GrVkCommandBuffer;
 class GrVkGpu;
 class GrVkRenderPass;
+struct SkIRect;
 
 class GrVkPipeline : public GrVkResource {
 public:
@@ -36,7 +39,11 @@
 
     VkPipeline pipeline() const { return fPipeline; }
 
-    static void SetDynamicState(GrVkGpu*, GrVkCommandBuffer*, const GrPipeline&);
+    static void SetDynamicScissorRectState(GrVkGpu*, GrVkCommandBuffer*, const GrRenderTarget*,
+                                           SkIRect);
+    static void SetDynamicViewportState(GrVkGpu*, GrVkCommandBuffer*, const GrRenderTarget*);
+    static void SetDynamicBlendConstantState(GrVkGpu*, GrVkCommandBuffer*, GrPixelConfig,
+                                             const GrXferProcessor&);
 
 #ifdef SK_TRACE_VK_RESOURCES
     void dumpInfo() const override {
diff --git a/tests/GpuSampleLocationsTest.cpp b/tests/GpuSampleLocationsTest.cpp
index 75d2846..aaab98a 100644
--- a/tests/GpuSampleLocationsTest.cpp
+++ b/tests/GpuSampleLocationsTest.cpp
@@ -91,10 +91,6 @@
     virtual ~TestSampleLocationsInterface() {}
 };
 
-static sk_sp<GrPipeline> construct_dummy_pipeline(GrRenderTargetContext* dc) {
-    return sk_sp<GrPipeline>(new GrPipeline(dc->accessRenderTarget(), SkBlendMode::kSrcOver));
-}
-
 void assert_equal(skiatest::Reporter* reporter, const SamplePattern& pattern,
                   const GrGpu::MultisampleSpecs& specs, bool flipY) {
     GrAlwaysAssert(specs.fSampleLocations);
@@ -136,11 +132,13 @@
     for (int repeat = 0; repeat < 2; ++repeat) {
         for (int i = 0; i < numTestPatterns; ++i) {
             testInterface->overrideSamplePattern(kTestPatterns[i]);
-            for (GrRenderTargetContext* dc : {bottomUps[i].get(), topDowns[i].get()}) {
-                sk_sp<GrPipeline> dummyPipeline = construct_dummy_pipeline(dc);
-                GrRenderTarget* rt = dc->accessRenderTarget();
+            for (GrRenderTargetContext* rtc : {bottomUps[i].get(), topDowns[i].get()}) {
+                GrPipeline dummyPipeline(rtc->accessRenderTarget(),
+                                         GrPipeline::ScissorState::kDisabled,
+                                         SkBlendMode::kSrcOver);
+                GrRenderTarget* rt = rtc->accessRenderTarget();
                 assert_equal(reporter, kTestPatterns[i],
-                             rt->renderTargetPriv().getMultisampleSpecs(*dummyPipeline),
+                             rt->renderTargetPriv().getMultisampleSpecs(dummyPipeline),
                              kBottomLeft_GrSurfaceOrigin == rt->origin());
             }
         }
diff --git a/tests/GrMeshTest.cpp b/tests/GrMeshTest.cpp
index 3a2be78..dd2a2d9 100644
--- a/tests/GrMeshTest.cpp
+++ b/tests/GrMeshTest.cpp
@@ -364,9 +364,9 @@
 
 void DrawMeshHelper::drawMesh(const GrMesh& mesh) {
     GrRenderTarget* rt = fState->drawOpArgs().fRenderTarget;
-    GrPipeline pipeline(rt, SkBlendMode::kSrc);
+    GrPipeline pipeline(rt, GrPipeline::ScissorState::kDisabled, SkBlendMode::kSrc);
     GrMeshTestProcessor mtp(mesh.isInstanced(), mesh.hasVertexData());
-    fState->commandBuffer()->draw(pipeline, mtp, &mesh, 1,
+    fState->commandBuffer()->draw(pipeline, mtp, &mesh, nullptr, 1,
                                   SkRect::MakeIWH(kImageWidth, kImageHeight));
 }
 
diff --git a/tests/GrPipelineDynamicStateTest.cpp b/tests/GrPipelineDynamicStateTest.cpp
new file mode 100644
index 0000000..4c5843a
--- /dev/null
+++ b/tests/GrPipelineDynamicStateTest.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+#include "Test.h"
+
+#if SK_SUPPORT_GPU
+
+#include "GrContext.h"
+#include "GrColor.h"
+#include "GrGeometryProcessor.h"
+#include "GrOpFlushState.h"
+#include "GrRenderTargetContext.h"
+#include "GrRenderTargetContextPriv.h"
+#include "GrResourceProvider.h"
+#include "SkMakeUnique.h"
+#include "glsl/GrGLSLVertexShaderBuilder.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLGeometryProcessor.h"
+#include "glsl/GrGLSLVarying.h"
+
+/**
+ * This is a GPU-backend specific test for dynamic pipeline state. It draws boxes using dynamic
+ * scissor rectangles then reads back the result to verify a successful test.
+ */
+
+using ScissorState = GrPipeline::ScissorState;
+
+static constexpr int kScreenSize = 6;
+static constexpr int kNumMeshes = 4;
+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 GrColor kMeshColors[kNumMeshes] {
+    GrColorPackRGBA(255, 0, 0, 255),
+    GrColorPackRGBA(0, 255, 0, 255),
+    GrColorPackRGBA(0, 0, 255, 255),
+    GrColorPackRGBA(0, 0, 0, 255)
+};
+
+struct Vertex {
+    float     fX;
+    float     fY;
+    GrColor   fColor;
+};
+
+class GrPipelineDynamicStateTestProcessor : public GrGeometryProcessor {
+public:
+    GrPipelineDynamicStateTestProcessor()
+        : fVertex(this->addVertexAttrib("vertex", kVec2f_GrVertexAttribType))
+        , fColor(this->addVertexAttrib("color", kVec4ub_GrVertexAttribType)) {
+        this->initClassID<GrPipelineDynamicStateTestProcessor>();
+    }
+
+    const char* name() const override { return "GrPipelineDynamicStateTest Processor"; }
+
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const final {}
+
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
+
+protected:
+    const Attribute& fVertex;
+    const Attribute& fColor;
+
+    friend class GLSLPipelineDynamicStateTestProcessor;
+    typedef GrGeometryProcessor INHERITED;
+};
+
+class GLSLPipelineDynamicStateTestProcessor : public GrGLSLGeometryProcessor {
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
+                 FPCoordTransformIter&& transformIter) final {}
+
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
+        const GrPipelineDynamicStateTestProcessor& mp =
+            args.fGP.cast<GrPipelineDynamicStateTestProcessor>();
+
+        GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
+        varyingHandler->emitAttributes(mp);
+        varyingHandler->addPassThroughAttribute(&mp.fColor, args.fOutputColor);
+
+        GrGLSLVertexBuilder* v = args.fVertBuilder;
+        v->codeAppendf("vec2 vertex = %s;", mp.fVertex.fName);
+        gpArgs->fPositionVar.set(kVec2f_GrSLType, "vertex");
+
+        GrGLSLPPFragmentBuilder* f = args.fFragBuilder;
+        f->codeAppendf("%s = vec4(1);", args.fOutputCoverage);
+    }
+};
+
+GrGLSLPrimitiveProcessor*
+GrPipelineDynamicStateTestProcessor::createGLSLInstance(const GrShaderCaps&) const {
+    return new GLSLPipelineDynamicStateTestProcessor;
+}
+
+class GrPipelineDynamicStateTestOp : public GrDrawOp {
+public:
+    DEFINE_OP_CLASS_ID
+
+    GrPipelineDynamicStateTestOp(ScissorState scissorState, sk_sp<const GrBuffer> vbuff)
+        : INHERITED(ClassID())
+        , fScissorState(scissorState)
+        , fVertexBuffer(std::move(vbuff)) {
+        this->setBounds(SkRect::MakeIWH(kScreenSize, kScreenSize),
+                        HasAABloat::kNo, IsZeroArea::kNo);
+    }
+
+private:
+    const char* name() const override { return "GrPipelineDynamicStateTestOp"; }
+    FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
+    bool xpRequiresDstTexture(const GrCaps&, const GrAppliedClip*) override { return false; }
+    bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
+    void onPrepare(GrOpFlushState*) override {}
+    void onExecute(GrOpFlushState* state) override {
+        GrRenderTarget* rt = state->drawOpArgs().fRenderTarget;
+        GrPipeline pipeline(rt, fScissorState, SkBlendMode::kSrc);
+        SkSTArray<kNumMeshes, GrMesh> meshes;
+        for (int i = 0; i < kNumMeshes; ++i) {
+            GrMesh& mesh = meshes.emplace_back(kTriangleStrip_GrPrimitiveType);
+            mesh.setNonIndexedNonInstanced(4);
+            mesh.setVertexData(fVertexBuffer.get(), 4 * i);
+        }
+        state->commandBuffer()->draw(pipeline, GrPipelineDynamicStateTestProcessor(),
+                                     meshes.begin(), kDynamicStates, 4,
+                                     SkRect::MakeIWH(kScreenSize, kScreenSize));
+    }
+
+    ScissorState                fScissorState;
+    const sk_sp<const GrBuffer> fVertexBuffer;
+
+    typedef GrDrawOp INHERITED;
+};
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrPipelineDynamicStateTest, reporter, ctxInfo) {
+    GrContext* const context = ctxInfo.grContext();
+    GrResourceProvider* rp = context->resourceProvider();
+
+    sk_sp<GrRenderTargetContext> rtc(
+        context->makeDeferredRenderTargetContext(SkBackingFit::kExact, kScreenSize, kScreenSize,
+                                                 kRGBA_8888_GrPixelConfig, nullptr));
+    if (!rtc) {
+        ERRORF(reporter, "could not create render target context.");
+        return;
+    }
+
+    constexpr float d = (float) kScreenSize;
+    Vertex vdata[kNumMeshes * 4] = {
+        {0, 0, kMeshColors[0]},
+        {0, d, kMeshColors[0]},
+        {d, 0, kMeshColors[0]},
+        {d, d, kMeshColors[0]},
+
+        {0, 0, kMeshColors[1]},
+        {0, d, kMeshColors[1]},
+        {d, 0, kMeshColors[1]},
+        {d, d, kMeshColors[1]},
+
+        {0, 0, kMeshColors[2]},
+        {0, d, kMeshColors[2]},
+        {d, 0, kMeshColors[2]},
+        {d, d, kMeshColors[2]},
+
+        {0, 0, kMeshColors[3]},
+        {0, d, kMeshColors[3]},
+        {d, 0, kMeshColors[3]},
+        {d, d, kMeshColors[3]}
+    };
+
+    sk_sp<const GrBuffer> vbuff(rp->createBuffer(sizeof(vdata), kVertex_GrBufferType,
+                                                 kDynamic_GrAccessPattern,
+                                                 GrResourceProvider::kNoPendingIO_Flag |
+                                                 GrResourceProvider::kRequireGpuMemory_Flag,
+                                                 vdata));
+    if (!vbuff) {
+        ERRORF(reporter, "vbuff is null.");
+        return;
+    }
+
+    uint32_t resultPx[kScreenSize * kScreenSize];
+
+    for (ScissorState scissorState : {ScissorState::kEnabled, ScissorState::kDisabled}) {
+        rtc->clear(nullptr, 0xbaaaaaad, true);
+        rtc->priv().testingOnly_addDrawOp(
+            skstd::make_unique<GrPipelineDynamicStateTestOp>(scissorState, vbuff));
+        rtc->readPixels(SkImageInfo::Make(kScreenSize, kScreenSize,
+                                          kRGBA_8888_SkColorType, kPremul_SkAlphaType),
+                        resultPx, 4 * kScreenSize, 0, 0, 0);
+        for (int y = 0; y < kScreenSize; ++y) {
+            for (int x = 0; x < kScreenSize; ++x) {
+                int expectedColorIdx;
+                if (ScissorState::kEnabled == scissorState) {
+                    expectedColorIdx = (x < kScreenSplitX ? 0 : 2) + (y < kScreenSplitY ? 0 : 1);
+                } else {
+                    expectedColorIdx = kNumMeshes - 1;
+                }
+                uint32_t expected = kMeshColors[expectedColorIdx];
+                uint32_t actual = resultPx[y * kScreenSize + x];
+                if (expected != actual) {
+                    ERRORF(reporter, "[scissor=%s] pixel (%i,%i): got 0x%x expected 0x%x",
+                           ScissorState::kEnabled == scissorState ? "enabled" : "disabled", x, y,
+                           actual, expected);
+                    return;
+                }
+            }
+        }
+    }
+}
+
+#endif