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