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