Make fragment processor iterators work with range for loops.

When iterating over the coord transforms or texture samplers of a
FP also have access to the owning FP.

Pass a coord transform range to GPs rather than a pointer to an
iterator.

Change-Id: If7c829a67dce6600d7f49e12d6f49f685dcace3a
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/256216
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index 5ce72ff..1000bd1 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -181,7 +181,7 @@
 
         void setData(const GrGLSLProgramDataManager& pdman,
                      const GrPrimitiveProcessor& gp,
-                     FPCoordTransformIter&& transformIter) override {
+                     const CoordTransformRange& transformRange) override {
             const DefaultGeoProc& dgp = gp.cast<DefaultGeoProc>();
 
             if (!dgp.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(dgp.viewMatrix())) {
@@ -200,7 +200,7 @@
                 pdman.set1f(fCoverageUniform, GrNormalizeByteToFloat(dgp.coverage()));
                 fCoverage = dgp.coverage();
             }
-            this->setTransformDataHelper(dgp.fLocalMatrix, pdman, &transformIter);
+            this->setTransformDataHelper(dgp.fLocalMatrix, pdman, transformRange);
 
             fColorSpaceHelper.setData(pdman, dgp.fColorSpaceXform.get());
         }
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index 933bc01..88acf1e 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -48,10 +48,9 @@
 }
 
 void GrFragmentProcessor::visitProxies(const GrOp::VisitProxyFunc& func) {
-    GrFragmentProcessor::TextureAccessIter iter(this);
-    while (const TextureSampler* sampler = iter.next()) {
-        bool mipped = (GrSamplerState::Filter::kMipMap == sampler->samplerState().filter());
-        func(sampler->proxy(), GrMipMapped(mipped));
+    for (auto [sampler, fp] : FPTextureSamplerRange(*this)) {
+        bool mipped = (GrSamplerState::Filter::kMipMap == sampler.samplerState().filter());
+        func(sampler.proxy(), GrMipMapped(mipped));
     }
 }
 
@@ -389,12 +388,6 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-GrFragmentProcessor::Iter::Iter(const GrPipeline& pipeline) {
-    for (int i = pipeline.numFragmentProcessors() - 1; i >= 0; --i) {
-        fFPStack.push_back(&pipeline.getFragmentProcessor(i));
-    }
-}
-
 GrFragmentProcessor::Iter::Iter(const GrPaint& paint) {
     for (int i = paint.numCoverageFragmentProcessors() - 1; i >= 0; --i) {
         fFPStack.push_back(paint.getCoverageFragmentProcessor(i));
@@ -404,16 +397,32 @@
     }
 }
 
-const GrFragmentProcessor* GrFragmentProcessor::Iter::next() {
-    if (fFPStack.empty()) {
-        return nullptr;
+GrFragmentProcessor::Iter::Iter(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::Iter::Iter(const GrPipeline& pipeline) {
+    for (int i = pipeline.numFragmentProcessors() - 1; i >= 0; --i) {
+        fFPStack.push_back(&pipeline.getFragmentProcessor(i));
+    }
+}
+
+const GrFragmentProcessor& GrFragmentProcessor::Iter::operator*() const { return *fFPStack.back(); }
+const GrFragmentProcessor* GrFragmentProcessor::Iter::operator->() const { return fFPStack.back(); }
+
+GrFragmentProcessor::Iter& GrFragmentProcessor::Iter::operator++() {
+    SkASSERT(!fFPStack.empty());
     const GrFragmentProcessor* back = fFPStack.back();
     fFPStack.pop_back();
     for (int i = back->numChildProcessors() - 1; i >= 0; --i) {
         fFPStack.push_back(&back->childProcessor(i));
     }
-    return back;
+    return *this;
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/gpu/GrFragmentProcessor.h b/src/gpu/GrFragmentProcessor.h
index 9188081..c5e7b96 100644
--- a/src/gpu/GrFragmentProcessor.h
+++ b/src/gpu/GrFragmentProcessor.h
@@ -193,72 +193,88 @@
      */
     bool isEqual(const GrFragmentProcessor& that) const;
 
-    /**
-     * Pre-order traversal of a FP hierarchy, or of the forest of FPs in a GrPipeline. In the latter
-     * case the tree rooted at each FP in the GrPipeline is visited successively.
-     */
-    class Iter : public SkNoncopyable {
-    public:
-        explicit Iter(const GrFragmentProcessor* fp) { fFPStack.push_back(fp); }
-        explicit Iter(const GrPipeline& pipeline);
-        explicit Iter(const GrPaint&);
-        const GrFragmentProcessor* next();
+    void visitProxies(const GrOp::VisitProxyFunc& func);
 
-    private:
-        SkSTArray<4, const GrFragmentProcessor*, true> fFPStack;
-    };
+    // 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
+    // iterates the tree rooted at each color FP and then each coverage FP.
+    //
+    // An iterator is constructed from one of the srcs and used like this:
+    //   for (GrFragmentProcessor::Iter iter(pipeline); iter; ++iter) {
+    //       const GrFragmentProcessor& fp = *iter;
+    //   }
+    // The exit test for the loop is using Iter's operator bool().
+    // To use a range-for loop instead see IterRange below.
+    class Iter;
 
-    /**
-     * Iterates over all the Ts owned by a GrFragmentProcessor and its children or over all the Ts
-     * owned by the forest of GrFragmentProcessors in a GrPipeline. FPs are visited in the same
-     * order as Iter and each of an FP's Ts are visited in order.
-     */
-    template <typename T, int (GrFragmentProcessor::*COUNT)() const,
-              const T& (GrFragmentProcessor::*GET)(int)const>
-    class FPItemIter : public SkNoncopyable {
-    public:
-        explicit FPItemIter(const GrFragmentProcessor* fp)
-                : fCurrFP(nullptr)
-                , fCTIdx(0)
-                , fFPIter(fp) {
-            fCurrFP = fFPIter.next();
-        }
-        explicit FPItemIter(const GrPipeline& pipeline)
-                : fCurrFP(nullptr)
-                , fCTIdx(0)
-                , fFPIter(pipeline) {
-            fCurrFP = fFPIter.next();
-        }
+    // Used to implement a range-for loop using Iter. Src is one of GrFragmentProcessor, GrPaint,
+    // GrProcessorSet, or GrPipeline. Type aliases for these defined below.
+    // Example usage:
+    //   for (const auto& fp : GrFragmentProcessor::PaintRange(paint)) {
+    //       if (fp.usesLocalCoords()) {
+    //       ...
+    //       }
+    //   }
+    template <typename Src> class IterRange;
 
-        const T* next() {
-            if (!fCurrFP) {
-                return nullptr;
-            }
-            while (fCTIdx == (fCurrFP->*COUNT)()) {
-                fCTIdx = 0;
-                fCurrFP = fFPIter.next();
-                if (!fCurrFP) {
-                    return nullptr;
-                }
-            }
-            return &(fCurrFP->*GET)(fCTIdx++);
-        }
+    // We would use template deduction guides for Iter but for:
+    // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79501
+    // Instead we use these specialized type aliases to make it prettier
+    // to construct Iters for particular sources of FPs.
+    using FPRange           = IterRange<GrFragmentProcessor>;
+    using PaintRange        = IterRange<GrPaint>;
 
-    private:
-        const GrFragmentProcessor*  fCurrFP;
-        int                         fCTIdx;
-        GrFragmentProcessor::Iter   fFPIter;
-    };
+    using CountFn = int (GrFragmentProcessor::*)() const;
+    template <typename Item> using GetFn = const Item& (GrFragmentProcessor::*)(int) const;
 
+    // Implementation detail for iterators that walk an array of things owned by a set of FPs.
+    template <typename Item, CountFn Count, GetFn<Item> Get> class FPItemIter;
+
+    // Loops over all the GrCoordTransforms 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 coord transform and the FP that owns it. Example usage:
+    //   for (GrFragmentProcessor::CoordTransformIter iter(pipeline); iter; ++iter) {
+    //       // transform is const GrCoordTransform& and owningFP is const GrFragmentProcessor&.
+    //       auto [transform, owningFP] = *iter;
+    //       ...
+    //   }
+    // See the ranges below to make this simpler a la range-for loops.
     using CoordTransformIter = FPItemIter<GrCoordTransform,
                                           &GrFragmentProcessor::numCoordTransforms,
                                           &GrFragmentProcessor::coordTransform>;
+    // Same as CoordTransformIter but for TextureSamplers:
+    //   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<TextureSampler,
+                                          &GrFragmentProcessor::numTextureSamplers,
+                                          &GrFragmentProcessor::textureSampler>;
 
-    using TextureAccessIter = FPItemIter<TextureSampler,
-                                         &GrFragmentProcessor::numTextureSamplers,
-                                         &GrFragmentProcessor::textureSampler>;
+    // Implementation detail for using CoordTransformIter and TextureSamplerIter in range-for loops.
+    template <typename Src, typename ItemIter> class FPItemRange;
 
-    void visitProxies(const GrOp::VisitProxyFunc& func);
+    // These allow iteration over coord transforms/texture samplers for various FP sources via
+    // range-for loops. An example usage for looping over the coord transforms in a pipeline:
+    // for (auto [transform, fp] : GrFragmentProcessor::PipelineCoordTransformRange(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 PipelineCoordTransformRange     = FPItemRange<GrPipeline,          CoordTransformIter>;
+    using PipelineTextureSamplerRange     = FPItemRange<GrPipeline,          TextureSamplerIter>;
+    using FPTextureSamplerRange           = FPItemRange<GrFragmentProcessor, TextureSamplerIter>;
+    using ProcessorSetTextureSamplerRange = FPItemRange<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 {
@@ -472,4 +488,96 @@
 
 GR_MAKE_BITFIELD_OPS(GrFragmentProcessor::OptimizationFlags)
 
+//////////////////////////////////////////////////////////////////////////////
+
+class GrFragmentProcessor::Iter {
+public:
+    explicit Iter(const GrFragmentProcessor& fp) { fFPStack.push_back(&fp); }
+    explicit Iter(const GrPaint&);
+    explicit Iter(const GrProcessorSet&);
+    explicit Iter(const GrPipeline&);
+
+    const GrFragmentProcessor& operator*() const;
+    const GrFragmentProcessor* operator->() const;
+    Iter& operator++();
+    operator bool() const { return !fFPStack.empty(); }
+    bool operator!=(const EndIter&) { return (bool)*this; }
+
+    // Because each iterator carries a stack we want to avoid copies.
+    Iter(const Iter&) = delete;
+    Iter& operator=(const Iter&) = delete;
+
+private:
+    SkSTArray<4, const GrFragmentProcessor*, true> fFPStack;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+template <typename Src> class GrFragmentProcessor::IterRange {
+public:
+    explicit IterRange(const Src& t) : fT(t) {}
+    Iter begin() const { return Iter(fT); }
+    EndIter end() const { return EndIter(); }
+
+private:
+    const Src& fT;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+template <typename Item, GrFragmentProcessor::CountFn Count, GrFragmentProcessor::GetFn<Item> Get>
+class GrFragmentProcessor::FPItemIter {
+public:
+    template <typename Src> explicit FPItemIter(const Src& s);
+
+    std::pair<const 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:
+    Iter fFPIter;
+    int fIndex;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+template <typename Src, typename ItemIter> class GrFragmentProcessor::FPItemRange {
+public:
+    FPItemRange(const Src& src) : fSrc(src) {}
+    ItemIter begin() const { return ItemIter(fSrc); }
+    FPItemEndIter end() const { return FPItemEndIter(); }
+
+private:
+    const Src& fSrc;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+template <typename Item, GrFragmentProcessor::CountFn Count, GrFragmentProcessor::GetFn<Item> Get>
+template <typename Src>
+GrFragmentProcessor::FPItemIter<Item, Count, Get>::FPItemIter(const 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;
+}
+
 #endif
diff --git a/src/gpu/GrPathProcessor.cpp b/src/gpu/GrPathProcessor.cpp
index 8524b72..571ad1c 100644
--- a/src/gpu/GrPathProcessor.cpp
+++ b/src/gpu/GrPathProcessor.cpp
@@ -85,7 +85,7 @@
 
     void setData(const GrGLSLProgramDataManager& pd,
                  const GrPrimitiveProcessor& primProc,
-                 FPCoordTransformIter&& transformIter) override {
+                 const CoordTransformRange& transformRange) override {
         const GrPathProcessor& pathProc = primProc.cast<GrPathProcessor>();
         if (pathProc.color() != fColor) {
             pd.set4fv(fColorUniform, 1, pathProc.color().vec());
@@ -93,9 +93,9 @@
         }
 
         int t = 0;
-        while (const GrCoordTransform* coordTransform = transformIter.next()) {
+        for (auto [transform, fp] : transformRange) {
             SkASSERT(fInstalledTransforms[t].fHandle.isValid());
-            const SkMatrix& m = GetTransformMatrix(pathProc.localMatrix(), *coordTransform);
+            const SkMatrix& m = GetTransformMatrix(pathProc.localMatrix(), transform);
             if (fInstalledTransforms[t].fCurrentValue.cheapEqualTo(m)) {
                 continue;
             }
diff --git a/src/gpu/GrPipeline.cpp b/src/gpu/GrPipeline.cpp
index e75ac06..f653542 100644
--- a/src/gpu/GrPipeline.cpp
+++ b/src/gpu/GrPipeline.cpp
@@ -111,3 +111,14 @@
 
     b->add32(blendKey);
 }
+
+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.proxy(), GrMipMapped(mipped));
+    }
+    if (fDstProxyView.asTextureProxy()) {
+        func(fDstProxyView.asTextureProxy(), GrMipMapped::kNo);
+    }
+}
diff --git a/src/gpu/GrPipeline.h b/src/gpu/GrPipeline.h
index f2a7f81..5921062 100644
--- a/src/gpu/GrPipeline.h
+++ b/src/gpu/GrPipeline.h
@@ -213,19 +213,7 @@
 
     const GrSwizzle& outputSwizzle() const { return fOutputSwizzle; }
 
-    void visitProxies(const GrOp::VisitProxyFunc& func) const {
-        // This iteration includes any clip coverage FPs
-        for (int i = 0; i < this->numFragmentProcessors(); ++i) {
-            GrFragmentProcessor::TextureAccessIter iter(fFragmentProcessors[i].get());
-            while (const GrFragmentProcessor::TextureSampler* sampler = iter.next()) {
-                bool mipped = (GrSamplerState::Filter::kMipMap == sampler->samplerState().filter());
-                func(sampler->proxy(), GrMipMapped(mipped));
-            }
-        }
-        if (fDstProxyView.asTextureProxy()) {
-            func(fDstProxyView.asTextureProxy(), GrMipMapped::kNo);
-        }
-    }
+    void visitProxies(const GrOp::VisitProxyFunc&) const;
 
 private:
     static constexpr uint8_t kLastInputFlag = (uint8_t)InputFlags::kSnapVerticesToPixelCenters;
diff --git a/src/gpu/GrProcessorSet.cpp b/src/gpu/GrProcessorSet.cpp
index cd30a88..cf75415 100644
--- a/src/gpu/GrProcessorSet.cpp
+++ b/src/gpu/GrProcessorSet.cpp
@@ -251,3 +251,10 @@
 #endif
     return analysis;
 }
+
+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.proxy(), GrMipMapped(mipped));
+    }
+}
diff --git a/src/gpu/GrProcessorSet.h b/src/gpu/GrProcessorSet.h
index 9583864..e155c67 100644
--- a/src/gpu/GrProcessorSet.h
+++ b/src/gpu/GrProcessorSet.h
@@ -153,15 +153,7 @@
     SkString dumpProcessors() const;
 #endif
 
-    void visitProxies(const GrOp::VisitProxyFunc& func) const {
-        for (int i = 0; i < this->numFragmentProcessors(); ++i) {
-            GrFragmentProcessor::TextureAccessIter iter(this->fragmentProcessor(i));
-            while (const GrFragmentProcessor::TextureSampler* sampler = iter.next()) {
-                bool mipped = (GrSamplerState::Filter::kMipMap == sampler->samplerState().filter());
-                func(sampler->proxy(), GrMipMapped(mipped));
-            }
-        }
-    }
+    void visitProxies(const GrOp::VisitProxyFunc& func) const;
 
 private:
     GrProcessorSet(Empty) : fXP((const GrXferProcessor*)nullptr), fFlags(kFinalized_Flag) {}
diff --git a/src/gpu/GrProgramInfo.cpp b/src/gpu/GrProgramInfo.cpp
index e61fff0..2b0fb84 100644
--- a/src/gpu/GrProgramInfo.cpp
+++ b/src/gpu/GrProgramInfo.cpp
@@ -116,12 +116,8 @@
         }
     }
 
-    GrFragmentProcessor::Iter iter(this->pipeline());
-    while (const GrFragmentProcessor* fp = iter.next()) {
-        for (int s = 0; s < fp->numTextureSamplers(); ++s) {
-            const auto& textureSampler = fp->textureSampler(s);
-            assertResolved(textureSampler.peekTexture(), textureSampler.samplerState());
-        }
+    for (auto [sampler, fp] : GrFragmentProcessor::PipelineTextureSamplerRange(this->pipeline())) {
+        assertResolved(sampler.peekTexture(), sampler.samplerState());
     }
 }
 
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index c4f7472..aa99e35 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -17,9 +17,8 @@
 #include "src/gpu/ccpr/GrOctoBounds.h"
 
 static bool has_coord_transforms(const GrPaint& paint) {
-    GrFragmentProcessor::Iter iter(paint);
-    while (const GrFragmentProcessor* fp = iter.next()) {
-        if (!fp->coordTransforms().empty()) {
+    for (const auto& fp : GrFragmentProcessor::PaintRange(paint)) {
+        if (!fp.coordTransforms().empty()) {
             return true;
         }
     }
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index 0c97eaf..4c4b300 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -105,12 +105,12 @@
 
 private:
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
-                 FPCoordTransformIter&& transformIter) override {
+                 const CoordTransformRange& transformRange) override {
         const auto& proc = primProc.cast<GrCCPathProcessor>();
         pdman.set2f(fAtlasAdjustUniform,
                     1.0f / proc.fAtlasDimensions.fWidth,
                     1.0f / proc.fAtlasDimensions.fHeight);
-        this->setTransformDataHelper(proc.fLocalMatrix, pdman, &transformIter);
+        this->setTransformDataHelper(proc.fLocalMatrix, pdman, transformRange);
     }
 
     GrGLSLUniformHandler::UniformHandle fAtlasAdjustUniform;
diff --git a/src/gpu/ccpr/GrCCStroker.cpp b/src/gpu/ccpr/GrCCStroker.cpp
index ccb687b..000e530 100644
--- a/src/gpu/ccpr/GrCCStroker.cpp
+++ b/src/gpu/ccpr/GrCCStroker.cpp
@@ -89,7 +89,7 @@
 
     class Impl : public GrGLSLGeometryProcessor {
         void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
-                     FPCoordTransformIter&&) override {}
+                     const CoordTransformRange&) override {}
         void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
     };
 
@@ -182,7 +182,7 @@
 
     class Impl : public GrGLSLGeometryProcessor {
         void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
-                     FPCoordTransformIter&&) override {}
+                     const CoordTransformRange&) override {}
         void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
     };
 
diff --git a/src/gpu/ccpr/GrGSCoverageProcessor.cpp b/src/gpu/ccpr/GrGSCoverageProcessor.cpp
index b8b8888..320cd0c 100644
--- a/src/gpu/ccpr/GrGSCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrGSCoverageProcessor.cpp
@@ -24,8 +24,8 @@
     virtual bool hasCoverage(const GrGSCoverageProcessor& proc) const { return false; }
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) final {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+                 const CoordTransformRange& transformRange) final {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
     }
 
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final {
diff --git a/src/gpu/ccpr/GrSampleMaskProcessor.cpp b/src/gpu/ccpr/GrSampleMaskProcessor.cpp
index 78c05c3..7c8e9a9 100644
--- a/src/gpu/ccpr/GrSampleMaskProcessor.cpp
+++ b/src/gpu/ccpr/GrSampleMaskProcessor.cpp
@@ -16,7 +16,7 @@
 
 private:
     void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&&) override {}
