Move GrDomainEffect functionality into GrTextureEffect and delete
the former.

New factories for GrTextureEffect have two main variants:
MakeTexelSubset(): adjusts the input integer rectangle to account for
filtering

MakeSubset(): assumes caller has calculated the exact rectangle needed
as floats.

Currently this disables filtering for shader-based mirroring or repeat.
Will fix this later. The old effect also did not support this.

Change-Id: If47d8ecfbb349b0d7b39ab5ba864fe3cc1b139e4
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/265518
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/core/SkBlurMF.cpp b/src/core/SkBlurMF.cpp
index d6df230..5102f9e 100644
--- a/src/core/SkBlurMF.cpp
+++ b/src/core/SkBlurMF.cpp
@@ -28,7 +28,6 @@
 #include "src/gpu/GrShaderCaps.h"
 #include "src/gpu/GrStyle.h"
 #include "src/gpu/GrTextureProxy.h"
-#include "src/gpu/effects/GrTextureDomain.h"
 #include "src/gpu/effects/GrTextureEffect.h"
 #include "src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h"
 #include "src/gpu/effects/generated/GrRRectBlurEffect.h"
diff --git a/src/core/SkGpuBlurUtils.cpp b/src/core/SkGpuBlurUtils.cpp
index 514e85a..f63f31e 100644
--- a/src/core/SkGpuBlurUtils.cpp
+++ b/src/core/SkGpuBlurUtils.cpp
@@ -311,17 +311,15 @@
         }
 
         GrPaint paint;
-        auto fp = GrTextureEffect::Make(std::move(srcProxy), srcAlphaType, SkMatrix::I(),
-                                        GrSamplerState::Filter::kBilerp);
+        std::unique_ptr<GrFragmentProcessor> fp;
         if (i == 1) {
-            // GrDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter.
-            GrTextureDomain::Mode domainMode;
+            GrSamplerState::WrapMode wrapMode;
             if (mode == SkTileMode::kClamp) {
-                domainMode = GrTextureDomain::kClamp_Mode;
+                wrapMode = GrSamplerState::WrapMode::kClamp;
             } else {
-                // GrDomainEffect does not support k[Mirror]Repeat with GrSamplerState::Filter.
-                // So we use decal.
-                domainMode = GrTextureDomain::kDecal_Mode;
+                // GrTextureEffect does not support WrapMode::k[Mirror]Repeat with
+                // GrSamplerState::Filter::kBilerp. So we use kClampToBorder.
+                wrapMode = GrSamplerState::WrapMode::kClampToBorder;
             }
             SkRect domain = SkRect::Make(*contentRect);
             domain.inset((i < scaleFactorX) ? SK_ScalarHalf + SK_ScalarNearlyZero : 0.0f,
@@ -333,8 +331,14 @@
             if (domain.fBottom < domain.fTop) {
                 domain.fTop = domain.fBottom = SkScalarAve(domain.fTop, domain.fBottom);
             }
-            fp = GrDomainEffect::Make(std::move(fp), domain, domainMode, true);
+            const auto& caps = *context->priv().caps();
+            GrSamplerState sampler(wrapMode, GrSamplerState::Filter::kBilerp);
+            fp = GrTextureEffect::MakeSubset(std::move(srcProxy), srcAlphaType, SkMatrix::I(),
+                                             sampler, domain, caps);
             srcRect.offset(-srcOffset);
+        } else {
+            fp = GrTextureEffect::Make(std::move(srcProxy), srcAlphaType, SkMatrix::I(),
+                                       GrSamplerState::Filter::kBilerp);
         }
         paint.addColorFragmentProcessor(std::move(fp));
         paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
@@ -388,11 +392,9 @@
     }
 
     GrPaint paint;
-    SkRect domain = GrTextureDomain::MakeTexelDomain(srcBounds, GrTextureDomain::kClamp_Mode,
-                                                     GrTextureDomain::kClamp_Mode);
-    auto fp = GrTextureEffect::Make(std::move(srcProxy), srcAlphaType, SkMatrix::I(),
-                                    GrSamplerState::Filter::kBilerp);
-    fp = GrDomainEffect::Make(std::move(fp), domain, GrTextureDomain::kClamp_Mode, true);
+    const auto& caps = *context->priv().caps();
+    auto fp = GrTextureEffect::MakeTexelSubset(std::move(srcProxy), srcAlphaType, SkMatrix::I(),
+                                               GrSamplerState::Filter::kBilerp, srcBounds, caps);
     paint.addColorFragmentProcessor(std::move(fp));
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
     GrFixedClip clip(SkIRect::MakeSize(dstSize));
diff --git a/src/core/SkGpuBlurUtils.h b/src/core/SkGpuBlurUtils.h
index 53463e8..47bb9f0 100644
--- a/src/core/SkGpuBlurUtils.h
+++ b/src/core/SkGpuBlurUtils.h
@@ -10,7 +10,6 @@
 
 #if SK_SUPPORT_GPU
 #include "src/gpu/GrRenderTargetContext.h"
-#include "src/gpu/effects/GrTextureDomain.h"
 
 class GrContext;
 class GrTexture;
diff --git a/src/effects/imagefilters/SkArithmeticImageFilter.cpp b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
index 8a08602..ecca33e 100644
--- a/src/effects/imagefilters/SkArithmeticImageFilter.cpp
+++ b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
@@ -25,7 +25,6 @@
 #include "src/gpu/GrTextureProxy.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/effects/GrSkSLFP.h"
-#include "src/gpu/effects/GrTextureDomain.h"
 #include "src/gpu/effects/generated/GrConstColorProcessor.h"
 #include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
@@ -348,18 +347,17 @@
 
     GrPaint paint;
     std::unique_ptr<GrFragmentProcessor> bgFP;
+    const auto& caps = *ctx.getContext()->priv().caps();
+    GrSamplerState sampler(GrSamplerState::WrapMode::kClampToBorder,
+                           GrSamplerState::Filter::kNearest);
 
     if (backgroundProxy) {
         SkIRect bgSubset = background->subset();
         SkMatrix backgroundMatrix = SkMatrix::MakeTrans(
                 SkIntToScalar(bgSubset.left() - backgroundOffset.fX),
                 SkIntToScalar(bgSubset.top()  - backgroundOffset.fY));
-        bgFP = GrTextureEffect::Make(std::move(backgroundProxy), background->alphaType(),
-                                     backgroundMatrix, GrSamplerState::Filter::kNearest);
-        bgFP = GrDomainEffect::Make(
-                std::move(bgFP),
-                GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode),
-                GrTextureDomain::kDecal_Mode, false);
+        bgFP = GrTextureEffect::MakeTexelSubset(std::move(backgroundProxy), background->alphaType(),
+                                                backgroundMatrix, sampler, bgSubset, caps);
         bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
                                              background->alphaType(),
                                              ctx.colorSpace());
@@ -373,18 +371,14 @@
         SkMatrix foregroundMatrix = SkMatrix::MakeTrans(
                 SkIntToScalar(fgSubset.left() - foregroundOffset.fX),
                 SkIntToScalar(fgSubset.top()  - foregroundOffset.fY));
-        auto foregroundFP =
-                GrTextureEffect::Make(std::move(foregroundProxy), foreground->alphaType(),
-                                      foregroundMatrix, GrSamplerState::Filter::kNearest);
-        foregroundFP = GrDomainEffect::Make(
-                std::move(foregroundFP),
-                GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode),
-                GrTextureDomain::kDecal_Mode, false);
-        foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
-                                                     foreground->getColorSpace(),
-                                                     foreground->alphaType(),
-                                                     ctx.colorSpace());
-        paint.addColorFragmentProcessor(std::move(foregroundFP));
+        auto fgFP = GrTextureEffect::MakeTexelSubset(std::move(foregroundProxy),
+                                                     foreground->alphaType(), foregroundMatrix,
+                                                     sampler, fgSubset, caps);
+        fgFP = GrColorSpaceXformEffect::Make(std::move(fgFP),
+                                             foreground->getColorSpace(),
+                                             foreground->alphaType(),
+                                             ctx.colorSpace());
+        paint.addColorFragmentProcessor(std::move(fgFP));
 
         static auto effect = std::get<0>(SkRuntimeEffect::Make(SkString(SKSL_ARITHMETIC_SRC)));
         ArithmeticFPInputs inputs;
diff --git a/src/effects/imagefilters/SkXfermodeImageFilter.cpp b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
index 8924456..0a80ea2 100644
--- a/src/effects/imagefilters/SkXfermodeImageFilter.cpp
+++ b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
@@ -22,9 +22,7 @@
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/GrRenderTargetContext.h"
 #include "src/gpu/GrTextureProxy.h"
-
 #include "src/gpu/SkGr.h"
