Add GrProgramInfo to centralize management of program information

This is the first step in moving the marshaling of program information earlier in renderTask processing (i.e., to onPrePrepare).

Change-Id: I91e3baed9a128e845bd32f9dbbacd9b21d852a3d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/244118
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/gm/clockwise.cpp b/gm/clockwise.cpp
index 8b7b0de..ab72288 100644
--- a/gm/clockwise.cpp
+++ b/gm/clockwise.cpp
@@ -36,6 +36,7 @@
 #include "src/gpu/GrPrimitiveProcessor.h"
 #include "src/gpu/GrProcessor.h"
 #include "src/gpu/GrProcessorSet.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrRenderTargetContext.h"
 #include "src/gpu/GrRenderTargetContextPriv.h"
@@ -165,9 +166,16 @@
         GrMesh mesh(GrPrimitiveType::kTriangleStrip);
         mesh.setNonIndexedNonInstanced(4);
         mesh.setVertexData(std::move(fVertexBuffer));
-        flushState->opsRenderPass()->draw(ClockwiseTestProcessor(fReadSkFragCoord), pipeline,
-                                          nullptr, nullptr, &mesh, 1,
-                                          SkRect::MakeXYWH(0, fY, 100, 100));
+
+        ClockwiseTestProcessor primProc(fReadSkFragCoord);
+
+        GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
+                                  flushState->drawOpArgs().origin(),
+                                  pipeline,
+                                  primProc,
+                                  nullptr, nullptr);
+
+        flushState->opsRenderPass()->draw(programInfo, &mesh, 1, SkRect::MakeXYWH(0, fY, 100, 100));
     }
 
     sk_sp<GrBuffer> fVertexBuffer;
diff --git a/gm/fwidth_squircle.cpp b/gm/fwidth_squircle.cpp
index 598e830..e75891e 100644
--- a/gm/fwidth_squircle.cpp
+++ b/gm/fwidth_squircle.cpp
@@ -30,6 +30,7 @@
 #include "src/gpu/GrPrimitiveProcessor.h"
 #include "src/gpu/GrProcessor.h"
 #include "src/gpu/GrProcessorSet.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrRenderTargetContext.h"
 #include "src/gpu/GrRenderTargetContextPriv.h"
@@ -150,7 +151,7 @@
         this->setBounds(SkRect::MakeIWH(kWidth, kHeight), HasAABloat::kNo, IsHairline::kNo);
     }
 
-    const char* name() const override { return "ClockwiseTestOp"; }
+    const char* name() const override { return "FwidthSquircleTestOp"; }
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
     GrProcessorSet::Analysis finalize(
             const GrCaps&, const GrAppliedClip*, bool hasMixedSampledCoverage, GrClampType) override {
@@ -172,12 +173,19 @@
         }
         GrPipeline pipeline(GrScissorTest::kDisabled, SkBlendMode::kSrcOver,
                             flushState->drawOpArgs().outputSwizzle());
+
+        FwidthSquircleTestProcessor primProc(fViewMatrix);
+
+        GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
+                                  flushState->drawOpArgs().origin(),
+                                  pipeline,
+                                  primProc,
+                                  nullptr, nullptr);
+
         GrMesh mesh(GrPrimitiveType::kTriangleStrip);
         mesh.setNonIndexedNonInstanced(4);
         mesh.setVertexData(std::move(fVertexBuffer));
-        flushState->opsRenderPass()->draw(FwidthSquircleTestProcessor(fViewMatrix), pipeline,
-                                          nullptr, nullptr, &mesh, 1, SkRect::MakeIWH(kWidth,
-                                                                                      kHeight));
+        flushState->opsRenderPass()->draw(programInfo, &mesh, 1, SkRect::MakeIWH(kWidth, kHeight));
     }
 
     sk_sp<GrBuffer> fVertexBuffer;
diff --git a/gm/samplelocations.cpp b/gm/samplelocations.cpp
index e930d0a..c7cc73c 100644
--- a/gm/samplelocations.cpp
+++ b/gm/samplelocations.cpp
@@ -235,11 +235,17 @@
                             flushState->drawOpArgs().outputSwizzle(),
                             GrPipeline::InputFlags::kHWAntialias, &kStencilWrite);
 
+        SampleLocationsTestProcessor primProc(fGradType);
+
+        GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
+                                  flushState->drawOpArgs().origin(),
+                                  pipeline,
+                                  primProc,
+                                  nullptr, nullptr);
+
         GrMesh mesh(GrPrimitiveType::kTriangleStrip);
         mesh.setInstanced(nullptr, 200*200, 0, 4);
-        flushState->opsRenderPass()->draw(
-                SampleLocationsTestProcessor(fGradType), pipeline, nullptr, nullptr, &mesh, 1,
-                SkRect::MakeIWH(200, 200));
+        flushState->opsRenderPass()->draw(programInfo, &mesh, 1, SkRect::MakeIWH(200, 200));
     }
 
     const GradType fGradType;
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 01d1ef7..a612881 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -120,6 +120,8 @@
   "$_src/gpu/GrMesh.h",
   "$_src/gpu/GrNativeRect.h",
   "$_src/gpu/GrNonAtomicRef.h",
+  "$_src/gpu/GrOnFlushResourceProvider.cpp",
+  "$_src/gpu/GrOnFlushResourceProvider.h",
   "$_src/gpu/GrOpFlushState.cpp",
   "$_src/gpu/GrOpFlushState.h",
   "$_src/gpu/GrOpsRenderPass.cpp",
@@ -132,8 +134,6 @@
   "$_src/gpu/GrPathRendererChain.h",
   "$_src/gpu/GrPathRenderer.cpp",
   "$_src/gpu/GrPathRenderer.h",
-  "$_src/gpu/GrOnFlushResourceProvider.cpp",
-  "$_src/gpu/GrOnFlushResourceProvider.h",
   "$_src/gpu/GrPipeline.cpp",
   "$_src/gpu/GrPipeline.h",
   "$_src/gpu/GrPrimitiveProcessor.cpp",
@@ -142,6 +142,7 @@
   "$_src/gpu/GrProcessorSet.h",
   "$_src/gpu/GrProgramDesc.cpp",
   "$_src/gpu/GrProgramDesc.h",
+  "$_src/gpu/GrProgramInfo.h",
   "$_src/gpu/GrProcessor.cpp",
   "$_src/gpu/GrProcessor.h",
   "$_src/gpu/GrProcessorAnalysis.cpp",
diff --git a/src/gpu/GrOpFlushState.cpp b/src/gpu/GrOpFlushState.cpp
index 8e10019..93a6026 100644
--- a/src/gpu/GrOpFlushState.cpp
+++ b/src/gpu/GrOpFlushState.cpp
@@ -13,6 +13,7 @@
 #include "src/gpu/GrDrawOpAtlas.h"
 #include "src/gpu/GrGpu.h"
 #include "src/gpu/GrImageInfo.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrResourceProvider.h"
 
 //////////////////////////////////////////////////////////////////////////////
@@ -52,10 +53,16 @@
             this->opsRenderPass()->inlineUpload(this, fCurrUpload->fUpload);
             ++fCurrUpload;
         }
-        this->opsRenderPass()->draw(
-                *fCurrDraw->fGeometryProcessor, *pipeline, fCurrDraw->fFixedDynamicState,
-                fCurrDraw->fDynamicStateArrays, fCurrDraw->fMeshes, fCurrDraw->fMeshCnt,
-                chainBounds);
+
+        GrProgramInfo programInfo(this->proxy()->numSamples(),
+                                  this->proxy()->origin(),
+                                  *pipeline,
+                                  *fCurrDraw->fGeometryProcessor,
+                                  fCurrDraw->fFixedDynamicState,
+                                  fCurrDraw->fDynamicStateArrays);
+
+        this->opsRenderPass()->draw(programInfo, fCurrDraw->fMeshes,
+                                    fCurrDraw->fMeshCnt, chainBounds);
         fTokenTracker->flushToken();
         ++fCurrDraw;
     }
diff --git a/src/gpu/GrOpsRenderPass.cpp b/src/gpu/GrOpsRenderPass.cpp
index 3728426..fd4345b 100644
--- a/src/gpu/GrOpsRenderPass.cpp
+++ b/src/gpu/GrOpsRenderPass.cpp
@@ -15,6 +15,7 @@
 #include "src/gpu/GrGpu.h"
 #include "src/gpu/GrMesh.h"
 #include "src/gpu/GrPrimitiveProcessor.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRenderTarget.h"
 #include "src/gpu/GrRenderTargetPriv.h"
 #include "src/gpu/GrTexturePriv.h"
@@ -35,10 +36,7 @@
 }
 
 #ifdef SK_DEBUG
-static void assert_msaa_and_mips_are_resolved(
-        const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
-        const GrPipeline::FixedDynamicState* fixedDynamicState,
-        const GrPipeline::DynamicStateArrays* dynamicStateArrays, int meshCount) {
+static void assert_msaa_and_mips_are_resolved(const GrProgramInfo& programInfo, int meshCount) {
     auto assertResolved = [](GrTexture* tex, const GrSamplerState& sampler) {
         SkASSERT(tex);
 
@@ -52,68 +50,81 @@
         }
     };
 
-    if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
-        for (int m = 0, i = 0; m < meshCount; ++m) {
-            for (int s = 0; s < primProc.numTextureSamplers(); ++s, ++i) {
-                auto* tex = dynamicStateArrays->fPrimitiveProcessorTextures[i]->peekTexture();
-                assertResolved(tex, primProc.textureSampler(s).samplerState());
+    if (programInfo.hasDynamicPrimProcTextures()) {
+        for (int m = 0; m < meshCount; ++m) {
+            auto dynamicPrimProcTextures = programInfo.dynamicPrimProcTextures(m);
+
+            for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
+                auto* tex = dynamicPrimProcTextures[s]->peekTexture();
+                assertResolved(tex, programInfo.primProc().textureSampler(s).samplerState());
             }
         }
-    } else {
-        for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-            auto* tex = fixedDynamicState->fPrimitiveProcessorTextures[i]->peekTexture();
-            assertResolved(tex, primProc.textureSampler(i).samplerState());
+    } else if (programInfo.hasFixedPrimProcTextures()) {
+        auto fixedPrimProcTextures = programInfo.fixedPrimProcTextures();
+
+        for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
+            auto* tex = fixedPrimProcTextures[s]->peekTexture();
+            assertResolved(tex, programInfo.primProc().textureSampler(s).samplerState());
         }
     }
 
-    GrFragmentProcessor::Iter iter(pipeline);
+    GrFragmentProcessor::Iter iter(programInfo.pipeline());
     while (const GrFragmentProcessor* fp = iter.next()) {
-        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
-            const auto& textureSampler = fp->textureSampler(i);
+        for (int s = 0; s < fp->numTextureSamplers(); ++s) {
+            const auto& textureSampler = fp->textureSampler(s);
             assertResolved(textureSampler.peekTexture(), textureSampler.samplerState());
         }
     }
 }
 #endif
 