+                 const CoordTransformRange&) override {}
 
     void onEmitCode(EmitArgs&, GrGPArgs*) override;
 
diff --git a/src/gpu/ccpr/GrStencilAtlasOp.cpp b/src/gpu/ccpr/GrStencilAtlasOp.cpp
index 0c38f07..2fed59e 100644
--- a/src/gpu/ccpr/GrStencilAtlasOp.cpp
+++ b/src/gpu/ccpr/GrStencilAtlasOp.cpp
@@ -59,7 +59,7 @@
     }
 
     void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&&) override {}
+                 const CoordTransformRange&) override {}
 };
 
 GrGLSLPrimitiveProcessor* StencilResolveProcessor::createGLSLInstance(const GrShaderCaps&) const {
diff --git a/src/gpu/ccpr/GrVSCoverageProcessor.cpp b/src/gpu/ccpr/GrVSCoverageProcessor.cpp
index e61c76e..231c046 100644
--- a/src/gpu/ccpr/GrVSCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrVSCoverageProcessor.cpp
@@ -19,8 +19,8 @@
 
 private:
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) final {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+                 const CoordTransformRange& transformRange) final {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
     }
 
     void onEmitCode(EmitArgs&, GrGPArgs*) override;
diff --git a/src/gpu/dawn/GrDawnProgramBuilder.cpp b/src/gpu/dawn/GrDawnProgramBuilder.cpp
index 8ac8fbd..86ea295 100644
--- a/src/gpu/dawn/GrDawnProgramBuilder.cpp
+++ b/src/gpu/dawn/GrDawnProgramBuilder.cpp
@@ -517,16 +517,12 @@
     this->setRenderTargetState(renderTarget, programInfo.origin());
     const GrPipeline& pipeline = programInfo.pipeline();
     const GrPrimitiveProcessor& primProc = programInfo.primProc();