-#include "src/gpu/effects/GrTextureDomain.h"
 #include "src/gpu/effects/GrTextureEffect.h"
 #include "src/gpu/effects/generated/GrConstColorProcessor.h"
 #endif
@@ -260,18 +258,17 @@
 
     GrPaint paint;
     std::unique_ptr<GrFragmentProcessor> bgFP;
+    const auto& caps = *ctx.getContext()->priv().caps();
+    GrSamplerState sampler(GrSamplerState::WrapMode::kClampToBorder,
+                           GrSamplerState::Filter::kNearest);
 
     if (backgroundProxy) {
         SkIRect bgSubset = background->subset();
         SkMatrix bgMatrix = SkMatrix::MakeTrans(
                 SkIntToScalar(bgSubset.left() - backgroundOffset.fX),
                 SkIntToScalar(bgSubset.top()  - backgroundOffset.fY));
-        bgFP = GrTextureEffect::Make(std::move(backgroundProxy), background->alphaType(), bgMatrix,
-                                     GrSamplerState::Filter::kNearest);
-        bgFP = GrDomainEffect::Make(
-                std::move(bgFP),
-                GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode),
-                GrTextureDomain::kDecal_Mode, false);
+        bgFP = GrTextureEffect::MakeTexelSubset(std::move(backgroundProxy), background->alphaType(),
+                                                bgMatrix, sampler, bgSubset, caps);
         bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
                                              background->alphaType(),
                                              ctx.colorSpace());
@@ -285,18 +282,14 @@
         SkMatrix fgMatrix = SkMatrix::MakeTrans(
                 SkIntToScalar(fgSubset.left() - foregroundOffset.fX),
                 SkIntToScalar(fgSubset.top()  - foregroundOffset.fY));
-        auto foregroundFP =
-                GrTextureEffect::Make(std::move(foregroundProxy), foreground->alphaType(), fgMatrix,
-                                      GrSamplerState::Filter::kNearest);
-        foregroundFP = GrDomainEffect::Make(
-                std::move(foregroundFP),
-                GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode),
-                GrTextureDomain::kDecal_Mode, false);
-        foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
-                                                     foreground->getColorSpace(),
-                                                     foreground->alphaType(),
-                                                     ctx.colorSpace());
-        paint.addColorFragmentProcessor(std::move(foregroundFP));
+        auto fgFP = GrTextureEffect::MakeTexelSubset(std::move(foregroundProxy),
+                                                     foreground->alphaType(), fgMatrix, sampler,
+                                                     fgSubset, caps);
+        fgFP = GrColorSpaceXformEffect::Make(std::move(fgFP),
+                                             foreground->getColorSpace(),
+                                             foreground->alphaType(),
+                                             ctx.colorSpace());
+        paint.addColorFragmentProcessor(std::move(fgFP));
 
         std::unique_ptr<GrFragmentProcessor> xferFP = this->makeFGFrag(std::move(bgFP));
 
diff --git a/src/gpu/GrImageTextureMaker.cpp b/src/gpu/GrImageTextureMaker.cpp
index ead5dfe..cc6f145 100644
--- a/src/gpu/GrImageTextureMaker.cpp
+++ b/src/gpu/GrImageTextureMaker.cpp
@@ -8,6 +8,7 @@
 #include "src/gpu/GrImageTextureMaker.h"
 
 #include "src/gpu/GrColorSpaceXform.h"
+#include "src/gpu/GrContextPriv.h"
 #include "src/gpu/GrRecordingContextPriv.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/effects/GrYUVtoRGBEffect.h"
@@ -113,8 +114,9 @@
         domain = &constraintRect;
     }
 
