Add non-srcover fallback for drawImageSet

Bug: skia:
Change-Id: Ie0b950f0b8bf0986e8419c49594a7b85c42e0295
Reviewed-on: https://skia-review.googlesource.com/c/184921
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/gm/drawimageset.cpp b/gm/drawimageset.cpp
index 6afd360..f993a7c 100644
--- a/gm/drawimageset.cpp
+++ b/gm/drawimageset.cpp
@@ -148,14 +148,14 @@
                 canvas->experimental_DrawImageSetV1(fSet, kM * kN, fm, SkBlendMode::kSrcOver);
                 canvas->restore();
             }
-            // A more exotic case with an unusual blend mode, all aa flags set, and alpha,
+            // A more exotic case with an unusual blend mode, mixed 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 = fSet[0].fImage;
             entry.fAlpha = 0.7f;
-            entry.fAAFlags = SkCanvas::kAll_QuadAAFlags;
+            entry.fAAFlags = SkCanvas::kLeft_QuadAAFlag | SkCanvas::kTop_QuadAAFlag;
             canvas->save();
             canvas->rotate(3.f);
             canvas->experimental_DrawImageSetV1(&entry, 1, fm, SkBlendMode::kExclusion);
diff --git a/src/gpu/GrColorSpaceXform.cpp b/src/gpu/GrColorSpaceXform.cpp
index 2cd5e5a..986ac7a 100644
--- a/src/gpu/GrColorSpaceXform.cpp
+++ b/src/gpu/GrColorSpaceXform.cpp
@@ -171,3 +171,16 @@
     return std::unique_ptr<GrFragmentProcessor>(new GrColorSpaceXformEffect(std::move(child),
                                                                             std::move(xform)));
 }
+
+std::unique_ptr<GrFragmentProcessor> GrColorSpaceXformEffect::Make(
+        std::unique_ptr<GrFragmentProcessor> child, sk_sp<GrColorSpaceXform> colorXform) {
+    if (!child) {
+        return nullptr;
+    }
+    if (!colorXform) {
+        return child;
+    }
+
+    return std::unique_ptr<GrFragmentProcessor>(new GrColorSpaceXformEffect(std::move(child),
+                                                                            std::move(colorXform)));
+}
diff --git a/src/gpu/GrColorSpaceXform.h b/src/gpu/GrColorSpaceXform.h
index 242eeb5..7b28164 100644
--- a/src/gpu/GrColorSpaceXform.h
+++ b/src/gpu/GrColorSpaceXform.h
@@ -61,6 +61,13 @@
                                                      SkColorSpace* src, SkAlphaType srcAT,
                                                      SkColorSpace* dst);
 
+    /**
+     * Returns a fragment processor that calls the passed in FP and then converts it with the given
+     * color xform. Returns null if child is null, returns child if the xform is null (e.g. noop).
+     */
+    static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> child,
+                                                     sk_sp<GrColorSpaceXform> colorXform);
+
     const char* name() const override { return "ColorSpaceXform"; }
     std::unique_ptr<GrFragmentProcessor> clone() const override;
 
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index e6c59c7..ddfb13a 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -939,17 +939,47 @@
 }
 
 void GrRenderTargetContext::drawTextureSet(const GrClip& clip, const TextureSetEntry set[], int cnt,
-                                           GrSamplerState::Filter filter,
+                                           GrSamplerState::Filter filter, SkBlendMode mode,
                                            const SkMatrix& viewMatrix,
                                            sk_sp<GrColorSpaceXform> texXform) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
     SkDEBUGCODE(this->validate();)
     GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawTextureSet", fContext);
+
+    AutoCheckFlush acf(this->drawingManager());
+
     GrAAType aaType = this->chooseAAType(GrAA::kYes, GrAllowMixedSamples::kNo);
