Support domain clamping and transform matrices in YUVToRGB effects

This helps avoid flattening of YUV/A images when drawn with a strict src
rect constraint. SkiaRenderer almost always provides a strict constraint
for their YUV videos.

This adds a GM that replicates the issue in skbug:8959, and adds a GM to
the wacky_yuv set that checks domain clamping across all of the different
formats.

Bug: 8959
Change-Id: I53f531a94f3b63f81d8c3cbe22d868e3356aeabd
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/207020
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index 4870f01..332abcc 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -24,6 +24,7 @@
 static const int kTileWidthHeight = 128;
 static const int kLabelWidth = 64;
 static const int kLabelHeight = 32;
+static const int kDomainPadding = 8;
 static const int kPad = 1;
 
 enum YUVFormat {
@@ -153,12 +154,14 @@
 }
 
 static SkBitmap make_bitmap(SkColorType colorType, const SkPath& path,
-                            const SkTDArray<SkRect>& circles, bool opaque) {
+                            const SkTDArray<SkRect>& circles, bool opaque, bool padWithRed) {
     const SkColor kGreen  = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 178, 240, 104));
     const SkColor kBlue   = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 173, 167, 252));
     const SkColor kYellow = ToolUtils::color_to_565(SkColorSetARGB(0xFF, 255, 221, 117));
 
-    SkImageInfo ii = SkImageInfo::Make(kTileWidthHeight, kTileWidthHeight,
+    int widthHeight = kTileWidthHeight + (padWithRed ? 2 * kDomainPadding : 0);
+
+    SkImageInfo ii = SkImageInfo::Make(widthHeight, widthHeight,
                                        colorType, kPremul_SkAlphaType);
 
     SkBitmap bm;
@@ -167,6 +170,11 @@
     std::unique_ptr<SkCanvas> canvas = SkCanvas::MakeRasterDirect(ii,
                                                                   bm.getPixels(),
                                                                   bm.rowBytes());
+    if (padWithRed) {
+        canvas->clear(SK_ColorRED);
+        canvas->translate(kDomainPadding, kDomainPadding);
+        canvas->clipRect(SkRect::MakeWH(kTileWidthHeight, kTileWidthHeight));
+    }
     canvas->clear(opaque ? kGreen : SK_ColorTRANSPARENT);
 
     SkPaint paint;
@@ -800,7 +808,9 @@
 // YV12
 class WackyYUVFormatsGM : public GM {
 public:
-    WackyYUVFormatsGM(bool useTargetColorSpace) : fUseTargetColorSpace(useTargetColorSpace) {
+    WackyYUVFormatsGM(bool useTargetColorSpace, bool useDomain)
+            : fUseTargetColorSpace(useTargetColorSpace)
+            , fUseDomain(useDomain) {
         this->setBGColor(0xFFCCCCCC);
     }
 
@@ -811,6 +821,9 @@
         if (fUseTargetColorSpace) {
             name += "_cs";
         }
+        if (fUseDomain) {
+            name += "_domain";
+        }
 
         return name;
     }
@@ -818,8 +831,9 @@
     SkISize onISize() override {
         int numCols = 2 * (kLastEnum_SkYUVColorSpace + 1); // opacity x color-space
         int numRows = 1 + (kLast_YUVFormat + 1);  // origin + # yuv formats
-        return SkISize::Make(kLabelWidth  + numCols * (kTileWidthHeight + kPad),
-                             kLabelHeight + numRows * (kTileWidthHeight + kPad));
+        int wh = SkScalarCeilToInt(kTileWidthHeight * (fUseDomain ? 1.5f : 1.f));
+        return SkISize::Make(kLabelWidth  + numCols * (wh + kPad),
+                             kLabelHeight + numRows * (wh + kPad));
     }
 
     void onOnceBeforeDraw() override {
@@ -831,14 +845,14 @@
             // transparent
             SkTDArray<SkRect> circles;
             SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 5, &circles);
-            fOriginalBMs[0] = make_bitmap(kRGBA_8888_SkColorType, path, circles, false);
+            fOriginalBMs[0] = make_bitmap(kRGBA_8888_SkColorType, path, circles, false, fUseDomain);
         }
 
         {
             // opaque
             SkTDArray<SkRect> circles;
             SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 7, &circles);
-            fOriginalBMs[1] = make_bitmap(kRGBA_8888_SkColorType, path, circles, true);
+            fOriginalBMs[1] = make_bitmap(kRGBA_8888_SkColorType, path, circles, true, fUseDomain);
         }
 
         if (fUseTargetColorSpace) {
@@ -887,6 +901,14 @@
                         }
 
                         int counterMod = counter % 3;