-bool GrOpsRenderPass::draw(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline,
-                           const GrPipeline::FixedDynamicState* fixedDynamicState,
-                           const GrPipeline::DynamicStateArrays* dynamicStateArrays,
+bool GrOpsRenderPass::draw(const GrProgramInfo& programInfo,
                            const GrMesh meshes[], int meshCount, const SkRect& bounds) {
+    if (!meshCount) {
+        return true;
+    }
+
 #ifdef SK_DEBUG
-    SkASSERT(!primProc.hasInstanceAttributes() || this->gpu()->caps()->instanceAttribSupport());
+    SkASSERT(!programInfo.primProc().hasInstanceAttributes() ||
+             this->gpu()->caps()->instanceAttribSupport());
     for (int i = 0; i < meshCount; ++i) {
-        SkASSERT(primProc.hasVertexAttributes() == meshes[i].hasVertexData());
-        SkASSERT(primProc.hasInstanceAttributes() == meshes[i].hasInstanceData());
+        SkASSERT(programInfo.primProc().hasVertexAttributes() == meshes[i].hasVertexData());
+        SkASSERT(programInfo.primProc().hasInstanceAttributes() == meshes[i].hasInstanceData());
     }
 
-    SkASSERT(!pipeline.isScissorEnabled() || fixedDynamicState ||
-             (dynamicStateArrays && dynamicStateArrays->fScissorRects));
+    SkASSERT(!programInfo.pipeline().isScissorEnabled() || programInfo.fixedDynamicState() ||
+             (programInfo.dynamicStateArrays() && programInfo.dynamicStateArrays()->fScissorRects));
 
-    SkASSERT(!pipeline.isBad());
+    SkASSERT(!programInfo.pipeline().isBad());
 
-    if (fixedDynamicState && fixedDynamicState->fPrimitiveProcessorTextures) {
-        GrTextureProxy** processorProxies = fixedDynamicState->fPrimitiveProcessorTextures;
-        for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-            SkASSERT(processorProxies[i]->isInstantiated());
+    if (programInfo.hasFixedPrimProcTextures()) {
+        auto fixedPrimProcTextures = programInfo.fixedPrimProcTextures();
+        for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
+            SkASSERT(fixedPrimProcTextures[s]->isInstantiated());
         }
     }
-    if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
-        int n = primProc.numTextureSamplers() * meshCount;
-        const auto* textures = dynamicStateArrays->fPrimitiveProcessorTextures;
-        for (int i = 0; i < n; ++i) {
-            SkASSERT(textures[i]->isInstantiated());
+
+    if (programInfo.hasDynamicPrimProcTextures()) {
+        for (int m = 0; m < meshCount; ++m) {
+            auto dynamicPrimProcTextures = programInfo.dynamicPrimProcTextures(m);
+            for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
+                SkASSERT(dynamicPrimProcTextures[s]->isInstantiated());
+            }
         }
-        SkASSERT(meshCount >= 1);
-        const GrTextureProxy* const* primProcProxies =
-                dynamicStateArrays->fPrimitiveProcessorTextures;
-        for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-            const GrBackendFormat& format = primProcProxies[i]->backendFormat();
-            GrTextureType type = primProcProxies[i]->textureType();
-            GrPixelConfig config = primProcProxies[i]->config();
-            for (int j = 1; j < meshCount; ++j) {
-                const GrTextureProxy* testProxy =
-                        primProcProxies[j*primProc.numTextureSamplers() + i];
+
+        // Check that, for a given sampler, the properties of the dynamic textures remain
+        // the same for all the meshes
+        for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
+            auto dynamicPrimProcTextures = programInfo.dynamicPrimProcTextures(0);
+
+            const GrBackendFormat& format = dynamicPrimProcTextures[s]->backendFormat();
+            GrTextureType type = dynamicPrimProcTextures[s]->textureType();
+            GrPixelConfig config = dynamicPrimProcTextures[s]->config();
+
+            for (int m = 1; m < meshCount; ++m) {
+                dynamicPrimProcTextures = programInfo.dynamicPrimProcTextures(m);
+
+                auto testProxy = dynamicPrimProcTextures[s];
                 SkASSERT(testProxy->backendFormat() == format);
                 SkASSERT(testProxy->textureType() == type);
                 SkASSERT(testProxy->config() == config);
@@ -121,22 +132,17 @@
         }
     }
 
-    assert_msaa_and_mips_are_resolved(
-            primProc, pipeline, fixedDynamicState, dynamicStateArrays, meshCount);
+    assert_msaa_and_mips_are_resolved(programInfo, meshCount);
 #endif
 
-    if (primProc.numVertexAttributes() > this->gpu()->caps()->maxVertexAttributes()) {
+    if (programInfo.primProc().numVertexAttributes() > this->gpu()->caps()->maxVertexAttributes()) {
         this->gpu()->stats()->incNumFailedDraws();
         return false;
     }
-    this->onDraw(primProc, pipeline, fixedDynamicState, dynamicStateArrays, meshes, meshCount,
-                 bounds);
+    this->onDraw(programInfo, meshes, meshCount, bounds);
+
 #ifdef SK_DEBUG
-    GrProcessor::CustomFeatures processorFeatures = primProc.requestedFeatures();
-    for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
-        processorFeatures |= pipeline.getFragmentProcessor(i).requestedFeatures();
-    }
-    processorFeatures |= pipeline.getXferProcessor().requestedFeatures();
+    GrProcessor::CustomFeatures processorFeatures = programInfo.requestedFeatures();
     if (GrProcessor::CustomFeatures::kSampleLocations & processorFeatures) {
         // Verify we always have the same sample pattern key, regardless of graphics state.
         SkASSERT(this->gpu()->findOrAssignSamplePatternKey(fRenderTarget)
diff --git a/src/gpu/GrOpsRenderPass.h b/src/gpu/GrOpsRenderPass.h
index b23ee56..e3d24a7 100644
--- a/src/gpu/GrOpsRenderPass.h
+++ b/src/gpu/GrOpsRenderPass.h
@@ -18,6 +18,7 @@
 class GrMesh;
 class GrPipeline;
 class GrPrimitiveProcessor;
+class GrProgramInfo;
 class GrRenderTarget;
 class GrSemaphore;
 struct SkIRect;
@@ -55,13 +56,7 @@
     // GrMesh object and emit a draw for it. Each draw will use the same GrPipeline and
     // GrPrimitiveProcessor. This may fail if the draw would exceed any resource limits (e.g.
     // number of vertex attributes is too large).
-    bool draw(const GrPrimitiveProcessor&,
-              const GrPipeline&,
-              const GrPipeline::FixedDynamicState*,
-              const GrPipeline::DynamicStateArrays*,
-              const GrMesh[],
-              int meshCount,
-              const SkRect& bounds);
+    bool draw(const GrProgramInfo&, const GrMesh[], int meshCount, const SkRect& bounds);
 
     // Performs an upload of vertex data in the middle of a set of a set of draws
     virtual void inlineUpload(GrOpFlushState*, GrDeferredTextureUploadFn&) = 0;
@@ -100,12 +95,7 @@
     virtual GrGpu* gpu() = 0;
 
     // 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[],
-                        int meshCount,
+    virtual void onDraw(const GrProgramInfo&, const GrMesh[], int meshCount,
                         const SkRect& bounds) = 0;
 
     // overridden by backend-specific derived class to perform the clear.
diff --git a/src/gpu/GrOpsTask.cpp b/src/gpu/GrOpsTask.cpp
index 5670bd1..35bfcb6 100644
--- a/src/gpu/GrOpsTask.cpp
+++ b/src/gpu/GrOpsTask.cpp
@@ -412,11 +412,10 @@
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
             TRACE_EVENT0("skia.gpu", chain.head()->name());
 #endif
-            GrOpFlushState::OpArgs opArgs(
-                chain.head(),
-                fTarget->asRenderTargetProxy(),
-                chain.appliedClip(),
-                chain.dstProxy());
+            GrOpFlushState::OpArgs opArgs(chain.head(),
+                                          fTarget->asRenderTargetProxy(),
+                                          chain.appliedClip(),
+                                          chain.dstProxy());
 
             flushState->setOpArgs(&opArgs);
             chain.head()->prepare(flushState);
diff --git a/src/gpu/GrPathRendering.cpp b/src/gpu/GrPathRendering.cpp
index 6dd3153..5e44531 100644
--- a/src/gpu/GrPathRendering.cpp
+++ b/src/gpu/GrPathRendering.cpp
@@ -12,6 +12,7 @@
 #include "src/core/SkScalerContext.h"
 #include "src/gpu/GrGpu.h"
 #include "src/gpu/GrPathRendering.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRenderTarget.h"
 
 const GrUserStencilSettings& GrPathRendering::GetStencilPassSettings(FillType fill) {
@@ -50,18 +51,15 @@
     this->onStencilPath(args, path);
 }
 
-void GrPathRendering::drawPath(GrRenderTarget* renderTarget, int numSamples, GrSurfaceOrigin origin,
-                               const GrPrimitiveProcessor& primProc,
-                               const GrPipeline& pipeline,
-                               const GrPipeline::FixedDynamicState& fixedDynamicState,
+void GrPathRendering::drawPath(GrRenderTarget* renderTarget,
+                               const GrProgramInfo& programInfo,
                                // Cover pass settings in pipeline.
                                const GrStencilSettings& stencilPassSettings,
                                const GrPath* path) {
     fGpu->handleDirtyContext();
-    if (GrXferBarrierType barrierType = pipeline.xferBarrierType(renderTarget->asTexture(),
-                                                                 *fGpu->caps())) {
+    if (auto barrierType = programInfo.pipeline().xferBarrierType(renderTarget->asTexture(),
+                                                                  *fGpu->caps())) {
         fGpu->xferBarrier(renderTarget, barrierType);
     }
-    this->onDrawPath(renderTarget, numSamples, origin, primProc, pipeline, fixedDynamicState,
-                     stencilPassSettings, path);
+    this->onDrawPath(renderTarget, programInfo, stencilPassSettings, path);
 }
diff --git a/src/gpu/GrPathRendering.h b/src/gpu/GrPathRendering.h
index f12d9f5..3e143e4 100644
--- a/src/gpu/GrPathRendering.h
+++ b/src/gpu/GrPathRendering.h
@@ -9,12 +9,16 @@
 #define GrPathRendering_DEFINED
 
 #include "include/core/SkPath.h"
-#include "src/gpu/GrPipeline.h"
 
 class GrGpu;
 class GrPath;
+class GrProgramInfo;
+class GrRenderTarget;
+class GrRenderTargetProxy;
+class GrScissorState;
 class GrStencilSettings;
 class GrStyle;
+struct GrUserStencilSettings;
 struct SkScalerContextEffects;
 class SkDescriptor;
 class SkTypeface;
@@ -108,10 +112,8 @@
 
     void stencilPath(const StencilPathArgs& args, const GrPath* path);
 
-    void drawPath(GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                  const GrPrimitiveProcessor& primProc,
-                  const GrPipeline& pipeline,
-                  const GrPipeline::FixedDynamicState&,
+    void drawPath(GrRenderTarget*,
+                  const GrProgramInfo&,
                   const GrStencilSettings& stencilPassSettings,  // Cover pass settings in pipeline.
                   const GrPath* path);
 
@@ -119,10 +121,8 @@
     GrPathRendering(GrGpu* gpu) : fGpu(gpu) { }
 
     virtual void onStencilPath(const StencilPathArgs&, const GrPath*) = 0;
-    virtual void onDrawPath(GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                            const GrPrimitiveProcessor&,
-                            const GrPipeline&,
-                            const GrPipeline::FixedDynamicState&,
+    virtual void onDrawPath(GrRenderTarget*,
+                            const GrProgramInfo&,
                             const GrStencilSettings&,
                             const GrPath*) = 0;
 
diff --git a/src/gpu/GrPathRendering_none.cpp b/src/gpu/GrPathRendering_none.cpp
index ea7fa78..3c2878d 100644
--- a/src/gpu/GrPathRendering_none.cpp
+++ b/src/gpu/GrPathRendering_none.cpp
@@ -42,10 +42,8 @@
 
 sk_sp<GrPath> GrGLPathRendering::createPath(const SkPath&, const GrStyle&) { return nullptr; }
 
-void GrGLPathRendering::onDrawPath(GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                                   const GrPrimitiveProcessor&,
-                                   const GrPipeline&,
-                                   const GrPipeline::FixedDynamicState&,
+void GrGLPathRendering::onDrawPath(GrRenderTarget*,
+                                   const GrProgramInfo&,
                                    const GrStencilSettings&,
                                    const GrPath*) {}
 
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index acc0fd1..ac7b8b9 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -12,6 +12,7 @@
 #include "src/gpu/GrPipeline.h"
 #include "src/gpu/GrPrimitiveProcessor.h"
 #include "src/gpu/GrProcessor.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRenderTargetPriv.h"
 #include "src/gpu/GrShaderCaps.h"
 #include "src/gpu/GrTexturePriv.h"
@@ -188,10 +189,8 @@
                                                                       fp.numCoordTransforms()), b);
 }
 
-bool GrProgramDesc::Build(
-        GrProgramDesc* desc, const GrRenderTarget* renderTarget,
-        const GrPrimitiveProcessor& primProc, bool hasPointSize, const GrPipeline& pipeline,
-        GrGpu* gpu) {
+bool GrProgramDesc::Build(GrProgramDesc* desc, const GrRenderTarget* renderTarget,
+                          const GrProgramInfo& programInfo, bool hasPointSize, GrGpu* gpu) {
     // The descriptor is used as a cache key. Thus when a field of the
     // descriptor will not affect program generation (because of the attribute
     // bindings in use or other descriptor field settings) it should be set
@@ -206,28 +205,30 @@
 
     GrProcessorKeyBuilder b(&desc->key());
 
-    primProc.getGLSLProcessorKey(shaderCaps, &b);
-    primProc.getAttributeKey(&b);
-    if (!gen_meta_key(primProc, shaderCaps, 0, &b)) {
+    programInfo.primProc().getGLSLProcessorKey(shaderCaps, &b);
+    programInfo.primProc().getAttributeKey(&b);
+    if (!gen_meta_key(programInfo.primProc(), shaderCaps, 0, &b)) {
         desc->key().reset();
         return false;
     }
-    GrProcessor::CustomFeatures processorFeatures = primProc.requestedFeatures();
 
-    for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
-        const GrFragmentProcessor& fp = pipeline.getFragmentProcessor(i);
-        if (!gen_frag_proc_and_meta_keys(primProc, fp, gpu, shaderCaps, &b)) {
+    // TODO: use programInfo.requestedFeatures here
+    GrProcessor::CustomFeatures processorFeatures = programInfo.primProc().requestedFeatures();
+
+    for (int i = 0; i < programInfo.pipeline().numFragmentProcessors(); ++i) {
+        const GrFragmentProcessor& fp = programInfo.pipeline().getFragmentProcessor(i);
+        if (!gen_frag_proc_and_meta_keys(programInfo.primProc(), fp, gpu, shaderCaps, &b)) {
             desc->key().reset();
             return false;
         }
         processorFeatures |= fp.requestedFeatures();
     }
 
-    const GrXferProcessor& xp = pipeline.getXferProcessor();
+    const GrXferProcessor& xp = programInfo.pipeline().getXferProcessor();
     const GrSurfaceOrigin* originIfDstTexture = nullptr;
     GrSurfaceOrigin origin;
-    if (pipeline.dstTextureProxy()) {
-        origin = pipeline.dstTextureProxy()->origin();
+    if (programInfo.pipeline().dstTextureProxy()) {
+        origin = programInfo.pipeline().dstTextureProxy()->origin();
         originIfDstTexture = &origin;
     }
     xp.getGLSLProcessorKey(shaderCaps, &b, originIfDstTexture);
@@ -238,7 +239,7 @@
     processorFeatures |= xp.requestedFeatures();
 
     if (processorFeatures & GrProcessor::CustomFeatures::kSampleLocations) {
-        SkASSERT(pipeline.isHWAntialiasState());
+        SkASSERT(programInfo.pipeline().isHWAntialiasState());
         b.add32(renderTarget->renderTargetPriv().getSamplePatternKey());
     }
 
@@ -249,17 +250,18 @@
 
     // make sure any padding in the header is zeroed.
     memset(header, 0, kHeaderSize);
-    header->fOutputSwizzle = pipeline.outputSwizzle().asKey();
-    header->fColorFragmentProcessorCnt = pipeline.numColorFragmentProcessors();
-    header->fCoverageFragmentProcessorCnt = pipeline.numCoverageFragmentProcessors();
+    header->fOutputSwizzle = programInfo.pipeline().outputSwizzle().asKey();
+    header->fColorFragmentProcessorCnt = programInfo.pipeline().numColorFragmentProcessors();
+    header->fCoverageFragmentProcessorCnt = programInfo.pipeline().numCoverageFragmentProcessors();
     // Fail if the client requested more processors than the key can fit.
-    if (header->fColorFragmentProcessorCnt != pipeline.numColorFragmentProcessors() ||
-        header->fCoverageFragmentProcessorCnt != pipeline.numCoverageFragmentProcessors()) {
+    if (header->fColorFragmentProcessorCnt != programInfo.pipeline().numColorFragmentProcessors() ||
+        header->fCoverageFragmentProcessorCnt !=
+                                         programInfo.pipeline().numCoverageFragmentProcessors()) {
         return false;
     }
     header->fProcessorFeatures = (uint8_t)processorFeatures;
     SkASSERT(header->processorFeatures() == processorFeatures);  // Ensure enough bits.
-    header->fSnapVerticesToPixelCenters = pipeline.snapVerticesToPixelCenters();
+    header->fSnapVerticesToPixelCenters = programInfo.pipeline().snapVerticesToPixelCenters();
     header->fHasPointSize = hasPointSize ? 1 : 0;
     return true;
 }
diff --git a/src/gpu/GrProgramDesc.h b/src/gpu/GrProgramDesc.h
index 63cd46d..75c763c 100644
--- a/src/gpu/GrProgramDesc.h
+++ b/src/gpu/GrProgramDesc.h
@@ -15,9 +15,8 @@
 #include "src/gpu/GrColor.h"
 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
 
+class GrProgramInfo;
 class GrShaderCaps;
-class GrPipeline;
-class GrPrimitiveProcessor;
 
 /** This class describes a program to generate. It also serves as a program cache key */
 class GrProgramDesc {
@@ -29,18 +28,14 @@
     * Builds a program descriptor. Before the descriptor can be used, the client must call finalize
     * on the returned GrProgramDesc.
     *
-    * @param GrPrimitiveProcessor The geometry
+    * @param desc         The built and finalized descriptor
+    * @param renderTarget The target of the draw
+    * @param programInfo  Program information need to build the key
     * @param hasPointSize Controls whether the shader will output a point size.
-    * @param GrPipeline  The optimized drawstate.  The descriptor will represent a program
-    *                        which this optstate can use to draw with.  The optstate contains
-    *                        general draw information, as well as the specific color, geometry,
-    *                        and coverage stages which will be used to generate the GL Program for
-    *                        this optstate.
-    * @param GrGpu          Ptr to the GrGpu object the program will be used with.
-    * @param GrProgramDesc  The built and finalized descriptor
+    * @param gpu          Pointer to the GrGpu object the program will be used with.
     **/
-    static bool Build(GrProgramDesc*, const GrRenderTarget*, const GrPrimitiveProcessor&,
-                      bool hasPointSize, const GrPipeline&, GrGpu*);
+    static bool Build(GrProgramDesc*, const GrRenderTarget*, const GrProgramInfo&,
+                      bool hasPointSize, GrGpu*);
 
     static bool BuildFromData(GrProgramDesc* desc, const void* keyData, size_t keyLength) {
         if (!SkTFitsIn<int>(keyLength)) {
diff --git a/src/gpu/GrProgramInfo.h b/src/gpu/GrProgramInfo.h
new file mode 100644
index 0000000..ec1d3b3
--- /dev/null
+++ b/src/gpu/GrProgramInfo.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrProgramInfo_DEFINED
+#define GrProgramInfo_DEFINED
+
+#include "include/gpu/GrTypes.h"
+#include "src/gpu/GrPipeline.h"
+#include "src/gpu/GrPrimitiveProcessor.h"
+
+class GrProgramInfo {
+public:
+    GrProgramInfo(int numSamples,
+                  GrSurfaceOrigin origin,
+                  const GrPipeline& pipeline,
+                  const GrPrimitiveProcessor& primProc,
+                  const GrPipeline::FixedDynamicState* fixedDynamicState,
+                  const GrPipeline::DynamicStateArrays* dynamicStateArrays)
+            : fNumSamples(numSamples)
+            , fOrigin(origin)
+            , fPipeline(pipeline)
+            , fPrimProc(primProc)
+            , fFixedDynamicState(fixedDynamicState)
+            , fDynamicStateArrays(dynamicStateArrays) {
+    }
+
+    int numSamples() const { return fNumSamples;  }
+    GrSurfaceOrigin origin() const { return fOrigin;  }
+    const GrPipeline& pipeline() const { return fPipeline; }
+    const GrPrimitiveProcessor& primProc() const { return fPrimProc; }
+    const GrPipeline::FixedDynamicState* fixedDynamicState() const { return fFixedDynamicState; }
+    const GrPipeline::DynamicStateArrays* dynamicStateArrays() const { return fDynamicStateArrays; }
+
+    // TODO: can this be removed?
+    const GrTextureProxy* const* primProcProxies() const {
+        const GrTextureProxy* const* primProcProxies = nullptr;
+        if (fDynamicStateArrays && fDynamicStateArrays->fPrimitiveProcessorTextures) {
+            primProcProxies = fDynamicStateArrays->fPrimitiveProcessorTextures;
+        } else if (fFixedDynamicState) {
+            primProcProxies = fFixedDynamicState->fPrimitiveProcessorTextures;
+        }
+
+        SkASSERT(SkToBool(primProcProxies) == SkToBool(fPrimProc.numTextureSamplers()));
+        return primProcProxies;
+    }
+
+    bool hasDynamicScissors() const {
+        return fPipeline.isScissorEnabled() &&
+               fDynamicStateArrays && fDynamicStateArrays->fScissorRects;
+    }
+
+    const SkIRect& dynamicScissor(int i) const {
+        SkASSERT(this->hasDynamicScissors());
+
+        return fDynamicStateArrays->fScissorRects[i];
+    }
+
+    bool hasFixedScissor() const { return fPipeline.isScissorEnabled() && fFixedDynamicState; }
+
+    const SkIRect& fixedScissor() const {
+        SkASSERT(this->hasFixedScissor());
+
+        return fFixedDynamicState->fScissorRect;
+    }
+
+    bool hasDynamicPrimProcTextures() const {
+        return fDynamicStateArrays && fDynamicStateArrays->fPrimitiveProcessorTextures;
+    }
+
+    const GrTextureProxy* const* dynamicPrimProcTextures(int i) const {
+        SkASSERT(this->hasDynamicPrimProcTextures());
+
+        return fDynamicStateArrays->fPrimitiveProcessorTextures +
+                                                                i * fPrimProc.numTextureSamplers();
+    }
+
+    bool hasFixedPrimProcTextures() const {
+        return fFixedDynamicState && fFixedDynamicState->fPrimitiveProcessorTextures;
+    }
+
+    const GrTextureProxy* const* fixedPrimProcTextures() const {
+        SkASSERT(this->hasFixedPrimProcTextures());
+
+        return fFixedDynamicState->fPrimitiveProcessorTextures;
+    }
+
+#ifdef SK_DEBUG
+    bool isNVPR() const {
+        return fPrimProc.isPathRendering() && !fPrimProc.willUseGeoShader() &&
+               !fPrimProc.numVertexAttributes() && !fPrimProc.numInstanceAttributes();
+    }
+
+    // TODO: calculate this once in the ctor and use more widely
+    GrProcessor::CustomFeatures requestedFeatures() const {
+        GrProcessor::CustomFeatures requestedFeatures = fPrimProc.requestedFeatures();
+        for (int i = 0; i < fPipeline.numFragmentProcessors(); ++i) {
+            requestedFeatures |= fPipeline.getFragmentProcessor(i).requestedFeatures();
+        }
+        requestedFeatures |= fPipeline.getXferProcessor().requestedFeatures();
+        return requestedFeatures;
+    }
+#endif
+
+private:
+    const int                             fNumSamples;
+    const GrSurfaceOrigin                 fOrigin;
+    const GrPipeline&                     fPipeline;
+    const GrPrimitiveProcessor&           fPrimProc;
+    const GrPipeline::FixedDynamicState*  fFixedDynamicState;
+    const GrPipeline::DynamicStateArrays* fDynamicStateArrays;
+};
+
+#endif
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.cpp b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
index b6ec180..c36075e 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
@@ -10,6 +10,7 @@
 #include "src/core/SkMakeUnique.h"
 #include "src/gpu/GrOpFlushState.h"
 #include "src/gpu/GrOpsRenderPass.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/ccpr/GrCCConicShader.h"
 #include "src/gpu/ccpr/GrCCCubicShader.h"
 #include "src/gpu/ccpr/GrCCQuadraticShader.h"
@@ -201,5 +202,14 @@
     GrPipeline::DynamicStateArrays dynamicStateArrays;
     dynamicStateArrays.fScissorRects = scissorRects;
     GrOpsRenderPass* renderPass = flushState->opsRenderPass();
-    renderPass->draw(*this, pipeline, nullptr, &dynamicStateArrays, meshes, meshCount, drawBounds);
+
+    GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
+                              flushState->drawOpArgs().origin(),
+                              pipeline,
+                              *this,
+                              nullptr,
+                              &dynamicStateArrays);
+
+
+    renderPass->draw(programInfo, meshes, meshCount, drawBounds);
 }
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index acc5613..4a7a136 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -141,8 +141,14 @@
                              baseInstance, enablePrimitiveRestart);
     mesh.setVertexData(resources.refVertexBuffer());
 
-    flushState->opsRenderPass()->draw(*this, pipeline, fixedDynamicState, nullptr, &mesh, 1,
-                                      bounds);
+    GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
+                              flushState->drawOpArgs().origin(),
+                              pipeline,
+                              *this,
+                              fixedDynamicState,
+                              nullptr);
+
+    flushState->opsRenderPass()->draw(programInfo, &mesh, 1, bounds);
 }
 
 void GrCCPathProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
diff --git a/src/gpu/ccpr/GrCCStroker.cpp b/src/gpu/ccpr/GrCCStroker.cpp
index 98b8038..c768991 100644
--- a/src/gpu/ccpr/GrCCStroker.cpp
+++ b/src/gpu/ccpr/GrCCStroker.cpp
@@ -11,6 +11,7 @@
 #include "src/core/SkPathPriv.h"
 #include "src/gpu/GrOnFlushResourceProvider.h"
 #include "src/gpu/GrOpsRenderPass.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/ccpr/GrCCCoverageProcessor.h"
 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
@@ -775,7 +776,15 @@
     SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count());
     GrPipeline::DynamicStateArrays dynamicStateArrays;
     dynamicStateArrays.fScissorRects = fScissorsBuffer.begin();
-    flushState->opsRenderPass()->draw(processor, pipeline, nullptr, &dynamicStateArrays,
+
+    GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
+                              flushState->drawOpArgs().origin(),
+                              pipeline,
+                              processor,
+                              nullptr,
+                              &dynamicStateArrays);
+
+    flushState->opsRenderPass()->draw(programInfo,
                                       fMeshesBuffer.begin(), fMeshesBuffer.count(),
                                       SkRect::Make(drawBounds));
     // Don't call reset(), as that also resets the reserve count.
diff --git a/src/gpu/ccpr/GrStencilAtlasOp.cpp b/src/gpu/ccpr/GrStencilAtlasOp.cpp
index 990cabc..2701ce4 100644
--- a/src/gpu/ccpr/GrStencilAtlasOp.cpp
+++ b/src/gpu/ccpr/GrStencilAtlasOp.cpp
@@ -10,6 +10,7 @@
 #include "include/private/GrRecordingContext.h"
 #include "src/gpu/GrOpFlushState.h"
 #include "src/gpu/GrOpsRenderPass.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/ccpr/GrCCPerFlushResources.h"
 #include "src/gpu/ccpr/GrSampleMaskProcessor.h"
@@ -147,6 +148,15 @@
     mesh.setInstanced(fResources->refStencilResolveBuffer(),
                       fEndStencilResolveInstance - fBaseStencilResolveInstance,
                       fBaseStencilResolveInstance, 4);
-    flushState->opsRenderPass()->draw(StencilResolveProcessor(), resolvePipeline, &scissorRectState,
-                                      nullptr, &mesh, 1, SkRect::Make(drawBoundsRect));
+
+    StencilResolveProcessor primProc;
+
+    GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
+                              flushState->drawOpArgs().origin(),
+                              resolvePipeline,
+                              primProc,
+                              &scissorRectState,
+                              nullptr);
+
+    flushState->opsRenderPass()->draw(programInfo, &mesh, 1, SkRect::Make(drawBoundsRect));
 }
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 0c1559c..bf2b81a 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -26,6 +26,7 @@
 #include "src/gpu/GrGpuResourcePriv.h"
 #include "src/gpu/GrMesh.h"
 #include "src/gpu/GrPipeline.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRenderTargetPriv.h"
 #include "src/gpu/GrShaderCaps.h"
 #include "src/gpu/GrSurfaceProxyPriv.h"
@@ -1658,28 +1659,11 @@
 }
 
 bool GrGLGpu::flushGLState(GrRenderTarget* renderTarget,
-                           int numSamples,
-                           GrSurfaceOrigin origin,
-                           const GrPrimitiveProcessor& primProc,
-                           const GrPipeline& pipeline,
-                           const GrPipeline::FixedDynamicState* fixedDynamicState,
-                           const GrPipeline::DynamicStateArrays* dynamicStateArrays,
-                           int dynamicStateArraysLength,
+                           const GrProgramInfo& programInfo,
                            bool willDrawPoints) {
-    const GrTextureProxy* const* primProcProxies = nullptr;
-    const GrTextureProxy* const* primProcProxiesToBind = nullptr;
-    if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
-        primProcProxies = dynamicStateArrays->fPrimitiveProcessorTextures;
-    } else if (fixedDynamicState && fixedDynamicState->fPrimitiveProcessorTextures) {
-        primProcProxies = fixedDynamicState->fPrimitiveProcessorTextures;
-        primProcProxiesToBind = fixedDynamicState->fPrimitiveProcessorTextures;
-    }
 
-    SkASSERT(SkToBool(primProcProxies) == SkToBool(primProc.numTextureSamplers()));
-
-    sk_sp<GrGLProgram> program(fProgramCache->refProgram(
-            this, renderTarget, numSamples, origin, primProc, primProcProxies, pipeline,
-            willDrawPoints));
+    sk_sp<GrGLProgram> program(fProgramCache->refProgram(this, renderTarget, programInfo,
+                                                         willDrawPoints));
     if (!program) {
         GrCapsDebugf(this->caps(), "Failed to create program!\n");
         return false;
@@ -1688,30 +1672,32 @@
     this->flushProgram(std::move(program));
 
     // Swizzle the blend to match what the shader will output.
-    this->flushBlendAndColorWrite(
-            pipeline.getXferProcessor().getBlendInfo(), pipeline.outputSwizzle());
+    this->flushBlendAndColorWrite(programInfo.pipeline().getXferProcessor().getBlendInfo(),
+                                  programInfo.pipeline().outputSwizzle());
 
-    fHWProgram->updateUniformsAndTextureBindings(renderTarget, origin,
-                                                 primProc, pipeline, primProcProxiesToBind);
+    fHWProgram->updateUniformsAndTextureBindings(renderTarget, programInfo);
 
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(renderTarget);
     GrStencilSettings stencil;
-    if (pipeline.isStencilEnabled()) {
+    if (programInfo.pipeline().isStencilEnabled()) {
         // TODO: attach stencil and create settings during render target flush.
         SkASSERT(glRT->renderTargetPriv().getStencilAttachment());
-        stencil.reset(*pipeline.getUserStencil(), pipeline.hasStencilClip(),
+        stencil.reset(*programInfo.pipeline().getUserStencil(),
+                      programInfo.pipeline().hasStencilClip(),
                       glRT->renderTargetPriv().numStencilBits());
     }
-    this->flushStencil(stencil, origin);
-    if (pipeline.isScissorEnabled()) {
+    this->flushStencil(stencil, programInfo.origin());
+    if (programInfo.pipeline().isScissorEnabled()) {
         static constexpr SkIRect kBogusScissor{0, 0, 1, 1};
-        GrScissorState state(fixedDynamicState ? fixedDynamicState->fScissorRect : kBogusScissor);
-        this->flushScissor(state, glRT->width(), glRT->height(), origin);
+        GrScissorState state(programInfo.fixedDynamicState() ? programInfo.fixedScissor()
+                                                             : kBogusScissor);
+        this->flushScissor(state, glRT->width(), glRT->height(), programInfo.origin());
     } else {
         this->disableScissor();
     }
-    this->flushWindowRectangles(pipeline.getWindowRectsState(), glRT, origin);
-    this->flushHWAAState(glRT, pipeline.isHWAntialiasState());
+    this->flushWindowRectangles(programInfo.pipeline().getWindowRectsState(),
+                                glRT, programInfo.origin());
+    this->flushHWAAState(glRT, programInfo.pipeline().isHWAntialiasState());
 
     // This must come after textures are flushed because a texture may need
     // to be msaa-resolved (which will modify bound FBO state).
@@ -2180,15 +2166,16 @@
     #endif
 #endif
 
-void GrGLGpu::draw(GrRenderTarget* renderTarget, int numSamples, GrSurfaceOrigin origin,
-                   const GrPrimitiveProcessor& primProc,
-                   const GrPipeline& pipeline,
-                   const GrPipeline::FixedDynamicState* fixedDynamicState,
-                   const GrPipeline::DynamicStateArrays* dynamicStateArrays,
+void GrGLGpu::draw(GrRenderTarget* renderTarget,
+                   const GrProgramInfo& programInfo,
                    const GrMesh meshes[],
                    int meshCount) {
     this->handleDirtyContext();
 
+    if (meshCount == 0) {
+        return;
+    }
+
     bool hasPoints = false;
     for (int i = 0; i < meshCount; ++i) {
         if (meshes[i].primitiveType() == GrPrimitiveType::kPoints) {
@@ -2196,32 +2183,28 @@
             break;
         }
     }
-    if (!this->flushGLState(renderTarget, numSamples, origin, primProc, pipeline, fixedDynamicState,
-                            dynamicStateArrays, meshCount, hasPoints)) {
+    if (!this->flushGLState(renderTarget, programInfo, hasPoints)) {
         return;
     }
 
-    bool dynamicScissor = false;
-    bool dynamicPrimProcTextures = false;
-    if (dynamicStateArrays) {
-        dynamicScissor = pipeline.isScissorEnabled() && dynamicStateArrays->fScissorRects;
-        dynamicPrimProcTextures = dynamicStateArrays->fPrimitiveProcessorTextures;
-    }
+    bool hasDynamicScissors = programInfo.hasDynamicScissors();
+    bool hasDynamicPrimProcTextures = programInfo.hasDynamicPrimProcTextures();
+
     for (int m = 0; m < meshCount; ++m) {
-        if (GrXferBarrierType barrierType = pipeline.xferBarrierType(renderTarget->asTexture(),
-                                                                     *this->caps())) {
+        if (auto barrierType = programInfo.pipeline().xferBarrierType(renderTarget->asTexture(),
+                                                                      *this->caps())) {
             this->xferBarrier(renderTarget, barrierType);
         }
 
-        if (dynamicScissor) {
+        if (hasDynamicScissors) {
             GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(renderTarget);
-            this->flushScissor(GrScissorState(dynamicStateArrays->fScissorRects[m]),
-                               glRT->width(), glRT->height(), origin);
+            this->flushScissor(GrScissorState(programInfo.dynamicScissor(m)),
+                               glRT->width(), glRT->height(), programInfo.origin());
         }
-        if (dynamicPrimProcTextures) {
-            auto texProxyArray = dynamicStateArrays->fPrimitiveProcessorTextures +
-                                 m * primProc.numTextureSamplers();
-            fHWProgram->updatePrimitiveProcessorTextureBindings(primProc, texProxyArray);
+        if (hasDynamicPrimProcTextures) {
+            auto texProxyArray = programInfo.dynamicPrimProcTextures(m);
+            fHWProgram->updatePrimitiveProcessorTextureBindings(programInfo.primProc(),
+                                                                texProxyArray);
         }
         if (this->glCaps().requiresCullFaceEnableDisableWhenDrawingLinesAfterNonLines() &&
             GrIsPrimTypeLines(meshes[m].primitiveType()) &&
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 8fba42a..8633c32 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -75,13 +75,7 @@
     // The GrGLOpsRenderPass does not buffer up draws before submitting them to the gpu.
     // Thus this is the implementation of the draw call for the corresponding passthrough function
     // on GrGLOpsRenderPass.
-    void draw(GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-              const GrPrimitiveProcessor&,
-              const GrPipeline&,
-              const GrPipeline::FixedDynamicState*,
-              const GrPipeline::DynamicStateArrays*,
-              const GrMesh[],
-              int meshCount);
+    void draw(GrRenderTarget*, const GrProgramInfo&, const GrMesh[], int meshCount);
 
     // GrMesh::SendToGpuImpl methods. These issue the actual GL draw calls.
     // Marked final as a hint to the compiler to not use virtual dispatch.
@@ -280,10 +274,7 @@
     // willDrawPoints must be true if point primitives will be rendered after setting the GL state.
     // If DynamicStateArrays is not null then dynamicStateArraysLength is the number of dynamic
     // state entries in each array.
-    bool flushGLState(GrRenderTarget*, int numSamples, GrSurfaceOrigin, const GrPrimitiveProcessor&,
-                      const GrPipeline&, const GrPipeline::FixedDynamicState*,
-                      const GrPipeline::DynamicStateArrays*, int dynamicStateArraysLength,
-                      bool willDrawPoints);
+    bool flushGLState(GrRenderTarget*, const GrProgramInfo&, bool willDrawPoints);
 
     void flushProgram(sk_sp<GrGLProgram>);
 
@@ -321,10 +312,7 @@
 
         void abandon();
         void reset();
-        GrGLProgram* refProgram(GrGLGpu*, GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                                const GrPrimitiveProcessor&,
-                                const GrTextureProxy* const primProcProxies[],
-                                const GrPipeline&, bool hasPointSize);
+        GrGLProgram* refProgram(GrGLGpu*, GrRenderTarget*, const GrProgramInfo&, bool hasPointSize);
         bool precompileShader(const SkData& key, const SkData& data);
 
     private:
diff --git a/src/gpu/gl/GrGLGpuProgramCache.cpp b/src/gpu/gl/GrGLGpuProgramCache.cpp
index 484048a..61ac321 100644
--- a/src/gpu/gl/GrGLGpuProgramCache.cpp
+++ b/src/gpu/gl/GrGLGpuProgramCache.cpp
@@ -47,30 +47,29 @@
 
 GrGLProgram* GrGLGpu::ProgramCache::refProgram(GrGLGpu* gpu,
                                                GrRenderTarget* renderTarget,
-                                               int numSamples,
-                                               GrSurfaceOrigin origin,
-                                               const GrPrimitiveProcessor& primProc,
-                                               const GrTextureProxy* const primProcProxies[],
-                                               const GrPipeline& pipeline,
+                                               const GrProgramInfo& programInfo,
                                                bool isPoints) {
+
+
+    // TODO: can this be unified between GL and Vk?
     // Get GrGLProgramDesc
     GrProgramDesc desc;
-    if (!GrProgramDesc::Build(&desc, renderTarget, primProc, isPoints, pipeline, gpu)) {
+    if (!GrProgramDesc::Build(&desc, renderTarget, programInfo, isPoints, gpu)) {
         GrCapsDebugf(gpu->caps(), "Failed to gl program descriptor!\n");
         return nullptr;
     }
     // If we knew the shader won't depend on origin, we could skip this (and use the same program
     // for both origins). Instrumenting all fragment processors would be difficult and error prone.
-    desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
+    desc.setSurfaceOriginKey(
+            GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(programInfo.origin()));
 
     std::unique_ptr<Entry>* entry = fMap.find(desc);
     if (entry && !(*entry)->fProgram) {
         // We've pre-compiled the GL program, but don't have the GrGLProgram scaffolding
         const GrGLPrecompiledProgram* precompiledProgram = &((*entry)->fPrecompiledProgram);
         SkASSERT(precompiledProgram->fProgramID != 0);
-        GrGLProgram* program = GrGLProgramBuilder::CreateProgram(renderTarget, numSamples, origin,
-                                                                 primProc, primProcProxies,
-                                                                 pipeline, &desc, fGpu,
+        GrGLProgram* program = GrGLProgramBuilder::CreateProgram(renderTarget, programInfo,
+                                                                 &desc, fGpu,
                                                                  precompiledProgram);
         if (nullptr == program) {
             // Should we purge the program ID from the cache at this point?
@@ -80,9 +79,8 @@
         (*entry)->fProgram.reset(program);
     } else if (!entry) {
         // We have a cache miss
-        GrGLProgram* program = GrGLProgramBuilder::CreateProgram(renderTarget, numSamples, origin,
-                                                                 primProc, primProcProxies,
-                                                                 pipeline, &desc, fGpu);
+        GrGLProgram* program = GrGLProgramBuilder::CreateProgram(renderTarget, programInfo,
+                                                                 &desc, fGpu);
         if (nullptr == program) {
             return nullptr;
         }
diff --git a/src/gpu/gl/GrGLOpsRenderPass.h b/src/gpu/gl/GrGLOpsRenderPass.h
index cd7e5a0..fc04883 100644
--- a/src/gpu/gl/GrGLOpsRenderPass.h
+++ b/src/gpu/gl/GrGLOpsRenderPass.h
@@ -52,15 +52,9 @@
 private:
     GrGpu* gpu() override { return fGpu; }
 
-    void onDraw(const GrPrimitiveProcessor& primProc,
-                const GrPipeline& pipeline,
-                const GrPipeline::FixedDynamicState* fixedDynamicState,
-                const GrPipeline::DynamicStateArrays* dynamicStateArrays,
-                const GrMesh mesh[],
-                int meshCount,
+    void onDraw(const GrProgramInfo& programInfo, const GrMesh mesh[], int meshCount,
                 const SkRect& bounds) override {
-        fGpu->draw(fRenderTarget, fRenderTarget->numSamples(), fOrigin, primProc, pipeline,
-                   fixedDynamicState, dynamicStateArrays, mesh, meshCount);
+        fGpu->draw(fRenderTarget, programInfo, mesh, meshCount);
     }
 
     void onClear(const GrFixedClip& clip, const SkPMColor4f& color) override {
diff --git a/src/gpu/gl/GrGLPathRendering.cpp b/src/gpu/gl/GrGLPathRendering.cpp
index 9dc4437..327c8d2 100644
--- a/src/gpu/gl/GrGLPathRendering.cpp
+++ b/src/gpu/gl/GrGLPathRendering.cpp
@@ -112,14 +112,10 @@
 }
 
 void GrGLPathRendering::onDrawPath(GrRenderTarget* renderTarget,
-                                   int numSamples, GrSurfaceOrigin origin,
-                                   const GrPrimitiveProcessor& primProc,
-                                   const GrPipeline& pipeline,
-                                   const GrPipeline::FixedDynamicState& fixedDynamicState,
+                                   const GrProgramInfo& programInfo,
                                    const GrStencilSettings& stencilPassSettings,
                                    const GrPath* path) {
-    if (!this->gpu()->flushGLState(renderTarget, numSamples, origin, primProc, pipeline,
-                                   &fixedDynamicState, nullptr, 1, false)) {
+    if (!this->gpu()->flushGLState(renderTarget, programInfo, 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 167aa402..14284b4 100644
--- a/src/gpu/gl/GrGLPathRendering.h
+++ b/src/gpu/gl/GrGLPathRendering.h
@@ -65,11 +65,7 @@
 
 protected:
     void onStencilPath(const StencilPathArgs&, const GrPath*) override;
-    void onDrawPath(GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                    const GrPrimitiveProcessor&,
-                    const GrPipeline&,
-                    const GrPipeline::FixedDynamicState&,
-                    const GrStencilSettings&,
+    void onDrawPath(GrRenderTarget*, const GrProgramInfo&, const GrStencilSettings&,
                     const GrPath*) override;
 
 private:
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index e96ffa3..74cf539 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -10,6 +10,7 @@
 #include "src/gpu/GrPathProcessor.h"
 #include "src/gpu/GrPipeline.h"
 #include "src/gpu/GrProcessor.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrTexturePriv.h"
 #include "src/gpu/GrXferProcessor.h"
 #include "src/gpu/gl/GrGLBuffer.h"
@@ -73,11 +74,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void GrGLProgram::updateUniformsAndTextureBindings(const GrRenderTarget* renderTarget,
-                                                   GrSurfaceOrigin origin,
-                                                   const GrPrimitiveProcessor& primProc,
-                                                   const GrPipeline& pipeline,
-                                                   const GrTextureProxy* const primProcTextures[]) {
-    this->setRenderTargetState(renderTarget, origin, primProc);
+                                                  const GrProgramInfo& programInfo) {
+
+    this->setRenderTargetState(renderTarget, programInfo.origin(), programInfo.primProc());
 
     // we set the textures, and uniforms for installed processors in a generic way, but subclasses
     // of GLProgram determine how to set coord transforms
@@ -85,23 +84,24 @@
     // We must bind to texture units in the same order in which we set the uniforms in
     // GrGLProgramDataManager. That is, we bind textures for processors in this order:
     // primProc, fragProcs, XP.
-    fPrimitiveProcessor->setData(fProgramDataManager, primProc,
-                                 GrFragmentProcessor::CoordTransformIter(pipeline));
-    if (primProcTextures) {
-        this->updatePrimitiveProcessorTextureBindings(primProc, primProcTextures);
+    fPrimitiveProcessor->setData(fProgramDataManager, programInfo.primProc(),
+                                 GrFragmentProcessor::CoordTransformIter(programInfo.pipeline()));
+    if (programInfo.hasFixedPrimProcTextures()) {
+        this->updatePrimitiveProcessorTextureBindings(programInfo.primProc(),
+                                                      programInfo.fixedPrimProcTextures());
     }
-    int nextTexSamplerIdx = primProc.numTextureSamplers();
+    int nextTexSamplerIdx = programInfo.primProc().numTextureSamplers();
 
-    this->setFragmentData(pipeline, &nextTexSamplerIdx);
+    this->setFragmentData(programInfo.pipeline(), &nextTexSamplerIdx);
 
-    const GrXferProcessor& xp = pipeline.getXferProcessor();
+    const GrXferProcessor& xp = programInfo.pipeline().getXferProcessor();
     SkIPoint offset;
-    GrTexture* dstTexture = pipeline.peekDstTexture(&offset);
+    GrTexture* dstTexture = programInfo.pipeline().peekDstTexture(&offset);
 
     fXferProcessor->setData(fProgramDataManager, xp, dstTexture, offset);
     if (dstTexture) {
         fGpu->bindTexture(nextTexSamplerIdx++, GrSamplerState::ClampNearest(),
-                          pipeline.dstTextureProxy()->textureSwizzle(),
+                          programInfo.pipeline().dstTextureProxy()->textureSwizzle(),
                           static_cast<GrGLTexture*>(dstTexture));
     }
     SkASSERT(nextTexSamplerIdx == fNumTextureSamplers);
diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h
index b28bedb..cf2e2aa 100644
--- a/src/gpu/gl/GrGLProgram.h
+++ b/src/gpu/gl/GrGLProgram.h
@@ -18,6 +18,7 @@
 class GrGLSLXferProcessor;
 class GrPipeline;
 class GrPrimitiveProcessor;
+class GrProgramInfo;
 class GrRenderTarget;
 class GrTextureProxy;
 
@@ -118,9 +119,7 @@
      *
      * It is the caller's responsibility to ensure the program is bound before calling.
      */
-    void updateUniformsAndTextureBindings(const GrRenderTarget*, GrSurfaceOrigin,
-                                          const GrPrimitiveProcessor&, const GrPipeline&,
-                                          const GrTextureProxy* const primitiveProcessorTextures[]);
+    void updateUniformsAndTextureBindings(const GrRenderTarget*, const GrProgramInfo&);
 
     void updatePrimitiveProcessorTextureBindings(const GrPrimitiveProcessor&,
                                                  const GrTextureProxy* const[]);
diff --git a/src/gpu/gl/GrGLVaryingHandler.cpp b/src/gpu/gl/GrGLVaryingHandler.cpp
index 8f48a21..76ab128 100644
--- a/src/gpu/gl/GrGLVaryingHandler.cpp
+++ b/src/gpu/gl/GrGLVaryingHandler.cpp
@@ -18,10 +18,7 @@
     GrGLProgramBuilder* glPB = (GrGLProgramBuilder*) fProgramBuilder;
     // This call is not used for non-NVPR backends.
     SkASSERT(glPB->gpu()->glCaps().shaderCaps()->pathRenderingSupport() &&
-             glPB->fPrimProc.isPathRendering() &&
-             !glPB->fPrimProc.willUseGeoShader() &&
-             !glPB->fPrimProc.numVertexAttributes() &&
-             !glPB->fPrimProc.numInstanceAttributes());
+             fProgramBuilder->fProgramInfo.isNVPR());
 #endif
     this->addVarying(name, v);
     auto varyingInfo = fPathProcVaryingInfos.push_back();
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
index 507d210..07ea39c 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
@@ -46,23 +46,18 @@
 }
 
 GrGLProgram* GrGLProgramBuilder::CreateProgram(GrRenderTarget* renderTarget,
-                                               int numSamples,
-                                               GrSurfaceOrigin origin,
-                                               const GrPrimitiveProcessor& primProc,
-                                               const GrTextureProxy* const primProcProxies[],
-                                               const GrPipeline& pipeline,
+                                               const GrProgramInfo& programInfo,
                                                GrProgramDesc* desc,
                                                GrGLGpu* gpu,
                                                const GrGLPrecompiledProgram* precompiledProgram) {
-    SkASSERT(!pipeline.isBad());
+    SkASSERT(!programInfo.pipeline().isBad());
 
     ATRACE_ANDROID_FRAMEWORK("Shader Compile");
     GrAutoLocaleSetter als("C");
 
     // create a builder.  This will be handed off to effects so they can use it to add
     // uniforms, varyings, textures, etc
-    GrGLProgramBuilder builder(gpu, renderTarget, numSamples, origin,
-                               pipeline, primProc, primProcProxies, desc);
+    GrGLProgramBuilder builder(gpu, renderTarget, programInfo, desc);
 
     auto persistentCache = gpu->getContext()->priv().getPersistentCache();
     if (persistentCache && !precompiledProgram) {
@@ -82,13 +77,9 @@
 
 GrGLProgramBuilder::GrGLProgramBuilder(GrGLGpu* gpu,
                                        GrRenderTarget* renderTarget,
-                                       int numSamples,
-                                       GrSurfaceOrigin origin,
-                                       const GrPipeline& pipeline,
-                                       const GrPrimitiveProcessor& primProc,
-                                       const GrTextureProxy* const primProcProxies[],
+                                       const GrProgramInfo& programInfo,
                                        GrProgramDesc* desc)
-        : INHERITED(renderTarget, numSamples, origin, primProc, primProcProxies, pipeline, desc)
+        : INHERITED(renderTarget, programInfo, desc)
         , fGpu(gpu)
         , fVaryingHandler(this)
         , fUniformHandler(this)
@@ -170,7 +161,7 @@
     if (!this->gpu()->getContext()->priv().getPersistentCache()) {
         return;
     }
-    sk_sp<SkData> key = SkData::MakeWithoutCopy(desc()->asKey(), desc()->keyLength());
+    sk_sp<SkData> key = SkData::MakeWithoutCopy(this->desc()->asKey(), this->desc()->keyLength());
     if (fGpu->glCaps().programBinarySupport()) {
         // binary cache
         GrGLsizei length = 0;
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.h b/src/gpu/gl/builders/GrGLProgramBuilder.h
index b0fa41f..800c64a 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.h
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.h
@@ -48,11 +48,7 @@
      * @return true if generation was successful.
      */
     static GrGLProgram* CreateProgram(GrRenderTarget*,
-                                      int numSamples,
-                                      GrSurfaceOrigin,
-                                      const GrPrimitiveProcessor&,
-                                      const GrTextureProxy* const primProcProxies[],
-                                      const GrPipeline&,
+                                      const GrProgramInfo&,
                                       GrProgramDesc*,
                                       GrGLGpu*,
                                       const GrGLPrecompiledProgram* = nullptr);
@@ -64,9 +60,7 @@
     GrGLGpu* gpu() const { return fGpu; }
 
 private:
-    GrGLProgramBuilder(GrGLGpu*, GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                       const GrPipeline&, const GrPrimitiveProcessor&,
-                       const GrTextureProxy* const primProcProxies[], GrProgramDesc*);
+    GrGLProgramBuilder(GrGLGpu*, GrRenderTarget*, const GrProgramInfo&, GrProgramDesc*);
 
     void addInputVars(const SkSL::Program::Inputs& inputs);
     bool compileAndAttachShaders(const SkSL::String& glsl,
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
index 25ac628..9621e14 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
@@ -21,22 +21,14 @@
 const int GrGLSLProgramBuilder::kVarsPerBlock = 8;
 
 GrGLSLProgramBuilder::GrGLSLProgramBuilder(GrRenderTarget* renderTarget,
-                                           int numSamples,
-                                           GrSurfaceOrigin origin,
-                                           const GrPrimitiveProcessor& primProc,
-                                           const GrTextureProxy* const primProcProxies[],
-                                           const GrPipeline& pipeline,
-                                           GrProgramDesc* desc)
+                                           const GrProgramInfo& programInfo,
+                                           const GrProgramDesc* desc)
         : fVS(this)
         , fGS(this)
         , fFS(this)
         , fStageIndex(-1)
         , fRenderTarget(renderTarget)
-        , fNumSamples(numSamples)
-        , fOrigin(origin)
-        , fPipeline(pipeline)
-        , fPrimProc(primProc)
-        , fPrimProcProxies(primProcProxies)
+        , fProgramInfo(programInfo)
         , fDesc(desc)
         , fGeometryProcessor(nullptr)
         , fXferProcessor(nullptr)
@@ -115,7 +107,7 @@
                                            name.c_str());
     }
 
-    GrGLSLPrimitiveProcessor::FPCoordTransformHandler transformHandler(fPipeline,
+    GrGLSLPrimitiveProcessor::FPCoordTransformHandler transformHandler(this->pipeline(),
                                                                        &fTransformedCoordVars);
     GrGLSLGeometryProcessor::EmitArgs args(&fVS,
                                            proc.willUseGeoShader() ? &fGS : nullptr,
@@ -229,7 +221,7 @@
     AutoStageAdvance adv(this);
 
     SkASSERT(!fXferProcessor);
-    const GrXferProcessor& xp = fPipeline.getXferProcessor();
+    const GrXferProcessor& xp = this->pipeline().getXferProcessor();
     fXferProcessor.reset(xp.createGLSLInstance());
 
     // Enable dual source secondary output if we have one
@@ -248,13 +240,13 @@
     SamplerHandle dstTextureSamplerHandle;
     GrSurfaceOrigin dstTextureOrigin = kTopLeft_GrSurfaceOrigin;
 
-    if (GrTexture* dstTexture = fPipeline.peekDstTexture()) {
+    if (GrTexture* dstTexture = this->pipeline().peekDstTexture()) {
         // GrProcessor::TextureSampler sampler(dstTexture);
-        SkASSERT(fPipeline.dstTextureProxy());
-        const GrSwizzle& swizzle = fPipeline.dstTextureProxy()->textureSwizzle();
+        SkASSERT(this->pipeline().dstTextureProxy());
+        const GrSwizzle& swizzle = this->pipeline().dstTextureProxy()->textureSwizzle();
         dstTextureSamplerHandle =
                 this->emitSampler(dstTexture, GrSamplerState(), swizzle, "DstTextureSampler");
-        dstTextureOrigin = fPipeline.dstTextureProxy()->origin();
+        dstTextureOrigin = this->pipeline().dstTextureProxy()->origin();
         SkASSERT(dstTexture->texturePriv().textureType() != GrTextureType::kExternal);
     }
 
@@ -270,7 +262,7 @@
                                        fFS.getSecondaryColorOutputName(),
                                        dstTextureSamplerHandle,
                                        dstTextureOrigin,
-                                       this->desc()->header().fOutputSwizzle);
+                                       this->header().fOutputSwizzle);
     fXferProcessor->emitCode(args);
 
     // We have to check that effects and the code they emit are consistent, ie if an effect
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.h b/src/gpu/glsl/GrGLSLProgramBuilder.h
index dec6d11..b9bc61b 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.h
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.h
@@ -11,6 +11,7 @@
 #include "src/gpu/GrCaps.h"
 #include "src/gpu/GrGeometryProcessor.h"
 #include "src/gpu/GrProgramDesc.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRenderTarget.h"
 #include "src/gpu/GrRenderTargetPriv.h"
 #include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
@@ -36,8 +37,13 @@
     virtual const GrCaps* caps() const = 0;
     const GrShaderCaps* shaderCaps() const { return this->caps()->shaderCaps(); }
 
-    const GrPrimitiveProcessor& primitiveProcessor() const { return fPrimProc; }
-    const GrTextureProxy* const* primProcProxies() const { return fPrimProcProxies; }
+    int numSamples() const { return fProgramInfo.numSamples(); }
+    GrSurfaceOrigin origin() const { return fProgramInfo.origin(); }
+    const GrPipeline& pipeline() const { return fProgramInfo.pipeline(); }
+    const GrPrimitiveProcessor& primitiveProcessor() const { return fProgramInfo.primProc(); }
+    const GrTextureProxy* const* primProcProxies() const { return fProgramInfo.primProcProxies(); }
+
+    // TODO: stop passing in the renderTarget for just the sampleLocations
     int effectiveSampleCnt() const {
         SkASSERT(GrProcessor::CustomFeatures::kSampleLocations & header().processorFeatures());
         return fRenderTarget->renderTargetPriv().getSampleLocations().count();
@@ -45,10 +51,8 @@
     const SkTArray<SkPoint>& getSampleLocations() const {
         return fRenderTarget->renderTargetPriv().getSampleLocations();
     }
-    int numSamples() const { return fNumSamples; }
-    GrSurfaceOrigin origin() const { return fOrigin; }
-    const GrPipeline& pipeline() const { return fPipeline; }
-    GrProgramDesc* desc() { return fDesc; }
+
+    const GrProgramDesc* desc() const { return fDesc; }
     const GrProgramDesc::KeyHeader& header() const { return fDesc->header(); }
 
     void appendUniformDecls(GrShaderFlags visibility, SkString*) const;
@@ -95,14 +99,10 @@
 
     int fStageIndex;
 
-    const GrRenderTarget*        fRenderTarget;
-    const int                    fNumSamples;
-    const GrSurfaceOrigin        fOrigin;
-    const GrPipeline&            fPipeline;
-    const GrPrimitiveProcessor&  fPrimProc;
-    const GrTextureProxy* const* fPrimProcProxies;
+    const GrRenderTarget*        fRenderTarget; // TODO: remove this
+    const GrProgramInfo&         fProgramInfo;
 
-    GrProgramDesc*               fDesc;
+    const GrProgramDesc*         fDesc;
 
     GrGLSLBuiltinUniformHandles  fUniformHandles;
 
@@ -112,13 +112,7 @@
     int fFragmentProcessorCnt;
 
 protected:
-    explicit GrGLSLProgramBuilder(GrRenderTarget* renderTarget,
-                                  int numSamples,
-                                  GrSurfaceOrigin origin,
-                                  const GrPrimitiveProcessor&,
-                                  const GrTextureProxy* const primProcProxies[],
-                                  const GrPipeline&,
-                                  GrProgramDesc*);
+    explicit GrGLSLProgramBuilder(GrRenderTarget*, const GrProgramInfo&, const GrProgramDesc*);
 
     void addFeature(GrShaderFlags shaders, uint32_t featureBit, const char* extensionName);
 
diff --git a/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp b/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp
index 733ea50..8edd4ba 100644
--- a/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLVertexGeoBuilder.cpp
@@ -14,7 +14,7 @@
 void GrGLSLVertexGeoBuilder::emitNormalizedSkPosition(SkString* out, const char* devPos,
                                                       const char* rtAdjustName,
                                                       GrSLType devPosType) {
-    if (this->getProgramBuilder()->desc()->header().fSnapVerticesToPixelCenters) {
+    if (this->getProgramBuilder()->header().fSnapVerticesToPixelCenters) {
         if (kFloat3_GrSLType == devPosType) {
             const char* p = devPos;
             out->appendf("{float2 _posTmp = float2(%s.x/%s.z, %s.y/%s.z);", p, p, p, p);
@@ -37,7 +37,7 @@
 void GrGLSLVertexBuilder::onFinalize() {
     // We could have the GrGeometryProcessor do this, but its just easier to have it performed
     // here. If we ever need to set variable pointsize, then we can reinvestigate.
-    if (this->getProgramBuilder()->desc()->header().fHasPointSize) {
+    if (this->getProgramBuilder()->header().fHasPointSize) {
         this->codeAppend("sk_PointSize = 1.0;");
     }
     fProgramBuilder->varyingHandler()->getVertexDecls(&this->inputs(), &this->outputs());
diff --git a/src/gpu/mock/GrMockOpsRenderPass.h b/src/gpu/mock/GrMockOpsRenderPass.h
index 020bdc3..659dfba 100644
--- a/src/gpu/mock/GrMockOpsRenderPass.h
+++ b/src/gpu/mock/GrMockOpsRenderPass.h
@@ -35,9 +35,8 @@
     int numDraws() const { return fNumDraws; }
 
 private:
-    void onDraw(const GrPrimitiveProcessor&, const GrPipeline&,
-                const GrPipeline::FixedDynamicState*, const GrPipeline::DynamicStateArrays*,
-                const GrMesh[], int meshCount, const SkRect& bounds) override {
+    void onDraw(const GrProgramInfo&, const GrMesh[], int meshCount,
+                const SkRect& bounds) override {
         this->markRenderTargetDirty();
         ++fNumDraws;
     }
diff --git a/src/gpu/mtl/GrMtlOpsRenderPass.h b/src/gpu/mtl/GrMtlOpsRenderPass.h
index 5e6f221..702bf29 100644
--- a/src/gpu/mtl/GrMtlOpsRenderPass.h
+++ b/src/gpu/mtl/GrMtlOpsRenderPass.h
@@ -44,16 +44,9 @@
 private:
     GrGpu* gpu() override { return fGpu; }
 
-    GrMtlPipelineState* prepareDrawState(
-            const GrPrimitiveProcessor& primProc,
-            const GrPipeline& pipeline,
-            const GrPipeline::FixedDynamicState* fixedDynamicState,
-            GrPrimitiveType primType);
+    GrMtlPipelineState* prepareDrawState(const GrProgramInfo&, GrPrimitiveType);
 
-    void onDraw(const GrPrimitiveProcessor& primProc,
-                const GrPipeline& pipeline,
-                const GrPipeline::FixedDynamicState* fixedDynamicState,
-                const GrPipeline::DynamicStateArrays* dynamicStateArrays,
+    void onDraw(const GrProgramInfo& programInfo,
                 const GrMesh mesh[],
                 int meshCount,
                 const SkRect& bounds) override;
diff --git a/src/gpu/mtl/GrMtlOpsRenderPass.mm b/src/gpu/mtl/GrMtlOpsRenderPass.mm
index b6823b1..13d7fc8 100644
--- a/src/gpu/mtl/GrMtlOpsRenderPass.mm
+++ b/src/gpu/mtl/GrMtlOpsRenderPass.mm
@@ -53,50 +53,34 @@
     fGpu->submitIndirectCommandBuffer(fRenderTarget, fOrigin, &iBounds);
 }
 
-GrMtlPipelineState* GrMtlOpsRenderPass::prepareDrawState(
-        const GrPrimitiveProcessor& primProc,
-        const GrPipeline& pipeline,
-        const GrPipeline::FixedDynamicState* fixedDynamicState,
-        GrPrimitiveType primType) {
+GrMtlPipelineState* GrMtlOpsRenderPass::prepareDrawState(const GrProgramInfo& programInfo,
+                                                         GrPrimitiveType primType) {
     // TODO: resolve textures and regenerate mipmaps as needed
 
-    const GrTextureProxy* const* primProcProxies = nullptr;
-    if (fixedDynamicState) {
-        primProcProxies = fixedDynamicState->fPrimitiveProcessorTextures;
-    }
-    SkASSERT(SkToBool(primProcProxies) == SkToBool(primProc.numTextureSamplers()));
-
     GrMtlPipelineState* pipelineState =
         fGpu->resourceProvider().findOrCreateCompatiblePipelineState(fRenderTarget,
-                                                                     fRenderTarget->numSamples(),
-                                                                     fOrigin,
-                                                                     pipeline,
-                                                                     primProc,
-                                                                     primProcProxies,
+                                                                     programInfo,
                                                                      primType);
     if (!pipelineState) {
         return nullptr;
     }
-    pipelineState->setData(fRenderTarget, fOrigin, primProc, pipeline, primProcProxies);
-    fCurrentVertexStride = primProc.vertexStride();
+
+    pipelineState->setData(fRenderTarget, programInfo);
+    fCurrentVertexStride = programInfo.primProc().vertexStride();
 
     return pipelineState;
 }
 
-void GrMtlOpsRenderPass::onDraw(const GrPrimitiveProcessor& primProc,
-                                     const GrPipeline& pipeline,
-                                     const GrPipeline::FixedDynamicState* fixedDynamicState,
-                                     const GrPipeline::DynamicStateArrays* dynamicStateArrays,
-                                     const GrMesh meshes[],
-                                     int meshCount,
-                                     const SkRect& bounds) {
+void GrMtlOpsRenderPass::onDraw(const GrProgramInfo& programInfo,
+                                const GrMesh meshes[],
+                                int meshCount,
+                                const SkRect& bounds) {
     if (!meshCount) {
         return;
     }
 
     GrPrimitiveType primitiveType = meshes[0].primitiveType();
-    GrMtlPipelineState* pipelineState = this->prepareDrawState(primProc, pipeline,
-                                                               fixedDynamicState, primitiveType);
+    GrMtlPipelineState* pipelineState = this->prepareDrawState(programInfo, primitiveType);
     if (!pipelineState) {
         return;
     }
@@ -107,21 +91,23 @@
     SkASSERT(fActiveRenderCmdEncoder);
 
     [fActiveRenderCmdEncoder setRenderPipelineState:pipelineState->mtlPipelineState()];
-    pipelineState->setDrawState(fActiveRenderCmdEncoder, pipeline.outputSwizzle(),
-                                pipeline.getXferProcessor());
+    pipelineState->setDrawState(fActiveRenderCmdEncoder,
+                                programInfo.pipeline().outputSwizzle(),
+                                programInfo.pipeline().getXferProcessor());
 
-    bool dynamicScissor =
-            pipeline.isScissorEnabled() && dynamicStateArrays && dynamicStateArrays->fScissorRects;
-    if (!pipeline.isScissorEnabled()) {
+    bool hasDynamicScissors = programInfo.hasDynamicScissors();
+
+    if (!programInfo.pipeline().isScissorEnabled()) {
         GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
                                                        fRenderTarget, fOrigin,
                                                        SkIRect::MakeWH(fRenderTarget->width(),
                                                                        fRenderTarget->height()));
-    } else if (!dynamicScissor) {
-        SkASSERT(fixedDynamicState);
+    } else if (!hasDynamicScissors) {
+        SkASSERT(programInfo.hasFixedScissor());
+
         GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
                                                        fRenderTarget, fOrigin,
-                                                       fixedDynamicState->fScissorRect);
+                                                       programInfo.fixedScissor());
     }
 
     for (int i = 0; i < meshCount; ++i) {
@@ -130,21 +116,21 @@
         if (mesh.primitiveType() != primitiveType) {
             SkDEBUGCODE(pipelineState = nullptr);
             primitiveType = mesh.primitiveType();
-            pipelineState = this->prepareDrawState(primProc, pipeline, fixedDynamicState,
-                                                   primitiveType);
+            pipelineState = this->prepareDrawState(programInfo, primitiveType);
             if (!pipelineState) {
                 return;
             }
 
             [fActiveRenderCmdEncoder setRenderPipelineState:pipelineState->mtlPipelineState()];
-            pipelineState->setDrawState(fActiveRenderCmdEncoder, pipeline.outputSwizzle(),
-                                        pipeline.getXferProcessor());
+            pipelineState->setDrawState(fActiveRenderCmdEncoder,
+                                        programInfo.pipeline().outputSwizzle(),
+                                        programInfo.pipeline().getXferProcessor());
         }
 
-        if (dynamicScissor) {
+        if (hasDynamicScissors) {
             GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder, fRenderTarget,
                                                            fOrigin,
-                                                           dynamicStateArrays->fScissorRects[i]);
+                                                           programInfo.dynamicScissor(i));
         }
 
         mesh.sendToGpu(this);
diff --git a/src/gpu/mtl/GrMtlPipelineState.h b/src/gpu/mtl/GrMtlPipelineState.h
index 4726fdb..e71aee2 100644
--- a/src/gpu/mtl/GrMtlPipelineState.h
+++ b/src/gpu/mtl/GrMtlPipelineState.h
@@ -46,9 +46,7 @@
 
     id<MTLRenderPipelineState> mtlPipelineState() { return fPipelineState; }
 
-    void setData(const GrRenderTarget*, GrSurfaceOrigin,
-                 const GrPrimitiveProcessor& primPRoc, const GrPipeline& pipeline,
-                 const GrTextureProxy* const primProcTextures[]);
+    void setData(const GrRenderTarget*, const GrProgramInfo&);
 
     void setDrawState(id<MTLRenderCommandEncoder>, const GrSwizzle& outputSwizzle,
                       const GrXferProcessor&);
diff --git a/src/gpu/mtl/GrMtlPipelineState.mm b/src/gpu/mtl/GrMtlPipelineState.mm
index 9fe2db2..96ecb18 100644
--- a/src/gpu/mtl/GrMtlPipelineState.mm
+++ b/src/gpu/mtl/GrMtlPipelineState.mm
@@ -58,23 +58,23 @@
 }
 
 void GrMtlPipelineState::setData(const GrRenderTarget* renderTarget,
-                                 GrSurfaceOrigin origin,
-                                 const GrPrimitiveProcessor& primProc,
-                                 const GrPipeline& pipeline,
-                                 const GrTextureProxy* const primProcTextures[]) {
-    SkASSERT(primProcTextures || !primProc.numTextureSamplers());
+                                 const GrProgramInfo& programInfo) {
+    SkASSERT(programInfo.primProcProxies() || !programInfo.primProc().numTextureSamplers());
 
-    this->setRenderTargetState(renderTarget, origin);
-    fGeometryProcessor->setData(fDataManager, primProc,
-                                GrFragmentProcessor::CoordTransformIter(pipeline));
+    // Note: the Metal backend currently only supports fixed primProc textures
+    const GrTextureProxy* const* primProcProxies = programInfo.primProcProxies();
+
+    this->setRenderTargetState(renderTarget, programInfo.origin());
+    fGeometryProcessor->setData(fDataManager, programInfo.primProc(),
+                                GrFragmentProcessor::CoordTransformIter(programInfo.pipeline()));
     fSamplerBindings.reset();
-    for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-        const auto& sampler = primProc.textureSampler(i);
-        auto texture = static_cast<GrMtlTexture*>(primProcTextures[i]->peekTexture());
+    for (int i = 0; i < programInfo.primProc().numTextureSamplers(); ++i) {
+        const auto& sampler = programInfo.primProc().textureSampler(i);
+        auto texture = static_cast<GrMtlTexture*>(primProcProxies[i]->peekTexture());
         fSamplerBindings.emplace_back(sampler.samplerState(), texture, fGpu);
     }
 
-    GrFragmentProcessor::Iter iter(pipeline);
+    GrFragmentProcessor::Iter iter(programInfo.pipeline());
     GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt);
     const GrFragmentProcessor* fp = iter.next();
     GrGLSLFragmentProcessor* glslFP = glslIter.next();
@@ -91,12 +91,13 @@
 
     {
         SkIPoint offset;
-        GrTexture* dstTexture = pipeline.peekDstTexture(&offset);
+        GrTexture* dstTexture = programInfo.pipeline().peekDstTexture(&offset);
 
-        fXferProcessor->setData(fDataManager, pipeline.getXferProcessor(), dstTexture, offset);
+        fXferProcessor->setData(fDataManager, programInfo.pipeline().getXferProcessor(),
+                                dstTexture, offset);
     }
 
-    if (GrTextureProxy* dstTextureProxy = pipeline.dstTextureProxy()) {
+    if (GrTextureProxy* dstTextureProxy = programInfo.pipeline().dstTextureProxy()) {
         fSamplerBindings.emplace_back(GrSamplerState::ClampNearest(),
                                       dstTextureProxy->peekTexture(),
                                       fGpu);
@@ -105,9 +106,10 @@
     SkASSERT(fNumSamplers == fSamplerBindings.count());
     fDataManager.resetDirtyBits();
 
-    if (pipeline.isStencilEnabled()) {
+    if (programInfo.pipeline().isStencilEnabled()) {
         SkASSERT(renderTarget->renderTargetPriv().getStencilAttachment());
-        fStencil.reset(*pipeline.getUserStencil(), pipeline.hasStencilClip(),
+        fStencil.reset(*programInfo.pipeline().getUserStencil(),
+                       programInfo.pipeline().hasStencilClip(),
                        renderTarget->renderTargetPriv().numStencilBits());
     }
 }
diff --git a/src/gpu/mtl/GrMtlPipelineStateBuilder.h b/src/gpu/mtl/GrMtlPipelineStateBuilder.h
index e44dae5b..fda1e65 100644
--- a/src/gpu/mtl/GrMtlPipelineStateBuilder.h
+++ b/src/gpu/mtl/GrMtlPipelineStateBuilder.h
@@ -17,6 +17,7 @@
 
 #import <Metal/Metal.h>
 
+class GrProgramInfo;
 class GrMtlGpu;
 class GrMtlPipelineState;
 
@@ -35,12 +36,8 @@
      */
     class Desc : public GrProgramDesc {
     public:
-        static bool Build(Desc*,
-                          GrRenderTarget*,
-                          const GrPrimitiveProcessor&,
-                          const GrPipeline&,
-                          GrPrimitiveType,
-                          GrMtlGpu* gpu);
+        static bool Build(Desc*, GrRenderTarget*,
+                          const GrProgramInfo&, GrPrimitiveType, GrMtlGpu* gpu);
 
         size_t shaderKeyLength() const { return fShaderKeyLength; }
 
@@ -59,23 +56,14 @@
      * @return true if generation was successful.
      */
     static GrMtlPipelineState* CreatePipelineState(GrMtlGpu*,
-                                                   GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                                                   const GrPrimitiveProcessor&,
-                                                   const GrTextureProxy* const primProcProxies[],
-                                                   const GrPipeline&,
+                                                   GrRenderTarget*,
+                                                   const GrProgramInfo&,
                                                    Desc*);
 
 private:
-    GrMtlPipelineStateBuilder(GrMtlGpu*, GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                              const GrPipeline&,
-                              const GrPrimitiveProcessor&,
-                              const GrTextureProxy* const primProcProxies[],
-                              GrProgramDesc*);
+    GrMtlPipelineStateBuilder(GrMtlGpu*, GrRenderTarget*, const GrProgramInfo&, GrProgramDesc*);
 
-    GrMtlPipelineState* finalize(GrRenderTarget* renderTarget,
-                                 const GrPrimitiveProcessor& primProc,
-                                 const GrPipeline& pipeline,
-                                 Desc*);
+    GrMtlPipelineState* finalize(GrRenderTarget*, const GrProgramInfo&, Desc*);
 
     const GrCaps* caps() const override;
 
diff --git a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
index 16d4b94..820eed6 100644
--- a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
+++ b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
@@ -22,31 +22,23 @@
 #error This file must be compiled with Arc. Use -fobjc-arc flag
 #endif
 
-GrMtlPipelineState* GrMtlPipelineStateBuilder::CreatePipelineState(
-        GrMtlGpu* gpu,
-        GrRenderTarget* renderTarget, int numSamples, GrSurfaceOrigin origin,
-        const GrPrimitiveProcessor& primProc,
-        const GrTextureProxy* const primProcProxies[],
-        const GrPipeline& pipeline,
-        Desc* desc) {
-    GrMtlPipelineStateBuilder builder(gpu, renderTarget, numSamples, origin, pipeline, primProc,
-                                      primProcProxies, desc);
+GrMtlPipelineState* GrMtlPipelineStateBuilder::CreatePipelineState(GrMtlGpu* gpu,
+                                                                   GrRenderTarget* renderTarget,
+                                                                   const GrProgramInfo& programInfo,
+                                                                   Desc* desc) {
+    GrMtlPipelineStateBuilder builder(gpu, renderTarget, programInfo, desc);
 
     if (!builder.emitAndInstallProcs()) {
         return nullptr;
     }
-    return builder.finalize(renderTarget, primProc, pipeline, desc);
+    return builder.finalize(renderTarget, programInfo, desc);
 }
 
 GrMtlPipelineStateBuilder::GrMtlPipelineStateBuilder(GrMtlGpu* gpu,
                                                      GrRenderTarget* renderTarget,
-                                                     int numSamples,
-                                                     GrSurfaceOrigin origin,
-                                                     const GrPipeline& pipeline,
-                                                     const GrPrimitiveProcessor& primProc,
-                                                     const GrTextureProxy* const primProcProxies[],
+                                                     const GrProgramInfo& programInfo,
                                                      GrProgramDesc* desc)
-    : INHERITED(renderTarget, numSamples, origin, primProc, primProcProxies, pipeline, desc)
+        : INHERITED(renderTarget, programInfo, desc)
         , fGpu(gpu)
         , fUniformHandler(this)
         , fVaryingHandler(this) {
@@ -348,8 +340,7 @@
 }
 
 GrMtlPipelineState* GrMtlPipelineStateBuilder::finalize(GrRenderTarget* renderTarget,
-                                                        const GrPrimitiveProcessor& primProc,
-                                                        const GrPipeline& pipeline,
+                                                        const GrProgramInfo& programInfo,
                                                         Desc* desc) {
     auto pipelineDescriptor = [MTLRenderPipelineDescriptor new];
 
@@ -397,9 +388,9 @@
 
     pipelineDescriptor.vertexFunction = vertexFunction;
     pipelineDescriptor.fragmentFunction = fragmentFunction;
-    pipelineDescriptor.vertexDescriptor = create_vertex_descriptor(primProc);
+    pipelineDescriptor.vertexDescriptor = create_vertex_descriptor(programInfo.primProc());
     pipelineDescriptor.colorAttachments[0] = create_color_attachment(renderTarget->config(),
-                                                                     pipeline);
+                                                                     programInfo.pipeline());
     pipelineDescriptor.sampleCount = renderTarget->numSamples();
     bool hasStencilAttachment = SkToBool(renderTarget->renderTargetPriv().getStencilAttachment());
     GrMtlCaps* mtlCaps = (GrMtlCaps*)this->caps();
@@ -455,12 +446,11 @@
 
 bool GrMtlPipelineStateBuilder::Desc::Build(Desc* desc,
                                             GrRenderTarget* renderTarget,
-                                            const GrPrimitiveProcessor& primProc,
-                                            const GrPipeline& pipeline,
+                                            const GrProgramInfo& programInfo,
                                             GrPrimitiveType primitiveType,
                                             GrMtlGpu* gpu) {
-    if (!INHERITED::Build(desc, renderTarget, primProc,
-                          GrPrimitiveType::kLines == primitiveType, pipeline, gpu)) {
+    if (!GrProgramDesc::Build(desc, renderTarget, programInfo,
+                              GrPrimitiveType::kPoints == primitiveType, gpu)) {
         return false;
     }
 
@@ -475,10 +465,10 @@
     bool hasStencilAttachment = SkToBool(renderTarget->renderTargetPriv().getStencilAttachment());
     b.add32(hasStencilAttachment ? gpu->mtlCaps().preferredStencilFormat().fInternalFormat
                                  : MTLPixelFormatInvalid);
-    b.add32((uint32_t)pipeline.isStencilEnabled());
+    b.add32((uint32_t)programInfo.pipeline().isStencilEnabled());
     // Stencil samples don't seem to be tracked in the MTLRenderPipeline
 
-    b.add32(pipeline.getBlendInfoKey());
+    b.add32(programInfo.pipeline().getBlendInfoKey());
 
     b.add32((uint32_t)primitiveType);
 
diff --git a/src/gpu/mtl/GrMtlResourceProvider.h b/src/gpu/mtl/GrMtlResourceProvider.h
index c1e88f8..c6ce35a 100644
--- a/src/gpu/mtl/GrMtlResourceProvider.h
+++ b/src/gpu/mtl/GrMtlResourceProvider.h
@@ -24,12 +24,9 @@
 public:
     GrMtlResourceProvider(GrMtlGpu* gpu);
 
-    GrMtlPipelineState* findOrCreateCompatiblePipelineState(
-        GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-        const GrPipeline&,
-        const GrPrimitiveProcessor&,
-        const GrTextureProxy* const primProcProxies[],
-        GrPrimitiveType);
+    GrMtlPipelineState* findOrCreateCompatiblePipelineState(GrRenderTarget*,
+                                                            const GrProgramInfo&,
+                                                            GrPrimitiveType);
 
     // Finds or creates a compatible MTLDepthStencilState based on the GrStencilSettings.
     GrMtlDepthStencil* findOrCreateCompatibleDepthStencilState(const GrStencilSettings&,
@@ -55,10 +52,7 @@
         ~PipelineStateCache();
 
         void release();
-        GrMtlPipelineState* refPipelineState(GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                                             const GrPrimitiveProcessor&,
-                                             const GrTextureProxy* const primProcProxies[],
-                                             const GrPipeline&,
+        GrMtlPipelineState* refPipelineState(GrRenderTarget*, const GrProgramInfo&,
                                              GrPrimitiveType);
 
     private:
diff --git a/src/gpu/mtl/GrMtlResourceProvider.mm b/src/gpu/mtl/GrMtlResourceProvider.mm
index 1c070b6..1074622 100644
--- a/src/gpu/mtl/GrMtlResourceProvider.mm
+++ b/src/gpu/mtl/GrMtlResourceProvider.mm
@@ -38,11 +38,10 @@
 }
 
 GrMtlPipelineState* GrMtlResourceProvider::findOrCreateCompatiblePipelineState(
-        GrRenderTarget* renderTarget, int numSamples, GrSurfaceOrigin origin,
-        const GrPipeline& pipeline, const GrPrimitiveProcessor& proc,
-        const GrTextureProxy* const primProcProxies[], GrPrimitiveType primType) {
-    return fPipelineStateCache->refPipelineState(renderTarget, numSamples, origin, proc,
-                                                 primProcProxies, pipeline, primType);
+        GrRenderTarget* renderTarget,
+        const GrProgramInfo& programInfo,
+        GrPrimitiveType primType) {
+    return fPipelineStateCache->refPipelineState(renderTarget, programInfo, primType);
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////
@@ -137,25 +136,23 @@
 
 GrMtlPipelineState* GrMtlResourceProvider::PipelineStateCache::refPipelineState(
         GrRenderTarget* renderTarget,
-        int numSamples,
-        GrSurfaceOrigin origin,
-        const GrPrimitiveProcessor& primProc,
-        const GrTextureProxy* const primProcProxies[],
-        const GrPipeline& pipeline,
+        const GrProgramInfo& programInfo,
         GrPrimitiveType primType) {
 #ifdef GR_PIPELINE_STATE_CACHE_STATS
     ++fTotalRequests;
 #endif
+
+    // TODO: unify GL, VK and Mtl
     // Get GrMtlProgramDesc
     GrMtlPipelineStateBuilder::Desc desc;
-    if (!GrMtlPipelineStateBuilder::Desc::Build(&desc, renderTarget, primProc, pipeline, primType,
-                                                fGpu)) {
+    if (!GrMtlPipelineStateBuilder::Desc::Build(&desc, renderTarget, programInfo, primType, fGpu)) {
         GrCapsDebugf(fGpu->caps(), "Failed to build mtl program descriptor!\n");
         return nullptr;
     }
     // If we knew the shader won't depend on origin, we could skip this (and use the same program
     // for both origins). Instrumenting all fragment processors would be difficult and error prone.
-    desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
+    desc.setSurfaceOriginKey(
+            GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(programInfo.origin()));
 
     std::unique_ptr<Entry>* entry = fMap.find(desc);
     if (!entry) {
@@ -163,8 +160,8 @@
         ++fCacheMisses;
 #endif
         GrMtlPipelineState* pipelineState(GrMtlPipelineStateBuilder::CreatePipelineState(
-            fGpu, renderTarget, numSamples, origin, primProc, primProcProxies, pipeline, &desc));
-        if (nullptr == pipelineState) {
+            fGpu, renderTarget, programInfo, &desc));
+        if (!pipelineState) {
             return nullptr;
         }
         entry = fMap.insert(desc, std::unique_ptr<Entry>(new Entry(fGpu, pipelineState)));
diff --git a/src/gpu/ops/GrDrawPathOp.cpp b/src/gpu/ops/GrDrawPathOp.cpp
index d7bb20d..d6700d3 100644
--- a/src/gpu/ops/GrDrawPathOp.cpp
+++ b/src/gpu/ops/GrDrawPathOp.cpp
@@ -9,6 +9,7 @@
 #include "include/private/SkTemplates.h"
 #include "src/gpu/GrAppliedClip.h"
 #include "src/gpu/GrMemoryPool.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrRenderTargetContext.h"
 #include "src/gpu/GrRenderTargetPriv.h"
@@ -92,13 +93,17 @@
                         std::move(appliedClip));
     sk_sp<GrPathProcessor> pathProc(GrPathProcessor::Create(this->color(), this->viewMatrix()));
 
+    GrProgramInfo programInfo(state->drawOpArgs().numSamples(),
+                              state->drawOpArgs().origin(),
+                              pipeline,
+                              *pathProc,
+                              &fixedDynamicState,
+                              nullptr);
+
     GrStencilSettings stencil;
     init_stencil_pass_settings(*state, this->fillType(), &stencil);
     state->gpu()->pathRendering()->drawPath(state->drawOpArgs().renderTarget(),
-                                            state->drawOpArgs().renderTarget()->numSamples(),
-                                            state->drawOpArgs().origin(),
-                                            *pathProc, pipeline, fixedDynamicState, stencil,
-                                            fPath.get());
+                                            programInfo, stencil, fPath.get());
 }
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/ops/GrFillRRectOp.cpp b/src/gpu/ops/GrFillRRectOp.cpp
index bbed9f2..c3f86a3 100644
--- a/src/gpu/ops/GrFillRRectOp.cpp
+++ b/src/gpu/ops/GrFillRRectOp.cpp
@@ -13,6 +13,7 @@
 #include "src/gpu/GrMemoryPool.h"
 #include "src/gpu/GrOpFlushState.h"
 #include "src/gpu/GrOpsRenderPass.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
 #include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
@@ -750,13 +751,19 @@
                                                                      std::move(fProcessors),
                                                                      std::move(clip));
 
+    GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
+                              flushState->drawOpArgs().origin(),
+                              *pipeline,
+                              *proc,
+                              fixedDynamicState,
+                              nullptr);
+
     GrMesh* mesh = flushState->allocator()->make<GrMesh>(GrPrimitiveType::kTriangles);
     mesh->setIndexedInstanced(
             std::move(fIndexBuffer), fIndexCount, std::move(fInstanceBuffer), fInstanceCount,
             fBaseInstance, GrPrimitiveRestart::kNo);
     mesh->setVertexData(std::move(fVertexBuffer));
-    flushState->opsRenderPass()->draw(
-            *proc, *pipeline, fixedDynamicState, nullptr, mesh, 1, this->bounds());
+    flushState->opsRenderPass()->draw(programInfo, mesh, 1, this->bounds());
     fIndexCount = 0;
 }
 
diff --git a/src/gpu/ops/GrStencilPathOp.h b/src/gpu/ops/GrStencilPathOp.h
index b46b29a..2d4f37f 100644
--- a/src/gpu/ops/GrStencilPathOp.h
+++ b/src/gpu/ops/GrStencilPathOp.h
@@ -10,6 +10,7 @@
 
 #include "src/gpu/GrPath.h"
 #include "src/gpu/GrPathRendering.h"
+#include "src/gpu/GrScissorState.h"
 #include "src/gpu/GrStencilSettings.h"
 #include "src/gpu/ops/GrOp.h"
 
diff --git a/src/gpu/vk/GrVkOpsRenderPass.cpp b/src/gpu/vk/GrVkOpsRenderPass.cpp
index c28e385..bd2c48e 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.cpp
+++ b/src/gpu/vk/GrVkOpsRenderPass.cpp
@@ -374,8 +374,7 @@
                           SkToBool(fCurrentSecondaryCommandBuffer));
 }
 
-void GrVkOpsRenderPass::inlineUpload(GrOpFlushState* state,
-                                          GrDeferredTextureUploadFn& upload) {
+void GrVkOpsRenderPass::inlineUpload(GrOpFlushState* state, GrDeferredTextureUploadFn& upload) {
     if (fCurrentSecondaryCommandBuffer) {
         fCurrentSecondaryCommandBuffer->end(fGpu);
         fGpu->submitSecondaryCommandBuffer(std::move(fCurrentSecondaryCommandBuffer));
@@ -428,10 +427,7 @@
 }
 
 GrVkPipelineState* GrVkOpsRenderPass::prepareDrawState(
-        const GrPrimitiveProcessor& primProc,
-        const GrPipeline& pipeline,
-        const GrPipeline::FixedDynamicState* fixedDynamicState,
-        const GrPipeline::DynamicStateArrays* dynamicStateArrays,
+        const GrProgramInfo& programInfo,
         GrPrimitiveType primitiveType,
         const SkIRect& renderPassScissorRect) {
     GrVkCommandBuffer* currentCB = this->currentCommandBuffer();
@@ -439,22 +435,9 @@
 
     VkRenderPass compatibleRenderPass = fCurrentRenderPass->vkRenderPass();
 
-    const GrTextureProxy* const* primProcProxies = nullptr;
-    if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
-        primProcProxies = dynamicStateArrays->fPrimitiveProcessorTextures;
-    } else if (fixedDynamicState) {
-        primProcProxies = fixedDynamicState->fPrimitiveProcessorTextures;
-    }
-
-    SkASSERT(SkToBool(primProcProxies) == SkToBool(primProc.numTextureSamplers()));
-
     GrVkPipelineState* pipelineState =
         fGpu->resourceProvider().findOrCreateCompatiblePipelineState(fRenderTarget,
-                                                                     fRenderTarget->numSamples(),
-                                                                     fOrigin,
-                                                                     pipeline,
-                                                                     primProc,
-                                                                     primProcProxies,
+                                                                     programInfo,
                                                                      primitiveType,
                                                                      compatibleRenderPass);
     if (!pipelineState) {
@@ -463,30 +446,38 @@
 
     pipelineState->bindPipeline(fGpu, currentCB);
 
-    pipelineState->setAndBindUniforms(fGpu, fRenderTarget, fOrigin, primProc, pipeline, currentCB);
+    // Both the 'programInfo' and this renderPass have an origin. Since they come from the
+    // same place (i.e., the target renderTargetProxy) that had best agree.
+    SkASSERT(programInfo.origin() == fOrigin);
+
+    // TODO: just pass in GrProgramInfo
+    pipelineState->setAndBindUniforms(fGpu, fRenderTarget, fOrigin,
+                                      programInfo.primProc(), programInfo.pipeline(), currentCB);
 
     // Check whether we need to bind textures between each GrMesh. If not we can bind them all now.
-    bool setTextures = !(dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures);
-    if (setTextures) {
-        pipelineState->setAndBindTextures(fGpu, primProc, pipeline, primProcProxies, currentCB);
+    if (!programInfo.hasDynamicPrimProcTextures()) {
+        // TODO: just pass in GrProgramInfo
+        pipelineState->setAndBindTextures(fGpu, programInfo.primProc(), programInfo.pipeline(),
+                                          programInfo.primProcProxies(), currentCB);
     }
 
-    if (!pipeline.isScissorEnabled()) {
+    if (!programInfo.pipeline().isScissorEnabled()) {
         GrVkPipeline::SetDynamicScissorRectState(fGpu, currentCB, fRenderTarget, fOrigin,
                                                  renderPassScissorRect);
-    } else if (!dynamicStateArrays || !dynamicStateArrays->fScissorRects) {
-        SkASSERT(fixedDynamicState);
+    } else if (!programInfo.hasDynamicScissors()) {
+        SkASSERT(programInfo.hasFixedScissor());
+
         SkIRect combinedScissorRect;
-        if (!combinedScissorRect.intersect(renderPassScissorRect,
-                                           fixedDynamicState->fScissorRect)) {
+        if (!combinedScissorRect.intersect(renderPassScissorRect, programInfo.fixedScissor())) {
             combinedScissorRect = SkIRect::MakeEmpty();
         }
         GrVkPipeline::SetDynamicScissorRectState(fGpu, currentCB, fRenderTarget, fOrigin,
                                                  combinedScissorRect);
     }
     GrVkPipeline::SetDynamicViewportState(fGpu, currentCB, fRenderTarget);
-    GrVkPipeline::SetDynamicBlendConstantState(fGpu, currentCB, pipeline.outputSwizzle(),
-                                               pipeline.getXferProcessor());
+    GrVkPipeline::SetDynamicBlendConstantState(fGpu, currentCB,
+                                               programInfo.pipeline().outputSwizzle(),
+                                               programInfo.pipeline().getXferProcessor());
 
     return pipelineState;
 }
@@ -500,41 +491,46 @@
 #endif
 
 
-void GrVkOpsRenderPass::onDraw(const GrPrimitiveProcessor& primProc,
-                                    const GrPipeline& pipeline,
-                                    const GrPipeline::FixedDynamicState* fixedDynamicState,
-                                    const GrPipeline::DynamicStateArrays* dynamicStateArrays,
-                                    const GrMesh meshes[],
-                                    int meshCount,
-                                    const SkRect& bounds) {
+void GrVkOpsRenderPass::onDraw(const GrProgramInfo& programInfo,
+                               const GrMesh meshes[], int meshCount,
+                               const SkRect& bounds) {
     if (!meshCount) {
         return;
     }
 
 #ifdef SK_DEBUG
-    if (dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures) {
-        for (int m = 0, i = 0; m < meshCount; ++m) {
-            for (int s = 0; s < primProc.numTextureSamplers(); ++s, ++i) {
-                auto texture = dynamicStateArrays->fPrimitiveProcessorTextures[i]->peekTexture();
+    if (programInfo.hasDynamicPrimProcTextures()) {
+        for (int m = 0; m < meshCount; ++m) {
+            auto dynamicPrimProcTextures = programInfo.dynamicPrimProcTextures(m);
+
+            for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
+                auto texture = dynamicPrimProcTextures[s]->peekTexture();
                 check_sampled_texture(texture, fRenderTarget, fGpu);
             }
         }
-    } else {
-        for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-            auto texture = fixedDynamicState->fPrimitiveProcessorTextures[i]->peekTexture();
+    } else if (programInfo.hasFixedPrimProcTextures()) {
+        auto fixedPrimProcTextures = programInfo.fixedPrimProcTextures();
+
+        for (int s = 0; s < programInfo.primProc().numTextureSamplers(); ++s) {
+            auto texture = fixedPrimProcTextures[s]->peekTexture();
             check_sampled_texture(texture, fRenderTarget, fGpu);
         }
     }
-    GrFragmentProcessor::Iter iter(pipeline);
+
+    GrFragmentProcessor::Iter iter(programInfo.pipeline());
     while (const GrFragmentProcessor* fp = iter.next()) {
         for (int i = 0; i < fp->numTextureSamplers(); ++i) {
             const GrFragmentProcessor::TextureSampler& sampler = fp->textureSampler(i);
             check_sampled_texture(sampler.peekTexture(), fRenderTarget, fGpu);
         }
     }
-    if (GrTexture* dstTexture = pipeline.peekDstTexture()) {
+    if (GrTexture* dstTexture = programInfo.pipeline().peekDstTexture()) {
         check_sampled_texture(dstTexture, fRenderTarget, fGpu);
     }
+
+    // Both the 'programInfo' and this renderPass have an origin. Since they come from the
+    // same place (i.e., the target renderTargetProxy) that had best agree.
+    SkASSERT(programInfo.origin() == fOrigin);
 #endif
 
     SkRect scissorRect = SkRect::Make(fBounds);
@@ -544,45 +540,41 @@
     }
 
     GrPrimitiveType primitiveType = meshes[0].primitiveType();
-    GrVkPipelineState* pipelineState = this->prepareDrawState(primProc, pipeline, fixedDynamicState,
-                                                              dynamicStateArrays, primitiveType,
+    GrVkPipelineState* pipelineState = this->prepareDrawState(programInfo, primitiveType,
                                                               renderPassScissorRect);
     if (!pipelineState) {
         return;
     }
 
-    bool dynamicScissor =
-            pipeline.isScissorEnabled() && dynamicStateArrays && dynamicStateArrays->fScissorRects;
-
-    bool dynamicTextures = dynamicStateArrays && dynamicStateArrays->fPrimitiveProcessorTextures;
+    bool hasDynamicScissors = programInfo.hasDynamicScissors();
+    bool hasDynamicTextures = programInfo.hasDynamicPrimProcTextures();
 
     for (int i = 0; i < meshCount; ++i) {
         const GrMesh& mesh = meshes[i];
         if (mesh.primitiveType() != primitiveType) {
             SkDEBUGCODE(pipelineState = nullptr);
             primitiveType = mesh.primitiveType();
-            pipelineState = this->prepareDrawState(primProc, pipeline, fixedDynamicState,
-                                                   dynamicStateArrays, primitiveType,
+            pipelineState = this->prepareDrawState(programInfo, primitiveType,
                                                    renderPassScissorRect);
             if (!pipelineState) {
                 return;
             }
         }
 
-        if (dynamicScissor) {
+        if (hasDynamicScissors) {
             SkIRect combinedScissorRect;
             if (!combinedScissorRect.intersect(renderPassScissorRect,
-                                               dynamicStateArrays->fScissorRects[i])) {
+                                               programInfo.dynamicScissor(i))) {
                 combinedScissorRect = SkIRect::MakeEmpty();
             }
             GrVkPipeline::SetDynamicScissorRectState(fGpu, this->currentCommandBuffer(),
                                                      fRenderTarget, fOrigin,
                                                      combinedScissorRect);
         }
-        if (dynamicTextures) {
-            GrTextureProxy* const* meshProxies = dynamicStateArrays->fPrimitiveProcessorTextures +
-                                                 primProc.numTextureSamplers() * i;
-            pipelineState->setAndBindTextures(fGpu, primProc, pipeline, meshProxies,
+        if (hasDynamicTextures) {
+            auto meshProxies = programInfo.dynamicPrimProcTextures(i);
+            pipelineState->setAndBindTextures(fGpu, programInfo.primProc(), programInfo.pipeline(),
+                                              meshProxies,
                                               this->currentCommandBuffer());
         }
         SkASSERT(pipelineState);
diff --git a/src/gpu/vk/GrVkOpsRenderPass.h b/src/gpu/vk/GrVkOpsRenderPass.h
index 9211288..65d6b5b 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.h
+++ b/src/gpu/vk/GrVkOpsRenderPass.h
@@ -70,19 +70,10 @@
                       const GrGpuBuffer* vertexBuffer,
                       const GrGpuBuffer* instanceBuffer);
 
-    GrVkPipelineState* prepareDrawState(const GrPrimitiveProcessor&,
-                                        const GrPipeline&,
-                                        const GrPipeline::FixedDynamicState*,
-                                        const GrPipeline::DynamicStateArrays*,
-                                        GrPrimitiveType,
+    GrVkPipelineState* prepareDrawState(const GrProgramInfo&, GrPrimitiveType,
                                         const SkIRect& renderPassScissorRect);
 
-    void onDraw(const GrPrimitiveProcessor&,
-                const GrPipeline&,
-                const GrPipeline::FixedDynamicState*,
-                const GrPipeline::DynamicStateArrays*,
-                const GrMesh[],
-                int meshCount,
+    void onDraw(const GrProgramInfo&, const GrMesh[], int meshCount,
                 const SkRect& bounds) override;
 
     // GrMesh::SendToGpuImpl methods. These issue the actual Vulkan draw commands.
diff --git a/src/gpu/vk/GrVkPipeline.cpp b/src/gpu/vk/GrVkPipeline.cpp
index 96e2ee5..6868ff5 100644
--- a/src/gpu/vk/GrVkPipeline.cpp
+++ b/src/gpu/vk/GrVkPipeline.cpp
@@ -278,17 +278,15 @@
     SkASSERT(viewportInfo->viewportCount == viewportInfo->scissorCount);
 }
 
-static void setup_multisample_state(int numColorSamples,
-                                    const GrPrimitiveProcessor& primProc,
-                                    const GrPipeline& pipeline,
+static void setup_multisample_state(const GrProgramInfo& programInfo,
                                     const GrCaps* caps,
                                     VkPipelineMultisampleStateCreateInfo* multisampleInfo) {
     memset(multisampleInfo, 0, sizeof(VkPipelineMultisampleStateCreateInfo));
     multisampleInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
     multisampleInfo->pNext = nullptr;
     multisampleInfo->flags = 0;
-    SkAssertResult(GrSampleCountToVkSampleCount(numColorSamples,
-                   &multisampleInfo->rasterizationSamples));
+    SkAssertResult(GrSampleCountToVkSampleCount(programInfo.numSamples(),
+                                                &multisampleInfo->rasterizationSamples));
     multisampleInfo->sampleShadingEnable = VK_FALSE;
     multisampleInfo->minSampleShading = 0.0f;
     multisampleInfo->pSampleMask = nullptr;
@@ -498,38 +496,40 @@
 }
 
 GrVkPipeline* GrVkPipeline::Create(
-        GrVkGpu* gpu, int numColorSamples, const GrPrimitiveProcessor& primProc,
-        const GrPipeline& pipeline, const GrStencilSettings& stencil, GrSurfaceOrigin origin,
+        GrVkGpu* gpu,
+        const GrProgramInfo& programInfo,
+        const GrStencilSettings& stencil,
         VkPipelineShaderStageCreateInfo* shaderStageInfo, int shaderStageCount,
         GrPrimitiveType primitiveType, VkRenderPass compatibleRenderPass, VkPipelineLayout layout,
         VkPipelineCache cache) {
     VkPipelineVertexInputStateCreateInfo vertexInputInfo;
     SkSTArray<2, VkVertexInputBindingDescription, true> bindingDescs;
     SkSTArray<16, VkVertexInputAttributeDescription> attributeDesc;
-    int totalAttributeCnt = primProc.numVertexAttributes() + primProc.numInstanceAttributes();
+    int totalAttributeCnt = programInfo.primProc().numVertexAttributes() +
+                            programInfo.primProc().numInstanceAttributes();
     SkASSERT(totalAttributeCnt <= gpu->vkCaps().maxVertexAttributes());
     VkVertexInputAttributeDescription* pAttribs = attributeDesc.push_back_n(totalAttributeCnt);
-    setup_vertex_input_state(primProc, &vertexInputInfo, &bindingDescs, pAttribs);
+    setup_vertex_input_state(programInfo.primProc(), &vertexInputInfo, &bindingDescs, pAttribs);
 
     VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo;
     setup_input_assembly_state(primitiveType, &inputAssemblyInfo);
 
     VkPipelineDepthStencilStateCreateInfo depthStencilInfo;
-    setup_depth_stencil_state(stencil, origin, &depthStencilInfo);
+    setup_depth_stencil_state(stencil, programInfo.origin(), &depthStencilInfo);
 
     VkPipelineViewportStateCreateInfo viewportInfo;
     setup_viewport_scissor_state(&viewportInfo);
 
     VkPipelineMultisampleStateCreateInfo multisampleInfo;
-    setup_multisample_state(numColorSamples, primProc, pipeline, gpu->caps(), &multisampleInfo);
+    setup_multisample_state(programInfo, gpu->caps(), &multisampleInfo);
 
     // We will only have one color attachment per pipeline.
     VkPipelineColorBlendAttachmentState attachmentStates[1];
     VkPipelineColorBlendStateCreateInfo colorBlendInfo;
-    setup_color_blend_state(pipeline, &colorBlendInfo, attachmentStates);
+    setup_color_blend_state(programInfo.pipeline(), &colorBlendInfo, attachmentStates);
 
     VkPipelineRasterizationStateCreateInfo rasterInfo;
-    setup_raster_state(pipeline, gpu->caps(), &rasterInfo);
+    setup_raster_state(programInfo.pipeline(), gpu->caps(), &rasterInfo);
 
     VkDynamicState dynamicStates[3];
     VkPipelineDynamicStateCreateInfo dynamicInfo;
diff --git a/src/gpu/vk/GrVkPipeline.h b/src/gpu/vk/GrVkPipeline.h
index 2911f5a4..ce33a49 100644
--- a/src/gpu/vk/GrVkPipeline.h
+++ b/src/gpu/vk/GrVkPipeline.h
@@ -25,11 +25,8 @@
 class GrVkPipeline : public GrVkResource {
 public:
     static GrVkPipeline* Create(GrVkGpu*,
-                                int numColorSamples,
-                                const GrPrimitiveProcessor&,
-                                const GrPipeline& pipeline,
+                                const GrProgramInfo&,
                                 const GrStencilSettings&,
-                                GrSurfaceOrigin,
                                 VkPipelineShaderStageCreateInfo* shaderStageInfo,
                                 int shaderStageCount,
                                 GrPrimitiveType primitiveType,
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
index cdfaf47..3398f3f 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
@@ -22,19 +22,14 @@
 GrVkPipelineState* GrVkPipelineStateBuilder::CreatePipelineState(
         GrVkGpu* gpu,
         GrRenderTarget* renderTarget,
-        int numSamples,
-        GrSurfaceOrigin origin,
-        const GrPrimitiveProcessor& primProc,
-        const GrTextureProxy* const primProcProxies[],
-        const GrPipeline& pipeline,
+        const GrProgramInfo& programInfo,
         const GrStencilSettings& stencil,
         GrPrimitiveType primitiveType,
         Desc* desc,
         VkRenderPass compatibleRenderPass) {
     // create a builder.  This will be handed off to effects so they can use it to add
     // uniforms, varyings, textures, etc
-    GrVkPipelineStateBuilder builder(gpu, renderTarget, numSamples, origin, pipeline, primProc,
-                                     primProcProxies, desc);
+    GrVkPipelineStateBuilder builder(gpu, renderTarget, programInfo, desc);
 
     if (!builder.emitAndInstallProcs()) {
         return nullptr;
@@ -45,13 +40,9 @@
 
 GrVkPipelineStateBuilder::GrVkPipelineStateBuilder(GrVkGpu* gpu,
                                                    GrRenderTarget* renderTarget,
-                                                   int numSamples,
-                                                   GrSurfaceOrigin origin,
-                                                   const GrPipeline& pipeline,
-                                                   const GrPrimitiveProcessor& primProc,
-                                                   const GrTextureProxy* const primProcProxies[],
+                                                   const GrProgramInfo& programInfo,
                                                    GrProgramDesc* desc)
-        : INHERITED(renderTarget, numSamples, origin, primProc, primProcProxies, pipeline, desc)
+        : INHERITED(renderTarget, programInfo, desc)
         , fGpu(gpu)
         , fVaryingHandler(this)
         , fUniformHandler(this) {}
@@ -142,7 +133,7 @@
 void GrVkPipelineStateBuilder::storeShadersInCache(const SkSL::String shaders[],
                                                    const SkSL::Program::Inputs inputs[],
                                                    bool isSkSL) {
-    Desc* desc = static_cast<Desc*>(this->desc());
+    const Desc* desc = static_cast<const Desc*>(this->desc());
     sk_sp<SkData> key = SkData::MakeWithoutCopy(desc->asKey(), desc->shaderKeyLength());
     sk_sp<SkData> data = GrPersistentCacheUtils::PackCachedShaders(isSkSL ? kSKSL_Tag : kSPIRV_Tag,
                                                                    shaders,
@@ -294,8 +285,7 @@
             this->storeShadersInCache(shaders, inputs, isSkSL);
         }
     }
-    GrVkPipeline* pipeline = resourceProvider.createPipeline(
-            fNumSamples, fPrimProc, fPipeline, stencil, this->origin(),
+        GrVkPipeline* pipeline = resourceProvider.createPipeline(fProgramInfo, stencil,
             shaderStageInfo, numShaderStages, primitiveType, compatibleRenderPass, pipelineLayout);
     for (int i = 0; i < kGrShaderTypeCount; ++i) {
         // This if check should not be needed since calling destroy on a VK_NULL_HANDLE is allowed.
@@ -329,13 +319,12 @@
 
 bool GrVkPipelineStateBuilder::Desc::Build(Desc* desc,
                                            GrRenderTarget* renderTarget,
-                                           const GrPrimitiveProcessor& primProc,
-                                           const GrPipeline& pipeline,
+                                           const GrProgramInfo& programInfo,
                                            const GrStencilSettings& stencil,
                                            GrPrimitiveType primitiveType,
                                            GrVkGpu* gpu) {
-    if (!INHERITED::Build(desc, renderTarget, primProc,
-                          primitiveType == GrPrimitiveType::kPoints, pipeline, gpu)) {
+    if (!GrProgramDesc::Build(desc, renderTarget, programInfo,
+                              primitiveType == GrPrimitiveType::kPoints, gpu)) {
         return false;
     }
 
@@ -351,7 +340,7 @@
 
     stencil.genKey(&b);
 
-    b.add32(pipeline.getBlendInfoKey());
+    b.add32(programInfo.pipeline().getBlendInfoKey());
 
     b.add32((uint32_t)primitiveType);
 
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.h b/src/gpu/vk/GrVkPipelineStateBuilder.h
index 769202d..0ba7fc5 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.h
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.h
@@ -40,8 +40,7 @@
     public:
         static bool Build(Desc*,
                           GrRenderTarget*,
-                          const GrPrimitiveProcessor&,
-                          const GrPipeline&,
+                          const GrProgramInfo&,
                           const GrStencilSettings&,
                           GrPrimitiveType primitiveType,
                           GrVkGpu* gpu);
@@ -64,11 +63,7 @@
     */
     static GrVkPipelineState* CreatePipelineState(GrVkGpu*,
                                                   GrRenderTarget*,
-                                                  int numSamples,
-                                                  GrSurfaceOrigin,
-                                                  const GrPrimitiveProcessor&,
-                                                  const GrTextureProxy* const primProcProxies[],
-                                                  const GrPipeline&,
+                                                  const GrProgramInfo&,
                                                   const GrStencilSettings&,
                                                   GrPrimitiveType,
                                                   Desc*,
@@ -82,13 +77,7 @@
     void finalizeFragmentSecondaryColor(GrShaderVar& outputColor) override;
 
 private:
-    GrVkPipelineStateBuilder(GrVkGpu*, GrRenderTarget*,
-                             int numSamples,
-                             GrSurfaceOrigin,
-                             const GrPipeline&,
-                             const GrPrimitiveProcessor&,
-                             const GrTextureProxy* const primProcProxies[],
-                             GrProgramDesc*);
+    GrVkPipelineStateBuilder(GrVkGpu*, GrRenderTarget*, const GrProgramInfo&, GrProgramDesc*);
 
     GrVkPipelineState* finalize(const GrStencilSettings&,
                                 GrPrimitiveType primitiveType,
diff --git a/src/gpu/vk/GrVkPipelineStateCache.cpp b/src/gpu/vk/GrVkPipelineStateCache.cpp
index 5449a45..fb4cfa8a 100644
--- a/src/gpu/vk/GrVkPipelineStateCache.cpp
+++ b/src/gpu/vk/GrVkPipelineStateCache.cpp
@@ -78,34 +78,33 @@
 
 GrVkPipelineState* GrVkResourceProvider::PipelineStateCache::refPipelineState(
         GrRenderTarget* renderTarget,
-        int numSamples,
-        GrSurfaceOrigin origin,
-        const GrPrimitiveProcessor& primProc,
-        const GrTextureProxy* const primProcProxies[],
-        const GrPipeline& pipeline,
+        const GrProgramInfo& programInfo,
         GrPrimitiveType primitiveType,
         VkRenderPass compatibleRenderPass) {
 #ifdef GR_PIPELINE_STATE_CACHE_STATS
     ++fTotalRequests;
 #endif
     GrStencilSettings stencil;
-    if (pipeline.isStencilEnabled()) {
+    if (programInfo.pipeline().isStencilEnabled()) {
         // TODO: attach stencil and create settings during render target flush.
         SkASSERT(renderTarget->renderTargetPriv().getStencilAttachment());
-        stencil.reset(*pipeline.getUserStencil(), pipeline.hasStencilClip(),
+        stencil.reset(*programInfo.pipeline().getUserStencil(),
+                      programInfo.pipeline().hasStencilClip(),
                       renderTarget->renderTargetPriv().numStencilBits());
     }
 
+    // TODO: can this be unified between Vulkan and GL?
     // Get GrVkProgramDesc
     GrVkPipelineStateBuilder::Desc desc;
-    if (!GrVkPipelineStateBuilder::Desc::Build(&desc, renderTarget, primProc, pipeline, stencil,
+    if (!GrVkPipelineStateBuilder::Desc::Build(&desc, renderTarget, programInfo, stencil,
                                                primitiveType, fGpu)) {
         GrCapsDebugf(fGpu->caps(), "Failed to build vk program descriptor!\n");
         return nullptr;
     }
     // If we knew the shader won't depend on origin, we could skip this (and use the same program
     // for both origins). Instrumenting all fragment processors would be difficult and error prone.
-    desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
+    desc.setSurfaceOriginKey(
+            GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(programInfo.origin()));
 
     std::unique_ptr<Entry>* entry = fMap.find(desc);
     if (!entry) {
@@ -113,9 +112,9 @@
         ++fCacheMisses;
 #endif
         GrVkPipelineState* pipelineState(GrVkPipelineStateBuilder::CreatePipelineState(
-                fGpu, renderTarget, numSamples, origin, primProc, primProcProxies, pipeline,
+                fGpu, renderTarget, programInfo,
                 stencil, primitiveType, &desc, compatibleRenderPass));
-        if (nullptr == pipelineState) {
+        if (!pipelineState) {
             return nullptr;
         }
         entry = fMap.insert(desc, std::unique_ptr<Entry>(new Entry(fGpu, pipelineState)));
diff --git a/src/gpu/vk/GrVkResourceProvider.cpp b/src/gpu/vk/GrVkResourceProvider.cpp
index 9a02cf3..5f21dbd 100644
--- a/src/gpu/vk/GrVkResourceProvider.cpp
+++ b/src/gpu/vk/GrVkResourceProvider.cpp
@@ -91,19 +91,16 @@
     fUniformDSHandle = GrVkDescriptorSetManager::Handle(0);
 }
 
-GrVkPipeline* GrVkResourceProvider::createPipeline(int numColorSamples,
-                                                   const GrPrimitiveProcessor& primProc,
-                                                   const GrPipeline& pipeline,
+GrVkPipeline* GrVkResourceProvider::createPipeline(const GrProgramInfo& programInfo,
                                                    const GrStencilSettings& stencil,
-                                                   GrSurfaceOrigin origin,
                                                    VkPipelineShaderStageCreateInfo* shaderStageInfo,
                                                    int shaderStageCount,
                                                    GrPrimitiveType primitiveType,
                                                    VkRenderPass compatibleRenderPass,
                                                    VkPipelineLayout layout) {
-    return GrVkPipeline::Create(
-            fGpu, numColorSamples, primProc, pipeline, stencil, origin, shaderStageInfo,
-            shaderStageCount, primitiveType, compatibleRenderPass, layout, this->pipelineCache());
+    return GrVkPipeline::Create(fGpu, programInfo, stencil, shaderStageInfo,
+                                shaderStageCount, primitiveType, compatibleRenderPass,
+                                layout, this->pipelineCache());
 }
 
 // To create framebuffers, we first need to create a simple RenderPass that is
@@ -228,13 +225,12 @@
 }
 
 GrVkPipelineState* GrVkResourceProvider::findOrCreateCompatiblePipelineState(
-        GrRenderTarget* renderTarget, int numSamples, GrSurfaceOrigin origin,
-        const GrPipeline& pipeline, const GrPrimitiveProcessor& proc,
-        const GrTextureProxy* const primProcProxies[], GrPrimitiveType primitiveType,
+        GrRenderTarget* renderTarget,
+        const GrProgramInfo& programInfo,
+        GrPrimitiveType primitiveType,
         VkRenderPass compatibleRenderPass) {
-    return fPipelineStateCache->refPipelineState(renderTarget, numSamples, origin, proc,
-                                                 primProcProxies, pipeline, primitiveType,
-                                                 compatibleRenderPass);
+    return fPipelineStateCache->refPipelineState(renderTarget, programInfo,
+                                                 primitiveType, compatibleRenderPass);
 }
 
 void GrVkResourceProvider::getSamplerDescriptorSetHandle(VkDescriptorType type,
diff --git a/src/gpu/vk/GrVkResourceProvider.h b/src/gpu/vk/GrVkResourceProvider.h
index 696245c..44755d7 100644
--- a/src/gpu/vk/GrVkResourceProvider.h
+++ b/src/gpu/vk/GrVkResourceProvider.h
@@ -43,11 +43,8 @@
     // Set up any initial vk objects
     void init();
 
-    GrVkPipeline* createPipeline(int numColorSamples,
-                                 const GrPrimitiveProcessor& primProc,
-                                 const GrPipeline& pipeline,
+    GrVkPipeline* createPipeline(const GrProgramInfo&,
                                  const GrStencilSettings& stencil,
-                                 GrSurfaceOrigin,
                                  VkPipelineShaderStageCreateInfo* shaderStageInfo,
                                  int shaderStageCount,
                                  GrPrimitiveType primitiveType,
@@ -114,10 +111,8 @@
             const GrVkYcbcrConversionInfo& ycbcrInfo);
 
     GrVkPipelineState* findOrCreateCompatiblePipelineState(
-            GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-            const GrPipeline&,
-            const GrPrimitiveProcessor&,
-            const GrTextureProxy* const primProcProxies[],
+            GrRenderTarget*,
+            const GrProgramInfo&,
             GrPrimitiveType,
             VkRenderPass compatibleRenderPass);
 
@@ -197,10 +192,8 @@
 
         void abandon();
         void release();
-        GrVkPipelineState* refPipelineState(GrRenderTarget*, int numSamples, GrSurfaceOrigin,
-                                            const GrPrimitiveProcessor&,
-                                            const GrTextureProxy* const primProcProxies[],
-                                            const GrPipeline&,
+        GrVkPipelineState* refPipelineState(GrRenderTarget*,
+                                            const GrProgramInfo&,
                                             GrPrimitiveType,
                                             VkRenderPass compatibleRenderPass);
 
diff --git a/tests/DrawOpAtlasTest.cpp b/tests/DrawOpAtlasTest.cpp
index cbd0917..ec6068c 100644
--- a/tests/DrawOpAtlasTest.cpp
+++ b/tests/DrawOpAtlasTest.cpp
@@ -211,11 +211,10 @@
     TestingUploadTarget uploadTarget;
 
     GrOpFlushState flushState(gpu, resourceProvider, uploadTarget.writeableTokenTracker());
-    GrOpFlushState::OpArgs opArgs(
-        op.get(),
-        rtc->asRenderTargetProxy(),
-        nullptr,
-        GrXferProcessor::DstProxy(nullptr, SkIPoint::Make(0, 0)));
+    GrOpFlushState::OpArgs opArgs(op.get(),
+                                  rtc->asRenderTargetProxy(),
+                                  nullptr,
+                                  GrXferProcessor::DstProxy(nullptr, SkIPoint::Make(0, 0)));
 
     // Cripple the atlas manager so it can't allocate any pages. This will force a failure
     // in the preparation of the text op
diff --git a/tests/GrMeshTest.cpp b/tests/GrMeshTest.cpp
index 3b39cd7..16ceaeb 100644
--- a/tests/GrMeshTest.cpp
+++ b/tests/GrMeshTest.cpp
@@ -20,6 +20,7 @@
 #include "src/gpu/GrMemoryPool.h"
 #include "src/gpu/GrOpFlushState.h"
 #include "src/gpu/GrOpsRenderPass.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRenderTargetContext.h"
 #include "src/gpu/GrRenderTargetContextPriv.h"
 #include "src/gpu/GrResourceProvider.h"
@@ -410,7 +411,14 @@
 void DrawMeshHelper::drawMesh(const GrMesh& mesh) {
     GrPipeline pipeline(GrScissorTest::kDisabled, SkBlendMode::kSrc, GrSwizzle::RGBA());
     GrMeshTestProcessor mtp(mesh.isInstanced(), mesh.hasVertexData());
-    fState->opsRenderPass()->draw(mtp, pipeline, nullptr, nullptr, &mesh, 1,
+
+    GrProgramInfo programInfo(fState->drawOpArgs().numSamples(),
+                              fState->drawOpArgs().origin(),
+                              pipeline,
+                              mtp,
+                              nullptr, nullptr);
+
+    fState->opsRenderPass()->draw(programInfo, &mesh, 1,
                                   SkRect::MakeIWH(kImageWidth, kImageHeight));
 }
 
diff --git a/tests/GrPipelineDynamicStateTest.cpp b/tests/GrPipelineDynamicStateTest.cpp
index 8a4064c..46d834c 100644
--- a/tests/GrPipelineDynamicStateTest.cpp
+++ b/tests/GrPipelineDynamicStateTest.cpp
@@ -18,6 +18,7 @@
 #include "src/gpu/GrMemoryPool.h"
 #include "src/gpu/GrOpFlushState.h"
 #include "src/gpu/GrOpsRenderPass.h"
+#include "src/gpu/GrProgramInfo.h"
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrRenderTargetContext.h"
 #include "src/gpu/GrRenderTargetContextPriv.h"
@@ -140,8 +141,9 @@
         return GrProcessorSet::EmptySetAnalysis();
     }
     void onPrepare(GrOpFlushState*) override {}
-    void onExecute(GrOpFlushState* state, const SkRect& chainBounds) override {
-        GrPipeline pipeline(fScissorTest, SkBlendMode::kSrc, state->drawOpArgs().outputSwizzle());
+    void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+        GrPipeline pipeline(fScissorTest, SkBlendMode::kSrc,
+                            flushState->drawOpArgs().outputSwizzle());
         SkSTArray<kNumMeshes, GrMesh> meshes;
         for (int i = 0; i < kNumMeshes; ++i) {
             GrMesh& mesh = meshes.emplace_back(GrPrimitiveType::kTriangleStrip);
@@ -150,9 +152,18 @@
         }
         GrPipeline::DynamicStateArrays dynamicState;
         dynamicState.fScissorRects = kDynamicScissors;
-        state->opsRenderPass()->draw(GrPipelineDynamicStateTestProcessor(), pipeline, nullptr,
-                                     &dynamicState, meshes.begin(), 4,
-                                     SkRect::MakeIWH(kScreenSize, kScreenSize));
+
+        GrPipelineDynamicStateTestProcessor primProc;
+
+        GrProgramInfo programInfo(flushState->drawOpArgs().numSamples(),
+                                  flushState->drawOpArgs().origin(),
+                                  pipeline,
+                                  primProc,
+                                  nullptr,
+                                  &dynamicState);
+
+        flushState->opsRenderPass()->draw(programInfo, meshes.begin(), 4,
+                                          SkRect::MakeIWH(kScreenSize, kScreenSize));
     }
 
     GrScissorTest               fScissorTest;