Support per-entry transforms in image-set API

Bug: skia:
Change-Id: I508ec8cb1df1c407853b401c73c66a575fb9c661
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/196642
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 3c426c4..a148cef 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -1068,14 +1068,20 @@
     if (mode != SkBlendMode::kSrcOver ||
         !fContext->priv().caps()->dynamicStateArrayGeometryProcessorTextureSupport()) {
         // Draw one at a time with GrFillRectOp and a GrPaint that emulates what GrTextureOp does
+        SkMatrix ctm;
         for (int i = 0; i < cnt; ++i) {
             float alpha = set[i].fAlpha;
+            ctm = viewMatrix;
+            if (set[i].fPreViewMatrix) {
+                ctm.preConcat(*set[i].fPreViewMatrix);
+            }
+
             if (set[i].fDstClipQuad == nullptr) {
                 // Stick with original rectangles, which allows the ops to know more about what's
                 // being drawn.
                 this->drawTexture(clip, set[i].fProxy, filter, mode, {alpha, alpha, alpha, alpha},
                                   set[i].fSrcRect, set[i].fDstRect, aa, set[i].fAAFlags,
-                                  SkCanvas::kFast_SrcRectConstraint, viewMatrix, texXform);
+                                  SkCanvas::kFast_SrcRectConstraint, ctm, texXform);
             } else {
                 // Generate interpolated texture coordinates to match the dst clip
                 SkPoint srcQuad[4];
@@ -1085,7 +1091,7 @@
                 // keep seams look more correct.
                 this->drawTextureQuad(clip, set[i].fProxy, filter, mode,
                                       {alpha, alpha, alpha, alpha}, srcQuad, set[i].fDstClipQuad,
-                                      aa, set[i].fAAFlags, nullptr, viewMatrix, texXform);
+                                      aa, set[i].fAAFlags, nullptr, ctm, texXform);
             }
         }
     } else {
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index 1d2f7a5..4691e94 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -197,6 +197,7 @@
         SkRect fSrcRect;
         SkRect fDstRect;
         const SkPoint* fDstClipQuad; // Must be null, or point to an array of 4 points
+        const SkMatrix* fPreViewMatrix; // If not null, entry's CTM is 'viewMatrix' * fPreViewMatrix
         float fAlpha;
         GrQuadAAFlags fAAFlags;
     };
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 1dc594b..eb8b84a 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1298,8 +1298,8 @@
                                 const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) {
     ASSERT_SINGLE_OWNER
     GrQuadAAFlags aaFlags = paint.isAntiAlias() ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
-    this->drawImageQuad(
-            image, src, &dst, nullptr, GrAA(paint.isAntiAlias()), aaFlags, paint, constraint);
+    this->drawImageQuad(image, src, &dst, nullptr, GrAA(paint.isAntiAlias()), aaFlags, nullptr,
+                        paint, constraint);
 }
 
 // When drawing nine-patches or n-patches, cap the filter quality at kBilerp.
@@ -1410,7 +1410,7 @@
     paint.setBlendMode(mode);
     paint.setFilterQuality(filterQuality);
     paint.setAntiAlias(true);
-    this->tmp_drawImageSetV2(set, nullptr, count, nullptr, paint,
+    this->tmp_drawImageSetV3(set, nullptr, nullptr, count, nullptr, nullptr, paint,
                              SkCanvas::kFast_SrcRectConstraint);
 }
 
diff --git a/src/gpu/SkGpuDevice.h b/src/gpu/SkGpuDevice.h
index 9d60096..b41b8c8 100644
--- a/src/gpu/SkGpuDevice.h
+++ b/src/gpu/SkGpuDevice.h
@@ -130,10 +130,12 @@
 
     /*
      * dstClipCounts[] is a parallel array to the image entries, acting like the intended
-     * dstClipCount field in ImageSetEntry.
+     * dstClipCount field in ImageSetEntry. Similarly, preViewMatrixIdx is parallel and will
+     * become an index field in ImageSetEntry that specifies an entry in the matrix array.
      */
