Move GrGeometryProcessor's textures out of classes and into
GrPipeline::FixedDynamicState.

This will allow specification of different textures for different
GrMeshes using GrPipeline::DynamicStateArrays in a future change.

Change-Id: I4a7897df33a84e4072151149e5d586dca074393f
Reviewed-on: https://skia-review.googlesource.com/145264
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/include/private/GrSurfaceProxy.h b/include/private/GrSurfaceProxy.h
index 5a50314..f18a77d 100644
--- a/include/private/GrSurfaceProxy.h
+++ b/include/private/GrSurfaceProxy.h
@@ -240,6 +240,9 @@
         SkASSERT(LazyState::kFully != this->lazyInstantiationState());
         return fHeight;
     }
+
+    SkISize isize() const { return {fWidth, fHeight}; }
+
     int worstCaseWidth() const;
     int worstCaseHeight() const;
     /**
diff --git a/src/gpu/GrGpuCommandBuffer.cpp b/src/gpu/GrGpuCommandBuffer.cpp
index 42e00f3..f4de5c6 100644
--- a/src/gpu/GrGpuCommandBuffer.cpp
+++ b/src/gpu/GrGpuCommandBuffer.cpp
@@ -47,6 +47,11 @@
     if (pipeline.isBad() || !primProc.instantiate(resourceProvider)) {
         return false;
     }
+    for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
+        if (!fixedDynamicState->fPrimitiveProcessorTextures[i]->instantiate(resourceProvider)) {
+            return false;
+        }
+    }
 
     if (primProc.numVertexAttributes() > this->gpu()->caps()->maxVertexAttributes()) {
         this->gpu()->stats()->incNumFailedDraws();
diff --git a/src/gpu/GrOpFlushState.cpp b/src/gpu/GrOpFlushState.cpp
index 8d8b7bb..128a98c 100644
--- a/src/gpu/GrOpFlushState.cpp
+++ b/src/gpu/GrOpFlushState.cpp
@@ -129,6 +129,9 @@
     auto& draw = fDraws.append(&fArena);
     GrDeferredUploadToken token = fTokenTracker->issueDrawToken();
 
+    for (int i = 0; i < gp->numTextureSamplers(); ++i) {
+        fixedDynamicState->fPrimitiveProcessorTextures[i]->addPendingRead();
+    }
     draw.fGeometryProcessor.reset(gp);
     draw.fPipeline = pipeline;
     draw.fFixedDynamicState = fixedDynamicState;
diff --git a/src/gpu/GrOpFlushState.h b/src/gpu/GrOpFlushState.h
index 66ac34c..dcdcbeb 100644
--- a/src/gpu/GrOpFlushState.h
+++ b/src/gpu/GrOpFlushState.h
@@ -118,6 +118,11 @@
     // that share a geometry processor into a Draw is that it allows the Gpu object to setup
     // the shared state once and then issue draws for each mesh.
     struct Draw {
+        ~Draw() {
+            for (int i = 0; i < fGeometryProcessor->numTextureSamplers(); ++i) {
+                fFixedDynamicState->fPrimitiveProcessorTextures[i]->completedRead();
+            }
+        }
         int fMeshCnt = 0;
         GrPendingProgramElement<const GrGeometryProcessor> fGeometryProcessor;
         const GrPipeline* fPipeline;
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index 24dd9f8..aaf36be 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -76,8 +76,11 @@
      * 2) DynamicStateArrays - use this to specify per mesh values for dynamic state.
      **/
     struct FixedDynamicState {
-        FixedDynamicState(const SkIRect& scissorRect) : fScissorRect(scissorRect) {}
-        SkIRect fScissorRect;
+        explicit FixedDynamicState(const SkIRect& scissorRect) : fScissorRect(scissorRect) {}
+        FixedDynamicState() = default;
+        SkIRect fScissorRect = SkIRect::EmptyIRect();
+        // Must have GrPrimitiveProcessor::numTextureSamplers() entries. Can be null if no samplers.
+        GrTextureProxy** fPrimitiveProcessorTextures = nullptr;
     };
 
     /**
diff --git a/src/gpu/GrPrimitiveProcessor.cpp b/src/gpu/GrPrimitiveProcessor.cpp
index 84db9ca..6d2b1e9 100644
--- a/src/gpu/GrPrimitiveProcessor.cpp
+++ b/src/gpu/GrPrimitiveProcessor.cpp
@@ -74,33 +74,6 @@
 }
 #endif
 
-void GrPrimitiveProcessor::addPendingIOs() const {
-    for (int i = 0; i < fTextureSamplerCnt; ++i) {
-        this->textureSampler(i).proxyRef()->markPendingIO();
-    }
-}
-
-void GrPrimitiveProcessor::removeRefs() const {
-    for (int i = 0; i < fTextureSamplerCnt; ++i) {
-        this->textureSampler(i).proxyRef()->removeRef();
-    }
-}
-
-void GrPrimitiveProcessor::pendingIOComplete() const {
-    for (int i = 0; i < fTextureSamplerCnt; ++i) {
-        this->textureSampler(i).proxyRef()->pendingIOComplete();
-    }
-}
-
-bool GrPrimitiveProcessor::instantiate(GrResourceProvider* resourceProvider) const {
-    for (int i = 0; i < fTextureSamplerCnt; ++i) {
-        if (!this->textureSampler(i).instantiate(resourceProvider)) {
-            return false;
-        }
-    }
-    return true;
-}
-
 uint32_t
 GrPrimitiveProcessor::getTransformKey(const SkTArray<const GrCoordTransform*, true>& coords,
                                       int numCoords) const {
@@ -122,34 +95,50 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-GrPrimitiveProcessor::TextureSampler::TextureSampler(sk_sp<GrTextureProxy> proxy,
-                                                     const GrSamplerState& samplerState,
-                                                     GrShaderFlags visibility) {
-    this->reset(std::move(proxy), samplerState, visibility);
+static inline GrSamplerState::Filter clamp_filter(GrTextureType type,
+                                                  GrSamplerState::Filter requestedFilter) {
+    if (GrTextureTypeHasRestrictedSampling(type)) {
+        return SkTMin(requestedFilter, GrSamplerState::Filter::kBilerp);
+    }
+    return requestedFilter;
 }
 
-GrPrimitiveProcessor::TextureSampler::TextureSampler(sk_sp<GrTextureProxy> proxy,
+GrPrimitiveProcessor::TextureSampler::TextureSampler(GrTextureType textureType,
+                                                     GrPixelConfig config,
+                                                     const GrSamplerState& samplerState,
+                                                     GrShaderFlags visibility) {
+    this->reset(textureType, config, samplerState, visibility);
+}
+
+GrPrimitiveProcessor::TextureSampler::TextureSampler(GrTextureType textureType,
+                                                     GrPixelConfig config,
                                                      GrSamplerState::Filter filterMode,
                                                      GrSamplerState::WrapMode wrapXAndY,
                                                      GrShaderFlags visibility) {
-    this->reset(std::move(proxy), filterMode, wrapXAndY, visibility);
+    this->reset(textureType, config, filterMode, wrapXAndY, visibility);
 }
 
-void GrPrimitiveProcessor::TextureSampler::reset(sk_sp<GrTextureProxy> proxy,
+void GrPrimitiveProcessor::TextureSampler::reset(GrTextureType textureType,
+                                                 GrPixelConfig config,
                                                  const GrSamplerState& samplerState,
                                                  GrShaderFlags visibility) {
-    fProxyRef.setProxy(std::move(proxy), kRead_GrIOType);
+    SkASSERT(kUnknown_GrPixelConfig != config);
     fSamplerState = samplerState;
-    fSamplerState.setFilterMode(SkTMin(samplerState.filter(), this->proxy()->highestFilterMode()));
+    fSamplerState.setFilterMode(clamp_filter(textureType, samplerState.filter()));
+    fTextureType = textureType;
+    fConfig = config;
     fVisibility = visibility;
 }
 
-void GrPrimitiveProcessor::TextureSampler::reset(sk_sp<GrTextureProxy> proxy,
+void GrPrimitiveProcessor::TextureSampler::reset(GrTextureType textureType,
+                                                 GrPixelConfig config,
                                                  GrSamplerState::Filter filterMode,
                                                  GrSamplerState::WrapMode wrapXAndY,
                                                  GrShaderFlags visibility) {
-    fProxyRef.setProxy(std::move(proxy), kRead_GrIOType);
-    filterMode = SkTMin(filterMode, this->proxy()->highestFilterMode());
+    SkASSERT(kUnknown_GrPixelConfig != config);
+    filterMode = clamp_filter(textureType, filterMode);
     fSamplerState = GrSamplerState(wrapXAndY, filterMode);
+    fTextureType = textureType;
+    fConfig = config;
     fVisibility = visibility;
 }
diff --git a/src/gpu/GrPrimitiveProcessor.h b/src/gpu/GrPrimitiveProcessor.h
index ddebd21..8c4d0aa 100644
--- a/src/gpu/GrPrimitiveProcessor.h
+++ b/src/gpu/GrPrimitiveProcessor.h
@@ -36,10 +36,12 @@
 
 class GrGLSLPrimitiveProcessor;
 
-/*
+/**
  * GrPrimitiveProcessor defines an interface which all subclasses must implement.  All
  * GrPrimitiveProcessors must proivide seed color and coverage for the Ganesh color / coverage
  * pipelines, and they must provide some notion of equality
+ *
+ * TODO: Overrides of GrProgramElement virtuals don't do anything anymore. Remove this base class.
  */
 class GrPrimitiveProcessor : public GrProcessor, public GrProgramElement {
 public:
@@ -136,7 +138,7 @@
 
     virtual float getSampleShading() const { return 0.0; }
 
-    bool instantiate(GrResourceProvider*) const;
+    bool instantiate(GrResourceProvider*) const { return true; }
 
 protected:
     void setVertexAttributeCnt(int cnt) {
@@ -164,9 +166,9 @@
     inline static const TextureSampler& IthTextureSampler(int i);
 
 private:
-    void addPendingIOs() const final;
-    void removeRefs() const final;
-    void pendingIOComplete() const final;
+    void addPendingIOs() const final {}
+    void removeRefs() const final {}
+    void pendingIOComplete() const final {}
     void notifyRefCntIsZero() const final {}
 
     virtual const Attribute& onVertexAttribute(int) const = 0;
@@ -190,9 +192,9 @@
 public:
     TextureSampler() = default;
 
-    TextureSampler(sk_sp<GrTextureProxy>, const GrSamplerState&, GrShaderFlags visibility);
+    TextureSampler(GrTextureType, GrPixelConfig, const GrSamplerState&, GrShaderFlags visibility);
 
-    explicit TextureSampler(sk_sp<GrTextureProxy>,
+    explicit TextureSampler(GrTextureType, GrPixelConfig,
                             GrSamplerState::Filter = GrSamplerState::Filter::kNearest,
                             GrSamplerState::WrapMode wrapXAndY = GrSamplerState::WrapMode::kClamp,
                             GrShaderFlags visibility = kFragment_GrShaderFlag);
@@ -200,36 +202,25 @@
     TextureSampler(const TextureSampler&) = delete;
     TextureSampler& operator=(const TextureSampler&) = delete;
 
-    void reset(sk_sp<GrTextureProxy>, const GrSamplerState&,
+    void reset(GrTextureType, GrPixelConfig, const GrSamplerState&,
                GrShaderFlags visibility = kFragment_GrShaderFlag);
-    void reset(sk_sp<GrTextureProxy>,
+    void reset(GrTextureType, GrPixelConfig,
                GrSamplerState::Filter = GrSamplerState::Filter::kNearest,
                GrSamplerState::WrapMode wrapXAndY = GrSamplerState::WrapMode::kClamp,
                GrShaderFlags visibility = kFragment_GrShaderFlag);
 
-    bool instantiate(GrResourceProvider* resourceProvider) const {
-        return SkToBool(fProxyRef.get()->instantiate(resourceProvider));
-    }
+    GrTextureType textureType() const { return fTextureType; }
+    GrPixelConfig config() const { return fConfig; }
 
-    // 'peekTexture' should only ever be called after a successful 'instantiate' call
-    GrTexture* peekTexture() const {
-        SkASSERT(fProxyRef.get()->peekTexture());
-        return fProxyRef.get()->peekTexture();
-    }
-
-    GrTextureProxy* proxy() const { return fProxyRef.get(); }
     GrShaderFlags visibility() const { return fVisibility; }
     const GrSamplerState& samplerState() const { return fSamplerState; }
 
-    bool isInitialized() const { return SkToBool(fProxyRef.get()); }
-    /**
-     * For internal use by GrPrimitiveProcessor.
-     */
-    const GrTextureProxyRef* proxyRef() const { return &fProxyRef; }
+    bool isInitialized() const { return fConfig != kUnknown_GrPixelConfig; }
 
 private:
-    GrTextureProxyRef fProxyRef;
     GrSamplerState fSamplerState;
+    GrTextureType fTextureType = GrTextureType::k2D;
+    GrPixelConfig fConfig = kUnknown_GrPixelConfig;
     GrShaderFlags fVisibility = kNone_GrShaderFlags;
 };
 
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index e99a947..5c10b9f 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -80,8 +80,7 @@
     uint16_t* k16 = reinterpret_cast<uint16_t*>(b->add32n(word32Count));
     for (int i = 0; i < numTextureSamplers; ++i) {
         const GrPrimitiveProcessor::TextureSampler& sampler = pp.textureSampler(i);
-        const GrTexture* tex = sampler.peekTexture();
-        k16[i] = sampler_key(tex->texturePriv().textureType(), tex->config(), caps);
+        k16[i] = sampler_key(sampler.textureType(), sampler.config(), caps);
     }
     // zero the last 16 bits if the number of uniforms for samplers is odd.
     if (numTextureSamplers & 0x1) {
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index 016756a..a2c0c14 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -312,7 +312,7 @@
     }
 }
 
-inline void GrCCDrawPathsOp::recordInstance(const GrTextureProxy* atlasProxy, int instanceIdx) {
+inline void GrCCDrawPathsOp::recordInstance(GrTextureProxy* atlasProxy, int instanceIdx) {
     if (fInstanceRanges.empty()) {
         fInstanceRanges.push_back({atlasProxy, instanceIdx});
         return;
@@ -347,8 +347,9 @@
     for (const InstanceRange& range : fInstanceRanges) {
         SkASSERT(range.fEndInstanceIdx > baseInstance);
 
-        GrCCPathProcessor pathProc(flushState->resourceProvider(), sk_ref_sp(range.fAtlasProxy),
-                                   fViewMatrixIfUsingLocalCoords);
+        GrCCPathProcessor pathProc(range.fAtlasProxy, fViewMatrixIfUsingLocalCoords);
+        GrTextureProxy* atlasProxy = range.fAtlasProxy;
+        fixedDynamicState.fPrimitiveProcessorTextures = &atlasProxy;
         pathProc.drawPaths(flushState, pipeline, &fixedDynamicState, *resources, baseInstance,
                            range.fEndInstanceIdx, this->bounds());
 
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.h b/src/gpu/ccpr/GrCCDrawPathsOp.h
index 9ef317f..c877e8b 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.h
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.h
@@ -80,7 +80,7 @@
                     const SkIRect& maskDevIBounds, Visibility maskVisibility,
                     const SkRect& devBounds, GrPaint&&);
 
-    void recordInstance(const GrTextureProxy* atlasProxy, int instanceIdx);
+    void recordInstance(GrTextureProxy* atlasProxy, int instanceIdx);
 
     const SkMatrix fViewMatrixIfUsingLocalCoords;
 
@@ -110,7 +110,7 @@
     GrProcessorSet fProcessors;
 
     struct InstanceRange {
-        const GrTextureProxy* fAtlasProxy;
+        GrTextureProxy* fAtlasProxy;
         int fEndInstanceIdx;
     };
 
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index ae3cba3..4a9a8aa 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -79,12 +79,14 @@
     }
 }
 
-GrCCPathProcessor::GrCCPathProcessor(GrResourceProvider* resourceProvider,
-                                     sk_sp<GrTextureProxy> atlas,
+GrCCPathProcessor::GrCCPathProcessor(const GrTextureProxy* atlas,
                                      const SkMatrix& viewMatrixIfUsingLocalCoords)
         : INHERITED(kGrCCPathProcessor_ClassID)
-        , fAtlasAccess(std::move(atlas), GrSamplerState::Filter::kNearest,
-                       GrSamplerState::WrapMode::kClamp, kFragment_GrShaderFlag) {
+        , fAtlasAccess(atlas->textureType(), atlas->config(), GrSamplerState::Filter::kNearest,
+                       GrSamplerState::WrapMode::kClamp, kFragment_GrShaderFlag)
+        , fAtlasSize(atlas->isize())
+        , fAtlasOrigin(atlas->origin()) {
+    // TODO: Can we just assert that atlas has GrCCAtlas::kTextureOrigin and remove fAtlasOrigin?
     this->setInstanceAttributeCnt(kNumInstanceAttribs);
     // Check that instance attributes exactly match Instance struct layout.
     SkASSERT(!strcmp(this->instanceAttribute(0).name(), "devbounds"));
@@ -98,8 +100,6 @@
     SkASSERT(this->debugOnly_instanceStride() == sizeof(Instance));
 
     this->setVertexAttributeCnt(1);
-
-    fAtlasAccess.instantiate(resourceProvider);
     this->setTextureSamplerCnt(1);
 
     if (!viewMatrixIfUsingLocalCoords.invert(&fLocalMatrix)) {
@@ -115,8 +115,8 @@
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
                  FPCoordTransformIter&& transformIter) override {
         const GrCCPathProcessor& proc = primProc.cast<GrCCPathProcessor>();
-        pdman.set2f(fAtlasAdjustUniform, 1.0f / proc.atlas()->width(),
-                    1.0f / proc.atlas()->height());
+        pdman.set2f(fAtlasAdjustUniform, 1.0f / proc.atlasSize().fWidth,
+                    1.0f / proc.atlasSize().fHeight);
         this->setTransformDataHelper(proc.localMatrix(), pdman, &transformIter);
     }
 
@@ -210,10 +210,10 @@
     // Convert to atlas coordinates in order to do our texture lookup.
     v->codeAppendf("float2 atlascoord = octocoord + float2(%s);",
                    proc.getInstanceAttrib(InstanceAttribs::kDevToAtlasOffset).name());
-    if (kTopLeft_GrSurfaceOrigin == proc.atlasProxy()->origin()) {
+    if (kTopLeft_GrSurfaceOrigin == proc.atlasOrigin()) {
         v->codeAppendf("%s.xy = atlascoord * %s;", texcoord.vsOut(), atlasAdjust);
     } else {
-        SkASSERT(kBottomLeft_GrSurfaceOrigin == proc.atlasProxy()->origin());
+        SkASSERT(kBottomLeft_GrSurfaceOrigin == proc.atlasOrigin());
         v->codeAppendf("%s.xy = float2(atlascoord.x * %s.x, 1 - atlascoord.y * %s.y);",
                        texcoord.vsOut(), atlasAdjust, atlasAdjust);
     }
diff --git a/src/gpu/ccpr/GrCCPathProcessor.h b/src/gpu/ccpr/GrCCPathProcessor.h
index 752083a..394746b 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.h
+++ b/src/gpu/ccpr/GrCCPathProcessor.h
@@ -69,12 +69,12 @@
     static sk_sp<const GrBuffer> FindVertexBuffer(GrOnFlushResourceProvider*);
     static sk_sp<const GrBuffer> FindIndexBuffer(GrOnFlushResourceProvider*);
 
-    GrCCPathProcessor(GrResourceProvider*, sk_sp<GrTextureProxy> atlas,
+    GrCCPathProcessor(const GrTextureProxy* atlas,
                       const SkMatrix& viewMatrixIfUsingLocalCoords = SkMatrix::I());
 
     const char* name() const override { return "GrCCPathProcessor"; }
-    const GrSurfaceProxy* atlasProxy() const { return fAtlasAccess.proxy(); }
-    const GrTexture* atlas() const { return fAtlasAccess.peekTexture(); }
+    const SkISize& atlasSize() const { return fAtlasSize; }
+    GrSurfaceOrigin atlasOrigin() const { return fAtlasOrigin; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     const Attribute& getInstanceAttrib(InstanceAttribs attribID) const {
         int idx = static_cast<int>(attribID);
@@ -96,6 +96,9 @@
     const TextureSampler& onTextureSampler(int) const override { return fAtlasAccess; }
 
     const TextureSampler fAtlasAccess;
+    SkISize fAtlasSize;
+    GrSurfaceOrigin fAtlasOrigin;
+
     SkMatrix fLocalMatrix;
     static constexpr Attribute kInstanceAttribs[kNumInstanceAttribs] = {
             {"devbounds", kFloat4_GrVertexAttribType},
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index aed1af7..d709a8b 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -65,14 +65,18 @@
 
     void onExecute(GrOpFlushState* flushState) override {
         SkASSERT(fStashedAtlasProxy);
+        GrPipeline::FixedDynamicState dynamicState;
+        auto atlasProxy = fStashedAtlasProxy.get();
+        dynamicState.fPrimitiveProcessorTextures = &atlasProxy;
+
         GrPipeline pipeline(flushState->proxy(), GrPipeline::ScissorState::kDisabled,
                             SkBlendMode::kSrc);
-        GrCCPathProcessor pathProc(flushState->resourceProvider(), std::move(fStashedAtlasProxy));
-        pathProc.drawPaths(flushState, pipeline, nullptr, *fResources, fBaseInstance, fEndInstance,
-                           this->bounds());
+        GrCCPathProcessor pathProc(atlasProxy);
+        pathProc.drawPaths(flushState, pipeline, &dynamicState, *fResources, fBaseInstance,
+                           fEndInstance, this->bounds());
         // Ensure we released the stashed atlas proxy. This allows its underlying texture to be
         // reused as the current flush's mainline CCPR atlas if needed.
-        SkASSERT(!fStashedAtlasProxy);
+        fStashedAtlasProxy.reset();
     }
 
 private:
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.cpp b/src/gpu/effects/GrBitmapTextGeoProc.cpp
index 32da45c..ee38f43 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.cpp
+++ b/src/gpu/effects/GrBitmapTextGeoProc.cpp
@@ -86,13 +86,12 @@
             fColor = btgp.color();
         }
 
-        SkASSERT(btgp.numTextureSamplers() >= 1);
-        GrTexture* atlas = btgp.textureSampler(0).peekTexture();
-        SkASSERT(atlas && SkIsPow2(atlas->width()) && SkIsPow2(atlas->height()));
+        const SkISize& atlasSize = btgp.atlasSize();
+        SkASSERT(SkIsPow2(atlasSize.fWidth) && SkIsPow2(atlasSize.fHeight));
 
-        if (fAtlasSize.fWidth != atlas->width() || fAtlasSize.fHeight != atlas->height()) {
-            pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlas->width(), 1.0f / atlas->height());
-            fAtlasSize.set(atlas->width(), atlas->height());
+        if (fAtlasSize != atlasSize) {
+            pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlasSize.fWidth, 1.0f / atlasSize.fHeight);
+            fAtlasSize = atlasSize;
         }
         this->setTransformDataHelper(btgp.localMatrix(), pdman, &transformIter);
     }
@@ -149,9 +148,13 @@
 
     this->setVertexAttributeCnt(cnt);
 
+    if (numActiveProxies) {
+        fAtlasSize = proxies[0]->isize();
+    }
     for (int i = 0; i < numActiveProxies; ++i) {
         SkASSERT(proxies[i]);
-        fTextureSamplers[i].reset(std::move(proxies[i]), params);
+        SkASSERT(proxies[i]->isize() == fAtlasSize);
+        fTextureSamplers[i].reset(proxies[i]->textureType(), proxies[i]->config(), params);
     }
     this->setTextureSamplerCnt(numActiveProxies);
 }
@@ -165,11 +168,16 @@
                                         const GrSamplerState& params) {
     SkASSERT(numActiveProxies <= kMaxTextures);
 
+    if (!fTextureSamplers[0].isInitialized()) {
+        fAtlasSize = proxies[0]->isize();
+    }
+
     for (int i = 0; i < numActiveProxies; ++i) {
         SkASSERT(proxies[i]);
+        SkASSERT(proxies[i]->isize() == fAtlasSize);
 
         if (!fTextureSamplers[i].isInitialized()) {
-            fTextureSamplers[i].reset(std::move(proxies[i]), params);
+            fTextureSamplers[i].reset(proxies[i]->textureType(), proxies[i]->config(), params);
         }
     }
     this->setTextureSamplerCnt(numActiveProxies);
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.h b/src/gpu/effects/GrBitmapTextGeoProc.h
index 1bc0620..d2d530e 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.h
+++ b/src/gpu/effects/GrBitmapTextGeoProc.h
@@ -21,6 +21,7 @@
  */
 class GrBitmapTextGeoProc : public GrGeometryProcessor {
 public:
+    static constexpr int kMaxTextures = 4;
 
     static sk_sp<GrGeometryProcessor> Make(GrColor color,
                                            const sk_sp<GrTextureProxy>* proxies,
@@ -44,6 +45,7 @@
     bool hasVertexColor() const { return fInColor.isInitialized(); }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
     bool usesW() const { return fUsesW; }
+    const SkISize& atlasSize() const { return fAtlasSize; }
 
     void addNewProxies(const sk_sp<GrTextureProxy>*, int numActiveProxies, const GrSamplerState&);
 
@@ -52,8 +54,6 @@
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override;
 
 private:
-    static constexpr int kMaxTextures = 4;
-
     GrBitmapTextGeoProc(GrColor, const sk_sp<GrTextureProxy>* proxies, int numProxies,
                         const GrSamplerState& params, GrMaskFormat format,
                         const SkMatrix& localMatrix, bool usesW);
@@ -64,6 +64,7 @@
     GrColor          fColor;
     SkMatrix         fLocalMatrix;
     bool             fUsesW;
+    SkISize          fAtlasSize;  // size for all textures used with fTextureSamplers[].
     TextureSampler   fTextureSamplers[kMaxTextures];
     Attribute        fInPosition;
     Attribute        fInColor;
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 351e191..59ec5da 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -172,15 +172,13 @@
         }
 #endif
 
-        SkASSERT(dfa8gp.numTextureSamplers() >= 1);
-        GrTexture* atlas = dfa8gp.textureSampler(0).peekTexture();
-        SkASSERT(atlas && SkIsPow2(atlas->width()) && SkIsPow2(atlas->height()));
+        const SkISize& atlasSize = dfa8gp.atlasSize();
+        SkASSERT(SkIsPow2(atlasSize.fWidth) && SkIsPow2(atlasSize.fHeight));
 
-        if (fAtlasSize.fWidth != atlas->width() || fAtlasSize.fHeight != atlas->height()) {
-            fAtlasSize.set(atlas->width(), atlas->height());
-            pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlas->width(), 1.0f / atlas->height());
+        if (fAtlasSize != atlasSize) {
+            pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlasSize.fWidth, 1.0f / atlasSize.fHeight);
+            fAtlasSize = atlasSize;
         }
-
         this->setTransformDataHelper(dfa8gp.localMatrix(), pdman, &transformIter);
     }
 
@@ -234,9 +232,13 @@
     }
     this->setVertexAttributeCnt(3);
 
+    if (numProxies) {
+        fAtlasSize = proxies[0]->isize();
+    }
     for (int i = 0; i < numProxies; ++i) {
         SkASSERT(proxies[i]);
-        fTextureSamplers[i].reset(std::move(proxies[i]), params);
+        SkASSERT(proxies[i]->isize() == fAtlasSize);
+        fTextureSamplers[i].reset(proxies[i]->textureType(), proxies[i]->config(), params);
     }
     this->setTextureSamplerCnt(numProxies);
 }
@@ -246,10 +248,15 @@
                                                  const GrSamplerState& params) {
     SkASSERT(numProxies <= kMaxTextures);
 
+    if (!fTextureSamplers[0].isInitialized()) {
+        fAtlasSize = proxies[0]->isize();
+    }
+
     for (int i = 0; i < numProxies; ++i) {
         SkASSERT(proxies[i]);
+        SkASSERT(proxies[i]->isize() == fAtlasSize);
         if (!fTextureSamplers[i].isInitialized()) {
-            fTextureSamplers[i].reset(std::move(proxies[i]), params);
+            fTextureSamplers[i].reset(proxies[i]->textureType(), proxies[i]->config(), params);
         }
     }
     this->setTextureSamplerCnt(numProxies);
@@ -460,13 +467,11 @@
             pdman.setMatrix3f(fMatrixUniform, matrix);
         }
 
-        SkASSERT(dfpgp.numTextureSamplers() >= 1);
-        GrTexture* atlas = dfpgp.textureSampler(0).peekTexture();
-        SkASSERT(atlas && SkIsPow2(atlas->width()) && SkIsPow2(atlas->height()));
-
-        if (fAtlasSize.fWidth != atlas->width() || fAtlasSize.fHeight != atlas->height()) {
-            fAtlasSize.set(atlas->width(), atlas->height());
-            pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlas->width(), 1.0f / atlas->height());
+        const SkISize& atlasSize = dfpgp.atlasSize();
+        SkASSERT(SkIsPow2(atlasSize.fWidth) && SkIsPow2(atlasSize.fHeight));
+        if (fAtlasSize != atlasSize) {
+            pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlasSize.fWidth, 1.0f / atlasSize.fHeight);
+            fAtlasSize = atlasSize;
         }
 
         if (dfpgp.matrix().hasPerspective()) {
@@ -515,9 +520,15 @@
     SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
 
     this->setVertexAttributeCnt(3);
+
+    if (numProxies) {
+        fAtlasSize = proxies[0]->isize();
+    }
+
     for (int i = 0; i < numProxies; ++i) {
         SkASSERT(proxies[i]);
-        fTextureSamplers[i].reset(std::move(proxies[i]), params);
+        SkASSERT(proxies[i]->isize() == fAtlasSize);
+        fTextureSamplers[i].reset(proxies[i]->textureType(), proxies[i]->config(), params);
     }
     this->setTextureSamplerCnt(numProxies);
 }
@@ -527,11 +538,16 @@
                                                const GrSamplerState& params) {
     SkASSERT(numProxies <= kMaxTextures);
 
+    if (!fTextureSamplers[0].isInitialized()) {
+        fAtlasSize = proxies[0]->isize();
+    }
+
     for (int i = 0; i < numProxies; ++i) {
         SkASSERT(proxies[i]);
+        SkASSERT(proxies[i]->isize() == fAtlasSize);
 
         if (!fTextureSamplers[i].isInitialized()) {
-            fTextureSamplers[i].reset(std::move(proxies[i]), params);
+            fTextureSamplers[i].reset(proxies[i]->textureType(), proxies[i]->config(), params);
         }
     }
     this->setTextureSamplerCnt(numProxies);
@@ -773,13 +789,11 @@
             fDistanceAdjust = wa;
         }
 
-        SkASSERT(dflcd.numTextureSamplers() >= 1);
-        GrTexture* atlas = dflcd.textureSampler(0).peekTexture();
-        SkASSERT(atlas && SkIsPow2(atlas->width()) && SkIsPow2(atlas->height()));
-
-        if (fAtlasSize.fWidth != atlas->width() || fAtlasSize.fHeight != atlas->height()) {
-            fAtlasSize.set(atlas->width(), atlas->height());
-            pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlas->width(), 1.0f / atlas->height());
+        const SkISize& atlasSize = dflcd.atlasSize();
+        SkASSERT(SkIsPow2(atlasSize.fWidth) && SkIsPow2(atlasSize.fHeight));
+        if (fAtlasSize != atlasSize) {
+            pdman.set2f(fAtlasSizeInvUniform, 1.0f / atlasSize.fWidth, 1.0f / atlasSize.fHeight);
+            fAtlasSize = atlasSize;
         }
         this->setTransformDataHelper(dflcd.localMatrix(), pdman, &transformIter);
     }
@@ -829,9 +843,14 @@
     }
     this->setVertexAttributeCnt(3);
 
