Replace GrTextureDomainEffect with GrDomainEffect.

The new effect takes a child processor rather than a texture proxy.

In future changes it can implement domains on top of other effects
rather than incorporating the domain into each effect.

The longer term plan is to remove GrTextureDomain as a helper and just
have GrDomainEffect. That requires rewriting all the effects to take a
child effect.

Change-Id: Ieaab3a838b7eb4fbf7d8250b8816980645b7cea0
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/252097
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/gm/texturedomaineffect.cpp b/gm/texturedomaineffect.cpp
index 5355e2e..2252ef5 100644
--- a/gm/texturedomaineffect.cpp
+++ b/gm/texturedomaineffect.cpp
@@ -45,7 +45,7 @@
 
 namespace skiagm {
 /**
- * This GM directly exercises GrTextureDomainEffect.
+ * This GM directly exercises GrDomainEffect.
  */
 class TextureDomainEffect : public GpuGM {
 public:
@@ -141,12 +141,15 @@
 
                     GrPaint grPaint;
                     grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
-                    auto fp = GrTextureDomainEffect::Make(
-                            proxy, SkColorTypeToGrColorType(fBitmap.colorType()),
-                            textureMatrices[tm],
-                            GrTextureDomain::MakeTexelDomain(texelDomains[d], mode),
-                            mode, fFilter);
 
+                    auto fp = GrSimpleTextureEffect::Make(
+                            proxy, SkColorTypeToGrColorType(fBitmap.colorType()), SkMatrix::I(),
+                            fFilter);
+                    bool filterIfDecal = GrDomainEffect::DecalFilterFromSamplerFilter(fFilter);
+                    fp = GrDomainEffect::Make(
+                            std::move(fp), textureMatrices[tm],
+                            GrTextureDomain::MakeTexelDomain(texelDomains[d], mode), mode,
+                            filterIfDecal);
                     if (!fp) {
                         continue;
                     }
diff --git a/src/core/SkGpuBlurUtils.cpp b/src/core/SkGpuBlurUtils.cpp
index c863908..a21ae6a 100644
--- a/src/core/SkGpuBlurUtils.cpp
+++ b/src/core/SkGpuBlurUtils.cpp
@@ -319,7 +319,7 @@
 
         GrPaint paint;
         if (GrTextureDomain::kIgnore_Mode != mode && i == 1) {
-            // GrTextureDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter.
+            // GrDomainEffect does not support kRepeat_Mode with GrSamplerState::Filter.
             GrTextureDomain::Mode modeForScaling = GrTextureDomain::kRepeat_Mode == mode
                                                                 ? GrTextureDomain::kDecal_Mode
                                                                 : mode;
@@ -335,12 +335,9 @@
                 domain.fTop = domain.fBottom = SkScalarAve(domain.fTop, domain.fBottom);
             }
             domain.offset(proxyOffset.x(), proxyOffset.y());
-            auto fp = GrTextureDomainEffect::Make(std::move(srcProxy),
-                                                  srcColorType,
-                                                  SkMatrix::I(),
-                                                  domain,
-                                                  modeForScaling,
+            auto fp = GrSimpleTextureEffect::Make(std::move(srcProxy), srcColorType, SkMatrix::I(),
                                                   GrSamplerState::Filter::kBilerp);
+            fp = GrDomainEffect::Make(std::move(fp), SkMatrix::I(), domain, modeForScaling, true);
             paint.addColorFragmentProcessor(std::move(fp));
             srcRect.offset(-(*srcOffset));
             // TODO: consume the srcOffset in both first draws and always set it to zero
@@ -404,9 +401,10 @@
     GrPaint paint;
     SkRect domain = GrTextureDomain::MakeTexelDomain(localSrcBounds, GrTextureDomain::kClamp_Mode,
                                                      GrTextureDomain::kClamp_Mode);
-    auto fp = GrTextureDomainEffect::Make(std::move(srcProxy), srcColorType, SkMatrix::I(), domain,
-                                          GrTextureDomain::kClamp_Mode,
+    auto fp = GrSimpleTextureEffect::Make(std::move(srcProxy), srcColorType, SkMatrix::I(),
                                           GrSamplerState::Filter::kBilerp);
+    fp = GrDomainEffect::Make(std::move(fp), SkMatrix::I(), domain, GrTextureDomain::kClamp_Mode,
+                              true);
     paint.addColorFragmentProcessor(std::move(fp));
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
     GrFixedClip clip(SkIRect::MakeWH(finalW, finalH));
diff --git a/src/effects/imagefilters/SkArithmeticImageFilter.cpp b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
index 2b1aaaf..cbdce86 100644
--- a/src/effects/imagefilters/SkArithmeticImageFilter.cpp
+++ b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
@@ -354,10 +354,12 @@
                 SkIntToScalar(bgSubset.left() - backgroundOffset.fX),
                 SkIntToScalar(bgSubset.top()  - backgroundOffset.fY));
         GrColorType bgColorType = SkColorTypeToGrColorType(background->colorType());
-        bgFP = GrTextureDomainEffect::Make(
-                std::move(backgroundProxy), bgColorType, backgroundMatrix,
+        bgFP = GrSimpleTextureEffect::Make(std::move(backgroundProxy), bgColorType, SkMatrix::I(),
+                                           GrSamplerState::Filter::kNearest);
+        bgFP = GrDomainEffect::Make(
+                std::move(bgFP), backgroundMatrix,
                 GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode),
-                GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
+                GrTextureDomain::kDecal_Mode, false);
         bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
                                              background->alphaType(),
                                              ctx.colorSpace());
@@ -372,10 +374,13 @@
                 SkIntToScalar(fgSubset.left() - foregroundOffset.fX),
                 SkIntToScalar(fgSubset.top()  - foregroundOffset.fY));
         GrColorType fgColorType = SkColorTypeToGrColorType(foreground->colorType());
-        auto foregroundFP = GrTextureDomainEffect::Make(
-                std::move(foregroundProxy), fgColorType, foregroundMatrix,
+        auto foregroundFP =
+                GrSimpleTextureEffect::Make(std::move(foregroundProxy), fgColorType, SkMatrix::I(),
+                                            GrSamplerState::Filter::kNearest);
+        foregroundFP = GrDomainEffect::Make(
+                std::move(foregroundFP), foregroundMatrix,
                 GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode),
-                GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
+                GrTextureDomain::kDecal_Mode, false);
         foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
                                                      foreground->getColorSpace(),
                                                      foreground->alphaType(),
diff --git a/src/effects/imagefilters/SkXfermodeImageFilter.cpp b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
index 14cf52f..34bf8fa 100644
--- a/src/effects/imagefilters/SkXfermodeImageFilter.cpp
+++ b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
@@ -267,10 +267,12 @@
                 SkIntToScalar(bgSubset.left() - backgroundOffset.fX),
                 SkIntToScalar(bgSubset.top()  - backgroundOffset.fY));
         GrColorType bgColorType = SkColorTypeToGrColorType(background->colorType());
