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