-    auto fp = GrYUVtoRGBEffect::Make(fImage->fProxies, fImage->fYUVAIndices,
-                                     fImage->fYUVColorSpace, filter, textureMatrix, domain);
+    const auto& caps = *fImage->context()->priv().caps();
+    auto fp = GrYUVtoRGBEffect::Make(fImage->fProxies, fImage->fYUVAIndices, fImage->fYUVColorSpace,
+                                     filter, caps, textureMatrix, domain);
     if (fImage->fFromColorSpace) {
         fp = GrColorSpaceXformEffect::Make(std::move(fp), fImage->fFromColorSpace.get(),
                                            fImage->alphaType(), fImage->colorSpace());
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index fe6b12b..f3ae030 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -107,7 +107,6 @@
         kGrDistanceFieldA8TextGeoProc_ClassID,
         kGrDistanceFieldLCDTextGeoProc_ClassID,
         kGrDistanceFieldPathGeoProc_ClassID,
-        kGrDomainEffect_ClassID,
         kGrDualIntervalGradientColorizer_ClassID,
         kGrEllipseEffect_ClassID,
         kGrFillRRectOp_Processor_ClassID,
diff --git a/src/gpu/GrProcessorUnitTest.cpp b/src/gpu/GrProcessorUnitTest.cpp
index afd79ed..21f282b 100644
--- a/src/gpu/GrProcessorUnitTest.cpp
+++ b/src/gpu/GrProcessorUnitTest.cpp
@@ -81,7 +81,7 @@
  * we verify the count is as expected.  If a new factory is added, then these numbers must be
  * manually adjusted.
  */
-static const int kFPFactoryCount = 37;
+static const int kFPFactoryCount = 36;
 static const int kGPFactoryCount = 14;
 static const int kXPFactoryCount = 4;
 
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index bab4d1db..b98386b 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -45,7 +45,6 @@
 #include "src/gpu/SkGr.h"
 #include "src/gpu/effects/GrBicubicEffect.h"
 #include "src/gpu/effects/GrRRectEffect.h"
-#include "src/gpu/effects/GrTextureDomain.h"
 #include "src/gpu/effects/generated/GrColorMatrixFragmentProcessor.h"
 #include "src/gpu/geometry/GrQuad.h"
 #include "src/gpu/geometry/GrQuadUtils.h"
diff --git a/src/gpu/GrSamplerState.h b/src/gpu/GrSamplerState.h
index 848c84c..fc6b728 100644
--- a/src/gpu/GrSamplerState.h
+++ b/src/gpu/GrSamplerState.h
@@ -27,6 +27,9 @@
     constexpr GrSamplerState(WrapMode wrapXAndY, Filter filter)
             : fWrapModes{wrapXAndY, wrapXAndY}, fFilter(filter) {}
 
+    constexpr GrSamplerState(WrapMode wrapX, WrapMode wrapY, Filter filter)
+            : fWrapModes{wrapX, wrapY}, fFilter(filter) {}
+
     constexpr GrSamplerState(const WrapMode wrapModes[2], Filter filter)
             : fWrapModes{wrapModes[0], wrapModes[1]}, fFilter(filter) {}
 
diff --git a/src/gpu/GrTextureProducer.cpp b/src/gpu/GrTextureProducer.cpp
index 46c6134..243582d 100644
--- a/src/gpu/GrTextureProducer.cpp
+++ b/src/gpu/GrTextureProducer.cpp
@@ -33,15 +33,12 @@
 
     SkRect localRect = inputProxy->getBoundsRect();
 
-    bool needsDomain = false;
     bool resizing = false;
     if (copyParams.fFilter != GrSamplerState::Filter::kNearest) {
-        bool resizing = localRect.width()  != dstRect.width() ||
-                        localRect.height() != dstRect.height();
-        needsDomain = resizing && inputProxy->isFunctionallyExact();
+        resizing = localRect.width() != dstRect.width() || localRect.height() != dstRect.height();
     }
 
-    if (copyParams.fFilter == GrSamplerState::Filter::kNearest && !needsDomain && !resizing &&
+    if (copyParams.fFilter == GrSamplerState::Filter::kNearest && !resizing &&
         dstWillRequireMipMaps) {
         sk_sp<GrTextureProxy> proxy = GrCopyBaseMipMapToTextureProxy(context, inputProxy.get(),
                                                                      colorType);
@@ -57,18 +54,13 @@
         return nullptr;
     }
 
+    const auto& caps = *context->priv().caps();
     GrPaint paint;
 
-    auto fp = GrTextureEffect::Make(std::move(inputProxy), kUnknown_SkAlphaType, SkMatrix::I(),
-                                    copyParams.fFilter);
-    if (needsDomain) {
-        const SkRect domain = localRect.makeInset(0.5f, 0.5f);
-        // This would cause us to read values from outside the subset. Surely, the caller knows
-        // better!
-        SkASSERT(copyParams.fFilter != GrSamplerState::Filter::kMipMap);
-        fp = GrDomainEffect::Make(std::move(fp), domain, GrTextureDomain::kClamp_Mode,
-                                  copyParams.fFilter);
-    }
+    GrSamplerState sampler(GrSamplerState::WrapMode::kClamp, copyParams.fFilter);
+    auto boundsRect = SkIRect::MakeSize(inputProxy->dimensions());
+    auto fp = GrTextureEffect::MakeTexelSubset(std::move(inputProxy), kUnknown_SkAlphaType,
+                                               SkMatrix::I(), sampler, boundsRect, localRect, caps);
     paint.addColorFragmentProcessor(std::move(fp));
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
 
@@ -199,21 +191,19 @@
         const SkRect& domain,
         const GrSamplerState::Filter* filterOrNullForBicubic) {
     SkASSERT(kTightCopy_DomainMode != domainMode);
-    bool clampToBorderSupport = fContext->priv().caps()->clampToBorderSupport();
+    const auto& caps = *fContext->priv().caps();
     SkAlphaType srcAlphaType = this->alphaType();
     if (filterOrNullForBicubic) {
-        GrSamplerState::WrapMode wrapMode = fDomainNeedsDecal && clampToBorderSupport
+        GrSamplerState::WrapMode wrapMode = fDomainNeedsDecal
                                                     ? GrSamplerState::WrapMode::kClampToBorder
                                                     : GrSamplerState::WrapMode::kClamp;
         GrSamplerState samplerState(wrapMode, *filterOrNullForBicubic);
-        auto fp =
-                GrTextureEffect::Make(std::move(proxy), srcAlphaType, textureMatrix, samplerState);
-        if (kDomain_DomainMode == domainMode || (fDomainNeedsDecal && !clampToBorderSupport)) {
-            GrTextureDomain::Mode wrapMode = fDomainNeedsDecal ? GrTextureDomain::kDecal_Mode
-                                                               : GrTextureDomain::kClamp_Mode;
-            return GrDomainEffect::Make(std::move(fp), domain, wrapMode, *filterOrNullForBicubic);
+        if (kNoDomain_DomainMode == domainMode) {
+            return GrTextureEffect::Make(std::move(proxy), srcAlphaType, textureMatrix,
+                                         samplerState, caps);
         }
-        return fp;
+        return GrTextureEffect::MakeSubset(std::move(proxy), srcAlphaType, textureMatrix,
+                                           samplerState, domain, caps);
     } else {
         static const GrSamplerState::WrapMode kClampClamp[] = {
                 GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp};
@@ -221,6 +211,7 @@
                 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;
diff --git a/src/gpu/GrYUVProvider.cpp b/src/gpu/GrYUVProvider.cpp
index d974f2e..178554a 100644
--- a/src/gpu/GrYUVProvider.cpp
+++ b/src/gpu/GrYUVProvider.cpp
@@ -168,8 +168,9 @@
     }
 
     GrPaint paint;
+    const auto& caps = *ctx->priv().caps();
     auto yuvToRgbProcessor = GrYUVtoRGBEffect::Make(yuvTextureProxies, yuvaIndices, yuvColorSpace,
-                                                    GrSamplerState::Filter::kNearest);
+                                                    GrSamplerState::Filter::kNearest, caps);
     paint.addColorFragmentProcessor(std::move(yuvToRgbProcessor));
 
     // If the caller expects the pixels in a different color space than the one from the image,
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 6fa5173..ba29ad7 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -43,7 +43,6 @@
 #include "src/gpu/GrTracing.h"
 #include "src/gpu/SkGr.h"
 #include "src/gpu/effects/GrBicubicEffect.h"
-#include "src/gpu/effects/GrTextureDomain.h"
 #include "src/gpu/geometry/GrShape.h"
 #include "src/gpu/text/GrTextTarget.h"
 #include "src/image/SkImage_Base.h"
@@ -936,6 +935,7 @@
     // the rest from the SkPaint.
     std::unique_ptr<GrFragmentProcessor> fp;
 
+    const auto& caps = *this->caps();
     if (needsTextureDomain && (SkCanvas::kStrict_SrcRectConstraint == constraint)) {
         // Use a constrained texture domain to avoid color bleeding
         SkRect domain;
@@ -955,9 +955,8 @@
             static constexpr auto kDir = GrBicubicEffect::Direction::kXY;
             fp = GrBicubicEffect::Make(std::move(proxy), texMatrix, domain, kDir, srcAlphaType);
         } else {
-            fp = GrTextureEffect::Make(std::move(proxy), srcAlphaType, texMatrix, samplerState);
-            fp = GrDomainEffect::Make(std::move(fp), domain, GrTextureDomain::kClamp_Mode,
-                                      samplerState.filter());
+            fp = GrTextureEffect::MakeSubset(std::move(proxy), srcAlphaType, texMatrix,
+                                             samplerState, domain, caps);
         }
     } else if (bicubic) {
         SkASSERT(GrSamplerState::Filter::kNearest == samplerState.filter());
@@ -965,7 +964,7 @@
         static constexpr auto kDir = GrBicubicEffect::Direction::kXY;
         fp = GrBicubicEffect::Make(std::move(proxy), texMatrix, wrapMode, kDir, srcAlphaType);
     } else {
-        fp = GrTextureEffect::Make(std::move(proxy), srcAlphaType, texMatrix, samplerState);
+        fp = GrTextureEffect::Make(std::move(proxy), srcAlphaType, texMatrix, samplerState, caps);
     }
 
     fp = GrColorSpaceXformEffect::Make(std::move(fp), bitmap.colorSpace(), bitmap.alphaType(),
diff --git a/src/gpu/effects/GrTextureDomain.cpp b/src/gpu/effects/GrTextureDomain.cpp
index ad629c5..cd15573 100644
--- a/src/gpu/effects/GrTextureDomain.cpp
+++ b/src/gpu/effects/GrTextureDomain.cpp
@@ -319,191 +319,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-std::unique_ptr<GrFragmentProcessor> GrDomainEffect::Make(std::unique_ptr<GrFragmentProcessor> fp,
-                                                          const SkRect& domain,
-                                                          GrTextureDomain::Mode mode,
-                                                          bool decalIsFiltered) {
-    return Make(std::move(fp), domain, mode, mode, decalIsFiltered);
-}
-
-std::unique_ptr<GrFragmentProcessor> GrDomainEffect::Make(std::unique_ptr<GrFragmentProcessor> fp,
-                                                          const SkRect& domain,
-                                                          GrTextureDomain::Mode modeX,
-                                                          GrTextureDomain::Mode modeY,
-                                                          bool decalIsFiltered) {
-    if (modeX == GrTextureDomain::kIgnore_Mode && modeY == GrTextureDomain::kIgnore_Mode) {
-        return fp;
-    }
-    int count = 0;
-    GrCoordTransform* coordTransform = nullptr;
-    for (auto [transform, ignored] : GrFragmentProcessor::FPCoordTransformRange(*fp)) {
-        ++count;
-        coordTransform = &transform;
-    }
-    // If there are no coord transforms on the passed FP or it's children then there's no need to
-    // enforce a domain.
-    // We have a limitation that only one coord transform is support when overriding local coords.
-    // If that limit were relaxed we would need to add a coord transform for each descendent FP
-    // transform and possibly have multiple domain rects to account for different proxy
-    // normalization and y-reversals.
-    if (count != 1) {
-        return fp;
-    }
-    GrCoordTransform transformCopy = *coordTransform;
-    // Reset the child FP's coord transform.
-    *coordTransform = {};
-    // If both domain modes happen to be ignore, it would be faster to just drop the domain logic
-    // entirely and return the original FP. We'd need a GrMatrixProcessor if the matrix is not
-    // identity, though.
-    return std::unique_ptr<GrFragmentProcessor>(new GrDomainEffect(
-            std::move(fp), transformCopy, domain, modeX, modeY, decalIsFiltered));
-}
-
-std::unique_ptr<GrFragmentProcessor> GrDomainEffect::Make(std::unique_ptr<GrFragmentProcessor> fp,
-                                                          const SkRect& domain,
-                                                          GrTextureDomain::Mode mode,
-                                                          GrSamplerState::Filter filter) {
-    bool filterIfDecal = filter != GrSamplerState::Filter::kNearest;
-    return Make(std::move(fp), domain, mode, filterIfDecal);
-}
-
-std::unique_ptr<GrFragmentProcessor> GrDomainEffect::Make(std::unique_ptr<GrFragmentProcessor> fp,
-                                                          const SkRect& domain,
-                                                          GrTextureDomain::Mode modeX,
-                                                          GrTextureDomain::Mode modeY,
-                                                          GrSamplerState::Filter filter) {
-    bool filterIfDecal = filter != GrSamplerState::Filter::kNearest;
-    return Make(std::move(fp), domain, modeX, modeY, filterIfDecal);
-}
-GrFragmentProcessor::OptimizationFlags GrDomainEffect::Flags(GrFragmentProcessor* fp,
-                                                             GrTextureDomain::Mode modeX,
-                                                             GrTextureDomain::Mode modeY) {
-    auto fpFlags = GrFragmentProcessor::ProcessorOptimizationFlags(fp);
-    if (modeX == GrTextureDomain::kDecal_Mode || modeY == GrTextureDomain::kDecal_Mode) {
-        return fpFlags & ~kPreservesOpaqueInput_OptimizationFlag;
-    }
-    return fpFlags;
-}
-
-GrDomainEffect::GrDomainEffect(std::unique_ptr<GrFragmentProcessor> fp,
-                               const GrCoordTransform& coordTransform,
-                               const SkRect& domain,
-                               GrTextureDomain::Mode modeX,
-                               GrTextureDomain::Mode modeY,
-                               bool decalIsFiltered)
-        : INHERITED(kGrDomainEffect_ClassID, Flags(fp.get(), modeX, modeY))
-        , fCoordTransform(coordTransform)
-        , fDomain(domain, modeX, modeY)
-        , fDecalIsFiltered(decalIsFiltered) {
-    SkASSERT(fp);
-    fp->setSampledWithExplicitCoords(true);
-    this->registerChildProcessor(std::move(fp));
-    this->addCoordTransform(&fCoordTransform);
-    if (fDomain.modeX() != GrTextureDomain::kDecal_Mode &&
-        fDomain.modeY() != GrTextureDomain::kDecal_Mode) {
-        // Canonicalize this don't care value so we don't have to worry about it elsewhere.
-        fDecalIsFiltered = false;
-    }
-}
-
-GrDomainEffect::GrDomainEffect(const GrDomainEffect& that)
-        : INHERITED(kGrDomainEffect_ClassID, that.optimizationFlags())
-        , fCoordTransform(that.fCoordTransform)
-        , fDomain(that.fDomain)
-        , fDecalIsFiltered(that.fDecalIsFiltered) {
-    auto child = that.childProcessor(0).clone();
-    child->setSampledWithExplicitCoords(true);
-    this->registerChildProcessor(std::move(child));
-    this->addCoordTransform(&fCoordTransform);
-}
-
-void GrDomainEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                           GrProcessorKeyBuilder* b) const {
-    b->add32(GrTextureDomain::GLDomain::DomainKey(fDomain));
-}
-
-GrGLSLFragmentProcessor* GrDomainEffect::onCreateGLSLInstance() const {
-    class GLSLProcessor : public GrGLSLFragmentProcessor {
-    public:
-        void emitCode(EmitArgs& args) override {
-            const GrDomainEffect& de = args.fFp.cast<GrDomainEffect>();
-            const GrTextureDomain& domain = de.fDomain;
-
-            SkString coords2D =
-                    args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
-
-            fGLDomain.sampleProcessor(domain, args.fInputColor, args.fOutputColor, coords2D, this,
-                                      args, 0);
-        }
-
-    protected:
-        void onSetData(const GrGLSLProgramDataManager& pdman,
-                       const GrFragmentProcessor& fp) override {
-            const GrDomainEffect& de = fp.cast<GrDomainEffect>();
-            const GrTextureDomain& domain = de.fDomain;
-            // TODO: Update GrCoordTransform to return a view instead of proxy
-            const GrSurfaceProxy* proxy = de.fCoordTransform.proxy();
-            // If we don't have a proxy the value of the origin doesn't matter
-            GrSurfaceOrigin origin = proxy ? proxy->origin() : kTopLeft_GrSurfaceOrigin;
-            fGLDomain.setData(pdman, domain, proxy, origin, de.fDecalIsFiltered);
-        }
-
-    private:
-        GrTextureDomain::GLDomain         fGLDomain;
-    };
-
-    return new GLSLProcessor;
-}
-
-bool GrDomainEffect::onIsEqual(const GrFragmentProcessor& sBase) const {
-    auto& td = sBase.cast<GrDomainEffect>();
-    return fDomain == td.fDomain && fDecalIsFiltered == td.fDecalIsFiltered;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrDomainEffect);
-
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrDomainEffect::TestCreate(GrProcessorTestData* d) {
-    do {
-        GrTextureDomain::Mode modeX =
-                (GrTextureDomain::Mode)d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
-        GrTextureDomain::Mode modeY =
-                (GrTextureDomain::Mode)d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
-        auto child = GrProcessorUnitTest::MakeChildFP(d);
-        const auto* childPtr = child.get();
-        SkRect domain;
-        // We assert if the child's coord transform has a proxy and the domain rect is outside its
-        // bounds.
-        GrFragmentProcessor::CoordTransformIter ctIter(*child);
-        if (!ctIter) {
-            continue;
-        }
-        auto [transform, fp] = *ctIter;
-        if (auto proxy = transform.proxy()) {
-            auto [w, h] = proxy->backingStoreDimensions();
-            domain.fLeft   = d->fRandom->nextRangeScalar(0, w);
-            domain.fRight  = d->fRandom->nextRangeScalar(0, w);
-            domain.fTop    = d->fRandom->nextRangeScalar(0, h);
-            domain.fBottom = d->fRandom->nextRangeScalar(0, h);
-        } else {
-            domain.fLeft   = d->fRandom->nextRangeScalar(-100.f, 100.f);
-            domain.fRight  = d->fRandom->nextRangeScalar(-100.f, 100.f);
-            domain.fTop    = d->fRandom->nextRangeScalar(-100.f, 100.f);
-            domain.fBottom = d->fRandom->nextRangeScalar(-100.f, 100.f);
-        }
-        domain.sort();
-        bool filterIfDecal = d->fRandom->nextBool();
-        auto result = GrDomainEffect::Make(std::move(child), domain, modeX, modeY, filterIfDecal);
-        if (result && result.get() != childPtr) {
-            return result;
-        }
-    } while (true);
-}
-#endif
-
-///////////////////////////////////////////////////////////////////////////////
 std::unique_ptr<GrFragmentProcessor> GrDeviceSpaceTextureDecalFragmentProcessor::Make(
         sk_sp<GrSurfaceProxy> proxy, const SkIRect& subset, const SkIPoint& deviceSpaceOffset) {
     return std::unique_ptr<GrFragmentProcessor>(new GrDeviceSpaceTextureDecalFragmentProcessor(
diff --git a/src/gpu/effects/GrTextureDomain.h b/src/gpu/effects/GrTextureDomain.h
index 8e2a6e4..70e6766 100644
--- a/src/gpu/effects/GrTextureDomain.h
+++ b/src/gpu/effects/GrTextureDomain.h
@@ -231,85 +231,6 @@
     int     fIndex;
 };
 
-/**
- * This effect applies a domain rectangle with an edge "mode" to the result of the child FP's coord
- * transform. Currently the passed FP (including its descendants) must have exactly 1 coord
- * transform (due to internal program builder restrictions). Also, it's important to note that the
- * domain rectangle is applied  AFTER the corod transform. This allows us to continue to lift the
- * coord transform to the vertex shader. It might make this nicer for some use cases to add a
- * pre-coord transform option and try to adjust the domain rect internally to convert to
- * post-coord transform and keep everything in the vertex shader for simple use cases.
- */
-class GrDomainEffect : public GrFragmentProcessor {
-public:
-    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor>,
-                                                     const SkRect& domain,
-                                                     GrTextureDomain::Mode,
-                                                     bool decalIsFiltered);
-
-    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor>,
-                                                     const SkRect& domain,
-                                                     GrTextureDomain::Mode modeX,
-                                                     GrTextureDomain::Mode modeY,
-                                                     bool decalIsFiltered);
-
-    // These variants infer decalIsFiltered from the Filter mode (true if not kNearest).
-    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor>,
-                                                     const SkRect& domain,
-                                                     GrTextureDomain::Mode,
-                                                     GrSamplerState::Filter);
-
-    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor>,
-                                                     const SkRect& domain,
-                                                     GrTextureDomain::Mode modeX,
-                                                     GrTextureDomain::Mode modeY,
-                                                     GrSamplerState::Filter);
-
-    const char* name() const override { return "Domain"; }
-
-    std::unique_ptr<GrFragmentProcessor> clone() const override {
-        return std::unique_ptr<GrFragmentProcessor>(new GrDomainEffect(*this));
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override {
-        SkString str;
-        str.appendf("Domain: [L: %.2f, T: %.2f, R: %.2f, B: %.2f], filterDecal: %d",
-                    fDomain.domain().fLeft, fDomain.domain().fTop, fDomain.domain().fRight,
-                    fDomain.domain().fBottom, fDecalIsFiltered);
-        str.append(INHERITED::dumpInfo());
-        return str;
-    }
-#endif
-
-private:
-    GrFragmentProcessor::OptimizationFlags Flags(GrFragmentProcessor*, GrTextureDomain::Mode,
-                                                 GrTextureDomain::Mode);
-
-    GrCoordTransform fCoordTransform;
-    GrTextureDomain fDomain;
-    bool fDecalIsFiltered;
-
-    GrDomainEffect(std::unique_ptr<GrFragmentProcessor>,
-                   const GrCoordTransform& transform,
-                   const SkRect& domain,
-                   GrTextureDomain::Mode modeX,
-                   GrTextureDomain::Mode modeY,
-                   bool decalIsFiltered);
-
-    explicit GrDomainEffect(const GrDomainEffect&);
-
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
-
-    void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
-
-    bool onIsEqual(const GrFragmentProcessor&) const override;
-
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-
-    typedef GrFragmentProcessor INHERITED;
-};
-
 class GrDeviceSpaceTextureDecalFragmentProcessor : public GrFragmentProcessor {
 public:
     static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrSurfaceProxy>,
diff --git a/src/gpu/effects/GrTextureEffect.cpp b/src/gpu/effects/GrTextureEffect.cpp
index e964a77..c8a52fb 100644
--- a/src/gpu/effects/GrTextureEffect.cpp
+++ b/src/gpu/effects/GrTextureEffect.cpp
@@ -8,24 +8,233 @@
 #include "src/gpu/effects/GrTextureEffect.h"
 
 #include "include/gpu/GrTexture.h"
+#include "src/gpu/GrTexturePriv.h"
 #include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
 #include "src/gpu/glsl/GrGLSLProgramBuilder.h"
 #include "src/sksl/SkSLCPP.h"
 #include "src/sksl/SkSLUtil.h"
 
+namespace {
+struct Span {
+    float fA = 0.f, fB = 0.f;
+
+    Span makeInset(float o) const {
+        Span r = {fA + o, fB - o};
+        if (r.fA > r.fB) {
+            r.fA = r.fB = (r.fA + r.fB) / 2;
+        }
+        return r;
+    }
+
+    bool contains(Span r) const { return fA <= r.fA && fB >= r.fB; }
+};
+}  // anonymous namespace
+
+GrTextureEffect::Sampling::Sampling(GrSamplerState sampler, SkISize size, const GrCaps& caps)
+        : fHWSampler(sampler) {
+    if (!caps.clampToBorderSupport()) {
+        if (fHWSampler.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder) {
+            fHWSampler.setWrapModeX(GrSamplerState::WrapMode::kClamp);
+            fShaderModes[0] = ShaderMode::kDecal;
+            Span span{0, (float)size.width()};
+            if (sampler.filter() != GrSamplerState::Filter::kNearest) {
+                span = span.makeInset(0.5f);
+            }
+            fShaderSubset.fLeft  = span.fA;
+            fShaderSubset.fRight = span.fB;
+        }
+        if (fHWSampler.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder) {
+            fHWSampler.setWrapModeY(GrSamplerState::WrapMode::kClamp);
+            fShaderModes[1] = ShaderMode::kDecal;
+            Span span{0, (float)size.height()};
+            if (sampler.filter() != GrSamplerState::Filter::kNearest) {
+                span = span.makeInset(0.5f);
+            }
+            fShaderSubset.fTop    = span.fA;
+            fShaderSubset.fBottom = span.fB;
+        }
+    }
+}
+
+GrTextureEffect::Sampling::Sampling(const GrSurfaceProxy& proxy,
+                                    GrSamplerState sampler,
+                                    const SkRect& subset,
+                                    bool adjustForFilter,
+                                    const SkRect* domain,
+                                    const GrCaps& caps) {
+    using Mode = GrSamplerState::WrapMode;
+    using Filter = GrSamplerState::Filter;
+
+    struct Result1D {
+        ShaderMode fShaderMode;
+        Span fShaderSubset;
+        Mode fHWMode;
+        Filter fFilter;
+    };
+
+    auto resolve = [adjustForFilter, filter = sampler.filter(), &caps](int size, Mode mode,
+                                                                       Span subset, Span domain) {
+        float inset;
+        Result1D r;
+        r.fFilter = filter;
+        bool canDoHW = mode != Mode::kClampToBorder || caps.clampToBorderSupport();
+        if (canDoHW && size > 0 && subset.fA <= 0 && subset.fB >= size) {
+            r.fShaderMode = ShaderMode::kNone;
+            r.fHWMode = mode;
+            return r;
+        }
+
+        inset = (adjustForFilter && filter != Filter::kNearest) ? 0.5 : 0;
+        auto insetSubset = subset.makeInset(inset);
+
+        if (canDoHW && insetSubset.contains(domain)) {
+            r.fShaderMode = ShaderMode::kNone;
+            r.fHWMode = mode;
+            return r;
+        }
+
+        if (mode == Mode::kRepeat || mode == Mode::kMirrorRepeat) {
+            r.fFilter = Filter::kNearest;
+            r.fShaderSubset = subset;
+        } else {
+            r.fShaderSubset = insetSubset;
+        }
+        r.fShaderMode = static_cast<ShaderMode>(mode);
+        r.fHWMode = Mode::kClamp;
+        return r;
+    };
+
+    SkISize dim = proxy.isFullyLazy() ? SkISize{-1, -1} : proxy.backingStoreDimensions();
+
+    Span subsetX{subset.fLeft, subset.fRight};
+    auto domainX = domain ? Span{domain->fLeft, domain->fRight}
+                          : Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
+    auto x = resolve(dim.width(), sampler.wrapModeX(), subsetX, domainX);
+
+    Span subsetY{subset.fTop, subset.fBottom};
+    auto domainY = domain ? Span{domain->fTop, domain->fBottom}
+                          : Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
+    auto y = resolve(dim.height(), sampler.wrapModeY(), subsetY, domainY);
+
+    fHWSampler = {x.fHWMode, y.fHWMode, std::min(x.fFilter, y.fFilter)};
+    fShaderModes[0] = x.fShaderMode;
+    fShaderModes[1] = y.fShaderMode;
+    fShaderSubset = {x.fShaderSubset.fA, y.fShaderSubset.fA,
+                     x.fShaderSubset.fB, y.fShaderSubset.fB};
+}
+
+bool GrTextureEffect::Sampling::usesDecal() const {
+    return fShaderModes[0] == ShaderMode::kDecal || fShaderModes[1] == ShaderMode::kDecal ||
+           fHWSampler.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder ||
+           fHWSampler.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder;
+}
+
 std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(sk_sp<GrSurfaceProxy> proxy,
                                                            SkAlphaType alphaType,
                                                            const SkMatrix& matrix,
-                                                           GrSamplerState sampler) {
+                                                           GrSamplerState::Filter filter) {
     return std::unique_ptr<GrFragmentProcessor>(
-            new GrTextureEffect(std::move(proxy), alphaType, matrix, sampler));
+            new GrTextureEffect(std::move(proxy), alphaType, matrix, Sampling(filter)));
+}
+
+std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(sk_sp<GrSurfaceProxy> proxy,
+                                                           SkAlphaType alphaType,
+                                                           const SkMatrix& matrix,
+                                                           GrSamplerState sampler,
+                                                           const GrCaps& caps) {
+    Sampling sampling(sampler, proxy->dimensions(), caps);
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrTextureEffect(std::move(proxy), alphaType, matrix, sampling));
+}
+
+std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeTexelSubset(sk_sp<GrSurfaceProxy> proxy,
+                                                                      SkAlphaType alphaType,
+                                                                      const SkMatrix& matrix,
+                                                                      GrSamplerState sampler,
+                                                                      const SkIRect& subset,
+                                                                      const GrCaps& caps) {
+    Sampling sampling(*proxy, sampler, SkRect::Make(subset), true, nullptr, caps);
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrTextureEffect(std::move(proxy), alphaType, matrix, sampling));
+}
+
+std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeTexelSubset(sk_sp<GrSurfaceProxy> proxy,
+                                                                      SkAlphaType alphaType,
+                                                                      const SkMatrix& matrix,
+                                                                      GrSamplerState sampler,
+                                                                      const SkIRect& subset,
+                                                                      const SkRect& domain,
+                                                                      const GrCaps& caps) {
+    Sampling sampling(*proxy, sampler, SkRect::Make(subset), true, &domain, caps);
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrTextureEffect(std::move(proxy), alphaType, matrix, sampling));
+}
+
+std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(sk_sp<GrSurfaceProxy> proxy,
+                                                                 SkAlphaType alphaType,
+                                                                 const SkMatrix& matrix,
+                                                                 GrSamplerState sampler,
+                                                                 const SkRect& subset,
+                                                                 const GrCaps& caps) {
+    Sampling sampling(*proxy, sampler, subset, false, nullptr, caps);
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrTextureEffect(std::move(proxy), alphaType, matrix, sampling));
+}
+
+std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(sk_sp<GrSurfaceProxy> proxy,
+                                                                 SkAlphaType alphaType,
+                                                                 const SkMatrix& matrix,
+                                                                 GrSamplerState sampler,
+                                                                 const SkRect& subset,
+                                                                 const SkRect& domain,
+                                                                 const GrCaps& caps) {
+    Sampling sampling(*proxy, sampler, subset, false, &domain, caps);
+    return std::unique_ptr<GrFragmentProcessor>(
+            new GrTextureEffect(std::move(proxy), alphaType, matrix, sampling));
 }
 
 GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
     class Impl : public GrGLSLFragmentProcessor {
+        UniformHandle fSubsetUni;
+        UniformHandle fDecalUni;
+
     public:
         void emitCode(EmitArgs& args) override {
+            auto appendWrap = [](GrGLSLShaderBuilder* builder, ShaderMode mode, const char* inCoord,
+                                 const char* domainStart, const char* domainEnd, bool is2D,
+                                 const char* out) {
+                switch (mode) {
+                    case ShaderMode::kNone:
+                        builder->codeAppendf("%s = %s;\n", out, inCoord);
+                        break;
+                    case ShaderMode::kDecal:
+                        // The lookup coordinate to use for decal will be clamped just like
+                        // kClamp_Mode, it's just that the post-processing will be different, so
+                        // fall through
+                    case ShaderMode::kClamp:
+                        builder->codeAppendf("%s = clamp(%s, %s, %s);", out, inCoord, domainStart,
+                                             domainEnd);
+                        break;
+                    case ShaderMode::kRepeat:
+                        builder->codeAppendf("%s = mod(%s - %s, %s - %s) + %s;", out, inCoord,
+                                             domainStart, domainEnd, domainStart, domainStart);
+                        break;
+                    case ShaderMode::kMirrorRepeat: {
+                        const char* type = is2D ? "float2" : "float";
+                        builder->codeAppend("{");
+                        builder->codeAppendf("%s w = %s - %s;", type, domainEnd, domainStart);
+                        builder->codeAppendf("%s w2 = 2 * w;", type);
+                        builder->codeAppendf("%s m = mod(%s - %s, w2);", type, inCoord,
+                                             domainStart);
+                        builder->codeAppendf("%s = mix(m, w2 - m, step(w, m)) + %s;", out,
+                                             domainStart);
+                        builder->codeAppend("}");
+                        break;
+                    }
+                }
+            };
+            auto te = args.fFp.cast<GrTextureEffect>();
             const char* coords;
             if (args.fFp.coordTransformsApplyToLocalCoords()) {
                 coords = args.fTransformedCoords[0].fVaryingPoint.c_str();
@@ -33,30 +242,165 @@
                 coords = "_coords";
             }
             auto* fb = args.fFragBuilder;
-            fb->codeAppendf("%s = ", args.fOutputColor);
-            fb->appendTextureLookupAndBlend(args.fInputColor, SkBlendMode::kModulate,
-                                            args.fTexSamplers[0], coords);
-            fb->codeAppendf(";");
+            if (te.fShaderModes[0] == ShaderMode::kNone &&
+                te.fShaderModes[1] == ShaderMode::kNone) {
+                fb->codeAppendf("%s = ", args.fOutputColor);
+                fb->appendTextureLookupAndBlend(args.fInputColor, SkBlendMode::kModulate,
+                                                args.fTexSamplers[0], coords);
+                fb->codeAppendf(";");
+            } else {
+                const char* subsetName;
+                SkString uniName("TexDom");
+                fSubsetUni = args.fUniformHandler->addUniform(
+                        kFragment_GrShaderFlag, kHalf4_GrSLType, "subset", &subsetName);
+
+                // Always use a local variable for the input coordinates; often callers pass in an
+                // expression and we want to cache it across all of its references in the code below
+                auto inCoords = fb->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
+                fb->codeAppend("float2 clampedCoord;");
+                SkString start;
+                SkString end;
+                if (te.fShaderModes[0] == te.fShaderModes[1]) {
+                    // Doing the domain setup using vectors seems to avoid shader compilation issues
+                    // on Chromecast, possibly due to reducing shader length.
+                    start.printf("%s.xy", subsetName);
+                    end.printf("%s.zw", subsetName);
+                    appendWrap(fb, te.fShaderModes[0], inCoords.c_str(), start.c_str(), end.c_str(),
+                               true, "clampedCoord");
+                } else {
+                    SkString origX, origY;
+                    // Apply x mode to the x coordinate using the left and right edges of the domain
+                    // rect (stored as the x and z components of the domain uniform).
+                    start.printf("%s.x", subsetName);
+                    end.printf("%s.z", subsetName);
+                    origX.printf("%s.x", inCoords.c_str());
+                    appendWrap(fb, te.fShaderModes[0], origX.c_str(), start.c_str(), end.c_str(),
+                               false, "clampedCoord.x");
+                    // Repeat the same logic for y.
+                    start.printf("%s.y", subsetName);
+                    end.printf("%s.w", subsetName);
+                    origY.printf("%s.y", inCoords.c_str());
+                    appendWrap(fb, te.fShaderModes[1], origY.c_str(), start.c_str(), end.c_str(),
+                               false, "clampedCoord.y");
+                }
+                SkString textureLookup;
+                fb->appendTextureLookup(&textureLookup, args.fTexSamplers[0], "clampedCoord");
+                fb->codeAppendf("half4 textureColor = %s;", textureLookup.c_str());
+
+                // Apply decal mode's transparency interpolation if needed
+                bool decalX = te.fShaderModes[0] == ShaderMode::kDecal;
+                bool decalY = te.fShaderModes[1] == ShaderMode::kDecal;
+                if (decalX || decalY) {
+                    const char* decalName;
+                    // Half3 since this will hold texture width, height, and then a step function
+                    // control param
+                    fDecalUni = args.fUniformHandler->addUniform(
+                            kFragment_GrShaderFlag, kHalf3_GrSLType, uniName.c_str(), &decalName);
+                    // The decal err is the max absolute value between the clamped coordinate and
+                    // the original pixel coordinate. This will then be clamped to 1.f if it's
+                    // greater than the control parameter, which simulates kNearest and kBilerp
+                    // behavior depending on if it's 0 or 1.
+                    if (decalX && decalY) {
+                        fb->codeAppendf(
+                                "half err = max(half(abs(clampedCoord.x - %s.x) * %s.x), "
+                                "               half(abs(clampedCoord.y - %s.y) * %s.y));",
+                                inCoords.c_str(), decalName, inCoords.c_str(), decalName);
+                    } else if (decalX) {
+                        fb->codeAppendf("half err = half(abs(clampedCoord.x - %s.x) * %s.x);",
+                                        inCoords.c_str(), decalName);
+                    } else {
+                        SkASSERT(decalY);
+                        fb->codeAppendf("half err = half(abs(clampedCoord.y - %s.y) * %s.y);",
+                                        inCoords.c_str(), decalName);
+                    }
+
+                    // Apply a transform to the error rate, which let's us simulate nearest or
+                    // bilerp filtering in the same shader. When the texture is nearest filtered,
+                    // fSizeName.z is set to 0 so this becomes a step function centered at the
+                    // clamped coordinate. When bilerp, fSizeName.z is set to 1 and it becomes
+                    // a simple linear blend between texture and transparent.
+                    fb->codeAppendf(
+                            "if (err > %s.z) { err = 1.0; } else if (%s.z < 1) { err = 0.0; }",
+                            decalName, decalName);
+                    fb->codeAppend("textureColor = mix(textureColor, half4(0), err);");
+                }
+                fb->codeAppendf("%s = textureColor * %s;", args.fOutputColor, args.fInputColor);
+            }
+        }
+
+    protected:
+        void onSetData(const GrGLSLProgramDataManager& pdm,
+                       const GrFragmentProcessor& fp) override {
+            const auto& te = fp.cast<GrTextureEffect>();
+            if (fSubsetUni.isValid()) {
+                const float w = te.fSampler.peekTexture()->width();
+                const float h = te.fSampler.peekTexture()->height();
+
+                const auto& s = te.fSubset;
+                float rect[] = {s.fLeft, s.fTop, s.fRight, s.fBottom};
+                float decalW[3];
+
+                if (te.fSampler.view().origin() == kBottomLeft_GrSurfaceOrigin) {
+                    rect[1] = h - rect[1];
+                    rect[3] = h - rect[3];
+                    std::swap(rect[1], rect[3]);
+                }
+
+                if (te.fSampler.peekTexture()->texturePriv().textureType() !=
+                    GrTextureType::kRectangle) {
+                    float iw = 1.f / w;
+                    float ih = 1.f / h;
+                    rect[0] *= iw;
+                    rect[2] *= iw;
+                    rect[1] *= ih;
+                    rect[3] *= ih;
+                    decalW[0] = w;
+                    decalW[1] = h;
+                } else {
+                    decalW[0] = 1;
+                    decalW[1] = 1;
+                }
+                pdm.set4fv(fSubsetUni, 1, rect);
+
+                if (fDecalUni.isValid()) {
+                    bool filter = te.textureSampler(0).samplerState().filter() !=
+                                  GrSamplerState::Filter::kNearest;
+                    decalW[2] = filter ? 1.f : 0.f;
+                    pdm.set3fv(fDecalUni, 1, decalW);
+                }
+            }
         }
     };
     return new Impl;
 }
 
