GrBicubicEffect uses GrTextureEffect.

This opens the door to, but doesn't add support for, applying this
effect to other child processor types.

Fixes issue when applying effect to rectangle textures where steps were
done using increments of 1/w and 1/h instead of 1.

Change-Id: I74e20d6c96931c337b22b156a61c8e279c6fbc62
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/259420
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
diff --git a/src/gpu/GrSurfaceContext.cpp b/src/gpu/GrSurfaceContext.cpp
index 407dfe3..629532b 100644
--- a/src/gpu/GrSurfaceContext.cpp
+++ b/src/gpu/GrSurfaceContext.cpp
@@ -637,13 +637,10 @@
             } else if (nextH == srcH) {
                 dir = GrBicubicEffect::Direction::kX;
             }
-            if (srcW != texView.proxy()->width() || srcH != texView.proxy()->height()) {
-                auto domain = GrTextureDomain::MakeTexelDomain(
-                        SkIRect::MakeXYWH(srcX, srcY, srcW, srcH), GrTextureDomain::kClamp_Mode);
-                fp = GrBicubicEffect::Make(std::move(texView), matrix, domain, dir, prevAlphaType);
-            } else {
-                fp = GrBicubicEffect::Make(std::move(texView), matrix, dir, prevAlphaType);
-            }
+            static constexpr GrSamplerState::WrapMode kWM = GrSamplerState::WrapMode::kClamp;
+            auto subset = SkRect::MakeXYWH(srcX, srcY, srcW, srcH);
+            fp = GrBicubicEffect::MakeSubset(std::move(texView), prevAlphaType, matrix, kWM, kWM,
+                                             subset, dir, *this->caps());
             if (xform) {
                 fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(xform));
             }
diff --git a/src/gpu/GrTextureProducer.cpp b/src/gpu/GrTextureProducer.cpp
index a850014..0e2f908 100644
--- a/src/gpu/GrTextureProducer.cpp
+++ b/src/gpu/GrTextureProducer.cpp
@@ -193,11 +193,10 @@
     SkASSERT(view.asTextureProxy());
     const auto& caps = *fContext->priv().caps();
     SkAlphaType srcAlphaType = this->alphaType();
