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