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