-        bgFP = GrTextureDomainEffect::Make(
-                    std::move(backgroundProxy), bgColorType, bgMatrix,
-                    GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode),
-                    GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
+        bgFP = GrSimpleTextureEffect::Make(std::move(backgroundProxy), bgColorType, SkMatrix::I(),
+                                           GrSamplerState::Filter::kNearest);
+        bgFP = GrDomainEffect::Make(
+                std::move(bgFP), bgMatrix,
+                GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode),
+                GrTextureDomain::kDecal_Mode, false);
         bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
                                              background->alphaType(),
                                              ctx.colorSpace());
@@ -285,10 +287,13 @@
                 SkIntToScalar(fgSubset.left() - foregroundOffset.fX),
                 SkIntToScalar(fgSubset.top()  - foregroundOffset.fY));
         GrColorType fgColorType = SkColorTypeToGrColorType(foreground->colorType());
-        auto foregroundFP = GrTextureDomainEffect::Make(
-                std::move(foregroundProxy), fgColorType, fgMatrix,
+        auto foregroundFP =
+                GrSimpleTextureEffect::Make(std::move(foregroundProxy), fgColorType, SkMatrix::I(),
+                                            GrSamplerState::Filter::kNearest);
+        foregroundFP = GrDomainEffect::Make(
+                std::move(foregroundFP), fgMatrix,
                 GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode),
-                GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
+                GrTextureDomain::kDecal_Mode, false);
         foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
                                                      foreground->getColorSpace(),
                                                      foreground->alphaType(),
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index e0c656e..b498a6f 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -105,6 +105,7 @@
         kGrDistanceFieldA8TextGeoProc_ClassID,
         kGrDistanceFieldLCDTextGeoProc_ClassID,
         kGrDistanceFieldPathGeoProc_ClassID,
+        kGrDomainEffect_ClassID,
         kGrDualIntervalGradientColorizer_ClassID,
         kGrEllipseEffect_ClassID,
         kGrFillRRectOp_Processor_ClassID,
@@ -138,7 +139,6 @@
         kGrSampleMaskProcessor_ClassID,
         kGrSaturateProcessor_ClassID,
         kGrSweepGradientLayout_ClassID,
-        kGrTextureDomainEffect_ClassID,
         kGrTextureGradientColorizer_ClassID,
         kGrTiledGradientEffect_ClassID,
         kGrTwoPointConicalGradientLayout_ClassID,
diff --git a/src/gpu/GrTextureProducer.cpp b/src/gpu/GrTextureProducer.cpp
index cfc2c8d..8c1fb50 100644
--- a/src/gpu/GrTextureProducer.cpp
+++ b/src/gpu/GrTextureProducer.cpp
@@ -63,9 +63,12 @@
         // This would cause us to read values from outside the subset. Surely, the caller knows
         // better!
         SkASSERT(copyParams.fFilter != GrSamplerState::Filter::kMipMap);