-    void tmp_drawImageSetV2(const SkCanvas::ImageSetEntry[], int dstClipCounts[], int count,
-            const SkPoint dstClips[], const SkPaint& paint,
+    void tmp_drawImageSetV3(const SkCanvas::ImageSetEntry[],
+            int dstClipCounts[], int preViewMatrixIdx[], int count,
+            const SkPoint dstClips[], const SkMatrix preViewMatrices[], const SkPaint& paint,
             SkCanvas::SrcRectConstraint constraint = SkCanvas::kStrict_SrcRectConstraint);
     void tmp_drawEdgeAAQuad(const SkRect& rect, const SkPoint clip[], int clipCount,
                             SkCanvas::QuadAAFlags aaFlags, SkColor color, SkBlendMode mode);
@@ -219,10 +221,11 @@
                         bool bicubic,
                         bool needsTextureDomain);
 
-    // If not null, dstClip must be contained inside dst and will also respect the edge AA flags
+    // If not null, dstClip must be contained inside dst and will also respect the edge AA flags.
+    // If 'preViewMatrix' is not null, final CTM will be this->ctm() * preViewMatrix.
     void drawImageQuad(const SkImage*, const SkRect* src, const SkRect* dst,
                        const SkPoint dstClip[4], GrAA aa, GrQuadAAFlags aaFlags,
-                       const SkPaint&, SkCanvas::SrcRectConstraint);
+                       const SkMatrix* preViewMatrix, const SkPaint&, SkCanvas::SrcRectConstraint);
 
     // TODO(michaelludwig): This can be removed once drawBitmapRect is removed from SkDevice
     // so that drawImageQuad is the sole entry point into the draw-single-image op
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index 5b50506..8c280d5 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -355,7 +355,8 @@
 
 void SkGpuDevice::drawImageQuad(const SkImage* image, const SkRect* srcRect, const SkRect* dstRect,
                                 const SkPoint dstClip[4], GrAA aa, GrQuadAAFlags aaFlags,
-                                const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) {
+                                const SkMatrix* preViewMatrix, const SkPaint& paint,
+                                SkCanvas::SrcRectConstraint constraint) {
     SkRect src;
     SkRect dst;
     SkMatrix srcToDst;
@@ -372,15 +373,21 @@
     bool useDecal = mode == ImageDrawMode::kDecal;
     bool attemptDrawTexture = !useDecal; // rtc->drawTexture() only clamps
 
+    // Get final CTM matrix
+    SkMatrix ctm = this->ctm();
+    if (preViewMatrix) {
+        ctm.preConcat(*preViewMatrix);
+    }
+
     // YUVA images can be stored in multiple images with different plane resolutions, so this
     // uses an effect to combine them dynamically on the GPU. This is done before requesting a
     // pinned texture proxy because YUV images force-flatten to RGBA in that scenario.
     if (as_IB(image)->isYUVA()) {
         SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
-        LogDrawScaleFactor(this->ctm(), srcToDst, paint.getFilterQuality());
+        LogDrawScaleFactor(ctm, srcToDst, paint.getFilterQuality());
 
         GrYUVAImageTextureMaker maker(fContext.get(), image, useDecal);
-        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), this->ctm(),
+        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm,
                               paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint,
                               /* attempt draw texture */ false);
         return;