+    if (numProxies) {
+        fAtlasSize = proxies[0]->isize();
+    }
+
     for (int i = 0; i < numProxies; ++i) {
         SkASSERT(proxies[i]);
-        fTextureSamplers[i].reset(std::move(proxies[i]), params);
+        SkASSERT(proxies[i]->isize() == fAtlasSize);
+        fTextureSamplers[i].reset(proxies[i]->textureType(), proxies[i]->config(), params);
     }
     this->setTextureSamplerCnt(numProxies);
 }
@@ -841,11 +860,16 @@
                                                   const GrSamplerState& params) {
     SkASSERT(numProxies <= kMaxTextures);
 
+    if (!fTextureSamplers[0].isInitialized()) {
+        fAtlasSize = proxies[0]->isize();
+    }
+
     for (int i = 0; i < numProxies; ++i) {
         SkASSERT(proxies[i]);
+        SkASSERT(proxies[i]->isize() == fAtlasSize);
 
         if (!fTextureSamplers[i].isInitialized()) {
-            fTextureSamplers[i].reset(std::move(proxies[i]), params);
+            fTextureSamplers[i].reset(proxies[i]->textureType(), proxies[i]->config(), params);
         }
     }
     this->setTextureSamplerCnt(numProxies);
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.h b/src/gpu/effects/GrDistanceFieldGeoProc.h
index a0fb9d7..fcd98aa 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.h
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.h
@@ -53,6 +53,8 @@
  */
 class GrDistanceFieldA8TextGeoProc : public GrGeometryProcessor {
 public:
+    static constexpr int kMaxTextures = 4;
+
     /** The local matrix should be identity if local coords are not required by the GrPipeline. */
 #ifdef SK_GAMMA_APPLY_TO_A8
     static sk_sp<GrGeometryProcessor> Make(const sk_sp<GrTextureProxy>* proxies,
@@ -84,6 +86,7 @@
     float getDistanceAdjust() const { return fDistanceAdjust; }
 #endif
     uint32_t getFlags() const { return fFlags; }
+    const SkISize& atlasSize() const { return fAtlasSize; }
 
     void addNewProxies(const sk_sp<GrTextureProxy>* proxies, int numProxies, const GrSamplerState&);
 
@@ -106,9 +109,8 @@
 
     const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; }
 
-    static constexpr int kMaxTextures = 4;
-
     TextureSampler   fTextureSamplers[kMaxTextures];
+    SkISize          fAtlasSize;  // size for all textures used with fTextureSamplers[].
     SkMatrix         fLocalMatrix;
     Attribute        fInPosition;
     uint32_t         fFlags;
@@ -132,6 +134,7 @@
  */
 class GrDistanceFieldPathGeoProc : public GrGeometryProcessor {
 public:
+    static constexpr int kMaxTextures = 4;
 
     /** The local matrix should be identity if local coords are not required by the GrPipeline. */
     static sk_sp<GrGeometryProcessor> Make(const SkMatrix& matrix,
@@ -151,6 +154,7 @@
     const Attribute& inTextureCoords() const { return kInTextureCoords; }
     const SkMatrix& matrix() const { return fMatrix; }
     uint32_t getFlags() const { return fFlags; }
+    const SkISize& atlasSize() const { return fAtlasSize; }
 
     void addNewProxies(const sk_sp<GrTextureProxy>*, int numActiveProxies, const GrSamplerState&);
 
@@ -159,8 +163,6 @@
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override;
 
 private:
-    static constexpr int kMaxTextures = 4;
-
     GrDistanceFieldPathGeoProc(const SkMatrix& matrix,
                                const sk_sp<GrTextureProxy>* proxies,
                                int numActiveProxies,
@@ -169,8 +171,9 @@
     const Attribute& onVertexAttribute(int i) const override;
     const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; }
 
-    SkMatrix         fMatrix;      // view matrix if perspective, local matrix otherwise
+    SkMatrix         fMatrix;     // view matrix if perspective, local matrix otherwise
     TextureSampler   fTextureSamplers[kMaxTextures];
+    SkISize          fAtlasSize;  // size for all textures used with fTextureSamplers[].
     uint32_t         fFlags;
     static constexpr Attribute kInPosition = {"inPosition", kFloat2_GrVertexAttribType};
     static constexpr Attribute kInColor = {"inColor", kUByte4_norm_GrVertexAttribType};
@@ -189,6 +192,8 @@
  */
 class GrDistanceFieldLCDTextGeoProc : public GrGeometryProcessor {
 public:
+    static constexpr int kMaxTextures = 4;
+
     struct DistanceAdjust {
         SkScalar fR, fG, fB;
         static DistanceAdjust Make(SkScalar r, SkScalar g, SkScalar b) {
@@ -225,6 +230,7 @@
     DistanceAdjust getDistanceAdjust() const { return fDistanceAdjust; }
     uint32_t getFlags() const { return fFlags; }
     const SkMatrix& localMatrix() const { return fLocalMatrix; }
+    const SkISize& atlasSize() const { return fAtlasSize; }
 
     void addNewProxies(const sk_sp<GrTextureProxy>*, int numActiveProxies, const GrSamplerState&);
 
@@ -240,9 +246,8 @@
     const Attribute& onVertexAttribute(int) const override;
     const TextureSampler& onTextureSampler(int i) const override { return fTextureSamplers[i]; }
 
-    static constexpr int kMaxTextures = 4;
-
     TextureSampler   fTextureSamplers[kMaxTextures];
+    SkISize          fAtlasSize;  // size for all textures used with fTextureSamplers[].
     const SkMatrix   fLocalMatrix;
     DistanceAdjust   fDistanceAdjust;
     Attribute        fInPosition;
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index cc858a8..7ef043b 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -1675,8 +1675,10 @@
 }
 
 void GrGLGpu::generateMipmapsForProcessorTextures(const GrPrimitiveProcessor& primProc,
-                                                  const GrPipeline& pipeline) {
+                                                  const GrPipeline& pipeline,
+                                                  const GrTextureProxy* const primProcTextures[]) {
     auto genLevelsIfNeeded = [this](GrTexture* tex, const GrSamplerState& sampler) {
+        SkASSERT(tex);
         if (sampler.filter() == GrSamplerState::Filter::kMipMap &&
             tex->texturePriv().mipMapped() == GrMipMapped::kYes &&
             tex->texturePriv().mipMapsAreDirty()) {
@@ -1686,8 +1688,8 @@
     };
 
     for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-        const auto& textureSampler = primProc.textureSampler(i);
-        genLevelsIfNeeded(textureSampler.peekTexture(), textureSampler.samplerState());
+        GrTexture* tex = primProcTextures[i]->peekTexture();
+        genLevelsIfNeeded(tex, primProc.textureSampler(i).samplerState());
     }
 
     GrFragmentProcessor::Iter iter(pipeline);
@@ -1708,8 +1710,11 @@
         GrCapsDebugf(this->caps(), "Failed to create program!\n");
         return false;
     }
-
-    this->generateMipmapsForProcessorTextures(primProc, pipeline);
+    const GrTextureProxy* const* primProcProxies = nullptr;
+    if (fixedDynamicState) {
+        primProcProxies = fixedDynamicState->fPrimitiveProcessorTextures;
+    }
+    this->generateMipmapsForProcessorTextures(primProc, pipeline, primProcProxies);
 
     GrXferProcessor::BlendInfo blendInfo;
     pipeline.getXferProcessor().getBlendInfo(&blendInfo);
@@ -1726,7 +1731,7 @@
         this->flushBlend(blendInfo, swizzle);
     }
 
-    fHWProgram->setData(primProc, pipeline);
+    fHWProgram->updateUniformsAndTextureBindings(primProc, pipeline, primProcProxies);
 
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(pipeline.renderTarget());
     GrStencilSettings stencil;
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 32b2945..0f1127a 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -247,7 +247,9 @@
 
     void setTextureSwizzle(int unitIdx, GrGLenum target, const GrGLenum swizzle[]);
 
-    void generateMipmapsForProcessorTextures(const GrPrimitiveProcessor&, const GrPipeline&);
+    void generateMipmapsForProcessorTextures(
+            const GrPrimitiveProcessor&, const GrPipeline&,
+            const GrTextureProxy* const primitiveProcessorTextures[]);
 
     // Flushes state from GrPipeline to GL. Returns false if the state couldn't be set.
     // willDrawPoints must be true if point primitives will be rendered after setting the GL state.
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index e0eccf2..9e90642 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -72,7 +72,10 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-void GrGLProgram::setData(const GrPrimitiveProcessor& primProc, const GrPipeline& pipeline) {
+void GrGLProgram::updateUniformsAndTextureBindings(const GrPrimitiveProcessor& primProc,
+                                                   const GrPipeline& pipeline,
+                                                   const GrTextureProxy* const primProcTextures[]) {
+    SkASSERT(primProcTextures || !primProc.numTextureSamplers());
     this->setRenderTargetState(primProc, pipeline.proxy());
 
     // we set the textures, and uniforms for installed processors in a generic way, but subclasses
@@ -85,9 +88,8 @@
     fPrimitiveProcessor->setData(fProgramDataManager, primProc,
                                  GrFragmentProcessor::CoordTransformIter(pipeline));
     for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-        const GrPrimitiveProcessor::TextureSampler& sampler = primProc.textureSampler(i);
-        fGpu->bindTexture(nextTexSamplerIdx++, sampler.samplerState(),
-                          static_cast<GrGLTexture*>(sampler.peekTexture()));
+        auto* tex = static_cast<GrGLTexture*>(primProcTextures[i]->peekTexture());
+        fGpu->bindTexture(nextTexSamplerIdx++, primProc.textureSampler(i).samplerState(), tex);
     }
 
     this->setFragmentData(pipeline, &nextTexSamplerIdx);
diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h
index b05b536..ca9253c 100644
--- a/src/gpu/gl/GrGLProgram.h
+++ b/src/gpu/gl/GrGLProgram.h
@@ -19,6 +19,7 @@
 class GrPipeline;
 class GrPrimitiveProcessor;
 class GrRenderTargetProxy;
+class GrTextureProxy;
 
 /**
  * This class manages a GPU program and records per-program information. It also records the vertex
@@ -110,12 +111,13 @@
     };
 
     /**
-     * This function uploads uniforms, calls each GrGL*Processor's setData, and retrieves the
-     * textures that need to be bound on each unit. It is the caller's responsibility to ensure
-     * the program is bound before calling, and to bind the outgoing textures to their respective
-     * units upon return. (Each index in the array corresponds to its matching GL texture unit.)
+     * This function uploads uniforms, calls each GrGLSL*Processor's setData. It binds all fragment
+     * processor textures. Primitive process textures are also bound here but are passed separately.
+     *
+     * It is the caller's responsibility to ensure the program is bound before calling.
      */
-    void setData(const GrPrimitiveProcessor&, const GrPipeline&);
+    void updateUniformsAndTextureBindings(const GrPrimitiveProcessor&, const GrPipeline&,
+                                          const GrTextureProxy* const primitiveProcessorTextures[]);
 
     int vertexStride() const { return fVertexStride; }
     int instanceStride() const { return fInstanceStride; }
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
index b0cf816..66e333a 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
@@ -101,9 +101,8 @@
         SkString name;
         name.printf("TextureSampler_%d", i);
         const auto& sampler = proc.textureSampler(i);
-        GrTextureType textureType = sampler.peekTexture()->texturePriv().textureType();
-        texSamplers[i] = this->emitSampler(textureType, sampler.peekTexture()->config(),
-                                           name.c_str(), sampler.visibility());
+        texSamplers[i] = this->emitSampler(sampler.textureType(), sampler.config(), name.c_str(),
+                                           sampler.visibility());
     }
 
     GrGLSLPrimitiveProcessor::FPCoordTransformHandler transformHandler(fPipeline,
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index cfd0c45..a6e2bce 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -295,9 +295,16 @@
     }
     SkASSERT(proxies[0]);
 
+    static constexpr int kMaxTextures = GrBitmapTextGeoProc::kMaxTextures;
+    GR_STATIC_ASSERT(GrDistanceFieldA8TextGeoProc::kMaxTextures == kMaxTextures);
+    GR_STATIC_ASSERT(GrDistanceFieldLCDTextGeoProc::kMaxTextures == kMaxTextures);
+
     static const uint32_t kPipelineFlags = 0;
     auto pipe = target->makePipeline(kPipelineFlags, std::move(fProcessors),
-                                     target->detachAppliedClip());
+                                     target->detachAppliedClip(), kMaxTextures);
+    for (unsigned i = 0; i < numActiveProxies; ++i) {
+        pipe.fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get();
+    }
 
     FlushInfo flushInfo;
     flushInfo.fPipeline = pipe.fPipeline;
@@ -400,6 +407,9 @@
     if (gp->numTextureSamplers() != (int) numActiveProxies) {
         // During preparation the number of atlas pages has increased.
         // Update the proxies used in the GP to match.
+        for (unsigned i = gp->numTextureSamplers(); i < numActiveProxies; ++i) {
+            flushInfo->fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get();
+        }
         if (this->usesDistanceFields()) {
             if (this->isLCD()) {
                 reinterpret_cast<GrDistanceFieldLCDTextGeoProc*>(gp)->addNewProxies(
@@ -415,7 +425,6 @@
                                                                       samplerState);
         }
     }
-
     GrMesh mesh(GrPrimitiveType::kTriangles);
     int maxGlyphsPerDraw =
             static_cast<int>(flushInfo->fIndexBuffer->gpuMemorySize() / sizeof(uint16_t) / 6);
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 14f7e95..1bf8a8a 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -107,7 +107,7 @@
         sk_sp<const GrBuffer> fIndexBuffer;
         sk_sp<GrGeometryProcessor> fGeometryProcessor;
         const GrPipeline* fPipeline;
-        const GrPipeline::FixedDynamicState* fFixedDynamicState;
+        GrPipeline::FixedDynamicState* fFixedDynamicState;
         int fGlyphsToFlush;
         int fVertexOffset;
     };
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index 00dbef5..0da289a 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -32,10 +32,10 @@
         GrColor fColor;
     };
 
-    static sk_sp<GrGeometryProcessor> Make(sk_sp<GrTextureProxy> proxy,
+    static sk_sp<GrGeometryProcessor> Make(const GrTextureProxy* proxy,
                                            sk_sp<GrColorSpaceXform> csxf,
                                            GrSamplerState::Filter filter) {
-        return sk_sp<GrGeometryProcessor>(new LatticeGP(std::move(proxy), std::move(csxf), filter));
+        return sk_sp<GrGeometryProcessor>(new LatticeGP(proxy, std::move(csxf), filter));
     }
 
     const char* name() const override { return "LatticeGP"; }
@@ -92,10 +92,10 @@
     }
 
 private:
-    LatticeGP(sk_sp<GrTextureProxy> proxy, sk_sp<GrColorSpaceXform> csxf,
+    LatticeGP(const GrTextureProxy* proxy, sk_sp<GrColorSpaceXform> csxf,
               GrSamplerState::Filter filter)
             : INHERITED(kLatticeGP_ClassID), fColorSpaceXform(std::move(csxf)) {
-        fSampler.reset(std::move(proxy), filter);
+        fSampler.reset(proxy->textureType(), proxy->config(), filter);
         this->setTextureSamplerCnt(1);
         this->setVertexAttributeCnt(4);
     }
@@ -202,7 +202,7 @@
 
 private:
     void onPrepareDraws(Target* target) override {
-        auto gp = LatticeGP::Make(fProxy, fColorSpaceXform, fFilter);
+        auto gp = LatticeGP::Make(fProxy.get(), fColorSpaceXform, fFilter);
         if (!gp) {
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
@@ -281,7 +281,8 @@
                                                   kVertsPerRect * patch.fIter->numRectsToDraw());
             }
         }
-        auto pipe = fHelper.makePipeline(target);
+        auto pipe = fHelper.makePipeline(target, 1);
+        pipe.fFixedDynamicState->fPrimitiveProcessorTextures[0] = fProxy.get();
         helper.recordDraw(target, gp.get(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
 
diff --git a/src/gpu/ops/GrMeshDrawOp.cpp b/src/gpu/ops/GrMeshDrawOp.cpp
index 0bac5ef..5450b11 100644
--- a/src/gpu/ops/GrMeshDrawOp.cpp
+++ b/src/gpu/ops/GrMeshDrawOp.cpp
@@ -63,7 +63,8 @@
 //////////////////////////////////////////////////////////////////////////////
 
 GrMeshDrawOp::Target::PipelineAndFixedDynamicState GrMeshDrawOp::Target::makePipeline(
-        uint32_t pipelineFlags, GrProcessorSet&& processorSet, GrAppliedClip&& clip) {
+        uint32_t pipelineFlags, GrProcessorSet&& processorSet, GrAppliedClip&& clip,
+        int numPrimProcTextures) {
     GrPipeline::InitArgs pipelineArgs;
     pipelineArgs.fFlags = pipelineFlags;
     pipelineArgs.fProxy = this->proxy();
@@ -71,8 +72,12 @@
     pipelineArgs.fCaps = &this->caps();
     pipelineArgs.fResourceProvider = this->resourceProvider();
     GrPipeline::FixedDynamicState* fixedDynamicState = nullptr;
-    if (clip.scissorState().enabled()) {
+    if (clip.scissorState().enabled() || numPrimProcTextures) {
         fixedDynamicState = this->allocFixedDynamicState(clip.scissorState().rect());
+        if (numPrimProcTextures) {
+            fixedDynamicState->fPrimitiveProcessorTextures =
+                    this->allocPrimitiveProcessorTextureArray(numPrimProcTextures);
+        }
     }
     return {this->allocPipeline(pipelineArgs, std::move(processorSet), std::move(clip)),
             fixedDynamicState};
diff --git a/src/gpu/ops/GrMeshDrawOp.h b/src/gpu/ops/GrMeshDrawOp.h
index e90de47..3cb5ffe 100644
--- a/src/gpu/ops/GrMeshDrawOp.h
+++ b/src/gpu/ops/GrMeshDrawOp.h
@@ -132,10 +132,19 @@
         return this->pipelineArena()->make<GrPipeline>(std::forward<Args>(args)...);
     }
 
-    template <typename... Args>
-    GrPipeline::FixedDynamicState* allocFixedDynamicState(Args&... args) {
-        return this->pipelineArena()->make<GrPipeline::FixedDynamicState>(
-                std::forward<Args>(args)...);
+    GrPipeline::FixedDynamicState* allocFixedDynamicState(const SkIRect& rect,
+                                                          int numPrimitiveProcessorTextures = 0) {
+        auto result = this->pipelineArena()->make<GrPipeline::FixedDynamicState>(rect);
+        if (numPrimitiveProcessorTextures) {
+            result->fPrimitiveProcessorTextures =
+                    this->allocPrimitiveProcessorTextureArray(numPrimitiveProcessorTextures);
+        }
+        return result;
+    }
+
+    GrTextureProxy** allocPrimitiveProcessorTextureArray(int n) {
+        SkASSERT(n > 0);
+        return this->pipelineArena()->makeArrayDefault<GrTextureProxy*>(n);
     }
 
     // Once we have C++17 structured bindings make this just be a tuple because then we can do:
@@ -144,7 +153,7 @@
     //      std::tie(flushInfo.fPipeline, flushInfo.fFixedState) = target->makePipeline(...);
     struct PipelineAndFixedDynamicState {
         const GrPipeline* fPipeline;
-        const GrPipeline::FixedDynamicState* fFixedDynamicState;
+        GrPipeline::FixedDynamicState* fFixedDynamicState;
     };
 
     /**
@@ -152,7 +161,8 @@
      * GrAppliedClip and uses a fixed dynamic state.
      */
     PipelineAndFixedDynamicState makePipeline(uint32_t pipelineFlags, GrProcessorSet&&,
-                                              GrAppliedClip&&);
+                                              GrAppliedClip&&,
+                                              int numPrimitiveProcessorTextures = 0);
 
     virtual GrRenderTargetProxy* proxy() const = 0;
 
diff --git a/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp b/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp
index 7ed74c3..f1692e6 100644
--- a/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp
+++ b/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp
@@ -135,7 +135,8 @@
 }
 
 auto GrSimpleMeshDrawOpHelper::internalMakePipeline(GrMeshDrawOp::Target* target,
-                                                    const GrPipeline::InitArgs& args)
+                                                    const GrPipeline::InitArgs& args,
+                                                    int numPrimitiveProcessorProxies)
         -> PipelineAndFixedDynamicState {
     // A caller really should only call this once as the processor set and applied clip get
     // moved into the GrPipeline.
@@ -143,8 +144,12 @@
     SkDEBUGCODE(fMadePipeline = true);
     auto clip = target->detachAppliedClip();
     GrPipeline::FixedDynamicState* fixedDynamicState = nullptr;
-    if (clip.scissorState().enabled()) {
+    if (clip.scissorState().enabled() || numPrimitiveProcessorProxies) {
         fixedDynamicState = target->allocFixedDynamicState(clip.scissorState().rect());
+        if (numPrimitiveProcessorProxies) {
+            fixedDynamicState->fPrimitiveProcessorTextures =
+                    target->allocPrimitiveProcessorTextureArray(numPrimitiveProcessorProxies);
+        }
     }
     if (fProcessors) {
         return {target->allocPipeline(args, std::move(*fProcessors), std::move(clip)),
@@ -176,11 +181,12 @@
            fStencilSettings == that.fStencilSettings;
 }
 
-auto GrSimpleMeshDrawOpHelperWithStencil::makePipeline(GrMeshDrawOp::Target* target)
+auto GrSimpleMeshDrawOpHelperWithStencil::makePipeline(GrMeshDrawOp::Target* target,
+                                                       int numPrimitiveProcessorTextures)
         -> PipelineAndFixedDynamicState {
     auto args = INHERITED::pipelineInitArgs(target);
     args.fUserStencil = fStencilSettings;
-    return this->internalMakePipeline(target, args);
+    return this->internalMakePipeline(target, args, numPrimitiveProcessorTextures);
 }
 
 SkString GrSimpleMeshDrawOpHelperWithStencil::dumpInfo() const {
diff --git a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
index 2bffa09..cd72786 100644
--- a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
+++ b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
@@ -88,8 +88,10 @@
 
     using PipelineAndFixedDynamicState = GrOpFlushState::PipelineAndFixedDynamicState;
     /** Makes a pipeline that consumes the processor set and the op's applied clip. */
-    PipelineAndFixedDynamicState makePipeline(GrMeshDrawOp::Target* target) {
-        return this->internalMakePipeline(target, this->pipelineInitArgs(target));
+    PipelineAndFixedDynamicState makePipeline(GrMeshDrawOp::Target* target,
+                                              int numPrimitiveProcessorTextures = 0) {
+        return this->internalMakePipeline(target, this->pipelineInitArgs(target),
+                                          numPrimitiveProcessorTextures);
     }
 
     struct MakeArgs {
@@ -116,7 +118,8 @@
     GrPipeline::InitArgs pipelineInitArgs(GrMeshDrawOp::Target* target) const;
 
     PipelineAndFixedDynamicState internalMakePipeline(GrMeshDrawOp::Target*,
-                                                      const GrPipeline::InitArgs&);
+                                                      const GrPipeline::InitArgs&,
+                                                      int numPrimitiveProcessorTextures);
 
 private:
     GrProcessorSet* fProcessors;
@@ -162,7 +165,8 @@
     bool isCompatible(const GrSimpleMeshDrawOpHelperWithStencil& that, const GrCaps&,
                       const SkRect& thisBounds, const SkRect& thatBounds) const;
 
-    PipelineAndFixedDynamicState makePipeline(GrMeshDrawOp::Target*);
+    PipelineAndFixedDynamicState makePipeline(GrMeshDrawOp::Target*,
+                                              int numPrimitiveProcessorTextures = 0);
 
     SkString dumpInfo() const;
 
diff --git a/src/gpu/ops/GrSmallPathRenderer.cpp b/src/gpu/ops/GrSmallPathRenderer.cpp
index 286fab7..4bbff1d 100644
--- a/src/gpu/ops/GrSmallPathRenderer.cpp
+++ b/src/gpu/ops/GrSmallPathRenderer.cpp
@@ -304,7 +304,7 @@
         sk_sp<const GrBuffer> fIndexBuffer;
         sk_sp<GrGeometryProcessor>   fGeometryProcessor;
         const GrPipeline* fPipeline;
-        const GrPipeline::FixedDynamicState* fFixedDynamicState;
+        GrPipeline::FixedDynamicState* fFixedDynamicState;
         int fVertexOffset;
         int fInstancesToFlush;
     };
@@ -312,7 +312,15 @@
     void onPrepareDraws(Target* target) override {
         int instanceCount = fShapes.count();
 
-        auto pipe = fHelper.makePipeline(target);
+        static constexpr int kMaxTextures = GrDistanceFieldPathGeoProc::kMaxTextures;
+        GR_STATIC_ASSERT(GrBitmapTextGeoProc::kMaxTextures == kMaxTextures);
+
+        auto pipe = fHelper.makePipeline(target, kMaxTextures);
+        int numActiveProxies = fAtlas->numActivePages();
+        const auto proxies = fAtlas->getProxies();
+        for (int i = 0; i < numActiveProxies; ++i) {
+            pipe.fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get();
+        }
 
         FlushInfo flushInfo;
         flushInfo.fPipeline = pipe.fPipeline;
@@ -807,7 +815,12 @@
 
     void flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) const {
         GrGeometryProcessor* gp = flushInfo->fGeometryProcessor.get();
-        if (gp->numTextureSamplers() != (int)fAtlas->numActivePages()) {
+        int numAtlasTextures = SkToInt(fAtlas->numActivePages());
+        auto proxies = fAtlas->getProxies();
+        if (gp->numTextureSamplers() != numAtlasTextures) {
+            for (int i = gp->numTextureSamplers(); i < numAtlasTextures; ++i) {
+                flushInfo->fFixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i].get();
+            }
             // During preparation the number of atlas pages has increased.
             // Update the proxies used in the GP to match.
             if (fUsesDistanceField) {
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index c69eecc..7ba41c5 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -104,23 +104,22 @@
         return caps.integerSupport() && caps.maxFragmentSamplers() > 1;
     }
 
-    static sk_sp<GrGeometryProcessor> Make(sk_sp<GrTextureProxy> proxies[], int proxyCnt,
+    static sk_sp<GrGeometryProcessor> Make(GrTextureType textureType, GrPixelConfig textureConfig,
+                                           int textureCnt,
                                            sk_sp<GrColorSpaceXform> textureColorSpaceXform,
                                            sk_sp<GrColorSpaceXform> paintColorSpaceXform,
-                                           bool coverageAA,
-                                           bool perspective, Domain domain,
+                                           bool coverageAA, bool perspective, Domain domain,
                                            const GrSamplerState::Filter filters[],
                                            const GrShaderCaps& caps) {
         // We use placement new to avoid always allocating space for kMaxTextures TextureSampler
         // instances.
-        int samplerCnt = NumSamplersToUse(proxyCnt, caps);
+        int samplerCnt = NumSamplersToUse(textureCnt, caps);
         size_t size = sizeof(TextureGeometryProcessor) + sizeof(TextureSampler) * (samplerCnt - 1);
         void* mem = GrGeometryProcessor::operator new(size);
-        return sk_sp<TextureGeometryProcessor>(
-                new (mem) TextureGeometryProcessor(proxies, proxyCnt, samplerCnt,
-                                                   std::move(textureColorSpaceXform),
-                                                   std::move(paintColorSpaceXform),
-                                                   coverageAA, perspective, domain, filters, caps));
+        return sk_sp<TextureGeometryProcessor>(new (mem) TextureGeometryProcessor(
+                textureType, textureConfig, textureCnt, samplerCnt,
+                std::move(textureColorSpaceXform), std::move(paintColorSpaceXform), coverageAA,
+                perspective, domain, filters, caps));
     }
 
     ~TextureGeometryProcessor() override {
@@ -286,20 +285,20 @@
         return SkTMin(SkNextPow2(numRealProxies), SkTMin(kMaxTextures, caps.maxFragmentSamplers()));
     }
 
-    TextureGeometryProcessor(sk_sp<GrTextureProxy> proxies[], int proxyCnt, int samplerCnt,
-                             sk_sp<GrColorSpaceXform> textureColorSpaceXform,
-                             sk_sp<GrColorSpaceXform> paintColorSpaceXform,
-                             bool coverageAA, bool perspective, Domain domain,
+    TextureGeometryProcessor(GrTextureType textureType, GrPixelConfig textureConfig, int textureCnt,
+                             int samplerCnt, sk_sp<GrColorSpaceXform> textureColorSpaceXform,
+                             sk_sp<GrColorSpaceXform> paintColorSpaceXform, bool coverageAA,
+                             bool perspective, Domain domain,
                              const GrSamplerState::Filter filters[], const GrShaderCaps& caps)
             : INHERITED(kTextureGeometryProcessor_ClassID)
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
             , fPaintColorSpaceXform(std::move(paintColorSpaceXform)) {
-        SkASSERT(proxyCnt > 0 && samplerCnt >= proxyCnt);
-        fSamplers[0].reset(std::move(proxies[0]), filters[0]);
-        for (int i = 1; i < proxyCnt; ++i) {
+        SkASSERT(textureCnt > 0 && samplerCnt >= textureCnt);
+        fSamplers[0].reset(textureType, textureConfig, filters[0]);
+        for (int i = 1; i < textureCnt; ++i) {
             // This class has one sampler built in, the rest come from memory this processor was
             // placement-newed into and so haven't been constructed.
-            new (&fSamplers[i]) TextureSampler(std::move(proxies[i]), filters[i]);
+            new (&fSamplers[i]) TextureSampler(textureType, textureConfig, filters[i]);
         }
 
         if (perspective) {
@@ -312,11 +311,11 @@
         int vertexAttributeCnt = 3;
 
         if (samplerCnt > 1) {
-            // Here we initialize any extra samplers by repeating the last one samplerCnt - proxyCnt
-            // times.
-            GrTextureProxy* dupeProxy = fSamplers[proxyCnt - 1].proxy();
-            for (int i = proxyCnt; i < samplerCnt; ++i) {
-                new (&fSamplers[i]) TextureSampler(sk_ref_sp(dupeProxy), filters[proxyCnt - 1]);
+            // Here we initialize any extra samplers. We repeat the first filter because our caller
+            // will specify the first texture for all the extra samplers. In GL the filter is
+            // implemented as a texture parameter and the last sampler will win.
+            for (int i = textureCnt; i < samplerCnt; ++i) {
+                new (&fSamplers[i]) TextureSampler(textureType, textureConfig, filters[0]);
             }
             SkASSERT(caps.integerSupport());
             fTextureIdx = {"textureIdx", kInt_GrVertexAttribType};
@@ -764,22 +763,20 @@
     }
 
     void onPrepareDraws(Target* target) override {
-        sk_sp<GrTextureProxy> proxiesSPs[kMaxTextures];
         auto proxies = this->proxies();
         auto filters = this->filters();
         for (int i = 0; i < fProxyCnt; ++i) {
             if (!proxies[i]->instantiate(target->resourceProvider())) {
                 return;
             }
-            proxiesSPs[i] = sk_ref_sp(proxies[i]);
         }
 
         Domain domain = fDomain ? Domain::kYes : Domain::kNo;
         bool coverageAA = GrAAType::kCoverage == this->aaType();
         sk_sp<GrGeometryProcessor> gp = TextureGeometryProcessor::Make(
-                proxiesSPs, fProxyCnt, std::move(fTextureColorSpaceXform),
-                std::move(fPaintColorSpaceXform), coverageAA, fPerspective,
-                domain, filters, *target->caps().shaderCaps());
+                proxies[0]->textureType(), proxies[0]->config(), fProxyCnt,
+                std::move(fTextureColorSpaceXform), std::move(fPaintColorSpaceXform), coverageAA,
+                fPerspective, domain, filters, *target->caps().shaderCaps());
         GrPipeline::InitArgs args;
         args.fProxy = target->proxy();
         args.fCaps = &target->caps();
@@ -790,7 +787,17 @@
         }
 
         auto clip = target->detachAppliedClip();
-        const auto* fixedDynamicState = target->allocFixedDynamicState(clip.scissorState().rect());
+        auto* fixedDynamicState = target->allocFixedDynamicState(clip.scissorState().rect(),
+                                                                 gp->numTextureSamplers());
+        for (int i = 0; i < fProxyCnt; ++i) {
+            SkASSERT(proxies[i]->textureType() == proxies[0]->textureType());
+            SkASSERT(proxies[i]->config() == proxies[0]->config());
+            fixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[i];
+        }
+        // Rebind texture proxy 0 to the extra samplers.
+        for (int i = fProxyCnt; i < gp->numTextureSamplers(); ++i) {
+            fixedDynamicState->fPrimitiveProcessorTextures[i] = proxies[0];
+        }
         const auto* pipeline =
                 target->allocPipeline(args, GrProcessorSet::MakeEmptySet(), std::move(clip));
         using TessFn =
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.cpp b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
index 013c1d7..3cbc6ad 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
@@ -585,7 +585,11 @@
     }
     fLastPipelineState = pipelineState;
 
-    pipelineState->setData(fGpu, primProc, pipeline);
+    const GrTextureProxy* const* primProcProxies = nullptr;
+    if (fixedDynamicState) {
+        primProcProxies = fixedDynamicState->fPrimitiveProcessorTextures;
+    }
+    pipelineState->setData(fGpu, primProc, pipeline, primProcProxies);
 
     pipelineState->bind(fGpu, cbInfo.currentCmdBuf());
 
@@ -643,8 +647,8 @@
     };
 
     for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
-        const GrPrimitiveProcessor::TextureSampler& sampler = primProc.textureSampler(i);
-        prepareSampledImage(sampler.peekTexture(), sampler.samplerState().filter());
+        auto texture = fixedDynamicState->fPrimitiveProcessorTextures[i]->peekTexture();
+        prepareSampledImage(texture, primProc.textureSampler(i).samplerState().filter());
     }
     GrFragmentProcessor::Iter iter(pipeline);
     while (const GrFragmentProcessor* fp = iter.next()) {
diff --git a/src/gpu/vk/GrVkPipelineState.cpp b/src/gpu/vk/GrVkPipelineState.cpp
index 04a85a4..011db9d 100644
--- a/src/gpu/vk/GrVkPipelineState.cpp
+++ b/src/gpu/vk/GrVkPipelineState.cpp
@@ -167,7 +167,9 @@
 
 void GrVkPipelineState::setData(GrVkGpu* gpu,
                                 const GrPrimitiveProcessor& primProc,
-                                const GrPipeline& pipeline) {
+                                const GrPipeline& pipeline,
+                                const GrTextureProxy* const primProcTextures[]) {
+    SkASSERT(primProcTextures || !primProc.numTextureSamplers());
     // This is here to protect against someone calling setData multiple times in a row without
     // freeing the tempData between calls.
     this->freeTempResources(gpu);
@@ -181,8 +183,8 @@
                                 GrFragmentProcessor::CoordTransformIter(pipeline));
     for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
         const auto& sampler = primProc.textureSampler(i);
-        samplerBindings[currTextureBinding++] = {sampler.samplerState(),
-                                                 static_cast<GrVkTexture*>(sampler.peekTexture())};
+        auto texture = static_cast<GrVkTexture*>(primProcTextures[i]->peekTexture());
+        samplerBindings[currTextureBinding++] = {sampler.samplerState(), texture};
     }
 
     GrFragmentProcessor::Iter iter(pipeline);
diff --git a/src/gpu/vk/GrVkPipelineState.h b/src/gpu/vk/GrVkPipelineState.h
index 1df4bb5..d61359c 100644
--- a/src/gpu/vk/GrVkPipelineState.h
+++ b/src/gpu/vk/GrVkPipelineState.h
@@ -56,7 +56,8 @@
 
     ~GrVkPipelineState();
 
-    void setData(GrVkGpu*, const GrPrimitiveProcessor&, const GrPipeline&);
+    void setData(GrVkGpu*, const GrPrimitiveProcessor&, const GrPipeline&,
+                 const GrTextureProxy* const primitiveProcessorTextures[]);
 
     void bind(const GrVkGpu* gpu, GrVkCommandBuffer* commandBuffer);