-    fGeometryProcessor->setData(fDataManager, primProc,
-                                GrFragmentProcessor::CoordTransformIter(pipeline));
-    GrFragmentProcessor::Iter iter(pipeline);
+    GrFragmentProcessor::PipelineCoordTransformRange transformRange(pipeline);
+    fGeometryProcessor->setData(fDataManager, primProc, transformRange);
+    GrFragmentProcessor::Iter fpIter(pipeline);
     GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt);
-    const GrFragmentProcessor* fp = iter.next();
-    GrGLSLFragmentProcessor* glslFP = glslIter.next();
-    while (fp && glslFP) {
-        glslFP->setData(fDataManager, *fp);
-        fp = iter.next();
-        glslFP = glslIter.next();
+    for (; fpIter && glslIter; ++fpIter, ++glslIter) {
+        glslIter->setData(fDataManager, *fpIter);
     }
     SkIPoint offset;
     GrTexture* dstTexture = pipeline.peekDstTexture(&offset);
@@ -554,17 +550,13 @@
                         &binding);
         }
     }
-    GrFragmentProcessor::Iter iter(pipeline);
+    GrFragmentProcessor::Iter fpIter(pipeline);
     GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt);
-    const GrFragmentProcessor* fp = iter.next();
-    GrGLSLFragmentProcessor* glslFP = glslIter.next();
-    while (fp && glslFP) {
-        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
-            auto& s = fp->textureSampler(i);
+    for (; fpIter && glslIter; ++fpIter, ++glslIter) {
+        for (int i = 0; i < fpIter->numTextureSamplers(); ++i) {
+            auto& s = fpIter->textureSampler(i);
             set_texture(gpu, s.samplerState(), s.peekTexture(), &bindings, &binding);
         }
-        fp = iter.next();
-        glslFP = glslIter.next();
     }
     SkIPoint offset;
     if (GrTexture* dstTexture = pipeline.peekDstTexture(&offset)) {
diff --git a/src/gpu/effects/GrBezierEffect.cpp b/src/gpu/effects/GrBezierEffect.cpp
index 37bdae8..9066c40 100644
--- a/src/gpu/effects/GrBezierEffect.cpp
+++ b/src/gpu/effects/GrBezierEffect.cpp
@@ -26,7 +26,7 @@
                               GrProcessorKeyBuilder*);
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
-                 FPCoordTransformIter&& transformIter) override {
+                 const CoordTransformRange& transformRange) override {
         const GrConicEffect& ce = primProc.cast<GrConicEffect>();
 
         if (!ce.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(ce.viewMatrix())) {
@@ -45,7 +45,7 @@
             pdman.set1f(fCoverageScaleUniform, GrNormalizeByteToFloat(ce.coverageScale()));
             fCoverageScale = ce.coverageScale();
         }
-        this->setTransformDataHelper(ce.localMatrix(), pdman, &transformIter);
+        this->setTransformDataHelper(ce.localMatrix(), pdman, transformRange);
     }
 
 private:
@@ -278,7 +278,7 @@
                               GrProcessorKeyBuilder*);
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
-                 FPCoordTransformIter&& transformIter) override {
+                 const CoordTransformRange& transformRange) override {
         const GrQuadEffect& qe = primProc.cast<GrQuadEffect>();
 
         if (!qe.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(qe.viewMatrix())) {
@@ -297,7 +297,7 @@
             pdman.set1f(fCoverageScaleUniform, GrNormalizeByteToFloat(qe.coverageScale()));
             fCoverageScale = qe.coverageScale();
         }
-        this->setTransformDataHelper(qe.localMatrix(), pdman, &transformIter);
+        this->setTransformDataHelper(qe.localMatrix(), pdman, transformRange);
     }
 
 private:
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.cpp b/src/gpu/effects/GrBitmapTextGeoProc.cpp
index 1cec0a1..5c710cc 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.cpp
+++ b/src/gpu/effects/GrBitmapTextGeoProc.cpp
@@ -76,7 +76,7 @@
     }
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp,
-                 FPCoordTransformIter&& transformIter) override {
+                 const CoordTransformRange& transformRange) override {
         const GrBitmapTextGeoProc& btgp = gp.cast<GrBitmapTextGeoProc>();
         if (btgp.color() != fColor && !btgp.hasVertexColor()) {
             pdman.set4fv(fColorUniform, 1, btgp.color().vec());
@@ -92,7 +92,7 @@
                         1.0f / atlasDimensions.fHeight);
             fAtlasDimensions = atlasDimensions;
         }
-        this->setTransformDataHelper(btgp.localMatrix(), pdman, &transformIter);
+        this->setTransformDataHelper(btgp.localMatrix(), pdman, transformRange);
     }
 
     static inline void GenKey(const GrGeometryProcessor& proc,
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 9196449..1122d0e 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -163,7 +163,7 @@
     }
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
-                 FPCoordTransformIter&& transformIter) override {
+                 const CoordTransformRange& transformRange) override {
         const GrDistanceFieldA8TextGeoProc& dfa8gp = proc.cast<GrDistanceFieldA8TextGeoProc>();
 
 #ifdef SK_GAMMA_APPLY_TO_A8
@@ -183,7 +183,7 @@
                         1.0f / atlasDimensions.fHeight);
             fAtlasDimensions = atlasDimensions;
         }