@@ -392,20 +399,20 @@
     if (sk_sp<GrTextureProxy> proxy = as_IB(image)->refPinnedTextureProxy(this->context(),
                                                                           &pinnedUniqueID)) {
         SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
-        LogDrawScaleFactor(this->ctm(), srcToDst, paint.getFilterQuality());
+        LogDrawScaleFactor(ctm, srcToDst, paint.getFilterQuality());
 
         SkAlphaType alphaType = image->alphaType();
         SkColorSpace* colorSpace = as_IB(image)->colorSpace();
 
         if (attemptDrawTexture && can_use_draw_texture(paint)) {
-            draw_texture(fRenderTargetContext.get(), this->clip(), this->ctm(), paint, src,  dst,
+            draw_texture(fRenderTargetContext.get(), this->clip(), ctm, paint, src,  dst,
                          dstClip, aa, aaFlags, constraint, std::move(proxy), alphaType, colorSpace);
             return;
         }
 
         GrTextureAdjuster adjuster(fContext.get(), std::move(proxy), alphaType, pinnedUniqueID,
                                    colorSpace, useDecal);
-        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), this->ctm(),
+        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm,
                               paint, &adjuster, src, dst, dstClip, srcToDst, aa, aaFlags,
                               constraint, /* attempt draw_texture */ false);
         return;
@@ -415,8 +422,7 @@
     // TODO (michaelludwig): Implement this with per-edge AA flags to handle seaming properly
     // instead of going through drawBitmapRect (which will be removed from SkDevice in the future)
     SkBitmap bm;
-    if (this->shouldTileImage(image, &src, constraint, paint.getFilterQuality(), this->ctm(),
-                              srcToDst)) {
+    if (this->shouldTileImage(image, &src, constraint, paint.getFilterQuality(), ctm, srcToDst)) {
         // only support tiling as bitmap at the moment, so force raster-version
         if (!as_IB(image)->getROPixels(&bm)) {
             return;
@@ -427,20 +433,20 @@
 
     // This is the funnel for all non-tiled bitmap/image draw calls. Log a histogram entry.
     SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
-    LogDrawScaleFactor(this->ctm(), srcToDst, paint.getFilterQuality());
+    LogDrawScaleFactor(ctm, srcToDst, paint.getFilterQuality());
 
     // Lazily generated images must get drawn as a texture producer that handles the final
     // texture creation.
     if (image->isLazyGenerated()) {
         GrImageTextureMaker maker(fContext.get(), image, SkImage::kAllow_CachingHint, useDecal);
-        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), this->ctm(),
+        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm,
                               paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint,
                               attemptDrawTexture);
         return;
     }
     if (as_IB(image)->getROPixels(&bm)) {
         GrBitmapTextureMaker maker(fContext.get(), bm, useDecal);
-        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), this->ctm(),
+        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), ctm,
                               paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint,
                               attemptDrawTexture);
     }
@@ -450,8 +456,9 @@
 
 // For ease-of-use, the temporary API treats null dstClipCounts as if it were the proper sized
 // array, filled with all 0s (so dstClips can be null too)
-void SkGpuDevice::tmp_drawImageSetV2(const SkCanvas::ImageSetEntry set[], int dstClipCounts[],
-                                     int count, const SkPoint dstClips[], const SkPaint& paint,
+void SkGpuDevice::tmp_drawImageSetV3(const SkCanvas::ImageSetEntry set[], int dstClipCounts[],
+                                     int preViewMatrixIdx[], int count, const SkPoint dstClips[],
+                                     const SkMatrix preViewMatrices[], const SkPaint& paint,
                                      SkCanvas::SrcRectConstraint constraint) {
     SkASSERT(count > 0);
 
@@ -461,10 +468,15 @@
         for (int i = 0; i < count; ++i) {
             // Only no clip or quad clip are supported
             SkASSERT(!dstClipCounts || dstClipCounts[i] == 0 || dstClipCounts[i] == 4);
+
+            int xform = preViewMatrixIdx ? preViewMatrixIdx[i] : -1;
+            SkASSERT(xform < 0 || preViewMatrices);
+
             // Always send GrAA::kYes to preserve seaming across tiling in MSAA
             this->drawImageQuad(set[i].fImage.get(), &set[i].fSrcRect, &set[i].fDstRect,
                     (dstClipCounts && dstClipCounts[i] > 0) ? dstClips + dstClipIndex : nullptr,
-                    GrAA::kYes, SkToGrQuadAAFlags(set[i].fAAFlags), paint, constraint);
+                    GrAA::kYes, SkToGrQuadAAFlags(set[i].fAAFlags),
+                    xform < 0 ? nullptr : preViewMatrices + xform, paint, constraint);
             if (dstClipCounts) {
                 dstClipIndex += dstClipCounts[i];
             }
@@ -531,9 +543,14 @@
                 continue;
             }
         }