-void GrTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const {}
+void GrTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const {
+    bool shaderFilter = (fShaderModes[0] == ShaderMode::kDecal ||
+                         fShaderModes[1] == ShaderMode::kDecal) &&
+                        fSampler.samplerState().filter() != GrSamplerState::Filter::kNearest;
+    auto m0 = static_cast<uint32_t>(fShaderModes[0]);
+    auto m1 = static_cast<uint32_t>(fShaderModes[1]);
+    b->add32(shaderFilter << 31 | (m0 << 16) | m1);
+}
 
-bool GrTextureEffect::onIsEqual(const GrFragmentProcessor&) const { return true; }
-
-static inline bool uses_border(const GrSamplerState s) {
-    return s.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder ||
-           s.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder;
+bool GrTextureEffect::onIsEqual(const GrFragmentProcessor& other) const {
+    auto that = other.cast<GrTextureEffect>();
+    return fShaderModes[0] == that.fShaderModes[1] && fShaderModes[1] == that.fShaderModes[1] &&
+           fSubset == that.fSubset;
 }
 
 GrTextureEffect::GrTextureEffect(sk_sp<GrSurfaceProxy> texture, SkAlphaType alphaType,
-                                 const SkMatrix& matrix, GrSamplerState sampler)
+                                 const SkMatrix& matrix, const Sampling& sampling)
         : GrFragmentProcessor(kGrTextureEffect_ClassID,
-                              ModulateForSamplerOptFlags(alphaType, uses_border(sampler)))
+                              ModulateForSamplerOptFlags(alphaType, sampling.usesDecal()))
         , fCoordTransform(matrix, texture.get())