+                        if (format == kY410_YUVFormat && counterMod == 2) {
+                            // This format doesn't work as pixmaps
+                            counterMod = 1;
+                        } else if (fUseDomain && counterMod == 0) {
+                            // Copies flatten to RGB when they copy the YUVA data, which doesn't
+                            // know about the intended domain and the domain padding bleeds in
+                            counterMod = 1;
+                        }
                         switch (counterMod) {
                         case 0:
                             fImages[opaque][cs][format] = SkImage::MakeFromYUVATexturesCopy(
@@ -933,38 +955,52 @@
     void onDraw(SkCanvas* canvas) override {
         this->createImages(canvas->getGrContext());
 
-        int x = kLabelWidth;
+        SkRect srcRect = SkRect::MakeWH(fOriginalBMs[0].width(), fOriginalBMs[0].height());
+        SkRect dstRect = SkRect::MakeXYWH(kLabelWidth, 0.f, srcRect.width(), srcRect.height());
+
+        SkCanvas::SrcRectConstraint constraint = SkCanvas::kFast_SrcRectConstraint;
+        if (fUseDomain) {
+            srcRect.inset(kDomainPadding, kDomainPadding);
+            // Draw a larger rectangle to ensure bilerp filtering would normally read outside the
+            // srcRect and hit the red pixels, if strict constraint weren't used.
+            dstRect.fRight = kLabelWidth + 1.5f * srcRect.width();
+            dstRect.fBottom = 1.5f * srcRect.height();
+            constraint = SkCanvas::kStrict_SrcRectConstraint;
+        }
+
         for (int cs = kJPEG_SkYUVColorSpace; cs <= kLastEnum_SkYUVColorSpace; ++cs) {
             SkPaint paint;
+            paint.setFilterQuality(kLow_SkFilterQuality);
             if (kIdentity_SkYUVColorSpace == cs) {
                 // The identity color space needs post processing to appear correctly
                 paint.setColorFilter(yuv_to_rgb_colorfilter());
             }
 
             for (int opaque : { 0, 1 }) {
-                int y = kLabelHeight;
+                dstRect.offsetTo(dstRect.fLeft, kLabelHeight);
 
-                draw_col_label(canvas, x+kTileWidthHeight/2, cs, opaque);
+                draw_col_label(canvas, dstRect.fLeft + dstRect.height() / 2, cs, opaque);
 
-                canvas->drawBitmap(fOriginalBMs[opaque], x, y);
-                y += kTileWidthHeight + kPad;
+                canvas->drawBitmapRect(fOriginalBMs[opaque], srcRect, dstRect, nullptr, constraint);
+                dstRect.offset(0.f, dstRect.height() + kPad);
 
                 for (int format = kAYUV_YUVFormat; format <= kLast_YUVFormat; ++format) {
-                    draw_row_label(canvas, y, format);
+                    draw_row_label(canvas, dstRect.fTop, format);
                     if (fUseTargetColorSpace && fImages[opaque][cs][format]) {
                         // Making a CS-specific version of a kIdentity_SkYUVColorSpace YUV image
                         // doesn't make a whole lot of sense. The colorSpace conversion will
                         // operate on the YUV components rather than the RGB components.
                         sk_sp<SkImage> csImage =
                             fImages[opaque][cs][format]->makeColorSpace(fTargetColorSpace);
-                        canvas->drawImage(csImage, x, y, &paint);
+                        canvas->drawImageRect(csImage, srcRect, dstRect, &paint, constraint);
                     } else {
-                        canvas->drawImage(fImages[opaque][cs][format], x, y, &paint);
+                        canvas->drawImageRect(fImages[opaque][cs][format], srcRect, dstRect, &paint,
+                                              constraint);
                     }
-                    y += kTileWidthHeight + kPad;
+                    dstRect.offset(0.f, dstRect.height() + kPad);
                 }
 
-                x += kTileWidthHeight + kPad;
+                dstRect.offset(dstRect.width() + kPad, 0.f);
             }
         }
         if (auto context = canvas->getGrContext()) {
@@ -987,6 +1023,7 @@
     sk_sp<SkImage>             fImages[2][kLastEnum_SkYUVColorSpace + 1][kLast_YUVFormat + 1];
     SkTArray<GrBackendTexture> fBackendTextures;
     bool                       fUseTargetColorSpace;
+    bool                       fUseDomain;
     sk_sp<SkColorSpace>        fTargetColorSpace;
 
     typedef GM INHERITED;
@@ -994,8 +1031,9 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-DEF_GM(return new WackyYUVFormatsGM(false);)
-DEF_GM(return new WackyYUVFormatsGM(true);)
+DEF_GM(return new WackyYUVFormatsGM(/* cs */ false, /* domain */ false);)
+DEF_GM(return new WackyYUVFormatsGM(/* cs */ true,  /* domain */ false);)
+DEF_GM(return new WackyYUVFormatsGM(/* cs */ false, /* domain */ true);)
 
 class YUVMakeColorSpaceGM : public GpuGM {
 public:
@@ -1024,14 +1062,14 @@
             // transparent
             SkTDArray<SkRect> circles;
             SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 5, &circles);
-            fOriginalBMs[0] = make_bitmap(kN32_SkColorType, path, circles, false);
+            fOriginalBMs[0] = make_bitmap(kN32_SkColorType, path, circles, false, false);
         }
 
         {
             // opaque
             SkTDArray<SkRect> circles;
             SkPath path = create_splat(origin, innerRadius, outerRadius, 1.0f, 7, &circles);
-            fOriginalBMs[1] = make_bitmap(kN32_SkColorType, path, circles, true);
+            fOriginalBMs[1] = make_bitmap(kN32_SkColorType, path, circles, true, false);
         }
 
         fTargetColorSpace = SkColorSpace::MakeSRGB()->makeColorSpin();
diff --git a/gm/yuvtorgbeffect.cpp b/gm/yuvtorgbeffect.cpp
index fe8d127..30d9129 100644
--- a/gm/yuvtorgbeffect.cpp
+++ b/gm/yuvtorgbeffect.cpp
@@ -9,6 +9,7 @@
 
 #include "gm.h"
 
+#include "GrClip.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrProxyProvider.h"
@@ -249,4 +250,115 @@
 };
 
 DEF_GM(return new YUVNV12toRGBEffect;)
+
+//////////////////////////////////////////////////////////////////////////////
+
+// This GM tests domain clamping on YUV multiplanar images where the U and V
+// planes have different resolution from Y. See skbug:8959
+
+class YUVtoRGBDomainEffect : public GpuGM {
+public:
+    YUVtoRGBDomainEffect() {
+        this->setBGColor(0xFFFFFFFF);
+    }
+
+protected:
+    SkString onShortName() override {
+        return SkString("yuv_to_rgb_domain_effect");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make((YSIZE + kTestPad) * 3 + kDrawPad, (YSIZE + kTestPad) * 2 + kDrawPad);
+    }
+
+    void onOnceBeforeDraw() override {
+        SkBitmap bmp[3];
+        SkImageInfo yinfo = SkImageInfo::MakeA8(YSIZE, YSIZE);
+        bmp[0].allocPixels(yinfo);
+        SkImageInfo uinfo = SkImageInfo::MakeA8(USIZE, USIZE);
+        bmp[1].allocPixels(uinfo);
+        SkImageInfo vinfo = SkImageInfo::MakeA8(VSIZE, VSIZE);
+        bmp[2].allocPixels(vinfo);
+
+        int innerColor[] = {149, 43, 21};
+        int outerColor[] = {128, 128, 128};
+        for (int i = 0; i < 3; ++i) {
+            bmp[i].eraseColor(SkColorSetARGB(outerColor[i], 0, 0, 0));
+            SkIRect innerRect = i == 0 ? SkIRect::MakeLTRB(2, 2, 6, 6) : SkIRect::MakeLTRB(1, 1, 3, 3);
+            bmp[i].erase(SkColorSetARGB(innerColor[i], 0, 0, 0), innerRect);
+            fImage[i] = SkImage::MakeFromBitmap(bmp[i]);
+        }
+    }
+
+    DrawResult onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
+                      SkCanvas* canvas, SkString* errorMsg) override {
+        GrProxyProvider* proxyProvider = context->priv().proxyProvider();
+        sk_sp<GrTextureProxy> proxies[3];
+
+        for (int i = 0; i < 3; ++i) {
+            proxies[i] = proxyProvider->createTextureProxy(fImage[i], kNone_GrSurfaceFlags, 1,
+                                                           SkBudgeted::kYes, SkBackingFit::kExact);
+            if (!proxies[i]) {
+                *errorMsg = "Failed to create proxy";
+                return DrawResult::kFail;
+            }
+        }
+
+        // Draw a 2x2 grid of the YUV images.
+        // Rows = kNearest, kBilerp, Cols = No clamp, clamp
+        static const GrSamplerState::Filter kFilters[] = {
+                GrSamplerState::Filter::kNearest, GrSamplerState::Filter::kBilerp };
+        static const SkRect kGreenRect = SkRect::MakeLTRB(2.f, 2.f, 6.f, 6.f);
+
+        SkYUVAIndex yuvaIndices[4] = {
+            { SkYUVAIndex::kY_Index, SkColorChannel::kR },
+            { SkYUVAIndex::kU_Index, SkColorChannel::kR },
+            { SkYUVAIndex::kV_Index, SkColorChannel::kR },
+            { -1, SkColorChannel::kA }
+        };
+        SkRect rect = SkRect::MakeWH(YSIZE, YSIZE);
+
+        SkScalar y = kDrawPad + kTestPad;
+        for (uint32_t i = 0; i < SK_ARRAY_COUNT(kFilters); ++i) {
+            SkScalar x = kDrawPad + kTestPad;
+
+            for (uint32_t j = 0; j < 2; ++j) {
+                SkMatrix ctm = SkMatrix::MakeTrans(x, y);
+                ctm.postScale(10.f, 10.f);
+
+                SkRect domain = kGreenRect;
+                if (kFilters[i] == GrSamplerState::Filter::kNearest) {
+                    // Make a very small inset for nearest-neighbor filtering so that 0.5px
+                    // centers don't round out beyond the green pixels.
+                    domain.inset(0.01f, 0.01f);
+                }
+
+                const SkRect* domainPtr = j > 0 ? &domain : nullptr;
+                std::unique_ptr<GrFragmentProcessor> fp(GrYUVtoRGBEffect::Make(proxies, yuvaIndices,
+                        kJPEG_SkYUVColorSpace, kFilters[i], SkMatrix::I(), domainPtr));
+                if (fp) {
+                    GrPaint grPaint;
+                    grPaint.addColorFragmentProcessor(std::move(fp));
+                    renderTargetContext->drawRect(
+                            GrNoClip(), std::move(grPaint), GrAA::kYes, ctm, rect);
+                }
+                x += rect.width() + kTestPad;
+            }
+
+            y += rect.height() + kTestPad;
+        }
+
+        return DrawResult::kOk;
+     }
+
+private:
+    sk_sp<SkImage> fImage[3];
+
+    static constexpr SkScalar kDrawPad = 10.f;
+    static constexpr SkScalar kTestPad = 10.f;
+
+    typedef GM INHERITED;
+};
+
+DEF_GM(return new YUVtoRGBDomainEffect;)
 }
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
index 37c0a27..9f092dc 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
@@ -272,6 +272,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "wacky_yuv_formats_domain",
+      "serialize-8888",
+      "gm",
+      "_",
       "bitmapfilters",
       "serialize-8888",
       "gm",
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json
index 9f08685..913aa19 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json
@@ -348,6 +348,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "wacky_yuv_formats_domain",
+      "serialize-8888",
+      "gm",
+      "_",
       "bitmapfilters",
       "serialize-8888",
       "gm",
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
index 746d91f..2b5d0bf 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
@@ -266,6 +266,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "wacky_yuv_formats_domain",
+      "serialize-8888",
+      "gm",
+      "_",
       "bitmapfilters",
       "serialize-8888",
       "gm",
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
index 9fc449f..0ff90c3 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
@@ -267,6 +267,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "wacky_yuv_formats_domain",
+      "serialize-8888",
+      "gm",
+      "_",
       "bitmapfilters",
       "serialize-8888",
       "gm",
