Add general quad API to SkGpuDevice

Heavily refactors SkGpuDevice's internal texturing code in an attempt
to consolidate entry points for drawing an image. Helps lay the ground
work for eventually implementing bitmap tiling with per-edge AA.

Bug: skia:
Change-Id: I9feb86d5315d73119deb21e954c45e45513a63f6
Reviewed-on: https://skia-review.googlesource.com/c/191571
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index 21815f7..da2c887 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -6,9 +6,12 @@
  */
 
 #include "SkGpuDevice.h"
+
+#include "GrBitmapTextureMaker.h"
 #include "GrBlurUtils.h"
 #include "GrCaps.h"
 #include "GrColorSpaceXform.h"
+#include "GrImageTextureMaker.h"
 #include "GrRenderTargetContext.h"
 #include "GrShape.h"
 #include "GrStyle.h"
@@ -16,11 +19,15 @@
 #include "GrTextureMaker.h"
 #include "SkDraw.h"
 #include "SkGr.h"
+#include "SkImage_Base.h"
 #include "SkMaskFilterBase.h"
+#include "SkYUVAIndex.h"
 #include "effects/GrBicubicEffect.h"
 #include "effects/GrSimpleTextureEffect.h"
 #include "effects/GrTextureDomain.h"
 
+namespace {
+
 static inline bool use_shader(bool textureIsAlphaOnly, const SkPaint& paint) {
     return textureIsAlphaOnly && paint.getShader();
 }
@@ -87,6 +94,73 @@
     return false;
 }
 