-        this->setTransformDataHelper(dfa8gp.localMatrix(), pdman, &transformIter);
+        this->setTransformDataHelper(dfa8gp.localMatrix(), pdman, transformRange);
     }
 
     static inline void GenKey(const GrGeometryProcessor& gp,
@@ -463,8 +463,7 @@
     }
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
-                 FPCoordTransformIter&& transformIter) override {
-
+                 const CoordTransformRange& transformRange) override {
         const GrDistanceFieldPathGeoProc& dfpgp = proc.cast<GrDistanceFieldPathGeoProc>();
 
         if (dfpgp.matrix().hasPerspective() && !fMatrix.cheapEqualTo(dfpgp.matrix())) {
@@ -484,9 +483,9 @@
         }
 
         if (dfpgp.matrix().hasPerspective()) {
-            this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+            this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
         } else {
-            this->setTransformDataHelper(dfpgp.matrix(), pdman, &transformIter);
+            this->setTransformDataHelper(dfpgp.matrix(), pdman, transformRange);
         }
     }
 
@@ -791,7 +790,7 @@
     }
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& processor,
-                 FPCoordTransformIter&& transformIter) override {
+                 const CoordTransformRange& transformRange) override {
         SkASSERT(fDistanceAdjustUni.isValid());
 
         const GrDistanceFieldLCDTextGeoProc& dflcd = processor.cast<GrDistanceFieldLCDTextGeoProc>();
@@ -812,7 +811,7 @@
                         1.0f / atlasDimensions.fHeight);
             fAtlasDimensions = atlasDimensions;
         }
-        this->setTransformDataHelper(dflcd.localMatrix(), pdman, &transformIter);
+        this->setTransformDataHelper(dflcd.localMatrix(), pdman, transformRange);
     }
 
     static inline void GenKey(const GrGeometryProcessor& gp,
diff --git a/src/gpu/effects/GrShadowGeoProc.cpp b/src/gpu/effects/GrShadowGeoProc.cpp
index decae06..a1c4c54 100644
--- a/src/gpu/effects/GrShadowGeoProc.cpp
+++ b/src/gpu/effects/GrShadowGeoProc.cpp
@@ -51,8 +51,8 @@
     }
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
-                 FPCoordTransformIter&& transformIter) override {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+                 const CoordTransformRange& transformRange) override {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
     }
 
 private:
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index a626f80..7914d77 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -84,8 +84,8 @@
     // We must bind to texture units in the same order in which we set the uniforms in
     // GrGLProgramDataManager. That is, we bind textures for processors in this order:
     // primProc, fragProcs, XP.
-    fPrimitiveProcessor->setData(fProgramDataManager, programInfo.primProc(),
-                                 GrFragmentProcessor::CoordTransformIter(programInfo.pipeline()));
+    GrFragmentProcessor::PipelineCoordTransformRange range(programInfo.pipeline());
+    fPrimitiveProcessor->setData(fProgramDataManager, programInfo.primProc(), range);
     if (programInfo.hasFixedPrimProcTextures()) {
         this->updatePrimitiveProcessorTextureBindings(programInfo.primProc(),
                                                       programInfo.fixedPrimProcTextures());
@@ -118,21 +118,17 @@
 }
 
 void GrGLProgram::setFragmentData(const GrPipeline& pipeline, int* nextTexSamplerIdx) {
-    GrFragmentProcessor::Iter iter(pipeline);
+    GrFragmentProcessor::Iter fpIter(pipeline);
     GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt);