diff --git a/infra/bots/recipes/test.py b/infra/bots/recipes/test.py
index 9a8dce6..70f1aa4 100644
--- a/infra/bots/recipes/test.py
+++ b/infra/bots/recipes/test.py
@@ -443,7 +443,8 @@
                        'fontmgr_bounds',
                        'fontmgr_match',
                        'fontmgr_iter',
-                       'imagemasksubset']
+                       'imagemasksubset',
+                       'wacky_yuv_formats_domain']
 
   # skia:5589
   bad_serialize_gms.extend(['bitmapfilters',
diff --git a/src/gpu/GrImageTextureMaker.cpp b/src/gpu/GrImageTextureMaker.cpp
index 5e5327f..0ce1f7b 100644
--- a/src/gpu/GrImageTextureMaker.cpp
+++ b/src/gpu/GrImageTextureMaker.cpp
@@ -92,13 +92,7 @@
     const GrSamplerState::Filter* filterOrNullForBicubic) {
 
     // Check simple cases to see if we need to fall back to flattening the image
-    // TODO: See if we can relax this -- for example, if filterConstraint
-    //       is kYes_FilterConstraint we still may not need a TextureDomain
-    //       in some cases. Or allow YUVtoRGBEffect to take a wrap mode to
-    //       handle ClampToBorder when a decal is needed.
-    if (!textureMatrix.isIdentity() || kNo_FilterConstraint != filterConstraint ||
-        !coordsLimitedToConstraintRect || !filterOrNullForBicubic ||
-        this->domainNeedsDecal()) {
+    if (!filterOrNullForBicubic || this->domainNeedsDecal()) {
         return this->INHERITED::createFragmentProcessor(textureMatrix, constraintRect,
                                                         filterConstraint,
                                                         coordsLimitedToConstraintRect,
@@ -106,15 +100,24 @@
     }
 
     // Check to see if the client has given us pre-mipped textures or we can generate them
-    // If not, fall back to bilerp
+    // If not, fall back to bilerp. Also fall back to bilerp when a domain is requested
     GrSamplerState::Filter filter = *filterOrNullForBicubic;
     if (GrSamplerState::Filter::kMipMap == filter &&
-        !fImage->setupMipmapsForPlanes(this->context())) {
+        (filterConstraint == GrTextureProducer::kYes_FilterConstraint ||
+         !fImage->setupMipmapsForPlanes(this->context()))) {
         filter = GrSamplerState::Filter::kBilerp;
     }
 
+    // Cannot rely on GrTextureProducer's domain infrastructure since we need to calculate domain's
+    // per plane, which may be different, so respect the filterConstraint without any additional
+    // analysis.
+    const SkRect* domain = nullptr;
+    if (filterConstraint == GrTextureProducer::kYes_FilterConstraint) {
+        domain = &constraintRect;
+    }
+
     auto fp = GrYUVtoRGBEffect::Make(fImage->fProxies, fImage->fYUVAIndices,
-                                     fImage->fYUVColorSpace, filter);
+                                     fImage->fYUVColorSpace, filter, textureMatrix, domain);
     if (fImage->fFromColorSpace) {
         fp = GrColorSpaceXformEffect::Make(std::move(fp), fImage->fFromColorSpace.get(),
                                            fImage->alphaType(), fImage->colorSpace());
diff --git a/src/gpu/GrImageTextureMaker.h b/src/gpu/GrImageTextureMaker.h
index ba7772b..4fa5e9b 100644
--- a/src/gpu/GrImageTextureMaker.h
+++ b/src/gpu/GrImageTextureMaker.h
@@ -47,6 +47,9 @@
 public:
     GrYUVAImageTextureMaker(GrContext* context, const SkImage* client, bool useDecal = false);
 
+    // This could be made more nuanced and compare all of the texture proxy resolutions, but
+    // it's probably not worth the effort.
+    bool hasMixedResolutions() const override { return true; }
 protected:
     // TODO: consider overriding this, for the case where the underlying generator might be
     //       able to efficiently produce a "stretched" texture natively (e.g. picture-backed)
diff --git a/src/gpu/GrTextureProducer.h b/src/gpu/GrTextureProducer.h
index b1d94df..bdb5859 100644
--- a/src/gpu/GrTextureProducer.h
+++ b/src/gpu/GrTextureProducer.h
@@ -107,6 +107,8 @@
     bool domainNeedsDecal() const { return fDomainNeedsDecal; }
     virtual SkAlphaType alphaType() const = 0;
     virtual SkColorSpace* colorSpace() const = 0;
+    // If the "texture" samples multiple images that have different resolutions (e.g. YUV420)
+    virtual bool hasMixedResolutions() const { return false; }
 
 protected:
     friend class GrTextureProducer_TestAccess;
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index 8f4d772..57de71c 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -284,7 +284,8 @@
 
     // Check for optimization to drop the src rect constraint when on bilerp.
     if (filterMode && GrSamplerState::Filter::kBilerp == *filterMode &&
-        GrTextureAdjuster::kYes_FilterConstraint == constraintMode && coordsAllInsideSrcRect) {
+        GrTextureAdjuster::kYes_FilterConstraint == constraintMode && coordsAllInsideSrcRect &&
+        !producer->hasMixedResolutions()) {
         SkMatrix combinedMatrix;
         combinedMatrix.setConcat(ctm, srcToDst);
         if (can_ignore_bilerp_constraint(*producer, src, combinedMatrix, rtc->fsaaType())) {
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.cpp b/src/gpu/effects/GrYUVtoRGBEffect.cpp
index b8b93fd..963ed77 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.cpp
+++ b/src/gpu/effects/GrYUVtoRGBEffect.cpp
@@ -7,6 +7,13 @@
 
 #include "GrYUVtoRGBEffect.h"
 
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLProgramBuilder.h"
+#include "GrTexture.h"
+#include "SkSLCPP.h"
+#include "SkSLUtil.h"
+
 static const float kJPEGConversionMatrix[16] = {
     1.0f,  0.0f,       1.402f,    -0.703749f,
     1.0f, -0.344136f, -0.714136f,  0.531211f,
@@ -31,7 +38,9 @@
 std::unique_ptr<GrFragmentProcessor> GrYUVtoRGBEffect::Make(const sk_sp<GrTextureProxy> proxies[],
                                                             const SkYUVAIndex yuvaIndices[4],
                                                             SkYUVColorSpace yuvColorSpace,
-                                                            GrSamplerState::Filter filterMode) {
+                                                            GrSamplerState::Filter filterMode,
+                                                            const SkMatrix& localMatrix,
+                                                            const SkRect* domain) {
     int numPlanes;
     SkAssertResult(SkYUVAIndex::AreValidIndices(yuvaIndices, &numPlanes));
 
@@ -51,7 +60,8 @@
     }
 
     return std::unique_ptr<GrFragmentProcessor>(new GrYUVtoRGBEffect(
-            proxies, scales, filterModes, numPlanes, yuvaIndices, yuvColorSpace));
+            proxies, scales, filterModes, numPlanes, yuvaIndices, yuvColorSpace, localMatrix,
+            domain));
 }
 
 #ifdef SK_DEBUG
@@ -68,105 +78,107 @@
 }
 #endif
 
-#include "glsl/GrGLSLFragmentProcessor.h"
-#include "glsl/GrGLSLFragmentShaderBuilder.h"
-#include "glsl/GrGLSLProgramBuilder.h"
-#include "GrTexture.h"
-#include "SkSLCPP.h"
-#include "SkSLUtil.h"
-class GrGLSLYUVtoRGBEffect : public GrGLSLFragmentProcessor {
-public:
-    GrGLSLYUVtoRGBEffect() {}
-
-    void emitCode(EmitArgs& args) override {
-        GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-        const GrYUVtoRGBEffect& _outer = args.fFp.cast<GrYUVtoRGBEffect>();
-        (void)_outer;
-
-        if (kIdentity_SkYUVColorSpace != _outer.yuvColorSpace()) {
-            fColorSpaceMatrixVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                                    kHalf4x4_GrSLType,
-                                                                    "colorSpaceMatrix");
-        }
-
-        int numSamplers = args.fTexSamplers.count();
-
-        SkString coords[4];
-        for (int i = 0; i < numSamplers; ++i) {
-            coords[i] = fragBuilder->ensureCoords2D(args.fTransformedCoords[i]);
-        }
-
-        for (int i = 0; i < numSamplers; ++i) {
-            fragBuilder->codeAppendf(
-                "half4 tmp%d = texture(%s, %s).%s;",
-                    i,
-                    fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[i]).c_str(),
-                    coords[i].c_str(),
-                    fragBuilder->getProgramBuilder()->samplerSwizzle(args.fTexSamplers[i]).c_str());
-        }
-
-        static const char kChannelToChar[4] = { 'x', 'y', 'z', 'w' };
-
-        fragBuilder->codeAppendf(
-            "half4 yuvOne = half4(half(tmp%d.%c), half(tmp%d.%c), half(tmp%d.%c), 1.0);",
-                _outer.yuvaIndex(0).fIndex, kChannelToChar[(int)_outer.yuvaIndex(0).fChannel],
-                _outer.yuvaIndex(1).fIndex, kChannelToChar[(int)_outer.yuvaIndex(1).fChannel],
-                _outer.yuvaIndex(2).fIndex, kChannelToChar[(int)_outer.yuvaIndex(2).fChannel]);
-
-        if (kIdentity_SkYUVColorSpace != _outer.yuvColorSpace()) {
-            SkASSERT(fColorSpaceMatrixVar.isValid());
-            fragBuilder->codeAppendf(
-                "yuvOne *= %s;", args.fUniformHandler->getUniformCStr(fColorSpaceMatrixVar));
-        }
-
-
-        if (_outer.yuvaIndex(3).fIndex >= 0) {
-            fragBuilder->codeAppendf(
-                "half a = tmp%d.%c;", _outer.yuvaIndex(3).fIndex,
-                                       kChannelToChar[(int)_outer.yuvaIndex(3).fChannel]);
-            // premultiply alpha
-            fragBuilder->codeAppend("yuvOne *= a;");
-        } else {
-            fragBuilder->codeAppendf("half a = 1.0;");
-        }
-
-        fragBuilder->codeAppendf("%s = half4(yuvOne.xyz, a);", args.fOutputColor);
-    }
-
-private:
-    void onSetData(const GrGLSLProgramDataManager& pdman,
-                   const GrFragmentProcessor& _proc) override {
-        const GrYUVtoRGBEffect& _outer = _proc.cast<GrYUVtoRGBEffect>();
-
-        switch (_outer.yuvColorSpace()) {
-            case kJPEG_SkYUVColorSpace:
-                SkASSERT(fColorSpaceMatrixVar.isValid());
-                pdman.setMatrix4f(fColorSpaceMatrixVar, kJPEGConversionMatrix);
-                break;
-            case kRec601_SkYUVColorSpace:
-                SkASSERT(fColorSpaceMatrixVar.isValid());
-                pdman.setMatrix4f(fColorSpaceMatrixVar, kRec601ConversionMatrix);
-                break;
-            case kRec709_SkYUVColorSpace:
-                SkASSERT(fColorSpaceMatrixVar.isValid());
-                pdman.setMatrix4f(fColorSpaceMatrixVar, kRec709ConversionMatrix);
-                break;
-            case kIdentity_SkYUVColorSpace:
-                break;
-        }
-    }
-
-    UniformHandle fColorSpaceMatrixVar;
-};
-
 GrGLSLFragmentProcessor* GrYUVtoRGBEffect::onCreateGLSLInstance() const {
-    return new GrGLSLYUVtoRGBEffect();
+    class GrGLSLYUVtoRGBEffect : public GrGLSLFragmentProcessor {
+    public:
+        GrGLSLYUVtoRGBEffect() {}
+
+        void emitCode(EmitArgs& args) override {
+            GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+            const GrYUVtoRGBEffect& _outer = args.fFp.cast<GrYUVtoRGBEffect>();
+            (void)_outer;
+
+            if (kIdentity_SkYUVColorSpace != _outer.yuvColorSpace()) {
+                fColorSpaceMatrixVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                                        kHalf4x4_GrSLType,
+                                                                        "colorSpaceMatrix");
+            }
+
+            int numSamplers = args.fTexSamplers.count();
+
+            SkString coords[4];
+            for (int i = 0; i < numSamplers; ++i) {
+                coords[i] = fragBuilder->ensureCoords2D(args.fTransformedCoords[i]);
+            }
+
+            for (int i = 0; i < numSamplers; ++i) {
+                SkString sampleVar;
+                sampleVar.printf("tmp%d", i);
+                fragBuilder->codeAppendf("half4 %s;", sampleVar.c_str());
+                fGLDomains[i].sampleTexture(fragBuilder, args.fUniformHandler, args.fShaderCaps,
+                        _outer.fDomains[i], sampleVar.c_str(), coords[i], args.fTexSamplers[i]);
+            }
+
+            static const char kChannelToChar[4] = { 'x', 'y', 'z', 'w' };
+
+            fragBuilder->codeAppendf(
+                "half4 yuvOne = half4(tmp%d.%c, tmp%d.%c, tmp%d.%c, 1.0);",
+                    _outer.yuvaIndex(0).fIndex, kChannelToChar[(int)_outer.yuvaIndex(0).fChannel],
+                    _outer.yuvaIndex(1).fIndex, kChannelToChar[(int)_outer.yuvaIndex(1).fChannel],
+                    _outer.yuvaIndex(2).fIndex, kChannelToChar[(int)_outer.yuvaIndex(2).fChannel]);
+
+            if (kIdentity_SkYUVColorSpace != _outer.yuvColorSpace()) {
+                SkASSERT(fColorSpaceMatrixVar.isValid());
+                fragBuilder->codeAppendf(
+                    "yuvOne *= %s;", args.fUniformHandler->getUniformCStr(fColorSpaceMatrixVar));
+            }
+
+            if (_outer.yuvaIndex(3).fIndex >= 0) {
+                fragBuilder->codeAppendf(
+                    "half a = tmp%d.%c;", _outer.yuvaIndex(3).fIndex,
+                                           kChannelToChar[(int)_outer.yuvaIndex(3).fChannel]);
+                // premultiply alpha
+                fragBuilder->codeAppend("yuvOne *= a;");
+            } else {
+                fragBuilder->codeAppend("half a = 1.0;");
+            }
+
+            fragBuilder->codeAppendf("%s = half4(yuvOne.xyz, a);", args.fOutputColor);
+        }
+
+    private:
+        void onSetData(const GrGLSLProgramDataManager& pdman,
+                       const GrFragmentProcessor& _proc) override {
+            const GrYUVtoRGBEffect& _outer = _proc.cast<GrYUVtoRGBEffect>();
+
+            switch (_outer.yuvColorSpace()) {
+                case kJPEG_SkYUVColorSpace:
+                    SkASSERT(fColorSpaceMatrixVar.isValid());
+                    pdman.setMatrix4f(fColorSpaceMatrixVar, kJPEGConversionMatrix);
+                    break;
+                case kRec601_SkYUVColorSpace:
+                    SkASSERT(fColorSpaceMatrixVar.isValid());
+                    pdman.setMatrix4f(fColorSpaceMatrixVar, kRec601ConversionMatrix);
+                    break;
+                case kRec709_SkYUVColorSpace:
+                    SkASSERT(fColorSpaceMatrixVar.isValid());
+                    pdman.setMatrix4f(fColorSpaceMatrixVar, kRec709ConversionMatrix);
+                    break;
+                case kIdentity_SkYUVColorSpace:
+                    break;
+            }
+
+            int numSamplers = _outer.numTextureSamplers();
+            for (int i = 0; i < numSamplers; ++i) {
+                fGLDomains[i].setData(pdman, _outer.fDomains[i],
+                        _outer.textureSampler(i).proxy(), _outer.textureSampler(i).samplerState());
+            }
+        }
+
+        UniformHandle fColorSpaceMatrixVar;
+        GrTextureDomain::GLDomain fGLDomains[4];
+    };
+
+    return new GrGLSLYUVtoRGBEffect;
 }
 void GrYUVtoRGBEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
                                              GrProcessorKeyBuilder* b) const {
+    using Domain = GrTextureDomain::GLDomain;
+
     b->add32(this->numTextureSamplers());
 
     uint32_t packed = 0;
+    uint32_t domain = 0;
     for (int i = 0; i < 4; ++i) {
         if (this->yuvaIndex(i).fIndex < 0) {
             continue;
@@ -178,12 +190,15 @@
         SkASSERT(index < 4 && chann < 4);
 
         packed |= (index | (chann << 2)) << (i * 4);
+
+        domain |= Domain::DomainKey(fDomains[i]) << (i * Domain::kDomainKeyBits);
     }
     if (kIdentity_SkYUVColorSpace == this->yuvColorSpace()) {
         packed |= 0x1 << 16;
     }
 
     b->add32(packed);
+    b->add32(domain);
 }
 bool GrYUVtoRGBEffect::onIsEqual(const GrFragmentProcessor& other) const {
     const GrYUVtoRGBEffect& that = other.cast<GrYUVtoRGBEffect>();
@@ -199,6 +214,9 @@
         if (fSamplerTransforms[i] != that.fSamplerTransforms[i]) {
             return false;
         }
+        if (!(fDomains[i] == that.fDomains[i])) {
+            return false;
+        }
     }
 
     if (fYUVColorSpace != that.fYUVColorSpace) {
@@ -209,6 +227,7 @@
 }
 GrYUVtoRGBEffect::GrYUVtoRGBEffect(const GrYUVtoRGBEffect& src)
         : INHERITED(kGrYUVtoRGBEffect_ClassID, src.optimizationFlags())
+        , fDomains{src.fDomains[0], src.fDomains[1], src.fDomains[2], src.fDomains[3]}
         , fYUVColorSpace(src.fYUVColorSpace) {
     int numPlanes = src.numTextureSamplers();
     for (int i = 0; i < numPlanes; ++i) {
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.h b/src/gpu/effects/GrYUVtoRGBEffect.h
index 93ec5238..9e4a843 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.h
+++ b/src/gpu/effects/GrYUVtoRGBEffect.h
@@ -12,15 +12,22 @@
 
 #include "GrFragmentProcessor.h"
 #include "GrCoordTransform.h"
+#include "GrTextureDomain.h"
 
 #include "SkYUVAIndex.h"
 
 class GrYUVtoRGBEffect : public GrFragmentProcessor {
 public:
+    // The domain supported by this effect is more limited than the general GrTextureDomain due
+    // to the multi-planar, varying resolution images that it has to sample. If 'domain' is provided
+    // it is the Y plane's domain. This will automatically inset for bilinear filtering, and only
+    // the clamp wrap mode is supported.
     static std::unique_ptr<GrFragmentProcessor> Make(const sk_sp<GrTextureProxy> proxies[],
                                                      const SkYUVAIndex indices[4],
                                                      SkYUVColorSpace yuvColorSpace,
-                                                     GrSamplerState::Filter filterMode);
+                                                     GrSamplerState::Filter filterMode,
+                                                     const SkMatrix& localMatrix = SkMatrix::I(),
+                                                     const SkRect* domain = nullptr);
 #ifdef SK_DEBUG
     SkString dumpInfo() const override;
 #endif
@@ -35,13 +42,31 @@
 private:
     GrYUVtoRGBEffect(const sk_sp<GrTextureProxy> proxies[], const SkSize scales[],
                      const GrSamplerState::Filter filterModes[], int numPlanes,
-                     const SkYUVAIndex yuvaIndices[4], SkYUVColorSpace yuvColorSpace)
+                     const SkYUVAIndex yuvaIndices[4], SkYUVColorSpace yuvColorSpace,
+                     const SkMatrix& localMatrix, const SkRect* domain)
             : INHERITED(kGrYUVtoRGBEffect_ClassID, kNone_OptimizationFlags)
+            , fDomains{GrTextureDomain::IgnoredDomain(), GrTextureDomain::IgnoredDomain(),
+                       GrTextureDomain::IgnoredDomain(), GrTextureDomain::IgnoredDomain()}
             , fYUVColorSpace(yuvColorSpace) {
         for (int i = 0; i < numPlanes; ++i) {
+            SkMatrix planeMatrix = SkMatrix::MakeScale(scales[i].width(), scales[i].height());
+            if (domain) {
+                SkASSERT(filterModes[i] != GrSamplerState::Filter::kMipMap);
+
+                SkRect scaledDomain = planeMatrix.mapRect(*domain);
+                if (filterModes[i] != GrSamplerState::Filter::kNearest) {
+                    // Inset by half a pixel for bilerp, after scaling to the size of the plane
+                    scaledDomain.inset(0.5f, 0.5f);
+                }
+
+                fDomains[i] = GrTextureDomain(proxies[i].get(), scaledDomain,
+                        GrTextureDomain::kClamp_Mode, GrTextureDomain::kClamp_Mode, i);
+            }
+
+            planeMatrix.postConcat(localMatrix);
             fSamplers[i].reset(std::move(proxies[i]),
                                GrSamplerState(GrSamplerState::WrapMode::kClamp, filterModes[i]));
-            fSamplerTransforms[i] = SkMatrix::MakeScale(scales[i].width(), scales[i].height());
+            fSamplerTransforms[i] = planeMatrix;
             fSamplerCoordTransforms[i] =
                     GrCoordTransform(fSamplerTransforms[i], fSamplers[i].proxy());
         }
@@ -62,6 +87,7 @@
     TextureSampler   fSamplers[4];
     SkMatrix44       fSamplerTransforms[4];
     GrCoordTransform fSamplerCoordTransforms[4];
+    GrTextureDomain  fDomains[4];
     SkYUVAIndex      fYUVAIndices[4];
     SkYUVColorSpace  fYUVColorSpace;