-        , fSampler(std::move(texture), sampler) {
+        , fSampler(std::move(texture), sampling.fHWSampler)
+        , fSubset(sampling.fShaderSubset)
+        , fShaderModes{sampling.fShaderModes[0], sampling.fShaderModes[1]} {
+    // We always compare the range even when it isn't used so assert we have canonical don't care
+    // values.
+    SkASSERT(fShaderModes[0] != ShaderMode::kNone || (fSubset.fLeft == 0 && fSubset.fRight == 0));
+    SkASSERT(fShaderModes[1] != ShaderMode::kNone || (fSubset.fTop == 0 && fSubset.fBottom == 0));
     this->setTextureSamplerCnt(1);
     this->addCoordTransform(&fCoordTransform);
 }
@@ -64,7 +408,9 @@
 GrTextureEffect::GrTextureEffect(const GrTextureEffect& src)
         : INHERITED(kGrTextureEffect_ClassID, src.optimizationFlags())
         , fCoordTransform(src.fCoordTransform)
-        , fSampler(src.fSampler) {
+        , fSampler(src.fSampler)
+        , fSubset(src.fSubset)
+        , fShaderModes{src.fShaderModes[0], src.fShaderModes[1]} {
     this->setTextureSamplerCnt(1);
     this->addCoordTransform(&fCoordTransform);
 }
@@ -95,6 +441,6 @@
                                              : GrSamplerState::Filter::kNearest);
 
     const SkMatrix& matrix = GrTest::TestMatrix(testData->fRandom);
-    return GrTextureEffect::Make(std::move(proxy), at, matrix, params);
+    return GrTextureEffect::Make(std::move(proxy), at, matrix, params, *testData->caps());
 }
 #endif