+    auto wm = fDomainNeedsDecal ? GrSamplerState::WrapMode::kClampToBorder
+                                : GrSamplerState::WrapMode::kClamp;
     if (filterOrNullForBicubic) {
-        GrSamplerState::WrapMode wrapMode = fDomainNeedsDecal
-                                                    ? GrSamplerState::WrapMode::kClampToBorder
-                                                    : GrSamplerState::WrapMode::kClamp;
-        GrSamplerState samplerState(wrapMode, *filterOrNullForBicubic);
+        GrSamplerState samplerState(wm, *filterOrNullForBicubic);
         if (kNoDomain_DomainMode == domainMode) {
             return GrTextureEffect::Make(std::move(view), srcAlphaType, textureMatrix, samplerState,
                                          caps);
@@ -205,23 +204,15 @@
         return GrTextureEffect::MakeSubset(std::move(view), srcAlphaType, textureMatrix,
                                            samplerState, domain, caps);
     } else {
-        static const GrSamplerState::WrapMode kClampClamp[] = {
-                GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp};
-        static const GrSamplerState::WrapMode kDecalDecal[] = {
-                GrSamplerState::WrapMode::kClampToBorder, GrSamplerState::WrapMode::kClampToBorder};
 
         static constexpr auto kDir = GrBicubicEffect::Direction::kXY;
-        bool clampToBorderSupport = caps.clampToBorderSupport();
-        if (kDomain_DomainMode == domainMode || (fDomainNeedsDecal && !clampToBorderSupport)) {
-            GrTextureDomain::Mode wrapMode = fDomainNeedsDecal ? GrTextureDomain::kDecal_Mode
-                                         : GrTextureDomain::kClamp_Mode;
-            return GrBicubicEffect::Make(std::move(view), textureMatrix, kClampClamp, wrapMode,
-                                         wrapMode, kDir, srcAlphaType,
-                                         kDomain_DomainMode == domainMode ? &domain : nullptr);
+        const auto& caps = *fContext->priv().caps();
+        if (kDomain_DomainMode == domainMode) {
+            return GrBicubicEffect::MakeSubset(std::move(view), srcAlphaType, textureMatrix, wm, wm,
+                                               domain, kDir, caps);
         } else {
-            return GrBicubicEffect::Make(std::move(view), textureMatrix,
-                                         fDomainNeedsDecal ? kDecalDecal : kClampClamp, kDir,
-                                         srcAlphaType);
+            return GrBicubicEffect::Make(std::move(view), srcAlphaType, textureMatrix, wm, wm, kDir,
+                                         caps);
         }
     }
 }
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index e04e563..d52787e 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -938,16 +938,18 @@
     if (needsTextureDomain && (SkCanvas::kStrict_SrcRectConstraint == constraint)) {
         if (bicubic) {
             static constexpr auto kDir = GrBicubicEffect::Direction::kXY;
-            fp = GrBicubicEffect::Make(std::move(view), texMatrix, srcRect, kDir, srcAlphaType);
+            fp = GrBicubicEffect::MakeSubset(std::move(view), srcAlphaType, texMatrix,
+                                             samplerState.wrapModeX(), samplerState.wrapModeY(),
+                                             srcRect, kDir, caps);
         } else {
             fp = GrTextureEffect::MakeSubset(std::move(view), srcAlphaType, texMatrix,
                                              samplerState, srcRect, caps);
         }
     } else if (bicubic) {
         SkASSERT(GrSamplerState::Filter::kNearest == samplerState.filter());
-        GrSamplerState::WrapMode wrapMode[2] = {samplerState.wrapModeX(), samplerState.wrapModeY()};
         static constexpr auto kDir = GrBicubicEffect::Direction::kXY;
-        fp = GrBicubicEffect::Make(std::move(view), texMatrix, wrapMode, kDir, srcAlphaType);
+        fp = GrBicubicEffect::Make(std::move(view), srcAlphaType, texMatrix,
+                                   samplerState.wrapModeX(), samplerState.wrapModeY(), kDir, caps);
     } else {
         fp = GrTextureEffect::Make(std::move(view), srcAlphaType, texMatrix, samplerState, caps);
     }
diff --git a/src/gpu/effects/GrBicubicEffect.cpp b/src/gpu/effects/GrBicubicEffect.cpp
index ad48d1c..98eaf17 100644
--- a/src/gpu/effects/GrBicubicEffect.cpp
+++ b/src/gpu/effects/GrBicubicEffect.cpp
@@ -5,31 +5,21 @@
  * found in the LICENSE file.
  */
 
-#include "src/core/SkMatrixPriv.h"
 #include "src/gpu/effects/GrBicubicEffect.h"
 
 #include "include/gpu/GrTexture.h"
+#include "src/core/SkMatrixPriv.h"
+#include "src/gpu/effects/GrTextureEffect.h"
 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
 #include "src/gpu/glsl/GrGLSLProgramDataManager.h"
 #include "src/gpu/glsl/GrGLSLUniformHandler.h"
 
-class GrGLBicubicEffect : public GrGLSLFragmentProcessor {
+class GrBicubicEffect::Impl : public GrGLSLFragmentProcessor {
 public:
     void emitCode(EmitArgs&) override;
 
-    static inline void GenKey(const GrProcessor& effect, const GrShaderCaps&,
-                              GrProcessorKeyBuilder* b) {
-        const GrBicubicEffect& bicubicEffect = effect.cast<GrBicubicEffect>();
-        b->add32(GrTextureDomain::GLDomain::DomainKey(bicubicEffect.domain()));
-        uint32_t bidir = bicubicEffect.direction() == GrBicubicEffect::Direction::kXY ? 1 : 0;
-        b->add32(bidir | (bicubicEffect.alphaType() << 1));
-    }
-
-protected:
-    void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
-
 private:
-    typedef GrGLSLProgramDataManager::UniformHandle UniformHandle;
+    void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
 
     UniformHandle fDimensions;
     GrTextureDomain::GLDomain   fDomain;
@@ -37,7 +27,7 @@
     typedef GrGLSLFragmentProcessor INHERITED;
 };
 
-void GrGLBicubicEffect::emitCode(EmitArgs& args) {
+void GrBicubicEffect::Impl::emitCode(EmitArgs& args) {
     const GrBicubicEffect& bicubicEffect = args.fFp.cast<GrBicubicEffect>();
 
     GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
@@ -64,7 +54,7 @@
      * favorite overall spline - this is now commonly known as the Mitchell filter, and is the
      * source of the specific weights below.
      *
-     * This is GLSL, so the matrix is column-major (transposed from standard matrix notation).
+     * This is SkSL, so the matrix is column-major (transposed from standard matrix notation).
      */
     fragBuilder->codeAppend("half4x4 kMitchellCoefficients = half4x4("
                             " 1.0 / 18.0,  16.0 / 18.0,   1.0 / 18.0,  0.0 / 18.0,"
@@ -78,7 +68,7 @@
     // double hit a texel.
     fragBuilder->codeAppendf("half2 f = half2(fract(coord * %s.zw));", dims);
     fragBuilder->codeAppendf("coord = coord + (half2(0.5) - f) * %s.xy;", dims);
-    if (bicubicEffect.direction() == GrBicubicEffect::Direction::kXY) {
+    if (bicubicEffect.fDirection == GrBicubicEffect::Direction::kXY) {
         fragBuilder->codeAppend(
                 "half4 wx = kMitchellCoefficients * half4(1.0, f.x, f.x * f.x, f.x * f.x * f.x);");
         fragBuilder->codeAppend(
@@ -88,15 +78,9 @@
             for (int x = 0; x < 4; ++x) {
                 SkString coord;
                 coord.printf("coord + %s.xy * float2(%d, %d)", dims, x - 1, y - 1);
-                SkString sampleVar;
-                sampleVar.printf("rowColors[%d]", x);
-                fDomain.sampleTexture(fragBuilder,
-                                      args.fUniformHandler,
-                                      args.fShaderCaps,
-                                      bicubicEffect.domain(),
-                                      sampleVar.c_str(),
-                                      coord,
-                                      args.fTexSamplers[0]);
+                auto childStr =
+                        this->invokeChild(0, args, SkSL::String(coord.c_str(), coord.size()));
+                fragBuilder->codeAppendf("rowColors[%d] = %s;", x, childStr.c_str());
             }
             fragBuilder->codeAppendf(
                     "half4 s%d = wx.x * rowColors[0] + wx.y * rowColors[1] + wx.z * rowColors[2] + "
@@ -114,102 +98,139 @@
         for (int i = 0; i < 4; ++i) {
             SkString coord;
             coord.printf("coord + %s.xy * half(%d)", dims, i - 1);
-            SkString samplerVar;
-            samplerVar.printf("c[%d]", i);
-            // With added complexity we could apply the domain once in X or Y depending on
-            // direction rather than for each of the four lookups, but then we might not be
-            // be able to share code for Direction::kX and ::kY.
-            fDomain.sampleTexture(fragBuilder,
-                                  args.fUniformHandler,
-                                  args.fShaderCaps,
-                                  bicubicEffect.domain(),
-                                  samplerVar.c_str(),
-                                  coord,
-                                  args.fTexSamplers[0]);
+            auto childStr = this->invokeChild(0, args, SkSL::String(coord.c_str(), coord.size()));
+            fragBuilder->codeAppendf("c[%d] = %s;", i, childStr.c_str());
         }
         fragBuilder->codeAppend(
                 "half4 bicubicColor = c[0] * w.x + c[1] * w.y + c[2] * w.z + c[3] * w.w;");
     }
     // Bicubic can send colors out of range, so clamp to get them back in (source) gamut.
     // The kind of clamp we have to do depends on the alpha type.
-    if (kPremul_SkAlphaType == bicubicEffect.alphaType()) {
-        fragBuilder->codeAppend("bicubicColor.a = saturate(bicubicColor.a);");
-        fragBuilder->codeAppend(
-                "bicubicColor.rgb = max(half3(0.0), min(bicubicColor.rgb, bicubicColor.aaa));");
-    } else {
-        fragBuilder->codeAppend("bicubicColor = saturate(bicubicColor);");
+    switch (bicubicEffect.fClamp) {
+        case Clamp::kUnpremul:
+            fragBuilder->codeAppend("bicubicColor = saturate(bicubicColor);");
+            break;
+        case Clamp::kPremul:
+            fragBuilder->codeAppend(
+                    "bicubicColor.rgb = max(half3(0.0), min(bicubicColor.rgb, bicubicColor.aaa));");
+            break;
     }
     fragBuilder->codeAppendf("%s = bicubicColor * %s;", args.fOutputColor, args.fInputColor);
 }
 
-void GrGLBicubicEffect::onSetData(const GrGLSLProgramDataManager& pdman,
-                                  const GrFragmentProcessor& processor) {
+void GrBicubicEffect::Impl::onSetData(const GrGLSLProgramDataManager& pdman,
+                                      const GrFragmentProcessor& processor) {
     const GrBicubicEffect& bicubicEffect = processor.cast<GrBicubicEffect>();
-    const auto& view = processor.textureSampler(0).view();
-    SkISize textureDims = view.proxy()->backingStoreDimensions();
+    // Currently we only ever construct with GrTextureEffect and always take its
+    // coord transform as our own.
+    SkASSERT(bicubicEffect.fCoordTransform.peekTexture());
+    SkISize textureDims = bicubicEffect.fCoordTransform.peekTexture()->dimensions();
 
     float dims[4] = {0, 0, 0, 0};
-    if (bicubicEffect.direction() != GrBicubicEffect::Direction::kY) {
-        dims[0] = 1.0f / textureDims.width();
-        dims[2] = textureDims.width();
+    if (bicubicEffect.fDirection != GrBicubicEffect::Direction::kY) {
+        if (bicubicEffect.fCoordTransform.normalize()) {
+            dims[0] = 1.f / textureDims.width();
+            dims[2] = textureDims.width();
+        } else {
+            dims[0] = dims[2] = 1.f;
+        }
     }
-    if (bicubicEffect.direction() != GrBicubicEffect::Direction::kX) {
-        dims[1] = 1.0f / textureDims.height();
-        dims[3] = textureDims.height();
+    if (bicubicEffect.fDirection != GrBicubicEffect::Direction::kX) {
+        if (bicubicEffect.fCoordTransform.normalize()) {
+            dims[1] = 1.f / textureDims.height();
+            dims[3] = textureDims.height();
+        } else {
+            dims[1] = dims[3] = 1.f;
+        }
     }
     pdman.set4fv(fDimensions, 1, dims);
-    fDomain.setData(pdman, bicubicEffect.domain(), view,
-                    processor.textureSampler(0).samplerState());
 }
 
-GrBicubicEffect::GrBicubicEffect(GrSurfaceProxyView view, const SkMatrix& matrix,
-                                 const SkRect& domain, const GrSamplerState::WrapMode wrapModes[2],
-                                 GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY,
-                                 Direction direction, SkAlphaType alphaType)
-        : INHERITED{kGrBicubicEffect_ClassID,
-                    ModulateForSamplerOptFlags(
-                            alphaType, GrTextureDomain::IsDecalSampled(wrapModes, modeX, modeY))}
-        , fCoordTransform(matrix, view.proxy(), view.origin())
-        , fDomain(view.proxy(), domain, modeX, modeY)
-        , fTextureSampler(std::move(view),
-                          GrSamplerState(wrapModes, GrSamplerState::Filter::kNearest))
-        , fAlphaType(alphaType)
-        , fDirection(direction) {
+std::unique_ptr<GrFragmentProcessor> GrBicubicEffect::Make(GrSurfaceProxyView view,
+                                                           SkAlphaType alphaType,
+                                                           const SkMatrix& matrix,
+                                                           Direction direction) {
+    auto fp = GrTextureEffect::Make(std::move(view), alphaType, matrix);
+    auto clamp = kPremul_SkAlphaType == alphaType ? Clamp::kPremul : Clamp::kUnpremul;
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrBicubicEffect(std::move(fp), direction, clamp));
+}
+
+std::unique_ptr<GrFragmentProcessor> GrBicubicEffect::Make(GrSurfaceProxyView view,
+                                                           SkAlphaType alphaType,
+                                                           const SkMatrix& matrix,
+                                                           const GrSamplerState::WrapMode wrapX,
+                                                           const GrSamplerState::WrapMode wrapY,
+                                                           Direction direction,
+                                                           const GrCaps& caps) {
+    GrSamplerState sampler(wrapX, wrapY, GrSamplerState::Filter::kNearest);
+    std::unique_ptr<GrFragmentProcessor> fp;
+    fp = GrTextureEffect::Make(std::move(view), alphaType, matrix, sampler, caps);
+    auto clamp = kPremul_SkAlphaType == alphaType ? Clamp::kPremul : Clamp::kUnpremul;
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrBicubicEffect(std::move(fp), direction, clamp));
+}
+
+std::unique_ptr<GrFragmentProcessor> GrBicubicEffect::MakeSubset(
+        GrSurfaceProxyView view,
+        SkAlphaType alphaType,
+        const SkMatrix& matrix,
+        const GrSamplerState::WrapMode wrapX,
+        const GrSamplerState::WrapMode wrapY,
+        const SkRect& subset,
+        Direction direction,
+        const GrCaps& caps) {
+    GrSamplerState sampler(wrapX, wrapY, GrSamplerState::Filter::kNearest);
+    std::unique_ptr<GrFragmentProcessor> fp;
+    fp = GrTextureEffect::MakeSubset(std::move(view), alphaType, matrix, sampler, subset, caps);
+    auto clamp = kPremul_SkAlphaType == alphaType ? Clamp::kPremul : Clamp::kUnpremul;
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrBicubicEffect(std::move(fp), direction, clamp));
+}
+
+GrBicubicEffect::GrBicubicEffect(std::unique_ptr<GrFragmentProcessor> fp,
+                                 Direction direction,
+                                 Clamp clamp)
+        : INHERITED(kGrBicubicEffect_ClassID, ProcessorOptimizationFlags(fp.get()))
+        , fDirection(direction)
+        , fClamp(clamp) {
+    SkASSERT(fp->numCoordTransforms() == 1);
+    fCoordTransform = fp->coordTransform(0);
     this->addCoordTransform(&fCoordTransform);
-    this->setTextureSamplerCnt(1);
+    fp->coordTransform(0) = {};
+    fp->setSampledWithExplicitCoords(true);
+    this->registerChildProcessor(std::move(fp));
 }
 
 GrBicubicEffect::GrBicubicEffect(const GrBicubicEffect& that)
         : INHERITED(kGrBicubicEffect_ClassID, that.optimizationFlags())
         , fCoordTransform(that.fCoordTransform)
-        , fDomain(that.fDomain)
-        , fTextureSampler(that.fTextureSampler)
-        , fAlphaType(that.fAlphaType)
-        , fDirection(that.fDirection) {
+        , fDirection(that.fDirection)
+        , fClamp(that.fClamp) {
     this->addCoordTransform(&fCoordTransform);
-    this->setTextureSamplerCnt(1);
+    auto child = that.childProcessor(0).clone();
+    child->setSampledWithExplicitCoords(true);
+    this->registerChildProcessor(std::move(child));
 }
 
 void GrBicubicEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
                                             GrProcessorKeyBuilder* b) const {
-    GrGLBicubicEffect::GenKey(*this, caps, b);
+    uint32_t key = (fDirection == GrBicubicEffect::Direction::kXY)
+                 | (static_cast<uint32_t>(fClamp) << 1);
+    b->add32(key);
 }
 
-GrGLSLFragmentProcessor* GrBicubicEffect::onCreateGLSLInstance() const  {
-    return new GrGLBicubicEffect;
-}
+GrGLSLFragmentProcessor* GrBicubicEffect::onCreateGLSLInstance() const { return new Impl(); }
 
-bool GrBicubicEffect::onIsEqual(const GrFragmentProcessor& sBase) const {
-    const GrBicubicEffect& s = sBase.cast<GrBicubicEffect>();
-    return fDomain == s.fDomain && fDirection == s.fDirection && fAlphaType == s.fAlphaType;
+bool GrBicubicEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    const auto& that = other.cast<GrBicubicEffect>();
+    return fDirection == that.fDirection && fClamp == that.fClamp;
 }
 
 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrBicubicEffect);
 
 #if GR_TEST_UTILS
 std::unique_ptr<GrFragmentProcessor> GrBicubicEffect::TestCreate(GrProcessorTestData* d) {
-    static const GrSamplerState::WrapMode kClampClamp[] = {GrSamplerState::WrapMode::kClamp,
-                                                           GrSamplerState::WrapMode::kClamp};
     Direction direction = Direction::kX;
     switch (d->fRandom->nextULessThan(3)) {
         case 0:
@@ -223,8 +244,24 @@
             break;
     }
     auto [view, ct, at] = d->randomView();
+    auto m = GrTest::TestMatrix(d->fRandom);
+    if (d->fRandom->nextBool()) {
+        GrSamplerState::WrapMode wm[2];
+        GrTest::TestWrapModes(d->fRandom, wm);
 
-    return GrBicubicEffect::Make(std::move(view), SkMatrix::I(), kClampClamp, direction, at);
+        if (d->fRandom->nextBool()) {
+            SkRect subset;
+            subset.fLeft   = d->fRandom->nextSScalar1() * view.width();
+            subset.fTop    = d->fRandom->nextSScalar1() * view.height();
+            subset.fRight  = d->fRandom->nextSScalar1() * view.width();
+            subset.fBottom = d->fRandom->nextSScalar1() * view.height();
+            subset.sort();
+            return MakeSubset(std::move(view), at, m, wm[0], wm[1], subset, direction, *d->caps());
+        }
+        return Make(std::move(view), at, m, wm[0], wm[1], direction, *d->caps());
+    } else {
+        return Make(std::move(view), at, m, direction);
+    }
 }
 #endif
 
diff --git a/src/gpu/effects/GrBicubicEffect.h b/src/gpu/effects/GrBicubicEffect.h
index 3ae0e43..1ad5688 100644
--- a/src/gpu/effects/GrBicubicEffect.h
+++ b/src/gpu/effects/GrBicubicEffect.h
@@ -35,78 +35,37 @@
         return std::unique_ptr<GrFragmentProcessor>(new GrBicubicEffect(*this));
     }
 
-    const GrTextureDomain& domain() const { return fDomain; }
-
-    Direction direction() const { return fDirection; }
-
-    SkAlphaType alphaType() const { return fAlphaType; }
-
     /**
      * Create a Mitchell filter effect with specified texture matrix with clamp wrap mode.
      */
     static std::unique_ptr<GrFragmentProcessor> Make(GrSurfaceProxyView view,
-                                                     const SkMatrix& matrix,
-                                                     Direction direction,
-                                                     SkAlphaType alphaType) {
-        static constexpr GrSamplerState::WrapMode kClampClamp[] = {
-                GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp};
-        return Make(std::move(view), matrix, kClampClamp, GrTextureDomain::kIgnore_Mode,
-                    GrTextureDomain::kIgnore_Mode, direction, alphaType);
-    }
+                                                     SkAlphaType,
+                                                     const SkMatrix&,
+                                                     Direction direction);
 
     /**
-     * Create a Mitchell filter effect with specified texture matrix and x/y tile modes.
+     * Create a Mitchell filter effect for a texture with arbitrary wrap modes.
      */
     static std::unique_ptr<GrFragmentProcessor> Make(GrSurfaceProxyView view,
-                                                     const SkMatrix& matrix,
-                                                     const GrSamplerState::WrapMode wrapModes[2],
-                                                     Direction direction,
-                                                     SkAlphaType alphaType) {
-        // Ignore the domain on x and y, since this factory relies solely on the wrap mode of the
-        // sampler to constrain texture coordinates
-        return Make(std::move(view), matrix, wrapModes, GrTextureDomain::kIgnore_Mode,
-                    GrTextureDomain::kIgnore_Mode, direction, alphaType);
-    }
+                                                     SkAlphaType,
+                                                     const SkMatrix&,
+                                                     const GrSamplerState::WrapMode wrapX,
+                                                     const GrSamplerState::WrapMode wrapY,
+                                                     Direction,
+                                                     const GrCaps&);
 
     /**
-     * Create a Mitchell filter effect with specified texture matrix and x/y tile modes. This
-     * supports providing modes for the texture domain explicitly, in the event that it should
-     * override the behavior of the sampler's tile mode (e.g. clamp to border unsupported).
+     * Create a Mitchell filter effect for a subset of a texture, specified by a texture coordinate
+     * rectangle subset. The WrapModes apply to the subset.
      */
-    static std::unique_ptr<GrFragmentProcessor> Make(GrSurfaceProxyView view,
-                                                     const SkMatrix& matrix,
-                                                     const GrSamplerState::WrapMode wrapModes[2],
-                                                     GrTextureDomain::Mode modeX,
-                                                     GrTextureDomain::Mode modeY,
-                                                     Direction direction,
-                                                     SkAlphaType alphaType,
-                                                     const SkRect* domain = nullptr) {
-        SkRect resolvedDomain;
-        if (domain) {
-            resolvedDomain = *domain;
-        } else {
-            resolvedDomain = GrTextureDomain::MakeTexelDomain(
-                    SkIRect::MakeSize(view.proxy()->dimensions()), modeX, modeY);
-        }
-        return std::unique_ptr<GrFragmentProcessor>(
-                new GrBicubicEffect(std::move(view), matrix, resolvedDomain, wrapModes, modeX,
-                                    modeY, direction, alphaType));
-    }
-
-    /**
-     * Create a Mitchell filter effect with a texture matrix and a domain.
-     */
-    static std::unique_ptr<GrFragmentProcessor> Make(GrSurfaceProxyView view,
-                                                     const SkMatrix& matrix,
-                                                     const SkRect& domain,
-                                                     Direction direction,
-                                                     SkAlphaType alphaType) {
-        static const GrSamplerState::WrapMode kClampClamp[] = {
-                GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp};
-        return Make(std::move(view), matrix, kClampClamp, GrTextureDomain::kClamp_Mode,
-                    GrTextureDomain::kClamp_Mode, direction, alphaType, &domain);
-    }
-
+    static std::unique_ptr<GrFragmentProcessor> MakeSubset(GrSurfaceProxyView view,
+                                                           SkAlphaType,
+                                                           const SkMatrix&,
+                                                           const GrSamplerState::WrapMode wrapX,
+                                                           const GrSamplerState::WrapMode wrapY,
+                                                           const SkRect& subset,
+                                                           Direction,
+                                                           const GrCaps&);
     /**
      * Determines whether the bicubic effect should be used based on the transformation from the
      * local coords to the device. Returns true if the bicubic effect should be used. filterMode
@@ -118,9 +77,14 @@
                                  GrSamplerState::Filter* filterMode);
 
 private:
-    GrBicubicEffect(GrSurfaceProxyView, const SkMatrix& matrix, const SkRect& domain,
-                    const GrSamplerState::WrapMode wrapModes[2], GrTextureDomain::Mode modeX,
-                    GrTextureDomain::Mode modeY, Direction direction, SkAlphaType);
+    class Impl;
+
+    enum class Clamp {
+        kUnpremul,  // clamps rgba to 0..1
+        kPremul,    // clamps a to 0..1 and rgb to 0..a
+    };
+
+    GrBicubicEffect(std::unique_ptr<GrFragmentProcessor> fp, Direction direction, Clamp clamp);
     explicit GrBicubicEffect(const GrBicubicEffect&);
 
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
@@ -129,13 +93,9 @@
 
     bool onIsEqual(const GrFragmentProcessor&) const override;
 
-    const TextureSampler& onTextureSampler(int) const override { return fTextureSampler; }
-
     GrCoordTransform fCoordTransform;
-    GrTextureDomain fDomain;
-    TextureSampler fTextureSampler;
-    SkAlphaType fAlphaType;
     Direction fDirection;
+    Clamp fClamp;
 
     GR_DECLARE_FRAGMENT_PROCESSOR_TEST
 
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
index 4603aab..b509e0b 100755
--- a/src/shaders/SkImageShader.cpp
+++ b/src/shaders/SkImageShader.cpp
@@ -201,8 +201,8 @@
         return nullptr;
     }
 
-    GrSamplerState::WrapMode wrapModes[] = {tile_mode_to_wrap_mode(fTileModeX),
-                                            tile_mode_to_wrap_mode(fTileModeY)};
+    GrSamplerState::WrapMode wm[] = {tile_mode_to_wrap_mode(fTileModeX),
+                                     tile_mode_to_wrap_mode(fTileModeY)};
 
     // Must set wrap and filter on the sampler before requesting a texture. In two places below
     // we check the matrix scale factors to determine how to interpret the filter quality setting.
@@ -212,7 +212,7 @@
     GrSamplerState::Filter textureFilterMode = GrSkFilterQualityToGrFilterMode(
             fImage->width(), fImage->height(), args.fFilterQuality, *args.fViewMatrix, *lm,
             args.fContext->priv().options().fSharpenMipmappedTextures, &doBicubic);
-    GrSamplerState samplerState(wrapModes, textureFilterMode);
+    GrSamplerState samplerState(wm, textureFilterMode);
     SkScalar scaleAdjust[2] = { 1.0f, 1.0f };
     GrSurfaceProxyView view = as_IB(fImage)->refView(args.fContext, samplerState, scaleAdjust);
     if (!view) {
@@ -227,24 +227,9 @@
 
     std::unique_ptr<GrFragmentProcessor> inner;
     if (doBicubic) {
-        // If either domainX or domainY are un-ignored, a texture domain effect has to be used to
-        // implement the decal mode (while leaving non-decal axes alone). The wrap mode originally
-        // clamp-to-border is reset to clamp since the hw cannot implement it directly.
-        GrTextureDomain::Mode domainX = GrTextureDomain::kIgnore_Mode;
-        GrTextureDomain::Mode domainY = GrTextureDomain::kIgnore_Mode;
-        if (!caps.clampToBorderSupport()) {
-            if (wrapModes[0] == GrSamplerState::WrapMode::kClampToBorder) {
-                domainX = GrTextureDomain::kDecal_Mode;
-                wrapModes[0] = GrSamplerState::WrapMode::kClamp;
-            }
-            if (wrapModes[1] == GrSamplerState::WrapMode::kClampToBorder) {
-                domainY = GrTextureDomain::kDecal_Mode;
-                wrapModes[1] = GrSamplerState::WrapMode::kClamp;
-            }
-        }
         static constexpr auto kDir = GrBicubicEffect::Direction::kXY;
-        inner = GrBicubicEffect::Make(std::move(view), lmInverse, wrapModes, domainX, domainY, kDir,
-                                      srcAlphaType);
+        inner = GrBicubicEffect::Make(std::move(view), srcAlphaType, lmInverse, wm[0], wm[1], kDir,
+                                      caps);
     } else {
         inner = GrTextureEffect::Make(std::move(view), srcAlphaType, lmInverse, samplerState, caps);
     }