-        paint.addColorFragmentProcessor(
-            GrTextureDomainEffect::Make(std::move(inputProxy), colorType, SkMatrix::I(), domain,
-                                        GrTextureDomain::kClamp_Mode, copyParams.fFilter));
+        auto fp = GrSimpleTextureEffect::Make(std::move(inputProxy), colorType, SkMatrix::I(),
+                                              copyParams.fFilter);
+        bool filterIfDecal = GrDomainEffect::DecalFilterFromSamplerFilter(copyParams.fFilter);
+        fp = GrDomainEffect::Make(std::move(fp), SkMatrix::I(), domain,
+                                  GrTextureDomain::kClamp_Mode, filterIfDecal);
+        paint.addColorFragmentProcessor(std::move(fp));
     } else {
         GrSamplerState samplerState(GrSamplerState::WrapMode::kClamp, copyParams.fFilter);
         paint.addColorTextureProcessor(std::move(inputProxy), colorType, SkMatrix::I(),
@@ -206,8 +209,12 @@
         if (kDomain_DomainMode == domainMode || (fDomainNeedsDecal && !clampToBorderSupport)) {
             GrTextureDomain::Mode wrapMode = fDomainNeedsDecal ? GrTextureDomain::kDecal_Mode
                                                                : GrTextureDomain::kClamp_Mode;
-            return GrTextureDomainEffect::Make(std::move(proxy), srcColorType, textureMatrix,
-                                               domain, wrapMode, *filterOrNullForBicubic);
+            auto fp = GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, SkMatrix::I(),
+                                                  *filterOrNullForBicubic);
+            bool filterIfDecal =
+                    GrDomainEffect::DecalFilterFromSamplerFilter(*filterOrNullForBicubic);
+            return GrDomainEffect::Make(std::move(fp), textureMatrix, domain, wrapMode,
+                                        filterIfDecal);
         } else {
             GrSamplerState::WrapMode wrapMode =
                     fDomainNeedsDecal ? GrSamplerState::WrapMode::kClampToBorder
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index abd0237..743b6c7 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -958,8 +958,12 @@
             fp = GrBicubicEffect::Make(std::move(proxy), srcColorType, texMatrix, domain, kDir,
                                        bitmap.alphaType());
         } else {
-            fp = GrTextureDomainEffect::Make(std::move(proxy), srcColorType, texMatrix, domain,
-                                             GrTextureDomain::kClamp_Mode, samplerState.filter());
+            fp = GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, SkMatrix::I(),
+                                             samplerState);
+            bool filterIfDecal =
+                    GrDomainEffect::DecalFilterFromSamplerFilter(samplerState.filter());
+            fp = GrDomainEffect::Make(std::move(fp), texMatrix, domain,
+                                      GrTextureDomain::kClamp_Mode, filterIfDecal);
         }
     } else if (bicubic) {
         SkASSERT(GrSamplerState::Filter::kNearest == samplerState.filter());
diff --git a/src/gpu/effects/GrTextureDomain.cpp b/src/gpu/effects/GrTextureDomain.cpp
index 8bb662b..38a1a0b 100644
--- a/src/gpu/effects/GrTextureDomain.cpp
+++ b/src/gpu/effects/GrTextureDomain.cpp
@@ -37,8 +37,7 @@
     // We don't currently handle domains that are empty or don't intersect the texture.
     // It is OK if the domain rect is a line or point, but it should not be inverted. We do not
     // handle rects that do not intersect the [0..1]x[0..1] rect.
-    SkASSERT(domain.fLeft <= domain.fRight);
-    SkASSERT(domain.fTop <= domain.fBottom);
+    SkASSERT(domain.isSorted());
     fDomain.fLeft = SkScalarPin(domain.fLeft, 0.0f, kFullRect.fRight);
     fDomain.fRight = SkScalarPin(domain.fRight, fDomain.fLeft, kFullRect.fRight);
     fDomain.fTop = SkScalarPin(domain.fTop, 0.0f, kFullRect.fBottom);
@@ -47,6 +46,13 @@
     SkASSERT(fDomain.fTop <= fDomain.fBottom);
 }
 
+GrTextureDomain::GrTextureDomain(const SkRect& domain, Mode modeX, Mode modeY, int index)
+        : fDomain(domain), fModeX(modeX), fModeY(modeY), fIndex(index) {
+    // We don't currently handle domains that are empty or don't intersect the texture.
+    // It is OK if the domain rect is a line or point, but it should not be inverted.
+    SkASSERT(domain.isSorted());
+}
+
 //////////////////////////////////////////////////////////////////////////////
 
 static SkString clamp_expression(GrTextureDomain::Mode mode, const char* inCoord,
@@ -76,6 +82,22 @@
     return clampedExpr;
 }
 
+void GrTextureDomain::GLDomain::sampleProcessor(const GrTextureDomain& textureDomain,
+                                                const char* inColor,
+                                                const char* outColor,
+                                                const SkString& inCoords,
+                                                GrGLSLFragmentProcessor* parent,
+                                                GrGLSLFragmentProcessor::EmitArgs& args,
+                                                int childIndex) {
+    auto appendProcessorSample = [parent, &args, childIndex, inColor](const char* coord) {
+        SkString outColor("childColor");
+        parent->invokeChild(childIndex, inColor, &outColor, args, coord);
+        return outColor;
+    };
+    this->sample(args.fFragBuilder, args.fUniformHandler, textureDomain, outColor, inCoords,
+                 appendProcessorSample);
+}
+
 void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder,
                                               GrGLSLUniformHandler* uniformHandler,
                                               const GrShaderCaps* shaderCaps,
@@ -84,6 +106,21 @@
                                               const SkString& inCoords,
                                               GrGLSLFragmentProcessor::SamplerHandle sampler,
                                               const char* inModulateColor) {
+    auto appendTextureSample = [&sampler, inModulateColor, builder](const char* coord) {
+        builder->codeAppend("half4 textureColor = ");
+        builder->appendTextureLookupAndModulate(inModulateColor, sampler, coord);
+        builder->codeAppend(";");
+        return SkString("textureColor");
+    };
+    this->sample(builder, uniformHandler, textureDomain, outColor, inCoords, appendTextureSample);
+}
+
+void GrTextureDomain::GLDomain::sample(GrGLSLShaderBuilder* builder,
+                                       GrGLSLUniformHandler* uniformHandler,
+                                       const GrTextureDomain& textureDomain,
+                                       const char* outColor,
+                                       const SkString& inCoords,
+                                       const std::function<AppendSample>& appendSample) {
     SkASSERT(!fHasMode || (textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY));
     SkDEBUGCODE(fModeX = textureDomain.modeX();)
     SkDEBUGCODE(fModeY = textureDomain.modeY();)
@@ -139,11 +176,8 @@
     }
     builder->codeAppend(";");
 