+
+        int xform = preViewMatrixIdx ? preViewMatrixIdx[i] : -1;
+        SkASSERT(xform < 0 || preViewMatrices);
+
         textures[i].fSrcRect = set[i].fSrcRect;
         textures[i].fDstRect = set[i].fDstRect;
         textures[i].fDstClipQuad = clip;
+        textures[i].fPreViewMatrix = xform < 0 ? nullptr : preViewMatrices + xform;
         textures[i].fAlpha = set[i].fAlpha * paint.getAlphaf();
         textures[i].fAAFlags = SkToGrQuadAAFlags(set[i].fAAFlags);
 
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 210dfee..9db287a 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -307,10 +307,10 @@
         GrAAType overallAAType = GrAAType::kNone; // aa type maximally compatible with all dst rects
         bool mustFilter = false;
         fCanSkipAllocatorGather = static_cast<unsigned>(true);
-        // All dst rects are transformed by the same view matrix, so their quad types are identical,
-        // unless an entry provides a dstClip that forces quad type to be at least standard.
-        GrQuadType baseQuadType = GrQuadTypeForTransformedRect(viewMatrix);
-        fQuads.reserve(cnt, baseQuadType);
+        // Most dst rects are transformed by the same view matrix, so their quad types start
+        // identical, unless an entry provides a dstClip or additional transform that changes it.
+        // The quad list will automatically adapt to that.
+        fQuads.reserve(cnt, GrQuadTypeForTransformedRect(viewMatrix));
 
         for (unsigned p = 0; p < fProxyCnt; ++p) {
             fProxies[p].fProxy = SkRef(set[p].fProxy.get());
@@ -321,12 +321,17 @@
                 fCanSkipAllocatorGather = static_cast<unsigned>(false);
             }
 
+            SkMatrix ctm = viewMatrix;
+            if (set[p].fPreViewMatrix) {
+                ctm.preConcat(*set[p].fPreViewMatrix);
+            }
+
             // Use dstRect unless dstClip is provided, which is assumed to be a quad
             auto quad = set[p].fDstClipQuad == nullptr ?
-                    GrPerspQuad::MakeFromRect(set[p].fDstRect, viewMatrix) :
-                    GrPerspQuad::MakeFromSkQuad(set[p].fDstClipQuad, viewMatrix);
-            GrQuadType quadType = baseQuadType;
-            if (set[p].fDstClipQuad && baseQuadType != GrQuadType::kPerspective) {
+                    GrPerspQuad::MakeFromRect(set[p].fDstRect, ctm) :
+                    GrPerspQuad::MakeFromSkQuad(set[p].fDstClipQuad, ctm);
+            GrQuadType quadType = GrQuadTypeForTransformedRect(ctm);
+            if (set[p].fDstClipQuad && quadType != GrQuadType::kPerspective) {
                 quadType = GrQuadType::kStandard;
             }
 
@@ -342,7 +347,7 @@
             }
             if (!mustFilter && this->filter() != GrSamplerState::Filter::kNearest) {
                 mustFilter = quadType != GrQuadType::kRect ||
-                             GrTextureOp::GetFilterHasEffect(viewMatrix, set[p].fSrcRect,
+                             GrTextureOp::GetFilterHasEffect(ctm, set[p].fSrcRect,
                                                              set[p].fDstRect);
             }
             float alpha = SkTPin(set[p].fAlpha, 0.f, 1.f);