diff --git a/src/gpu/effects/GrTextureEffect.h b/src/gpu/effects/GrTextureEffect.h
index c6fc305..9e62a48 100644
--- a/src/gpu/effects/GrTextureEffect.h
+++ b/src/gpu/effects/GrTextureEffect.h
@@ -15,23 +15,117 @@
 
 class GrTextureEffect : public GrFragmentProcessor {
 public:
+    /** Make from a filter. The sampler will be configured with clamp mode. */
+    static std::unique_ptr<GrFragmentProcessor> Make(
+            sk_sp<GrSurfaceProxy>,
+            SkAlphaType,
+            const SkMatrix& = SkMatrix::I(),
+            GrSamplerState::Filter = GrSamplerState::Filter::kNearest);
+
+    /**
+     * Make from a full GrSamplerState. Caps are required to determine support for kClampToBorder.
+     * This will be emulated in the shader if there is no hardware support.
+     */
     static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrSurfaceProxy>,
                                                      SkAlphaType,
-                                                     const SkMatrix& = SkMatrix::I(),
-                                                     GrSamplerState = {});
+                                                     const SkMatrix&,
+                                                     GrSamplerState,
+                                                     const GrCaps& caps);
+
+    /**
+     * Makes a texture effect that samples a subset of a texture. The wrap modes of the
+     * GrSampleState are applied to the subset in the shader rather than using HW samplers.
+     * The 'subset' parameter specifies the texels in the base level. The shader code will
+     * avoid allowing bilerp filtering to read outside the texel window. However, if MIP
+     * filtering is used and a shader invocation reads from a level other than the base
+     * then it may read texel values that were computed from in part from base level texels
+     * outside the window. More specifically, we treat the MIP map case exactly like the
+     * bilerp case in terms of how the final texture coords are computed.
+     */
+    static std::unique_ptr<GrFragmentProcessor> MakeTexelSubset(sk_sp<GrSurfaceProxy>,
+                                                                SkAlphaType,
+                                                                const SkMatrix&,
+                                                                GrSamplerState,
+                                                                const SkIRect& subset,
+                                                                const GrCaps& caps);
+    /**
+     * The same as above but also takes a 'domain' that specifies any known limit on the post-
+     * matrix texture coords that will be used to sample the texture. Specifying this requires
+     * knowledge of how this effect will be nested into a paint, the local coords used with the
+     * draw, etc. It is only used to attempt to optimize away the shader subset calculations.
+     */
+    static std::unique_ptr<GrFragmentProcessor> MakeTexelSubset(sk_sp<GrSurfaceProxy>,
+                                                                SkAlphaType,
+                                                                const SkMatrix&,
+                                                                GrSamplerState,
+                                                                const SkIRect& subset,
+                                                                const SkRect& domain,
+                                                                const GrCaps& caps);
+
+    /**
+     * This is similar to MakeTexelSubset but takes a float rather than integer subset rect.
+     * No adjustment is done for filtering, the texture coordinates are limited to the
+     * unmodified subset. The subset should be unnormalized. The effect will apply texture
+     * coordinate normalization after subset restriction (logically).
+     */
+    static std::unique_ptr<GrFragmentProcessor> MakeSubset(sk_sp<GrSurfaceProxy>,
+                                                           SkAlphaType,
+                                                           const SkMatrix&,
+                                                           GrSamplerState,
+                                                           const SkRect& subset,
+                                                           const GrCaps& caps);
+
+    /**
+     * The same as above but also takes a 'domain' that specifies any known limit on the post-
+     * matrix texture coords that will be used to sample the texture. Specifying this requires
+     * knowledge of how this effect will be nested into a paint, the local coords used with the
+     * draw, etc. It is only used to attempt to optimize away the shader subset calculations.
+     */
+    static std::unique_ptr<GrFragmentProcessor> MakeSubset(sk_sp<GrSurfaceProxy>,
+                                                           SkAlphaType,
+                                                           const SkMatrix&,
+                                                           GrSamplerState,
+                                                           const SkRect& subset,
+                                                           const SkRect& domain,
+                                                           const GrCaps& caps);
 
     std::unique_ptr<GrFragmentProcessor> clone() const override;
 