-    // Look up the texture sample at the clamped coordinate location
-    builder->codeAppend("half4 inside = ");
-    builder->appendTextureLookupAndModulate(inModulateColor, sampler, "clampedCoord",
-                                            kFloat2_GrSLType);
-    builder->codeAppend(";");
+    // Sample 'appendSample' at the clamped coordinate location.
+    SkString color = appendSample("clampedCoord");
 
     // Apply decal mode's transparency interpolation if needed
     if (decalX || decalY) {
@@ -170,175 +204,211 @@
         // is set to 1 and it becomes a simple linear blend between texture and transparent.
         builder->codeAppendf("if (err > %s.z) { err = 1.0; } else if (%s.z < 1) { err = 0.0; }",
                              fDecalName.c_str(), fDecalName.c_str());
-        builder->codeAppendf("%s = mix(inside, half4(0, 0, 0, 0), err);", outColor);
+        builder->codeAppendf("%s = mix(%s, half4(0, 0, 0, 0), err);", outColor, color.c_str());
     } else {
         // A simple look up
-        builder->codeAppendf("%s = inside;", outColor);
+        builder->codeAppendf("%s = %s;", outColor, color.c_str());
     }
 }
 
 void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
                                         const GrTextureDomain& textureDomain,
                                         GrTextureProxy* proxy,
