C++程序  |  601行  |  27.35 KB

/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#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"
#include "GrTextureAdjuster.h"
#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();
}

//////////////////////////////////////////////////////////////////////////////
//  Helper functions for dropping src rect constraint in bilerp mode.

static const SkScalar kColorBleedTolerance = 0.001f;

static bool has_aligned_samples(const SkRect& srcRect, const SkRect& transformedRect) {
    // detect pixel disalignment
    if (SkScalarAbs(SkScalarRoundToScalar(transformedRect.left()) - transformedRect.left()) < kColorBleedTolerance &&
        SkScalarAbs(SkScalarRoundToScalar(transformedRect.top())  - transformedRect.top())  < kColorBleedTolerance &&
        SkScalarAbs(transformedRect.width()  - srcRect.width())  < kColorBleedTolerance &&
        SkScalarAbs(transformedRect.height() - srcRect.height()) < kColorBleedTolerance) {
        return true;
    }
    return false;
}

static bool may_color_bleed(const SkRect& srcRect,
                            const SkRect& transformedRect,
                            const SkMatrix& m,
                            GrFSAAType fsaaType) {
    // Only gets called if has_aligned_samples returned false.
    // So we can assume that sampling is axis aligned but not texel aligned.
    SkASSERT(!has_aligned_samples(srcRect, transformedRect));
    SkRect innerSrcRect(srcRect), innerTransformedRect, outerTransformedRect(transformedRect);
    if (GrFSAAType::kUnifiedMSAA == fsaaType) {
        innerSrcRect.inset(SK_Scalar1, SK_Scalar1);
    } else {
        innerSrcRect.inset(SK_ScalarHalf, SK_ScalarHalf);
    }
    m.mapRect(&innerTransformedRect, innerSrcRect);

    // The gap between outerTransformedRect and innerTransformedRect
    // represents the projection of the source border area, which is
    // problematic for color bleeding.  We must check whether any
    // destination pixels sample the border area.
    outerTransformedRect.inset(kColorBleedTolerance, kColorBleedTolerance);
    innerTransformedRect.outset(kColorBleedTolerance, kColorBleedTolerance);
    SkIRect outer, inner;
    outerTransformedRect.round(&outer);
    innerTransformedRect.round(&inner);
    // If the inner and outer rects round to the same result, it means the
    // border does not overlap any pixel centers. Yay!
    return inner != outer;
}

static bool can_ignore_bilerp_constraint(const GrTextureProducer& producer,
                                         const SkRect& srcRect,
                                         const SkMatrix& srcRectToDeviceSpace,
                                         GrFSAAType fsaaType) {
    if (srcRectToDeviceSpace.rectStaysRect()) {
        // sampling is axis-aligned
        SkRect transformedRect;
        srcRectToDeviceSpace.mapRect(&transformedRect, srcRect);

        if (has_aligned_samples(srcRect, transformedRect) ||
            !may_color_bleed(srcRect, transformedRect, srcRectToDeviceSpace, fsaaType)) {
            return true;
        }
    }
    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.
 */
static bool can_use_draw_texture(const SkPaint& paint) {
    return (!paint.getColorFilter() && !paint.getShader() && !paint.getMaskFilter() &&
            !paint.getImageFilter() && paint.getFilterQuality() < kMedium_SkFilterQuality);
}

// 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,
                                dstInfo.colorSpace(), kPremul_SkAlphaType);
    GrSamplerState::Filter filter;
    switch (paint.getFilterQuality()) {
        case kNone_SkFilterQuality:
            filter = GrSamplerState::Filter::kNearest;
            break;
        case kLow_SkFilterQuality:
            filter = GrSamplerState::Filter::kBilerp;
            break;
        case kMedium_SkFilterQuality:
        case kHigh_SkFilterQuality:
            SK_ABORT("Quality level not allowed.");
    }

    // Must specify the strict constraint when the proxy is not functionally exact and the src
    // rect would access pixels outside the proxy's content area without the constraint.
    if (constraint != SkCanvas::kStrict_SrcRectConstraint &&
        !GrProxyProvider::IsFunctionallyExact(proxy.get())) {
        // Conservative estimate of how much a coord could be outset from src rect:
        // 1/2 pixel for AA and 1/2 pixel for bilerp
        float buffer = 0.5f * (aa == GrAA::kYes) +
                       0.5f * (filter == GrSamplerState::Filter::kBilerp);
        SkRect safeBounds = SkRect::MakeWH(proxy->width(), proxy->height());
        safeBounds.inset(buffer, buffer);
        if (!safeBounds.contains(srcRect)) {
            constraint = SkCanvas::kStrict_SrcRectConstraint;
        }
    }
    SkPMColor4f color;
    if (GrPixelConfigIsAlphaOnly(proxy->config())) {
        color = SkColor4fPrepForDst(paint.getColor4f(), dstInfo, *rtc->caps()).premul();
    } else {
        float paintAlpha = paint.getColor4f().fA;
        color = { paintAlpha, paintAlpha, paintAlpha, paintAlpha };
    }

    if (dstClip) {
        // Get source coords corresponding to dstClip
        SkPoint srcQuad[4];
        GrMapRectPoints(dstRect, srcRect, dstClip, srcQuad, 4);

        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));
    }
}