-    auto op =
-            GrTextureOp::Make(fContext, set, cnt, filter, aaType, viewMatrix, std::move(texXform));
-    this->addDrawOp(clip, std::move(op));
+    if (mode != SkBlendMode::kSrcOver ||
+        !fContext->contextPriv().caps()->dynamicStateArrayGeometryProcessorTextureSupport()) {
+        // Draw one at a time with GrFillRectOp and a GrPaint that emulates what GrTextureOp does
+        const GrXPFactory* xpFactory = SkBlendMode_AsXPFactory(mode);
+        for (int i = 0; i < cnt; ++i) {
+            GrPaint paint;
+            paint.setColor4f({set[i].fAlpha, set[i].fAlpha, set[i].fAlpha, set[i].fAlpha});
+            paint.setXPFactory(xpFactory);
+
+            // See if we can disable bilerp filtering when the src and dst rects line up
+            if (filter != GrSamplerState::Filter::kNearest &&
+                !GrTextureOp::GetFilterHasEffect(viewMatrix, set[i].fSrcRect, set[i].fDstRect)) {
+                filter = GrSamplerState::Filter::kNearest;
+            }
+
+            auto fp = GrSimpleTextureEffect::Make(set[i].fProxy, SkMatrix::I(), filter);
+            fp = GrColorSpaceXformEffect::Make(std::move(fp), texXform);
+            paint.addColorFragmentProcessor(std::move(fp));
+
+            auto op = GrFillRectOp::MakePerEdgeWithLocalRect(fContext, std::move(paint), aaType,
+                                                             set[i].fAAFlags, viewMatrix,
+                                                             set[i].fDstRect, set[i].fSrcRect);
+            this->addDrawOp(clip, std::move(op));
+        }
+    } else {
+        // Can use a single op, avoiding GrPaint creation, and can batch across proxies
+        auto op = GrTextureOp::Make(fContext, set, cnt, filter, aaType, viewMatrix,
+                                    std::move(texXform));
+        this->addDrawOp(clip, std::move(op));
+    }
 }
 
 void GrRenderTargetContext::fillRectWithLocalMatrix(const GrClip& clip,
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index 6d5e002..1dfe59a 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -162,7 +162,8 @@
      * texture color xform. The textures must all have the same GrTextureType and GrConfig.
      */
     void drawTextureSet(const GrClip&, const TextureSetEntry[], int cnt, GrSamplerState::Filter,
-                        const SkMatrix& viewMatrix, sk_sp<GrColorSpaceXform> texXform);
+                        SkBlendMode mode, const SkMatrix& viewMatrix,
+                        sk_sp<GrColorSpaceXform> texXform);
 
     /**
      * Draw a roundrect using a paint.
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index a5fd260..c2fff53 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1496,11 +1496,7 @@
 void SkGpuDevice::drawImageSet(const SkCanvas::ImageSetEntry set[], int count,
                                SkFilterQuality filterQuality, SkBlendMode mode) {
     SkASSERT(count > 0);
-    if (mode != SkBlendMode::kSrcOver ||
-        !fContext->contextPriv().caps()->dynamicStateArrayGeometryProcessorTextureSupport()) {
-        INHERITED::drawImageSet(set, count, filterQuality, mode);
-        return;
-    }
+
     GrSamplerState sampler;
     sampler.setFilterMode(kNone_SkFilterQuality == filterQuality ? GrSamplerState::Filter::kNearest
                                                                  : GrSamplerState::Filter::kBilerp);
@@ -1514,7 +1510,7 @@
                     set[base].fImage->colorSpace(), set[base].fImage->alphaType(),
                     fRenderTargetContext->colorSpaceInfo().colorSpace(), kPremul_SkAlphaType);
             fRenderTargetContext->drawTextureSet(this->clip(), textures.get() + base, n,
-                                                 sampler.filter(), this->ctm(),
+                                                 sampler.filter(), mode, this->ctm(),
                                                  std::move(textureXform));
         }
     };
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index e83b435..e3594d3 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -40,20 +40,6 @@
 using VertexSpec = GrQuadPerEdgeAA::VertexSpec;
 using ColorType = GrQuadPerEdgeAA::ColorType;
 
-static bool filter_has_effect_for_rect_stays_rect(const GrPerspQuad& quad, const SkRect& srcRect) {
-    SkASSERT(quad.quadType() == GrQuadType::kRect);
-    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);
-}
-
 // if normalizing the domain then pass 1/width, 1/height, 1 for iw, ih, h. Otherwise pass
 // 1, 1, and height.
 static SkRect compute_domain(Domain domain, GrSamplerState::Filter filter, GrSurfaceOrigin origin,
@@ -229,7 +215,7 @@
             // Disable filtering if possible (note AA optimizations for rects are automatically
             // handled above in GrResolveAATypeForQuad).
             if (this->filter() != GrSamplerState::Filter::kNearest &&
-                !filter_has_effect_for_rect_stays_rect(quad, srcRect)) {
+                !GrTextureOp::GetFilterHasEffect(viewMatrix, srcRect, dstRect)) {
                 fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
             }
         }
@@ -291,7 +277,8 @@
             }
             if (!mustFilter && this->filter() != GrSamplerState::Filter::kNearest) {
                 mustFilter = quadType != GrQuadType::kRect ||
-                             filter_has_effect_for_rect_stays_rect(quad, set[p].fSrcRect);
+                             GrTextureOp::GetFilterHasEffect(viewMatrix, set[p].fSrcRect,
+                                                             set[p].fDstRect);
             }
             float alpha = SkTPin(set[p].fAlpha, 0.f, 1.f);
             SkPMColor4f color{alpha, alpha, alpha, alpha};
@@ -575,6 +562,29 @@
                            std::move(textureColorSpaceXform));
 }
 
+bool GetFilterHasEffect(const SkMatrix& viewMatrix, const SkRect& srcRect, const SkRect& dstRect) {
+    // Hypothetically we could disable bilerp filtering when flipping or rotating 90 degrees, but
+    // that makes the math harder and we don't want to increase the overhead of the checks
+    if (!viewMatrix.isScaleTranslate() ||
+        viewMatrix.getScaleX() < 0.0f || viewMatrix.getScaleY() < 0.0f) {
+        return true;
+    }
+
+    // Given the matrix conditions ensured above, this computes the device space coordinates for
+    // the top left corner of dstRect and its size.
+    SkScalar dw = viewMatrix.getScaleX() * dstRect.width();
+    SkScalar dh = viewMatrix.getScaleY() * dstRect.height();
+    SkScalar dl = viewMatrix.getScaleX() * dstRect.fLeft + viewMatrix.getTranslateX();
+    SkScalar dt = viewMatrix.getScaleY() * dstRect.fTop + viewMatrix.getTranslateY();
+
+    // 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 dw != srcRect.width() || dh != srcRect.height() ||
+           SkScalarFraction(dl) != SkScalarFraction(srcRect.fLeft) ||
+           SkScalarFraction(dt) != SkScalarFraction(srcRect.fTop);
+}
+
 }  // namespace GrTextureOp
 
 #if GR_TEST_UTILS
diff --git a/src/gpu/ops/GrTextureOp.h b/src/gpu/ops/GrTextureOp.h
index 44d34ab..1942344 100644
--- a/src/gpu/ops/GrTextureOp.h
+++ b/src/gpu/ops/GrTextureOp.h
@@ -46,4 +46,11 @@
                                GrAAType,
                                const SkMatrix& viewMatrix,
                                sk_sp<GrColorSpaceXform> textureXform);
+
+/**
+ * Returns true if bilerp texture filtering matters when rendering the src rect
+ * texels to dst rect, with the given view matrix.
+ */
+bool GetFilterHasEffect(const SkMatrix& viewMatrix, const SkRect& srcRect, const SkRect& dstRect);
+
 }