-    const char* name() const override { return "SimpleTextureEffect"; }
+    const char* name() const override { return "TextureEffect"; }
 
 private:
+    enum class ShaderMode : uint16_t {
+        kClamp         = static_cast<int>(GrSamplerState::WrapMode::kClamp),
+        kRepeat        = static_cast<int>(GrSamplerState::WrapMode::kRepeat),
+        kMirrorRepeat  = static_cast<int>(GrSamplerState::WrapMode::kMirrorRepeat),
+        kDecal         = static_cast<int>(GrSamplerState::WrapMode::kClampToBorder),
+        kNone,
+    };
+
+    struct Sampling {
+        GrSamplerState fHWSampler;
+        ShaderMode fShaderModes[2] = {ShaderMode::kNone, ShaderMode::kNone};
+        SkRect fShaderSubset = {0, 0, 0, 0};
+        Sampling(GrSamplerState::Filter filter) : fHWSampler(filter) {}
+        Sampling(GrSamplerState, SkISize, const GrCaps&);
+        Sampling(const GrSurfaceProxy& proxy,
+                 GrSamplerState sampler,
+                 const SkRect&,
+                 bool,
+                 const SkRect*,
+                 const GrCaps&);
+        inline bool usesDecal() const;
+    };
+
     GrCoordTransform fCoordTransform;
     TextureSampler fSampler;