-                                        const GrSamplerState& sampler) {
-    GrTexture* tex = proxy->peekTexture();
-    SkASSERT(fHasMode && textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY);
-    if (kIgnore_Mode != textureDomain.modeX() || kIgnore_Mode != textureDomain.modeY()) {
-        bool sendDecalData = textureDomain.modeX() == kDecal_Mode ||
-                             textureDomain.modeY() == kDecal_Mode;
+                                        const GrSamplerState& state) {
+    // We want a hard transition from texture content to trans-black in nearest mode.
+    bool filterDecal = state.filter() != GrSamplerState::Filter::kNearest;
+    this->setData(pdman, textureDomain, proxy, filterDecal);
+}
 
-        // If the texture is using nearest filtering, then the decal filter weight should step from
-        // 0 (texture) to 1 (transparent) one half pixel away from the domain. When doing any other
-        // form of filtering, the weight should be 1.0 so that it smoothly interpolates between the
-        // texture and transparent.
-        SkScalar decalFilterWeight = sampler.filter() == GrSamplerState::Filter::kNearest ?
-                SK_ScalarHalf : 1.0f;
+void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
+                                        const GrTextureDomain& textureDomain,
+                                        bool filterIfDecal) {
+    this->setData(pdman, textureDomain, nullptr, filterIfDecal);
+}
+
+void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
+                                        const GrTextureDomain& textureDomain,
+                                        GrTextureProxy* proxy,
+                                        bool filterIfDecal) {
+    SkASSERT(fHasMode && textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY);
+    if (kIgnore_Mode == textureDomain.modeX() && kIgnore_Mode == textureDomain.modeY()) {
+        return;
+    }
+    // If the texture is using nearest filtering, then the decal filter weight should step from
+    // 0 (texture) to 1 (transparent) one half pixel away from the domain. When doing any other
+    // form of filtering, the weight should be 1.0 so that it smoothly interpolates between the
+    // texture and transparent.
+    // Start off assuming we're in pixel units and later adjust if we have to deal with normalized
+    // texture coords.
+    float decalFilterWeights[3] = {1.f, 1.f, filterIfDecal ? 1.f : 0.5f};
+    bool sendDecalData = textureDomain.modeX() == kDecal_Mode ||
+                         textureDomain.modeY() == kDecal_Mode;
+    float tempDomainValues[4];
+    const float* values;
+    if (proxy) {
         SkScalar wInv, hInv, h;
+        GrTexture* tex = proxy->peekTexture();
         if (proxy->textureType() == GrTextureType::kRectangle) {
             wInv = hInv = 1.f;
             h = tex->height();
-
-            // Don't do any scaling by texture size for decal filter rate, it's already in pixels
-            if (sendDecalData) {
-                pdman.set3f(fDecalUni, 1.f, 1.f, decalFilterWeight);
-            }
+            // Don't do any scaling by texture size for decal filter rate, it's already in
+            // pixels
         } else {
             wInv = SK_Scalar1 / tex->width();
             hInv = SK_Scalar1 / tex->height();
             h = 1.f;
 
-            if (sendDecalData) {
-                pdman.set3f(fDecalUni, tex->width(), tex->height(), decalFilterWeight);
-            }
+            // Account for texture coord normalization in decal filter weights.
+            decalFilterWeights[0] = tex->width();
+            decalFilterWeights[1] = tex->height();
         }
 
-        float values[kPrevDomainCount] = {
-            SkScalarToFloat(textureDomain.domain().fLeft * wInv),
-            SkScalarToFloat(textureDomain.domain().fTop * hInv),
-            SkScalarToFloat(textureDomain.domain().fRight * wInv),
-            SkScalarToFloat(textureDomain.domain().fBottom * hInv)
-        };
+        tempDomainValues[0] = SkScalarToFloat(textureDomain.domain().fLeft * wInv);
+        tempDomainValues[1] = SkScalarToFloat(textureDomain.domain().fTop * hInv);
+        tempDomainValues[2] = SkScalarToFloat(textureDomain.domain().fRight * wInv);
+        tempDomainValues[3] = SkScalarToFloat(textureDomain.domain().fBottom * hInv);
 
         if (proxy->textureType() == GrTextureType::kRectangle) {
-            SkASSERT(values[0] >= 0.0f && values[0] <= proxy->width());
-            SkASSERT(values[1] >= 0.0f && values[1] <= proxy->height());
-            SkASSERT(values[2] >= 0.0f && values[2] <= proxy->width());
-            SkASSERT(values[3] >= 0.0f && values[3] <= proxy->height());
+            SkASSERT(tempDomainValues[0] >= 0.0f && tempDomainValues[0] <= proxy->width());
+            SkASSERT(tempDomainValues[1] >= 0.0f && tempDomainValues[1] <= proxy->height());
+            SkASSERT(tempDomainValues[2] >= 0.0f && tempDomainValues[2] <= proxy->width());
+            SkASSERT(tempDomainValues[3] >= 0.0f && tempDomainValues[3] <= proxy->height());
         } else {
-            SkASSERT(values[0] >= 0.0f && values[0] <= 1.0f);
-            SkASSERT(values[1] >= 0.0f && values[1] <= 1.0f);
-            SkASSERT(values[2] >= 0.0f && values[2] <= 1.0f);
-            SkASSERT(values[3] >= 0.0f && values[3] <= 1.0f);
+            SkASSERT(tempDomainValues[0] >= 0.0f && tempDomainValues[0] <= 1.0f);
+            SkASSERT(tempDomainValues[1] >= 0.0f && tempDomainValues[1] <= 1.0f);
+            SkASSERT(tempDomainValues[2] >= 0.0f && tempDomainValues[2] <= 1.0f);
+            SkASSERT(tempDomainValues[3] >= 0.0f && tempDomainValues[3] <= 1.0f);
         }
 
         // vertical flip if necessary
         if (kBottomLeft_GrSurfaceOrigin == proxy->origin()) {
-            values[1] = h - values[1];
-            values[3] = h - values[3];
+            tempDomainValues[1] = h - tempDomainValues[1];
+            tempDomainValues[3] = h - tempDomainValues[3];
 
             // The top and bottom were just flipped, so correct the ordering
             // of elements so that values = (l, t, r, b).
             using std::swap;
-            swap(values[1], values[3]);
+            swap(tempDomainValues[1], tempDomainValues[3]);
         }
-        if (0 != memcmp(values, fPrevDomain, kPrevDomainCount * sizeof(float))) {
-            pdman.set4fv(fDomainUni, 1, values);
-            memcpy(fPrevDomain, values, kPrevDomainCount * sizeof(float));
-        }
+        values = tempDomainValues;
+    } else {
+        values = textureDomain.domain().asScalars();
+    }
+    if (!std::equal(values, values + 4, fPrevDomain)) {
+        pdman.set4fv(fDomainUni, 1, values);
+        std::copy_n(values, 4, fPrevDomain);
+    }
+    if (sendDecalData &&
+        !std::equal(decalFilterWeights, decalFilterWeights + 3, fPrevDeclFilterWeights)) {
+        pdman.set3fv(fDecalUni, 1, decalFilterWeights);
+        std::copy_n(decalFilterWeights, 3, fPrevDeclFilterWeights);
     }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
-std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::Make(
-        sk_sp<GrTextureProxy> proxy,
-        GrColorType srcColorType,
-        const SkMatrix& matrix,
-        const SkRect& domain,
-        GrTextureDomain::Mode mode,
-        GrSamplerState::Filter filterMode) {
-    return Make(std::move(proxy), srcColorType, matrix, domain, mode, mode,
-                GrSamplerState(GrSamplerState::WrapMode::kClamp, filterMode));
+std::unique_ptr<GrFragmentProcessor> GrDomainEffect::Make(std::unique_ptr<GrFragmentProcessor> fp,
+                                                          const SkMatrix& matrix,
+                                                          const SkRect& domain,
+                                                          GrTextureDomain::Mode mode,
+                                                          bool decalIsFiltered) {
+    return Make(std::move(fp), matrix, domain, mode, mode, decalIsFiltered);
 }
 
-std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::Make(
-        sk_sp<GrTextureProxy> proxy,
-        GrColorType srcColorType,
-        const SkMatrix& matrix,
-        const SkRect& domain,
-        GrTextureDomain::Mode modeX,
-        GrTextureDomain::Mode modeY,
-        const GrSamplerState& sampler) {
+std::unique_ptr<GrFragmentProcessor> GrDomainEffect::Make(std::unique_ptr<GrFragmentProcessor> fp,
+                                                          const SkMatrix& matrix,
+                                                          const SkRect& domain,
+                                                          GrTextureDomain::Mode modeX,
+                                                          GrTextureDomain::Mode modeY,
+                                                          bool decalIsFiltered) {
+    if (modeX == GrTextureDomain::kIgnore_Mode && modeY == GrTextureDomain::kIgnore_Mode &&
+        matrix.isIdentity()) {
+        return fp;
+    }
+    // If there are no coord transforms on the passed FP or it's children then there's no need to
+    // enforce a domain.
+    CoordTransformIter iter(fp.get());
+    if (!iter.next()) {
+        return fp;
+    }
+    // We don't support more than 1 coord transform when overriding local coords.
+    if (iter.next()) {
+        return nullptr;
+    }
     // If both domain modes happen to be ignore, it would be faster to just drop the domain logic
-    // entirely Technically, we could also use the simple texture effect if the domain modes agree
-    // with the sampler modes and the proxy is the same size as the domain. It's a lot easier for
-    // calling code to detect these cases and handle it themselves.
-    return std::unique_ptr<GrFragmentProcessor>(new GrTextureDomainEffect(
-            std::move(proxy), srcColorType, matrix, domain, modeX, modeY, sampler));
+    // 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), matrix, domain, modeX, modeY, decalIsFiltered));
 }
 
-GrTextureDomainEffect::GrTextureDomainEffect(sk_sp<GrTextureProxy> proxy,
-                                             GrColorType srcColorType,
-                                             const SkMatrix& matrix,
-                                             const SkRect& domain,
-                                             GrTextureDomain::Mode modeX,
-                                             GrTextureDomain::Mode modeY,
-                                             const GrSamplerState& sampler)
-        : INHERITED(kGrTextureDomainEffect_ClassID,
-                    ModulateForSamplerOptFlags(srcColorType,
-                            GrTextureDomain::IsDecalSampled(sampler, modeX, modeY)))
-        , fCoordTransform(matrix, proxy.get())
-        , fTextureDomain(proxy.get(), domain, modeX, modeY)
-        , fTextureSampler(std::move(proxy), sampler) {
-    SkASSERT((modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode) ||
-             sampler.filter() == GrSamplerState::Filter::kNearest);
+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 SkMatrix& matrix,
+                               const SkRect& domain,
+                               GrTextureDomain::Mode modeX,
+                               GrTextureDomain::Mode modeY,
+                               bool decalIsFiltered)
+        : INHERITED(kGrDomainEffect_ClassID, Flags(fp.get(), modeX, modeY))
+        , fCoordTransform(matrix)
+        , fDomain(domain, modeX, modeY)
+        , fDecalIsFiltered(decalIsFiltered) {
+    SkASSERT(fp);
+    fp->setComputeLocalCoordsInVertexShader(false);
+    this->registerChildProcessor(std::move(fp));
     this->addCoordTransform(&fCoordTransform);
-    this->setTextureSamplerCnt(1);
+    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;
+    }
 }
 
-GrTextureDomainEffect::GrTextureDomainEffect(const GrTextureDomainEffect& that)
-        : INHERITED(kGrTextureDomainEffect_ClassID, that.optimizationFlags())
+GrDomainEffect::GrDomainEffect(const GrDomainEffect& that)
+        : INHERITED(kGrDomainEffect_ClassID, that.optimizationFlags())
         , fCoordTransform(that.fCoordTransform)
-        , fTextureDomain(that.fTextureDomain)
-        , fTextureSampler(that.fTextureSampler) {
+        , fDomain(that.fDomain)
+        , fDecalIsFiltered(that.fDecalIsFiltered) {
+    auto child = that.childProcessor(0).clone();
+    child->setComputeLocalCoordsInVertexShader(false);
+    this->registerChildProcessor(std::move(child));
     this->addCoordTransform(&fCoordTransform);
-    this->setTextureSamplerCnt(1);
 }
 
-void GrTextureDomainEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
-                                                  GrProcessorKeyBuilder* b) const {
-    b->add32(GrTextureDomain::GLDomain::DomainKey(fTextureDomain));
+void GrDomainEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
+                                           GrProcessorKeyBuilder* b) const {
+    b->add32(GrTextureDomain::GLDomain::DomainKey(fDomain));
 }
 
-GrGLSLFragmentProcessor* GrTextureDomainEffect::onCreateGLSLInstance() const  {
+GrGLSLFragmentProcessor* GrDomainEffect::onCreateGLSLInstance() const {
     class GLSLProcessor : public GrGLSLFragmentProcessor {
     public:
         void emitCode(EmitArgs& args) override {
-            const GrTextureDomainEffect& tde = args.fFp.cast<GrTextureDomainEffect>();
-            const GrTextureDomain& domain = tde.fTextureDomain;
+            const GrDomainEffect& de = args.fFp.cast<GrDomainEffect>();
+            const GrTextureDomain& domain = de.fDomain;
 
-            GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
             SkString coords2D =
-                              fragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
+                    args.fFragBuilder->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint);
 
-            fGLDomain.sampleTexture(fragBuilder,
-                                    args.fUniformHandler,
-                                    args.fShaderCaps,
-                                    domain,
-                                    args.fOutputColor,
-                                    coords2D,
-                                    args.fTexSamplers[0],
-                                    args.fInputColor);
+            fGLDomain.sampleProcessor(domain, args.fInputColor, args.fOutputColor, coords2D, this,
+                                      args, 0);
         }
 
     protected:
         void onSetData(const GrGLSLProgramDataManager& pdman,
                        const GrFragmentProcessor& fp) override {
-            const GrTextureDomainEffect& tde = fp.cast<GrTextureDomainEffect>();
-            const GrTextureDomain& domain = tde.fTextureDomain;
-            GrTextureProxy* proxy = tde.textureSampler(0).proxy();
-
-            fGLDomain.setData(pdman, domain, proxy, tde.textureSampler(0).samplerState());
+            const GrDomainEffect& de = fp.cast<GrDomainEffect>();
+            const GrTextureDomain& domain = de.fDomain;
+            fGLDomain.setData(pdman, domain, de.fDecalIsFiltered);
         }
 
     private:
@@ -348,41 +418,39 @@
     return new GLSLProcessor;
 }
 
-bool GrTextureDomainEffect::onIsEqual(const GrFragmentProcessor& sBase) const {
-    const GrTextureDomainEffect& s = sBase.cast<GrTextureDomainEffect>();
-    return this->fTextureDomain == s.fTextureDomain;
+bool GrDomainEffect::onIsEqual(const GrFragmentProcessor& sBase) const {
+    auto& td = sBase.cast<GrDomainEffect>();
+    return fDomain == td.fDomain && fDecalIsFiltered == td.fDecalIsFiltered;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureDomainEffect);
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrDomainEffect);
 
 #if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::TestCreate(GrProcessorTestData* d) {
-    int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
-                                        : GrProcessorUnitTest::kAlphaTextureIdx;
-    sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
+std::unique_ptr<GrFragmentProcessor> GrDomainEffect::TestCreate(GrProcessorTestData* d) {
     SkRect domain;
-    domain.fLeft = d->fRandom->nextRangeScalar(0, proxy->width());
-    domain.fRight = d->fRandom->nextRangeScalar(domain.fLeft, proxy->width());
-    domain.fTop = d->fRandom->nextRangeScalar(0, proxy->height());
-    domain.fBottom = d->fRandom->nextRangeScalar(domain.fTop, proxy->height());
-    GrTextureDomain::Mode modeX =
-        (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
-    GrTextureDomain::Mode modeY =
-        (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
+    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();
     const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
-    bool bilerp = modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode ?
-            d->fRandom->nextBool() : false;
-    return GrTextureDomainEffect::Make(
-            std::move(proxy),
-            d->textureProxyColorType(texIdx),
-            matrix,
-            domain,
-            modeX,
-            modeY,
-            GrSamplerState(GrSamplerState::WrapMode::kClamp, bilerp ?
-                           GrSamplerState::Filter::kBilerp : GrSamplerState::Filter::kNearest));
+    bool filterIfDecal = d->fRandom->nextBool();
+
+    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();
+        auto result =
+                GrDomainEffect::Make(std::move(child), matrix, domain, modeX, modeY, filterIfDecal);
+        if (result && result.get() != childPtr) {
+            return result;
+        }
+    } while (true);
 }
 #endif
 
diff --git a/src/gpu/effects/GrTextureDomain.h b/src/gpu/effects/GrTextureDomain.h
index 96c2d20..cae7ad0 100644
--- a/src/gpu/effects/GrTextureDomain.h
+++ b/src/gpu/effects/GrTextureDomain.h
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
-#ifndef GrTextureDomainEffect_DEFINED
-#define GrTextureDomainEffect_DEFINED
+#ifndef GrTextureDomain_DEFINED
+#define GrTextureDomain_DEFINED
 
 #include "src/gpu/GrCoordTransform.h"
 #include "src/gpu/GrFragmentProcessor.h"
@@ -50,6 +50,16 @@
     }
 
     /**
+     * Construct a domain used to sample a GrFragmentProcessor.
+     *
+     * @param index     Pass a value >= 0 if using multiple texture domains in the same effect.
+     *                  It is used to keep inserted variables from causing name collisions.
+     */
+    GrTextureDomain(const SkRect& domain, Mode modeX, Mode modeY, int index = -1);
+
+    /**
+     * Construct a domain used to directly sampler a texture.
+     *
      * @param index     Pass a value >= 0 if using multiple texture domains in the same effect.
      *                  It is used to keep inserted variables from causing name collisions.
      */
@@ -104,6 +114,7 @@
                (kIgnore_Mode == fModeY || (fDomain.fTop == that.fDomain.fTop &&
                                            fDomain.fBottom == that.fDomain.fBottom));
     }
+    bool operator!=(const GrTextureDomain& that) const { return !(*this == that); }
 
     /**
      * A GrGLSLFragmentProcessor subclass that corresponds to a GrProcessor subclass that uses
@@ -113,19 +124,32 @@
      */
     class GLDomain {
     public:
-        GLDomain() {
-            for (int i = 0; i < kPrevDomainCount; i++) {
-                fPrevDomain[i] = SK_FloatNaN;
-            }
-        }
+        GLDomain() = default;
 
         /**
-         * Call this from GrGLSLFragmentProcessor::emitCode() to sample the texture W.R.T. the
+         * Call this from GrGLSLFragmentProcessor::emitCode() to sample a child processor WRT the
          * domain and mode.
          *
          * @param outcolor  name of half4 variable to hold the sampled color.
-         * @param inCoords  name of float2 variable containing the coords to be used with the domain.
-         *                  It is assumed that this is a variable and not an expression.
+         * @param inCoords  name of float2 variable containing the coords to be used with the
+         *                  domain.
+         * @param inColor   color passed to the child processor.
+         */
+        void sampleProcessor(const GrTextureDomain& textureDomain,
+                             const char* inColor,
+                             const char* outColor,
+                             const SkString& inCoords,
+                             GrGLSLFragmentProcessor* parent,
+                             GrGLSLFragmentProcessor::EmitArgs& args,
+                             int childIndex);
+
+        /**
+         * Call this from GrGLSLFragmentProcessor::emitCode() to sample the texture WRT the domain
+         * and mode.
+         *
+         * @param outcolor  name of half4 variable to hold the sampled color.
+         * @param inCoords  name of float2 variable containing the coords to be used with the
+         *                  domain.
          * @param inModulateColor   if non-nullptr the sampled color will be modulated with this
          *                          expression before being written to outColor.
          */
@@ -140,11 +164,20 @@
 
         /**
          * Call this from GrGLSLFragmentProcessor::setData() to upload uniforms necessary for the
-         * texture domain. The rectangle is automatically adjusted to account for the texture's
-         * origin.
+         * domain. 'filterIfDecal' determines whether the transition to transparent black at the
+         * edge of domain is linearly interpolated over a unit interval or is "hard" when
+         * kDecal_Mode is used.
+         */
+        void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, bool filterIfDecal);
+
+        /**
+         * Call this from GrGLSLFragmentProcessor::setData() to upload uniforms necessary for the
+         * texture domain used with a texture proxy. The rectangle is automatically adjusted to
+         * account for the texture's origin. Filtering at the edge of the domain is inferred from
+         * the GrSamplerState's filter mode.
          */
         void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, GrTextureProxy*,
-                     const GrSamplerState& sampler);
+                     const GrSamplerState& state);
 
         enum {
             kModeBits = 2, // See DomainKey().
@@ -161,7 +194,20 @@
         }
 
     private:
-        static const int kPrevDomainCount = 4;
+        // Takes a builder and a coord and appends to the builder a string that is an expression
+        // the evaluates to a half4 color.
+        using AppendSample = SkString(const char* coord);
+
+        void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, GrTextureProxy*,
+                     bool filterIfDecal);
+
+        void sample(GrGLSLShaderBuilder* builder,
+                    GrGLSLUniformHandler* uniformHandler,
+                    const GrTextureDomain& textureDomain,
+                    const char* outColor,
+                    const SkString& inCoords,
+                    const std::function<AppendSample>& color);
+
         SkDEBUGCODE(Mode                        fModeX;)
         SkDEBUGCODE(Mode                        fModeY;)
         SkDEBUGCODE(bool                        fHasMode = false;)
@@ -172,67 +218,73 @@
         GrGLSLProgramDataManager::UniformHandle fDecalUni;
         SkString                                fDecalName;
 
-        float                                   fPrevDomain[kPrevDomainCount];
+        float                                   fPrevDomain[4] = {SK_FloatNaN};
+        float                                   fPrevDeclFilterWeights[3] = {SK_FloatNaN};
     };
 
 protected:
+    SkRect  fDomain;
     Mode    fModeX;
     Mode    fModeY;
-    SkRect  fDomain;
     int     fIndex;
 };
 
 /**
- * A basic texture effect that uses GrTextureDomain.
+ * This effect multiples thelocal coords by matrix, applies a domain, and then calls a child effect
+ * with the resulting coordinates.
  */