-    const GrFragmentProcessor* fp = iter.next();
-    GrGLSLFragmentProcessor* glslFP = glslIter.next();
-    while (fp && glslFP) {
-        glslFP->setData(fProgramDataManager, *fp);
-        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
-            const GrFragmentProcessor::TextureSampler& sampler = fp->textureSampler(i);
+    for (; fpIter && glslIter; ++fpIter, ++glslIter) {
+        glslIter->setData(fProgramDataManager, *fpIter);
+        for (int i = 0; i < fpIter->numTextureSamplers(); ++i) {
+            const GrFragmentProcessor::TextureSampler& sampler = fpIter->textureSampler(i);
             fGpu->bindTexture((*nextTexSamplerIdx)++, sampler.samplerState(), sampler.swizzle(),
                               static_cast<GrGLTexture*>(sampler.peekTexture()));
         }
-        fp = iter.next();
-        glslFP = glslIter.next();
     }
-    SkASSERT(!fp && !glslFP);
+    SkASSERT(!fpIter && !glslIter);
 }
 
 void GrGLProgram::setRenderTargetState(const GrRenderTarget* rt, GrSurfaceOrigin origin,
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
index e5e97de..706e892 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
@@ -117,14 +117,26 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-GrGLSLFragmentProcessor* GrGLSLFragmentProcessor::Iter::next() {
-    if (fFPStack.empty()) {
-        return nullptr;
+GrGLSLFragmentProcessor::Iter::Iter(std::unique_ptr<GrGLSLFragmentProcessor> fps[], int cnt) {
+    for (int i = cnt - 1; i >= 0; --i) {
+        fFPStack.push_back(fps[i].get());
     }
-    GrGLSLFragmentProcessor* back = fFPStack.back();
+}
+
+GrGLSLFragmentProcessor& GrGLSLFragmentProcessor::Iter::operator*() const {
+    return *fFPStack.back();
+}
+
+GrGLSLFragmentProcessor* GrGLSLFragmentProcessor::Iter::operator->() const {
+    return fFPStack.back();
+}
+
+GrGLSLFragmentProcessor::Iter& GrGLSLFragmentProcessor::Iter::operator++() {
+    SkASSERT(!fFPStack.empty());
+    const GrGLSLFragmentProcessor* back = fFPStack.back();
     fFPStack.pop_back();
     for (int i = back->numChildProcessors() - 1; i >= 0; --i) {
         fFPStack.push_back(back->childProcessor(i));
     }
-    return back;
+    return *this;
 }
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.h b/src/gpu/glsl/GrGLSLFragmentProcessor.h
index ad441d8..df501c2 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.h
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.h
@@ -35,7 +35,7 @@
 
 private:
     /**
-     * This class allows the shader builder to provide each GrGLSLFragmentProcesor with an array of
+     * This class allows the shader builder to provide each GrGLSLFragmentProcessor with an array of
      * generated variables where each generated variable corresponds to an element of an array on
      * the GrFragmentProcessor that generated the GLSLFP. For example, this is used to provide a
      * variable holding transformed coords for each GrCoordTransform owned by the FP.
@@ -54,15 +54,15 @@
 
         BuilderInputProvider childInputs(int childIdx) const {
             const GrFragmentProcessor* child = &fFP->childProcessor(childIdx);
-            GrFragmentProcessor::Iter iter(fFP);
             int numToSkip = 0;
-            while (true) {
-                const GrFragmentProcessor* fp = iter.next();
-                if (fp == child) {
+            for (const auto& fp : GrFragmentProcessor::FPRange(*fFP)) {
+                if (&fp == child) {
                     return BuilderInputProvider(child, fTs + numToSkip);
                 }
-                numToSkip += (fp->*COUNT)();
+                numToSkip += (fp.*COUNT)();
             }
+            SK_ABORT("Didn't find the child.");
+            return {nullptr, nullptr};
         }
 
     private:
@@ -134,9 +134,7 @@
 
     int numChildProcessors() const { return fChildProcessors.count(); }
 
-    GrGLSLFragmentProcessor* childProcessor(int index) {
-        return fChildProcessors[index];
-    }
+    GrGLSLFragmentProcessor* childProcessor(int index) const { return fChildProcessors[index]; }
 
     // Invoke the child with the default input color (solid white)
     inline void invokeChild(int childIndex, SkString* outputColor, EmitArgs& parentArgs,
@@ -168,17 +166,22 @@
 
     /**
      * Pre-order traversal of a GLSLFP hierarchy, or of multiple trees with roots in an array of
-     * GLSLFPS. This agrees with the traversal order of GrFragmentProcessor::Iter
+     * GLSLFPS. If initialized with an array color followed by coverage processors installed in a
+     * program thenthe iteration order will agree with a GrFragmentProcessor::Iter initialized with
+     * a GrPipeline that produces the same program key.
      */
-    class Iter : public SkNoncopyable {
+    class Iter {
     public:
-        explicit Iter(GrGLSLFragmentProcessor* fp) { fFPStack.push_back(fp); }
-        explicit Iter(std::unique_ptr<GrGLSLFragmentProcessor> fps[], int cnt) {
-            for (int i = cnt - 1; i >= 0; --i) {
-                fFPStack.push_back(fps[i].get());
-            }
-        }
-        GrGLSLFragmentProcessor* next();
+        Iter(std::unique_ptr<GrGLSLFragmentProcessor> fps[], int cnt);
+
+        GrGLSLFragmentProcessor& operator*() const;
+        GrGLSLFragmentProcessor* operator->() const;
+        Iter& operator++();
+        operator bool() const { return !fFPStack.empty(); }
+
+        // Because each iterator carries a stack we want to avoid copies.
+        Iter(const Iter&) = delete;
+        Iter& operator=(const Iter&) = delete;
 
     private:
         SkSTArray<4, GrGLSLFragmentProcessor*, true> fFPStack;
diff --git a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
index d99239e..adb20fa 100644
--- a/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLGeometryProcessor.cpp
@@ -102,10 +102,10 @@
 
 void GrGLSLGeometryProcessor::setTransformDataHelper(const SkMatrix& localMatrix,
                                                      const GrGLSLProgramDataManager& pdman,
-                                                     FPCoordTransformIter* transformIter) {
+                                                     const CoordTransformRange& transformRange) {
     int i = 0;
-    while (const GrCoordTransform* coordTransform = transformIter->next()) {
-        const SkMatrix& m = GetTransformMatrix(localMatrix, *coordTransform);
+    for (auto [transform, fp] : transformRange) {
+        const SkMatrix& m = GetTransformMatrix(localMatrix, transform);
         if (!fInstalledTransforms[i].fCurrentValue.cheapEqualTo(m)) {
             pdman.setSkMatrix(fInstalledTransforms[i].fHandle.toIndex(), m);
             fInstalledTransforms[i].fCurrentValue = m;
diff --git a/src/gpu/glsl/GrGLSLGeometryProcessor.h b/src/gpu/glsl/GrGLSLGeometryProcessor.h
index eb34412..c8f7895 100644
--- a/src/gpu/glsl/GrGLSLGeometryProcessor.h
+++ b/src/gpu/glsl/GrGLSLGeometryProcessor.h
@@ -26,7 +26,7 @@
     // A helper which subclasses can use to upload coord transform matrices in setData().
     void setTransformDataHelper(const SkMatrix& localMatrix,
                                 const GrGLSLProgramDataManager& pdman,
-                                FPCoordTransformIter*);
+                                const CoordTransformRange&);
 
     // Emit transformed local coords from the vertex shader as a uniform matrix and varying per
     // coord-transform. localCoordsVar must be a 2- or 3-component vector. If it is 3 then it is
diff --git a/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp b/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp
index 9d35c44..cccbeed 100644
--- a/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp
@@ -67,12 +67,15 @@
 //////////////////////////////////////////////////////////////////////////////
 
 const GrCoordTransform* GrGLSLPrimitiveProcessor::FPCoordTransformHandler::nextCoordTransform() {
-#ifdef SK_DEBUG
     SkASSERT(nullptr == fCurr || fAddedCoord);
+    if (!fIter) {
+        return nullptr;
+    }
+    auto [transform, fp] = *fIter;
+    ++fIter;
+#ifdef SK_DEBUG
     fAddedCoord = false;
-    fCurr = fIter.next();
-    return fCurr;
-#else
-    return fIter.next();
+    fCurr = &transform;
 #endif
+    return &transform;
 }
diff --git a/src/gpu/glsl/GrGLSLPrimitiveProcessor.h b/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
index 12fb74f..9327d15 100644
--- a/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
+++ b/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
@@ -23,9 +23,9 @@
 
 class GrGLSLPrimitiveProcessor {
 public:
-    using UniformHandle        = GrGLSLProgramDataManager::UniformHandle;
-    using SamplerHandle        = GrGLSLUniformHandler::SamplerHandle;
-    using FPCoordTransformIter = GrFragmentProcessor::CoordTransformIter;
+    using UniformHandle         = GrGLSLProgramDataManager::UniformHandle;
+    using SamplerHandle         = GrGLSLUniformHandler::SamplerHandle;
+    using CoordTransformRange   = GrFragmentProcessor::PipelineCoordTransformRange;
 
     struct TransformVar {
         TransformVar() = default;
@@ -132,12 +132,13 @@
      * GrPrimitiveProcessor parameter is guaranteed to be of the same type and to have an
      * identical processor key as the GrPrimitiveProcessor that created this
      * GrGLSLPrimitiveProcessor.
-     * The subclass may use the transform iterator to perform any setup required for the particular
-     * set of fp transform matrices, such as uploading via uniforms. The iterator will iterate over
-     * the transforms in the same order as the TransformHandler passed to emitCode.
+     * The subclass should use the transform range to perform any setup required for the coord
+     * transforms of the FPs that are part of the same program, such as updating matrix uniforms.
+     * The range will iterate over the transforms in the same order as the TransformHandler passed
+     * to emitCode.
      */
     virtual void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
-                         FPCoordTransformIter&&) = 0;
+                         const CoordTransformRange&) = 0;
 
     static SkMatrix GetTransformMatrix(const SkMatrix& localMatrix, const GrCoordTransform&);
 
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
index cb6a6bd..3474380 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
@@ -150,9 +150,8 @@
         const GrFragmentProcessor& fp = this->pipeline().getFragmentProcessor(i);
         output = this->emitAndInstallFragProc(fp, i, transformedCoordVarsIdx, **inOut, output,
                                               &glslFragmentProcessors);
-        GrFragmentProcessor::Iter iter(&fp);
-        while (const GrFragmentProcessor* fp = iter.next()) {
-            transformedCoordVarsIdx += fp->numCoordTransforms();
+        for (const auto& subFP : GrFragmentProcessor::FPRange(fp)) {
+            transformedCoordVarsIdx += subFP.numCoordTransforms();
         }
         **inOut = output;
     }
@@ -185,20 +184,18 @@
     GrGLSLFragmentProcessor* fragProc = fp.createGLSLInstance();
 
     SkSTArray<4, SamplerHandle> texSamplers;
-    GrFragmentProcessor::Iter fpIter(&fp);
     int samplerIdx = 0;
-    while (const auto* subFP = fpIter.next()) {
-        for (int i = 0; i < subFP->numTextureSamplers(); ++i) {
+    for (const auto& subFP : GrFragmentProcessor::FPRange(fp)) {
+        for (int i = 0; i < subFP.numTextureSamplers(); ++i) {
             SkString name;
             name.printf("TextureSampler_%d", samplerIdx++);
-            const auto& sampler = subFP->textureSampler(i);
+            const auto& sampler = subFP.textureSampler(i);
             texSamplers.emplace_back(this->emitSampler(sampler.proxy(),
                                                        sampler.samplerState(),
                                                        sampler.swizzle(),
                                                        name.c_str()));
         }
     }
-
     const GrGLSLPrimitiveProcessor::TransformVar* coordVars = fTransformedCoordVars.begin() +
                                                               transformedCoordVarsIdx;
     GrGLSLFragmentProcessor::TransformedCoordVars coords(&fp, coordVars);
diff --git a/src/gpu/mtl/GrMtlPipelineState.mm b/src/gpu/mtl/GrMtlPipelineState.mm
index 6f4ae8a..ecc6366 100644
--- a/src/gpu/mtl/GrMtlPipelineState.mm
+++ b/src/gpu/mtl/GrMtlPipelineState.mm
@@ -59,8 +59,8 @@
 void GrMtlPipelineState::setData(const GrRenderTarget* renderTarget,
                                  const GrProgramInfo& programInfo) {
     this->setRenderTargetState(renderTarget, programInfo.origin());
-    fGeometryProcessor->setData(fDataManager, programInfo.primProc(),
-                                GrFragmentProcessor::CoordTransformIter(programInfo.pipeline()));
+    GrFragmentProcessor::PipelineCoordTransformRange transformRange(programInfo.pipeline());
+    fGeometryProcessor->setData(fDataManager, programInfo.primProc(), transformRange);
 
     if (!programInfo.hasDynamicPrimProcTextures()) {
         auto proxies = programInfo.hasFixedPrimProcTextures() ? programInfo.fixedPrimProcTextures()
@@ -89,20 +89,16 @@
         fSamplerBindings.emplace_back(sampler.samplerState(), texture, fGpu);
     }
 
-    GrFragmentProcessor::Iter iter(programInfo.pipeline());
+    GrFragmentProcessor::Iter fpIter(programInfo.pipeline());
     GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt);
-    const GrFragmentProcessor* fp = iter.next();
-    GrGLSLFragmentProcessor* glslFP = glslIter.next();
-    while (fp && glslFP) {
-        glslFP->setData(fDataManager, *fp);
-        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
-            const auto& sampler = fp->textureSampler(i);
+    for (; fpIter && glslIter; ++fpIter, ++glslIter) {
+        glslIter->setData(fDataManager, *fpIter);
+        for (int i = 0; i < fpIter->numTextureSamplers(); ++i) {
+            const auto& sampler = fpIter->textureSampler(i);
             fSamplerBindings.emplace_back(sampler.samplerState(), sampler.peekTexture(), fGpu);
         }
-        fp = iter.next();
-        glslFP = glslIter.next();
     }
-    SkASSERT(!fp && !glslFP);
+    SkASSERT(!fpIter && !glslIter);
 
     {
         SkIPoint offset;
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index aa48aff..9efdd3a 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -611,9 +611,9 @@
 
         void setData(const GrGLSLProgramDataManager& pdman,
                      const GrPrimitiveProcessor& gp,
-                     FPCoordTransformIter&& transformIter) override {
+                     const CoordTransformRange& transformRange) override {
             const QuadEdgeEffect& qe = gp.cast<QuadEdgeEffect>();
-            this->setTransformDataHelper(qe.fLocalMatrix, pdman, &transformIter);
+            this->setTransformDataHelper(qe.fLocalMatrix, pdman, transformRange);
         }
 
     private:
diff --git a/src/gpu/ops/GrDashOp.cpp b/src/gpu/ops/GrDashOp.cpp
index f68a474..ecd5b16 100644
--- a/src/gpu/ops/GrDashOp.cpp
+++ b/src/gpu/ops/GrDashOp.cpp
@@ -820,7 +820,8 @@
                               GrProcessorKeyBuilder*);
 
     void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) override;
+                 const CoordTransformRange& transformRange) override;
+
 private:
     UniformHandle fParamUniform;
     UniformHandle fColorUniform;
@@ -893,13 +894,13 @@
 
 void GLDashingCircleEffect::setData(const GrGLSLProgramDataManager& pdman,
                                     const GrPrimitiveProcessor& processor,
-                                    FPCoordTransformIter&& transformIter)  {
+                                    const CoordTransformRange& transformRange) {
     const DashingCircleEffect& dce = processor.cast<DashingCircleEffect>();
     if (dce.color() != fColor) {
         pdman.set4fv(fColorUniform, 1, dce.color().vec());
         fColor = dce.color();
     }
-    this->setTransformDataHelper(dce.localMatrix(), pdman, &transformIter);
+    this->setTransformDataHelper(dce.localMatrix(), pdman, transformRange);
 }
 
 void GLDashingCircleEffect::GenKey(const GrGeometryProcessor& gp,
@@ -1029,7 +1030,7 @@
                               GrProcessorKeyBuilder*);
 
     void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& iter) override;
+                 const CoordTransformRange&) override;
 
 private:
     SkPMColor4f   fColor;
@@ -1120,13 +1121,13 @@
 
 void GLDashingLineEffect::setData(const GrGLSLProgramDataManager& pdman,
                                   const GrPrimitiveProcessor& processor,
-                                  FPCoordTransformIter&& transformIter) {
+                                  const CoordTransformRange& transformRange) {
     const DashingLineEffect& de = processor.cast<DashingLineEffect>();
     if (de.color() != fColor) {
         pdman.set4fv(fColorUniform, 1, de.color().vec());
         fColor = de.color();
     }
-    this->setTransformDataHelper(de.localMatrix(), pdman, &transformIter);
+    this->setTransformDataHelper(de.localMatrix(), pdman, transformRange);
 }
 
 void GLDashingLineEffect::GenKey(const GrGeometryProcessor& gp,
diff --git a/src/gpu/ops/GrFillRRectOp.cpp b/src/gpu/ops/GrFillRRectOp.cpp
index 5858dc2..e0d3167 100644
--- a/src/gpu/ops/GrFillRRectOp.cpp
+++ b/src/gpu/ops/GrFillRRectOp.cpp
@@ -632,8 +632,8 @@
     }
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) override {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+                 const CoordTransformRange& transformRange) override {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
     }
 };
 
@@ -738,8 +738,8 @@
     }
 
     void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
-                 FPCoordTransformIter&& transformIter) override {
-        this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+                 const CoordTransformRange& transformRange) override {
+        this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
     }
 };
 
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index aac4dd6..b9c11ff 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -47,9 +47,9 @@
         class GLSLProcessor : public GrGLSLGeometryProcessor {
         public:
             void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
-                         FPCoordTransformIter&& transformIter) override {
+                         const CoordTransformRange& transformRange) override {
                 const auto& latticeGP = proc.cast<LatticeGP>();
-                this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+                this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
                 fColorSpaceXformHelper.setData(pdman, latticeGP.fColorSpaceXform.get());
             }
 
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index 65bd63a..6adb970 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -221,9 +221,9 @@
         }
 
         void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
-                     FPCoordTransformIter&& transformIter) override {
+                     const CoordTransformRange& transformRange) override {
             this->setTransformDataHelper(primProc.cast<CircleGeometryProcessor>().fLocalMatrix,
-                                         pdman, &transformIter);
+                                         pdman, transformRange);
         }
 
     private:
@@ -481,10 +481,10 @@
         }
 
         void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
-                     FPCoordTransformIter&& transformIter) override {
+                     const CoordTransformRange& transformRange) override {
             this->setTransformDataHelper(
                     primProc.cast<ButtCapDashedCircleGeometryProcessor>().fLocalMatrix, pdman,
-                    &transformIter);
+                    transformRange);
         }
 
     private:
@@ -672,9 +672,9 @@
         }
 
         void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
-                     FPCoordTransformIter&& transformIter) override {
+                     const CoordTransformRange& transformRange) override {
             const EllipseGeometryProcessor& egp = primProc.cast<EllipseGeometryProcessor>();
-            this->setTransformDataHelper(egp.fLocalMatrix, pdman, &transformIter);
+            this->setTransformDataHelper(egp.fLocalMatrix, pdman, transformRange);
         }
 
     private:
@@ -868,7 +868,7 @@
         }
 
         void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& gp,
-                     FPCoordTransformIter&& transformIter) override {
+                     const CoordTransformRange& transformRange) override {
             const DIEllipseGeometryProcessor& diegp = gp.cast<DIEllipseGeometryProcessor>();
 
             if (!diegp.fViewMatrix.isIdentity() && !fViewMatrix.cheapEqualTo(diegp.fViewMatrix)) {
@@ -877,7 +877,7 @@
                 GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix);
                 pdman.setMatrix3f(fViewMatrixUniform, viewMatrix);
             }
