Reland "Add support for SkCanvas::kStrict_SrcRectConstraint to GrTextureOp."

This is a reland of a0047bcff704a9121a6d82a6f97d6124463a2e54

Original change's description:
> Add support for SkCanvas::kStrict_SrcRectConstraint to GrTextureOp.
> 
> Change-Id: I8faa2838b3110b8080ac48bbe223240e3fb22998
> Reviewed-on: https://skia-review.googlesource.com/129762
> Reviewed-by: Brian Osman <brianosman@google.com>
> Commit-Queue: Brian Salomon <bsalomon@google.com>

Change-Id: Icbad064ae2779f4e140a55e520c0034cd1d17477
Reviewed-on: https://skia-review.googlesource.com/130305
Reviewed-by: Stephen White <senorblanco@chromium.org>
diff --git a/gm/perspimages.cpp b/gm/perspimages.cpp
index a4242cf..b4ad5eb 100644
--- a/gm/perspimages.cpp
+++ b/gm/perspimages.cpp
@@ -28,7 +28,7 @@
 protected:
     SkString onShortName() override { return SkString("persp_images"); }
 
-    SkISize onISize() override { return SkISize::Make(1150, 880); }
+    SkISize onISize() override { return SkISize::Make(1150, 1280); }
 
     void onOnceBeforeDraw() override {
         fImages.push_back(make_image1());
@@ -60,7 +60,13 @@
         }
         canvas->translate(-bounds.fLeft + 10.f, -bounds.fTop + 10.f);
         canvas->save();
