Make GrTextureOp disable filtering/aa in more cases.

Previously we didn't do this at all for multiple-texture ops.

Improve the test detecting when filtering can be disabled.

Make draw_image_set GM create tiles with pixel of overlap for correct
filtering.

Add draw_image_set_rect_to_rect to exercise filtering/aa disablement
in combination with tiling.

Makes SkGpuDevice filter out inverted src rects (as is done implicitly
in SkBaseDevice by relying on drawImageRect).

Puts GrTextureOp::fFilter in bitfield.

Change-Id: Iee96cb54d665877c7f4aee422a3a7af2b249b1d6
Reviewed-on: https://skia-review.googlesource.com/c/161641
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/gm/drawimageset.cpp b/gm/drawimageset.cpp
index a979516..aa43f18 100644
--- a/gm/drawimageset.cpp
+++ b/gm/drawimageset.cpp
@@ -7,9 +7,78 @@
 
 #include "gm.h"
 
+#include <algorithm>
 #include "SkGradientShader.h"
 #include "SkSurface.h"
 
+// Makes a set of m x n tiled images to be drawn with SkCanvas::experimental_drawImageSetV0().
+static void make_image_tiles(int tileW, int tileH, int m, int n, const SkColor colors[4],
+                             SkCanvas::ImageSetEntry set[]) {
+    const int w = tileW * m;
+    const int h = tileH * n;
+    auto surf = SkSurface::MakeRaster(
+            SkImageInfo::Make(w, h, kRGBA_8888_SkColorType, kPremul_SkAlphaType));
+    surf->getCanvas()->clear(SK_ColorLTGRAY);
+
+    static constexpr SkScalar kStripeW = 10;
+    static constexpr SkScalar kStripeSpacing = 30;
+    SkPaint paint;
+
+    SkPoint pts1[] = {{0.f, 0.f}, {(SkScalar)w, (SkScalar)h}};
+    auto grad = SkGradientShader::MakeLinear(pts1, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    paint.setShader(std::move(grad));
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(kStripeW);
+    SkPoint stripePts[] = {{-w - kStripeW, -kStripeW}, {kStripeW, h + kStripeW}};
+    while (stripePts[0].fX <= w) {
+        surf->getCanvas()->drawPoints(SkCanvas::kLines_PointMode, 2, stripePts, paint);
+        stripePts[0].fX += kStripeSpacing;
+        stripePts[1].fX += kStripeSpacing;
+    }
+
+    SkPoint pts2[] = {{0.f, (SkScalar)h}, {(SkScalar)w, 0.f}};
+    grad = SkGradientShader::MakeLinear(pts2, colors + 2, nullptr, 2, SkShader::kClamp_TileMode);
+    paint.setShader(std::move(grad));
+    paint.setBlendMode(SkBlendMode::kMultiply);
+    stripePts[0] = {-w - kStripeW, h + kStripeW};
+    stripePts[1] = {kStripeW, -kStripeW};
+    while (stripePts[0].fX <= w) {
+        surf->getCanvas()->drawPoints(SkCanvas::kLines_PointMode, 2, stripePts, paint);
+        stripePts[0].fX += kStripeSpacing;
+        stripePts[1].fX += kStripeSpacing;
+    }
+    auto fullImage = surf->makeImageSnapshot();
+    for (int y = 0; y < n; ++y) {
+        for (int x = 0; x < m; ++x) {
+            // Images will have 1 pixel of overlap at interior seams for filtering continuity.
+            SkIRect subset = SkIRect::MakeXYWH(x * tileW - 1, y * tileH - 1, tileW + 2, tileH + 2);
+            set[y * m + x].fAAFlags = SkCanvas::kNone_QuadAAFlags;
+            if (x == 0) {
+                subset.fLeft = 0;
+                set[y * m + x].fAAFlags |= SkCanvas::kLeft_QuadAAFlag;
+            }
+            if (x == m - 1) {
+                subset.fRight = w;
+                set[y * m + x].fAAFlags |= SkCanvas::kRight_QuadAAFlag;
+            }
+            if (y == 0) {
+                subset.fTop = 0;
+                set[y * m + x].fAAFlags |= SkCanvas::kTop_QuadAAFlag;
+            }
+            if (y == n - 1) {
+                subset.fBottom = h;
+                set[y * m + x].fAAFlags |= SkCanvas::kBottom_QuadAAFlag;
+            }
+            set[y * m + x].fImage = fullImage->makeSubset(subset);
+            set[y * m + x].fSrcRect =
+                    SkRect::MakeXYWH(x == 0 ? 0 : 1, y == 0 ? 0 : 1, tileW, tileH);
+            set[y * m + x].fDstRect = SkRect::MakeXYWH(x * tileW, y * tileH, tileW, tileH);
+            SkASSERT(set[y * m + x].fImage);
+        }
+    }
+}
+
 namespace skiagm {
 
 class DrawImageSetGM : public GM {
@@ -17,52 +86,9 @@
     SkString onShortName() final { return SkString("draw_image_set"); }
     SkISize onISize() override { return SkISize::Make(1000, 725); }
     void onOnceBeforeDraw() override {
-        static constexpr SkScalar kW = SkIntToScalar(kTileW * kM);
-        static constexpr SkScalar kH = SkIntToScalar(kTileH * kN);
-
-        auto surf = SkSurface::MakeRaster(
-                SkImageInfo::Make(kW, kH, kRGBA_8888_SkColorType, kPremul_SkAlphaType));
-        surf->getCanvas()->clear(SK_ColorLTGRAY);
-
-        static constexpr SkScalar kStripeW = 10;
-        static constexpr SkScalar kStripeSpacing = 30;
-        SkPaint paint;
-
-        static constexpr SkPoint pts1[] = {{0.f, 0.f}, {kW, kH}};
-        static constexpr SkColor kColors1[] = {SK_ColorCYAN, SK_ColorBLACK};
-        auto grad =
-                SkGradientShader::MakeLinear(pts1, kColors1, nullptr, 2, SkShader::kClamp_TileMode);
-        paint.setShader(std::move(grad));
-        paint.setAntiAlias(true);
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setStrokeWidth(kStripeW);
-        SkPoint stripePts[] = {{-kW - kStripeW, -kStripeW}, {kStripeW, kH + kStripeW}};
-        while (stripePts[0].fX <= kW) {
-            surf->getCanvas()->drawPoints(SkCanvas::kLines_PointMode, 2, stripePts, paint);
-            stripePts[0].fX += kStripeSpacing;
-            stripePts[1].fX += kStripeSpacing;
-        }
-
-        static constexpr SkPoint pts2[] = {{0.f, kH}, {kW, 0.f}};
-        static constexpr SkColor kColors2[] = {SK_ColorMAGENTA, SK_ColorBLACK};
-        grad = SkGradientShader::MakeLinear(pts2, kColors2, nullptr, 2, SkShader::kClamp_TileMode);
-        paint.setShader(std::move(grad));
-        paint.setBlendMode(SkBlendMode::kMultiply);
-        stripePts[0] = {-kW - kStripeW, kH + kStripeW};
-        stripePts[1] = {kStripeW, -kStripeW};
-        while (stripePts[0].fX <= kW) {
-            surf->getCanvas()->drawPoints(SkCanvas::kLines_PointMode, 2, stripePts, paint);
-            stripePts[0].fX += kStripeSpacing;
-            stripePts[1].fX += kStripeSpacing;
-        }
-
-        auto img = surf->makeImageSnapshot();
-        for (int y = 0; y < kN; ++y) {
-            for (int x = 0; x < kM; ++x) {
-                fImage[x][y] =
-                        img->makeSubset(SkIRect::MakeXYWH(x * kTileW, y * kTileH, kTileW, kTileH));
-            }
-        }
+        static constexpr SkColor kColors[] = {SK_ColorCYAN,    SK_ColorBLACK,
+                                              SK_ColorMAGENTA, SK_ColorBLACK};
+        make_image_tiles(kTileW, kTileH, kM, kN, kColors, fSet);
     }
 
     void onDraw(SkCanvas* canvas) override {
@@ -92,27 +118,6 @@
         dst[2] = {1.f / 3.f * kM * kTileW, 1 / 2.f * kN * kTileH - 0.1f * kTileH};
         SkAssertResult(matrices[3].setPolyToPoly(src, dst, 4));
         matrices[3].postTranslate(100.f, d);
-        SkCanvas::ImageSetEntry set[kM * kN];
-        for (int x = 0; x < kM; ++x) {
-            for (int y = 0; y < kN; ++y) {
-                set[y * kM + x].fAAFlags = SkCanvas::kNone_QuadAAFlags;
-                if (x == 0) {
-                    set[y * kM + x].fAAFlags |= SkCanvas::kLeft_QuadAAFlag;
-                }
-                if (x == kM - 1) {
-                    set[y * kM + x].fAAFlags |= SkCanvas::kRight_QuadAAFlag;
-                }
-                if (y == 0) {
-                    set[y * kM + x].fAAFlags |= SkCanvas::kTop_QuadAAFlag;
-                }
-                if (y == kN - 1) {
-                    set[y * kM + x].fAAFlags |= SkCanvas::kBottom_QuadAAFlag;
-                }
-                set[y * kM + x].fSrcRect = SkRect::MakeWH(kTileW, kTileH);
-                set[y * kM + x].fDstRect = SkRect::MakeXYWH(x * kTileW, y * kTileH, kTileW, kTileH);
-                set[y * kM + x].fImage = fImage[x][y];
-            }
-        }
         for (auto fm : {kNone_SkFilterQuality, kLow_SkFilterQuality}) {
             for (size_t m = 0; m < SK_ARRAY_COUNT(matrices); ++m) {
                 // Draw grid of red lines at interior tile boundaries.
@@ -138,19 +143,19 @@
                 }
                 canvas->save();
                 canvas->concat(matrices[m]);
-                canvas->experimental_DrawImageSetV0(set, kM * kN, 1.f, fm, SkBlendMode::kSrcOver);
+                canvas->experimental_DrawImageSetV0(fSet, kM * kN, 1.f, fm, SkBlendMode::kSrcOver);
                 canvas->restore();
             }
-            // A more exotic case with an unusual blend mode, all aa flags set, and alpha, and
+            // A more exotic case with an unusual blend mode, all aa flags set, and alpha,
             // subsets the image
             SkCanvas::ImageSetEntry entry;
             entry.fSrcRect = SkRect::MakeWH(kTileW, kTileH).makeInset(kTileW / 4.f, kTileH / 4.f);
             entry.fDstRect = SkRect::MakeWH(2 * kTileW, 2 * kTileH).makeOffset(d / 4, 2 * d);
-            entry.fImage = fImage[0][0];
+            entry.fImage = fSet[0].fImage;
             entry.fAAFlags = SkCanvas::kAll_QuadAAFlags;
             canvas->save();
             canvas->rotate(3.f);
-            canvas->experimental_DrawImageSetV0(&entry, 1, 0.7f, fm, SkBlendMode::kLuminosity);
+            canvas->experimental_DrawImageSetV0(&entry, 1, 0.7f, fm, SkBlendMode::kExclusion);
             canvas->restore();
             canvas->translate(2 * d, 0);
         }
@@ -159,9 +164,89 @@
     static constexpr int kN = 3;
     static constexpr SkScalar kTileW = 30;
     static constexpr SkScalar kTileH = 60;
-    sk_sp<SkImage> fImage[kM][kN];
+    SkCanvas::ImageSetEntry fSet[kM * kN];
+};
+
+// This GM exercises rect-stays-rect type matrices to test that filtering and antialiasing are not
+// incorrectly disabled.
+class DrawImageSetRectToRectGM : public GM {
+private:
+    SkString onShortName() final { return SkString("draw_image_set_rect_to_rect"); }
+    SkISize onISize() override { return SkISize::Make(1250, 850); }
+    void onOnceBeforeDraw() override {
+        static constexpr SkColor kColors[] = {SK_ColorBLUE, SK_ColorWHITE,
+                                              SK_ColorRED,  SK_ColorWHITE};
+        make_image_tiles(kTileW, kTileH, kM, kN, kColors, fSet);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        static constexpr SkScalar kW = kM * kTileW;
+        static constexpr SkScalar kH = kN * kTileH;
+        SkMatrix matrices[5];
+        // Identity
+        matrices[0].reset();
+        // 90 degree rotation
+        matrices[1].setRotate(90, kW / 2.f, kH / 2.f);
+        // Scaling
+        matrices[2].setScale(2.f, 0.5f);
+        // Mirror in x and y
+        matrices[3].setScale(-1.f, -1.f);
+        matrices[3].postTranslate(kW, kH);
+        // Mirror in y, rotate, and scale.
+        matrices[4].setScale(1.f, -1.f);
+        matrices[4].postTranslate(0, kH);
+        matrices[4].postRotate(90, kW / 2.f, kH / 2.f);
+        matrices[4].postScale(2.f, 0.5f);
+
+        static constexpr SkScalar kTranslate = SkTMax(kW, kH) * 2.f + 10.f;
+        canvas->translate(5.f, 5.f);
+        canvas->save();
+        for (SkScalar frac : {0.f, 0.5f}) {
+            canvas->save();
+            canvas->translate(frac, frac);
+            for (size_t m = 0; m < SK_ARRAY_COUNT(matrices); ++m) {
+                canvas->save();
+                canvas->concat(matrices[m]);
+                canvas->experimental_DrawImageSetV0(fSet, kM * kN, 1.f, kLow_SkFilterQuality,
+                                                    SkBlendMode::kSrcOver);
+                canvas->restore();
+                canvas->translate(kTranslate, 0);
+            }
+            canvas->restore();
+            canvas->restore();
+            canvas->translate(0, kTranslate);
+            canvas->save();
+        }
+        for (SkVector scale : {SkVector{2.f, 0.5f}, SkVector{0.5, 2.f}}) {
+            SkCanvas::ImageSetEntry scaledSet[kM * kN];
+            std::copy_n(fSet, kM * kN, scaledSet);
+            for (int i = 0; i < kM * kN; ++i) {
+                scaledSet[i].fDstRect.fLeft *= scale.fX;
+                scaledSet[i].fDstRect.fTop *= scale.fY;
+                scaledSet[i].fDstRect.fRight *= scale.fX;
+                scaledSet[i].fDstRect.fBottom *= scale.fY;
+            }
+            for (size_t m = 0; m < SK_ARRAY_COUNT(matrices); ++m) {
+                canvas->save();
+                canvas->concat(matrices[m]);
+                canvas->experimental_DrawImageSetV0(scaledSet, kM * kN, 1.f, kLow_SkFilterQuality,
+                                                    SkBlendMode::kSrcOver);
+                canvas->restore();
+                canvas->translate(kTranslate, 0);
+            }
+            canvas->restore();
+            canvas->translate(0, kTranslate);
+            canvas->save();
+        }
+    }
+    static constexpr int kM = 2;
+    static constexpr int kN = 2;
+    static constexpr int kTileW = 40;
+    static constexpr int kTileH = 50;
+    SkCanvas::ImageSetEntry fSet[kM * kN];
 };
 
 DEF_GM(return new DrawImageSetGM();)
+DEF_GM(return new DrawImageSetRectToRectGM();)
 
 }  // namespace skiagm
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index c8132a0..61e8fb5 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -1847,6 +1847,7 @@
      * drawing images independently, though may in the future. The antialiasing flags are intended
      * to allow control over each edge's AA status, to allow perfect seaming for tile sets. The
      * current implementation only antialiases if all edges are flagged, however.
+     * Results are undefined if an image's src rect is not within the image's bounds.
      */
     void experimental_DrawImageSetV0(const ImageSetEntry imageSet[], int cnt, float alpha,
                                      SkFilterQuality quality, SkBlendMode mode);
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 cbe8c06..2f9ac8b 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
@@ -365,6 +365,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "draw_image_set_rect_to_rect",
+      "serialize-8888",
+      "gm",
+      "_",
       "analytic_antialias_convex",
       "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 c6f6efb..b3248d9 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
@@ -441,6 +441,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "draw_image_set_rect_to_rect",
+      "serialize-8888",
+      "gm",
+      "_",
       "analytic_antialias_convex",
       "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 26e6bd5..9d089e5 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
@@ -359,6 +359,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "draw_image_set_rect_to_rect",
+      "serialize-8888",
+      "gm",
+      "_",
       "analytic_antialias_convex",
       "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 aa28bf4..d4098c7 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
@@ -360,6 +360,10 @@
       "serialize-8888",
       "gm",
       "_",
+      "draw_image_set_rect_to_rect",
+      "serialize-8888",
+      "gm",
+      "_",
       "analytic_antialias_convex",
       "serialize-8888",
       "gm",
diff --git a/infra/bots/recipes/test.py b/infra/bots/recipes/test.py
index e3ccd62..31abb62 100644
--- a/infra/bots/recipes/test.py
+++ b/infra/bots/recipes/test.py
@@ -469,6 +469,7 @@
   bad_serialize_gms.append('all_bitmap_configs')
   bad_serialize_gms.append('makecolorspace')
   bad_serialize_gms.append('readpixels')
+  bad_serialize_gms.append('draw_image_set_rect_to_rect')
 
   # This GM forces a path to be convex. That property doesn't survive
   # serialization.
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 5e8cfca..9df0734 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1473,6 +1473,14 @@
         }
     };
     for (int i = 0; i < count; ++i) {
+        // The default SkBaseDevice implementation is based on drawImageRect which does not allow
+        // non-sorted src rects. TODO: Decide this is OK or make sure we handle it.
+        if (!set[i].fSrcRect.isSorted()) {
+            draw();
+            base = i + 1;
+            n = 0;
+            continue;
+        }
         textures[i].fProxy =
                 as_IB(set[i].fImage.get())
                         ->asTextureProxyRef(fContext.get(), GrSamplerState::ClampBilerp(), nullptr,
@@ -1486,12 +1494,13 @@
             draw();
             base = i + 1;
             n = 0;
-        } else if (n > 0 &&
-                   (textures[i].fProxy->textureType() != textures[base].fProxy->textureType() ||
-                    textures[i].fProxy->config() != textures[base].fProxy->config() ||
-                    set[i].fImage->alphaType() != set[base].fImage->alphaType() ||
-                    !SkColorSpace::Equals(set[i].fImage->colorSpace(),
-                                          set[base].fImage->colorSpace()))) {
+            continue;
+        }
+        if (n > 0 &&
+            (textures[i].fProxy->textureType() != textures[base].fProxy->textureType() ||
+             textures[i].fProxy->config() != textures[base].fProxy->config() ||
+             set[i].fImage->alphaType() != set[base].fImage->alphaType() ||
+             !SkColorSpace::Equals(set[i].fImage->colorSpace(), set[base].fImage->colorSpace()))) {
             draw();
             base = i;
             n = 1;
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 857b809..94e5ad5 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -571,6 +571,29 @@
     DomainAssigner<V>::Assign(vertices, domain, filter, srcRect, origin, iw, ih);
 }
 
+static bool aa_has_effect_for_rect_stays_rect(const GrPerspQuad& quad) {
+    SkASSERT((quad.w4f() == Sk4f(1)).allTrue());
+    float ql = quad.x(0);
+    float qt = quad.y(0);
+    float qr = quad.x(3);
+    float qb = quad.y(3);
+    return !SkScalarIsInt(ql) || !SkScalarIsInt(qr) || !SkScalarIsInt(qt) || !SkScalarIsInt(qb);
+}
+
+static bool filter_has_effect_for_rect_stays_rect(const GrPerspQuad& quad, const SkRect& srcRect) {
+    SkASSERT((quad.w4f() == Sk4f(1)).allTrue());
+    float ql = quad.x(0);
+    float qt = quad.y(0);
+    float qr = quad.x(3);
+    float qb = quad.y(3);
+    // Disable filtering when there is no scaling of the src rect and the src rect and dst rect
+    // align fractionally. If we allow inverted src rects this logic needs to consider that.
+    SkASSERT(srcRect.isSorted());
+    return (qr - ql) != srcRect.width() || (qb - qt) != srcRect.height() ||
+           SkScalarFraction(ql) != SkScalarFraction(srcRect.fLeft) ||
+           SkScalarFraction(qt) != SkScalarFraction(srcRect.fTop);
+}
+
 /**
  * Op that implements GrTextureOp::Make. It draws textured quads. Each quad can modulate against a
  * the texture by color. The blend with the destination is always src-over. The edges are non-AA.
@@ -681,7 +704,7 @@
             : INHERITED(ClassID())
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
             , fPaintColorSpaceXform(std::move(paintColorSpaceXform))
-            , fFilter(filter)
+            , fFilter(static_cast<unsigned>(filter))
             , fAAType(static_cast<unsigned>(aaType))
             , fFinalized(0) {
         switch (aaType) {
@@ -702,41 +725,31 @@
         }
         fPerspective = static_cast<unsigned>(viewMatrix.hasPerspective());
         auto quad = GrPerspQuad(dstRect, viewMatrix);
-        auto bounds = quad.bounds();
         // We expect our caller to have already caught this optimization.
         SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) ||
                  constraint == SkCanvas::kFast_SrcRectConstraint);
         if (viewMatrix.rectStaysRect()) {
-            // Disable filtering when there is no scaling or fractional translation.
-            // Disable coverage AA when rect falls on integers in device space.
-            if (SkScalarIsInt(bounds.fLeft) && SkScalarIsInt(bounds.fTop) &&
-                SkScalarIsInt(bounds.fRight) && SkScalarIsInt(bounds.fBottom)) {
-                if (viewMatrix.isScaleTranslate()) {
-                    if (bounds.width() == srcRect.width() && bounds.height() == srcRect.height()) {
-                        fFilter = GrSamplerState::Filter::kNearest;
-                    }
-                } else {
-                    if (bounds.width() == srcRect.height() && bounds.height() == srcRect.width()) {
-                        fFilter = GrSamplerState::Filter::kNearest;
-                    }
-                }
-                if (GrAAType::kCoverage == this->aaType()) {
-                    fAAType = static_cast<unsigned>(GrAAType::kNone);
-                    aaFlags = GrQuadAAFlags::kNone;
-                }
+            if (this->aaType() == GrAAType::kCoverage && !aa_has_effect_for_rect_stays_rect(quad)) {
+                fAAType = static_cast<unsigned>(GrAAType::kNone);
+                aaFlags = GrQuadAAFlags::kNone;
+            }
+            if (this->filter() != GrSamplerState::Filter::kNearest &&
+                !filter_has_effect_for_rect_stays_rect(quad, srcRect)) {
+                fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
             }
         }
         // We may have had a strict constraint with nearest filter solely due to possible AA bloat.
         // If we don't have (or determined we don't need) coverage AA then we can skip using a
         // domain.
         if (constraint == SkCanvas::kStrict_SrcRectConstraint &&
-            fFilter == GrSamplerState::Filter::kNearest &&
+            this->filter() == GrSamplerState::Filter::kNearest &&
             this->aaType() != GrAAType::kCoverage) {
             constraint = SkCanvas::kFast_SrcRectConstraint;
         }
         const auto& draw = fQuads.emplace_back(srcRect, quad, aaFlags, constraint, color);
         fProxyCnt = 1;
         fProxies[0] = {proxy.release(), 1};
+        auto bounds = quad.bounds();
         this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo);
         fDomain = static_cast<unsigned>(draw.domain());
         fCanSkipAllocatorGather =
@@ -749,14 +762,16 @@
             : INHERITED(ClassID())
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
             , fPaintColorSpaceXform(std::move(paintColorSpaceXform))
-            , fFilter(filter)
+            , fFilter(static_cast<unsigned>(filter))
             , fAAType(static_cast<unsigned>(aaType))
             , fFinalized(0) {
         fQuads.reserve(cnt);
         fProxyCnt = SkToUInt(cnt);
         SkRect bounds = SkRectPriv::MakeLargestInverted();
-        bool aa = false;
+        bool needAA = false;
+        bool mustFilter = false;
         fCanSkipAllocatorGather = static_cast<unsigned>(true);
+        bool rectStaysRect = viewMatrix.rectStaysRect();
         for (unsigned p = 0; p < fProxyCnt; ++p) {
             fProxies[p].fProxy = SkRef(set[p].fProxy.get());
             fProxies[p].fQuadCnt = 1;
@@ -773,6 +788,12 @@
                     aaFlags = GrQuadAAFlags::kNone;
                     break;
                 case GrAAType::kCoverage:
+                    if (rectStaysRect) {
+                        if (aaFlags != GrQuadAAFlags::kNone &&
+                            !aa_has_effect_for_rect_stays_rect(quad)) {
+                            aaFlags = GrQuadAAFlags::kNone;
+                        }
+                    }
                     break;
                 case GrAAType::kMSAA:
                     aaFlags = GrQuadAAFlags::kAll;
@@ -781,13 +802,20 @@
                     SK_ABORT("Should not use mixed sample AA");
                     break;
             }
-            aa = aa || (set[p].fAAFlags != GrQuadAAFlags::kNone);
+            needAA = needAA || (set[p].fAAFlags != GrQuadAAFlags::kNone);
+            if (!mustFilter && this->filter() != GrSamplerState::Filter::kNearest) {
+                mustFilter = !rectStaysRect ||
+                             filter_has_effect_for_rect_stays_rect(quad, set[p].fSrcRect);
+            }
             fQuads.emplace_back(set[p].fSrcRect, quad, aaFlags, SkCanvas::kFast_SrcRectConstraint,
                                 color);
         }
-        if (!aa) {
+        if (!needAA) {
             fAAType = static_cast<unsigned>(GrAAType::kNone);
         }
+        if (!mustFilter) {
+            fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
+        }
         this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo);
         fPerspective = static_cast<unsigned>(viewMatrix.hasPerspective());
         fDomain = static_cast<unsigned>(false);
@@ -807,8 +835,8 @@
 
         for (int i = start; i < start + cnt; ++i) {
             const auto q = fQuads[i];
-            tessellate_quad<Vertex>(q.quad(), q.aaFlags(), q.srcRect(), q.color(), origin, fFilter,
-                                    vertices, iw, ih, q.domain());
+            tessellate_quad<Vertex>(q.quad(), q.aaFlags(), q.srcRect(), q.color(), origin,
+                                    this->filter(), vertices, iw, ih, q.domain());
             vertices += 4;
         }
     }
@@ -840,7 +868,7 @@
 
         bool coverageAA = GrAAType::kCoverage == this->aaType();
         sk_sp<GrGeometryProcessor> gp = TextureGeometryProcessor::Make(
-                textureType, config, fFilter, std::move(fTextureColorSpaceXform),
+                textureType, config, this->filter(), std::move(fTextureColorSpaceXform),
                 std::move(fPaintColorSpaceXform), coverageAA, hasPerspective, domain,
                 *target->caps().shaderCaps());
         GrPipeline::InitArgs args;
@@ -996,6 +1024,7 @@
     }
 
     GrAAType aaType() const { return static_cast<GrAAType>(fAAType); }
+    GrSamplerState::Filter filter() const { return static_cast<GrSamplerState::Filter>(fFilter); }
 
     class Quad {
     public:
@@ -1028,14 +1057,14 @@
     SkSTArray<1, Quad, true> fQuads;
     sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
     sk_sp<GrColorSpaceXform> fPaintColorSpaceXform;
-    GrSamplerState::Filter fFilter;
+    unsigned fFilter : 2;
     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 fCanSkipAllocatorGather : 1;
-    unsigned fProxyCnt : 32 - 6;
+    unsigned fProxyCnt : 32 - 8;
     Proxy fProxies[1];
 
     typedef GrMeshDrawOp INHERITED;