-            this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+            this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
         }
 
     private:
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index 2726c24..da70bf6 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -575,10 +575,10 @@
         class GLSLProcessor : public GrGLSLGeometryProcessor {
         public:
             void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
-                         FPCoordTransformIter&& transformIter) override {
+                         const CoordTransformRange& transformRange) override {
                 const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
                 if (gp.fLocalCoord.isInitialized()) {
-                    this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
+                    this->setTransformDataHelper(SkMatrix::I(), pdman, transformRange);
                 }
                 fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
             }
diff --git a/src/gpu/vk/GrVkOpsRenderPass.cpp b/src/gpu/vk/GrVkOpsRenderPass.cpp
index 5bbeb6e..6a9ead7 100644
--- a/src/gpu/vk/GrVkOpsRenderPass.cpp
+++ b/src/gpu/vk/GrVkOpsRenderPass.cpp
@@ -575,12 +575,9 @@
         }
     }
 
-    GrFragmentProcessor::Iter iter(programInfo.pipeline());
-    while (const GrFragmentProcessor* fp = iter.next()) {
-        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
-            const GrFragmentProcessor::TextureSampler& sampler = fp->textureSampler(i);
-            check_sampled_texture(sampler.peekTexture(), fRenderTarget, fGpu);
-        }
+    GrFragmentProcessor::PipelineTextureSamplerRange textureSamplerRange(programInfo.pipeline());
+    for (auto [sampler, fp] : textureSamplerRange) {
+        check_sampled_texture(sampler.peekTexture(), fRenderTarget, fGpu);
     }
     if (GrTexture* dstTexture = programInfo.pipeline().peekDstTexture()) {
         check_sampled_texture(dstTexture, fRenderTarget, fGpu);
diff --git a/src/gpu/vk/GrVkPipelineState.cpp b/src/gpu/vk/GrVkPipelineState.cpp
index f41c53d..e2a6fe5 100644
--- a/src/gpu/vk/GrVkPipelineState.cpp
+++ b/src/gpu/vk/GrVkPipelineState.cpp
@@ -103,18 +103,14 @@
                                            GrVkCommandBuffer* commandBuffer) {
     this->setRenderTargetState(renderTarget, programInfo.origin());
 
-    fGeometryProcessor->setData(fDataManager, programInfo.primProc(),
-                                GrFragmentProcessor::CoordTransformIter(programInfo.pipeline()));
-    GrFragmentProcessor::Iter iter(programInfo.pipeline());
+    GrFragmentProcessor::PipelineCoordTransformRange transformRange(programInfo.pipeline());
+    fGeometryProcessor->setData(fDataManager, programInfo.primProc(), transformRange);
+    GrFragmentProcessor::Iter fpIter(programInfo.pipeline());
     GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt);
-    const GrFragmentProcessor* fp = iter.next();
-    GrGLSLFragmentProcessor* glslFP = glslIter.next();
-    while (fp && glslFP) {
-        glslFP->setData(fDataManager, *fp);
-        fp = iter.next();
-        glslFP = glslIter.next();
+    for (; fpIter && glslIter; ++fpIter, ++glslIter) {
+        glslIter->setData(fDataManager, *fpIter);
     }
-    SkASSERT(!fp && !glslFP);
+    SkASSERT(!fpIter && !glslIter);
 
     {
         SkIPoint offset;
@@ -168,20 +164,16 @@
         samplerBindings[currTextureBinding++] = {sampler.samplerState(), texture};
     }
 
-    GrFragmentProcessor::Iter iter(pipeline);
+    GrFragmentProcessor::Iter fpIter(pipeline);
     GrGLSLFragmentProcessor::Iter glslIter(fFragmentProcessors.get(), fFragmentProcessorCnt);
-    const GrFragmentProcessor* fp = iter.next();
-    GrGLSLFragmentProcessor* glslFP = glslIter.next();
-    while (fp && glslFP) {
-        for (int i = 0; i < fp->numTextureSamplers(); ++i) {
-            const auto& sampler = fp->textureSampler(i);
+    for (; fpIter && glslIter; ++fpIter, ++glslIter) {
+        for (int i = 0; i < fpIter->numTextureSamplers(); ++i) {
+            const auto& sampler = fpIter->textureSampler(i);
             samplerBindings[currTextureBinding++] =
                     {sampler.samplerState(), static_cast<GrVkTexture*>(sampler.peekTexture())};
         }
-        fp = iter.next();
-        glslFP = glslIter.next();
     }
-    SkASSERT(!fp && !glslFP);
+    SkASSERT(!fpIter && !glslIter);
 
     if (GrTexture* dstTexture = pipeline.peekDstTexture()) {
         samplerBindings[currTextureBinding++] = {