// 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)) {
        // 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(rtc, clip, ctm, paint, src, dst, dstClip, aa, aaFlags, constraint,
                     std::move(proxy), producer->alphaType(),  producer->colorSpace());
        return;
    }

    const SkMaskFilter* mf = paint.getMaskFilter();

    // The shader expects proper local coords, so we can't replace local coords with texture coords
    // if the shader will be used. If we have a mask filter we will change the underlying geometry
    // that is rendered.
    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 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(), ctm, srcToDst,
            context->priv().options().fSharpenMipmappedTextures, &doBicubic);
    const GrSamplerState::Filter* filterMode = doBicubic ? nullptr : &fm;

    GrTextureProducer::FilterConstraint constraintMode;
    if (SkCanvas::kFast_SrcRectConstraint == constraint) {
        constraintMode = GrTextureAdjuster::kNo_FilterConstraint;
    } else {
        constraintMode = GrTextureAdjuster::kYes_FilterConstraint;
    }

    // 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 = 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(ctm, srcToDst);
        if (can_ignore_bilerp_constraint(*producer, src, combinedMatrix, rtc->fsaaType())) {
            constraintMode = GrTextureAdjuster::kNo_FilterConstraint;
        }
    }

    SkMatrix textureMatrix;
    if (canUseTextureCoordsAsLocalCoords) {
        textureMatrix = SkMatrix::I();
    } else {
        if (!srcToDst.invert(&textureMatrix)) {
            return;
        }
    }
    auto fp = producer->createFragmentProcessor(textureMatrix, src, constraintMode,
                                                coordsAllInsideSrcRect, filterMode);
    fp = GrColorSpaceXformEffect::Make(std::move(fp), producer->colorSpace(), producer->alphaType(),
                                       rtc->colorSpaceInfo().colorSpace());
    if (!fp) {
        return;
    }

    GrPaint grPaint;
    if (!SkPaintToGrPaintWithTexture(context, rtc->colorSpaceInfo(), paint, ctm,
                                     std::move(fp), producer->isAlphaOnly(), &grPaint)) {
        return;
    }

    if (!mf) {
        // 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 SkMatrix* preViewMatrix, 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;
    }

    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

    // 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(ctm, srcToDst, paint.getFilterQuality());

        GrYUVAImageTextureMaker maker(fContext.get(), image, useDecal);
        draw_texture_producer(fContext.get(), fRenderTargetContext.get(), this->clip(), 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(this->context(),
                                                                          &pinnedUniqueID)) {
        SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
        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(), 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(), 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(), 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(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(), 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(), 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_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);

    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);

            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),
                    xform < 0 ? nullptr : preViewMatrices + xform, 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(this->context(),
                                                                               &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;
            }
        }

        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);

        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);
}