• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/gpu/ganesh/Device.h"
9 
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkColorSpace.h"
12 #include "include/gpu/GrRecordingContext.h"
13 #include "include/private/base/SkTPin.h"
14 #include "src/core/SkDraw.h"
15 #include "src/core/SkMaskFilterBase.h"
16 #include "src/core/SkSamplingPriv.h"
17 #include "src/core/SkSpecialImage.h"
18 #include "src/gpu/TiledTextureUtils.h"
19 #include "src/gpu/ganesh/GrBlurUtils.h"
20 #include "src/gpu/ganesh/GrColorSpaceXform.h"
21 #include "src/gpu/ganesh/GrFPArgs.h"
22 #include "src/gpu/ganesh/GrFragmentProcessors.h"
23 #include "src/gpu/ganesh/GrOpsTypes.h"
24 #include "src/gpu/ganesh/GrStyle.h"
25 #include "src/gpu/ganesh/SkGr.h"
26 #include "src/gpu/ganesh/SurfaceDrawContext.h"
27 #include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
28 #include "src/gpu/ganesh/effects/GrTextureEffect.h"
29 #include "src/gpu/ganesh/geometry/GrRect.h"
30 #include "src/gpu/ganesh/geometry/GrStyledShape.h"
31 #include "src/gpu/ganesh/image/GrImageUtils.h"
32 #include "src/gpu/ganesh/image/SkImage_Ganesh.h"
33 #include "src/gpu/ganesh/image/SkSpecialImage_Ganesh.h"
34 #include "src/image/SkImage_Base.h"
35 
36 using namespace skia_private;
37 
38 namespace {
39 
use_shader(bool textureIsAlphaOnly,const SkPaint & paint)40 inline bool use_shader(bool textureIsAlphaOnly, const SkPaint& paint) {
41     return textureIsAlphaOnly && paint.getShader();
42 }
43 
44 //////////////////////////////////////////////////////////////////////////////
45 //  Helper functions for dropping src rect subset with GrSamplerState::Filter::kLinear.
46 
47 static const SkScalar kColorBleedTolerance = 0.001f;
48 
has_aligned_samples(const SkRect & srcRect,const SkRect & transformedRect)49 bool has_aligned_samples(const SkRect& srcRect, const SkRect& transformedRect) {
50     // detect pixel disalignment
51     if (SkScalarAbs(SkScalarRoundToScalar(transformedRect.left()) - transformedRect.left()) < kColorBleedTolerance &&
52         SkScalarAbs(SkScalarRoundToScalar(transformedRect.top())  - transformedRect.top())  < kColorBleedTolerance &&
53         SkScalarAbs(transformedRect.width()  - srcRect.width())  < kColorBleedTolerance &&
54         SkScalarAbs(transformedRect.height() - srcRect.height()) < kColorBleedTolerance) {
55         return true;
56     }
57     return false;
58 }
59 
may_color_bleed(const SkRect & srcRect,const SkRect & transformedRect,const SkMatrix & m,int numSamples)60 bool may_color_bleed(const SkRect& srcRect,
61                      const SkRect& transformedRect,
62                      const SkMatrix& m,
63                      int numSamples) {
64     // Only gets called if has_aligned_samples returned false.
65     // So we can assume that sampling is axis aligned but not texel aligned.
66     SkASSERT(!has_aligned_samples(srcRect, transformedRect));
67     SkRect innerSrcRect(srcRect), innerTransformedRect, outerTransformedRect(transformedRect);
68     if (numSamples > 1) {
69         innerSrcRect.inset(SK_Scalar1, SK_Scalar1);
70     } else {
71         innerSrcRect.inset(SK_ScalarHalf, SK_ScalarHalf);
72     }
73     m.mapRect(&innerTransformedRect, innerSrcRect);
74 
75     // The gap between outerTransformedRect and innerTransformedRect
76     // represents the projection of the source border area, which is
77     // problematic for color bleeding.  We must check whether any
78     // destination pixels sample the border area.
79     outerTransformedRect.inset(kColorBleedTolerance, kColorBleedTolerance);
80     innerTransformedRect.outset(kColorBleedTolerance, kColorBleedTolerance);
81     SkIRect outer, inner;
82     outerTransformedRect.round(&outer);
83     innerTransformedRect.round(&inner);
84     // If the inner and outer rects round to the same result, it means the
85     // border does not overlap any pixel centers. Yay!
86     return inner != outer;
87 }
88 
can_ignore_linear_filtering_subset(const SkRect & srcSubset,const SkMatrix & srcRectToDeviceSpace,int numSamples)89 bool can_ignore_linear_filtering_subset(const SkRect& srcSubset,
90                                         const SkMatrix& srcRectToDeviceSpace,
91                                         int numSamples) {
92     if (srcRectToDeviceSpace.rectStaysRect()) {
93         // sampling is axis-aligned
94         SkRect transformedRect;
95         srcRectToDeviceSpace.mapRect(&transformedRect, srcSubset);
96 
97         if (has_aligned_samples(srcSubset, transformedRect) ||
98             !may_color_bleed(srcSubset, transformedRect, srcRectToDeviceSpace, numSamples)) {
99             return true;
100         }
101     }
102     return false;
103 }
104 
105 //////////////////////////////////////////////////////////////////////////////
106 //  Helper functions for drawing an image with ganesh::SurfaceDrawContext
107 
108 /**
109  * Checks whether the paint is compatible with using SurfaceDrawContext::drawTexture. It is more
110  * efficient than the SkImage general case.
111  */
can_use_draw_texture(const SkPaint & paint,const SkSamplingOptions & sampling)112 bool can_use_draw_texture(const SkPaint& paint, const SkSamplingOptions& sampling) {
113     return (!paint.getColorFilter() && !paint.getShader() && !paint.getMaskFilter() &&
114             !paint.getImageFilter() && !paint.getBlender() && !sampling.isAniso() &&
115             !sampling.useCubic && sampling.mipmap == SkMipmapMode::kNone);
116 }
117 
texture_color(SkColor4f paintColor,float entryAlpha,GrColorType srcColorType,const GrColorInfo & dstColorInfo)118 SkPMColor4f texture_color(SkColor4f paintColor, float entryAlpha, GrColorType srcColorType,
119                           const GrColorInfo& dstColorInfo) {
120     paintColor.fA *= entryAlpha;
121     if (GrColorTypeIsAlphaOnly(srcColorType)) {
122         return SkColor4fPrepForDst(paintColor, dstColorInfo).premul();
123     } else {
124         float paintAlpha = SkTPin(paintColor.fA, 0.f, 1.f);
125         return { paintAlpha, paintAlpha, paintAlpha, paintAlpha };
126     }
127 }
128 
129 // Assumes srcRect and dstRect have already been optimized to fit the proxy
draw_texture(skgpu::ganesh::SurfaceDrawContext * sdc,const GrClip * clip,const SkMatrix & ctm,const SkPaint & paint,GrSamplerState::Filter filter,const SkRect & srcRect,const SkRect & dstRect,const SkPoint dstClip[4],GrQuadAAFlags aaFlags,SkCanvas::SrcRectConstraint constraint,GrSurfaceProxyView view,const GrColorInfo & srcColorInfo)130 void draw_texture(skgpu::ganesh::SurfaceDrawContext* sdc,
131                   const GrClip* clip,
132                   const SkMatrix& ctm,
133                   const SkPaint& paint,
134                   GrSamplerState::Filter filter,
135                   const SkRect& srcRect,
136                   const SkRect& dstRect,
137                   const SkPoint dstClip[4],
138                   GrQuadAAFlags aaFlags,
139                   SkCanvas::SrcRectConstraint constraint,
140                   GrSurfaceProxyView view,
141                   const GrColorInfo& srcColorInfo) {
142     if (GrColorTypeIsAlphaOnly(srcColorInfo.colorType())) {
143         view.concatSwizzle(skgpu::Swizzle("aaaa"));
144     }
145     const GrColorInfo& dstInfo = sdc->colorInfo();
146     auto textureXform = GrColorSpaceXform::Make(srcColorInfo, sdc->colorInfo());
147     GrSurfaceProxy* proxy = view.proxy();
148     // Must specify the strict constraint when the proxy is not functionally exact and the src
149     // rect would access pixels outside the proxy's content area without the constraint.
150     if (constraint != SkCanvas::kStrict_SrcRectConstraint && !proxy->isFunctionallyExact()) {
151         // Conservative estimate of how much a coord could be outset from src rect:
152         // 1/2 pixel for AA and 1/2 pixel for linear filtering
153         float buffer = 0.5f * (aaFlags != GrQuadAAFlags::kNone) +
154                        GrTextureEffect::kLinearInset * (filter == GrSamplerState::Filter::kLinear);
155         SkRect safeBounds = proxy->getBoundsRect();
156         safeBounds.inset(buffer, buffer);
157         if (!safeBounds.contains(srcRect)) {
158             constraint = SkCanvas::kStrict_SrcRectConstraint;
159         }
160     }
161 
162     SkPMColor4f color = texture_color(paint.getColor4f(), 1.f, srcColorInfo.colorType(), dstInfo);
163     if (dstClip) {
164         // Get source coords corresponding to dstClip
165         SkPoint srcQuad[4];
166         GrMapRectPoints(dstRect, srcRect, dstClip, srcQuad, 4);
167 
168         sdc->drawTextureQuad(clip,
169                              std::move(view),
170                              srcColorInfo.colorType(),
171                              srcColorInfo.alphaType(),
172                              filter,
173                              GrSamplerState::MipmapMode::kNone,
174                              paint.getBlendMode_or(SkBlendMode::kSrcOver),
175                              color,
176                              srcQuad,
177                              dstClip,
178                              aaFlags,
179                              constraint == SkCanvas::kStrict_SrcRectConstraint ? &srcRect : nullptr,
180                              ctm,
181                              std::move(textureXform));
182     } else {
183         sdc->drawTexture(clip,
184                          std::move(view),
185                          srcColorInfo.alphaType(),
186                          filter,
187                          GrSamplerState::MipmapMode::kNone,
188                          paint.getBlendMode_or(SkBlendMode::kSrcOver),
189                          color,
190                          srcRect,
191                          dstRect,
192                          aaFlags,
193                          constraint,
194                          ctm,
195                          std::move(textureXform));
196     }
197 }
198 
downgrade_to_filter(const SkSamplingOptions & sampling)199 SkFilterMode downgrade_to_filter(const SkSamplingOptions& sampling) {
200     SkFilterMode filter = sampling.filter;
201     if (sampling.isAniso() || sampling.useCubic || sampling.mipmap != SkMipmapMode::kNone) {
202         // if we were "fancier" than just bilerp, only do bilerp
203         filter = SkFilterMode::kLinear;
204     }
205     return filter;
206 }
207 
208 } // anonymous namespace
209 
210 
211 //////////////////////////////////////////////////////////////////////////////
212 
213 namespace skgpu::ganesh {
214 
drawEdgeAAImage(const SkImage * image,const SkRect & src,const SkRect & dst,const SkPoint dstClip[4],SkCanvas::QuadAAFlags canvasAAFlags,const SkMatrix & localToDevice,const SkSamplingOptions & sampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint,const SkMatrix & srcToDst,SkTileMode tm)215 void Device::drawEdgeAAImage(const SkImage* image,
216                              const SkRect& src,
217                              const SkRect& dst,
218                              const SkPoint dstClip[4],
219                              SkCanvas::QuadAAFlags canvasAAFlags,
220                              const SkMatrix& localToDevice,
221                              const SkSamplingOptions& sampling,
222                              const SkPaint& paint,
223                              SkCanvas::SrcRectConstraint constraint,
224                              const SkMatrix& srcToDst,
225                              SkTileMode tm) {
226     GrRecordingContext* rContext = fContext.get();
227     SurfaceDrawContext* sdc = fSurfaceDrawContext.get();
228     const GrClip* clip = this->clip();
229 
230     GrQuadAAFlags aaFlags = SkToGrQuadAAFlags(canvasAAFlags);
231     auto ib = as_IB(image);
232     if (tm == SkTileMode::kClamp && !ib->isYUVA() && can_use_draw_texture(paint, sampling)) {
233         // We've done enough checks above to allow us to pass ClampNearest() and not check for
234         // scaling adjustments.
235         auto [view, ct] = skgpu::ganesh::AsView(rContext, image, skgpu::Mipmapped::kNo);
236         if (!view) {
237             return;
238         }
239         GrColorInfo info(image->imageInfo().colorInfo());
240         info = info.makeColorType(ct);
241         draw_texture(sdc,
242                      clip,
243                      localToDevice,
244                      paint,
245                      sampling.filter,
246                      src,
247                      dst,
248                      dstClip,
249                      aaFlags,
250                      constraint,
251                      std::move(view),
252                      info);
253         return;
254     }
255 
256     const SkMaskFilter* mf = paint.getMaskFilter();
257 
258     // The shader expects proper local coords, so we can't replace local coords with texture coords
259     // if the shader will be used. If we have a mask filter we will change the underlying geometry
260     // that is rendered.
261     bool canUseTextureCoordsAsLocalCoords = !use_shader(image->isAlphaOnly(), paint) && !mf;
262 
263     // Specifying the texture coords as local coordinates is an attempt to enable more GrDrawOp
264     // combining by not baking anything about the srcRect, dstRect, or ctm, into the texture
265     // FP. In the future this should be an opaque optimization enabled by the combination of
266     // GrDrawOp/GP and FP.
267     if (GrFragmentProcessors::IsSupported(mf)) {
268         mf = nullptr;
269     }
270 
271     bool restrictToSubset = SkCanvas::kStrict_SrcRectConstraint == constraint;
272 
273     // If we have to outset for AA then we will generate texture coords outside the src rect. The
274     // same happens for any mask filter that extends the bounds rendered in the dst.
275     // This is conservative as a mask filter does not have to expand the bounds rendered.
276     bool coordsAllInsideSrcRect = aaFlags == GrQuadAAFlags::kNone && !mf;
277 
278     // Check for optimization to drop the src rect constraint when using linear filtering.
279     // TODO: Just rely on image to handle this.
280     if (sampling.isAniso() && !sampling.useCubic && sampling.filter == SkFilterMode::kLinear &&
281         restrictToSubset && sampling.mipmap == SkMipmapMode::kNone && coordsAllInsideSrcRect &&
282         !ib->isYUVA()) {
283         SkMatrix combinedMatrix;
284         combinedMatrix.setConcat(localToDevice, srcToDst);
285         if (can_ignore_linear_filtering_subset(src, combinedMatrix, sdc->numSamples())) {
286             restrictToSubset = false;
287         }
288     }
289 
290     SkMatrix textureMatrix;
291     if (canUseTextureCoordsAsLocalCoords) {
292         textureMatrix = SkMatrix::I();
293     } else {
294         if (!srcToDst.invert(&textureMatrix)) {
295             return;
296         }
297     }
298     const SkRect* subset = restrictToSubset       ? &src : nullptr;
299     const SkRect* domain = coordsAllInsideSrcRect ? &src : nullptr;
300     SkTileMode tileModes[] = {tm, tm};
301     std::unique_ptr<GrFragmentProcessor> fp = skgpu::ganesh::AsFragmentProcessor(
302             rContext, image, sampling, tileModes, textureMatrix, subset, domain);
303     fp = GrColorSpaceXformEffect::Make(
304             std::move(fp), image->imageInfo().colorInfo(), sdc->colorInfo());
305     if (image->isAlphaOnly()) {
306         if (const auto* shader = as_SB(paint.getShader())) {
307             auto shaderFP = GrFragmentProcessors::Make(shader,
308                                                        GrFPArgs(rContext,
309                                                                 &sdc->colorInfo(),
310                                                                 sdc->surfaceProps(),
311                                                                 GrFPArgs::Scope::kDefault),
312                                                        localToDevice);
313             if (!shaderFP) {
314                 return;
315             }
316             fp = GrBlendFragmentProcessor::Make<SkBlendMode::kDstIn>(std::move(fp),
317                                                                      std::move(shaderFP));
318         } else {
319             // Multiply the input (paint) color by the texture (alpha)
320             fp = GrFragmentProcessor::MulInputByChildAlpha(std::move(fp));
321         }
322     }
323 
324     GrPaint grPaint;
325     if (!SkPaintToGrPaintReplaceShader(rContext,
326                                        sdc->colorInfo(),
327                                        paint,
328                                        localToDevice,
329                                        std::move(fp),
330                                        sdc->surfaceProps(),
331                                        &grPaint)) {
332         return;
333     }
334 
335     if (!mf) {
336         // Can draw the image directly (any mask filter on the paint was converted to an FP already)
337         if (dstClip) {
338             SkPoint srcClipPoints[4];
339             SkPoint* srcClip = nullptr;
340             if (canUseTextureCoordsAsLocalCoords) {
341                 // Calculate texture coordinates that match the dst clip
342                 GrMapRectPoints(dst, src, dstClip, srcClipPoints, 4);
343                 srcClip = srcClipPoints;
344             }
345             sdc->fillQuadWithEdgeAA(clip, std::move(grPaint), aaFlags, localToDevice,
346                                     dstClip, srcClip);
347         } else {
348             // Provide explicit texture coords when possible, otherwise rely on texture matrix
349             sdc->fillRectWithEdgeAA(clip, std::move(grPaint), aaFlags, localToDevice, dst,
350                                     canUseTextureCoordsAsLocalCoords ? &src : nullptr);
351         }
352     } else {
353         // Must draw the mask filter as a GrStyledShape. For now, this loses the per-edge AA
354         // information since it always draws with AA, but that should not be noticeable since the
355         // mask filter is probably a blur.
356         GrStyledShape shape;
357         if (dstClip) {
358             // Represent it as an SkPath formed from the dstClip
359             SkPath path;
360             path.addPoly(dstClip, 4, true);
361             shape = GrStyledShape(path);
362         } else {
363             shape = GrStyledShape(dst);
364         }
365 
366         GrBlurUtils::DrawShapeWithMaskFilter(
367                 rContext, sdc, clip, shape, std::move(grPaint), localToDevice, mf);
368     }
369 }
370 
drawSpecial(SkSpecialImage * special,const SkMatrix & localToDevice,const SkSamplingOptions & origSampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint)371 void Device::drawSpecial(SkSpecialImage* special,
372                          const SkMatrix& localToDevice,
373                          const SkSamplingOptions& origSampling,
374                          const SkPaint& paint,
375                          SkCanvas::SrcRectConstraint constraint) {
376     SkASSERT(!paint.getMaskFilter() && !paint.getImageFilter());
377     SkASSERT(special->isGaneshBacked());
378 
379     SkRect src = SkRect::Make(special->subset());
380     SkRect dst = SkRect::MakeWH(special->width(), special->height());
381     SkMatrix srcToDst = SkMatrix::RectToRect(src, dst);
382 
383     SkSamplingOptions sampling = SkSamplingOptions(downgrade_to_filter(origSampling));
384     GrAA aa = fSurfaceDrawContext->chooseAA(paint);
385     SkCanvas::QuadAAFlags aaFlags = (aa == GrAA::kYes) ? SkCanvas::kAll_QuadAAFlags
386                                                        : SkCanvas::kNone_QuadAAFlags;
387 
388     GrSurfaceProxyView view = SkSpecialImages::AsView(this->recordingContext(), special);
389     if (!view) {
390         // This shouldn't happen since we shouldn't be mixing SkSpecialImage subclasses but
391         // returning early should avoid problems in release builds.
392         SkASSERT(false);
393         return;
394     }
395 
396     if (constraint == SkCanvas::kFast_SrcRectConstraint) {
397         // If 'fast' was requested, we assume the caller has done sufficient analysis to know the
398         // logical dimensions are safe (which is true for FilterResult, the only current caller that
399         // passes in 'fast'). Without exactify'ing the proxy, GrTextureEffect would re-introduce
400         // subset clamping.
401         view.proxy()->priv().exactify();
402     }
403 
404     SkImage_Ganesh image(sk_ref_sp(special->getContext()),
405                          special->uniqueID(),
406                          std::move(view),
407                          special->colorInfo());
408     // In most cases this ought to hit draw_texture since there won't be a color filter,
409     // alpha-only texture+shader, or a high filter quality.
410     this->drawEdgeAAImage(&image,
411                           src,
412                           dst,
413                           /* dstClip= */nullptr,
414                           aaFlags,
415                           localToDevice,
416                           sampling,
417                           paint,
418                           constraint,
419                           srcToDst,
420                           SkTileMode::kClamp);
421 }
422 
drawImageQuadDirect(const SkImage * image,const SkRect & srcRect,const SkRect & dstRect,const SkPoint dstClip[4],SkCanvas::QuadAAFlags aaFlags,const SkMatrix * preViewMatrix,const SkSamplingOptions & origSampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint)423 void Device::drawImageQuadDirect(const SkImage* image,
424                                  const SkRect& srcRect,
425                                  const SkRect& dstRect,
426                                  const SkPoint dstClip[4],
427                                  SkCanvas::QuadAAFlags aaFlags,
428                                  const SkMatrix* preViewMatrix,
429                                  const SkSamplingOptions& origSampling,
430                                  const SkPaint& paint,
431                                  SkCanvas::SrcRectConstraint constraint) {
432     SkRect src;
433     SkRect dst;
434     SkMatrix srcToDst;
435     auto mode = TiledTextureUtils::OptimizeSampleArea(SkISize::Make(image->width(),
436                                                                     image->height()),
437                                                       srcRect, dstRect, dstClip,
438                                                       &src, &dst, &srcToDst);
439     if (mode == TiledTextureUtils::ImageDrawMode::kSkip) {
440         return;
441     }
442 
443     if (src.contains(image->bounds())) {
444         constraint = SkCanvas::kFast_SrcRectConstraint;
445     }
446     // Depending on the nature of image, it can flow through more or less optimal pipelines
447     SkTileMode tileMode = mode == TiledTextureUtils::ImageDrawMode::kDecal ? SkTileMode::kDecal
448                                                                            : SkTileMode::kClamp;
449 
450     // Get final CTM matrix
451     SkMatrix ctm = this->localToDevice();
452     if (preViewMatrix) {
453         ctm.preConcat(*preViewMatrix);
454     }
455 
456     SkSamplingOptions sampling = origSampling;
457     if (sampling.mipmap != SkMipmapMode::kNone &&
458         TiledTextureUtils::CanDisableMipmap(ctm, srcToDst)) {
459         sampling = SkSamplingOptions(sampling.filter);
460     }
461 
462     this->drawEdgeAAImage(image,
463                           src,
464                           dst,
465                           dstClip,
466                           aaFlags,
467                           ctm,
468                           sampling,
469                           paint,
470                           constraint,
471                           srcToDst,
472                           tileMode);
473 }
474 
drawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[],int count,const SkPoint dstClips[],const SkMatrix preViewMatrices[],const SkSamplingOptions & sampling,const SkPaint & paint,SkCanvas::SrcRectConstraint constraint)475 void Device::drawEdgeAAImageSet(const SkCanvas::ImageSetEntry set[], int count,
476                                 const SkPoint dstClips[], const SkMatrix preViewMatrices[],
477                                 const SkSamplingOptions& sampling, const SkPaint& paint,
478                                 SkCanvas::SrcRectConstraint constraint) {
479     SkASSERT(count > 0);
480     if (!can_use_draw_texture(paint, sampling)) {
481         // Send every entry through drawImageQuad() to handle the more complicated paint
482         int dstClipIndex = 0;
483         for (int i = 0; i < count; ++i) {
484             // Only no clip or quad clip are supported
485             SkASSERT(!set[i].fHasClip || dstClips);
486             SkASSERT(set[i].fMatrixIndex < 0 || preViewMatrices);
487 
488             SkTCopyOnFirstWrite<SkPaint> entryPaint(paint);
489             if (set[i].fAlpha != 1.f) {
490                 auto paintAlpha = paint.getAlphaf();
491                 entryPaint.writable()->setAlphaf(paintAlpha * set[i].fAlpha);
492             }
493             this->drawImageQuadDirect(
494                     set[i].fImage.get(), set[i].fSrcRect, set[i].fDstRect,
495                     set[i].fHasClip ? dstClips + dstClipIndex : nullptr,
496                     static_cast<SkCanvas::QuadAAFlags>(set[i].fAAFlags),
497                     set[i].fMatrixIndex < 0 ? nullptr : preViewMatrices + set[i].fMatrixIndex,
498                     sampling, *entryPaint, constraint);
499             dstClipIndex += 4 * set[i].fHasClip;
500         }
501         return;
502     }
503 
504     GrSamplerState::Filter filter = sampling.filter == SkFilterMode::kNearest
505                                             ? GrSamplerState::Filter::kNearest
506                                             : GrSamplerState::Filter::kLinear;
507     SkBlendMode mode = paint.getBlendMode_or(SkBlendMode::kSrcOver);
508 
509     AutoTArray<GrTextureSetEntry> textures(count);
510     // We accumulate compatible proxies until we find an an incompatible one or reach the end and
511     // issue the accumulated 'n' draws starting at 'base'. 'p' represents the number of proxy
512     // switches that occur within the 'n' entries.
513     int base = 0, n = 0, p = 0;
514     auto draw = [&](int nextBase) {
515         if (n > 0) {
516             auto textureXform = GrColorSpaceXform::Make(set[base].fImage->imageInfo().colorInfo(),
517                                                         fSurfaceDrawContext->colorInfo());
518             fSurfaceDrawContext->drawTextureSet(this->clip(),
519                                                 textures.get() + base,
520                                                 n,
521                                                 p,
522                                                 filter,
523                                                 GrSamplerState::MipmapMode::kNone,
524                                                 mode,
525                                                 constraint,
526                                                 this->localToDevice(),
527                                                 std::move(textureXform));
528         }
529         base = nextBase;
530         n = 0;
531         p = 0;
532     };
533     int dstClipIndex = 0;
534     for (int i = 0; i < count; ++i) {
535         SkASSERT(!set[i].fHasClip || dstClips);
536         SkASSERT(set[i].fMatrixIndex < 0 || preViewMatrices);
537 
538         // Manage the dst clip pointer tracking before any continues are used so we don't lose
539         // our place in the dstClips array.
540         const SkPoint* clip = set[i].fHasClip ? dstClips + dstClipIndex : nullptr;
541         dstClipIndex += 4 * set[i].fHasClip;
542 
543         // The default SkDevice implementation is based on drawImageRect which does not allow
544         // non-sorted src rects. TODO: Decide this is OK or make sure we handle it.
545         if (!set[i].fSrcRect.isSorted()) {
546             draw(i + 1);
547             continue;
548         }
549 
550         GrSurfaceProxyView view;
551         const SkImage_Base* image = as_IB(set[i].fImage.get());
552         // Extract view from image, but skip YUV images so they get processed through
553         // drawImageQuad and the proper effect to dynamically sample their planes.
554         if (!image->isYUVA()) {
555             std::tie(view, std::ignore) =
556                     skgpu::ganesh::AsView(this->recordingContext(), image, skgpu::Mipmapped::kNo);
557             if (image->isAlphaOnly()) {
558                 skgpu::Swizzle swizzle = skgpu::Swizzle::Concat(view.swizzle(),
559                                                                 skgpu::Swizzle("aaaa"));
560                 view = {view.detachProxy(), view.origin(), swizzle};
561             }
562         }
563 
564         if (!view) {
565             // This image can't go through the texture op, send through general image pipeline
566             // after flushing current batch.
567             draw(i + 1);
568             SkTCopyOnFirstWrite<SkPaint> entryPaint(paint);
569             if (set[i].fAlpha != 1.f) {
570                 auto paintAlpha = paint.getAlphaf();
571                 entryPaint.writable()->setAlphaf(paintAlpha * set[i].fAlpha);
572             }
573             this->drawImageQuadDirect(
574                     image, set[i].fSrcRect, set[i].fDstRect, clip,
575                     static_cast<SkCanvas::QuadAAFlags>(set[i].fAAFlags),
576                     set[i].fMatrixIndex < 0 ? nullptr : preViewMatrices + set[i].fMatrixIndex,
577                     sampling, *entryPaint, constraint);
578             continue;
579         }
580 
581         textures[i].fProxyView = std::move(view);
582         textures[i].fSrcAlphaType = image->alphaType();
583         textures[i].fSrcRect = set[i].fSrcRect;
584         textures[i].fDstRect = set[i].fDstRect;
585         textures[i].fDstClipQuad = clip;
586         textures[i].fPreViewMatrix =
587                 set[i].fMatrixIndex < 0 ? nullptr : preViewMatrices + set[i].fMatrixIndex;
588         textures[i].fColor = texture_color(paint.getColor4f(), set[i].fAlpha,
589                                            SkColorTypeToGrColorType(image->colorType()),
590                                            fSurfaceDrawContext->colorInfo());
591         textures[i].fAAFlags = SkToGrQuadAAFlags(set[i].fAAFlags);
592 
593         if (n > 0 &&
594             (!GrTextureProxy::ProxiesAreCompatibleAsDynamicState(
595                     textures[i].fProxyView.proxy(),
596                     textures[base].fProxyView.proxy()) ||
597              textures[i].fProxyView.swizzle() != textures[base].fProxyView.swizzle() ||
598              set[i].fImage->alphaType() != set[base].fImage->alphaType() ||
599              !SkColorSpace::Equals(set[i].fImage->colorSpace(), set[base].fImage->colorSpace()))) {
600             draw(i);
601         }
602         // Whether or not we submitted a draw in the above if(), this ith entry is in the current
603         // set being accumulated so increment n, and increment p if proxies are different.
604         ++n;
605         if (n == 1 || textures[i - 1].fProxyView.proxy() != textures[i].fProxyView.proxy()) {
606             // First proxy or a different proxy (that is compatible, otherwise we'd have drawn up
607             // to i - 1).
608             ++p;
609         }
610     }
611     draw(count);
612 }
613 
614 }  // namespace skgpu::ganesh
615