+    SkRect fSubset;
+    ShaderMode fShaderModes[2];
+
+    inline GrTextureEffect(sk_sp<GrSurfaceProxy>, SkAlphaType, const SkMatrix&, const Sampling&);
 
     GrTextureEffect(const GrTextureEffect& src);
 
-    inline GrTextureEffect(sk_sp<GrSurfaceProxy>, SkAlphaType, const SkMatrix&, GrSamplerState);
-
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
 
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.cpp b/src/gpu/effects/GrYUVtoRGBEffect.cpp
index 987db7e..a85b810 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.cpp
+++ b/src/gpu/effects/GrYUVtoRGBEffect.cpp
@@ -19,6 +19,7 @@
                                                             const SkYUVAIndex yuvaIndices[4],
                                                             SkYUVColorSpace yuvColorSpace,
                                                             GrSamplerState::Filter filterMode,
+                                                            const GrCaps& caps,
                                                             const SkMatrix& localMatrix,
                                                             const SkRect* domain) {
     int numPlanes;
@@ -63,16 +64,18 @@
         } else if (domain) {
             planeDomain = *domain;
         }
-        planeFPs[i] =
-                GrTextureEffect::Make(proxies[i], kUnknown_SkAlphaType, *planeMatrix, planeFilter);
+
         if (domain) {
             SkASSERT(planeFilter != GrSamplerState::Filter::kMipMap);
             if (planeFilter != GrSamplerState::Filter::kNearest) {
                 // Inset by half a pixel for bilerp, after scaling to the size of the plane
                 planeDomain.inset(0.5f, 0.5f);
             }
-            planeFPs[i] = GrDomainEffect::Make(std::move(planeFPs[i]), planeDomain,
-                                               GrTextureDomain::kClamp_Mode, false);
+            planeFPs[i] = GrTextureEffect::MakeSubset(proxies[i], kUnknown_SkAlphaType,
+                                                      *planeMatrix, planeFilter, planeDomain, caps);
+        } else {
+            planeFPs[i] = GrTextureEffect::Make(proxies[i], kUnknown_SkAlphaType, *planeMatrix,
+                                                planeFilter);
         }
     }
 
@@ -187,7 +190,6 @@
 
         UniformHandle fColorSpaceMatrixVar;
         UniformHandle fColorSpaceTranslateVar;
-        GrTextureDomain::GLDomain fGLDomains[4];
     };
 
     return new GrGLSLYUVtoRGBEffect;
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.h b/src/gpu/effects/GrYUVtoRGBEffect.h
index b99de32..6ed3b63 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.h
+++ b/src/gpu/effects/GrYUVtoRGBEffect.h
@@ -10,11 +10,9 @@
 
 #include "include/core/SkTypes.h"
 
+#include "include/core/SkYUVAIndex.h"
 #include "src/gpu/GrCoordTransform.h"
 #include "src/gpu/GrFragmentProcessor.h"
-#include "src/gpu/effects/GrTextureDomain.h"
-
-#include "include/core/SkYUVAIndex.h"
 
 class GrYUVtoRGBEffect : public GrFragmentProcessor {
 public:
@@ -26,6 +24,7 @@
                                                      const SkYUVAIndex indices[4],
                                                      SkYUVColorSpace yuvColorSpace,
                                                      GrSamplerState::Filter filterMode,
+                                                     const GrCaps&,
                                                      const SkMatrix& localMatrix = SkMatrix::I(),
                                                      const SkRect* domain = nullptr);
 #ifdef SK_DEBUG
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 3b7fafc..2eb257c 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -30,7 +30,6 @@
 #include "src/gpu/GrTexturePriv.h"
 #include "src/gpu/GrTextureProxy.h"
 #include "src/gpu/SkGr.h"
-#include "src/gpu/effects/GrTextureDomain.h"
 #include "src/gpu/effects/generated/GrClampFragmentProcessor.h"
 #include "src/gpu/geometry/GrQuad.h"
 #include "src/gpu/geometry/GrQuadBuffer.h"
@@ -1072,13 +1071,21 @@
 
         GrSurfaceProxy* proxy = proxyView.proxy();
         std::unique_ptr<GrFragmentProcessor> fp;
-        fp = GrTextureEffect::Make(sk_ref_sp(proxy), alphaType, SkMatrix::I(), filter);
         if (domain) {
             // Update domain to match what GrTextureOp would do for bilerp, but don't do any
-            // normalization since GrTextureDomainEffect handles that and the origin.
+            // normalization since GrTextureEffect handles that and the origin.
             SkRect correctedDomain = normalize_domain(filter, {1.f, 1.f, 0.f}, domain);
-            fp = GrDomainEffect::Make(std::move(fp), correctedDomain, GrTextureDomain::kClamp_Mode,
-                                      filter);
+            const auto& caps = *context->priv().caps();
+            SkRect localRect;
+            if (localQuad.asRect(&localRect)) {
+                fp = GrTextureEffect::MakeSubset(sk_ref_sp(proxy), alphaType, SkMatrix::I(), filter,
+                                                 correctedDomain, localRect, caps);
+            } else {
+                fp = GrTextureEffect::MakeSubset(sk_ref_sp(proxy), alphaType, SkMatrix::I(), filter,
+                                                 correctedDomain, caps);
+            }
+        } else {
+            fp = GrTextureEffect::Make(sk_ref_sp(proxy), alphaType, SkMatrix::I(), filter);
         }
         fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(textureXform));
         paint.addColorFragmentProcessor(std::move(fp));
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index 44a0275..6a21b93 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -348,8 +348,9 @@
     GrPaint paint;
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
 
+    const auto& caps = *ctx->priv().caps();
     auto fp = GrYUVtoRGBEffect::Make(proxies, yuvaIndices, yuvColorSpace,
-                                     GrSamplerState::Filter::kNearest);
+                                     GrSamplerState::Filter::kNearest, caps);
     if (colorSpaceXform) {
         fp = GrColorSpaceXformEffect::Make(std::move(fp), std::move(colorSpaceXform));
     }
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
index a88c349..2e3bba5 100755
--- a/src/shaders/SkImageShader.cpp
+++ b/src/shaders/SkImageShader.cpp
@@ -204,22 +204,6 @@
     GrSamplerState::WrapMode wrapModes[] = {tile_mode_to_wrap_mode(fTileModeX),
                                             tile_mode_to_wrap_mode(fTileModeY)};
 
-    // 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 (!args.fContext->priv().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;
-        }
-    }
-
     // 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.
     // This completely ignores the complexity of the drawVertices case where explicit local coords
@@ -240,22 +224,31 @@
 
     lmInverse.postScale(scaleAdjust[0], scaleAdjust[1]);
 
+    const auto& caps = *args.fContext->priv().caps();
+
     std::unique_ptr<GrFragmentProcessor> inner;
     if (doBicubic) {
-        // domainX and domainY will properly apply the decal effect with the texture domain used in
-        // the bicubic filter if clamp to border was unsupported in hardware
+        // 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(proxy), lmInverse, wrapModes, domainX, domainY,
                                       kDir, srcAlphaType);
     } else {
-        auto dimensions = proxy->dimensions();
-        inner = GrTextureEffect::Make(std::move(proxy), srcAlphaType, lmInverse, samplerState);
-        if (domainX != GrTextureDomain::kIgnore_Mode || domainY != GrTextureDomain::kIgnore_Mode) {
-            SkRect domain = GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(dimensions),
-                                                             domainX, domainY);
-            inner = GrDomainEffect::Make(std::move(inner), domain, domainX, domainY,
-                                         samplerState.filter());
-        }
+        inner = GrTextureEffect::Make(std::move(proxy), srcAlphaType, lmInverse, samplerState,
+                                      caps);
     }
     inner = GrColorSpaceXformEffect::Make(std::move(inner), fImage->colorSpace(), srcAlphaType,
                                           args.fDstColorInfo->colorSpace());