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/GrBlurUtils.h"
9
10 #include "include/core/SkAlphaType.h"
11 #include "include/core/SkBitmap.h"
12 #include "include/core/SkBlendMode.h"
13 #include "include/core/SkBlurTypes.h"
14 #include "include/core/SkCanvas.h"
15 #include "include/core/SkColorSpace.h"
16 #include "include/core/SkData.h"
17 #include "include/core/SkImageInfo.h"
18 #include "include/core/SkM44.h"
19 #include "include/core/SkMatrix.h"
20 #include "include/core/SkPaint.h"
21 #include "include/core/SkPath.h"
22 #include "include/core/SkPoint.h"
23 #include "include/core/SkRRect.h"
24 #include "include/core/SkRect.h"
25 #include "include/core/SkRefCnt.h"
26 #include "include/core/SkRegion.h"
27 #include "include/core/SkSamplingOptions.h"
28 #include "include/core/SkScalar.h"
29 #include "include/core/SkSize.h"
30 #include "include/core/SkSpan.h"
31 #include "include/core/SkString.h"
32 #include "include/core/SkStrokeRec.h"
33 #include "include/core/SkSurface.h"
34 #include "include/core/SkSurfaceProps.h"
35 #include "include/core/SkTileMode.h"
36 #include "include/effects/SkRuntimeEffect.h"
37 #include "include/gpu/GpuTypes.h"
38 #include "include/gpu/ganesh/GrDirectContext.h"
39 #include "include/gpu/ganesh/GrRecordingContext.h"
40 #include "include/gpu/ganesh/GrTypes.h"
41 #include "include/private/SkColorData.h"
42 #include "include/private/base/SkAssert.h"
43 #include "include/private/base/SkFixed.h"
44 #include "include/private/base/SkFloatingPoint.h"
45 #include "include/private/base/SkMath.h"
46 #include "include/private/base/SkTemplates.h"
47 #include "include/private/gpu/ganesh/GrTypesPriv.h"
48 #include "src/base/SkFloatBits.h"
49 #include "src/base/SkTLazy.h"
50 #include "src/core/SkBlurMaskFilterImpl.h"
51 #include "src/core/SkDraw.h"
52 #include "src/core/SkMask.h"
53 #include "src/core/SkMaskFilterBase.h"
54 #include "src/core/SkRRectPriv.h"
55 #include "src/core/SkRuntimeEffectPriv.h"
56 #ifdef SKIA_OHOS
57 #include "src/core/SkSDFFilter.h"
58 #endif
59 #include "src/core/SkTraceEvent.h"
60 #include "src/gpu/BlurUtils.h"
61 #include "src/gpu/ResourceKey.h"
62 #include "src/gpu/SkBackingFit.h"
63 #include "src/gpu/Swizzle.h"
64 #include "src/gpu/ganesh/GrCaps.h"
65 #include "src/gpu/ganesh/GrClip.h"
66 #include "src/gpu/ganesh/GrColorInfo.h"
67 #include "src/gpu/ganesh/GrColorSpaceXform.h"
68 #include "src/gpu/ganesh/GrDirectContextPriv.h"
69 #include "src/gpu/ganesh/GrFixedClip.h"
70 #include "src/gpu/ganesh/GrFragmentProcessor.h"
71 #include "src/gpu/ganesh/GrFragmentProcessors.h"
72 #include "src/gpu/ganesh/GrPaint.h"
73 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
74 #include "src/gpu/ganesh/GrSamplerState.h"
75 #include "src/gpu/ganesh/GrShaderCaps.h"
76 #include "src/gpu/ganesh/GrStyle.h"
77 #include "src/gpu/ganesh/GrSurfaceProxy.h"
78 #include "src/gpu/ganesh/GrSurfaceProxyView.h"
79 #include "src/gpu/ganesh/GrTextureProxy.h"
80 #include "src/gpu/ganesh/GrThreadSafeCache.h"
81 #include "src/gpu/ganesh/GrUtil.h"
82 #include "src/gpu/ganesh/SkGr.h"
83 #include "src/gpu/ganesh/SurfaceContext.h"
84 #include "src/gpu/ganesh/SurfaceDrawContext.h"
85 #include "src/gpu/ganesh/SurfaceFillContext.h"
86 #include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
87 #include "src/gpu/ganesh/effects/GrMatrixEffect.h"
88 #include "src/gpu/ganesh/effects/GrSkSLFP.h"
89 #include "src/gpu/ganesh/effects/GrTextureEffect.h"
90 #include "src/gpu/ganesh/geometry/GrStyledShape.h"
91
92 #include <algorithm>
93 #include <array>
94 #include <cstdint>
95 #include <initializer_list>
96 #include <memory>
97 #include <tuple>
98 #include <utility>
99
100 namespace GrBlurUtils {
101
clip_bounds_quick_reject(const SkIRect & clipBounds,const SkIRect & rect)102 static bool clip_bounds_quick_reject(const SkIRect& clipBounds, const SkIRect& rect) {
103 return clipBounds.isEmpty() || rect.isEmpty() || !SkIRect::Intersects(clipBounds, rect);
104 }
105
106 static constexpr auto kMaskOrigin = kTopLeft_GrSurfaceOrigin;
107
108 // Draw a mask using the supplied paint. Since the coverage/geometry
109 // is already burnt into the mask this boils down to a rect draw.
110 // Return true if the mask was successfully drawn.
draw_mask(skgpu::ganesh::SurfaceDrawContext * sdc,const GrClip * clip,const SkMatrix & viewMatrix,const SkIRect & maskBounds,GrPaint && paint,GrSurfaceProxyView mask)111 static bool draw_mask(skgpu::ganesh::SurfaceDrawContext* sdc,
112 const GrClip* clip,
113 const SkMatrix& viewMatrix,
114 const SkIRect& maskBounds,
115 GrPaint&& paint,
116 GrSurfaceProxyView mask) {
117 SkMatrix inverse;
118 if (!viewMatrix.invert(&inverse)) {
119 return false;
120 }
121
122 mask.concatSwizzle(skgpu::Swizzle("aaaa"));
123
124 SkMatrix matrix = SkMatrix::Translate(-SkIntToScalar(maskBounds.fLeft),
125 -SkIntToScalar(maskBounds.fTop));
126 matrix.preConcat(viewMatrix);
127 paint.setCoverageFragmentProcessor(
128 GrTextureEffect::Make(std::move(mask), kUnknown_SkAlphaType, matrix));
129
130 sdc->fillPixelsWithLocalMatrix(clip, std::move(paint), maskBounds, inverse);
131 return true;
132 }
133
mask_release_proc(void * addr,void *)134 static void mask_release_proc(void* addr, void* /*context*/) {
135 SkMaskBuilder::FreeImage(addr);
136 }
137
138 // This stores the mapping from an unclipped, integerized, device-space, shape bounds to
139 // the filtered mask's draw rect.
140 struct DrawRectData {
141 SkIVector fOffset;
142 SkISize fSize;
143 };
144
create_data(const SkIRect & drawRect,const SkIRect & origDevBounds)145 static sk_sp<SkData> create_data(const SkIRect& drawRect, const SkIRect& origDevBounds) {
146
147 DrawRectData drawRectData { {drawRect.fLeft - origDevBounds.fLeft,
148 drawRect.fTop - origDevBounds.fTop},
149 drawRect.size() };
150
151 return SkData::MakeWithCopy(&drawRectData, sizeof(drawRectData));
152 }
153
extract_draw_rect_from_data(SkData * data,const SkIRect & origDevBounds)154 static SkIRect extract_draw_rect_from_data(SkData* data, const SkIRect& origDevBounds) {
155 auto drawRectData = static_cast<const DrawRectData*>(data->data());
156
157 return SkIRect::MakeXYWH(origDevBounds.fLeft + drawRectData->fOffset.fX,
158 origDevBounds.fTop + drawRectData->fOffset.fY,
159 drawRectData->fSize.fWidth,
160 drawRectData->fSize.fHeight);
161 }
162
sw_create_filtered_mask(GrRecordingContext * rContext,const SkMatrix & viewMatrix,const GrStyledShape & shape,const SkMaskFilter * filter,const SkIRect & unclippedDevShapeBounds,const SkIRect & clipBounds,SkIRect * drawRect,skgpu::UniqueKey * key)163 static GrSurfaceProxyView sw_create_filtered_mask(GrRecordingContext* rContext,
164 const SkMatrix& viewMatrix,
165 const GrStyledShape& shape,
166 const SkMaskFilter* filter,
167 const SkIRect& unclippedDevShapeBounds,
168 const SkIRect& clipBounds,
169 SkIRect* drawRect,
170 skgpu::UniqueKey* key) {
171 SkASSERT(filter);
172 SkASSERT(!shape.style().applies());
173
174 auto threadSafeCache = rContext->priv().threadSafeCache();
175
176 GrSurfaceProxyView filteredMaskView;
177 sk_sp<SkData> data;
178
179 if (key->isValid()) {
180 std::tie(filteredMaskView, data) = threadSafeCache->findWithData(*key);
181 }
182
183 if (filteredMaskView) {
184 SkASSERT(data);
185 SkASSERT(kMaskOrigin == filteredMaskView.origin());
186
187 *drawRect = extract_draw_rect_from_data(data.get(), unclippedDevShapeBounds);
188 } else {
189 SkStrokeRec::InitStyle fillOrHairline = shape.style().isSimpleHairline()
190 ? SkStrokeRec::kHairline_InitStyle
191 : SkStrokeRec::kFill_InitStyle;
192
193 // TODO: it seems like we could create an SkDraw here and set its fMatrix field rather
194 // than explicitly transforming the path to device space.
195 SkPath devPath;
196
197 shape.asPath(&devPath);
198
199 devPath.transform(viewMatrix);
200
201 SkMaskBuilder srcM, dstM;
202 if (!SkDraw::DrawToMask(devPath, clipBounds, filter, &viewMatrix, &srcM,
203 SkMaskBuilder::kComputeBoundsAndRenderImage_CreateMode,
204 fillOrHairline)) {
205 return {};
206 }
207 SkAutoMaskFreeImage autoSrc(srcM.image());
208
209 SkASSERT(SkMask::kA8_Format == srcM.fFormat);
210
211 if (!as_MFB(filter)->filterMask(&dstM, srcM, viewMatrix, nullptr)) {
212 return {};
213 }
214 // this will free-up dstM when we're done (allocated in filterMask())
215 SkAutoMaskFreeImage autoDst(dstM.image());
216
217 if (clip_bounds_quick_reject(clipBounds, dstM.fBounds)) {
218 return {};
219 }
220
221 // we now have a device-aligned 8bit mask in dstM, ready to be drawn using
222 // the current clip (and identity matrix) and GrPaint settings
223 SkBitmap bm;
224 if (!bm.installPixels(SkImageInfo::MakeA8(dstM.fBounds.width(), dstM.fBounds.height()),
225 autoDst.release(), dstM.fRowBytes, mask_release_proc, nullptr)) {
226 return {};
227 }
228 bm.setImmutable();
229
230 std::tie(filteredMaskView, std::ignore) = GrMakeUncachedBitmapProxyView(
231 rContext, bm, skgpu::Mipmapped::kNo, SkBackingFit::kApprox);
232 if (!filteredMaskView) {
233 return {};
234 }
235
236 SkASSERT(kMaskOrigin == filteredMaskView.origin());
237
238 *drawRect = dstM.fBounds;
239
240 if (key->isValid()) {
241 key->setCustomData(create_data(*drawRect, unclippedDevShapeBounds));
242 std::tie(filteredMaskView, data) = threadSafeCache->addWithData(*key, filteredMaskView);
243 // If we got a different view back from 'addWithData' it could have a different drawRect
244 *drawRect = extract_draw_rect_from_data(data.get(), unclippedDevShapeBounds);
245 }
246 }
247
248 return filteredMaskView;
249 }
250
251 // Create a mask of 'shape' and return the resulting surfaceDrawContext
create_mask_GPU(GrRecordingContext * rContext,const SkIRect & maskRect,const SkMatrix & origViewMatrix,const GrStyledShape & shape,int sampleCnt)252 static std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> create_mask_GPU(
253 GrRecordingContext* rContext,
254 const SkIRect& maskRect,
255 const SkMatrix& origViewMatrix,
256 const GrStyledShape& shape,
257 int sampleCnt) {
258 // We cache blur masks. Use default surface props here so we can use the same cached mask
259 // regardless of the final dst surface.
260 SkSurfaceProps defaultSurfaceProps;
261
262 // Use GetApproxSize to implement our own approximate size matching, but demand
263 // a "SkBackingFit::kExact" size match on the actual render target. We do this because the
264 // filter will reach outside the src bounds, so we need to pre-clear these values to ensure a
265 // "decal" sampling effect (i.e., ensure reads outside the src bounds return alpha=0).
266 //
267 // FIXME: Reads outside the left and top edges will actually clamp to the edge pixel. And in the
268 // event that GetApproxSize does not change the size, reads outside the right and/or bottom will
269 // do the same. We should offset our filter within the render target and expand the size as
270 // needed to guarantee at least 1px of padding on all sides.
271 auto approxSize = skgpu::GetApproxSize(maskRect.size());
272 auto sdc = skgpu::ganesh::SurfaceDrawContext::MakeWithFallback(rContext,
273 GrColorType::kAlpha_8,
274 nullptr,
275 SkBackingFit::kExact,
276 approxSize,
277 defaultSurfaceProps,
278 sampleCnt,
279 skgpu::Mipmapped::kNo,
280 GrProtected::kNo,
281 kMaskOrigin);
282 if (!sdc) {
283 return nullptr;
284 }
285
286 sdc->clear(SK_PMColor4fTRANSPARENT);
287
288 GrPaint maskPaint;
289 maskPaint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op);
290
291 // setup new clip
292 GrFixedClip clip(sdc->dimensions(), SkIRect::MakeWH(maskRect.width(), maskRect.height()));
293
294 // Draw the mask into maskTexture with the path's integerized top-left at the origin using
295 // maskPaint.
296 SkMatrix viewMatrix = origViewMatrix;
297 viewMatrix.postTranslate(-SkIntToScalar(maskRect.fLeft), -SkIntToScalar(maskRect.fTop));
298 sdc->drawShape(&clip, std::move(maskPaint), GrAA::kYes, viewMatrix, GrStyledShape(shape));
299 return sdc;
300 }
301
get_unclipped_shape_dev_bounds(const GrStyledShape & shape,const SkMatrix & matrix,SkIRect * devBounds)302 static bool get_unclipped_shape_dev_bounds(const GrStyledShape& shape, const SkMatrix& matrix,
303 SkIRect* devBounds) {
304 SkRect shapeDevBounds;
305 if (shape.inverseFilled()) {
306 shapeDevBounds = {SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity,
307 SK_ScalarInfinity, SK_ScalarInfinity};
308 } else {
309 SkRect shapeBounds = shape.styledBounds();
310 if (shapeBounds.isEmpty()) {
311 return false;
312 }
313 matrix.mapRect(&shapeDevBounds, shapeBounds);
314 }
315 // Even though these are "unclipped" bounds we still clip to the int32_t range.
316 // This is the largest int32_t that is representable exactly as a float. The next 63 larger ints
317 // would round down to this value when cast to a float, but who really cares.
318 // INT32_MIN is exactly representable.
319 static constexpr int32_t kMaxInt = 2147483520;
320 if (!shapeDevBounds.intersect(SkRect::MakeLTRB(INT32_MIN, INT32_MIN, kMaxInt, kMaxInt))) {
321 return false;
322 }
323 // Make sure that the resulting SkIRect can have representable width and height
324 if (SkScalarRoundToInt(shapeDevBounds.width()) > kMaxInt ||
325 SkScalarRoundToInt(shapeDevBounds.height()) > kMaxInt) {
326 return false;
327 }
328 shapeDevBounds.roundOut(devBounds);
329 return true;
330 }
331
332 // Gets the shape bounds, the clip bounds, and the intersection (if any). Returns false if there
333 // is no intersection.
get_shape_and_clip_bounds(skgpu::ganesh::SurfaceDrawContext * sdc,const GrClip * clip,const GrStyledShape & shape,const SkMatrix & matrix,SkIRect * unclippedDevShapeBounds,SkIRect * devClipBounds)334 static bool get_shape_and_clip_bounds(skgpu::ganesh::SurfaceDrawContext* sdc,
335 const GrClip* clip,
336 const GrStyledShape& shape,
337 const SkMatrix& matrix,
338 SkIRect* unclippedDevShapeBounds,
339 SkIRect* devClipBounds) {
340 // compute bounds as intersection of rt size, clip, and path
341 *devClipBounds = clip ? clip->getConservativeBounds()
342 : SkIRect::MakeWH(sdc->width(), sdc->height());
343
344 if (!get_unclipped_shape_dev_bounds(shape, matrix, unclippedDevShapeBounds)) {
345 *unclippedDevShapeBounds = SkIRect::MakeEmpty();
346 return false;
347 }
348
349 return true;
350 }
351
352 /**
353 * If we cannot create a FragmentProcess for a mask filter, we might have special logic for
354 * it here. That code path requires constructing a src mask as input. Since that is a potentially
355 * expensive operation, this function tests if filter_mask would succeed if the mask
356 * were to be created.
357 *
358 * 'maskRect' returns the device space portion of the mask that the filter needs. The mask
359 * passed into 'filter_mask' should have the same extent as 'maskRect' but be
360 * translated to the upper-left corner of the mask (i.e., (maskRect.fLeft, maskRect.fTop)
361 * appears at (0, 0) in the mask).
362 *
363 * Logically, how this works is:
364 * can_filter_mask is called
365 * if (it returns true)
366 * the returned mask rect is used for quick rejecting
367 * the mask rect is used to generate the mask
368 * filter_mask is called to filter the mask
369 *
370 * TODO: this should work as:
371 * if (can_filter_mask(devShape, ...)) // rect, rrect, drrect, path
372 * filter_mask(devShape, ...)
373 * this would hide the RRect special case and the mask generation
374 */
can_filter_mask(const SkMaskFilterBase * maskFilter,const GrStyledShape & shape,const SkIRect & devSpaceShapeBounds,const SkIRect & clipBounds,const SkMatrix & ctm,SkIRect * maskRect)375 static bool can_filter_mask(const SkMaskFilterBase* maskFilter,
376 const GrStyledShape& shape,
377 const SkIRect& devSpaceShapeBounds,
378 const SkIRect& clipBounds,
379 const SkMatrix& ctm,
380 SkIRect* maskRect) {
381 if (maskFilter->type() != SkMaskFilterBase::Type::kBlur) {
382 return false;
383 }
384 auto bmf = static_cast<const SkBlurMaskFilterImpl*>(maskFilter);
385 SkScalar xformedSigma = bmf->computeXformedSigma(ctm);
386 if (skgpu::BlurIsEffectivelyIdentity(xformedSigma)) {
387 *maskRect = devSpaceShapeBounds;
388 return maskRect->intersect(clipBounds);
389 }
390
391 if (maskRect) {
392 float sigma3 = 3 * xformedSigma;
393
394 // Outset srcRect and clipRect by 3 * sigma, to compute affected blur area.
395 SkIRect clipRect = clipBounds.makeOutset(sigma3, sigma3);
396 SkIRect srcRect = devSpaceShapeBounds.makeOutset(sigma3, sigma3);
397
398 if (!srcRect.intersect(clipRect)) {
399 srcRect.setEmpty();
400 }
401 *maskRect = srcRect;
402 }
403
404 // We prefer to blur paths with small blur radii on the CPU.
405 static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64);
406 static const SkScalar kMIN_GPU_BLUR_SIGMA = SkIntToScalar(32);
407
408 if (devSpaceShapeBounds.width() <= kMIN_GPU_BLUR_SIZE &&
409 devSpaceShapeBounds.height() <= kMIN_GPU_BLUR_SIZE &&
410 xformedSigma <= kMIN_GPU_BLUR_SIGMA) {
411 return false;
412 }
413
414 return true;
415 }
416
417 ///////////////////////////////////////////////////////////////////////////////
418 // Circle Blur
419 ///////////////////////////////////////////////////////////////////////////////
420
create_profile_effect(GrRecordingContext * rContext,const SkRect & circle,float sigma,float * solidRadius,float * textureRadius)421 static std::unique_ptr<GrFragmentProcessor> create_profile_effect(GrRecordingContext* rContext,
422 const SkRect& circle,
423 float sigma,
424 float* solidRadius,
425 float* textureRadius) {
426 float circleR = circle.width() / 2.0f;
427 if (!SkIsFinite(circleR) || circleR < SK_ScalarNearlyZero) {
428 return nullptr;
429 }
430
431 auto threadSafeCache = rContext->priv().threadSafeCache();
432
433 // Profile textures are cached by the ratio of sigma to circle radius and by the size of the
434 // profile texture (binned by powers of 2).
435 SkScalar sigmaToCircleRRatio = sigma / circleR;
436 // When sigma is really small this becomes a equivalent to convolving a Gaussian with a
437 // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the
438 // Guassian and the profile texture is a just a Gaussian evaluation. However, we haven't yet
439 // implemented this latter optimization.
440 sigmaToCircleRRatio = std::min(sigmaToCircleRRatio, 8.f);
441 SkFixed sigmaToCircleRRatioFixed;
442 static const SkScalar kHalfPlaneThreshold = 0.1f;
443 bool useHalfPlaneApprox = false;
444 if (sigmaToCircleRRatio <= kHalfPlaneThreshold) {
445 useHalfPlaneApprox = true;
446 sigmaToCircleRRatioFixed = 0;
447 *solidRadius = circleR - 3 * sigma;
448 *textureRadius = 6 * sigma;
449 } else {
450 // Convert to fixed point for the key.
451 sigmaToCircleRRatioFixed = SkScalarToFixed(sigmaToCircleRRatio);
452 // We shave off some bits to reduce the number of unique entries. We could probably
453 // shave off more than we do.
454 sigmaToCircleRRatioFixed &= ~0xff;
455 sigmaToCircleRRatio = SkFixedToScalar(sigmaToCircleRRatioFixed);
456 sigma = circleR * sigmaToCircleRRatio;
457 *solidRadius = 0;
458 *textureRadius = circleR + 3 * sigma;
459 }
460
461 static constexpr int kProfileTextureWidth = 512;
462 // This would be kProfileTextureWidth/textureRadius if it weren't for the fact that we do
463 // the calculation of the profile coord in a coord space that has already been scaled by
464 // 1 / textureRadius. This is done to avoid overflow in length().
465 SkMatrix texM = SkMatrix::Scale(kProfileTextureWidth, 1.f);
466
467 static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
468 skgpu::UniqueKey key;
469 skgpu::UniqueKey::Builder builder(&key, kDomain, 1, "1-D Circular Blur");
470 builder[0] = sigmaToCircleRRatioFixed;
471 builder.finish();
472
473 GrSurfaceProxyView profileView = threadSafeCache->find(key);
474 if (profileView) {
475 SkASSERT(profileView.asTextureProxy());
476 SkASSERT(profileView.origin() == kTopLeft_GrSurfaceOrigin);
477 return GrTextureEffect::Make(std::move(profileView), kPremul_SkAlphaType, texM);
478 }
479
480 SkBitmap bm;
481 if (useHalfPlaneApprox) {
482 bm = skgpu::CreateHalfPlaneProfile(kProfileTextureWidth);
483 } else {
484 // Rescale params to the size of the texture we're creating.
485 SkScalar scale = kProfileTextureWidth / *textureRadius;
486 bm = skgpu::CreateCircleProfile(sigma * scale, circleR * scale, kProfileTextureWidth);
487 }
488
489 profileView = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, bm));
490 if (!profileView) {
491 return nullptr;
492 }
493
494 profileView = threadSafeCache->add(key, profileView);
495 return GrTextureEffect::Make(std::move(profileView), kPremul_SkAlphaType, texM);
496 }
497
make_circle_blur(GrRecordingContext * context,const SkRect & circle,float sigma)498 static std::unique_ptr<GrFragmentProcessor> make_circle_blur(GrRecordingContext* context,
499 const SkRect& circle,
500 float sigma) {
501 if (skgpu::BlurIsEffectivelyIdentity(sigma)) {
502 return nullptr;
503 }
504
505 float solidRadius;
506 float textureRadius;
507 std::unique_ptr<GrFragmentProcessor> profile =
508 create_profile_effect(context, circle, sigma, &solidRadius, &textureRadius);
509 if (!profile) {
510 return nullptr;
511 }
512
513 static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
514 "uniform shader blurProfile;"
515 "uniform float4 circleData;"
516
517 "half4 main(float2 xy) {"
518 // We just want to compute "(length(vec) - circleData.z + 0.5) * circleData.w" but need
519 // to rearrange to avoid passing large values to length() that would overflow.
520 "half4 halfCircleData = circleData;"
521 "half2 vec = (sk_FragCoord.xy - halfCircleData.xy) * circleData.w;"
522 "half dist = length(vec) + (0.5 - halfCircleData.z) * halfCircleData.w;"
523 "return blurProfile.eval(half2(dist, 0.5)).aaaa;"
524 "}"
525 );
526
527 SkV4 circleData = {circle.centerX(), circle.centerY(), solidRadius, 1.f / textureRadius};
528 auto circleBlurFP = GrSkSLFP::Make(effect, "CircleBlur", /*inputFP=*/nullptr,
529 GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha,
530 "blurProfile", GrSkSLFP::IgnoreOptFlags(std::move(profile)),
531 "circleData", circleData);
532 // Modulate blur with the input color.
533 return GrBlendFragmentProcessor::Make<SkBlendMode::kModulate>(std::move(circleBlurFP),
534 /*dst=*/nullptr);
535 }
536
537 ///////////////////////////////////////////////////////////////////////////////
538 // Rect Blur
539 ///////////////////////////////////////////////////////////////////////////////
540
make_rect_integral_fp(GrRecordingContext * rContext,float sixSigma)541 static std::unique_ptr<GrFragmentProcessor> make_rect_integral_fp(GrRecordingContext* rContext,
542 float sixSigma) {
543 SkASSERT(!skgpu::BlurIsEffectivelyIdentity(sixSigma / 6.f));
544 auto threadSafeCache = rContext->priv().threadSafeCache();
545
546 int width = skgpu::ComputeIntegralTableWidth(sixSigma);
547
548 static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
549 skgpu::UniqueKey key;
550 skgpu::UniqueKey::Builder builder(&key, kDomain, 1, "Rect Blur Mask");
551 builder[0] = width;
552 builder.finish();
553
554 SkMatrix m = SkMatrix::Scale(width / sixSigma, 1.f);
555
556 GrSurfaceProxyView view = threadSafeCache->find(key);
557
558 if (view) {
559 SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
560 return GrTextureEffect::Make(
561 std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
562 }
563
564 SkBitmap bitmap = skgpu::CreateIntegralTable(width);
565 if (bitmap.empty()) {
566 return {};
567 }
568
569 view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, bitmap));
570 if (!view) {
571 return {};
572 }
573
574 view = threadSafeCache->add(key, view);
575
576 SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
577 return GrTextureEffect::Make(
578 std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
579 }
580
make_rect_blur(GrRecordingContext * context,const GrShaderCaps & caps,const SkRect & srcRect,const SkMatrix & viewMatrix,float transformedSigma)581 static std::unique_ptr<GrFragmentProcessor> make_rect_blur(GrRecordingContext* context,
582 const GrShaderCaps& caps,
583 const SkRect& srcRect,
584 const SkMatrix& viewMatrix,
585 float transformedSigma) {
586 SkASSERT(viewMatrix.preservesRightAngles());
587 SkASSERT(srcRect.isSorted());
588
589 if (skgpu::BlurIsEffectivelyIdentity(transformedSigma)) {
590 // No need to blur the rect
591 return nullptr;
592 }
593
594 SkMatrix invM;
595 SkRect rect;
596 if (viewMatrix.rectStaysRect()) {
597 invM = SkMatrix::I();
598 // We can do everything in device space when the src rect projects to a rect in device space
599 SkAssertResult(viewMatrix.mapRect(&rect, srcRect));
600 } else {
601 // The view matrix may scale, perhaps anisotropically. But we want to apply our device space
602 // "transformedSigma" to the delta of frag coord from the rect edges. Factor out the scaling
603 // to define a space that is purely rotation/translation from device space (and scale from
604 // src space) We'll meet in the middle: pre-scale the src rect to be in this space and then
605 // apply the inverse of the rotation/translation portion to the frag coord.
606 SkMatrix m;
607 SkSize scale;
608 if (!viewMatrix.decomposeScale(&scale, &m)) {
609 return nullptr;
610 }
611 if (!m.invert(&invM)) {
612 return nullptr;
613 }
614 rect = {srcRect.left() * scale.width(),
615 srcRect.top() * scale.height(),
616 srcRect.right() * scale.width(),
617 srcRect.bottom() * scale.height()};
618 }
619
620 if (!caps.fFloatIs32Bits) {
621 // We promote the math that gets us into the Gaussian space to full float when the rect
622 // coords are large. If we don't have full float then fail. We could probably clip the rect
623 // to an outset device bounds instead.
624 if (SkScalarAbs(rect.fLeft) > 16000.f || SkScalarAbs(rect.fTop) > 16000.f ||
625 SkScalarAbs(rect.fRight) > 16000.f || SkScalarAbs(rect.fBottom) > 16000.f) {
626 return nullptr;
627 }
628 }
629
630 const float sixSigma = 6 * transformedSigma;
631 std::unique_ptr<GrFragmentProcessor> integral = make_rect_integral_fp(context, sixSigma);
632 if (!integral) {
633 return nullptr;
634 }
635
636 // In the fast variant we think of the midpoint of the integral texture as aligning with the
637 // closest rect edge both in x and y. To simplify texture coord calculation we inset the rect so
638 // that the edge of the inset rect corresponds to t = 0 in the texture. It actually simplifies
639 // things a bit in the !isFast case, too.
640 float threeSigma = sixSigma / 2;
641 SkRect insetRect = {rect.left() + threeSigma,
642 rect.top() + threeSigma,
643 rect.right() - threeSigma,
644 rect.bottom() - threeSigma};
645
646 // In our fast variant we find the nearest horizontal and vertical edges and for each do a
647 // lookup in the integral texture for each and multiply them. When the rect is less than 6 sigma
648 // wide then things aren't so simple and we have to consider both the left and right edge of the
649 // rectangle (and similar in y).
650 bool isFast = insetRect.isSorted();
651
652 static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
653 // Effect that is a LUT for integral of normal distribution. The value at x:[0,6*sigma] is
654 // the integral from -inf to (3*sigma - x). I.e. x is mapped from [0, 6*sigma] to
655 // [3*sigma to -3*sigma]. The flip saves a reversal in the shader.
656 "uniform shader integral;"
657
658 "uniform float4 rect;"
659 "uniform int isFast;" // specialized
660
661 "half4 main(float2 pos) {"
662 "half xCoverage, yCoverage;"
663 "if (bool(isFast)) {"
664 // Get the smaller of the signed distance from the frag coord to the left and right
665 // edges and similar for y.
666 // The integral texture goes "backwards" (from 3*sigma to -3*sigma), So, the below
667 // computations align the left edge of the integral texture with the inset rect's
668 // edge extending outward 6 * sigma from the inset rect.
669 "half2 xy = max(half2(rect.LT - pos), half2(pos - rect.RB));"
670 "xCoverage = integral.eval(half2(xy.x, 0.5)).a;"
671 "yCoverage = integral.eval(half2(xy.y, 0.5)).a;"
672 "} else {"
673 // We just consider just the x direction here. In practice we compute x and y
674 // separately and multiply them together.
675 // We define our coord system so that the point at which we're evaluating a kernel
676 // defined by the normal distribution (K) at 0. In this coord system let L be left
677 // edge and R be the right edge of the rectangle.
678 // We can calculate C by integrating K with the half infinite ranges outside the
679 // L to R range and subtracting from 1:
680 // C = 1 - <integral of K from from -inf to L> - <integral of K from R to inf>
681 // K is symmetric about x=0 so:
682 // C = 1 - <integral of K from from -inf to L> - <integral of K from -inf to -R>
683
684 // The integral texture goes "backwards" (from 3*sigma to -3*sigma) which is
685 // factored in to the below calculations.
686 // Also, our rect uniform was pre-inset by 3 sigma from the actual rect being
687 // blurred, also factored in.
688 "half4 rect = half4(half2(rect.LT - pos), half2(pos - rect.RB));"
689 "xCoverage = 1 - integral.eval(half2(rect.L, 0.5)).a"
690 "- integral.eval(half2(rect.R, 0.5)).a;"
691 "yCoverage = 1 - integral.eval(half2(rect.T, 0.5)).a"
692 "- integral.eval(half2(rect.B, 0.5)).a;"
693 "}"
694 "return half4(xCoverage * yCoverage);"
695 "}"
696 );
697
698 std::unique_ptr<GrFragmentProcessor> fp =
699 GrSkSLFP::Make(effect, "RectBlur", /*inputFP=*/nullptr,
700 GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha,
701 "integral", GrSkSLFP::IgnoreOptFlags(std::move(integral)),
702 "rect", insetRect,
703 "isFast", GrSkSLFP::Specialize<int>(isFast));
704 // Modulate blur with the input color.
705 fp = GrBlendFragmentProcessor::Make<SkBlendMode::kModulate>(std::move(fp),
706 /*dst=*/nullptr);
707 if (!invM.isIdentity()) {
708 fp = GrMatrixEffect::Make(invM, std::move(fp));
709 }
710 return GrFragmentProcessor::DeviceSpace(std::move(fp));
711 }
712
713 ///////////////////////////////////////////////////////////////////////////////
714 // RRect Blur
715 ///////////////////////////////////////////////////////////////////////////////
716
717 static constexpr auto kBlurredRRectMaskOrigin = kTopLeft_GrSurfaceOrigin;
718
make_blurred_rrect_key(skgpu::UniqueKey * key,const SkRRect & rrectToDraw,float xformedSigma)719 static void make_blurred_rrect_key(skgpu::UniqueKey* key,
720 const SkRRect& rrectToDraw,
721 float xformedSigma) {
722 SkASSERT(!skgpu::BlurIsEffectivelyIdentity(xformedSigma));
723 static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
724
725 skgpu::UniqueKey::Builder builder(key, kDomain, 9, "RoundRect Blur Mask");
726 builder[0] = SkScalarCeilToInt(xformedSigma - 1 / 6.0f);
727
728 int index = 1;
729 // TODO: this is overkill for _simple_ circular rrects
730 for (auto c : {SkRRect::kUpperLeft_Corner,
731 SkRRect::kUpperRight_Corner,
732 SkRRect::kLowerRight_Corner,
733 SkRRect::kLowerLeft_Corner}) {
734 SkASSERT(SkScalarIsInt(rrectToDraw.radii(c).fX) && SkScalarIsInt(rrectToDraw.radii(c).fY));
735 builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fX);
736 builder[index++] = SkScalarCeilToInt(rrectToDraw.radii(c).fY);
737 }
738 builder.finish();
739 }
740
fillin_view_on_gpu(GrDirectContext * dContext,const GrSurfaceProxyView & lazyView,GrThreadSafeCache::Trampoline * trampoline,const SkRRect & rrectToDraw,const SkISize & dimensions,float xformedSigma)741 static bool fillin_view_on_gpu(GrDirectContext* dContext,
742 const GrSurfaceProxyView& lazyView,
743 GrThreadSafeCache::Trampoline* trampoline,
744 const SkRRect& rrectToDraw,
745 const SkISize& dimensions,
746 float xformedSigma) {
747 SkASSERT(!skgpu::BlurIsEffectivelyIdentity(xformedSigma));
748
749 // We cache blur masks. Use default surface props here so we can use the same cached mask
750 // regardless of the final dst surface.
751 SkSurfaceProps defaultSurfaceProps;
752
753 std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> sdc =
754 skgpu::ganesh::SurfaceDrawContext::MakeWithFallback(dContext,
755 GrColorType::kAlpha_8,
756 nullptr,
757 SkBackingFit::kExact,
758 dimensions,
759 defaultSurfaceProps,
760 1,
761 skgpu::Mipmapped::kNo,
762 GrProtected::kNo,
763 kBlurredRRectMaskOrigin);
764 if (!sdc) {
765 return false;
766 }
767
768 GrPaint paint;
769
770 sdc->clear(SK_PMColor4fTRANSPARENT);
771 sdc->drawRRect(nullptr,
772 std::move(paint),
773 GrAA::kYes,
774 SkMatrix::I(),
775 rrectToDraw,
776 GrStyle::SimpleFill());
777
778 GrSurfaceProxyView srcView = sdc->readSurfaceView();
779 SkASSERT(srcView.asTextureProxy());
780 auto rtc2 = GaussianBlur(dContext,
781 std::move(srcView),
782 sdc->colorInfo().colorType(),
783 sdc->colorInfo().alphaType(),
784 nullptr,
785 SkIRect::MakeSize(dimensions),
786 SkIRect::MakeSize(dimensions),
787 xformedSigma,
788 xformedSigma,
789 SkTileMode::kClamp,
790 SkBackingFit::kExact);
791 if (!rtc2 || !rtc2->readSurfaceView()) {
792 return false;
793 }
794
795 auto view = rtc2->readSurfaceView();
796 SkASSERT(view.swizzle() == lazyView.swizzle());
797 SkASSERT(view.origin() == lazyView.origin());
798 trampoline->fProxy = view.asTextureProxyRef();
799
800 return true;
801 }
802
803 // Create a cpu-side blurred-rrect mask that is close to the version the gpu would've produced.
804 // The match needs to be close bc the cpu- and gpu-generated version must be interchangeable.
create_mask_on_cpu(GrRecordingContext * rContext,const SkRRect & rrectToDraw,const SkISize & dimensions,float xformedSigma)805 static GrSurfaceProxyView create_mask_on_cpu(GrRecordingContext* rContext,
806 const SkRRect& rrectToDraw,
807 const SkISize& dimensions,
808 float xformedSigma) {
809 SkBitmap result = skgpu::CreateRRectBlurMask(rrectToDraw, dimensions, xformedSigma);
810 if (result.empty()) {
811 return {};
812 }
813
814 auto view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, result));
815 if (!view) {
816 return {};
817 }
818
819 SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
820 return view;
821 }
822
find_or_create_rrect_blur_mask_fp(GrRecordingContext * rContext,const SkRRect & rrectToDraw,const SkISize & dimensions,float xformedSigma)823 static std::unique_ptr<GrFragmentProcessor> find_or_create_rrect_blur_mask_fp(
824 GrRecordingContext* rContext,
825 const SkRRect& rrectToDraw,
826 const SkISize& dimensions,
827 float xformedSigma) {
828 SkASSERT(!skgpu::BlurIsEffectivelyIdentity(xformedSigma));
829 skgpu::UniqueKey key;
830 make_blurred_rrect_key(&key, rrectToDraw, xformedSigma);
831
832 auto threadSafeCache = rContext->priv().threadSafeCache();
833
834 // It seems like we could omit this matrix and modify the shader code to not normalize
835 // the coords used to sample the texture effect. However, the "proxyDims" value in the
836 // shader is not always the actual the proxy dimensions. This is because 'dimensions' here
837 // was computed using integer corner radii as determined in
838 // SkComputeBlurredRRectParams whereas the shader code uses the float radius to compute
839 // 'proxyDims'. Why it draws correctly with these unequal values is a mystery for the ages.
840 auto m = SkMatrix::Scale(dimensions.width(), dimensions.height());
841
842 GrSurfaceProxyView view;
843
844 if (GrDirectContext* dContext = rContext->asDirectContext()) {
845 // The gpu thread gets priority over the recording threads. If the gpu thread is first,
846 // it crams a lazy proxy into the cache and then fills it in later.
847 auto [lazyView, trampoline] = GrThreadSafeCache::CreateLazyView(dContext,
848 GrColorType::kAlpha_8,
849 dimensions,
850 kBlurredRRectMaskOrigin,
851 SkBackingFit::kExact);
852 if (!lazyView) {
853 return nullptr;
854 }
855
856 view = threadSafeCache->findOrAdd(key, lazyView);
857 if (view != lazyView) {
858 SkASSERT(view.asTextureProxy());
859 SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
860 return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
861 }
862
863 if (!fillin_view_on_gpu(dContext,
864 lazyView,
865 trampoline.get(),
866 rrectToDraw,
867 dimensions,
868 xformedSigma)) {
869 // In this case something has gone disastrously wrong so set up to drop the draw
870 // that needed this resource and reduce future pollution of the cache.
871 threadSafeCache->remove(key);
872 return nullptr;
873 }
874 } else {
875 view = threadSafeCache->find(key);
876 if (view) {
877 SkASSERT(view.asTextureProxy());
878 SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
879 return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
880 }
881
882 view = create_mask_on_cpu(rContext, rrectToDraw, dimensions, xformedSigma);
883 if (!view) {
884 return nullptr;
885 }
886
887 view = threadSafeCache->add(key, view);
888 }
889
890 SkASSERT(view.asTextureProxy());
891 SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
892 return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
893 }
894
make_rrect_blur(GrRecordingContext * context,float sigma,float xformedSigma,const SkRRect & srcRRect,const SkRRect & devRRect)895 static std::unique_ptr<GrFragmentProcessor> make_rrect_blur(GrRecordingContext* context,
896 float sigma,
897 float xformedSigma,
898 const SkRRect& srcRRect,
899 const SkRRect& devRRect) {
900 SkASSERTF(!SkRRectPriv::IsCircle(devRRect),
901 "Unexpected circle. %d\n\t%s\n\t%s",
902 SkRRectPriv::IsCircle(srcRRect),
903 srcRRect.dumpToString(true).c_str(),
904 devRRect.dumpToString(true).c_str());
905 SkASSERTF(!devRRect.isRect(),
906 "Unexpected rect. %d\n\t%s\n\t%s",
907 srcRRect.isRect(),
908 srcRRect.dumpToString(true).c_str(),
909 devRRect.dumpToString(true).c_str());
910
911 // TODO: loosen this up
912 if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
913 return nullptr;
914 }
915
916 if (skgpu::BlurIsEffectivelyIdentity(xformedSigma)) {
917 return nullptr;
918 }
919
920 // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be sufficiently
921 // small relative to both the size of the corner radius and the width (and height) of the rrect.
922 SkRRect rrectToDraw;
923 SkISize dimensions;
924 SkScalar ignored[kBlurRRectMaxDivisions];
925
926 bool ninePatchable = ComputeBlurredRRectParams(srcRRect,
927 devRRect,
928 sigma,
929 xformedSigma,
930 &rrectToDraw,
931 &dimensions,
932 ignored,
933 ignored,
934 ignored,
935 ignored);
936 if (!ninePatchable) {
937 return nullptr;
938 }
939
940 std::unique_ptr<GrFragmentProcessor> maskFP =
941 find_or_create_rrect_blur_mask_fp(context, rrectToDraw, dimensions, xformedSigma);
942 if (!maskFP) {
943 return nullptr;
944 }
945
946 static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
947 "uniform shader ninePatchFP;"
948
949 "uniform half cornerRadius;"
950 "uniform float4 proxyRect;"
951 "uniform half blurRadius;"
952
953 "half4 main(float2 xy) {"
954 // Warp the fragment position to the appropriate part of the 9-patch blur texture by
955 // snipping out the middle section of the proxy rect.
956 "float2 translatedFragPosFloat = sk_FragCoord.xy - proxyRect.LT;"
957 "float2 proxyCenter = (proxyRect.RB - proxyRect.LT) * 0.5;"
958 "half edgeSize = 2.0 * blurRadius + cornerRadius + 0.5;"
959
960 // Position the fragment so that (0, 0) marks the center of the proxy rectangle.
961 // Negative coordinates are on the left/top side and positive numbers are on the
962 // right/bottom.
963 "translatedFragPosFloat -= proxyCenter;"
964
965 // Temporarily strip off the fragment's sign. x/y are now strictly increasing as we
966 // move away from the center.
967 "half2 fragDirection = half2(sign(translatedFragPosFloat));"
968 "translatedFragPosFloat = abs(translatedFragPosFloat);"
969
970 // Our goal is to snip out the "middle section" of the proxy rect (everything but the
971 // edge). We've repositioned our fragment position so that (0, 0) is the centerpoint
972 // and x/y are always positive, so we can subtract here and interpret negative results
973 // as being within the middle section.
974 "half2 translatedFragPosHalf = half2(translatedFragPosFloat - (proxyCenter - edgeSize));"
975
976 // Remove the middle section by clamping to zero.
977 "translatedFragPosHalf = max(translatedFragPosHalf, 0);"
978
979 // Reapply the fragment's sign, so that negative coordinates once again mean left/top
980 // side and positive means bottom/right side.
981 "translatedFragPosHalf *= fragDirection;"
982
983 // Offset the fragment so that (0, 0) marks the upper-left again, instead of the center
984 // point.
985 "translatedFragPosHalf += half2(edgeSize);"
986
987 "half2 proxyDims = half2(2.0 * edgeSize);"
988 "half2 texCoord = translatedFragPosHalf / proxyDims;"
989
990 "return ninePatchFP.eval(texCoord).aaaa;"
991 "}"
992 );
993
994 float cornerRadius = SkRRectPriv::GetSimpleRadii(devRRect).fX;
995 float blurRadius = 3.f * SkScalarCeilToScalar(xformedSigma - 1 / 6.0f);
996 SkRect proxyRect = devRRect.getBounds().makeOutset(blurRadius, blurRadius);
997
998 auto rrectBlurFP = GrSkSLFP::Make(effect, "RRectBlur", /*inputFP=*/nullptr,
999 GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha,
1000 "ninePatchFP", GrSkSLFP::IgnoreOptFlags(std::move(maskFP)),
1001 "cornerRadius", cornerRadius,
1002 "proxyRect", proxyRect,
1003 "blurRadius", blurRadius);
1004 // Modulate blur with the input color.
1005 return GrBlendFragmentProcessor::Make<SkBlendMode::kModulate>(std::move(rrectBlurFP),
1006 /*dst=*/nullptr);
1007 }
1008
1009 /**
1010 * Try to directly render the mask filter into the target. Returns true if drawing was
1011 * successful. If false is returned then paint is unmodified.
1012 */
direct_filter_mask(GrRecordingContext * context,const SkMaskFilterBase * maskFilter,skgpu::ganesh::SurfaceDrawContext * sdc,GrPaint && paint,const GrClip * clip,const SkMatrix & viewMatrix,const GrStyledShape & shape)1013 static bool direct_filter_mask(GrRecordingContext* context,
1014 const SkMaskFilterBase* maskFilter,
1015 skgpu::ganesh::SurfaceDrawContext* sdc,
1016 GrPaint&& paint,
1017 const GrClip* clip,
1018 const SkMatrix& viewMatrix,
1019 const GrStyledShape& shape) {
1020 SkASSERT(sdc);
1021 if (maskFilter->type() != SkMaskFilterBase::Type::kBlur) {
1022 return false;
1023 }
1024 auto bmf = static_cast<const SkBlurMaskFilterImpl*>(maskFilter);
1025
1026 if (bmf->blurStyle() != kNormal_SkBlurStyle) {
1027 return false;
1028 }
1029
1030 // TODO: we could handle blurred stroked circles
1031 if (!shape.style().isSimpleFill()) {
1032 return false;
1033 }
1034
1035 SkScalar xformedSigma = bmf->computeXformedSigma(viewMatrix);
1036 if (skgpu::BlurIsEffectivelyIdentity(xformedSigma)) {
1037 sdc->drawShape(clip, std::move(paint), GrAA::kYes, viewMatrix, GrStyledShape(shape));
1038 return true;
1039 }
1040
1041 SkRRect srcRRect;
1042 bool inverted;
1043 if (!shape.asRRect(&srcRRect, &inverted) || inverted) {
1044 return false;
1045 }
1046
1047 std::unique_ptr<GrFragmentProcessor> fp;
1048
1049 SkRRect devRRect;
1050 bool devRRectIsValid = srcRRect.transform(viewMatrix, &devRRect);
1051
1052 bool devRRectIsCircle = devRRectIsValid && SkRRectPriv::IsCircle(devRRect);
1053
1054 bool canBeRect = srcRRect.isRect() && viewMatrix.preservesRightAngles();
1055 bool canBeCircle = (SkRRectPriv::IsCircle(srcRRect) && viewMatrix.isSimilarity()) ||
1056 devRRectIsCircle;
1057
1058 if (canBeRect || canBeCircle) {
1059 if (canBeRect) {
1060 fp = make_rect_blur(context, *context->priv().caps()->shaderCaps(),
1061 srcRRect.rect(), viewMatrix, xformedSigma);
1062 } else {
1063 SkRect devBounds;
1064 if (devRRectIsCircle) {
1065 devBounds = devRRect.getBounds();
1066 } else {
1067 SkPoint center = {srcRRect.getBounds().centerX(), srcRRect.getBounds().centerY()};
1068 viewMatrix.mapPoints(¢er, 1);
1069 SkScalar radius = viewMatrix.mapVector(0, srcRRect.width()/2.f).length();
1070 devBounds = {center.x() - radius,
1071 center.y() - radius,
1072 center.x() + radius,
1073 center.y() + radius};
1074 }
1075 fp = make_circle_blur(context, devBounds, xformedSigma);
1076 }
1077
1078 if (!fp) {
1079 return false;
1080 }
1081
1082 SkRect srcProxyRect = srcRRect.rect();
1083 // Determine how much to outset the src rect to ensure we hit pixels within three sigma.
1084 SkScalar outsetX = 3.0f*xformedSigma;
1085 SkScalar outsetY = 3.0f*xformedSigma;
1086 if (viewMatrix.isScaleTranslate()) {
1087 outsetX /= SkScalarAbs(viewMatrix.getScaleX());
1088 outsetY /= SkScalarAbs(viewMatrix.getScaleY());
1089 } else {
1090 SkSize scale;
1091 if (!viewMatrix.decomposeScale(&scale, nullptr)) {
1092 return false;
1093 }
1094 outsetX /= scale.width();
1095 outsetY /= scale.height();
1096 }
1097 srcProxyRect.outset(outsetX, outsetY);
1098
1099 paint.setCoverageFragmentProcessor(std::move(fp));
1100 sdc->drawRect(clip, std::move(paint), GrAA::kNo, viewMatrix, srcProxyRect);
1101 return true;
1102 }
1103 if (!viewMatrix.isScaleTranslate()) {
1104 return false;
1105 }
1106 if (!devRRectIsValid || !SkRRectPriv::AllCornersCircular(devRRect)) {
1107 return false;
1108 }
1109
1110 fp = make_rrect_blur(context, bmf->sigma(), xformedSigma, srcRRect, devRRect);
1111 if (!fp) {
1112 return false;
1113 }
1114
1115 if (!bmf->ignoreXform()) {
1116 SkRect srcProxyRect = srcRRect.rect();
1117 srcProxyRect.outset(3.0f*bmf->sigma(), 3.0f*bmf->sigma());
1118 paint.setCoverageFragmentProcessor(std::move(fp));
1119 sdc->drawRect(clip, std::move(paint), GrAA::kNo, viewMatrix, srcProxyRect);
1120 } else {
1121 SkMatrix inverse;
1122 if (!viewMatrix.invert(&inverse)) {
1123 return false;
1124 }
1125
1126 SkIRect proxyBounds;
1127 float extra=3.f*SkScalarCeilToScalar(xformedSigma-1/6.0f);
1128 devRRect.rect().makeOutset(extra, extra).roundOut(&proxyBounds);
1129
1130 paint.setCoverageFragmentProcessor(std::move(fp));
1131 sdc->fillPixelsWithLocalMatrix(clip, std::move(paint), proxyBounds, inverse);
1132 }
1133
1134 return true;
1135 }
1136
1137 // The key and clip-bounds are computed together because the caching decision can impact the
1138 // clip-bound - since we only cache un-clipped masks the clip can be removed entirely.
1139 // A 'false' return value indicates that the shape is known to be clipped away.
compute_key_and_clip_bounds(skgpu::UniqueKey * maskKey,SkIRect * boundsForClip,const GrCaps * caps,const SkMatrix & viewMatrix,bool inverseFilled,const SkMaskFilterBase * maskFilter,const GrStyledShape & shape,const SkIRect & unclippedDevShapeBounds,const SkIRect & devClipBounds)1140 static bool compute_key_and_clip_bounds(skgpu::UniqueKey* maskKey,
1141 SkIRect* boundsForClip,
1142 const GrCaps* caps,
1143 const SkMatrix& viewMatrix,
1144 bool inverseFilled,
1145 const SkMaskFilterBase* maskFilter,
1146 const GrStyledShape& shape,
1147 const SkIRect& unclippedDevShapeBounds,
1148 const SkIRect& devClipBounds) {
1149 SkASSERT(maskFilter);
1150 *boundsForClip = devClipBounds;
1151
1152 #ifndef SK_DISABLE_MASKFILTERED_MASK_CACHING
1153 // To prevent overloading the cache with entries during animations we limit the cache of masks
1154 // to cases where the matrix preserves axis alignment.
1155 bool useCache = !inverseFilled && viewMatrix.preservesAxisAlignment() &&
1156 shape.hasUnstyledKey() && as_MFB(maskFilter)->asABlur(nullptr);
1157
1158 if (useCache) {
1159 SkIRect clippedMaskRect, unClippedMaskRect;
1160 can_filter_mask(maskFilter, shape, unclippedDevShapeBounds, devClipBounds,
1161 viewMatrix, &clippedMaskRect);
1162 if (clippedMaskRect.isEmpty()) {
1163 return false;
1164 }
1165 can_filter_mask(maskFilter, shape, unclippedDevShapeBounds, unclippedDevShapeBounds,
1166 viewMatrix, &unClippedMaskRect);
1167
1168 // Use the cache only if >50% of the filtered mask is visible.
1169 int unclippedWidth = unClippedMaskRect.width();
1170 int unclippedHeight = unClippedMaskRect.height();
1171 int64_t unclippedArea = sk_64_mul(unclippedWidth, unclippedHeight);
1172 int64_t clippedArea = sk_64_mul(clippedMaskRect.width(), clippedMaskRect.height());
1173 int maxTextureSize = caps->maxTextureSize();
1174 if (unclippedArea > 2 * clippedArea || unclippedWidth > maxTextureSize ||
1175 unclippedHeight > maxTextureSize) {
1176 useCache = false;
1177 } else {
1178 // Make the clip not affect the mask
1179 *boundsForClip = unclippedDevShapeBounds;
1180 }
1181 }
1182
1183 if (useCache) {
1184 static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
1185 skgpu::UniqueKey::Builder builder(maskKey, kDomain, 5 + 2 + shape.unstyledKeySize(),
1186 "Mask Filtered Masks");
1187
1188 // We require the upper left 2x2 of the matrix to match exactly for a cache hit.
1189 SkScalar sx = viewMatrix.get(SkMatrix::kMScaleX);
1190 SkScalar sy = viewMatrix.get(SkMatrix::kMScaleY);
1191 SkScalar kx = viewMatrix.get(SkMatrix::kMSkewX);
1192 SkScalar ky = viewMatrix.get(SkMatrix::kMSkewY);
1193 SkScalar tx = viewMatrix.get(SkMatrix::kMTransX);
1194 SkScalar ty = viewMatrix.get(SkMatrix::kMTransY);
1195 // Allow 8 bits each in x and y of subpixel positioning. But, note that we're allowing
1196 // reuse for integer translations.
1197 SkFixed fracX = SkScalarToFixed(SkScalarFraction(tx)) & 0x0000FF00;
1198 SkFixed fracY = SkScalarToFixed(SkScalarFraction(ty)) & 0x0000FF00;
1199
1200 #ifdef SKIA_OHOS
1201 builder[0] = SkFloat2Bits(roundf(sx * 100) / 100.f);
1202 builder[1] = SkFloat2Bits(roundf(sy * 100) / 100.f);
1203 builder[2] = SkFloat2Bits(roundf(kx * 100) / 100.f);
1204 builder[3] = SkFloat2Bits(roundf(ky * 100) / 100.f);
1205 #else
1206 builder[0] = SkFloat2Bits(sx);
1207 builder[1] = SkFloat2Bits(sy);
1208 builder[2] = SkFloat2Bits(kx);
1209 builder[3] = SkFloat2Bits(ky);
1210 #endif // SKIA_OHOS
1211 // Distinguish between hairline and filled paths. For hairlines, we also need to include
1212 // the cap. (SW grows hairlines by 0.5 pixel with round and square caps). Note that
1213 // stroke-and-fill of hairlines is turned into pure fill by SkStrokeRec, so this covers
1214 // all cases we might see.
1215 uint32_t styleBits = shape.style().isSimpleHairline()
1216 ? ((shape.style().strokeRec().getCap() << 1) | 1)
1217 : 0;
1218 builder[4] = fracX | (fracY >> 8) | (styleBits << 16);
1219
1220 SkMaskFilterBase::BlurRec rec;
1221 SkAssertResult(as_MFB(maskFilter)->asABlur(&rec));
1222
1223 builder[5] = rec.fStyle; // TODO: we could put this with the other style bits
1224 #ifdef SKIA_OHOS
1225 builder[6] = SkFloat2Bits(roundf(rec.fSigma * 100) / 100.f);
1226 #else
1227 builder[6] = SkFloat2Bits(rec.fSigma);
1228 #endif // SKIA_OHOS
1229 shape.writeUnstyledKey(&builder[7]);
1230 }
1231 #endif
1232
1233 return true;
1234 }
1235
1236 /**
1237 * This function is used to implement filters that require an explicit src mask. It should only
1238 * be called if can_filter_mask returned true and the maskRect param should be the output from
1239 * that call.
1240 * Implementations are free to get the GrContext from the src texture in order to create
1241 * additional textures and perform multiple passes.
1242 */
filter_mask(GrRecordingContext * context,const SkMaskFilterBase * maskFilter,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,const SkMatrix & ctm,const SkIRect & maskRect)1243 static GrSurfaceProxyView filter_mask(GrRecordingContext* context,
1244 const SkMaskFilterBase* maskFilter,
1245 GrSurfaceProxyView srcView,
1246 GrColorType srcColorType,
1247 SkAlphaType srcAlphaType,
1248 const SkMatrix& ctm,
1249 const SkIRect& maskRect) {
1250 if (maskFilter->type() != SkMaskFilterBase::Type::kBlur) {
1251 return {};
1252 }
1253 auto bmf = static_cast<const SkBlurMaskFilterImpl*>(maskFilter);
1254 // 'maskRect' isn't snapped to the UL corner but the mask in 'src' is.
1255 const SkIRect clipRect = SkIRect::MakeWH(maskRect.width(), maskRect.height());
1256
1257 SkScalar xformedSigma = bmf->computeXformedSigma(ctm);
1258
1259 // If we're doing a normal blur, we can clobber the pathTexture in the
1260 // gaussianBlur. Otherwise, we need to save it for later compositing.
1261 bool isNormalBlur = (kNormal_SkBlurStyle == bmf->blurStyle());
1262 auto srcBounds = SkIRect::MakeSize(srcView.proxy()->dimensions());
1263 auto surfaceDrawContext = GaussianBlur(context,
1264 srcView,
1265 srcColorType,
1266 srcAlphaType,
1267 nullptr,
1268 clipRect,
1269 srcBounds,
1270 xformedSigma,
1271 xformedSigma,
1272 SkTileMode::kClamp);
1273 if (!surfaceDrawContext || !surfaceDrawContext->asTextureProxy()) {
1274 return {};
1275 }
1276
1277 if (!isNormalBlur) {
1278 GrPaint paint;
1279 // Blend pathTexture over blurTexture.
1280 paint.setCoverageFragmentProcessor(GrTextureEffect::Make(std::move(srcView), srcAlphaType));
1281 if (kInner_SkBlurStyle == bmf->blurStyle()) {
1282 // inner: dst = dst * src
1283 paint.setCoverageSetOpXPFactory(SkRegion::kIntersect_Op);
1284 } else if (kSolid_SkBlurStyle == bmf->blurStyle()) {
1285 // solid: dst = src + dst - src * dst
1286 // = src + (1 - src) * dst
1287 paint.setCoverageSetOpXPFactory(SkRegion::kUnion_Op);
1288 } else if (kOuter_SkBlurStyle == bmf->blurStyle()) {
1289 // outer: dst = dst * (1 - src)
1290 // = 0 * src + (1 - src) * dst
1291 paint.setCoverageSetOpXPFactory(SkRegion::kDifference_Op);
1292 } else {
1293 paint.setCoverageSetOpXPFactory(SkRegion::kReplace_Op);
1294 }
1295
1296 surfaceDrawContext->fillPixelsWithLocalMatrix(nullptr, std::move(paint), clipRect,
1297 SkMatrix::I());
1298 }
1299
1300 return surfaceDrawContext->readSurfaceView();
1301 }
1302
hw_create_filtered_mask(GrDirectContext * dContext,skgpu::ganesh::SurfaceDrawContext * sdc,const SkMatrix & viewMatrix,const GrStyledShape & shape,const SkMaskFilterBase * filter,const SkIRect & unclippedDevShapeBounds,const SkIRect & clipBounds,SkIRect * maskRect,skgpu::UniqueKey * key)1303 static GrSurfaceProxyView hw_create_filtered_mask(GrDirectContext* dContext,
1304 skgpu::ganesh::SurfaceDrawContext* sdc,
1305 const SkMatrix& viewMatrix,
1306 const GrStyledShape& shape,
1307 const SkMaskFilterBase* filter,
1308 const SkIRect& unclippedDevShapeBounds,
1309 const SkIRect& clipBounds,
1310 SkIRect* maskRect,
1311 skgpu::UniqueKey* key) {
1312 if (!can_filter_mask(filter, shape, unclippedDevShapeBounds, clipBounds, viewMatrix,
1313 maskRect)) {
1314 return {};
1315 }
1316
1317 if (clip_bounds_quick_reject(clipBounds, *maskRect)) {
1318 // clipped out
1319 return {};
1320 }
1321
1322 auto threadSafeCache = dContext->priv().threadSafeCache();
1323
1324 GrSurfaceProxyView lazyView;
1325 sk_sp<GrThreadSafeCache::Trampoline> trampoline;
1326
1327 if (key->isValid()) {
1328 // In this case, we want GPU-filtered masks to have priority over SW-generated ones so
1329 // we pre-emptively add a lazy-view to the cache and fill it in later.
1330 std::tie(lazyView, trampoline) = GrThreadSafeCache::CreateLazyView(
1331 dContext, GrColorType::kAlpha_8, maskRect->size(),
1332 kMaskOrigin, SkBackingFit::kApprox);
1333 if (!lazyView) {
1334 return {}; // fall back to a SW-created mask - 'create_mask_GPU' probably won't succeed
1335 }
1336
1337 key->setCustomData(create_data(*maskRect, unclippedDevShapeBounds));
1338 auto [cachedView, data] = threadSafeCache->findOrAddWithData(*key, lazyView);
1339 if (cachedView != lazyView) {
1340 // In this case, the gpu-thread lost out to a recording thread - use its result.
1341 SkASSERT(data);
1342 SkASSERT(cachedView.asTextureProxy());
1343 SkASSERT(cachedView.origin() == kMaskOrigin);
1344
1345 *maskRect = extract_draw_rect_from_data(data.get(), unclippedDevShapeBounds);
1346 return cachedView;
1347 }
1348 }
1349
1350 std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> maskSDC(
1351 create_mask_GPU(dContext, *maskRect, viewMatrix, shape, sdc->numSamples()));
1352 if (!maskSDC) {
1353 if (key->isValid()) {
1354 // It is very unlikely that 'create_mask_GPU' will fail after 'CreateLazyView'
1355 // succeeded but, if it does, remove the lazy-view from the cache and fallback to
1356 // a SW-created mask. Note that any recording threads that glommed onto the
1357 // lazy-view will have to, later, drop those draws.
1358 threadSafeCache->remove(*key);
1359 }
1360 return {};
1361 }
1362
1363 auto filteredMaskView = filter_mask(dContext, filter,
1364 maskSDC->readSurfaceView(),
1365 maskSDC->colorInfo().colorType(),
1366 maskSDC->colorInfo().alphaType(),
1367 viewMatrix,
1368 *maskRect);
1369 if (!filteredMaskView) {
1370 if (key->isValid()) {
1371 // Remove the lazy-view from the cache and fallback to a SW-created mask. Note that
1372 // any recording threads that glommed onto the lazy-view will have to, later, drop
1373 // those draws.
1374 threadSafeCache->remove(*key);
1375 }
1376 return {};
1377 }
1378
1379 if (key->isValid()) {
1380 SkASSERT(filteredMaskView.dimensions() == lazyView.dimensions());
1381 SkASSERT(filteredMaskView.swizzle() == lazyView.swizzle());
1382 SkASSERT(filteredMaskView.origin() == lazyView.origin());
1383
1384 trampoline->fProxy = filteredMaskView.asTextureProxyRef();
1385 return lazyView;
1386 }
1387
1388 return filteredMaskView;
1389 }
1390
draw_shape_with_mask_filter(GrRecordingContext * rContext,skgpu::ganesh::SurfaceDrawContext * sdc,const GrClip * clip,GrPaint && paint,const SkMatrix & viewMatrix,const SkMaskFilterBase * maskFilter,const GrStyledShape & origShape)1391 static void draw_shape_with_mask_filter(GrRecordingContext* rContext,
1392 skgpu::ganesh::SurfaceDrawContext* sdc,
1393 const GrClip* clip,
1394 GrPaint&& paint,
1395 const SkMatrix& viewMatrix,
1396 const SkMaskFilterBase* maskFilter,
1397 const GrStyledShape& origShape) {
1398 SkASSERT(maskFilter);
1399
1400 const GrStyledShape* shape = &origShape;
1401 SkTLazy<GrStyledShape> tmpShape;
1402
1403 if (origShape.style().applies()) {
1404 SkScalar styleScale = GrStyle::MatrixToScaleFactor(viewMatrix);
1405 if (styleScale == 0) {
1406 return;
1407 }
1408
1409 tmpShape.init(origShape.applyStyle(GrStyle::Apply::kPathEffectAndStrokeRec, styleScale));
1410 if (tmpShape->isEmpty()) {
1411 return;
1412 }
1413
1414 shape = tmpShape.get();
1415 }
1416
1417 #ifdef SKIA_OHOS
1418 SkIRect devSpaceShapeBounds;
1419 if (get_unclipped_shape_dev_bounds(*shape, viewMatrix, &devSpaceShapeBounds) &&
1420 maskFilter->quick_check_gpu_draw(viewMatrix, devSpaceShapeBounds)) {
1421 SkRRect srcRRect;
1422 bool inverted;
1423 bool canUseSDFShadow = SDFBlur::GetSDFBlurEnabled() &&
1424 shape->asRRect(&srcRRect, &inverted) && !inverted &&
1425 (paint.numTotalFragmentProcessors() == 0);
1426 if (canUseSDFShadow &&
1427 maskFilter->directFilterRRectMaskGPU(rContext, sdc, std::move(paint), clip, viewMatrix, *shape, srcRRect)) {
1428 return;
1429 }
1430 }
1431 #ifdef SKIA_OHOS_FOR_OHOS_TRACE
1432 if (SDFBlur::GetSDFBlurDebugTraceEnabled()) {
1433 HITRACE_OHOS_NAME_ALWAYS("directFilterRRectMaskGPU doSDF fail!");
1434 }
1435 #endif // SKIA_OHOS_FOR_OHOS_TRACE
1436 #endif // SKIA_OHOS
1437
1438 if (direct_filter_mask(rContext, maskFilter, sdc, std::move(paint), clip, viewMatrix, *shape)) {
1439 // the mask filter was able to draw itself directly, so there's nothing
1440 // left to do.
1441 return;
1442 }
1443 assert_alive(paint);
1444
1445 // If the path is hairline, ignore inverse fill.
1446 bool inverseFilled = shape->inverseFilled() &&
1447 !GrIsStrokeHairlineOrEquivalent(shape->style(), viewMatrix, nullptr);
1448
1449 SkMatrix matrixTrans = SkMatrix::I().Translate(viewMatrix.getTranslateX(), viewMatrix.getTranslateY());
1450 SkIRect unclippedDevShapeBounds, devClipBounds;
1451 if (!get_shape_and_clip_bounds(sdc, clip, *shape, viewMatrix,
1452 &unclippedDevShapeBounds, &devClipBounds)) {
1453 // TODO: just cons up an opaque mask here
1454 if (!inverseFilled) {
1455 return;
1456 }
1457 }
1458
1459 skgpu::UniqueKey maskKey;
1460 SkIRect boundsForClip;
1461 if (!compute_key_and_clip_bounds(&maskKey, &boundsForClip,
1462 sdc->caps(),
1463 viewMatrix, inverseFilled,
1464 maskFilter, *shape,
1465 unclippedDevShapeBounds,
1466 devClipBounds)) {
1467 return; // 'shape' was entirely clipped out
1468 }
1469
1470 GrSurfaceProxyView filteredMaskView;
1471 SkIRect maskRect;
1472
1473 if (auto dContext = rContext->asDirectContext()) {
1474 filteredMaskView = hw_create_filtered_mask(dContext, sdc,
1475 viewMatrix,
1476 *shape, maskFilter,
1477 unclippedDevShapeBounds, boundsForClip,
1478 &maskRect, &maskKey);
1479 if (filteredMaskView) {
1480 if (draw_mask(sdc, clip, viewMatrix, maskRect, std::move(paint), std::move(filteredMaskView))) {
1481 // This path is completely drawn
1482 return;
1483 }
1484 assert_alive(paint);
1485 }
1486 }
1487
1488 // Either HW mask rendering failed or we're in a DDL recording thread
1489 filteredMaskView = sw_create_filtered_mask(rContext,
1490 viewMatrix, *shape, maskFilter,
1491 unclippedDevShapeBounds, boundsForClip,
1492 &maskRect, &maskKey);
1493 if (filteredMaskView) {
1494 if (draw_mask(sdc, clip, viewMatrix, maskRect, std::move(paint), std::move(filteredMaskView))) {
1495 return;
1496 }
1497 assert_alive(paint);
1498 }
1499 }
1500
ComputeBlurredRRectParams(const SkRRect & srcRRect,const SkRRect & devRRect,SkScalar sigma,SkScalar xformedSigma,SkRRect * rrectToDraw,SkISize * widthHeight,SkScalar rectXs[kBlurRRectMaxDivisions],SkScalar rectYs[kBlurRRectMaxDivisions],SkScalar texXs[kBlurRRectMaxDivisions],SkScalar texYs[kBlurRRectMaxDivisions])1501 bool ComputeBlurredRRectParams(const SkRRect& srcRRect,
1502 const SkRRect& devRRect,
1503 SkScalar sigma,
1504 SkScalar xformedSigma,
1505 SkRRect* rrectToDraw,
1506 SkISize* widthHeight,
1507 SkScalar rectXs[kBlurRRectMaxDivisions],
1508 SkScalar rectYs[kBlurRRectMaxDivisions],
1509 SkScalar texXs[kBlurRRectMaxDivisions],
1510 SkScalar texYs[kBlurRRectMaxDivisions]) {
1511 unsigned int devBlurRadius = 3 * SkScalarCeilToInt(xformedSigma - 1 / 6.0f);
1512 SkScalar srcBlurRadius = 3.0f * sigma;
1513
1514 const SkRect& devOrig = devRRect.getBounds();
1515 const SkVector& devRadiiUL = devRRect.radii(SkRRect::kUpperLeft_Corner);
1516 const SkVector& devRadiiUR = devRRect.radii(SkRRect::kUpperRight_Corner);
1517 const SkVector& devRadiiLR = devRRect.radii(SkRRect::kLowerRight_Corner);
1518 const SkVector& devRadiiLL = devRRect.radii(SkRRect::kLowerLeft_Corner);
1519
1520 const int devLeft = SkScalarCeilToInt(std::max<SkScalar>(devRadiiUL.fX, devRadiiLL.fX));
1521 const int devTop = SkScalarCeilToInt(std::max<SkScalar>(devRadiiUL.fY, devRadiiUR.fY));
1522 const int devRight = SkScalarCeilToInt(std::max<SkScalar>(devRadiiUR.fX, devRadiiLR.fX));
1523 const int devBot = SkScalarCeilToInt(std::max<SkScalar>(devRadiiLL.fY, devRadiiLR.fY));
1524
1525 // This is a conservative check for nine-patchability
1526 if (devOrig.fLeft + devLeft + devBlurRadius >= devOrig.fRight - devRight - devBlurRadius ||
1527 devOrig.fTop + devTop + devBlurRadius >= devOrig.fBottom - devBot - devBlurRadius) {
1528 return false;
1529 }
1530
1531 const SkVector& srcRadiiUL = srcRRect.radii(SkRRect::kUpperLeft_Corner);
1532 const SkVector& srcRadiiUR = srcRRect.radii(SkRRect::kUpperRight_Corner);
1533 const SkVector& srcRadiiLR = srcRRect.radii(SkRRect::kLowerRight_Corner);
1534 const SkVector& srcRadiiLL = srcRRect.radii(SkRRect::kLowerLeft_Corner);
1535
1536 const SkScalar srcLeft = std::max<SkScalar>(srcRadiiUL.fX, srcRadiiLL.fX);
1537 const SkScalar srcTop = std::max<SkScalar>(srcRadiiUL.fY, srcRadiiUR.fY);
1538 const SkScalar srcRight = std::max<SkScalar>(srcRadiiUR.fX, srcRadiiLR.fX);
1539 const SkScalar srcBot = std::max<SkScalar>(srcRadiiLL.fY, srcRadiiLR.fY);
1540
1541 int newRRWidth = 2 * devBlurRadius + devLeft + devRight + 1;
1542 int newRRHeight = 2 * devBlurRadius + devTop + devBot + 1;
1543 widthHeight->fWidth = newRRWidth + 2 * devBlurRadius;
1544 widthHeight->fHeight = newRRHeight + 2 * devBlurRadius;
1545
1546 const SkRect srcProxyRect = srcRRect.getBounds().makeOutset(srcBlurRadius, srcBlurRadius);
1547
1548 rectXs[0] = srcProxyRect.fLeft;
1549 rectXs[1] = srcProxyRect.fLeft + 2 * srcBlurRadius + srcLeft;
1550 rectXs[2] = srcProxyRect.fRight - 2 * srcBlurRadius - srcRight;
1551 rectXs[3] = srcProxyRect.fRight;
1552
1553 rectYs[0] = srcProxyRect.fTop;
1554 rectYs[1] = srcProxyRect.fTop + 2 * srcBlurRadius + srcTop;
1555 rectYs[2] = srcProxyRect.fBottom - 2 * srcBlurRadius - srcBot;
1556 rectYs[3] = srcProxyRect.fBottom;
1557
1558 texXs[0] = 0.0f;
1559 texXs[1] = 2.0f * devBlurRadius + devLeft;
1560 texXs[2] = 2.0f * devBlurRadius + devLeft + 1;
1561 texXs[3] = SkIntToScalar(widthHeight->fWidth);
1562
1563 texYs[0] = 0.0f;
1564 texYs[1] = 2.0f * devBlurRadius + devTop;
1565 texYs[2] = 2.0f * devBlurRadius + devTop + 1;
1566 texYs[3] = SkIntToScalar(widthHeight->fHeight);
1567
1568 const SkRect newRect = SkRect::MakeXYWH(SkIntToScalar(devBlurRadius),
1569 SkIntToScalar(devBlurRadius),
1570 SkIntToScalar(newRRWidth),
1571 SkIntToScalar(newRRHeight));
1572 SkVector newRadii[4];
1573 newRadii[0] = {SkScalarCeilToScalar(devRadiiUL.fX), SkScalarCeilToScalar(devRadiiUL.fY)};
1574 newRadii[1] = {SkScalarCeilToScalar(devRadiiUR.fX), SkScalarCeilToScalar(devRadiiUR.fY)};
1575 newRadii[2] = {SkScalarCeilToScalar(devRadiiLR.fX), SkScalarCeilToScalar(devRadiiLR.fY)};
1576 newRadii[3] = {SkScalarCeilToScalar(devRadiiLL.fX), SkScalarCeilToScalar(devRadiiLL.fY)};
1577
1578 rrectToDraw->setRectRadii(newRect, newRadii);
1579 return true;
1580 }
1581
DrawShapeWithMaskFilter(GrRecordingContext * rContext,skgpu::ganesh::SurfaceDrawContext * sdc,const GrClip * clip,const GrStyledShape & shape,GrPaint && paint,const SkMatrix & viewMatrix,const SkMaskFilter * mf)1582 void DrawShapeWithMaskFilter(GrRecordingContext* rContext,
1583 skgpu::ganesh::SurfaceDrawContext* sdc,
1584 const GrClip* clip,
1585 const GrStyledShape& shape,
1586 GrPaint&& paint,
1587 const SkMatrix& viewMatrix,
1588 const SkMaskFilter* mf) {
1589 draw_shape_with_mask_filter(rContext, sdc, clip, std::move(paint),
1590 viewMatrix, as_MFB(mf), shape);
1591 }
1592
DrawShapeWithMaskFilter(GrRecordingContext * rContext,skgpu::ganesh::SurfaceDrawContext * sdc,const GrClip * clip,const SkPaint & paint,const SkMatrix & ctm,const GrStyledShape & shape)1593 void DrawShapeWithMaskFilter(GrRecordingContext* rContext,
1594 skgpu::ganesh::SurfaceDrawContext* sdc,
1595 const GrClip* clip,
1596 const SkPaint& paint,
1597 const SkMatrix& ctm,
1598 const GrStyledShape& shape) {
1599 if (rContext->abandoned()) {
1600 return;
1601 }
1602
1603 GrPaint grPaint;
1604 if (!SkPaintToGrPaint(rContext, sdc->colorInfo(), paint, ctm, sdc->surfaceProps(), &grPaint)) {
1605 return;
1606 }
1607
1608 SkMaskFilterBase* mf = as_MFB(paint.getMaskFilter());
1609 if (mf && !GrFragmentProcessors::IsSupported(mf)) {
1610 // The MaskFilter wasn't already handled in SkPaintToGrPaint
1611 draw_shape_with_mask_filter(rContext, sdc, clip, std::move(grPaint), ctm, mf, shape);
1612 } else {
1613 sdc->drawShape(clip, std::move(grPaint), sdc->chooseAA(paint), ctm, GrStyledShape(shape));
1614 }
1615 }
1616
1617
1618 // =================== Gaussian Blur =========================================
1619
1620 namespace {
1621
1622 enum class Direction { kX, kY };
1623
make_texture_effect(const GrCaps * caps,GrSurfaceProxyView srcView,SkAlphaType srcAlphaType,const GrSamplerState & sampler,const SkIRect & srcSubset,const SkIRect & srcRelativeDstRect,const SkISize & radii)1624 std::unique_ptr<GrFragmentProcessor> make_texture_effect(const GrCaps* caps,
1625 GrSurfaceProxyView srcView,
1626 SkAlphaType srcAlphaType,
1627 const GrSamplerState& sampler,
1628 const SkIRect& srcSubset,
1629 const SkIRect& srcRelativeDstRect,
1630 const SkISize& radii) {
1631 // It's pretty common to blur a subset of an input texture. In reduced shader mode we always
1632 // apply the wrap mode in the shader.
1633 if (caps->reducedShaderMode()) {
1634 return GrTextureEffect::MakeSubset(std::move(srcView),
1635 srcAlphaType,
1636 SkMatrix::I(),
1637 sampler,
1638 SkRect::Make(srcSubset),
1639 *caps,
1640 GrTextureEffect::kDefaultBorder,
1641 /*alwaysUseShaderTileMode=*/true);
1642 } else {
1643 // Inset because we expect to be invoked at pixel centers
1644 SkRect domain = SkRect::Make(srcRelativeDstRect);
1645 domain.inset(0.5f, 0.5f);
1646 domain.outset(radii.width(), radii.height());
1647 return GrTextureEffect::MakeSubset(std::move(srcView),
1648 srcAlphaType,
1649 SkMatrix::I(),
1650 sampler,
1651 SkRect::Make(srcSubset),
1652 domain,
1653 *caps);
1654 }
1655 }
1656
1657 } // end namespace
1658
1659 /**
1660 * Draws 'dstRect' into 'surfaceFillContext' evaluating a 1D Gaussian over 'srcView'. The src rect
1661 * is 'dstRect' offset by 'dstToSrcOffset'. 'mode' and 'bounds' are applied to the src coords.
1662 */
convolve_gaussian_1d(skgpu::ganesh::SurfaceFillContext * sfc,GrSurfaceProxyView srcView,const SkIRect & srcSubset,SkIVector dstToSrcOffset,const SkIRect & dstRect,SkAlphaType srcAlphaType,Direction direction,int radius,float sigma,SkTileMode mode)1663 static void convolve_gaussian_1d(skgpu::ganesh::SurfaceFillContext* sfc,
1664 GrSurfaceProxyView srcView,
1665 const SkIRect& srcSubset,
1666 SkIVector dstToSrcOffset,
1667 const SkIRect& dstRect,
1668 SkAlphaType srcAlphaType,
1669 Direction direction,
1670 int radius,
1671 float sigma,
1672 SkTileMode mode) {
1673 SkASSERT(radius && !skgpu::BlurIsEffectivelyIdentity(sigma));
1674 auto srcRect = dstRect.makeOffset(dstToSrcOffset);
1675
1676 std::array<SkV4, skgpu::kMaxBlurSamples/2> offsetsAndKernel;
1677 skgpu::Compute1DBlurLinearKernel(sigma, radius, offsetsAndKernel);
1678
1679 // The child of the 1D linear blur effect must be linearly sampled.
1680 GrSamplerState sampler{SkTileModeToWrapMode(mode), GrSamplerState::Filter::kLinear};
1681
1682 SkISize radii = {direction == Direction::kX ? radius : 0,
1683 direction == Direction::kY ? radius : 0};
1684 std::unique_ptr<GrFragmentProcessor> child = make_texture_effect(sfc->caps(),
1685 std::move(srcView),
1686 srcAlphaType,
1687 sampler,
1688 srcSubset,
1689 srcRect,
1690 radii);
1691
1692 auto conv = GrSkSLFP::Make(skgpu::GetLinearBlur1DEffect(radius),
1693 "GaussianBlur1D",
1694 /*inputFP=*/nullptr,
1695 GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha,
1696 "offsetsAndKernel", SkSpan<SkV4>{offsetsAndKernel},
1697 "dir", direction == Direction::kX ? SkV2{1.f, 0.f}
1698 : SkV2{0.f, 1.f},
1699 "child", std::move(child));
1700 sfc->fillRectToRectWithFP(srcRect, dstRect, std::move(conv));
1701 }
1702
convolve_gaussian_2d(GrRecordingContext * rContext,GrSurfaceProxyView srcView,GrColorType srcColorType,const SkIRect & srcBounds,const SkIRect & dstBounds,int radiusX,int radiusY,SkScalar sigmaX,SkScalar sigmaY,SkTileMode mode,sk_sp<SkColorSpace> finalCS,SkBackingFit dstFit)1703 static std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> convolve_gaussian_2d(
1704 GrRecordingContext* rContext,
1705 GrSurfaceProxyView srcView,
1706 GrColorType srcColorType,
1707 const SkIRect& srcBounds,
1708 const SkIRect& dstBounds,
1709 int radiusX,
1710 int radiusY,
1711 SkScalar sigmaX,
1712 SkScalar sigmaY,
1713 SkTileMode mode,
1714 sk_sp<SkColorSpace> finalCS,
1715 SkBackingFit dstFit) {
1716 SkASSERT(radiusX && radiusY);
1717 SkASSERT(!skgpu::BlurIsEffectivelyIdentity(sigmaX) &&
1718 !skgpu::BlurIsEffectivelyIdentity(sigmaY));
1719 // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
1720 // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
1721 auto sdc = skgpu::ganesh::SurfaceDrawContext::Make(
1722 rContext,
1723 srcColorType,
1724 std::move(finalCS),
1725 dstFit,
1726 dstBounds.size(),
1727 SkSurfaceProps(),
1728 /*label=*/"SurfaceDrawContext_ConvolveGaussian2d",
1729 /* sampleCnt= */ 1,
1730 skgpu::Mipmapped::kNo,
1731 srcView.proxy()->isProtected(),
1732 srcView.origin());
1733 if (!sdc) {
1734 return nullptr;
1735 }
1736
1737 // GaussianBlur() should have downsampled the request until we can handle the 2D blur with
1738 // just a uniform array, which is asserted inside the Compute function.
1739 const SkISize radii{radiusX, radiusY};
1740 std::array<SkV4, skgpu::kMaxBlurSamples/4> kernel;
1741 std::array<SkV4, skgpu::kMaxBlurSamples/2> offsets;
1742 skgpu::Compute2DBlurKernel({sigmaX, sigmaY}, radii, kernel);
1743 skgpu::Compute2DBlurOffsets(radii, offsets);
1744
1745 GrSamplerState sampler{SkTileModeToWrapMode(mode), GrSamplerState::Filter::kNearest};
1746 auto child = make_texture_effect(sdc->caps(),
1747 std::move(srcView),
1748 kPremul_SkAlphaType,
1749 sampler,
1750 srcBounds,
1751 dstBounds,
1752 radii);
1753 auto conv = GrSkSLFP::Make(skgpu::GetBlur2DEffect(radii),
1754 "GaussianBlur2D",
1755 /*inputFP=*/nullptr,
1756 GrSkSLFP::OptFlags::kNone,
1757 "kernel", SkSpan<SkV4>{kernel},
1758 "offsets", SkSpan<SkV4>{offsets},
1759 "child", std::move(child));
1760
1761 GrPaint paint;
1762 paint.setColorFragmentProcessor(std::move(conv));
1763 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
1764
1765 // 'dstBounds' is actually in 'srcView' proxy space. It represents the blurred area from src
1766 // space that we want to capture in the new RTC at {0, 0}. Hence, we use its size as the rect to
1767 // draw and it directly as the local rect.
1768 sdc->fillRectToRect(nullptr,
1769 std::move(paint),
1770 GrAA::kNo,
1771 SkMatrix::I(),
1772 SkRect::Make(dstBounds.size()),
1773 SkRect::Make(dstBounds));
1774
1775 return sdc;
1776 }
1777
convolve_gaussian(GrRecordingContext * rContext,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,SkIRect srcBounds,SkIRect dstBounds,Direction direction,int radius,float sigma,SkTileMode mode,sk_sp<SkColorSpace> finalCS,SkBackingFit fit)1778 static std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> convolve_gaussian(
1779 GrRecordingContext* rContext,
1780 GrSurfaceProxyView srcView,
1781 GrColorType srcColorType,
1782 SkAlphaType srcAlphaType,
1783 SkIRect srcBounds,
1784 SkIRect dstBounds,
1785 Direction direction,
1786 int radius,
1787 float sigma,
1788 SkTileMode mode,
1789 sk_sp<SkColorSpace> finalCS,
1790 SkBackingFit fit) {
1791 SkASSERT(radius > 0 && !skgpu::BlurIsEffectivelyIdentity(sigma));
1792 // Logically we're creating an infinite blur of 'srcBounds' of 'srcView' with 'mode' tiling
1793 // and then capturing the 'dstBounds' portion in a new RTC where the top left of 'dstBounds' is
1794 // at {0, 0} in the new RTC.
1795 //
1796 // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
1797 // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
1798 auto dstSDC =
1799 skgpu::ganesh::SurfaceDrawContext::Make(rContext,
1800 srcColorType,
1801 std::move(finalCS),
1802 fit,
1803 dstBounds.size(),
1804 SkSurfaceProps(),
1805 /*label=*/"SurfaceDrawContext_ConvolveGaussian",
1806 /* sampleCnt= */ 1,
1807 skgpu::Mipmapped::kNo,
1808 srcView.proxy()->isProtected(),
1809 srcView.origin());
1810 if (!dstSDC) {
1811 return nullptr;
1812 }
1813 // This represents the translation from 'dstSurfaceDrawContext' coords to 'srcView' coords.
1814 auto rtcToSrcOffset = dstBounds.topLeft();
1815
1816 auto srcBackingBounds = SkIRect::MakeSize(srcView.proxy()->backingStoreDimensions());
1817 // We've implemented splitting the dst bounds up into areas that do and do not need to
1818 // use shader based tiling but only for some modes...
1819 bool canSplit = mode == SkTileMode::kDecal || mode == SkTileMode::kClamp;
1820 // ...but it's not worth doing the splitting if we'll get HW tiling instead of shader tiling.
1821 bool canHWTile =
1822 srcBounds.contains(srcBackingBounds) &&
1823 !rContext->priv().caps()->reducedShaderMode() && // this mode always uses shader tiling
1824 !(mode == SkTileMode::kDecal && !rContext->priv().caps()->clampToBorderSupport());
1825 if (!canSplit || canHWTile) {
1826 auto dstRect = SkIRect::MakeSize(dstBounds.size());
1827 convolve_gaussian_1d(dstSDC.get(),
1828 std::move(srcView),
1829 srcBounds,
1830 rtcToSrcOffset,
1831 dstRect,
1832 srcAlphaType,
1833 direction,
1834 radius,
1835 sigma,
1836 mode);
1837 return dstSDC;
1838 }
1839
1840 // 'left' and 'right' are the sub rects of 'srcBounds' where 'mode' must be enforced.
1841 // 'mid' is the area where we can ignore the mode because the kernel does not reach to the
1842 // edge of 'srcBounds'.
1843 SkIRect mid, left, right;
1844 // 'top' and 'bottom' are areas of 'dstBounds' that are entirely above/below 'srcBounds'.
1845 // These are areas that we can simply clear in the dst in kDecal mode. If 'srcBounds'
1846 // straddles the top edge of 'dstBounds' then 'top' will be inverted and we will skip
1847 // processing for the rect. Similar for 'bottom'. The positional/directional labels above refer
1848 // to the Direction::kX case and one should think of these as 'left' and 'right' for
1849 // Direction::kY.
1850 SkIRect top, bottom;
1851 if (Direction::kX == direction) {
1852 top = {dstBounds.left(), dstBounds.top(), dstBounds.right(), srcBounds.top()};
1853 bottom = {dstBounds.left(), srcBounds.bottom(), dstBounds.right(), dstBounds.bottom()};
1854
1855 // Inset for sub-rect of 'srcBounds' where the x-dir kernel doesn't reach the edges, clipped
1856 // vertically to dstBounds.
1857 int midA = std::max(srcBounds.top(), dstBounds.top());
1858 int midB = std::min(srcBounds.bottom(), dstBounds.bottom());
1859 mid = {srcBounds.left() + radius, midA, srcBounds.right() - radius, midB};
1860 if (mid.isEmpty()) {
1861 // There is no middle where the bounds can be ignored. Make the left span the whole
1862 // width of dst and we will not draw mid or right.
1863 left = {dstBounds.left(), mid.top(), dstBounds.right(), mid.bottom()};
1864 } else {
1865 left = {dstBounds.left(), mid.top(), mid.left(), mid.bottom()};
1866 right = {mid.right(), mid.top(), dstBounds.right(), mid.bottom()};
1867 }
1868 } else {
1869 // This is the same as the x direction code if you turn your head 90 degrees CCW. Swap x and
1870 // y and swap top/bottom with left/right.
1871 top = {dstBounds.left(), dstBounds.top(), srcBounds.left(), dstBounds.bottom()};
1872 bottom = {srcBounds.right(), dstBounds.top(), dstBounds.right(), dstBounds.bottom()};
1873
1874 int midA = std::max(srcBounds.left(), dstBounds.left());
1875 int midB = std::min(srcBounds.right(), dstBounds.right());
1876 mid = {midA, srcBounds.top() + radius, midB, srcBounds.bottom() - radius};
1877
1878 if (mid.isEmpty()) {
1879 left = {mid.left(), dstBounds.top(), mid.right(), dstBounds.bottom()};
1880 } else {
1881 left = {mid.left(), dstBounds.top(), mid.right(), mid.top()};
1882 right = {mid.left(), mid.bottom(), mid.right(), dstBounds.bottom()};
1883 }
1884 }
1885
1886 auto convolve = [&](SkIRect rect) {
1887 // Transform rect into the render target's coord system.
1888 rect.offset(-rtcToSrcOffset);
1889 convolve_gaussian_1d(dstSDC.get(),
1890 srcView,
1891 srcBounds,
1892 rtcToSrcOffset,
1893 rect,
1894 srcAlphaType,
1895 direction,
1896 radius,
1897 sigma,
1898 mode);
1899 };
1900 auto clear = [&](SkIRect rect) {
1901 // Transform rect into the render target's coord system.
1902 rect.offset(-rtcToSrcOffset);
1903 dstSDC->clearAtLeast(rect, SK_PMColor4fTRANSPARENT);
1904 };
1905
1906 // Doing mid separately will cause two draws to occur (left and right batch together). At
1907 // small sizes of mid it is worse to issue more draws than to just execute the slightly
1908 // more complicated shader that implements the tile mode across mid. This threshold is
1909 // very arbitrary right now. It is believed that a 21x44 mid on a Moto G4 is a significant
1910 // regression compared to doing one draw but it has not been locally evaluated or tuned.
1911 // The optimal cutoff is likely to vary by GPU.
1912 if (!mid.isEmpty() && mid.width() * mid.height() < 256 * 256) {
1913 left.join(mid);
1914 left.join(right);
1915 mid = SkIRect::MakeEmpty();
1916 right = SkIRect::MakeEmpty();
1917 // It's unknown whether for kDecal it'd be better to expand the draw rather than a draw and
1918 // up to two clears.
1919 if (mode == SkTileMode::kClamp) {
1920 left.join(top);
1921 left.join(bottom);
1922 top = SkIRect::MakeEmpty();
1923 bottom = SkIRect::MakeEmpty();
1924 }
1925 }
1926
1927 if (!top.isEmpty()) {
1928 if (mode == SkTileMode::kDecal) {
1929 clear(top);
1930 } else {
1931 convolve(top);
1932 }
1933 }
1934
1935 if (!bottom.isEmpty()) {
1936 if (mode == SkTileMode::kDecal) {
1937 clear(bottom);
1938 } else {
1939 convolve(bottom);
1940 }
1941 }
1942
1943 if (mid.isEmpty()) {
1944 convolve(left);
1945 } else {
1946 convolve(left);
1947 convolve(right);
1948 convolve(mid);
1949 }
1950 return dstSDC;
1951 }
1952
1953 // Expand the contents of 'src' to fit in 'dstSize'. At this point, we are expanding an intermediate
1954 // image, so there's no need to account for a proxy offset from the original input.
reexpand(GrRecordingContext * rContext,std::unique_ptr<skgpu::ganesh::SurfaceContext> src,const SkRect & srcBounds,SkISize dstSize,sk_sp<SkColorSpace> colorSpace,SkBackingFit fit)1955 static std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> reexpand(
1956 GrRecordingContext* rContext,
1957 std::unique_ptr<skgpu::ganesh::SurfaceContext> src,
1958 const SkRect& srcBounds,
1959 SkISize dstSize,
1960 sk_sp<SkColorSpace> colorSpace,
1961 SkBackingFit fit) {
1962 GrSurfaceProxyView srcView = src->readSurfaceView();
1963 if (!srcView.asTextureProxy()) {
1964 return nullptr;
1965 }
1966
1967 GrColorType srcColorType = src->colorInfo().colorType();
1968 SkAlphaType srcAlphaType = src->colorInfo().alphaType();
1969
1970 #if defined(SK_USE_PADDED_BLUR_UPSCALE)
1971 // The blur output completely filled the src SurfaceContext, so that is our subset boundary,
1972 // ensuring we don't access undefined pixels in the approx-fit backing texture.
1973 SkRect srcContent = SkRect::MakeIWH(src->width(), src->height());
1974 #endif
1975
1976 src.reset(); // no longer needed
1977
1978 // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
1979 // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
1980 auto dstSDC = skgpu::ganesh::SurfaceDrawContext::Make(rContext,
1981 srcColorType,
1982 std::move(colorSpace),
1983 fit,
1984 dstSize,
1985 SkSurfaceProps(),
1986 /*label=*/"SurfaceDrawContext_Reexpand",
1987 /* sampleCnt= */ 1,
1988 skgpu::Mipmapped::kNo,
1989 srcView.proxy()->isProtected(),
1990 srcView.origin());
1991 if (!dstSDC) {
1992 return nullptr;
1993 }
1994
1995 GrPaint paint;
1996 auto fp = GrTextureEffect::MakeSubset(std::move(srcView),
1997 srcAlphaType,
1998 SkMatrix::I(),
1999 GrSamplerState::Filter::kLinear,
2000 #if defined(SK_USE_PADDED_BLUR_UPSCALE)
2001 srcContent,
2002 #else
2003 srcBounds,
2004 srcBounds,
2005 #endif
2006 *rContext->priv().caps());
2007 paint.setColorFragmentProcessor(std::move(fp));
2008 paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
2009
2010 dstSDC->fillRectToRect(
2011 nullptr, std::move(paint), GrAA::kNo, SkMatrix::I(), SkRect::Make(dstSize), srcBounds);
2012
2013 return dstSDC;
2014 }
2015
two_pass_gaussian(GrRecordingContext * rContext,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,sk_sp<SkColorSpace> colorSpace,SkIRect srcBounds,SkIRect dstBounds,float sigmaX,float sigmaY,int radiusX,int radiusY,SkTileMode mode,SkBackingFit fit)2016 static std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> two_pass_gaussian(
2017 GrRecordingContext* rContext,
2018 GrSurfaceProxyView srcView,
2019 GrColorType srcColorType,
2020 SkAlphaType srcAlphaType,
2021 sk_sp<SkColorSpace> colorSpace,
2022 SkIRect srcBounds,
2023 SkIRect dstBounds,
2024 float sigmaX,
2025 float sigmaY,
2026 int radiusX,
2027 int radiusY,
2028 SkTileMode mode,
2029 SkBackingFit fit) {
2030 SkASSERT(radiusX || radiusY);
2031 std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> dstSDC;
2032 if (radiusX > 0) {
2033 SkBackingFit xFit = radiusY > 0 ? SkBackingFit::kApprox : fit;
2034 // Expand the dstBounds vertically to produce necessary content for the y-pass. Then we will
2035 // clip these in a tile-mode dependent way to ensure the tile-mode gets implemented
2036 // correctly. However, if we're not going to do a y-pass then we must use the original
2037 // dstBounds without clipping to produce the correct output size.
2038 SkIRect xPassDstBounds = dstBounds;
2039 if (radiusY) {
2040 xPassDstBounds.outset(0, radiusY);
2041 if (mode == SkTileMode::kRepeat || mode == SkTileMode::kMirror) {
2042 int srcH = srcBounds.height();
2043 int srcTop = srcBounds.top();
2044 if (mode == SkTileMode::kMirror) {
2045 srcTop -= srcH;
2046 srcH *= 2;
2047 }
2048
2049 float floatH = srcH;
2050 // First row above the dst rect where we should restart the tile mode.
2051 int n = sk_float_floor2int_no_saturate((xPassDstBounds.top() - srcTop) / floatH);
2052 int topClip = srcTop + n * srcH;
2053
2054 // First row above below the dst rect where we should restart the tile mode.
2055 n = sk_float_ceil2int_no_saturate((xPassDstBounds.bottom() - srcBounds.bottom()) /
2056 floatH);
2057 int bottomClip = srcBounds.bottom() + n * srcH;
2058
2059 xPassDstBounds.fTop = std::max(xPassDstBounds.top(), topClip);
2060 xPassDstBounds.fBottom = std::min(xPassDstBounds.bottom(), bottomClip);
2061 } else {
2062 if (xPassDstBounds.fBottom <= srcBounds.top()) {
2063 if (mode == SkTileMode::kDecal) {
2064 return nullptr;
2065 }
2066 xPassDstBounds.fTop = srcBounds.top();
2067 xPassDstBounds.fBottom = xPassDstBounds.fTop + 1;
2068 } else if (xPassDstBounds.fTop >= srcBounds.bottom()) {
2069 if (mode == SkTileMode::kDecal) {
2070 return nullptr;
2071 }
2072 xPassDstBounds.fBottom = srcBounds.bottom();
2073 xPassDstBounds.fTop = xPassDstBounds.fBottom - 1;
2074 } else {
2075 xPassDstBounds.fTop = std::max(xPassDstBounds.fTop, srcBounds.top());
2076 xPassDstBounds.fBottom = std::min(xPassDstBounds.fBottom, srcBounds.bottom());
2077 }
2078 int leftSrcEdge = srcBounds.fLeft - radiusX;
2079 int rightSrcEdge = srcBounds.fRight + radiusX;
2080 if (mode == SkTileMode::kClamp) {
2081 // In clamp the column just outside the src bounds has the same value as the
2082 // column just inside, unlike decal.
2083 leftSrcEdge += 1;
2084 rightSrcEdge -= 1;
2085 }
2086 if (xPassDstBounds.fRight <= leftSrcEdge) {
2087 if (mode == SkTileMode::kDecal) {
2088 return nullptr;
2089 }
2090 xPassDstBounds.fLeft = xPassDstBounds.fRight - 1;
2091 } else {
2092 xPassDstBounds.fLeft = std::max(xPassDstBounds.fLeft, leftSrcEdge);
2093 }
2094 if (xPassDstBounds.fLeft >= rightSrcEdge) {
2095 if (mode == SkTileMode::kDecal) {
2096 return nullptr;
2097 }
2098 xPassDstBounds.fRight = xPassDstBounds.fLeft + 1;
2099 } else {
2100 xPassDstBounds.fRight = std::min(xPassDstBounds.fRight, rightSrcEdge);
2101 }
2102 }
2103 }
2104 dstSDC = convolve_gaussian(rContext,
2105 std::move(srcView),
2106 srcColorType,
2107 srcAlphaType,
2108 srcBounds,
2109 xPassDstBounds,
2110 Direction::kX,
2111 radiusX,
2112 sigmaX,
2113 mode,
2114 colorSpace,
2115 xFit);
2116 if (!dstSDC) {
2117 return nullptr;
2118 }
2119 srcView = dstSDC->readSurfaceView();
2120 SkIVector newDstBoundsOffset = dstBounds.topLeft() - xPassDstBounds.topLeft();
2121 dstBounds = SkIRect::MakeSize(dstBounds.size()).makeOffset(newDstBoundsOffset);
2122 srcBounds = SkIRect::MakeSize(xPassDstBounds.size());
2123 }
2124
2125 if (!radiusY) {
2126 return dstSDC;
2127 }
2128
2129 return convolve_gaussian(rContext,
2130 std::move(srcView),
2131 srcColorType,
2132 srcAlphaType,
2133 srcBounds,
2134 dstBounds,
2135 Direction::kY,
2136 radiusY,
2137 sigmaY,
2138 mode,
2139 std::move(colorSpace),
2140 fit);
2141 }
2142
GaussianBlur(GrRecordingContext * rContext,GrSurfaceProxyView srcView,GrColorType srcColorType,SkAlphaType srcAlphaType,sk_sp<SkColorSpace> colorSpace,SkIRect dstBounds,SkIRect srcBounds,float sigmaX,float sigmaY,SkTileMode mode,SkBackingFit fit)2143 std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> GaussianBlur(GrRecordingContext* rContext,
2144 GrSurfaceProxyView srcView,
2145 GrColorType srcColorType,
2146 SkAlphaType srcAlphaType,
2147 sk_sp<SkColorSpace> colorSpace,
2148 SkIRect dstBounds,
2149 SkIRect srcBounds,
2150 float sigmaX,
2151 float sigmaY,
2152 SkTileMode mode,
2153 SkBackingFit fit) {
2154 SkASSERT(rContext);
2155 TRACE_EVENT2("skia.gpu", "GaussianBlur", "sigmaX", sigmaX, "sigmaY", sigmaY);
2156
2157 if (!srcView.asTextureProxy()) {
2158 return nullptr;
2159 }
2160
2161 int maxRenderTargetSize = rContext->priv().caps()->maxRenderTargetSize();
2162 if (dstBounds.width() > maxRenderTargetSize || dstBounds.height() > maxRenderTargetSize) {
2163 return nullptr;
2164 }
2165
2166 int radiusX = skgpu::BlurSigmaRadius(sigmaX);
2167 int radiusY = skgpu::BlurSigmaRadius(sigmaY);
2168 // Attempt to reduce the srcBounds in order to detect that we can set the sigmas to zero or
2169 // to reduce the amount of work to rescale the source if sigmas are large. TODO: Could consider
2170 // how to minimize the required source bounds for repeat/mirror modes.
2171 if (mode == SkTileMode::kClamp || mode == SkTileMode::kDecal) {
2172 SkIRect reach = dstBounds.makeOutset(radiusX, radiusY);
2173 SkIRect intersection;
2174 if (!intersection.intersect(reach, srcBounds)) {
2175 if (mode == SkTileMode::kDecal) {
2176 return nullptr;
2177 } else {
2178 if (reach.fLeft >= srcBounds.fRight) {
2179 srcBounds.fLeft = srcBounds.fRight - 1;
2180 } else if (reach.fRight <= srcBounds.fLeft) {
2181 srcBounds.fRight = srcBounds.fLeft + 1;
2182 }
2183 if (reach.fTop >= srcBounds.fBottom) {
2184 srcBounds.fTop = srcBounds.fBottom - 1;
2185 } else if (reach.fBottom <= srcBounds.fTop) {
2186 srcBounds.fBottom = srcBounds.fTop + 1;
2187 }
2188 }
2189 } else {
2190 srcBounds = intersection;
2191 }
2192 }
2193
2194 if (mode != SkTileMode::kDecal) {
2195 // All non-decal tile modes are equivalent for one pixel width/height src and amount to a
2196 // single color value repeated at each column/row. Applying the normalized kernel to that
2197 // column/row yields that same color. So no blurring is necessary.
2198 if (srcBounds.width() == 1) {
2199 sigmaX = 0.f;
2200 radiusX = 0;
2201 }
2202 if (srcBounds.height() == 1) {
2203 sigmaY = 0.f;
2204 radiusY = 0;
2205 }
2206 }
2207
2208 // If we determined that there is no blurring necessary in either direction then just do a
2209 // a draw that applies the tile mode.
2210 if (!radiusX && !radiusY) {
2211 // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
2212 // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
2213 auto result =
2214 skgpu::ganesh::SurfaceDrawContext::Make(rContext,
2215 srcColorType,
2216 std::move(colorSpace),
2217 fit,
2218 dstBounds.size(),
2219 SkSurfaceProps(),
2220 /*label=*/"SurfaceDrawContext_GaussianBlur",
2221 /* sampleCnt= */ 1,
2222 skgpu::Mipmapped::kNo,
2223 srcView.proxy()->isProtected(),
2224 srcView.origin());
2225 if (!result) {
2226 return nullptr;
2227 }
2228 GrSamplerState sampler(SkTileModeToWrapMode(mode), GrSamplerState::Filter::kNearest);
2229 auto fp = GrTextureEffect::MakeSubset(std::move(srcView),
2230 srcAlphaType,
2231 SkMatrix::I(),
2232 sampler,
2233 SkRect::Make(srcBounds),
2234 SkRect::Make(dstBounds),
2235 *rContext->priv().caps());
2236 result->fillRectToRectWithFP(dstBounds, SkIRect::MakeSize(dstBounds.size()), std::move(fp));
2237 return result;
2238 }
2239
2240 // Any sigma higher than the limit for the 1D linear-filtered Gaussian blur is downsampled. If
2241 // the sigma in X and Y just so happen to fit in the 2D limit, we'll use that. The 2D limit is
2242 // always less than the linear blur sigma limit.
2243 static constexpr float kMaxSigma = skgpu::kMaxLinearBlurSigma;
2244 if (sigmaX <= kMaxSigma && sigmaY <= kMaxSigma) {
2245 // For really small blurs (certainly no wider than 5x5 on desktop GPUs) it is faster to just
2246 // launch a single non separable kernel vs two launches.
2247 const int kernelSize = skgpu::BlurKernelWidth(radiusX) * skgpu::BlurKernelWidth(radiusY);
2248 if (radiusX > 0 && radiusY > 0 &&
2249 kernelSize <= skgpu::kMaxBlurSamples &&
2250 !rContext->priv().caps()->reducedShaderMode()) {
2251 // Apply the proxy offset to src bounds and offset directly
2252 return convolve_gaussian_2d(rContext,
2253 std::move(srcView),
2254 srcColorType,
2255 srcBounds,
2256 dstBounds,
2257 radiusX,
2258 radiusY,
2259 sigmaX,
2260 sigmaY,
2261 mode,
2262 std::move(colorSpace),
2263 fit);
2264 }
2265 // This will automatically degenerate into a single pass of X or Y if only one of the
2266 // radii are non-zero.
2267 SkASSERT(skgpu::BlurLinearKernelWidth(radiusX) <= skgpu::kMaxBlurSamples &&
2268 skgpu::BlurLinearKernelWidth(radiusY) <= skgpu::kMaxBlurSamples);
2269 return two_pass_gaussian(rContext,
2270 std::move(srcView),
2271 srcColorType,
2272 srcAlphaType,
2273 std::move(colorSpace),
2274 srcBounds,
2275 dstBounds,
2276 sigmaX,
2277 sigmaY,
2278 radiusX,
2279 radiusY,
2280 mode,
2281 fit);
2282 }
2283
2284 GrColorInfo colorInfo(srcColorType, srcAlphaType, colorSpace);
2285 auto srcCtx = rContext->priv().makeSC(srcView, colorInfo);
2286 SkASSERT(srcCtx);
2287
2288 #if defined(SK_USE_PADDED_BLUR_UPSCALE)
2289 // When we are in clamp mode any artifacts in the edge pixels due to downscaling may be
2290 // exacerbated because of the tile mode. The particularly egregious case is when the original
2291 // image has transparent black around the edges and the downscaling pulls in some non-zero
2292 // values from the interior. Ultimately it'd be better for performance if the calling code could
2293 // give us extra context around the blur to account for this. We don't currently have a good way
2294 // to communicate this up stack. So we leave a 1 pixel border around the rescaled src bounds.
2295 // We populate the top 1 pixel tall row of this border by rescaling the top row of the original
2296 // source bounds into it. Because this is only rescaling in x (i.e. rescaling a 1 pixel high
2297 // row into a shorter but still 1 pixel high row) we won't read any interior values. And similar
2298 // for the other three borders. We'll adjust the source/dest bounds rescaled blur so that this
2299 // border of extra pixels is used as the edge pixels for clamp mode but the dest bounds
2300 // corresponds only to the pixels inside the border (the normally rescaled pixels inside this
2301 // border).
2302 // Moreover, if we clamped the rescaled size to 1 column or row then we still have a sigma
2303 // that is greater than kMaxSigma. By using a pad and making the src 3 wide/tall instead of
2304 // 1 we can recurse again and do another downscale. Since mirror and repeat modes are trivial
2305 // for a single col/row we only add padding based on sigma exceeding kMaxSigma for decal.
2306 int padX = mode == SkTileMode::kClamp || (mode == SkTileMode::kDecal && sigmaX > kMaxSigma) ? 1
2307 : 0;
2308 int padY = mode == SkTileMode::kClamp || (mode == SkTileMode::kDecal && sigmaY > kMaxSigma) ? 1
2309 : 0;
2310 #endif
2311
2312 float scaleX = sigmaX > kMaxSigma ? kMaxSigma / sigmaX : 1.f;
2313 float scaleY = sigmaY > kMaxSigma ? kMaxSigma / sigmaY : 1.f;
2314 // We round down here so that when we recalculate sigmas we know they will be below
2315 // kMaxSigma (but clamp to 1 do we don't have an empty texture).
2316 SkISize rescaledSize = {std::max(sk_float_floor2int(srcBounds.width() * scaleX), 1),
2317 std::max(sk_float_floor2int(srcBounds.height() * scaleY), 1)};
2318 // Compute the sigmas using the actual scale factors used once we integerized the
2319 // rescaledSize.
2320 scaleX = static_cast<float>(rescaledSize.width()) / srcBounds.width();
2321 scaleY = static_cast<float>(rescaledSize.height()) / srcBounds.height();
2322 sigmaX *= scaleX;
2323 sigmaY *= scaleY;
2324
2325 #if !defined(SK_USE_PADDED_BLUR_UPSCALE)
2326 // Historically, padX and padY were calculated after scaling sigmaX,Y, which meant that they
2327 // would never be greater than kMaxSigma. This causes pixel diffs so must be guarded along with
2328 // the rest of the padding dst behavior.
2329 int padX = mode == SkTileMode::kClamp || (mode == SkTileMode::kDecal && sigmaX > kMaxSigma) ? 1
2330 : 0;
2331 int padY = mode == SkTileMode::kClamp || (mode == SkTileMode::kDecal && sigmaY > kMaxSigma) ? 1
2332 : 0;
2333 #endif
2334
2335 // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
2336 // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
2337 auto rescaledSDC = skgpu::ganesh::SurfaceDrawContext::Make(
2338 srcCtx->recordingContext(),
2339 colorInfo.colorType(),
2340 colorInfo.refColorSpace(),
2341 SkBackingFit::kApprox,
2342 {rescaledSize.width() + 2 * padX, rescaledSize.height() + 2 * padY},
2343 SkSurfaceProps(),
2344 /*label=*/"RescaledSurfaceDrawContext",
2345 /* sampleCnt= */ 1,
2346 skgpu::Mipmapped::kNo,
2347 srcCtx->asSurfaceProxy()->isProtected(),
2348 srcCtx->origin());
2349 if (!rescaledSDC) {
2350 return nullptr;
2351 }
2352 if ((padX || padY) && mode == SkTileMode::kDecal) {
2353 rescaledSDC->clear(SkPMColor4f{0, 0, 0, 0});
2354 }
2355 if (!srcCtx->rescaleInto(rescaledSDC.get(),
2356 SkIRect::MakeSize(rescaledSize).makeOffset(padX, padY),
2357 srcBounds,
2358 SkSurface::RescaleGamma::kSrc,
2359 SkSurface::RescaleMode::kRepeatedLinear)) {
2360 return nullptr;
2361 }
2362 if (mode == SkTileMode::kClamp) {
2363 SkASSERT(padX == 1 && padY == 1);
2364 // Rather than run a potentially multi-pass rescaler on single rows/columns we just do a
2365 // single bilerp draw. If we find this quality unacceptable we should think more about how
2366 // to rescale these with better quality but without 4 separate multi-pass downscales.
2367 auto cheapDownscale = [&](SkIRect dstRect, SkIRect srcRect) {
2368 rescaledSDC->drawTexture(nullptr,
2369 srcCtx->readSurfaceView(),
2370 srcAlphaType,
2371 GrSamplerState::Filter::kLinear,
2372 GrSamplerState::MipmapMode::kNone,
2373 SkBlendMode::kSrc,
2374 SK_PMColor4fWHITE,
2375 SkRect::Make(srcRect),
2376 SkRect::Make(dstRect),
2377 GrQuadAAFlags::kNone,
2378 SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint,
2379 SkMatrix::I(),
2380 nullptr);
2381 };
2382 auto [dw, dh] = rescaledSize;
2383 // The are the src rows and columns from the source that we will scale into the dst padding.
2384 float sLCol = srcBounds.left();
2385 float sTRow = srcBounds.top();
2386 float sRCol = srcBounds.right() - 1;
2387 float sBRow = srcBounds.bottom() - 1;
2388
2389 int sx = srcBounds.left();
2390 int sy = srcBounds.top();
2391 int sw = srcBounds.width();
2392 int sh = srcBounds.height();
2393
2394 // Downscale the edges from the original source. These draws should batch together (and with
2395 // the above interior rescaling when it is a single pass).
2396 cheapDownscale(SkIRect::MakeXYWH(0, 1, 1, dh), SkIRect::MakeXYWH(sLCol, sy, 1, sh));
2397 cheapDownscale(SkIRect::MakeXYWH(1, 0, dw, 1), SkIRect::MakeXYWH(sx, sTRow, sw, 1));
2398 cheapDownscale(SkIRect::MakeXYWH(dw + 1, 1, 1, dh), SkIRect::MakeXYWH(sRCol, sy, 1, sh));
2399 cheapDownscale(SkIRect::MakeXYWH(1, dh + 1, dw, 1), SkIRect::MakeXYWH(sx, sBRow, sw, 1));
2400
2401 // Copy the corners from the original source. These would batch with the edges except that
2402 // at time of writing we recognize these can use kNearest and downgrade the filter. So they
2403 // batch with each other but not the edge draws.
2404 cheapDownscale(SkIRect::MakeXYWH(0, 0, 1, 1), SkIRect::MakeXYWH(sLCol, sTRow, 1, 1));
2405 cheapDownscale(SkIRect::MakeXYWH(dw + 1, 0, 1, 1), SkIRect::MakeXYWH(sRCol, sTRow, 1, 1));
2406 cheapDownscale(SkIRect::MakeXYWH(dw + 1, dh + 1, 1, 1),
2407 SkIRect::MakeXYWH(sRCol, sBRow, 1, 1));
2408 cheapDownscale(SkIRect::MakeXYWH(0, dh + 1, 1, 1), SkIRect::MakeXYWH(sLCol, sBRow, 1, 1));
2409 }
2410 srcView = rescaledSDC->readSurfaceView();
2411 // Drop the contexts so we don't hold the proxies longer than necessary.
2412 rescaledSDC.reset();
2413 srcCtx.reset();
2414
2415 // Compute the dst bounds in the scaled down space. First move the origin to be at the top
2416 // left since we trimmed off everything above and to the left of the original src bounds during
2417 // the rescale.
2418 SkRect scaledDstBounds = SkRect::Make(dstBounds.makeOffset(-srcBounds.topLeft()));
2419 scaledDstBounds.fLeft *= scaleX;
2420 scaledDstBounds.fTop *= scaleY;
2421 scaledDstBounds.fRight *= scaleX;
2422 scaledDstBounds.fBottom *= scaleY;
2423 // Account for padding in our rescaled src, if any.
2424 scaledDstBounds.offset(padX, padY);
2425 // Turn the scaled down dst bounds into an integer pixel rect, adding 1px of padding to help
2426 // with boundary sampling during re-expansion when there are extreme scale factors. This is
2427 // particularly important when the blurs extend across Chrome raster tiles; w/o it the re-expand
2428 // produces visible seams: crbug.com/1500021.
2429 #if defined(SK_USE_PADDED_BLUR_UPSCALE)
2430 static constexpr int kDstPadding = 1;
2431 #else
2432 static constexpr int kDstPadding = 0;
2433 #endif
2434 auto scaledDstBoundsI = scaledDstBounds.roundOut();
2435 scaledDstBoundsI.outset(kDstPadding, kDstPadding);
2436
2437 SkIRect scaledSrcBounds = SkIRect::MakeSize(srcView.dimensions());
2438 auto sdc = GaussianBlur(rContext,
2439 std::move(srcView),
2440 srcColorType,
2441 srcAlphaType,
2442 colorSpace,
2443 scaledDstBoundsI,
2444 scaledSrcBounds,
2445 sigmaX,
2446 sigmaY,
2447 mode,
2448 fit);
2449 if (!sdc) {
2450 return nullptr;
2451 }
2452
2453 SkASSERT(sdc->width() == scaledDstBoundsI.width() &&
2454 sdc->height() == scaledDstBoundsI.height());
2455 // We rounded out the integer scaled dst bounds. Select the fractional dst bounds from the
2456 // integer dimension blurred result when we scale back up. This also accounts for the padding
2457 // added to 'scaledDstBoundsI' when sampling from the blurred result.
2458 scaledDstBounds.offset(-scaledDstBoundsI.left(), -scaledDstBoundsI.top());
2459 return reexpand(rContext,
2460 std::move(sdc),
2461 scaledDstBounds,
2462 dstBounds.size(),
2463 std::move(colorSpace),
2464 fit);
2465 }
2466
2467 } // namespace GrBlurUtils
2468