-class GrTextureDomainEffect : public GrFragmentProcessor {
+class GrDomainEffect : public GrFragmentProcessor {
 public:
-    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy>,
-                                                     GrColorType srcColorType,
+    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor>,
                                                      const SkMatrix&,
                                                      const SkRect& domain,
-                                                     GrTextureDomain::Mode mode,
-                                                     GrSamplerState::Filter filterMode);
+                                                     GrTextureDomain::Mode,
+                                                     bool decalIsFiltered);
 
-    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy>,
-                                                     GrColorType srcColorType,
+    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor>,
                                                      const SkMatrix&,
                                                      const SkRect& domain,
                                                      GrTextureDomain::Mode modeX,
                                                      GrTextureDomain::Mode modeY,
-                                                     const GrSamplerState& sampler);
+                                                     bool decalIsFiltered);
 
-    const char* name() const override { return "TextureDomain"; }
+    const char* name() const override { return "Domain"; }
+
+    static bool DecalFilterFromSamplerFilter(GrSamplerState::Filter filter) {
+        return filter > GrSamplerState::Filter::kNearest;
+    }
 
     std::unique_ptr<GrFragmentProcessor> clone() const override {
-        return std::unique_ptr<GrFragmentProcessor>(new GrTextureDomainEffect(*this));
+        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]",
-                    fTextureDomain.domain().fLeft, fTextureDomain.domain().fTop,
-                    fTextureDomain.domain().fRight, fTextureDomain.domain().fBottom);
+        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 fTextureDomain;
-    TextureSampler fTextureSampler;
+    GrTextureDomain fDomain;
+    bool fDecalIsFiltered;
 
