Remove generic GrFragmentProcessor texture sampling.

Instead GrTextureEffect is a special effect known by
program builders, pipeline states, etc.

Change-Id: I4436d7a10a1c3174fe1f02f136363a1c117f92fb
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/301357
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index c16014c..1f89155 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -21,14 +21,6 @@
     if (this->classID() != that.classID()) {
         return false;
     }
-    if (this->numTextureSamplers() != that.numTextureSamplers()) {
-        return false;
-    }
-    for (int i = 0; i < this->numTextureSamplers(); ++i) {
-        if (this->textureSampler(i) != that.textureSampler(i)) {
-            return false;
-        }
-    }
     if (this->usesVaryingCoordsDirectly() != that.usesVaryingCoordsDirectly()) {
         return false;
     }
@@ -46,11 +38,35 @@
     return true;
 }
 
-void GrFragmentProcessor::visitProxies(const GrOp::VisitProxyFunc& func) {
-    for (auto [sampler, fp] : FPTextureSamplerRange(*this)) {
-        bool mipped = (GrSamplerState::Filter::kMipMap == sampler.samplerState().filter());
-        func(sampler.view().proxy(), GrMipMapped(mipped));
+void GrFragmentProcessor::visitProxies(const GrOp::VisitProxyFunc& func) const {
+    this->visitTextureEffects([&func](const GrTextureEffect& te) {
+        bool mipped = (GrSamplerState::Filter::kMipMap == te.samplerState().filter());
+        func(te.view().proxy(), GrMipMapped(mipped));
+    });
+}
+
+void GrFragmentProcessor::visitTextureEffects(
+        const std::function<void(const GrTextureEffect&)>& func) const {
+    if (auto* te = this->asTextureEffect()) {
+        func(*te);
     }
+    for (auto& child : fChildProcessors) {
+        child->visitTextureEffects(func);
+    }
+}
+
+GrTextureEffect* GrFragmentProcessor::asTextureEffect() {
+    if (this->classID() == kGrTextureEffect_ClassID) {
+        return static_cast<GrTextureEffect*>(this);
+    }
+    return nullptr;
+}
+
+const GrTextureEffect* GrFragmentProcessor::asTextureEffect() const {
+    if (this->classID() == kGrTextureEffect_ClassID) {
+        return static_cast<const GrTextureEffect*>(this);
+    }
+    return nullptr;
 }
 
 GrGLSLFragmentProcessor* GrFragmentProcessor::createGLSLInstance() const {
@@ -62,11 +78,6 @@
     return glFragProc;
 }
 
-const GrFragmentProcessor::TextureSampler& GrFragmentProcessor::textureSampler(int i) const {
-    SkASSERT(i >= 0 && i < fTextureSamplerCnt);
-    return this->onTextureSampler(i);
-}
-
 void GrFragmentProcessor::addAndPushFlagToChildren(PrivateFlags flag) {
     // This propagates down, so if we've already marked it, all our children should have it too
     if (!(fFlags & flag)) {
@@ -84,19 +95,13 @@
 
 #ifdef SK_DEBUG
 bool GrFragmentProcessor::isInstantiated() const {
-    for (int i = 0; i < fTextureSamplerCnt; ++i) {
-        if (!this->textureSampler(i).isInstantiated()) {
-            return false;
+    bool result = true;
+    this->visitTextureEffects([&result](const GrTextureEffect& te) {
+        if (!te.texture()) {
+            result = false;
         }
-    }
-
-    for (int i = 0; i < this->numChildProcessors(); ++i) {
-        if (!this->childProcessor(i).isInstantiated()) {
-            return false;
-        }
-    }
-
-    return true;
+    });
+    return result;
 }
 #endif
 
@@ -449,41 +454,8 @@
     }
 }
 
-GrFragmentProcessor::CIter::CIter(const GrProcessorSet& set) {
-    for (int i = set.numCoverageFragmentProcessors() - 1; i >= 0; --i) {
-        fFPStack.push_back(set.coverageFragmentProcessor(i));
-    }
-    for (int i = set.numColorFragmentProcessors() - 1; i >= 0; --i) {
-        fFPStack.push_back(set.colorFragmentProcessor(i));
-    }
-}
-
 GrFragmentProcessor::CIter::CIter(const GrPipeline& pipeline) {
     for (int i = pipeline.numFragmentProcessors() - 1; i >= 0; --i) {
         fFPStack.push_back(&pipeline.getFragmentProcessor(i));
     }
 }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-GrFragmentProcessor::TextureSampler::TextureSampler(GrSurfaceProxyView view,
-                                                    GrSamplerState samplerState)
-        : fView(std::move(view)), fSamplerState(samplerState) {
-    GrSurfaceProxy* proxy = this->proxy();
-    fSamplerState.setFilterMode(
-            std::min(samplerState.filter(),
-                   GrTextureProxy::HighestFilterMode(proxy->backendFormat().textureType())));
-}
-
-#if GR_TEST_UTILS
-void GrFragmentProcessor::TextureSampler::set(GrSurfaceProxyView view,
-                                              GrSamplerState samplerState) {
-    SkASSERT(view.proxy()->asTextureProxy());
-    fView = std::move(view);
-    fSamplerState = samplerState;
-
-    fSamplerState.setFilterMode(
-            std::min(samplerState.filter(),
-                   GrTextureProxy::HighestFilterMode(this->proxy()->backendFormat().textureType())));
-}
-#endif
diff --git a/src/gpu/GrFragmentProcessor.h b/src/gpu/GrFragmentProcessor.h
index 7ea426d..fe634b3 100644
--- a/src/gpu/GrFragmentProcessor.h
+++ b/src/gpu/GrFragmentProcessor.h
@@ -20,14 +20,13 @@
 class GrProcessorKeyBuilder;
 class GrShaderCaps;
 class GrSwizzle;
+class GrTextureEffect;
 
 /** Provides custom fragment shader code. Fragment processors receive an input color (half4) and
     produce an output color. They may reference textures and uniforms.
  */
 class GrFragmentProcessor : public GrProcessor {
 public:
-    class TextureSampler;
-
     /**
     *  In many instances (e.g. SkShader::asFragmentProcessor() implementations) it is desirable to
     *  only consider the input color's alpha. However, there is a competing desire to have reusable
@@ -115,9 +114,6 @@
         }
     }
 
-    int numTextureSamplers() const { return fTextureSamplerCnt; }
-    const TextureSampler& textureSampler(int i) const;
-
     int numVaryingCoordsUsed() const { return this->usesVaryingCoordsDirectly() ? 1 : 0; }
 
     int numChildProcessors() const { return fChildProcessors.count(); }
@@ -231,7 +227,12 @@
      */
     bool isEqual(const GrFragmentProcessor& that) const;
 
-    void visitProxies(const GrOp::VisitProxyFunc& func);
+    void visitProxies(const GrOp::VisitProxyFunc& func) const;
+
+    void visitTextureEffects(const std::function<void(const GrTextureEffect&)>&) const;
+
+    GrTextureEffect* asTextureEffect();
+    const GrTextureEffect* asTextureEffect() const;
 
     // A pre-order traversal iterator over a hierarchy of FPs. It can also iterate over all the FP
     // hierarchies rooted in a GrPaint, GrProcessorSet, or GrPipeline. For these collections it
@@ -251,18 +252,13 @@
     // Used to implement a range-for loop using CIter. Src is one of GrFragmentProcessor,
     // GrPaint, GrProcessorSet, or GrPipeline. Type aliases for these defined below.
     // Example usage:
-    //   for (const auto& fp : GrFragmentProcessor::PaintRange(paint)) {
+    //   for (const auto& fp : GrFragmentProcessor::PaintCRange(paint)) {
     //       if (fp.usesLocalCoords()) {
     //       ...
     //       }
     //   }
     template <typename Src> class CIterRange;
-    // Like CIterRange but non const and only constructable from GrFragmentProcessor. This could
-    // support GrPaint as it owns non-const FPs but no need for it as of now.
-    //   for (auto& fp0 : GrFragmentProcessor::IterRange(fp)) {
-    //       ...
-    //   }
-    class IterRange;
+
 
     // We would use template deduction guides for Iter/CIter but for:
     // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79501
@@ -271,56 +267,8 @@
     using FPCRange = CIterRange<GrFragmentProcessor>;
     using PaintCRange = CIterRange<GrPaint>;
 
-    // Implementation details for iterators that walk an array of Items owned by a set of FPs.
-    using CountFn = int (GrFragmentProcessor::*)() const;
-    // Defined GetFn to be a member function that returns an Item by index. The function itself is
-    // const if Item is a const type and non-const if Item is non-const.
-    template <typename Item, bool IsConst = std::is_const<Item>::value> struct GetT;
-    template <typename Item> struct GetT<Item, false> {
-        using GetFn = Item& (GrFragmentProcessor::*)(int);
-    };
-    template <typename Item> struct GetT<Item, true> {
-        using GetFn = const Item& (GrFragmentProcessor::*)(int) const;
-    };
-    template <typename Item> using GetFn = typename GetT<Item>::GetFn;
-    // This is an iterator over the Items owned by a (collection of) FP. CountFn is a FP member that
-    // gets the number of Items owned by each FP and GetFn is a member that gets them by index.
-    template <typename Item, CountFn Count, GetFn<Item> Get> class FPItemIter;
-
-    // Loops over all the TextureSamplers owned by GrFragmentProcessors. The possible sources for
-    // the iteration are the same as those for Iter and the FPs are walked in the same order as
-    // Iter. This provides access to the texture sampler and the FP that owns it. Example usage:
-    //   for (GrFragmentProcessor::TextureSamplerIter iter(pipeline); iter; ++iter) {
-    //       // TextureSamplerIter is const GrFragmentProcessor::TextureSampler& and
-    //       // owningFP is const GrFragmentProcessor&.
-    //       auto [sampler, owningFP] = *iter;
-    //       ...
-    //   }
-    // See the ranges below to make this simpler a la range-for loops.
-    using TextureSamplerIter = FPItemIter<const TextureSampler,
-                                          &GrFragmentProcessor::numTextureSamplers,
-                                          &GrFragmentProcessor::textureSampler>;
-
-    // Implementation detail for using TextureSamplerIter in range-for loops.
-    template <typename Src, typename ItemIter> class FPItemRange;
-
-    // These allow iteration over texture samplers for various FP sources via range-for loops.
-    // An example usage for looping over the texture samplers in a pipeline:
-    // for (auto [sampler, fp] : GrFragmentProcessor::PipelineTextureSamplerRange(pipeline)) {
-    //     ...
-    // }
-    // Only the combinations of FP sources and iterable things have been defined but it is easy
-    // to add more as they become useful. Maybe someday we'll have template argument deduction
-    // with guides for type aliases and the sources can be removed from the type aliases:
-    // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1021r5.html
-    using PipelineTextureSamplerRange = FPItemRange<const GrPipeline, TextureSamplerIter>;
-    using FPTextureSamplerRange = FPItemRange<const GrFragmentProcessor, TextureSamplerIter>;
-    using ProcessorSetTextureSamplerRange = FPItemRange<const GrProcessorSet, TextureSamplerIter>;
-
     // Sentinel type for range-for using Iter.
     class EndIter {};
-    // Sentinel type for range-for using FPItemIter.
-    class FPItemEndIter {};
 
 protected:
     enum OptimizationFlags : uint32_t {
@@ -414,28 +362,12 @@
      */
     void cloneAndRegisterAllChildProcessors(const GrFragmentProcessor& src);
 
-    void setTextureSamplerCnt(int cnt) {
-        SkASSERT(cnt >= 0);
-        fTextureSamplerCnt = cnt;
-    }
-
     // FP implementations must call this function if their matching GrGLSLFragmentProcessor's
     // emitCode() function uses the EmitArgs::fSampleCoord variable in generated SkSL.
     void setUsesSampleCoordsDirectly() {
         fFlags |= kUsesSampleCoordsDirectly_Flag;
     }
 
-    /**
-     * Helper for implementing onTextureSampler(). E.g.:
-     * return IthTexureSampler(i, fMyFirstSampler, fMySecondSampler, fMyThirdSampler);
-     */
-    template <typename... Args>
-    static const TextureSampler& IthTextureSampler(int i, const TextureSampler& samp0,
-                                                   const Args&... samps) {
-        return (0 == i) ? samp0 : IthTextureSampler(i - 1, samps...);
-    }
-    inline static const TextureSampler& IthTextureSampler(int i);
-
 private:
     // Implementation details of Iter and CIter.
     template <typename> class IterBase;
@@ -459,8 +391,6 @@
      */
     virtual bool onIsEqual(const GrFragmentProcessor&) const = 0;
 
-    virtual const TextureSampler& onTextureSampler(int) const { return IthTextureSampler(0); }
-
     enum PrivateFlags {
         kFirstPrivateFlag = kAll_OptimizationFlags + 1,
 
@@ -476,75 +406,16 @@
     };
     void addAndPushFlagToChildren(PrivateFlags flag);
 
-    uint32_t fFlags = 0;
-
-    int fTextureSamplerCnt = 0;
-
     SkSTArray<1, std::unique_ptr<GrFragmentProcessor>, true> fChildProcessors;
     const GrFragmentProcessor* fParent = nullptr;
+    uint32_t fFlags = 0;
     SkSL::SampleUsage fUsage;
 
     typedef GrProcessor INHERITED;
 };
 
-/**
- * Used to represent a texture that is required by a GrFragmentProcessor. It holds a GrTextureProxy
- * along with an associated GrSamplerState. TextureSamplers don't perform any coord manipulation to
- * account for texture origin.
- */
-class GrFragmentProcessor::TextureSampler {
-public:
-    TextureSampler() = default;
-
-    /**
-     * This copy constructor is used by GrFragmentProcessor::clone() implementations.
-     */
-    explicit TextureSampler(const TextureSampler&) = default;
-
-    TextureSampler(GrSurfaceProxyView, GrSamplerState = {});
-
-    TextureSampler(TextureSampler&&) = default;
-    TextureSampler& operator=(TextureSampler&&) = default;
-    TextureSampler& operator=(const TextureSampler&) = delete;
-
-    bool operator==(const TextureSampler& that) const {
-        return fView == that.fView && fSamplerState == that.fSamplerState;
-    }
-
-    bool operator!=(const TextureSampler& other) const { return !(*this == other); }
-
-    SkDEBUGCODE(bool isInstantiated() const { return this->proxy()->isInstantiated(); })
-
-    // 'peekTexture' should only ever be called after a successful 'instantiate' call
-    GrTexture* peekTexture() const {
-        SkASSERT(this->proxy()->isInstantiated());
-        return this->proxy()->peekTexture();
-    }
-
-    const GrSurfaceProxyView& view() const { return fView; }
-    GrSamplerState samplerState() const { return fSamplerState; }
-
-    bool isInitialized() const { return SkToBool(this->proxy()); }
-
-    GrSurfaceProxy* proxy() const { return fView.proxy(); }
-
-#if GR_TEST_UTILS
-    void set(GrSurfaceProxyView, GrSamplerState);
-#endif
-
-private:
-    GrSurfaceProxyView    fView;
-    GrSamplerState        fSamplerState;
-};
-
 //////////////////////////////////////////////////////////////////////////////
 
-const GrFragmentProcessor::TextureSampler& GrFragmentProcessor::IthTextureSampler(int i) {
-    SK_ABORT("Illegal texture sampler index");
-    static const TextureSampler kBogus;
-    return kBogus;
-}
-
 GR_MAKE_BITFIELD_OPS(GrFragmentProcessor::OptimizationFlags)
 
 //////////////////////////////////////////////////////////////////////////////
@@ -597,7 +468,6 @@
 public:
     explicit CIter(const GrFragmentProcessor& fp) : IterBase(fp) {}
     explicit CIter(const GrPaint&);
-    explicit CIter(const GrProcessorSet&);
     explicit CIter(const GrPipeline&);
     CIter& operator++() {
         this->increment();
@@ -617,60 +487,6 @@
     const Src& fT;
 };
 
-//////////////////////////////////////////////////////////////////////////////
-
-template <typename Item, GrFragmentProcessor::CountFn Count, GrFragmentProcessor::GetFn<Item> Get>
-class GrFragmentProcessor::FPItemIter {
-public:
-    template <typename Src> explicit FPItemIter(Src& s);
-
-    std::pair<Item&, const GrFragmentProcessor&> operator*() const {
-        return {(*fFPIter.*Get)(fIndex), *fFPIter};
-    }
-    FPItemIter& operator++();
-    operator bool() const { return fFPIter; }
-    bool operator!=(const FPItemEndIter&) { return (bool)*this; }
-
-    FPItemIter(const FPItemIter&) = delete;
-    FPItemIter& operator=(const FPItemIter&) = delete;
-
-private:
-    typename std::conditional<std::is_const<Item>::value, CIter, Iter>::type fFPIter;
-    int fIndex;
-};
-
-template <typename Item, GrFragmentProcessor::CountFn Count, GrFragmentProcessor::GetFn<Item> Get>
-template <typename Src>
-GrFragmentProcessor::FPItemIter<Item, Count, Get>::FPItemIter(Src& s) : fFPIter(s), fIndex(-1) {
-    if (fFPIter) {
-        ++*this;
-    }
-}
-
-template <typename Item, GrFragmentProcessor::CountFn Count, GrFragmentProcessor::GetFn<Item> Get>
-GrFragmentProcessor::FPItemIter<Item, Count, Get>&
-GrFragmentProcessor::FPItemIter<Item, Count, Get>::operator++() {
-    ++fIndex;
-    if (fIndex < ((*fFPIter).*Count)()) {
-        return *this;
-    }
-    fIndex = 0;
-    do {} while (++fFPIter && !((*fFPIter).*Count)());
-    return *this;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-template <typename Src, typename ItemIter> class GrFragmentProcessor::FPItemRange {
-public:
-    FPItemRange(Src& src) : fSrc(src) {}
-    ItemIter begin() const { return ItemIter(fSrc); }
-    FPItemEndIter end() const { return FPItemEndIter(); }
-
-private:
-    Src& fSrc;
-};
-
 /**
  * Some fragment-processor creation methods have preconditions that might not be satisfied by the
  * calling code. Those methods can return a `GrFPResult` from their factory methods. If creation
diff --git a/src/gpu/GrPipeline.cpp b/src/gpu/GrPipeline.cpp
index 58c8f0c..95cc8a7 100644
--- a/src/gpu/GrPipeline.cpp
+++ b/src/gpu/GrPipeline.cpp
@@ -108,11 +108,17 @@
     b->add32(blendKey);
 }
 
+void GrPipeline::visitTextureEffects(
+        const std::function<void(const GrTextureEffect&)>& func) const {
+    for (auto& fp : fFragmentProcessors) {
+        fp->visitTextureEffects(func);
+    }
+}
+
 void GrPipeline::visitProxies(const GrOp::VisitProxyFunc& func) const {
     // This iteration includes any clip coverage FPs
-    for (auto [sampler, fp] : GrFragmentProcessor::PipelineTextureSamplerRange(*this)) {
-        bool mipped = (GrSamplerState::Filter::kMipMap == sampler.samplerState().filter());
-        func(sampler.view().proxy(), GrMipMapped(mipped));
+    for (auto& fp : fFragmentProcessors) {
+        fp->visitProxies(func);
     }
     if (fDstProxyView.asTextureProxy()) {
         func(fDstProxyView.asTextureProxy(), GrMipMapped::kNo);
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index a59adde..11eb8b0 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -113,6 +113,8 @@
     }
     int numFragmentProcessors() const { return fFragmentProcessors.count(); }
 
+    void visitTextureEffects(const std::function<void(const GrTextureEffect&)>&) const;
+
     const GrXferProcessor& getXferProcessor() const {
         if (fXferProcessor) {
             return *fXferProcessor.get();
diff --git a/src/gpu/GrProcessorSet.cpp b/src/gpu/GrProcessorSet.cpp
index 48fb9af..b8f077d 100644
--- a/src/gpu/GrProcessorSet.cpp
+++ b/src/gpu/GrProcessorSet.cpp
@@ -253,8 +253,7 @@
 }
 
 void GrProcessorSet::visitProxies(const GrOp::VisitProxyFunc& func) const {
-    for (auto [sampler, fp] : GrFragmentProcessor::ProcessorSetTextureSamplerRange(*this)) {
-        bool mipped = (GrSamplerState::Filter::kMipMap == sampler.samplerState().filter());
-        func(sampler.view().proxy(), GrMipMapped(mipped));
+    for (int i = fFragmentProcessorOffset; i < fFragmentProcessors.count(); ++i) {
+        fFragmentProcessors[i]->visitProxies(func);
     }
 }
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index 2a9f1ae..3383fcf 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -56,24 +56,6 @@
     return SkToU32(samplerTypeKey | swizzleKey << kSamplerOrImageTypeKeyBits);
 }
 
-static void add_fp_sampler_keys(GrProcessorKeyBuilder* b, const GrFragmentProcessor& fp,
-                                const GrCaps& caps) {
-    int numTextureSamplers = fp.numTextureSamplers();
-    if (!numTextureSamplers) {
-        return;
-    }
-    for (int i = 0; i < numTextureSamplers; ++i) {
-        const GrFragmentProcessor::TextureSampler& sampler = fp.textureSampler(i);
-        const GrBackendFormat& backendFormat = sampler.view().proxy()->backendFormat();
-
-        uint32_t samplerKey = sampler_key(backendFormat.textureType(), sampler.view().swizzle(),
-                                          caps);
-        b->add32(samplerKey);
-
-        caps.addExtraSamplerKey(b, sampler.samplerState(), backendFormat);
-    }
-}
-
 static void add_pp_sampler_keys(GrProcessorKeyBuilder* b, const GrPrimitiveProcessor& pp,
                                 const GrCaps& caps) {
     int numTextureSamplers = pp.numTextureSamplers();
@@ -113,7 +95,12 @@
         return false;
     }
 
-    add_fp_sampler_keys(b, fp, caps);
+    fp.visitTextureEffects([&](const GrTextureEffect& te) {
+        const GrBackendFormat& backendFormat = te.view().proxy()->backendFormat();
+        uint32_t samplerKey = sampler_key(backendFormat.textureType(), te.view().swizzle(), caps);
+        b->add32(samplerKey);
+        caps.addExtraSamplerKey(b, te.samplerState(), backendFormat);
+    });
 
     uint32_t* key = b->add32n(2);
     key[0] = (classID << 16) | SkToU32(processorKeySize);
diff --git a/src/gpu/GrProgramInfo.cpp b/src/gpu/GrProgramInfo.cpp
index 43b307a..34e6459 100644
--- a/src/gpu/GrProgramInfo.cpp
+++ b/src/gpu/GrProgramInfo.cpp
@@ -31,25 +31,26 @@
 }
 
 void GrProgramInfo::checkAllInstantiated() const {
-    for (auto [sampler, fp] : GrFragmentProcessor::PipelineTextureSamplerRange(this->pipeline())) {
-        SkASSERT(sampler.proxy()->isInstantiated());
-    }
+    this->pipeline().visitProxies([](GrSurfaceProxy* proxy, GrMipMapped) {
+        SkASSERT(proxy->isInstantiated());
+        return true;
+    });
 }
 
 void GrProgramInfo::checkMSAAAndMIPSAreResolved() const {
-    for (auto [sampler, fp] : GrFragmentProcessor::PipelineTextureSamplerRange(this->pipeline())) {
-        GrTexture* tex = sampler.peekTexture();
+    this->pipeline().visitTextureEffects([](const GrTextureEffect& te) {
+        GrTexture* tex = te.texture();
         SkASSERT(tex);
 
         // Ensure mipmaps were all resolved ahead of time by the DAG.
-        if (GrSamplerState::Filter::kMipMap == sampler.samplerState().filter() &&
+        if (GrSamplerState::Filter::kMipMap == te.samplerState().filter() &&
             (tex->width() != 1 || tex->height() != 1)) {
-            // There are some cases where we might be given a non-mipmapped texture with a mipmap
-            // filter. See skbug.com/7094.
+            // There are some cases where we might be given a non-mipmapped texture with a
+            // mipmap filter. See skbug.com/7094.
             SkASSERT(tex->texturePriv().mipMapped() != GrMipMapped::kYes ||
                      !tex->texturePriv().mipMapsAreDirty());
         }
-    }
+    });
 }
 
 #endif
diff --git a/src/gpu/d3d/GrD3DOpsRenderPass.cpp b/src/gpu/d3d/GrD3DOpsRenderPass.cpp
index baac3d2..84ee42a 100644
--- a/src/gpu/d3d/GrD3DOpsRenderPass.cpp
+++ b/src/gpu/d3d/GrD3DOpsRenderPass.cpp
@@ -213,10 +213,11 @@
     for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
         update_resource_state(primProcTextures[i]->peekTexture(), fRenderTarget, fGpu);
     }
-    GrFragmentProcessor::PipelineTextureSamplerRange textureSamplerRange(pipeline);
-    for (auto [sampler, fp] : textureSamplerRange) {
-        update_resource_state(sampler.peekTexture(), fRenderTarget, fGpu);
-    }
+
+    pipeline.visitTextureEffects([&](const GrTextureEffect& te) {
+        update_resource_state(te.texture(), fRenderTarget, fGpu);
+    });
+
     if (GrTexture* dstTexture = pipeline.peekDstTexture()) {
         update_resource_state(dstTexture, fRenderTarget, fGpu);
     }
diff --git a/src/gpu/d3d/GrD3DPipelineState.cpp b/src/gpu/d3d/GrD3DPipelineState.cpp
index c91990a..5f8a73a 100644
--- a/src/gpu/d3d/GrD3DPipelineState.cpp
+++ b/src/gpu/d3d/GrD3DPipelineState.cpp
@@ -110,20 +110,15 @@
         rangeSizes[currTextureBinding++] = 1;
     }
 
-    for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
-        for (auto& fp : GrFragmentProcessor::FPCRange(pipeline.getFragmentProcessor(i))) {
-            for (int s = 0; s < fp.numTextureSamplers(); ++s) {
-                const auto& sampler = fp.textureSampler(s);
-                auto texture = static_cast<GrD3DTexture*>(sampler.peekTexture());
-                shaderResourceViews[currTextureBinding] = texture->shaderResourceView();
-                samplers[currTextureBinding] =
-                        gpu->resourceProvider().findOrCreateCompatibleSampler(
-                                sampler.samplerState());
-                gpu->currentCommandList()->addSampledTextureRef(texture);
-                rangeSizes[currTextureBinding++] = 1;
-            }
-        }
-    }
+    pipeline.visitTextureEffects([&](const GrTextureEffect& te) {
+        GrSamplerState samplerState = te.samplerState();
+        auto* texture = static_cast<GrD3DTexture*>(te.texture());
+        shaderResourceViews[currTextureBinding] = texture->shaderResourceView();
+        samplers[currTextureBinding] =
+                gpu->resourceProvider().findOrCreateCompatibleSampler(samplerState);
+        gpu->currentCommandList()->addSampledTextureRef(texture);
+        rangeSizes[currTextureBinding++] = 1;
+    });
 
     if (GrTexture* dstTexture = pipeline.peekDstTexture()) {
         auto texture = static_cast<GrD3DTexture*>(dstTexture);
diff --git a/src/gpu/dawn/GrDawnProgramBuilder.cpp b/src/gpu/dawn/GrDawnProgramBuilder.cpp
index 7d388ca..9680897 100644
--- a/src/gpu/dawn/GrDawnProgramBuilder.cpp
+++ b/src/gpu/dawn/GrDawnProgramBuilder.cpp
@@ -567,15 +567,9 @@
         }
     }
 
-    for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
-        for (auto& fp : GrFragmentProcessor::FPCRange(pipeline.getFragmentProcessor(i))) {
-            for (int s = 0; s < fp.numTextureSamplers(); ++s) {
-                const auto& sampler = fp.textureSampler(s);
-                set_texture(gpu, sampler.samplerState(), sampler.peekTexture(), &bindings,
-                            &binding);
-            }
-        }
-    }
+    pipeline.visitTextureEffects([&](const GrTextureEffect& te) {
+        set_texture(gpu, te.samplerState(), te.texture(), &bindings, &binding);
+    });
 
     SkIPoint offset;
     if (GrTexture* dstTexture = pipeline.peekDstTexture(&offset)) {
diff --git a/src/gpu/effects/GrTextureEffect.cpp b/src/gpu/effects/GrTextureEffect.cpp
index fe7792a..07591d5 100644
--- a/src/gpu/effects/GrTextureEffect.cpp
+++ b/src/gpu/effects/GrTextureEffect.cpp
@@ -11,8 +11,6 @@
 #include "src/gpu/GrTexture.h"
 #include "src/gpu/GrTexturePriv.h"
 #include "src/gpu/effects/GrMatrixEffect.h"
-#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
-#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
 #include "src/gpu/glsl/GrGLSLProgramBuilder.h"
 #include "src/sksl/SkSLCPP.h"
 #include "src/sksl/SkSLUtil.h"
@@ -308,442 +306,427 @@
     return m == ShaderMode::kClampToBorderNearest || m == ShaderMode::kClampToBorderFilter;
 }
 
-GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
-    class Impl : public GrGLSLFragmentProcessor {
-        UniformHandle fSubsetUni;
-        UniformHandle fClampUni;
-        UniformHandle fNormUni;
-        UniformHandle fBorderUni;
+void GrTextureEffect::Impl::emitCode(EmitArgs& args) {
+    using ShaderMode = GrTextureEffect::ShaderMode;
 
-    public:
-        void emitCode(EmitArgs& args) override {
-            auto& te = args.fFp.cast<GrTextureEffect>();
-            auto* fb = args.fFragBuilder;
+    auto& te = args.fFp.cast<GrTextureEffect>();
+    auto* fb = args.fFragBuilder;
 
-            if (te.fShaderModes[0] == ShaderMode::kNone &&
-                te.fShaderModes[1] == ShaderMode::kNone) {
-                fb->codeAppendf("%s = ", args.fOutputColor);
-                if (te.fLazyProxyNormalization) {
-                    const char* norm = nullptr;
-                    fNormUni = args.fUniformHandler->addUniform(&te, kFragment_GrShaderFlag,
-                                                                kFloat4_GrSLType, "norm", &norm);
-                    fb->appendTextureLookupAndBlend(args.fInputColor, SkBlendMode::kModulate,
-                                                    args.fTexSamplers[0],
-                                                    SkStringPrintf("%s * %s.zw", args.fSampleCoord,
-                                                                   norm).c_str());
-                } else {
-                    fb->appendTextureLookupAndBlend(args.fInputColor, SkBlendMode::kModulate,
-                                                    args.fTexSamplers[0], args.fSampleCoord);
-                }
-                fb->codeAppendf(";");
+    if (te.fShaderModes[0] == ShaderMode::kNone &&
+        te.fShaderModes[1] == ShaderMode::kNone) {
+        fb->codeAppendf("%s = ", args.fOutputColor);
+        if (te.fLazyProxyNormalization) {
+            const char* norm = nullptr;
+            fNormUni = args.fUniformHandler->addUniform(&te, kFragment_GrShaderFlag,
+                                                        kFloat4_GrSLType, "norm", &norm);
+            SkString coordString = SkStringPrintf("%s * %s.zw", args.fSampleCoord, norm);
+            fb->appendTextureLookupAndBlend(args.fInputColor,
+                                            SkBlendMode::kModulate,
+                                            fSamplerHandle,
+                                            coordString.c_str());
+        } else {
+            fb->appendTextureLookupAndBlend(args.fInputColor, SkBlendMode::kModulate,
+                                            fSamplerHandle, args.fSampleCoord);
+        }
+        fb->codeAppendf(";");
+    } else {
+        // Tripping this assert means we have a normalized fully lazy proxy with a
+        // non-default ShaderMode. There's nothing fundamentally wrong with doing that, but
+        // it hasn't been tested and this code path probably won't handle normalization
+        // properly in that case.
+        SkASSERT(!te.fLazyProxyNormalization);
+        // Here is the basic flow of the various ShaderModes are implemented in a series of
+        // steps. Not all the steps apply to all the modes. We try to emit only the steps
+        // that are necessary for the given x/y shader modes.
+        //
+        // 0) Start with interpolated coordinates (unnormalize if doing anything
+        //    complicated).
+        // 1) Map the coordinates into the subset range [Repeat and MirrorRepeat], or pass
+        //    through output of 0).
+        // 2) Clamp the coordinates to a 0.5 inset of the subset rect [Clamp, Repeat, and
+        //    MirrorRepeat always or ClampToBorder only when filtering] or pass through
+        //    output of 1). The clamp rect collapses to a line or point it if the subset
+        //    rect is less than one pixel wide/tall.
+        // 3) Look up texture with output of 2) [All]
+        // 3) Use the difference between 1) and 2) to apply filtering at edge [Repeat or
+        //    ClampToBorder]. In the Repeat case this requires extra texture lookups on the
+        //    other side of the subset (up to 3 more reads). Or if ClampToBorder and not
+        //    filtering do a hard less than/greater than test with the subset rect.
+
+        // Convert possible projective texture coordinates into non-homogeneous half2.
+        fb->codeAppendf("float2 inCoord = %s;", args.fSampleCoord);
+
+        const auto& m = te.fShaderModes;
+        GrTextureType textureType = te.view().proxy()->backendFormat().textureType();
+        bool normCoords = textureType != GrTextureType::kRectangle;
+
+        const char* borderName = nullptr;
+        if (te.hasClampToBorderShaderMode()) {
+            fBorderUni = args.fUniformHandler->addUniform(
+                    &te, kFragment_GrShaderFlag, kHalf4_GrSLType, "border", &borderName);
+        }
+        auto modeUsesSubset = [](ShaderMode m) {
+          switch (m) {
+              case ShaderMode::kNone:                     return false;
+              case ShaderMode::kClamp:                    return false;
+              case ShaderMode::kRepeatNearest:            return true;
+              case ShaderMode::kRepeatBilerp:             return true;
+              case ShaderMode::kRepeatMipMap:             return true;
+              case ShaderMode::kMirrorRepeat:             return true;
+              case ShaderMode::kClampToBorderNearest:     return true;
+              case ShaderMode::kClampToBorderFilter:      return true;
+          }
+          SkUNREACHABLE;
+        };
+
+        auto modeUsesClamp = [](ShaderMode m) {
+          switch (m) {
+              case ShaderMode::kNone:                     return false;
+              case ShaderMode::kClamp:                    return true;
+              case ShaderMode::kRepeatNearest:            return true;
+              case ShaderMode::kRepeatBilerp:             return true;
+              case ShaderMode::kRepeatMipMap:             return true;
+              case ShaderMode::kMirrorRepeat:             return true;
+              case ShaderMode::kClampToBorderNearest:     return false;
+              case ShaderMode::kClampToBorderFilter:      return true;
+          }
+          SkUNREACHABLE;
+        };
+
+        // To keep things a little simpler, when we have filtering logic in the shader we
+        // operate on unnormalized texture coordinates. We will add a uniform that stores
+        // {w, h, 1/w, 1/h} in a float4 below.
+        auto modeRequiresUnormCoords = [](ShaderMode m) {
+          switch (m) {
+              case ShaderMode::kNone:                     return false;
+              case ShaderMode::kClamp:                    return false;
+              case ShaderMode::kRepeatNearest:            return false;
+              case ShaderMode::kRepeatBilerp:             return true;
+              case ShaderMode::kRepeatMipMap:             return true;
+              case ShaderMode::kMirrorRepeat:             return false;
+              case ShaderMode::kClampToBorderNearest:     return true;
+              case ShaderMode::kClampToBorderFilter:      return true;
+          }
+          SkUNREACHABLE;
+        };
+
+        bool useSubset[2] = {modeUsesSubset(m[0]), modeUsesSubset(m[1])};
+        bool useClamp [2] = {modeUsesClamp (m[0]), modeUsesClamp (m[1])};
+
+        const char* subsetName = nullptr;
+        if (useSubset[0] || useSubset[1]) {
+            fSubsetUni = args.fUniformHandler->addUniform(
+                    &te, kFragment_GrShaderFlag, kFloat4_GrSLType, "subset", &subsetName);
+        }
+
+        const char* clampName = nullptr;
+        if (useClamp[0] || useClamp[1]) {
+            fClampUni = args.fUniformHandler->addUniform(
+                    &te, kFragment_GrShaderFlag, kFloat4_GrSLType, "clamp", &clampName);
+        }
+
+        const char* norm = nullptr;
+        if (normCoords && (modeRequiresUnormCoords(m[0]) ||
+                           modeRequiresUnormCoords(m[1]))) {
+            // TODO: Detect support for textureSize() or polyfill textureSize() in SkSL and
+            // always use?
+            fNormUni = args.fUniformHandler->addUniform(&te, kFragment_GrShaderFlag,
+                                                        kFloat4_GrSLType, "norm", &norm);
+            // TODO: Remove the normalization from the CoordTransform to skip unnormalizing
+            // step here.
+            fb->codeAppendf("inCoord *= %s.xy;", norm);
+        }
+
+        // Generates a string to read at a coordinate, normalizing coords if necessary.
+        auto read = [&](const char* coord) {
+            SkString result;
+            SkString normCoord;
+            if (norm) {
+                normCoord.printf("(%s) * %s.zw", coord, norm);
             } else {
-                // Tripping this assert means we have a normalized fully lazy proxy with a
-                // non-default ShaderMode. There's nothing fundamentally wrong with doing that, but
-                // it hasn't been tested and this code path probably won't handle normalization
-                // properly in that case.
-                SkASSERT(!te.fLazyProxyNormalization);
-                // Here is the basic flow of the various ShaderModes are implemented in a series of
-                // steps. Not all the steps apply to all the modes. We try to emit only the steps
-                // that are necessary for the given x/y shader modes.
-                //
-                // 0) Start with interpolated coordinates (unnormalize if doing anything
-                //    complicated).
-                // 1) Map the coordinates into the subset range [Repeat and MirrorRepeat], or pass
-                //    through output of 0).
-                // 2) Clamp the coordinates to a 0.5 inset of the subset rect [Clamp, Repeat, and
-                //    MirrorRepeat always or ClampToBorder only when filtering] or pass through
-                //    output of 1). The clamp rect collapses to a line or point it if the subset
-                //    rect is less than one pixel wide/tall.
-                // 3) Look up texture with output of 2) [All]
-                // 3) Use the difference between 1) and 2) to apply filtering at edge [Repeat or
-                //    ClampToBorder]. In the Repeat case this requires extra texture lookups on the
-                //    other side of the subset (up to 3 more reads). Or if ClampToBorder and not
-                //    filtering do a hard less than/greater than test with the subset rect.
+                normCoord = coord;
+            }
+            fb->appendTextureLookup(&result, fSamplerHandle, normCoord.c_str());
+            return result;
+        };
 
-                // Convert possible projective texture coordinates into non-homogeneous half2.
-                fb->codeAppendf("float2 inCoord = %s;", args.fSampleCoord);
-
-                const auto& m = te.fShaderModes;
-                GrTextureType textureType = te.fSampler.proxy()->backendFormat().textureType();
-                bool normCoords = textureType != GrTextureType::kRectangle;
-
-                const char* borderName = nullptr;
-                if (te.hasClampToBorderShaderMode()) {
-                    fBorderUni = args.fUniformHandler->addUniform(
-                            &te, kFragment_GrShaderFlag, kHalf4_GrSLType, "border", &borderName);
-                }
-                auto modeUsesSubset = [](ShaderMode m) {
-                    switch (m) {
-                        case ShaderMode::kNone:                     return false;
-                        case ShaderMode::kClamp:                    return false;
-                        case ShaderMode::kRepeatNearest:            return true;
-                        case ShaderMode::kRepeatBilerp:             return true;
-                        case ShaderMode::kRepeatMipMap:             return true;
-                        case ShaderMode::kMirrorRepeat:             return true;
-                        case ShaderMode::kClampToBorderNearest:     return true;
-                        case ShaderMode::kClampToBorderFilter:      return true;
-                    }
-                    SkUNREACHABLE;
-                };
-
-                auto modeUsesClamp = [](ShaderMode m) {
-                    switch (m) {
-                        case ShaderMode::kNone:                     return false;
-                        case ShaderMode::kClamp:                    return true;
-                        case ShaderMode::kRepeatNearest:            return true;
-                        case ShaderMode::kRepeatBilerp:             return true;
-                        case ShaderMode::kRepeatMipMap:             return true;
-                        case ShaderMode::kMirrorRepeat:             return true;
-                        case ShaderMode::kClampToBorderNearest:     return false;
-                        case ShaderMode::kClampToBorderFilter:      return true;
-                    }
-                    SkUNREACHABLE;
-                };
-
-                // To keep things a little simpler, when we have filtering logic in the shader we
-                // operate on unnormalized texture coordinates. We will add a uniform that stores
-                // {w, h, 1/w, 1/h} in a float4 below.
-                auto modeRequiresUnormCoords = [](ShaderMode m) {
-                  switch (m) {
-                      case ShaderMode::kNone:                     return false;
-                      case ShaderMode::kClamp:                    return false;
-                      case ShaderMode::kRepeatNearest:            return false;
-                      case ShaderMode::kRepeatBilerp:             return true;
-                      case ShaderMode::kRepeatMipMap:             return true;
-                      case ShaderMode::kMirrorRepeat:             return false;
-                      case ShaderMode::kClampToBorderNearest:     return true;
-                      case ShaderMode::kClampToBorderFilter:      return true;
-                  }
-                  SkUNREACHABLE;
-                };
-
-                bool useSubset[2] = {modeUsesSubset(m[0]), modeUsesSubset(m[1])};
-                bool useClamp [2] = {modeUsesClamp (m[0]), modeUsesClamp (m[1])};
-
-                const char* subsetName = nullptr;
-                if (useSubset[0] || useSubset[1]) {
-                    fSubsetUni = args.fUniformHandler->addUniform(
-                            &te, kFragment_GrShaderFlag, kFloat4_GrSLType, "subset", &subsetName);
-                }
-
-                const char* clampName = nullptr;
-                if (useClamp[0] || useClamp[1]) {
-                    fClampUni = args.fUniformHandler->addUniform(
-                            &te, kFragment_GrShaderFlag, kFloat4_GrSLType, "clamp", &clampName);
-                }
-
-                const char* norm = nullptr;
-                if (normCoords && (modeRequiresUnormCoords(m[0]) ||
-                                   modeRequiresUnormCoords(m[1]))) {
-                    // TODO: Detect support for textureSize() or polyfill textureSize() in SkSL and
-                    // always use?
-                    fNormUni = args.fUniformHandler->addUniform(&te, kFragment_GrShaderFlag,
-                                                                kFloat4_GrSLType, "norm", &norm);
-                    // TODO: Remove the normalization from the CoordTransform to skip unnormalizing
-                    // step here.
-                    fb->codeAppendf("inCoord *= %s.xy;", norm);
-                }
-
-                // Generates a string to read at a coordinate, normalizing coords if necessary.
-                auto read = [&](const char* coord) {
-                    SkString result;
-                    SkString normCoord;
-                    if (norm) {
-                        normCoord.printf("(%s) * %s.zw", coord, norm);
-                    } else {
-                        normCoord = coord;
-                    }
-                    fb->appendTextureLookup(&result, args.fTexSamplers[0], normCoord.c_str());
-                    return result;
-                };
-
-                // Implements coord wrapping for kRepeat and kMirrorRepeat
-                auto subsetCoord = [&](ShaderMode mode,
-                                       const char* coordSwizzle,
-                                       const char* subsetStartSwizzle,
-                                       const char* subsetStopSwizzle,
-                                       const char* extraCoord,
-                                       const char* coordWeight) {
-                    switch (mode) {
-                        // These modes either don't use the subset rect or don't need to map the
-                        // coords to be within the subset.
-                        case ShaderMode::kNone:
-                        case ShaderMode::kClampToBorderNearest:
-                        case ShaderMode::kClampToBorderFilter:
-                        case ShaderMode::kClamp:
-                            fb->codeAppendf("subsetCoord.%s = inCoord.%s;", coordSwizzle,
-                                            coordSwizzle);
-                            break;
-                        case ShaderMode::kRepeatNearest:
-                        case ShaderMode::kRepeatBilerp:
-                            fb->codeAppendf(
-                                    "subsetCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + "
-                                    "%s.%s;",
-                                    coordSwizzle, coordSwizzle, subsetName, subsetStartSwizzle,
-                                    subsetName, subsetStopSwizzle, subsetName, subsetStartSwizzle,
+        // Implements coord wrapping for kRepeat and kMirrorRepeat
+        auto subsetCoord = [&](ShaderMode mode,
+                               const char* coordSwizzle,
+                               const char* subsetStartSwizzle,
+                               const char* subsetStopSwizzle,
+                               const char* extraCoord,
+                               const char* coordWeight) {
+            switch (mode) {
+                // These modes either don't use the subset rect or don't need to map the
+                // coords to be within the subset.
+                case ShaderMode::kNone:
+                case ShaderMode::kClampToBorderNearest:
+                case ShaderMode::kClampToBorderFilter:
+                case ShaderMode::kClamp:
+                    fb->codeAppendf("subsetCoord.%s = inCoord.%s;", coordSwizzle, coordSwizzle);
+                    break;
+                case ShaderMode::kRepeatNearest:
+                case ShaderMode::kRepeatBilerp:
+                    fb->codeAppendf(
+                            "subsetCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + "
+                            "%s.%s;",
+                            coordSwizzle, coordSwizzle, subsetName, subsetStartSwizzle, subsetName,
+                            subsetStopSwizzle, subsetName, subsetStartSwizzle, subsetName,
+                            subsetStartSwizzle);
+                    break;
+                case ShaderMode::kRepeatMipMap:
+                    // The approach here is to generate two sets of texture coords that
+                    // are both "moving" at the same speed (if not direction) as
+                    // inCoords. We accomplish that by using two out of phase mirror
+                    // repeat coords. We will always sample using both coords but the
+                    // read from the upward sloping one is selected using a weight
+                    // that transitions from one set to the other near the reflection
+                    // point. Like the coords, the weight is a saw-tooth function,
+                    // phase-shifted, vertically translated, and then clamped to 0..1.
+                    // TODO: Skip this and use textureGrad() when available.
+                    SkASSERT(extraCoord);
+                    SkASSERT(coordWeight);
+                    fb->codeAppend("{");
+                    fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName, subsetStopSwizzle,
                                     subsetName, subsetStartSwizzle);
-                            break;
-                        case ShaderMode::kRepeatMipMap:
-                            // The approach here is to generate two sets of texture coords that
-                            // are both "moving" at the same speed (if not direction) as
-                            // inCoords. We accomplish that by using two out of phase mirror
-                            // repeat coords. We will always sample using both coords but the
-                            // read from the upward sloping one is selected using a weight
-                            // that transitions from one set to the other near the reflection
-                            // point. Like the coords, the weight is a saw-tooth function,
-                            // phase-shifted, vertically translated, and then clamped to 0..1.
-                            // TODO: Skip this and use textureGrad() when available.
-                            SkASSERT(extraCoord);
-                            SkASSERT(coordWeight);
-                            fb->codeAppend("{");
-                            fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName,
-                                            subsetStopSwizzle, subsetName, subsetStartSwizzle);
-                            fb->codeAppendf("float w2 = 2 * w;");
-                            fb->codeAppendf("float d = inCoord.%s - %s.%s;", coordSwizzle,
-                                            subsetName, subsetStartSwizzle);
-                            fb->codeAppend("float m = mod(d, w2);");
-                            fb->codeAppend("float o = mix(m, w2 - m, step(w, m));");
-                            fb->codeAppendf("subsetCoord.%s = o + %s.%s;", coordSwizzle, subsetName,
-                                            subsetStartSwizzle);
-                            fb->codeAppendf("%s = w - o + %s.%s;", extraCoord, subsetName,
-                                            subsetStartSwizzle);
-                            // coordWeight is used as the third param of mix() to blend between a
-                            // sample taken using subsetCoord and a sample at extraCoord.
-                            fb->codeAppend("float hw = w/2;");
-                            fb->codeAppend("float n = mod(d - hw, w2);");
-                            fb->codeAppendf(
-                                    "%s = saturate(half(mix(n, w2 - n, step(w, n)) - hw + "
-                                    "0.5));",
-                                    coordWeight);
-                            fb->codeAppend("}");
-                            break;
-                        case ShaderMode::kMirrorRepeat:
-                            fb->codeAppend("{");
-                            fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName,
-                                            subsetStopSwizzle, subsetName, subsetStartSwizzle);
-                            fb->codeAppendf("float w2 = 2 * w;");
-                            fb->codeAppendf("float m = mod(inCoord.%s - %s.%s, w2);", coordSwizzle,
-                                            subsetName, subsetStartSwizzle);
-                            fb->codeAppendf("subsetCoord.%s = mix(m, w2 - m, step(w, m)) + %s.%s;",
-                                            coordSwizzle, subsetName, subsetStartSwizzle);
-                            fb->codeAppend("}");
-                            break;
-                    }
-                };
-
-                auto clampCoord = [&](bool clamp,
-                                      const char* coordSwizzle,
-                                      const char* clampStartSwizzle,
-                                      const char* clampStopSwizzle) {
-                    if (clamp) {
-                        fb->codeAppendf("clampedCoord.%s = clamp(subsetCoord.%s, %s.%s, %s.%s);",
-                                        coordSwizzle, coordSwizzle, clampName, clampStartSwizzle,
-                                        clampName, clampStopSwizzle);
-                    } else {
-                        fb->codeAppendf("clampedCoord.%s = subsetCoord.%s;", coordSwizzle,
-                                        coordSwizzle);
-                    }
-                };
-
-                // Insert vars for extra coords and blending weights for kRepeatMipMap.
-                const char* extraRepeatCoordX  = nullptr;
-                const char* repeatCoordWeightX = nullptr;
-                const char* extraRepeatCoordY  = nullptr;
-                const char* repeatCoordWeightY = nullptr;
-                if (m[0] == ShaderMode::kRepeatMipMap) {
-                    fb->codeAppend("float extraRepeatCoordX; half repeatCoordWeightX;");
-                    extraRepeatCoordX   = "extraRepeatCoordX";
-                    repeatCoordWeightX  = "repeatCoordWeightX";
-                }
-                if (m[1] == ShaderMode::kRepeatMipMap) {
-                    fb->codeAppend("float extraRepeatCoordY; half repeatCoordWeightY;");
-                    extraRepeatCoordY   = "extraRepeatCoordY";
-                    repeatCoordWeightY  = "repeatCoordWeightY";
-                }
-
-                // Apply subset rect and clamp rect to coords.
-                fb->codeAppend("float2 subsetCoord;");
-                subsetCoord(te.fShaderModes[0], "x", "x", "z", extraRepeatCoordX,
-                            repeatCoordWeightX);
-                subsetCoord(te.fShaderModes[1], "y", "y", "w", extraRepeatCoordY,
-                            repeatCoordWeightY);
-                fb->codeAppend("float2 clampedCoord;");
-                clampCoord(useClamp[0], "x", "x", "z");
-                clampCoord(useClamp[1], "y", "y", "w");
-
-                // Additional clamping for the extra coords for kRepeatMipMap.
-                if (m[0] == ShaderMode::kRepeatMipMap) {
-                    fb->codeAppendf("extraRepeatCoordX = clamp(extraRepeatCoordX, %s.x, %s.z);",
-                                    clampName, clampName);
-                }
-                if (m[1] == ShaderMode::kRepeatMipMap) {
-                    fb->codeAppendf("extraRepeatCoordY = clamp(extraRepeatCoordY, %s.y, %s.w);",
-                                    clampName, clampName);
-                }
-
-                // Do the 2 or 4 texture reads for kRepeatMipMap and then apply the weight(s)
-                // to blend between them. If neither direction is kRepeatMipMap do a single
-                // read at clampedCoord.
-                if (m[0] == ShaderMode::kRepeatMipMap && m[1] == ShaderMode::kRepeatMipMap) {
+                    fb->codeAppendf("float w2 = 2 * w;");
+                    fb->codeAppendf("float d = inCoord.%s - %s.%s;", coordSwizzle, subsetName,
+                                    subsetStartSwizzle);
+                    fb->codeAppend("float m = mod(d, w2);");
+                    fb->codeAppend("float o = mix(m, w2 - m, step(w, m));");
+                    fb->codeAppendf("subsetCoord.%s = o + %s.%s;", coordSwizzle, subsetName,
+                                    subsetStartSwizzle);
+                    fb->codeAppendf("%s = w - o + %s.%s;", extraCoord, subsetName,
+                                    subsetStartSwizzle);
+                    // coordWeight is used as the third param of mix() to blend between a
+                    // sample taken using subsetCoord and a sample at extraCoord.
+                    fb->codeAppend("float hw = w/2;");
+                    fb->codeAppend("float n = mod(d - hw, w2);");
                     fb->codeAppendf(
-                            "half4 textureColor ="
-                            "   mix(mix(%s, %s, repeatCoordWeightX),"
-                            "       mix(%s, %s, repeatCoordWeightX),"
-                            "       repeatCoordWeightY);",
+                            "%s = saturate(half(mix(n, w2 - n, step(w, n)) - hw + "
+                            "0.5));",
+                            coordWeight);
+                    fb->codeAppend("}");
+                    break;
+                case ShaderMode::kMirrorRepeat:
+                    fb->codeAppend("{");
+                    fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName, subsetStopSwizzle,
+                                    subsetName, subsetStartSwizzle);
+                    fb->codeAppendf("float w2 = 2 * w;");
+                    fb->codeAppendf("float m = mod(inCoord.%s - %s.%s, w2);", coordSwizzle,
+                                    subsetName, subsetStartSwizzle);
+                    fb->codeAppendf("subsetCoord.%s = mix(m, w2 - m, step(w, m)) + %s.%s;",
+                                    coordSwizzle, subsetName, subsetStartSwizzle);
+                    fb->codeAppend("}");
+                    break;
+            }
+        };
+
+        auto clampCoord = [&](bool clamp,
+                              const char* coordSwizzle,
+                              const char* clampStartSwizzle,
+                              const char* clampStopSwizzle) {
+            if (clamp) {
+                fb->codeAppendf("clampedCoord.%s = clamp(subsetCoord.%s, %s.%s, %s.%s);",
+                                coordSwizzle, coordSwizzle, clampName, clampStartSwizzle, clampName,
+                                clampStopSwizzle);
+            } else {
+                fb->codeAppendf("clampedCoord.%s = subsetCoord.%s;", coordSwizzle, coordSwizzle);
+            }
+        };
+
+        // Insert vars for extra coords and blending weights for kRepeatMipMap.
+        const char* extraRepeatCoordX  = nullptr;
+        const char* repeatCoordWeightX = nullptr;
+        const char* extraRepeatCoordY  = nullptr;
+        const char* repeatCoordWeightY = nullptr;
+        if (m[0] == ShaderMode::kRepeatMipMap) {
+            fb->codeAppend("float extraRepeatCoordX; half repeatCoordWeightX;");
+            extraRepeatCoordX   = "extraRepeatCoordX";
+            repeatCoordWeightX  = "repeatCoordWeightX";
+        }
+        if (m[1] == ShaderMode::kRepeatMipMap) {
+            fb->codeAppend("float extraRepeatCoordY; half repeatCoordWeightY;");
+            extraRepeatCoordY   = "extraRepeatCoordY";
+            repeatCoordWeightY  = "repeatCoordWeightY";
+        }
+
+        // Apply subset rect and clamp rect to coords.
+        fb->codeAppend("float2 subsetCoord;");
+        subsetCoord(te.fShaderModes[0], "x", "x", "z", extraRepeatCoordX, repeatCoordWeightX);
+        subsetCoord(te.fShaderModes[1], "y", "y", "w", extraRepeatCoordY, repeatCoordWeightY);
+        fb->codeAppend("float2 clampedCoord;");
+        clampCoord(useClamp[0], "x", "x", "z");
+        clampCoord(useClamp[1], "y", "y", "w");
+
+        // Additional clamping for the extra coords for kRepeatMipMap.
+        if (m[0] == ShaderMode::kRepeatMipMap) {
+            fb->codeAppendf("extraRepeatCoordX = clamp(extraRepeatCoordX, %s.x, %s.z);", clampName,
+                            clampName);
+        }
+        if (m[1] == ShaderMode::kRepeatMipMap) {
+            fb->codeAppendf("extraRepeatCoordY = clamp(extraRepeatCoordY, %s.y, %s.w);", clampName,
+                            clampName);
+        }
+
+        // Do the 2 or 4 texture reads for kRepeatMipMap and then apply the weight(s)
+        // to blend between them. If neither direction is kRepeatMipMap do a single
+        // read at clampedCoord.
+        if (m[0] == ShaderMode::kRepeatMipMap && m[1] == ShaderMode::kRepeatMipMap) {
+            fb->codeAppendf(
+                    "half4 textureColor ="
+                    "   mix(mix(%s, %s, repeatCoordWeightX),"
+                    "       mix(%s, %s, repeatCoordWeightX),"
+                    "       repeatCoordWeightY);",
+                    read("clampedCoord").c_str(),
+                    read("float2(extraRepeatCoordX, clampedCoord.y)").c_str(),
+                    read("float2(clampedCoord.x, extraRepeatCoordY)").c_str(),
+                    read("float2(extraRepeatCoordX, extraRepeatCoordY)").c_str());
+
+        } else if (m[0] == ShaderMode::kRepeatMipMap) {
+            fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightX);",
                             read("clampedCoord").c_str(),
-                            read("float2(extraRepeatCoordX, clampedCoord.y)").c_str(),
-                            read("float2(clampedCoord.x, extraRepeatCoordY)").c_str(),
-                            read("float2(extraRepeatCoordX, extraRepeatCoordY)").c_str());
-
-                } else if (m[0] == ShaderMode::kRepeatMipMap) {
-                    fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightX);",
-                                    read("clampedCoord").c_str(),
-                                    read("float2(extraRepeatCoordX, clampedCoord.y)").c_str());
-                } else if (m[1] == ShaderMode::kRepeatMipMap) {
-                    fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightY);",
-                                    read("clampedCoord").c_str(),
-                                    read("float2(clampedCoord.x, extraRepeatCoordY)").c_str());
-                } else {
-                    fb->codeAppendf("half4 textureColor = %s;", read("clampedCoord").c_str());
-                }
-
-                // Strings for extra texture reads used only in kRepeatBilerp
-                SkString repeatBilerpReadX;
-                SkString repeatBilerpReadY;
-
-                // Calculate the amount the coord moved for clamping. This will be used
-                // to implement shader-based filtering for kClampToBorder and kRepeat.
-
-                if (m[0] == ShaderMode::kRepeatBilerp || m[0] == ShaderMode::kClampToBorderFilter) {
-                    fb->codeAppend("half errX = half(subsetCoord.x - clampedCoord.x);");
-                    fb->codeAppendf("float repeatCoordX = errX > 0 ? %s.x : %s.z;", clampName,
-                                    clampName);
-                    repeatBilerpReadX = read("float2(repeatCoordX, clampedCoord.y)");
-                }
-                if (m[1] == ShaderMode::kRepeatBilerp || m[1] == ShaderMode::kClampToBorderFilter) {
-                    fb->codeAppend("half errY = half(subsetCoord.y - clampedCoord.y);");
-                    fb->codeAppendf("float repeatCoordY = errY > 0 ? %s.y : %s.w;", clampName,
-                                    clampName);
-                    repeatBilerpReadY = read("float2(clampedCoord.x, repeatCoordY)");
-                }
-
-                // Add logic for kRepeatBilerp. Do 1 or 3 more texture reads depending
-                // on whether both modes are kRepeat and whether we're near a single subset edge
-                // or a corner. Then blend the multiple reads using the err values calculated
-                // above.
-                const char* ifStr = "if";
-                if (m[0] == ShaderMode::kRepeatBilerp && m[1] == ShaderMode::kRepeatBilerp) {
-                    auto repeatBilerpReadXY = read("float2(repeatCoordX, repeatCoordY)");
-                    fb->codeAppendf(
-                            "if (errX != 0 && errY != 0) {"
-                            "    errX = abs(errX);"
-                            "    textureColor = mix(mix(textureColor, %s, errX),"
-                            "                       mix(%s, %s, errX),"
-                            "                       abs(errY));"
-                            "}",
-                            repeatBilerpReadX.c_str(), repeatBilerpReadY.c_str(),
-                            repeatBilerpReadXY.c_str());
-                    ifStr = "else if";
-                }
-                if (m[0] == ShaderMode::kRepeatBilerp) {
-                    fb->codeAppendf(
-                            "%s (errX != 0) {"
-                            "    textureColor = mix(textureColor, %s, abs(errX));"
-                            "}",
-                            ifStr, repeatBilerpReadX.c_str());
-                }
-                if (m[1] == ShaderMode::kRepeatBilerp) {
-                    fb->codeAppendf(
-                            "%s (errY != 0) {"
-                            "    textureColor = mix(textureColor, %s, abs(errY));"
-                            "}",
-                            ifStr, repeatBilerpReadY.c_str());
-                }
-
-                // Do soft edge shader filtering against border color for kClampToBorderFilter using
-                // the err values calculated above.
-                if (m[0] == ShaderMode::kClampToBorderFilter) {
-                    fb->codeAppendf("textureColor = mix(textureColor, %s, min(abs(errX), 1));",
-                                    borderName);
-                }
-                if (m[1] == ShaderMode::kClampToBorderFilter) {
-                    fb->codeAppendf("textureColor = mix(textureColor, %s, min(abs(errY), 1));",
-                                    borderName);
-                }
-
-                // Do hard-edge shader transition to border color for kClampToBorderNearest at the
-                // subset boundaries. Snap the input coordinates to nearest neighbor (with an
-                // epsilon) before comparing to the subset rect to avoid GPU interpolation errors
-                if (m[0] == ShaderMode::kClampToBorderNearest) {
-                    fb->codeAppendf(
-                            "float snappedX = floor(inCoord.x + 0.001) + 0.5;"
-                            "if (snappedX < %s.x || snappedX > %s.z) {"
-                            "    textureColor = %s;"
-                            "}",
-                            subsetName, subsetName, borderName);
-                }
-                if (m[1] == ShaderMode::kClampToBorderNearest) {
-                    fb->codeAppendf(
-                            "float snappedY = floor(inCoord.y + 0.001) + 0.5;"
-                            "if (snappedY < %s.y || snappedY > %s.w) {"
-                            "    textureColor = %s;"
-                            "}",
-                            subsetName, subsetName, borderName);
-                }
-                fb->codeAppendf("%s = %s * textureColor;", args.fOutputColor, args.fInputColor);
-            }
+                            read("float2(extraRepeatCoordX, clampedCoord.y)").c_str());
+        } else if (m[1] == ShaderMode::kRepeatMipMap) {
+            fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightY);",
+                            read("clampedCoord").c_str(),
+                            read("float2(clampedCoord.x, extraRepeatCoordY)").c_str());
+        } else {
+            fb->codeAppendf("half4 textureColor = %s;", read("clampedCoord").c_str());
         }
 
-    protected:
-        void onSetData(const GrGLSLProgramDataManager& pdm,
-                       const GrFragmentProcessor& fp) override {
-            const auto& te = fp.cast<GrTextureEffect>();
+        // Strings for extra texture reads used only in kRepeatBilerp
+        SkString repeatBilerpReadX;
+        SkString repeatBilerpReadY;
 
-            const float w = te.fSampler.peekTexture()->width();
-            const float h = te.fSampler.peekTexture()->height();
-            const auto& s = te.fSubset;
-            const auto& c = te.fClamp;
+        // Calculate the amount the coord moved for clamping. This will be used
+        // to implement shader-based filtering for kClampToBorder and kRepeat.
 
-            auto type = te.fSampler.peekTexture()->texturePriv().textureType();
-
-            float norm[4] = {w, h, 1.f/w, 1.f/h};
-
-            if (fNormUni.isValid()) {
-                pdm.set4fv(fNormUni, 1, norm);
-                SkASSERT(type != GrTextureType::kRectangle);
-            }
-
-            auto pushRect = [&](float rect[4], UniformHandle uni) {
-                if (te.fSampler.view().origin() == kBottomLeft_GrSurfaceOrigin) {
-                    rect[1] = h - rect[1];
-                    rect[3] = h - rect[3];
-                    std::swap(rect[1], rect[3]);
-                }
-                if (!fNormUni.isValid() && type != GrTextureType::kRectangle) {
-                    rect[0] *= norm[2];
-                    rect[2] *= norm[2];
-                    rect[1] *= norm[3];
-                    rect[3] *= norm[3];
-                }
-                pdm.set4fv(uni, 1, rect);
-            };
-
-            if (fSubsetUni.isValid()) {
-                float subset[] = {s.fLeft, s.fTop, s.fRight, s.fBottom};
-                pushRect(subset, fSubsetUni);
-            }
-            if (fClampUni.isValid()) {
-                float subset[] = {c.fLeft, c.fTop, c.fRight, c.fBottom};
-                pushRect(subset, fClampUni);
-            }
-            if (fBorderUni.isValid()) {
-                pdm.set4fv(fBorderUni, 1, te.fBorder);
-            }
+        if (m[0] == ShaderMode::kRepeatBilerp || m[0] == ShaderMode::kClampToBorderFilter) {
+            fb->codeAppend("half errX = half(subsetCoord.x - clampedCoord.x);");
+            fb->codeAppendf("float repeatCoordX = errX > 0 ? %s.x : %s.z;", clampName, clampName);
+            repeatBilerpReadX = read("float2(repeatCoordX, clampedCoord.y)");
         }
-    };
-    return new Impl;
+        if (m[1] == ShaderMode::kRepeatBilerp || m[1] == ShaderMode::kClampToBorderFilter) {
+            fb->codeAppend("half errY = half(subsetCoord.y - clampedCoord.y);");
+            fb->codeAppendf("float repeatCoordY = errY > 0 ? %s.y : %s.w;", clampName, clampName);
+            repeatBilerpReadY = read("float2(clampedCoord.x, repeatCoordY)");
+        }
+
+        // Add logic for kRepeatBilerp. Do 1 or 3 more texture reads depending
+        // on whether both modes are kRepeat and whether we're near a single subset edge
+        // or a corner. Then blend the multiple reads using the err values calculated
+        // above.
+        const char* ifStr = "if";
+        if (m[0] == ShaderMode::kRepeatBilerp && m[1] == ShaderMode::kRepeatBilerp) {
+            auto repeatBilerpReadXY = read("float2(repeatCoordX, repeatCoordY)");
+            fb->codeAppendf(
+                    "if (errX != 0 && errY != 0) {"
+                    "    errX = abs(errX);"
+                    "    textureColor = mix(mix(textureColor, %s, errX),"
+                    "                       mix(%s, %s, errX),"
+                    "                       abs(errY));"
+                    "}",
+                    repeatBilerpReadX.c_str(), repeatBilerpReadY.c_str(),
+                    repeatBilerpReadXY.c_str());
+            ifStr = "else if";
+        }
+        if (m[0] == ShaderMode::kRepeatBilerp) {
+            fb->codeAppendf(
+                    "%s (errX != 0) {"
+                    "    textureColor = mix(textureColor, %s, abs(errX));"
+                    "}",
+                    ifStr, repeatBilerpReadX.c_str());
+        }
+        if (m[1] == ShaderMode::kRepeatBilerp) {
+            fb->codeAppendf(
+                    "%s (errY != 0) {"
+                    "    textureColor = mix(textureColor, %s, abs(errY));"
+                    "}",
+                    ifStr, repeatBilerpReadY.c_str());
+        }
+
+        // Do soft edge shader filtering against border color for kClampToBorderFilter using
+        // the err values calculated above.
+        if (m[0] == ShaderMode::kClampToBorderFilter) {
+            fb->codeAppendf("textureColor = mix(textureColor, %s, min(abs(errX), 1));", borderName);
+        }
+        if (m[1] == ShaderMode::kClampToBorderFilter) {
+            fb->codeAppendf("textureColor = mix(textureColor, %s, min(abs(errY), 1));", borderName);
+        }
+
+        // Do hard-edge shader transition to border color for kClampToBorderNearest at the
+        // subset boundaries. Snap the input coordinates to nearest neighbor (with an
+        // epsilon) before comparing to the subset rect to avoid GPU interpolation errors
+        if (m[0] == ShaderMode::kClampToBorderNearest) {
+            fb->codeAppendf(
+                    "float snappedX = floor(inCoord.x + 0.001) + 0.5;"
+                    "if (snappedX < %s.x || snappedX > %s.z) {"
+                    "    textureColor = %s;"
+                    "}",
+                    subsetName, subsetName, borderName);
+        }
+        if (m[1] == ShaderMode::kClampToBorderNearest) {
+            fb->codeAppendf(
+                    "float snappedY = floor(inCoord.y + 0.001) + 0.5;"
+                    "if (snappedY < %s.y || snappedY > %s.w) {"
+                    "    textureColor = %s;"
+                    "}",
+                    subsetName, subsetName, borderName);
+        }
+        fb->codeAppendf("%s = %s * textureColor;", args.fOutputColor, args.fInputColor);
+    }
 }
 
+void GrTextureEffect::Impl::onSetData(const GrGLSLProgramDataManager& pdm,
+                                      const GrFragmentProcessor& fp) {
+    const auto& te = fp.cast<GrTextureEffect>();
+
+    const float w = te.texture()->width();
+    const float h = te.texture()->height();
+    const auto& s = te.fSubset;
+    const auto& c = te.fClamp;
+
+    auto type = te.texture()->texturePriv().textureType();
+
+    float norm[4] = {w, h, 1.f/w, 1.f/h};
+
+    if (fNormUni.isValid()) {
+        pdm.set4fv(fNormUni, 1, norm);
+        SkASSERT(type != GrTextureType::kRectangle);
+    }
+
+    auto pushRect = [&](float rect[4], UniformHandle uni) {
+        if (te.view().origin() == kBottomLeft_GrSurfaceOrigin) {
+            rect[1] = h - rect[1];
+            rect[3] = h - rect[3];
+            std::swap(rect[1], rect[3]);
+        }
+        if (!fNormUni.isValid() && type != GrTextureType::kRectangle) {
+            rect[0] *= norm[2];
+            rect[2] *= norm[2];
+            rect[1] *= norm[3];
+            rect[3] *= norm[3];
+        }
+        pdm.set4fv(uni, 1, rect);
+    };
+
+    if (fSubsetUni.isValid()) {
+        float subset[] = {s.fLeft, s.fTop, s.fRight, s.fBottom};
+        pushRect(subset, fSubsetUni);
+    }
+    if (fClampUni.isValid()) {
+        float subset[] = {c.fLeft, c.fTop, c.fRight, c.fBottom};
+        pushRect(subset, fClampUni);
+    }
+    if (fBorderUni.isValid()) {
+        pdm.set4fv(fBorderUni, 1, te.fBorder);
+    }
+}
+
+GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const { return new Impl; }
+
 void GrTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const {
     auto m0 = static_cast<uint32_t>(fShaderModes[0]);
     auto m1 = static_cast<uint32_t>(fShaderModes[1]);
@@ -752,6 +735,12 @@
 
 bool GrTextureEffect::onIsEqual(const GrFragmentProcessor& other) const {
     auto& that = other.cast<GrTextureEffect>();
+    if (fView != that.fView) {
+        return false;
+    }
+    if (fSamplerState != that.fSamplerState) {
+        return false;
+    }
     if (fShaderModes[0] != that.fShaderModes[0] || fShaderModes[1] != that.fShaderModes[1]) {
         return false;
     }
@@ -764,11 +753,14 @@
     return true;
 }
 
-GrTextureEffect::GrTextureEffect(GrSurfaceProxyView view, SkAlphaType alphaType,
-                                 const Sampling& sampling, bool lazyProxyNormalization)
+GrTextureEffect::GrTextureEffect(GrSurfaceProxyView view,
+                                 SkAlphaType alphaType,
+                                 const Sampling& sampling,
+                                 bool lazyProxyNormalization)
         : GrFragmentProcessor(kGrTextureEffect_ClassID,
                               ModulateForSamplerOptFlags(alphaType, sampling.hasBorderAlpha()))
-        , fSampler(std::move(view), sampling.fHWSampler)
+        , fView(std::move(view))
+        , fSamplerState(sampling.fHWSampler)
         , fSubset(sampling.fShaderSubset)
         , fClamp(sampling.fShaderClamp)
         , fShaderModes{sampling.fShaderModes[0], sampling.fShaderModes[1]}
@@ -777,20 +769,19 @@
     // values.
     SkASSERT(fShaderModes[0] != ShaderMode::kNone || (fSubset.fLeft == 0 && fSubset.fRight == 0));
     SkASSERT(fShaderModes[1] != ShaderMode::kNone || (fSubset.fTop == 0 && fSubset.fBottom == 0));
-    this->setTextureSamplerCnt(1);
     this->setUsesSampleCoordsDirectly();
     std::copy_n(sampling.fBorder, 4, fBorder);
 }
 
 GrTextureEffect::GrTextureEffect(const GrTextureEffect& src)
         : INHERITED(kGrTextureEffect_ClassID, src.optimizationFlags())
-        , fSampler(src.fSampler)
+        , fView(src.fView)
+        , fSamplerState(src.fSamplerState)
         , fSubset(src.fSubset)
         , fClamp(src.fClamp)
         , fShaderModes{src.fShaderModes[0], src.fShaderModes[1]}
         , fLazyProxyNormalization(src.fLazyProxyNormalization) {
     std::copy_n(src.fBorder, 4, fBorder);
-    this->setTextureSamplerCnt(1);
     this->setUsesSampleCoordsDirectly();
 }
 
@@ -798,10 +789,6 @@
     return std::unique_ptr<GrFragmentProcessor>(new GrTextureEffect(*this));
 }
 
-const GrFragmentProcessor::TextureSampler& GrTextureEffect::onTextureSampler(int) const {
-    return fSampler;
-}
-
 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureEffect);
 #if GR_TEST_UTILS
 std::unique_ptr<GrFragmentProcessor> GrTextureEffect::TestCreate(GrProcessorTestData* testData) {
diff --git a/src/gpu/effects/GrTextureEffect.h b/src/gpu/effects/GrTextureEffect.h
index 511cdb6..4692063 100644
--- a/src/gpu/effects/GrTextureEffect.h
+++ b/src/gpu/effects/GrTextureEffect.h
@@ -11,6 +11,8 @@
 #include "include/core/SkImageInfo.h"
 #include "include/core/SkMatrix.h"
 #include "src/gpu/GrFragmentProcessor.h"
+#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
+#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
 
 class GrTextureEffect : public GrFragmentProcessor {
 public:
@@ -89,6 +91,29 @@
 
     const char* name() const override { return "TextureEffect"; }
 
+    GrSamplerState samplerState() const { return fSamplerState; }
+
+    GrTexture* texture() const { return fView.asTextureProxy()->peekTexture(); }
+
+    const GrSurfaceProxyView& view() const { return fView; }
+
+    class Impl : public GrGLSLFragmentProcessor {
+    public:
+        void emitCode(EmitArgs&) override;
+        void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
+
+        void setSamplerHandle(GrGLSLShaderBuilder::SamplerHandle handle) {
+            fSamplerHandle = handle;
+        }
+
+    private:
+        UniformHandle fSubsetUni;
+        UniformHandle fClampUni;
+        UniformHandle fNormUni;
+        UniformHandle fBorderUni;
+        GrGLSLShaderBuilder::SamplerHandle fSamplerHandle;
+    };
+
 private:
     struct Sampling;
 
@@ -109,7 +134,8 @@
     static ShaderMode GetShaderMode(GrSamplerState::WrapMode, GrSamplerState::Filter);
     static bool ShaderModeIsClampToBorder(ShaderMode);
 
-    TextureSampler fSampler;
+    GrSurfaceProxyView fView;
+    GrSamplerState fSamplerState;
     float fBorder[4];
     SkRect fSubset;
     SkRect fClamp;
@@ -127,8 +153,6 @@
 
     bool onIsEqual(const GrFragmentProcessor&) const override;
 
-    const TextureSampler& onTextureSampler(int) const override;
-
     bool hasClampToBorderShaderMode() const {
         return ShaderModeIsClampToBorder(fShaderModes[0]) ||
                ShaderModeIsClampToBorder(fShaderModes[1]);
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.cpp b/src/gpu/effects/GrYUVtoRGBEffect.cpp
index c453a3d..242deb5 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.cpp
+++ b/src/gpu/effects/GrYUVtoRGBEffect.cpp
@@ -159,13 +159,12 @@
 #ifdef SK_DEBUG
 SkString GrYUVtoRGBEffect::dumpInfo() const {
     SkString str;
-    for (int i = 0; i < this->numTextureSamplers(); ++i) {
-        str.appendf("%d: %d %d ", i,
-                    this->textureSampler(i).view().proxy()->uniqueID().asUInt(),
-                    this->textureSampler(i).view().proxy()->underlyingUniqueID().asUInt());
+    for (int i = 0; i < 4; ++i) {
+        str.appendf("yuvindex_%d: %d %d\n", i, fYUVAIndices->fIndex,
+                    static_cast<int>(fYUVAIndices->fChannel));
     }
-    str.appendf("\n");
-
+    str.appendf("cs: %d\n", static_cast<int>(fYUVColorSpace));
+    str.appendf("snap x: %d snap y: %d\n", fSnap[0], fSnap[1]);
     return str;
 }
 #endif
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index ee19309..d97173d 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -140,14 +140,12 @@
     }
     int nextTexSamplerIdx = primProc.numTextureSamplers();
 
-    GrFragmentProcessor::CIter fpIter(pipeline);
-    for (; fpIter; ++fpIter) {
-        for (int i = 0; i < fpIter->numTextureSamplers(); ++i) {
-            const GrFragmentProcessor::TextureSampler& sampler = fpIter->textureSampler(i);
-            fGpu->bindTexture(nextTexSamplerIdx++, sampler.samplerState(), sampler.view().swizzle(),
-                              static_cast<GrGLTexture*>(sampler.peekTexture()));
-        }
-    }
+    pipeline.visitTextureEffects([&](const GrTextureEffect& te) {
+        GrSamplerState samplerState = te.samplerState();
+        GrSwizzle swizzle = te.view().swizzle();
+        auto* texture = static_cast<GrGLTexture*>(te.texture());
+        fGpu->bindTexture(nextTexSamplerIdx++, samplerState, swizzle, texture);
+    });
 
     SkIPoint offset;
     GrTexture* dstTexture = pipeline.peekDstTexture(&offset);
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
index 6d1ac02..eb84ee8 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
@@ -26,8 +26,6 @@
     // Emit the child's helper function if this is the first time we've seen a call
     if (fFunctionNames[childIndex].size() == 0) {
         TransformedCoordVars coordVars = args.fTransformedCoords.childInputs(childIndex);
-        TextureSamplers textureSamplers = args.fTexSamplers.childInputs(childIndex);
-
         EmitArgs childArgs(fragBuilder,
                            args.fUniformHandler,
                            args.fShaderCaps,
@@ -35,8 +33,7 @@
                            "_output",
                            "_input",
                            "_coords",
-                           coordVars,
-                           textureSamplers);
+                           coordVars);
         fFunctionNames[childIndex] =
                 fragBuilder->writeProcessorFunction(this->childProcessor(childIndex), childArgs);
     }
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.h b/src/gpu/glsl/GrGLSLFragmentProcessor.h
index f04e0c0..8f5fdb5 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.h
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.h
@@ -73,8 +73,6 @@
 public:
     using TransformedCoordVars =
             BuilderInputProvider<GrShaderVar, &GrFragmentProcessor::numVaryingCoordsUsed>;
-    using TextureSamplers =
-            BuilderInputProvider<SamplerHandle, &GrFragmentProcessor::numTextureSamplers>;
 
     /** Called when the program stage should insert its code into the shaders. The code in each
         shader will be in its own block ({}) and so locally scoped names will not collide across
@@ -96,9 +94,6 @@
         @param localCoord        The name of a local coord reference to a float2 variable.
         @param transformedCoords Fragment shader variables containing the coords computed using
                                  each of the GrFragmentProcessor's GrCoordTransforms.
-        @param texSamplers       Contains one entry for each TextureSampler  of the GrProcessor.
-                                 These can be passed to the builder to emit texture reads in the
-                                 generated code.
      */
     struct EmitArgs {
         EmitArgs(GrGLSLFPFragmentBuilder* fragBuilder,
@@ -108,8 +103,7 @@
                  const char* outputColor,
                  const char* inputColor,
                  const char* sampleCoord,
-                 const TransformedCoordVars& transformedCoordVars,
-                 const TextureSamplers& textureSamplers)
+                 const TransformedCoordVars& transformedCoordVars)
                 : fFragBuilder(fragBuilder)
                 , fUniformHandler(uniformHandler)
                 , fShaderCaps(caps)
@@ -117,8 +111,7 @@
                 , fOutputColor(outputColor)
                 , fInputColor(inputColor ? inputColor : "half4(1.0)")
                 , fSampleCoord(sampleCoord)
-                , fTransformedCoords(transformedCoordVars)
-                , fTexSamplers(textureSamplers) {}
+                , fTransformedCoords(transformedCoordVars) {}
         GrGLSLFPFragmentBuilder* fFragBuilder;
         GrGLSLUniformHandler* fUniformHandler;
         const GrShaderCaps* fShaderCaps;
@@ -127,7 +120,6 @@
         const char* fInputColor;
         const char* fSampleCoord;
         const TransformedCoordVars& fTransformedCoords;
-        const TextureSamplers& fTexSamplers;
     };
 
     virtual void emitCode(EmitArgs&) = 0;
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
index 1ec0e62..22be124 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
@@ -130,36 +130,31 @@
 void GrGLSLProgramBuilder::emitAndInstallFragProcs(SkString* color, SkString* coverage) {
     int transformedCoordVarsIdx = 0;
     SkString** inOut = &color;
-    SkSTArray<8, std::unique_ptr<GrGLSLFragmentProcessor>> glslFragmentProcessors;
-    for (int i = 0; i < this->pipeline().numFragmentProcessors(); ++i) {
+    int fpCount = this->pipeline().numFragmentProcessors();
+    fFragmentProcessors.reset(new std::unique_ptr<GrGLSLFragmentProcessor>[fpCount]);
+    for (int i = 0; i < fpCount; ++i) {
         if (i == this->pipeline().numColorFragmentProcessors()) {
             inOut = &coverage;
         }
         SkString output;
         const GrFragmentProcessor& fp = this->pipeline().getFragmentProcessor(i);
-        output = this->emitAndInstallFragProc(fp, i, transformedCoordVarsIdx, **inOut, output,
-                                              &glslFragmentProcessors);
+        fFragmentProcessors[i] = std::unique_ptr<GrGLSLFragmentProcessor>(fp.createGLSLInstance());
+        output = this->emitFragProc(fp, *fFragmentProcessors[i], transformedCoordVarsIdx, **inOut,
+                                    output);
         for (const auto& subFP : GrFragmentProcessor::FPCRange(fp)) {
             transformedCoordVarsIdx += subFP.numVaryingCoordsUsed();
         }
         **inOut = output;
     }
-    int fpCount = glslFragmentProcessors.count();
-    fFragmentProcessors.reset(new std::unique_ptr<GrGLSLFragmentProcessor>[fpCount]);
-    for (int i = 0; i < fpCount; ++i) {
-        fFragmentProcessors[i] = std::move(glslFragmentProcessors[i]);
-    }
 }
 
 // TODO Processors cannot output zeros because an empty string is all 1s
 // the fix is to allow effects to take the SkString directly
-SkString GrGLSLProgramBuilder::emitAndInstallFragProc(
-        const GrFragmentProcessor& fp,
-        int index,
-        int transformedCoordVarsIdx,
-        const SkString& input,
-        SkString output,
-        SkTArray<std::unique_ptr<GrGLSLFragmentProcessor>>* glslFragmentProcessors) {
+SkString GrGLSLProgramBuilder::emitFragProc(const GrFragmentProcessor& fp,
+                                            GrGLSLFragmentProcessor& glslFP,
+                                            int transformedCoordVarsIdx,
+                                            const SkString& input,
+                                            SkString output) {
     SkASSERT(input.size());
     // Program builders have a bit of state we need to clear with each effect
     AutoStageAdvance adv(this);
@@ -170,24 +165,21 @@
     openBrace.printf("{ // Stage %d, %s\n", fStageIndex, fp.name());
     fFS.codeAppend(openBrace.c_str());
 
-    GrGLSLFragmentProcessor* fragProc = fp.createGLSLInstance();
-
-    SkSTArray<4, SamplerHandle> texSamplers;
     int samplerIdx = 0;
-    for (const auto& subFP : GrFragmentProcessor::FPCRange(fp)) {
-        for (int i = 0; i < subFP.numTextureSamplers(); ++i) {
+    for (auto [subFP, subGLSLFP] : GrGLSLFragmentProcessor::ParallelRange(fp, glslFP)) {
+        if (auto* te = subFP.asTextureEffect()) {
             SkString name;
             name.printf("TextureSampler_%d", samplerIdx++);
-            const auto& sampler = subFP.textureSampler(i);
-            texSamplers.emplace_back(this->emitSampler(sampler.view().proxy()->backendFormat(),
-                                                       sampler.samplerState(),
-                                                       sampler.view().swizzle(),
-                                                       name.c_str()));
+
+            GrSamplerState samplerState = te->samplerState();
+            const GrBackendFormat& format = te->view().proxy()->backendFormat();
+            GrSwizzle swizzle = te->view().swizzle();
+            SamplerHandle handle = this->emitSampler(format, samplerState, swizzle, name.c_str());
+            static_cast<GrTextureEffect::Impl&>(subGLSLFP).setSamplerHandle(handle);
         }
     }
     const GrShaderVar* coordVars = fTransformedCoordVars.begin() + transformedCoordVarsIdx;
     GrGLSLFragmentProcessor::TransformedCoordVars coords(&fp, coordVars);
-    GrGLSLFragmentProcessor::TextureSamplers textureSamplers(&fp, texSamplers.begin());
     GrGLSLFragmentProcessor::EmitArgs args(&fFS,
                                            this->uniformHandler(),
                                            this->shaderCaps(),
@@ -195,8 +187,7 @@
                                            output.c_str(),
                                            input.c_str(),
                                            "_coords",
-                                           coords,
-                                           textureSamplers);
+                                           coords);
 
     if (fp.referencesSampleCoords()) {
         // The fp's generated code expects a _coords variable, but we're at the root so _coords
@@ -222,12 +213,11 @@
         }
     }
 
-    fragProc->emitCode(args);
+    glslFP.emitCode(args);
 
     // We have to check that effects and the code they emit are consistent, ie if an effect
     // asks for dst color, then the emit code needs to follow suit
     SkDEBUGCODE(verify(fp);)
-    glslFragmentProcessors->emplace_back(fragProc);
 
     fFS.codeAppend("}");
     return output;
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.h b/src/gpu/glsl/GrGLSLProgramBuilder.h
index fcda22d..17fb4b1 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.h
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.h
@@ -153,12 +153,11 @@
 
     void emitAndInstallPrimProc(SkString* outputColor, SkString* outputCoverage);
     void emitAndInstallFragProcs(SkString* colorInOut, SkString* coverageInOut);
-    SkString emitAndInstallFragProc(const GrFragmentProcessor&,
-                                    int index,
-                                    int transformedCoordVarsIdx,
-                                    const SkString& input,
-                                    SkString output,
-                                    SkTArray<std::unique_ptr<GrGLSLFragmentProcessor>>*);
+    SkString emitFragProc(const GrFragmentProcessor&,
+                          GrGLSLFragmentProcessor&,
+                          int transformedCoordVarsIdx,
+                          const SkString& input,
+                          SkString output);
     void emitAndInstallXferProc(const SkString& colorIn, const SkString& coverageIn);
     SamplerHandle emitSampler(const GrBackendFormat&, GrSamplerState, const GrSwizzle&,
                               const char* name);
diff --git a/src/gpu/mtl/GrMtlPipelineState.mm b/src/gpu/mtl/GrMtlPipelineState.mm
index e4a1e2d..0a15db2 100644
--- a/src/gpu/mtl/GrMtlPipelineState.mm
+++ b/src/gpu/mtl/GrMtlPipelineState.mm
@@ -97,14 +97,9 @@
         fSamplerBindings.emplace_back(sampler.samplerState(), texture, fGpu);
     }
 
-    for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
-        for (auto& fp : GrFragmentProcessor::FPCRange(pipeline.getFragmentProcessor(i))) {
-            for (int s = 0; s < fp.numTextureSamplers(); ++s) {
-                const auto& sampler = fp.textureSampler(s);
-                fSamplerBindings.emplace_back(sampler.samplerState(), sampler.peekTexture(), fGpu);
-            }
-        }
-    }
+    pipeline.visitTextureEffects([&](const GrTextureEffect& te) {
+        fSamplerBindings.emplace_back(te.samplerState(), te.texture(), fGpu);
+    });
 
     if (GrTextureProxy* dstTextureProxy = pipeline.dstProxyView().asTextureProxy()) {
         fSamplerBindings.emplace_back(
diff --git a/src/gpu/vk/GrVkOpsRenderPass.cpp b/src/gpu/vk/GrVkOpsRenderPass.cpp
index 00f54b9..b26012d 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.cpp
+++ b/src/gpu/vk/GrVkOpsRenderPass.cpp
@@ -529,10 +529,9 @@
     for (int i = 0; i < primProc.numTextureSamplers(); ++i) {
         check_sampled_texture(primProcTextures[i]->peekTexture(), fRenderTarget, fGpu);
     }
-    GrFragmentProcessor::PipelineTextureSamplerRange textureSamplerRange(pipeline);
-    for (auto [sampler, fp] : textureSamplerRange) {
-        check_sampled_texture(sampler.peekTexture(), fRenderTarget, fGpu);
-    }
+    pipeline.visitTextureEffects([&](const GrTextureEffect& te) {
+        check_sampled_texture(te.texture(), fRenderTarget, fGpu);
+    });
     if (GrTexture* dstTexture = pipeline.peekDstTexture()) {
         check_sampled_texture(dstTexture, fRenderTarget, fGpu);
     }
diff --git a/src/gpu/vk/GrVkPipelineState.cpp b/src/gpu/vk/GrVkPipelineState.cpp
index 7ebed80..eaea83f 100644
--- a/src/gpu/vk/GrVkPipelineState.cpp
+++ b/src/gpu/vk/GrVkPipelineState.cpp
@@ -126,15 +126,11 @@
         samplerBindings[currTextureBinding++] = {sampler.samplerState(), texture};
     }
 
-    for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
-        for (auto& fp : GrFragmentProcessor::FPCRange(pipeline.getFragmentProcessor(i))) {
-            for (int s = 0; s < fp.numTextureSamplers(); ++s) {
-                const auto& sampler = fp.textureSampler(s);
-                samplerBindings[currTextureBinding++] = {
-                        sampler.samplerState(), static_cast<GrVkTexture*>(sampler.peekTexture())};
-            }
-        }
-    }
+    pipeline.visitTextureEffects([&](const GrTextureEffect& te) {
+        GrSamplerState samplerState = te.samplerState();
+        auto* texture = static_cast<GrVkTexture*>(te.texture());
+        samplerBindings[currTextureBinding++] = {samplerState, texture};
+    });
 
     if (GrTexture* dstTexture = pipeline.peekDstTexture()) {
         samplerBindings[currTextureBinding++] = {
@@ -222,29 +218,6 @@
     return true;
 }
 
-void set_uniform_descriptor_writes(VkWriteDescriptorSet* descriptorWrite,
-                                   VkDescriptorBufferInfo* bufferInfo,
-                                   const GrVkUniformBuffer* buffer,
-                                   VkDescriptorSet descriptorSet) {
-
-    memset(bufferInfo, 0, sizeof(VkDescriptorBufferInfo));
-    bufferInfo->buffer = buffer->buffer();
-    bufferInfo->offset = buffer->offset();
-    bufferInfo->range = buffer->size();
-
-    memset(descriptorWrite, 0, sizeof(VkWriteDescriptorSet));
-    descriptorWrite->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-    descriptorWrite->pNext = nullptr;
-    descriptorWrite->dstSet = descriptorSet;
-    descriptorWrite->dstBinding = GrVkUniformHandler::kUniformBinding;
-    descriptorWrite->dstArrayElement = 0;
-    descriptorWrite->descriptorCount = 1;
-    descriptorWrite->descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
-    descriptorWrite->pImageInfo = nullptr;
-    descriptorWrite->pBufferInfo = bufferInfo;
-    descriptorWrite->pTexelBufferView = nullptr;
-}
-
 void GrVkPipelineState::setRenderTargetState(const GrRenderTarget* rt, GrSurfaceOrigin origin) {
 
     // Load the RT height uniform if it is needed to y-flip gl_FragCoord.