-        for (auto subrect : {false, true}) {
+        enum class DrawType {
+            kDrawImage,
+            kDrawImageRectStrict,
+            kDrawImageRectFast,
+        };
+        for (auto type :
+             {DrawType::kDrawImage, DrawType::kDrawImageRectStrict, DrawType::kDrawImageRectFast}) {
             for (const auto& m : matrices) {
                 for (auto aa : {false, true}) {
                     paint.setAntiAlias(aa);
@@ -70,14 +76,22 @@
                             paint.setFilterQuality(filter);
                             canvas->save();
                             canvas->concat(m);
-                            if (subrect) {
-                                SkRect src = {img->width() / 4.f, img->height() / 4.f,
-                                              3.f * img->width() / 4.f, 3.f * img->height() / 4};
-                                SkRect dst = {0, 0,
-                                              3.f / 4.f * img->width(), 3.f / 4.f * img->height()};
-                                canvas->drawImageRect(img, src, dst, &paint);
-                            } else {
-                                canvas->drawImage(img, 0, 0, &paint);
+                            SkRect src = {img->width() / 4.f, img->height() / 4.f,
+                                          3.f * img->width() / 4.f, 3.f * img->height() / 4};
+                            SkRect dst = {0, 0,
+                                          3.f / 4.f * img->width(), 3.f / 4.f * img->height()};
+                            switch (type) {
+                                case DrawType::kDrawImage:
+                                    canvas->drawImage(img, 0, 0, &paint);
+                                    break;
+                                case DrawType::kDrawImageRectStrict:
+                                    canvas->drawImageRect(img, src, dst, &paint,
+                                                          SkCanvas::kStrict_SrcRectConstraint);
+                                    break;
+                                case DrawType::kDrawImageRectFast:
+                                    canvas->drawImageRect(img, src, dst, &paint,
+                                                          SkCanvas::kFast_SrcRectConstraint);
+                                    break;
                             }
                             canvas->restore();
                             ++n;
diff --git a/src/gpu/GrQuad.h b/src/gpu/GrQuad.h
index 74e2356..4a104aa 100644
--- a/src/gpu/GrQuad.h
+++ b/src/gpu/GrQuad.h
@@ -64,7 +64,7 @@
 
     SkPoint3 point(int i) const { return {fX[i], fY[i], fW[i]}; }
 
-    SkRect bounds() {
+    SkRect bounds() const {
         auto x = this->x4f() * this->iw4f();
         auto y = this->y4f() * this->iw4f();
         return {x.min(), y.min(), x.max(), y.max()};
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 4d74ce4..f4db697 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -766,6 +766,7 @@
 void GrRenderTargetContext::drawTexture(const GrClip& clip, sk_sp<GrTextureProxy> proxy,
                                         GrSamplerState::Filter filter, GrColor color,
                                         const SkRect& srcRect, const SkRect& dstRect, GrAA aa,
+                                        SkCanvas::SrcRectConstraint constraint,
                                         const SkMatrix& viewMatrix,
                                         sk_sp<GrColorSpaceXform> colorSpaceXform) {
     ASSERT_SINGLE_OWNER
@@ -783,9 +784,9 @@
     }
     GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
     bool allowSRGB = SkToBool(this->colorSpaceInfo().colorSpace());
-    this->addDrawOp(
-            clip, GrTextureOp::Make(std::move(proxy), filter, color, clippedSrcRect, clippedDstRect,
-                                    aaType, viewMatrix, std::move(colorSpaceXform), allowSRGB));
+    this->addDrawOp(clip, GrTextureOp::Make(std::move(proxy), filter, color, clippedSrcRect,
+                                            clippedDstRect, aaType, constraint, viewMatrix,
+                                            std::move(colorSpaceXform), allowSRGB));
 }
 
 void GrRenderTargetContext::fillRectWithLocalMatrix(const GrClip& clip,
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index b237d48..acbf963 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -150,7 +150,8 @@
      */
     void drawTexture(const GrClip& clip, sk_sp<GrTextureProxy>, GrSamplerState::Filter, GrColor,
                      const SkRect& srcRect, const SkRect& dstRect, GrAA aa,
-                     const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform>);
+                     SkCanvas::SrcRectConstraint, const SkMatrix& viewMatrix,
+                     sk_sp<GrColorSpaceXform>);
 
     /**
      * Draw a roundrect using a paint.
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index 70c89b5..9647281 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -87,20 +87,19 @@
 }
 
 /**
- * Checks whether the paint, matrix, and constraint are compatible with using
- * GrRenderTargetContext::drawTexture. It is more efficient than the GrTextureProducer
- * general case.
+ * Checks whether the paint is compatible with using GrRenderTargetContext::drawTexture. It is more
+ * efficient than the GrTextureProducer general case.
  */
-static bool can_use_draw_texture(const SkPaint& paint, GrAA aa, const SkMatrix& ctm,
-                                 SkCanvas::SrcRectConstraint constraint) {
+static bool can_use_draw_texture(const SkPaint& paint) {
     return (!paint.getColorFilter() && !paint.getShader() && !paint.getMaskFilter() &&
             !paint.getImageFilter() && paint.getFilterQuality() < kMedium_SkFilterQuality &&
-            paint.getBlendMode() == SkBlendMode::kSrcOver &&
-            SkCanvas::kFast_SrcRectConstraint == constraint);
+            paint.getBlendMode() == SkBlendMode::kSrcOver);
 }
 
 static void draw_texture(const SkPaint& paint, const SkMatrix& ctm, const SkRect* src,
-                         const SkRect* dst, GrAA aa, sk_sp<GrTextureProxy> proxy,
+                         const SkRect* dst, GrAA aa, SkCanvas::SrcRectConstraint constraint,
+                         sk_sp<GrTextureProxy> proxy,
+
                          SkColorSpace* colorSpace, const GrClip& clip, GrRenderTargetContext* rtc) {
     SkASSERT(!(SkToBool(src) && !SkToBool(dst)));
     SkRect srcRect = src ? *src : SkRect::MakeWH(proxy->width(), proxy->height());
@@ -129,7 +128,7 @@
     GrColor color = GrPixelConfigIsAlphaOnly(proxy->config())
                             ? SkColorToPremulGrColor(paint.getColor())
                             : SkColorAlphaToGrColor(paint.getColor());
-    rtc->drawTexture(clip, std::move(proxy), filter, color, srcRect, dstRect, aa, ctm,
+    rtc->drawTexture(clip, std::move(proxy), filter, color, srcRect, dstRect, aa, constraint, ctm,
                      std::move(csxf));
 }
 
@@ -141,9 +140,9 @@
                                          SkCanvas::SrcRectConstraint constraint,
                                          const SkMatrix& viewMatrix, const SkPaint& paint) {
     GrAA aa = GrAA(paint.isAntiAlias());
-    if (can_use_draw_texture(paint, aa, this->ctm(), constraint)) {
-        draw_texture(paint, viewMatrix, srcRect, dstRect, aa, std::move(proxy), colorSpace,
-                     this->clip(), fRenderTargetContext.get());
+    if (can_use_draw_texture(paint)) {
+        draw_texture(paint, viewMatrix, srcRect, dstRect, aa, constraint, std::move(proxy),
+                     colorSpace, this->clip(), fRenderTargetContext.get());
         return;
     }
     GrTextureAdjuster adjuster(this->context(), std::move(proxy), alphaType, pinnedUniqueID,
@@ -156,7 +155,7 @@
                                    SkCanvas::SrcRectConstraint constraint,
                                    const SkMatrix& viewMatrix, const SkPaint& paint) {
     GrAA aa = GrAA(paint.isAntiAlias());
-    if (can_use_draw_texture(paint, aa, viewMatrix, constraint)) {
+    if (can_use_draw_texture(paint)) {
         sk_sp<SkColorSpace> cs;
         // We've done enough checks above to allow us to pass ClampNearest() and not check for
         // scaling adjustments.
@@ -166,8 +165,8 @@
         if (!proxy) {
             return;
         }
-        draw_texture(paint, viewMatrix, srcRect, dstRect, aa, std::move(proxy), cs.get(),
-                     this->clip(), fRenderTargetContext.get());
+        draw_texture(paint, viewMatrix, srcRect, dstRect, aa, constraint, std::move(proxy),
+                     cs.get(), this->clip(), fRenderTargetContext.get());
         return;
     }
     this->drawTextureProducer(maker, srcRect, dstRect, constraint, viewMatrix, paint);
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index a2cd4eb..933a173 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -33,6 +33,8 @@
 
 enum class MultiTexture : bool { kNo = false, kYes = true };
 
+enum class Domain : bool { kNo = false, kYes = true };
+
 /**
  * Geometry Processor that draws a texture modulated by a vertex color (though, this is meant to be
  * the same value across all vertices of a quad and uses flat interpolation when available). This is
@@ -58,19 +60,30 @@
         int fTextureIdx;
     };
 
-    template <typename Pos, MultiTexture MT, GrAA> struct OptionalAAVertex;
+    template <typename Pos, MultiTexture MT, Domain D> struct OptionalDomainVertex;
     template <typename Pos, MultiTexture MT>
-    struct OptionalAAVertex<Pos, MT, GrAA::kNo> : OptionalMultiTextureVertex<Pos, MT> {
-        static constexpr GrAA kAA = GrAA::kNo;
+    struct OptionalDomainVertex<Pos, MT, Domain::kNo> : OptionalMultiTextureVertex<Pos, MT> {
+        static constexpr Domain kDomain = Domain::kNo;
     };
     template <typename Pos, MultiTexture MT>
-    struct OptionalAAVertex<Pos, MT, GrAA::kYes> : OptionalMultiTextureVertex<Pos, MT> {
+    struct OptionalDomainVertex<Pos, MT, Domain::kYes> : OptionalMultiTextureVertex<Pos, MT> {
+        static constexpr Domain kDomain = Domain::kYes;
+        SkRect fTextureDomain;
+    };
+
+    template <typename Pos, MultiTexture MT, Domain D, GrAA> struct OptionalAAVertex;
+    template <typename Pos, MultiTexture MT, Domain D>
+    struct OptionalAAVertex<Pos, MT, D, GrAA::kNo> : OptionalDomainVertex<Pos, MT, D> {
+        static constexpr GrAA kAA = GrAA::kNo;
+    };
+    template <typename Pos, MultiTexture MT, Domain D>
+    struct OptionalAAVertex<Pos, MT, D, GrAA::kYes> : OptionalDomainVertex<Pos, MT, D> {
         static constexpr GrAA kAA = GrAA::kYes;
         SkPoint3 fEdges[4];
     };
 
-    template <typename Pos, MultiTexture MT, GrAA AA>
-    using Vertex = OptionalAAVertex<Pos, MT, AA>;
+    template <typename Pos, MultiTexture MT, Domain D, GrAA AA>
+    using Vertex = OptionalAAVertex<Pos, MT, D, AA>;
 
     // Maximum number of textures supported by this op. Must also be checked against the caps
     // limit. These numbers were based on some limited experiments on a HP Z840 and Pixel XL 2016
@@ -87,7 +100,8 @@
 
     static sk_sp<GrGeometryProcessor> Make(sk_sp<GrTextureProxy> proxies[], int proxyCnt,
                                            sk_sp<GrColorSpaceXform> csxf, bool coverageAA,
-                                           bool perspective, const GrSamplerState::Filter filters[],
+                                           bool perspective, Domain domain,
+                                           const GrSamplerState::Filter filters[],
                                            const GrShaderCaps& caps) {
         // We use placement new to avoid always allocating space for kMaxTextures TextureSampler
         // instances.
@@ -96,7 +110,7 @@
         void* mem = GrGeometryProcessor::operator new(size);
         return sk_sp<TextureGeometryProcessor>(
                 new (mem) TextureGeometryProcessor(proxies, proxyCnt, samplerCnt, std::move(csxf),
-                                                   coverageAA, perspective, filters, caps));
+                                                   coverageAA, perspective, domain, filters, caps));
     }
 
     ~TextureGeometryProcessor() override {
@@ -112,6 +126,7 @@
         b->add32(GrColorSpaceXform::XformKey(fColorSpaceXform.get()));
         uint32_t x = this->usesCoverageEdgeAA() ? 0 : 1;
         x |= kFloat3_GrVertexAttribType == fPositions.fType ? 0 : 2;
+        x |= fDomain.isInitialized() ? 4 : 0;
         b->add32(x);
     }
 
@@ -150,6 +165,14 @@
                 args.fFragBuilder->codeAppend("float2 texCoord;");
                 args.fVaryingHandler->addPassThroughAttribute(&textureGP.fTextureCoords,
                                                               "texCoord");
+                if (textureGP.fDomain.isInitialized()) {
+                    args.fFragBuilder->codeAppend("float4 domain;");
+                    args.fVaryingHandler->addPassThroughAttribute(
+                            &textureGP.fDomain, "domain",
+                            GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
+                    args.fFragBuilder->codeAppend(
+                            "texCoord = clamp(texCoord, domain.xy, domain.zw);");
+                }
                 if (textureGP.numTextureSamplers() > 1) {
                     // If this changes to float, reconsider Interpolation::kMustBeFlat.
                     SkASSERT(kInt_GrVertexAttribType == textureGP.fTextureIdx.fType);
@@ -275,7 +298,8 @@
 
     TextureGeometryProcessor(sk_sp<GrTextureProxy> proxies[], int proxyCnt, int samplerCnt,
                              sk_sp<GrColorSpaceXform> csxf, bool coverageAA, bool perspective,
-                             const GrSamplerState::Filter filters[], const GrShaderCaps& caps)
+                             Domain domain, const GrSamplerState::Filter filters[],
+                             const GrShaderCaps& caps)
             : INHERITED(kTextureGeometryProcessor_ClassID), fColorSpaceXform(std::move(csxf)) {
         SkASSERT(proxyCnt > 0 && samplerCnt >= proxyCnt);
         fSamplers[0].reset(std::move(proxies[0]), filters[0]);
@@ -306,7 +330,9 @@
             SkASSERT(caps.integerSupport());
             fTextureIdx = this->addVertexAttrib("textureIdx", kInt_GrVertexAttribType);
         }
-
+        if (domain == Domain::kYes) {
+            fDomain = this->addVertexAttrib("domain", kFloat4_GrVertexAttribType);
+        }
         if (coverageAA) {
             fAAEdges[0] = this->addVertexAttrib("aaEdge0", kFloat3_GrVertexAttribType);
             fAAEdges[1] = this->addVertexAttrib("aaEdge1", kFloat3_GrVertexAttribType);
@@ -319,6 +345,7 @@
     Attribute fColors;
     Attribute fTextureCoords;
     Attribute fTextureIdx;
+    Attribute fDomain;
     Attribute fAAEdges[4];
     sk_sp<GrColorSpaceXform> fColorSpaceXform;
     TextureSampler fSamplers[1];
@@ -509,12 +536,52 @@
 template <typename V> struct TexIdAssigner<V, MultiTexture::kNo> {
     static void Assign(V* vertices, int textureIdx) {}
 };
+
+template <typename V, Domain D = V::kDomain> struct DomainAssigner;
+
+template <typename V> struct DomainAssigner<V, Domain::kYes> {
+    static void Assign(V* vertices, Domain domain, GrSamplerState::Filter filter,
+                       const SkRect& srcRect, GrSurfaceOrigin origin, float iw, float ih) {
+        static constexpr SkRect kLargeRect = {-2, -2, 2, 2};
+        SkRect domainRect;
+        if (domain == Domain::kYes) {
+            auto ltrb = Sk4f::Load(&srcRect);
+            if (filter == GrSamplerState::Filter::kBilerp) {
+                auto rblt = SkNx_shuffle<2, 3, 0, 1>(ltrb);
+                auto whwh = (rblt - ltrb).abs();
+                auto c = (rblt + ltrb) * 0.5f;
+                static const Sk4f kOffsets = {0.5f, 0.5f, -0.5f, -0.5f};
+                ltrb = (whwh < 1.f).thenElse(c, ltrb + kOffsets);
+            }
+            ltrb *= Sk4f(iw, ih, iw, ih);
+            if (origin == kBottomLeft_GrSurfaceOrigin) {
+                static const Sk4f kMul = {1.f, -1.f, 1.f, -1.f};
+                static const Sk4f kAdd = {0.f, 1.f, 0.f, 1.f};
+                ltrb = SkNx_shuffle<0, 3, 2, 1>(kMul * ltrb + kAdd);
+            }
+            ltrb.store(&domainRect);
+        } else {
+            domainRect = kLargeRect;
+        }
+        for (int i = 0; i < 4; ++i) {
+            vertices[i].fTextureDomain = domainRect;
+        }
+    }
+};
+
+template <typename V> struct DomainAssigner<V, Domain::kNo> {
+    static void Assign(V*, Domain domain, GrSamplerState::Filter, const SkRect&, GrSurfaceOrigin,
+                       float iw, float ih) {
+        SkASSERT(domain == Domain::kNo);
+    }
+};
+
 }  // anonymous namespace
 
 template <typename V>
 static void tessellate_quad(const GrPerspQuad& devQuad, const SkRect& srcRect, GrColor color,
-                            GrSurfaceOrigin origin, V* vertices, SkScalar iw, SkScalar ih,
-                            int textureIdx) {
+                            GrSurfaceOrigin origin, GrSamplerState::Filter filter, V* vertices,
+                            SkScalar iw, SkScalar ih, int textureIdx, Domain domain) {
     SkRect texRect = {
             iw * srcRect.fLeft,
             ih * srcRect.fTop,
@@ -531,6 +598,7 @@
     vertices[2].fColor = color;
     vertices[3].fColor = color;
     TexIdAssigner<V>::Assign(vertices, textureIdx);
+    DomainAssigner<V>::Assign(vertices, domain, filter, srcRect, origin, iw, ih);
 }
 
 /**
@@ -542,11 +610,12 @@
     static std::unique_ptr<GrDrawOp> Make(sk_sp<GrTextureProxy> proxy,
                                           GrSamplerState::Filter filter, GrColor color,
                                           const SkRect& srcRect, const SkRect& dstRect,
-                                          GrAAType aaType, const SkMatrix& viewMatrix,
-                                          sk_sp<GrColorSpaceXform> csxf, bool allowSRBInputs) {
+                                          GrAAType aaType, SkCanvas::SrcRectConstraint constraint,
+                                          const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> csxf,
+                                          bool allowSRBInputs) {
         return std::unique_ptr<GrDrawOp>(new TextureOp(std::move(proxy), filter, color, srcRect,
-                                                       dstRect, aaType, viewMatrix, std::move(csxf),
-                                                       allowSRBInputs));
+                                                       dstRect, aaType, constraint, viewMatrix,
+                                                       std::move(csxf), allowSRBInputs));
     }
 
     ~TextureOp() override {
@@ -587,11 +656,11 @@
             str.appendf(
                     "%d: Color: 0x%08x, ProxyIdx: %d, TexRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f] "
                     "Quad [(%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f)]\n",
-                    i, draw.fColor, draw.fTextureIdx, draw.fSrcRect.fLeft, draw.fSrcRect.fTop,
-                    draw.fSrcRect.fRight, draw.fSrcRect.fBottom, draw.fQuad.point(0).fX,
-                    draw.fQuad.point(0).fY, draw.fQuad.point(1).fX, draw.fQuad.point(1).fY,
-                    draw.fQuad.point(2).fX, draw.fQuad.point(2).fY, draw.fQuad.point(3).fX,
-                    draw.fQuad.point(3).fY);
+                    i, draw.color(), draw.textureIdx(), draw.srcRect().fLeft, draw.srcRect().fTop,
+                    draw.srcRect().fRight, draw.srcRect().fBottom, draw.quad().point(0).fX,
+                    draw.quad().point(0).fY, draw.quad().point(1).fX, draw.quad().point(1).fY,
+                    draw.quad().point(2).fX, draw.quad().point(2).fY, draw.quad().point(3).fX,
+                    draw.quad().point(3).fY);
         }
         str += INHERITED::dumpInfo();
         return str;
@@ -629,7 +698,8 @@
 
     TextureOp(sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter, GrColor color,
               const SkRect& srcRect, const SkRect& dstRect, GrAAType aaType,
-              const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> csxf, bool allowSRGBInputs)
+              SkCanvas::SrcRectConstraint constraint, const SkMatrix& viewMatrix,
+              sk_sp<GrColorSpaceXform> csxf, bool allowSRGBInputs)
             : INHERITED(ClassID())
             , fColorSpaceXform(std::move(csxf))
             , fProxy0(proxy.release())
@@ -639,29 +709,36 @@
             , fFinalized(0)
             , fAllowSRGBInputs(allowSRGBInputs ? 1 : 0) {
         SkASSERT(aaType != GrAAType::kMixedSamples);
-        Draw& draw = fDraws.push_back();
-        draw.fSrcRect = srcRect;
-        draw.fTextureIdx = 0;
-        draw.fColor = color;
+        // No need to use a texture domain with nearest filtering unless there is AA bloating.
+        if (constraint == SkCanvas::kStrict_SrcRectConstraint &&
+            filter == GrSamplerState::Filter::kNearest && GrAAType::kCoverage != aaType) {
+            constraint = SkCanvas::kFast_SrcRectConstraint;
+        }
+
+        const Draw& draw = fDraws.emplace_back(srcRect, 0, GrPerspQuad(dstRect, viewMatrix),
+                                               constraint, color);
         fPerspective = viewMatrix.hasPerspective();
+        fDomain = (bool)draw.domain();
         SkRect bounds;
-        draw.fQuad = GrPerspQuad(dstRect, viewMatrix);
-        bounds = draw.fQuad.bounds();
+        bounds = draw.quad().bounds();
         this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
 
         fMaxApproxDstPixelArea = RectSizeAsSizeT(bounds);
     }
 
-    template <typename Pos, MultiTexture MT, GrAA AA>
+    template <typename Pos, MultiTexture MT, Domain D, GrAA AA>
     void tess(void* v, const float iw[], const float ih[], const GrGeometryProcessor* gp) {
-        using Vertex = TextureGeometryProcessor::Vertex<Pos, MT, AA>;
+        using Vertex = TextureGeometryProcessor::Vertex<Pos, MT, D, AA>;
         SkASSERT(gp->getVertexStride() == sizeof(Vertex));
         auto vertices = static_cast<Vertex*>(v);
         auto proxies = this->proxies();
+        auto filters = this->filters();
         for (const auto& draw : fDraws) {
-            auto origin = proxies[draw.fTextureIdx]->origin();
-            tessellate_quad<Vertex>(draw.fQuad, draw.fSrcRect, draw.fColor, origin, vertices,
-                                    iw[draw.fTextureIdx], ih[draw.fTextureIdx], draw.fTextureIdx);
+            auto textureIdx = draw.textureIdx();
+            auto origin = proxies[textureIdx]->origin();
+            tessellate_quad<Vertex>(draw.quad(), draw.srcRect(), draw.color(), origin,
+                                    filters[textureIdx], vertices, iw[textureIdx], ih[textureIdx],
+                                    textureIdx, draw.domain());
             vertices += 4;
         }
     }
@@ -677,10 +754,11 @@
             proxiesSPs[i] = sk_ref_sp(proxies[i]);
         }
 
+        Domain domain = fDomain ? Domain::kYes : Domain::kNo;
         bool coverageAA = GrAAType::kCoverage == this->aaType();
         sk_sp<GrGeometryProcessor> gp = TextureGeometryProcessor::Make(
                 proxiesSPs, fProxyCnt, std::move(fColorSpaceXform), coverageAA, fPerspective,
-                filters, *target->caps().shaderCaps());
+                domain, filters, *target->caps().shaderCaps());
         GrPipeline::InitArgs args;
         args.fProxy = target->proxy();
         args.fCaps = &target->caps();
@@ -712,35 +790,33 @@
             ih[t] = 1.f / texture->height();
         }
 
-        if (fPerspective) {
-            if (fProxyCnt > 1) {
-                if (coverageAA) {
-                    this->tess<SkPoint3, MultiTexture::kYes, GrAA::kYes>(vdata, iw, ih, gp.get());
-                } else {
-                    this->tess<SkPoint3, MultiTexture::kYes, GrAA::kNo>(vdata, iw, ih, gp.get());
-                }
-            } else {
-                if (coverageAA) {
-                    this->tess<SkPoint3, MultiTexture::kNo, GrAA::kYes>(vdata, iw, ih, gp.get());
-                } else {
-                    this->tess<SkPoint3, MultiTexture::kNo, GrAA::kNo>(vdata, iw, ih, gp.get());
-                }
-            }
-        } else {
-            if (fProxyCnt > 1) {
-                if (coverageAA) {
-                    this->tess<SkPoint, MultiTexture::kYes, GrAA::kYes>(vdata, iw, ih, gp.get());
-                } else {
-                    this->tess<SkPoint, MultiTexture::kYes, GrAA::kNo>(vdata, iw, ih, gp.get());
-                }
-            } else {
-                if (coverageAA) {
-                    this->tess<SkPoint, MultiTexture::kNo, GrAA::kYes>(vdata, iw, ih, gp.get());
-                } else {
-                    this->tess<SkPoint, MultiTexture::kNo, GrAA::kNo>(vdata, iw, ih, gp.get());
-                }
-            }
-        }
+        using TessFn =
+                decltype(&TextureOp::tess<SkPoint, MultiTexture::kNo, Domain::kNo, GrAA::kNo>);
+        static constexpr TessFn kTessFns[] = {
+                &TextureOp::tess<SkPoint,  MultiTexture::kNo,  Domain::kNo,  GrAA::kNo>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kNo,  Domain::kNo,  GrAA::kYes>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kNo,  Domain::kYes, GrAA::kNo>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kNo,  Domain::kYes, GrAA::kYes>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kYes, Domain::kNo,  GrAA::kNo>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kYes, Domain::kNo,  GrAA::kYes>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kYes, Domain::kYes, GrAA::kNo>,
+                &TextureOp::tess<SkPoint,  MultiTexture::kYes, Domain::kYes, GrAA::kYes>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kNo,  Domain::kNo,  GrAA::kNo>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kNo,  Domain::kNo,  GrAA::kYes>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kNo,  Domain::kYes, GrAA::kNo>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kNo,  Domain::kYes, GrAA::kYes>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kYes, Domain::kNo,  GrAA::kNo>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kYes, Domain::kNo,  GrAA::kYes>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kYes, Domain::kYes, GrAA::kNo>,
+                &TextureOp::tess<SkPoint3, MultiTexture::kYes, Domain::kYes, GrAA::kYes>,
+        };
+        int tessFnIdx = 0;
+        tessFnIdx |= coverageAA      ? 0x1 : 0x0;
+        tessFnIdx |= fDomain         ? 0x2 : 0x0;
+        tessFnIdx |= (fProxyCnt > 1) ? 0x4 : 0x0;
+        tessFnIdx |= fPerspective    ? 0x8 : 0x0;
+        (this->*(kTessFns[tessFnIdx]))(vdata, iw, ih, gp.get());
+
         GrPrimitiveType primitiveType =
                 fDraws.count() > 1 ? GrPrimitiveType::kTriangles : GrPrimitiveType::kTriangleStrip;
         GrMesh mesh(primitiveType);
@@ -806,7 +882,7 @@
             int firstNewDraw = fDraws.count();
             fDraws.push_back_n(that->fDraws.count(), that->fDraws.begin());
             for (int i = firstNewDraw; i < fDraws.count(); ++i) {
-                fDraws[i].fTextureIdx = map[fDraws[i].fTextureIdx];
+                fDraws[i].setTextureIdx(map[fDraws[i].textureIdx()]);
             }
         } else {
             // We can get here when one of the ops is already multitextured but the other cannot
@@ -822,6 +898,7 @@
         this->joinBounds(*that);
         fMaxApproxDstPixelArea = SkTMax(that->fMaxApproxDstPixelArea, fMaxApproxDstPixelArea);
         fPerspective |= that->fPerspective;
+        fDomain |= that->fDomain;
         return true;
     }
 
@@ -894,9 +971,26 @@
         return &fFilter0;
     }
 
-    struct Draw {
+    class Draw {
+    public:
+        Draw(const SkRect& srcRect, int textureIdx, const GrPerspQuad& quad,
+             SkCanvas::SrcRectConstraint constraint, GrColor color)
+                : fSrcRect(srcRect)
+                , fHasDomain(constraint == SkCanvas::kStrict_SrcRectConstraint)
+                , fTextureIdx(SkToUInt(textureIdx))
+                , fQuad(quad)
+                , fColor(color) {}
+        const GrPerspQuad& quad() const { return fQuad; }
+        int textureIdx() const { return SkToInt(fTextureIdx); }
+        const SkRect& srcRect() const { return fSrcRect; }
+        GrColor color() const { return fColor; }
+        Domain domain() const { return Domain(fHasDomain); }
+        void setTextureIdx(int i) { fTextureIdx = SkToUInt(i); }
+
+    private:
         SkRect fSrcRect;
-        int fTextureIdx;
+        unsigned fHasDomain : 1;
+        unsigned fTextureIdx : 31;
         GrPerspQuad fQuad;
         GrColor fColor;
     };
@@ -914,6 +1008,7 @@
     uint8_t fProxyCnt;
     unsigned fAAType : 2;
     unsigned fPerspective : 1;
+    unsigned fDomain : 1;
     // Used to track whether fProxy is ref'ed or has a pending IO after finalize() is called.
     unsigned fFinalized : 1;
     unsigned fAllowSRGBInputs : 1;
@@ -930,10 +1025,11 @@
 
 std::unique_ptr<GrDrawOp> Make(sk_sp<GrTextureProxy> proxy, GrSamplerState::Filter filter,
                                GrColor color, const SkRect& srcRect, const SkRect& dstRect,
-                               GrAAType aaType, const SkMatrix& viewMatrix,
-                               sk_sp<GrColorSpaceXform> csxf, bool allowSRGBInputs) {
-    return TextureOp::Make(std::move(proxy), filter, color, srcRect, dstRect, aaType, viewMatrix,
-                           std::move(csxf), allowSRGBInputs);
+                               GrAAType aaType, SkCanvas::SrcRectConstraint constraint,
+                               const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> csxf,
+                               bool allowSRGBInputs) {
+    return TextureOp::Make(std::move(proxy), filter, color, srcRect, dstRect, aaType, constraint,
+                           viewMatrix, std::move(csxf), allowSRGBInputs);
 }
 
 }  // namespace GrTextureOp
@@ -970,8 +1066,10 @@
     if (random->nextBool()) {
         aaType = (fsaaType == GrFSAAType::kUnifiedMSAA) ? GrAAType::kMSAA : GrAAType::kCoverage;
     }
-    return GrTextureOp::Make(std::move(proxy), filter, color, srcRect, rect, aaType, viewMatrix,
-                             std::move(csxf), allowSRGBInputs);
+    auto constraint = random->nextBool() ? SkCanvas::kStrict_SrcRectConstraint
+                                         : SkCanvas::kFast_SrcRectConstraint;
+    return GrTextureOp::Make(std::move(proxy), filter, color, srcRect, rect, aaType, constraint,
+                             viewMatrix, std::move(csxf), allowSRGBInputs);
 }
 
 #endif
diff --git a/src/gpu/ops/GrTextureOp.h b/src/gpu/ops/GrTextureOp.h
index a6c1a4f..d61b36e 100644
--- a/src/gpu/ops/GrTextureOp.h
+++ b/src/gpu/ops/GrTextureOp.h
@@ -8,6 +8,7 @@
 #include "GrColor.h"
 #include "GrSamplerState.h"
 #include "GrTypesPriv.h"
+#include "SkCanvas.h"
 #include "SkRefCnt.h"
 
 class GrColorSpaceXform;
@@ -17,6 +18,7 @@
 class SkMatrix;
 
 namespace GrTextureOp {
+
 /**
  * Creates an op that draws a sub-rectangle of a texture. The passed color is modulated by the
  * texture's color. 'srcRect' specifies the rectangle of the texture to draw. 'dstRect' specifies
@@ -25,6 +27,6 @@
  */
 std::unique_ptr<GrDrawOp> Make(sk_sp<GrTextureProxy>, GrSamplerState::Filter, GrColor,
                                const SkRect& srcRect, const SkRect& dstRect, GrAAType,
-                               const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform>,
-                               bool allowSRGBInputs);
+                               SkCanvas::SrcRectConstraint, const SkMatrix& viewMatrix,
+                               sk_sp<GrColorSpaceXform>, bool allowSRGBInputs);
 }