+enum class ImageDrawMode {
+    // Src and dst have been restricted to the image content. May need to clamp, no need to decal.
+    kOptimized,
+    // Src and dst are their original sizes, requires use of a decal instead of plain clamping.
+    // This is used when a dst clip is provided and extends outside of the optimized dst rect.
+    kDecal,
+    // Src or dst are empty, or do not intersect the image content so don't draw anything.
+    kSkip
+};
+
+/**
+ * Optimize the src rect sampling area within an image (sized 'width' x 'height') such that
+ * 'outSrcRect' will be completely contained in the image's bounds. The corresponding rect
+ * to draw will be output to 'outDstRect'. The mapping between src and dst will be cached in
+ * 'srcToDst'. Outputs are not always updated when kSkip is returned.
+ *
+ * If 'origSrcRect' is null, implicitly use the image bounds. If 'origDstRect' is null, use the
+ * original src rect. 'dstClip' should be null when there is no additional clipping.
+ */
+static ImageDrawMode optimize_sample_area(const SkISize& image, const SkRect* origSrcRect,
+                                          const SkRect* origDstRect, const SkPoint dstClip[4],
+                                          SkRect* outSrcRect, SkRect* outDstRect,
+                                          SkMatrix* srcToDst) {
+    SkRect srcBounds = SkRect::MakeIWH(image.fWidth, image.fHeight);
+
+    SkRect src = origSrcRect ? *origSrcRect : srcBounds;
+    SkRect dst = origDstRect ? *origDstRect : src;
+
+    if (src.isEmpty() || dst.isEmpty()) {
+        return ImageDrawMode::kSkip;
+    }
+
+    if (outDstRect) {
+        srcToDst->setRectToRect(src, dst, SkMatrix::kFill_ScaleToFit);
+    } else {
+        srcToDst->setIdentity();
+    }
+
+    if (origSrcRect && !srcBounds.contains(src)) {
+        if (!src.intersect(srcBounds)) {
+            return ImageDrawMode::kSkip;
+        }
+        srcToDst->mapRect(&dst, src);
+
+        // Both src and dst have gotten smaller. If dstClip is provided, confirm it is still
+        // contained in dst, otherwise cannot optimize the sample area and must use a decal instead
+        if (dstClip) {
+            for (int i = 0; i < 4; ++i) {
+                if (!dst.contains(dstClip[i].fX, dstClip[i].fY)) {
+                    // Must resort to using a decal mode restricted to the clipped 'src', and
+                    // use the original dst rect (filling in src bounds as needed)
+                    *outSrcRect = src;
+                    *outDstRect = (origDstRect ? *origDstRect
+                                               : (origSrcRect ? *origSrcRect : srcBounds));
+                    return ImageDrawMode::kDecal;
+                }
+            }
+        }
+    }
+
+    // The original src and dst were fully contained in the image, or there was no dst clip to
+    // worry about, or the clip was still contained in the restricted dst rect.
+    *outSrcRect = src;
+    *outDstRect = dst;
+    return ImageDrawMode::kOptimized;
+}
+
 /**
  * Checks whether the paint is compatible with using GrRenderTargetContext::drawTexture. It is more
  * efficient than the GrTextureProducer general case.
@@ -96,20 +170,12 @@
             !paint.getImageFilter() && paint.getFilterQuality() < kMedium_SkFilterQuality);
 }
 
-static void draw_texture(const SkPaint& paint, const SkMatrix& ctm, const SkRect* src,
-                         const SkRect* dst, GrAA aa, SkCanvas::SrcRectConstraint constraint,
-                         sk_sp<GrTextureProxy> proxy, SkAlphaType alphaType,
-                         SkColorSpace* colorSpace, const GrClip& clip, GrRenderTargetContext* rtc) {
-    SkASSERT(!(SkToBool(src) && !SkToBool(dst)));
-    SkRect srcRect = src ? *src : SkRect::MakeWH(proxy->width(), proxy->height());
-    SkRect dstRect = dst ? *dst : srcRect;
-    if (src && !SkRect::MakeIWH(proxy->width(), proxy->height()).contains(srcRect)) {
-        // Shrink the src rect to be within bounds and proportionately shrink the dst rect.
-        SkMatrix srcToDst;
-        srcToDst.setRectToRect(srcRect, dstRect, SkMatrix::kFill_ScaleToFit);
-        SkAssertResult(srcRect.intersect(SkRect::MakeIWH(proxy->width(), proxy->height())));
-        srcToDst.mapRect(&dstRect, srcRect);
-    }
+// Assumes srcRect and dstRect have already been optimized to fit the proxy
+static void draw_texture(GrRenderTargetContext* rtc, const GrClip& clip, const SkMatrix& ctm,
+                         const SkPaint& paint, const SkRect& srcRect, const SkRect& dstRect,
+                         const SkPoint dstClip[4], GrAA aa, GrQuadAAFlags aaFlags,
+                         SkCanvas::SrcRectConstraint constraint, sk_sp<GrTextureProxy> proxy,
+                         SkAlphaType alphaType, SkColorSpace* colorSpace) {
     const GrColorSpaceInfo& dstInfo(rtc->colorSpaceInfo());
     auto textureXform =
         GrColorSpaceXform::Make(colorSpace          , alphaType,
@@ -148,107 +214,42 @@
         float paintAlpha = paint.getColor4f().fA;
         color = { paintAlpha, paintAlpha, paintAlpha, paintAlpha };
     }
-    GrQuadAAFlags aaFlags = aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
-    rtc->drawTexture(clip, std::move(proxy), filter, paint.getBlendMode(), color, srcRect, dstRect,
-                     aa, aaFlags, constraint, ctm, std::move(textureXform));
-}
 
-//////////////////////////////////////////////////////////////////////////////
+    if (dstClip) {
+        // Get source coords corresponding to dstClip
+        SkPoint srcQuad[4];
+        GrMapRectPoints(dstRect, srcRect, dstClip, srcQuad, 4);
 
-void SkGpuDevice::drawPinnedTextureProxy(sk_sp<GrTextureProxy> proxy, uint32_t pinnedUniqueID,
-                                         SkColorSpace* colorSpace, SkAlphaType alphaType,
-                                         const SkRect* srcRect, const SkRect* dstRect,
-                                         SkCanvas::SrcRectConstraint constraint,
-                                         const SkMatrix& viewMatrix, const SkPaint& paint) {
-    GrAA aa = GrAA(paint.isAntiAlias());
-    if (can_use_draw_texture(paint)) {
-        draw_texture(paint, viewMatrix, srcRect, dstRect, aa, constraint, std::move(proxy),
-                     alphaType, colorSpace, this->clip(), fRenderTargetContext.get());
-        return;
+        rtc->drawTextureQuad(clip, std::move(proxy), filter, paint.getBlendMode(), color,
+                             srcQuad, dstClip, aa, aaFlags,
+                             constraint == SkCanvas::kStrict_SrcRectConstraint ? &srcRect : nullptr,
+                             ctm, std::move(textureXform));
+    } else {
+        rtc->drawTexture(clip, std::move(proxy), filter, paint.getBlendMode(), color, srcRect,
+                         dstRect, aa, aaFlags, constraint, ctm, std::move(textureXform));
     }
-    GrTextureAdjuster adjuster(this->context(), std::move(proxy), alphaType, pinnedUniqueID,
-                               colorSpace);
-    this->drawTextureProducer(&adjuster, srcRect, dstRect, constraint, viewMatrix, paint, false);
 }
 
-void SkGpuDevice::drawTextureProducer(GrTextureProducer* producer,
-                                      const SkRect* srcRect,
-                                      const SkRect* dstRect,
-                                      SkCanvas::SrcRectConstraint constraint,
-                                      const SkMatrix& viewMatrix,
-                                      const SkPaint& paint,
-                                      bool attemptDrawTexture) {
+// Assumes srcRect and dstRect have already been optimized to fit the proxy.
+static void draw_texture_producer(GrContext* context, GrRenderTargetContext* rtc,
+                                  const GrClip& clip, const SkMatrix& ctm,
+                                  const SkPaint& paint, GrTextureProducer* producer,
+                                  const SkRect& src, const SkRect& dst, const SkPoint dstClip[4],
+                                  const SkMatrix& srcToDst, GrAA aa, GrQuadAAFlags aaFlags,
+                                  SkCanvas::SrcRectConstraint constraint, bool attemptDrawTexture) {
     if (attemptDrawTexture && can_use_draw_texture(paint)) {
-        GrAA aa = GrAA(paint.isAntiAlias());
         // We've done enough checks above to allow us to pass ClampNearest() and not check for
         // scaling adjustments.
         auto proxy = producer->refTextureProxyForParams(GrSamplerState::ClampNearest(), nullptr);
         if (!proxy) {
             return;
         }
-        draw_texture(paint, viewMatrix, srcRect, dstRect, aa, constraint, std::move(proxy),
-                     producer->alphaType(), producer->colorSpace(), this->clip(),
-                     fRenderTargetContext.get());
+
+        draw_texture(rtc, clip, ctm, paint, src, dst, dstClip, aa, aaFlags, constraint,
+                     std::move(proxy), producer->alphaType(),  producer->colorSpace());
         return;
     }
 
-    // This is the funnel for all non-tiled bitmap/image draw calls. Log a histogram entry.
-    SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
-
-    // Figure out the actual dst and src rect by clipping the src rect to the bounds of the
-    // adjuster. If the src rect is clipped then the dst rect must be recomputed. Also determine
-    // the matrix that maps the src rect to the dst rect.
-    SkRect clippedSrcRect;
-    SkRect clippedDstRect;
-    const SkRect srcBounds = SkRect::MakeIWH(producer->width(), producer->height());
-    SkMatrix srcToDstMatrix;
-    if (srcRect) {
-        if (!dstRect) {
-            dstRect = &srcBounds;
-        }
-        if (!srcBounds.contains(*srcRect)) {
-            clippedSrcRect = *srcRect;
-            if (!clippedSrcRect.intersect(srcBounds)) {
-                return;
-            }
-            if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
-                return;
-            }
-            srcToDstMatrix.mapRect(&clippedDstRect, clippedSrcRect);
-        } else {
-            clippedSrcRect = *srcRect;
-            clippedDstRect = *dstRect;
-            if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
-                return;
-            }
-        }
-    } else {
-        clippedSrcRect = srcBounds;
-        if (dstRect) {
-            clippedDstRect = *dstRect;
-            if (!srcToDstMatrix.setRectToRect(srcBounds, *dstRect, SkMatrix::kFill_ScaleToFit)) {
-                return;
-            }
-        } else {
-            clippedDstRect = srcBounds;
-            srcToDstMatrix.reset();
-        }
-    }
-
-    // Now that we have both the view and srcToDst matrices, log our scale factor.
-    LogDrawScaleFactor(viewMatrix, srcToDstMatrix, paint.getFilterQuality());
-
-    this->drawTextureProducerImpl(producer, clippedSrcRect, clippedDstRect, constraint, viewMatrix,
-                                  srcToDstMatrix, paint);
-}
-
-void SkGpuDevice::drawTextureProducerImpl(GrTextureProducer* producer,
-                                          const SkRect& clippedSrcRect,
-                                          const SkRect& clippedDstRect,
-                                          SkCanvas::SrcRectConstraint constraint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkMatrix& srcToDstMatrix,
-                                          const SkPaint& paint) {
     const SkMaskFilter* mf = paint.getMaskFilter();
 
     // The shader expects proper local coords, so we can't replace local coords with texture coords
@@ -257,17 +258,16 @@
     bool canUseTextureCoordsAsLocalCoords = !use_shader(producer->isAlphaOnly(), paint) && !mf;
 
     // Specifying the texture coords as local coordinates is an attempt to enable more GrDrawOp
-    // combining by not baking anything about the srcRect, dstRect, or viewMatrix, into the texture
+    // combining by not baking anything about the srcRect, dstRect, or ctm, into the texture
     // FP. In the future this should be an opaque optimization enabled by the combination of
     // GrDrawOp/GP and FP.
     if (mf && as_MFB(mf)->hasFragmentProcessor()) {
         mf = nullptr;
     }
-
     bool doBicubic;
     GrSamplerState::Filter fm = GrSkFilterQualityToGrFilterMode(
-            paint.getFilterQuality(), viewMatrix, srcToDstMatrix,
-            fContext->priv().options().fSharpenMipmappedTextures, &doBicubic);
+            paint.getFilterQuality(), ctm, srcToDst,
+            context->priv().options().fSharpenMipmappedTextures, &doBicubic);
     const GrSamplerState::Filter* filterMode = doBicubic ? nullptr : &fm;
 
     GrTextureProducer::FilterConstraint constraintMode;
@@ -280,58 +280,302 @@
     // If we have to outset for AA then we will generate texture coords outside the src rect. The
     // same happens for any mask filter that extends the bounds rendered in the dst.
     // This is conservative as a mask filter does not have to expand the bounds rendered.
-    bool coordsAllInsideSrcRect = !paint.isAntiAlias() && !mf;
+    bool coordsAllInsideSrcRect = aaFlags == GrQuadAAFlags::kNone && !mf;
 
     // Check for optimization to drop the src rect constraint when on bilerp.
     if (filterMode && GrSamplerState::Filter::kBilerp == *filterMode &&
         GrTextureAdjuster::kYes_FilterConstraint == constraintMode && coordsAllInsideSrcRect) {
         SkMatrix combinedMatrix;
-        combinedMatrix.setConcat(viewMatrix, srcToDstMatrix);
-        if (can_ignore_bilerp_constraint(*producer, clippedSrcRect, combinedMatrix,
-                                         fRenderTargetContext->fsaaType())) {
+        combinedMatrix.setConcat(ctm, srcToDst);
+        if (can_ignore_bilerp_constraint(*producer, src, combinedMatrix, rtc->fsaaType())) {
             constraintMode = GrTextureAdjuster::kNo_FilterConstraint;
         }
     }
 
-    const SkMatrix* textureMatrix;
-    SkMatrix tempMatrix;
+    SkMatrix textureMatrix;
     if (canUseTextureCoordsAsLocalCoords) {
-        textureMatrix = &SkMatrix::I();
+        textureMatrix = SkMatrix::I();
     } else {
-        if (!srcToDstMatrix.invert(&tempMatrix)) {
+        if (!srcToDst.invert(&textureMatrix)) {
             return;
         }
-        textureMatrix = &tempMatrix;
     }
-    auto fp = producer->createFragmentProcessor(*textureMatrix, clippedSrcRect, constraintMode,
+    auto fp = producer->createFragmentProcessor(textureMatrix, src, constraintMode,
                                                 coordsAllInsideSrcRect, filterMode);
     fp = GrColorSpaceXformEffect::Make(std::move(fp), producer->colorSpace(), producer->alphaType(),
-                                       fRenderTargetContext->colorSpaceInfo().colorSpace());
+                                       rtc->colorSpaceInfo().colorSpace());
     if (!fp) {
         return;
     }
 
     GrPaint grPaint;
-    if (!SkPaintToGrPaintWithTexture(fContext.get(), fRenderTargetContext->colorSpaceInfo(), paint,
-                                     viewMatrix, std::move(fp), producer->isAlphaOnly(),
-                                     &grPaint)) {
-        return;
-    }
-    GrAA aa = GrAA(paint.isAntiAlias());
-    if (canUseTextureCoordsAsLocalCoords) {
-        fRenderTargetContext->fillRectToRect(this->clip(), std::move(grPaint), aa, viewMatrix,
-                                             clippedDstRect, clippedSrcRect);
+    if (!SkPaintToGrPaintWithTexture(context, rtc->colorSpaceInfo(), paint, ctm,
+                                     std::move(fp), producer->isAlphaOnly(), &grPaint)) {
         return;
     }
 
     if (!mf) {
-        fRenderTargetContext->drawRect(this->clip(), std::move(grPaint), aa, viewMatrix,
-                                       clippedDstRect);
+        // Can draw the image directly (any mask filter on the paint was converted to an FP already)
+        if (dstClip) {
+            SkPoint srcClipPoints[4];
+            SkPoint* srcClip = nullptr;
+            if (canUseTextureCoordsAsLocalCoords) {
+                // Calculate texture coordinates that match the dst clip
+                GrMapRectPoints(dst, src, dstClip, srcClipPoints, 4);
+                srcClip = srcClipPoints;
+            }
+            rtc->fillQuadWithEdgeAA(clip, std::move(grPaint), aa, aaFlags, ctm, dstClip, srcClip);
+        } else {
+            // Provide explicit texture coords when possible, otherwise rely on texture matrix
+            rtc->fillRectWithEdgeAA(clip, std::move(grPaint), aa, aaFlags, ctm, dst,
+                                    canUseTextureCoordsAsLocalCoords ? &src : nullptr);
+        }
+    } else {
+        // Must draw the mask filter as a GrShape. For now, this loses the per-edge AA information
+        // since it always draws with AA, but that is should not be noticeable since the mask filter
+        // is probably a blur.
+        GrShape shape;
+        if (dstClip) {
+            // Represent it as an SkPath formed from the dstClip
+            SkPath path;
+            path.addPoly(dstClip, 4, true);
+            shape = GrShape(path);
+        } else {
+            shape = GrShape(dst);
+        }
+
+        GrBlurUtils::drawShapeWithMaskFilter(
+                context, rtc, clip, shape, std::move(grPaint), ctm, mf);
+    }
+}
+
+} // anonymous namespace
+
+//////////////////////////////////////////////////////////////////////////////
+
+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) {
+    SkRect src;
+    SkRect dst;
+    SkMatrix srcToDst;
+    ImageDrawMode mode = optimize_sample_area(SkISize::Make(image->width(), image->height()),
+                                              srcRect, dstRect, dstClip, &src, &dst, &srcToDst);
+    if (mode == ImageDrawMode::kSkip) {
         return;
     }
 
-    GrShape shape(clippedDstRect, GrStyle::SimpleFill());
+    if (src.contains(image->bounds())) {
+        constraint = SkCanvas::kFast_SrcRectConstraint;
+    }
+    // Depending on the nature of image, it can flow through more or less optimal pipelines
+    bool useDecal = mode == ImageDrawMode::kDecal;
+    bool attemptDrawTexture = !useDecal; // rtc->drawTexture() only clamps
 
-    GrBlurUtils::drawShapeWithMaskFilter(this->context(), fRenderTargetContext.get(), this->clip(),
-                                         shape, std::move(grPaint), viewMatrix, mf);
+    // 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());
+
+        GrYUVAImageTextureMaker maker(fContext.get(), image, useDecal);
+        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), this->ctm(),
+                              paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint,
+                              /* attempt draw texture */ false);
+        return;
+    }
+
+    // Pinned texture proxies can be rendered directly as textures, or with relatively simple
+    // adjustments applied to the image content (scaling, mipmaps, color space, etc.)
+    uint32_t pinnedUniqueID;
+    if (sk_sp<GrTextureProxy> proxy = as_IB(image)->refPinnedTextureProxy(&pinnedUniqueID)) {
+        SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
+        LogDrawScaleFactor(this->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,
+                         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(),
+                              paint, &adjuster, src, dst, dstClip, srcToDst, aa, aaFlags,
+                              constraint, /* attempt draw_texture */ false);
+        return;
+    }
+
+    // Next up, try tiling the image
+    // 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)) {
+        // only support tiling as bitmap at the moment, so force raster-version
+        if (!as_IB(image)->getROPixels(&bm)) {
+            return;
+        }
+        this->drawBitmapRect(bm, &src, dst, paint, constraint);
+        return;
+    }
+
+    // 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());
+
+    // 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(),
+                              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(),
+                              paint, &maker, src, dst, dstClip, srcToDst, aa, aaFlags, constraint,
+                              attemptDrawTexture);
+    }
+
+    // Otherwise don't know how to draw it
+}
+
+// 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,
+                                     SkCanvas::SrcRectConstraint constraint) {
+    SkASSERT(count > 0);
+
+    if (!can_use_draw_texture(paint)) {
+        // Send every entry through drawImageQuad() to handle the more complicated paint
+        int dstClipIndex = 0;
+        for (int i = 0; i < count; ++i) {
+            // Only no clip or quad clip are supported
+            SkASSERT(!dstClipCounts || dstClipCounts[i] == 0 || dstClipCounts[i] == 4);
+            // 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);
+            if (dstClipCounts) {
+                dstClipIndex += dstClipCounts[i];
+            }
+        }
+        return;
+    }
+
+    GrSamplerState::Filter filter = kNone_SkFilterQuality == paint.getFilterQuality() ?
+            GrSamplerState::Filter::kNearest : GrSamplerState::Filter::kBilerp;
+    SkBlendMode mode = paint.getBlendMode();
+
+    SkAutoTArray<GrRenderTargetContext::TextureSetEntry> textures(count);
+    // We accumulate compatible proxies until we find an an incompatible one or reach the end and
+    // issue the accumulated 'n' draws starting at 'base'.
+    int base = 0, n = 0;
+    auto draw = [&] {
+        if (n > 0) {
+            auto textureXform = GrColorSpaceXform::Make(
+                    set[base].fImage->colorSpace(), set[base].fImage->alphaType(),
+                    fRenderTargetContext->colorSpaceInfo().colorSpace(), kPremul_SkAlphaType);
+            fRenderTargetContext->drawTextureSet(this->clip(), textures.get() + base, n,
+                                                 filter, mode, GrAA::kYes, this->ctm(),
+                                                 std::move(textureXform));
+        }
+    };
+    int dstClipIndex = 0;
+    for (int i = 0; i < count; ++i) {
+        // Manage the dst clip pointer tracking before any continues are used so we don't lose
+        // our place in the dstClips array.
+        int clipCount = (dstClipCounts ? dstClipCounts[i] : 0);
+        SkASSERT(clipCount == 0 || (dstClipCounts[i] == 4 && dstClips));
+        const SkPoint* clip = clipCount > 0 ? dstClips + dstClipIndex : nullptr;
+        if (dstClipCounts) {
+            dstClipIndex += dstClipCounts[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;
+        }
+
+        uint32_t uniqueID;
+        textures[i].fProxy = as_IB(set[i].fImage.get())->refPinnedTextureProxy(&uniqueID);
+        if (!textures[i].fProxy) {
+            // FIXME(michaelludwig) - If asTextureProxyRef fails, does going through drawImageQuad
+            // make sense? Does that catch the lazy-image cases then?
+            // FIXME(michaelludwig) - Both refPinnedTextureProxy and asTextureProxyRef for YUVA
+            // images force flatten the planes. It would be nice to detect a YUVA image entry and
+            // send it to drawImageQuad (which uses a special effect for YUV)
+            textures[i].fProxy =
+                    as_IB(set[i].fImage.get())
+                            ->asTextureProxyRef(fContext.get(), GrSamplerState::ClampBilerp(),
+                                                nullptr);
+            // If we failed to make a proxy then flush the accumulated set and reset for the next
+            // image.
+            if (!textures[i].fProxy) {
+                draw();
+                base = i + 1;
+                n = 0;
+                continue;
+            }
+        }
+        textures[i].fSrcRect = set[i].fSrcRect;
+        textures[i].fDstRect = set[i].fDstRect;
+        textures[i].fDstClipQuad = clip;
+        textures[i].fAlpha = set[i].fAlpha * paint.getAlphaf();
+        textures[i].fAAFlags = SkToGrQuadAAFlags(set[i].fAAFlags);
+
+        if (n > 0 &&
+            (!GrTextureProxy::ProxiesAreCompatibleAsDynamicState(textures[i].fProxy.get(),
+                                                                 textures[base].fProxy.get()) ||
+             set[i].fImage->alphaType() != set[base].fImage->alphaType() ||
+             !SkColorSpace::Equals(set[i].fImage->colorSpace(), set[base].fImage->colorSpace()))) {
+            draw();
+            base = i;
+            n = 1;
+        } else {
+            ++n;
+        }
+    }
+    draw();
+}
+
+// TODO (michaelludwig) - to be removed when drawBitmapRect doesn't need it anymore
+void SkGpuDevice::drawTextureProducer(GrTextureProducer* producer,
+                                      const SkRect* srcRect,
+                                      const SkRect* dstRect,
+                                      SkCanvas::SrcRectConstraint constraint,
+                                      const SkMatrix& viewMatrix,
+                                      const SkPaint& paint,
+                                      bool attemptDrawTexture) {
+    // The texture refactor split the old logic of drawTextureProducer into the beginning of
+    // drawImageQuad() and into the static draw_texture_producer. Replicate necessary logic that
+    // drawImageQuad() handles.
+    SkRect src;
+    SkRect dst;
+    SkMatrix srcToDst;
+    ImageDrawMode mode = optimize_sample_area(SkISize::Make(producer->width(), producer->height()),
+                                              srcRect, dstRect, nullptr, &src, &dst, &srcToDst);
+    if (mode == ImageDrawMode::kSkip) {
+        return;
+    }
+    // There's no dstClip to worry about and the producer is already made so we wouldn't be able
+    // to tell it to use decals if we had to
+    SkASSERT(mode != ImageDrawMode::kDecal);
+
+    draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), viewMatrix,
+                          paint, producer, src, dst, /* clip */ nullptr, srcToDst,
+                          GrAA(paint.isAntiAlias()),
+                          paint.isAntiAlias() ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+                          constraint, attemptDrawTexture);
 }