-    GrTextureDomainEffect(sk_sp<GrTextureProxy>,
-                          GrColorType srcColorType,
-                          const SkMatrix&,
-                          const SkRect& domain,
-                          GrTextureDomain::Mode modeX,
-                          GrTextureDomain::Mode modeY,
-                          const GrSamplerState&);
+    GrDomainEffect(std::unique_ptr<GrFragmentProcessor>,
+                   const SkMatrix&,
+                   const SkRect& domain,
+                   GrTextureDomain::Mode modeX,
+                   GrTextureDomain::Mode modeY,
+                   bool decalIsFiltered);
 
-    explicit GrTextureDomainEffect(const GrTextureDomainEffect&);
+    explicit GrDomainEffect(const GrDomainEffect&);
 
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
 
@@ -240,8 +292,6 @@
 
     bool onIsEqual(const GrFragmentProcessor&) const override;
 
-    const TextureSampler& onTextureSampler(int) const override { return fTextureSampler; }
-
     GR_DECLARE_FRAGMENT_PROCESSOR_TEST
 
     typedef GrFragmentProcessor INHERITED;
diff --git a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
index 6a07445..961521f 100644
--- a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
@@ -170,7 +170,7 @@
                                                           args.fTransformedCoords[0].fUniformMatrix;
         if (mat.isValid()) {
             args.fUniformHandler->updateUniformVisibility(mat, kFragment_GrShaderFlag);
-            this->codeAppendf("_coords = (float3(_coords, 1) * %s).xy;\n",
+            this->codeAppendf("_coords = (%s * float3(_coords, 1)).xy;\n",
                               args.fTransformedCoords[0].fMatrixCode.c_str());
         }
     }
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 50d20c1..b9d3e6a 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -937,8 +937,10 @@
             SkRect correctedDomain;
             compute_domain(Domain::kYes, filter, kTopLeft_GrSurfaceOrigin, *domain,
                            1.f, 1.f, proxy->height(), &correctedDomain);
-            fp = GrTextureDomainEffect::Make(sk_ref_sp(proxy), srcColorType, SkMatrix::I(),
-                                             correctedDomain, GrTextureDomain::kClamp_Mode, filter);
+            fp = GrSimpleTextureEffect::Make(sk_ref_sp(proxy), srcColorType, SkMatrix::I(), filter);
+            bool filterIfDecal = GrDomainEffect::DecalFilterFromSamplerFilter(filter);
+            fp = GrDomainEffect::Make(std::move(fp), SkMatrix::I(), correctedDomain,
+                                      GrTextureDomain::kClamp_Mode, filterIfDecal);
         } else {
             fp = GrSimpleTextureEffect::Make(sk_ref_sp(proxy), srcColorType, SkMatrix::I(), filter);
         }
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
index 8b30f50..ee3c21a 100755
--- a/src/shaders/SkImageShader.cpp
+++ b/src/shaders/SkImageShader.cpp
@@ -250,8 +250,12 @@
         if (domainX != GrTextureDomain::kIgnore_Mode || domainY != GrTextureDomain::kIgnore_Mode) {
             SkRect domain = GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(proxy->dimensions()),
                                                              domainX, domainY);
-            inner = GrTextureDomainEffect::Make(std::move(proxy), srcColorType, lmInverse, domain,
-                                                domainX, domainY, samplerState);
+            inner = GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, SkMatrix::I(),
+                                                samplerState);
+            bool filterIfDecal =
+                    GrDomainEffect::DecalFilterFromSamplerFilter(samplerState.filter());
+            inner = GrDomainEffect::Make(std::move(inner), lmInverse, domain,
+                                         GrTextureDomain::kClamp_Mode, filterIfDecal);
         } else {
             inner = GrSimpleTextureEffect::Make(std::move(proxy), srcColorType, lmInverse,
                                                 samplerState);