Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
GrBlurUtils.cpp
Go to the documentation of this file.
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
9
16#include "include/core/SkData.h"
18#include "include/core/SkM44.h"
21#include "include/core/SkPath.h"
24#include "include/core/SkRect.h"
29#include "include/core/SkSize.h"
30#include "include/core/SkSpan.h"
40#include "include/gpu/GrTypes.h"
49#include "src/base/SkTLazy.h"
51#include "src/core/SkDraw.h"
52#include "src/core/SkMask.h"
57#include "src/gpu/BlurUtils.h"
58#include "src/gpu/ResourceKey.h"
60#include "src/gpu/Swizzle.h"
79#include "src/gpu/ganesh/SkGr.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
97namespace GrBlurUtils {
98
99static bool clip_bounds_quick_reject(const SkIRect& clipBounds, const SkIRect& rect) {
100 return clipBounds.isEmpty() || rect.isEmpty() || !SkIRect::Intersects(clipBounds, rect);
101}
102
103static 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.
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
131static void mask_release_proc(void* addr, void* /*context*/) {
133}
134
135// This stores the mapping from an unclipped, integerized, device-space, shape bounds to
136// the filtered mask's draw rect.
141
142static 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
151static 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
161 const SkMatrix& viewMatrix,
162 const GrStyledShape& shape,
163 const SkMaskFilter* filter,
164 const SkIRect& unclippedDevShapeBounds,
165 const SkIRect& clipBounds,
166 SkIRect* drawRect,
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()
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,
201 fillOrHairline)) {
202 return {};
203 }
204 SkAutoMaskFreeImage autoSrc(srcM.image());
205
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;
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
249static 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());
271 nullptr,
273 approxSize,
274 defaultSurfaceProps,
275 sampleCnt,
276 skgpu::Mipmapped::kNo,
277 GrProtected::kNo,
279 if (!sdc) {
280 return nullptr;
281 }
282
283 sdc->clear(SK_PMColor4fTRANSPARENT);
284
285 GrPaint maskPaint;
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
299static bool get_unclipped_shape_dev_bounds(const GrStyledShape& shape, const SkMatrix& matrix,
300 SkIRect* devBounds) {
301 SkRect shapeDevBounds;
302 if (shape.inverseFilled()) {
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.
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 */
372static 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
418static 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
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
495static std::unique_ptr<GrFragmentProcessor> make_circle_blur(GrRecordingContext* context,
496 const SkRect& circle,
497 float 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
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,
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
537static std::unique_ptr<GrFragmentProcessor> make_rect_integral_fp(GrRecordingContext* rContext,
538 float sixSigma) {
540 auto threadSafeCache = rContext->priv().threadSafeCache();
541
543
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) {
557 std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
558 }
559
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
574 std::move(view), kPremul_SkAlphaType, m, GrSamplerState::Filter::kLinear);
575}
576
577static 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;
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
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,
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
714
716 const SkRRect& rrectToDraw,
717 float xformedSigma) {
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,
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
738 const GrSurfaceProxyView& lazyView,
740 const SkRRect& rrectToDraw,
741 const SkISize& dimensions,
742 float 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 =
752 nullptr,
754 dimensions,
755 defaultSurfaceProps,
756 1,
757 skgpu::Mipmapped::kNo,
758 GrProtected::kNo,
760 if (!sdc) {
761 return false;
762 }
763
765
766 sdc->clear(SK_PMColor4fTRANSPARENT);
767 sdc->drawRRect(nullptr,
768 std::move(paint),
770 SkMatrix::I(),
771 rrectToDraw,
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,
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.
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
819static 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) {
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
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,
845 dimensions,
848 if (!lazyView) {
849 return nullptr;
850 }
851
852 view = threadSafeCache->findOrAdd(key, lazyView);
853 if (view != lazyView) {
854 SkASSERT(view.asTextureProxy());
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());
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());
888 return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
889}
890
891static std::unique_ptr<GrFragmentProcessor> make_rrect_blur(GrRecordingContext* context,
892 float sigma,
893 float xformedSigma,
894 const SkRRect& srcRRect,
895 const SkRRect& 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;
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
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,
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 */
1010 const SkMaskFilterBase* maskFilter,
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, nullptr, nullptr, &inverted) || inverted) {
1040 return false;
1041 }
1042
1043 std::unique_ptr<GrFragmentProcessor> fp;
1044
1045 SkRRect devRRect;
1046 bool devRRectIsValid = srcRRect.transform(viewMatrix, &devRRect);
1047
1048 bool devRRectIsCircle = devRRectIsValid && SkRRectPriv::IsCircle(devRRect);
1049
1050 bool canBeRect = srcRRect.isRect() && viewMatrix.preservesRightAngles();
1051 bool canBeCircle = (SkRRectPriv::IsCircle(srcRRect) && viewMatrix.isSimilarity()) ||
1052 devRRectIsCircle;
1053
1054 if (canBeRect || canBeCircle) {
1055 if (canBeRect) {
1056 fp = make_rect_blur(context, *context->priv().caps()->shaderCaps(),
1057 srcRRect.rect(), viewMatrix, xformedSigma);
1058 } else {
1059 SkRect devBounds;
1060 if (devRRectIsCircle) {
1061 devBounds = devRRect.getBounds();
1062 } else {
1063 SkPoint center = {srcRRect.getBounds().centerX(), srcRRect.getBounds().centerY()};
1064 viewMatrix.mapPoints(&center, 1);
1065 SkScalar radius = viewMatrix.mapVector(0, srcRRect.width()/2.f).length();
1066 devBounds = {center.x() - radius,
1067 center.y() - radius,
1068 center.x() + radius,
1069 center.y() + radius};
1070 }
1071 fp = make_circle_blur(context, devBounds, xformedSigma);
1072 }
1073
1074 if (!fp) {
1075 return false;
1076 }
1077
1078 SkRect srcProxyRect = srcRRect.rect();
1079 // Determine how much to outset the src rect to ensure we hit pixels within three sigma.
1080 SkScalar outsetX = 3.0f*xformedSigma;
1081 SkScalar outsetY = 3.0f*xformedSigma;
1082 if (viewMatrix.isScaleTranslate()) {
1083 outsetX /= SkScalarAbs(viewMatrix.getScaleX());
1084 outsetY /= SkScalarAbs(viewMatrix.getScaleY());
1085 } else {
1086 SkSize scale;
1087 if (!viewMatrix.decomposeScale(&scale, nullptr)) {
1088 return false;
1089 }
1090 outsetX /= scale.width();
1091 outsetY /= scale.height();
1092 }
1093 srcProxyRect.outset(outsetX, outsetY);
1094
1095 paint.setCoverageFragmentProcessor(std::move(fp));
1096 sdc->drawRect(clip, std::move(paint), GrAA::kNo, viewMatrix, srcProxyRect);
1097 return true;
1098 }
1099 if (!viewMatrix.isScaleTranslate()) {
1100 return false;
1101 }
1102 if (!devRRectIsValid || !SkRRectPriv::AllCornersCircular(devRRect)) {
1103 return false;
1104 }
1105
1106 fp = make_rrect_blur(context, bmf->sigma(), xformedSigma, srcRRect, devRRect);
1107 if (!fp) {
1108 return false;
1109 }
1110
1111 if (!bmf->ignoreXform()) {
1112 SkRect srcProxyRect = srcRRect.rect();
1113 srcProxyRect.outset(3.0f*bmf->sigma(), 3.0f*bmf->sigma());
1114 paint.setCoverageFragmentProcessor(std::move(fp));
1115 sdc->drawRect(clip, std::move(paint), GrAA::kNo, viewMatrix, srcProxyRect);
1116 } else {
1117 SkMatrix inverse;
1118 if (!viewMatrix.invert(&inverse)) {
1119 return false;
1120 }
1121
1122 SkIRect proxyBounds;
1123 float extra=3.f*SkScalarCeilToScalar(xformedSigma-1/6.0f);
1124 devRRect.rect().makeOutset(extra, extra).roundOut(&proxyBounds);
1125
1126 paint.setCoverageFragmentProcessor(std::move(fp));
1127 sdc->fillPixelsWithLocalMatrix(clip, std::move(paint), proxyBounds, inverse);
1128 }
1129
1130 return true;
1131}
1132
1133// The key and clip-bounds are computed together because the caching decision can impact the
1134// clip-bound - since we only cache un-clipped masks the clip can be removed entirely.
1135// A 'false' return value indicates that the shape is known to be clipped away.
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) {
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
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 */
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,
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
1290 const SkMatrix& viewMatrix,
1291 const GrStyledShape& shape,
1292 const SkMaskFilterBase* filter,
1293 const SkIRect& unclippedDevShapeBounds,
1294 const SkIRect& clipBounds,
1295 SkIRect* maskRect,
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;
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(),
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
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
1466 const SkRRect& devRRect,
1467 SkScalar sigma,
1468 SkScalar xformedSigma,
1469 SkRRect* rrectToDraw,
1470 SkISize* widthHeight,
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
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
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
1584namespace {
1585
1586enum class Direction { kX, kY };
1587
1588std::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,
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 */
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,
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
1667static 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);
1683 // Create the sdc with default SkSurfaceProps. Gaussian blurs will soon use a
1684 // SurfaceFillContext, at which point the SkSurfaceProps won't exist anymore.
1686 rContext,
1687 srcColorType,
1688 std::move(finalCS),
1689 dstFit,
1690 dstBounds.size(),
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),
1713 sampler,
1714 srcBounds,
1715 dstBounds,
1716 radii);
1717 auto conv = GrSkSLFP::Make(skgpu::GetBlur2DEffect(radii),
1718 "GaussianBlur2D",
1719 /*inputFP=*/nullptr,
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
1742static 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 =
1764 srcColorType,
1765 std::move(finalCS),
1766 fit,
1767 dstBounds.size(),
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();
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.
1919static 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,
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
1980static 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
2107std::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 =
2179 srcColorType,
2180 std::move(colorSpace),
2181 fit,
2182 dstBounds.size(),
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.
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(),
2306 {rescaledSize.width() + 2 * padX, rescaledSize.height() + 2 * padY},
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,
2339 SkRect::Make(srcRect),
2340 SkRect::Make(dstRect),
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
GrColorType
@ kTopLeft_GrSurfaceOrigin
Definition GrTypes.h:148
bool GrIsStrokeHairlineOrEquivalent(const GrStyle &style, const SkMatrix &matrix, SkScalar *outCoverage)
Definition GrUtil.cpp:65
SkAlphaType
Definition SkAlphaType.h:26
@ kUnknown_SkAlphaType
uninitialized
Definition SkAlphaType.h:27
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkASSERTF(cond, fmt,...)
Definition SkAssert.h:117
SkBackingFit
@ kOuter_SkBlurStyle
nothing inside, fuzzy outside
Definition SkBlurTypes.h:14
@ kSolid_SkBlurStyle
solid inside, fuzzy outside
Definition SkBlurTypes.h:13
@ kInner_SkBlurStyle
fuzzy inside, nothing outside
Definition SkBlurTypes.h:15
@ kNormal_SkBlurStyle
fuzzy inside and outside
Definition SkBlurTypes.h:12
constexpr SkPMColor4f SK_PMColor4fWHITE
constexpr SkPMColor4f SK_PMColor4fTRANSPARENT
int32_t SkFixed
Definition SkFixed.h:25
#define SkScalarToFixed(x)
Definition SkFixed.h:125
#define SkFixedToScalar(x)
Definition SkFixed.h:124
static uint32_t SkFloat2Bits(float value)
Definition SkFloatBits.h:41
#define sk_float_floor2int_no_saturate(x)
#define sk_float_ceil2int_no_saturate(x)
static bool SkIsFinite(T x, Pack... values)
#define sk_float_floor2int(x)
std::tuple< GrSurfaceProxyView, GrColorType > GrMakeUncachedBitmapProxyView(GrRecordingContext *rContext, const SkBitmap &bitmap, skgpu::Mipmapped mipmapped, SkBackingFit fit, skgpu::Budgeted budgeted)
Definition SkGr.cpp:253
bool SkPaintToGrPaint(GrRecordingContext *context, const GrColorInfo &dstColorInfo, const SkPaint &skPaint, const SkMatrix &ctm, const SkSurfaceProps &surfaceProps, GrPaint *grPaint)
Definition SkGr.cpp:553
static constexpr GrSamplerState::WrapMode SkTileModeToWrapMode(SkTileMode tileMode)
Definition SkGr.h:77
SkMaskFilterBase * as_MFB(SkMaskFilter *mf)
std::unique_ptr< uint8_t, SkFunctionObject< SkMaskBuilder::FreeImage > > SkAutoMaskFreeImage
Definition SkMask.h:316
static int64_t sk_64_mul(int64_t a, int64_t b)
Definition SkMath.h:33
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition SkPath.cpp:3824
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
SkRuntimeEffect * SkMakeRuntimeEffect(SkRuntimeEffect::Result(*make)(SkString, const SkRuntimeEffect::Options &), const char *sksl, SkRuntimeEffect::Options options=SkRuntimeEffect::Options{})
#define SkScalarRoundToInt(x)
Definition SkScalar.h:37
#define SkScalarCeilToInt(x)
Definition SkScalar.h:36
#define SK_ScalarNearlyZero
Definition SkScalar.h:99
#define SkIntToScalar(x)
Definition SkScalar.h:57
static bool SkScalarIsInt(SkScalar x)
Definition SkScalar.h:80
#define SK_ScalarInfinity
Definition SkScalar.h:26
static SkScalar SkScalarFraction(SkScalar x)
Definition SkScalar.h:67
#define SkScalarCeilToScalar(x)
Definition SkScalar.h:31
#define SkScalarAbs(x)
Definition SkScalar.h:39
#define SK_ScalarNegativeInfinity
Definition SkScalar.h:27
SkTileMode
Definition SkTileMode.h:13
static SkScalar center(float pos0, float pos1)
const GrCaps * caps() const
bool reducedShaderMode() const
Definition GrCaps.h:190
const GrShaderCaps * shaderCaps() const
Definition GrCaps.h:63
int maxTextureSize() const
Definition GrCaps.h:229
int maxRenderTargetSize() const
Definition GrCaps.h:223
bool clampToBorderSupport() const
Definition GrCaps.h:484
sk_sp< SkColorSpace > refColorSpace() const
GrColorType colorType() const
Definition GrColorInfo.h:43
virtual GrDirectContext * asDirectContext()
GrDirectContextPriv priv()
static std::unique_ptr< GrFragmentProcessor > DeviceSpace(std::unique_ptr< GrFragmentProcessor >)
static std::unique_ptr< GrFragmentProcessor > Make(const SkMatrix &matrix, std::unique_ptr< GrFragmentProcessor > child)
void setCoverageSetOpXPFactory(SkRegion::Op, bool invertCoverage=false)
Definition GrPaint.cpp:32
GrThreadSafeCache * threadSafeCache()
std::unique_ptr< skgpu::ganesh::SurfaceContext > makeSC(GrSurfaceProxyView readView, const GrColorInfo &)
GrRecordingContextPriv priv()
bool abandoned() override
static GrIgnoreOptFlags IgnoreOptFlags(std::unique_ptr< GrFragmentProcessor > child)
Definition GrSkSLFP.h:96
static std::unique_ptr< GrSkSLFP > Make(const SkRuntimeEffect *effect, const char *name, std::unique_ptr< GrFragmentProcessor > inputFP, OptFlags optFlags, Args &&... args)
Definition GrSkSLFP.h:158
bool isSimpleHairline() const
Definition GrStyle.h:117
static const GrStyle & SimpleFill()
Definition GrStyle.h:30
bool applies() const
Definition GrStyle.h:143
static SkScalar MatrixToScaleFactor(const SkMatrix &matrix)
Definition GrStyle.h:147
bool isSimpleFill() const
Definition GrStyle.h:114
const SkStrokeRec & strokeRec() const
Definition GrStyle.h:140
void asPath(SkPath *out) const
void writeUnstyledKey(uint32_t *key) const
int unstyledKeySize() const
bool hasUnstyledKey() const
bool inverseFilled() const
const GrStyle & style() const
SkRect styledBounds() const
GrStyledShape applyStyle(GrStyle::Apply apply, SkScalar scale) const
bool asRRect(SkRRect *rrect, SkPathDirection *dir, unsigned *start, bool *inverted) const
skgpu::Swizzle swizzle() const
void concatSwizzle(skgpu::Swizzle swizzle)
GrTextureProxy * asTextureProxy() const
SkISize dimensions() const
GrSurfaceOrigin origin() const
GrSurfaceProxy * proxy() const
SkISize backingStoreDimensions() const
GrProtected isProtected() const
SkISize dimensions() const
static std::unique_ptr< GrFragmentProcessor > MakeSubset(GrSurfaceProxyView, SkAlphaType, const SkMatrix &, GrSamplerState, const SkRect &subset, const GrCaps &caps, const float border[4]=kDefaultBorder, bool alwaysUseShaderTileMode=false)
static std::unique_ptr< GrFragmentProcessor > Make(GrSurfaceProxyView, SkAlphaType, const SkMatrix &=SkMatrix::I(), GrSamplerState::Filter=GrSamplerState::Filter::kNearest, GrSamplerState::MipmapMode mipmapMode=GrSamplerState::MipmapMode::kNone)
static constexpr float kDefaultBorder[4]
sk_sp< GrTextureProxy > fProxy
static std::tuple< GrSurfaceProxyView, sk_sp< Trampoline > > CreateLazyView(GrDirectContext *, GrColorType, SkISize dimensions, GrSurfaceOrigin, SkBackingFit)
void setImmutable()
Definition SkBitmap.cpp:400
bool installPixels(const SkImageInfo &info, void *pixels, size_t rowBytes, void(*releaseProc)(void *addr, void *context), void *context)
Definition SkBitmap.cpp:323
SkScalar computeXformedSigma(const SkMatrix &ctm) const
@ kFast_SrcRectConstraint
sample outside bounds; faster
Definition SkCanvas.h:1543
static sk_sp< SkData > MakeWithCopy(const void *data, size_t length)
Definition SkData.cpp:111
static bool DrawToMask(const SkPath &devPath, const SkIRect &clipBounds, const SkMaskFilter *, const SkMatrix *filterMatrix, SkMaskBuilder *dst, SkMaskBuilder::CreateMode mode, SkStrokeRec::InitStyle style)
virtual bool asABlur(BlurRec *) const
virtual Type type() const =0
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition SkMatrix.h:75
SkMatrix & postTranslate(SkScalar dx, SkScalar dy)
Definition SkMatrix.cpp:281
static constexpr int kMScaleX
horizontal scale factor
Definition SkMatrix.h:353
static constexpr int kMTransY
vertical translation
Definition SkMatrix.h:358
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition SkMatrix.h:91
bool preservesRightAngles(SkScalar tol=SK_ScalarNearlyZero) const
Definition SkMatrix.cpp:209
void mapPoints(SkPoint dst[], const SkPoint src[], int count) const
Definition SkMatrix.cpp:770
bool invert(SkMatrix *inverse) const
Definition SkMatrix.h:1206
bool rectStaysRect() const
Definition SkMatrix.h:271
SkScalar getScaleX() const
Definition SkMatrix.h:415
static const SkMatrix & I()
bool decomposeScale(SkSize *scale, SkMatrix *remaining=nullptr) const
SkScalar getScaleY() const
Definition SkMatrix.h:422
bool isScaleTranslate() const
Definition SkMatrix.h:236
static constexpr int kMTransX
horizontal translation
Definition SkMatrix.h:355
static constexpr int kMSkewY
vertical skew factor
Definition SkMatrix.h:356
SkScalar get(int index) const
Definition SkMatrix.h:392
static constexpr int kMScaleY
vertical scale factor
Definition SkMatrix.h:357
bool preservesAxisAlignment() const
Definition SkMatrix.h:299
bool isSimilarity(SkScalar tol=SK_ScalarNearlyZero) const
Definition SkMatrix.cpp:180
static constexpr int kMSkewX
horizontal skew factor
Definition SkMatrix.h:354
bool mapRect(SkRect *dst, const SkRect &src, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
void mapVector(SkScalar dx, SkScalar dy, SkVector *result) const
Definition SkMatrix.h:1524
bool isIdentity() const
Definition SkMatrix.h:223
void transform(const SkMatrix &matrix, SkPath *dst, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
Definition SkPath.cpp:1647
static bool IsSimpleCircular(const SkRRect &rr)
Definition SkRRectPriv.h:27
static SkVector GetSimpleRadii(const SkRRect &rr)
Definition SkRRectPriv.h:22
static bool AllCornersCircular(const SkRRect &rr, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkRRect.cpp:353
static bool IsCircle(const SkRRect &rr)
Definition SkRRectPriv.h:18
const SkRect & rect() const
Definition SkRRect.h:264
SkVector radii(Corner corner) const
Definition SkRRect.h:271
SkString dumpToString(bool asHex) const
Definition SkRRect.cpp:632
@ kUpperLeft_Corner
index of top-left corner radii
Definition SkRRect.h:252
@ kLowerRight_Corner
index of bottom-right corner radii
Definition SkRRect.h:254
@ kUpperRight_Corner
index of top-right corner radii
Definition SkRRect.h:253
@ kLowerLeft_Corner
index of bottom-left corner radii
Definition SkRRect.h:255
bool transform(const SkMatrix &matrix, SkRRect *dst) const
Definition SkRRect.cpp:436
SkScalar width() const
Definition SkRRect.h:95
bool isRect() const
Definition SkRRect.h:84
void setRectRadii(const SkRect &rect, const SkVector radii[4])
Definition SkRRect.cpp:189
const SkRect & getBounds() const
Definition SkRRect.h:279
@ kUnion_Op
target unioned with operand
Definition SkRegion.h:369
@ kReplace_Op
replace target with operand
Definition SkRegion.h:372
@ kIntersect_Op
target intersected with operand
Definition SkRegion.h:368
@ kDifference_Op
target minus operand
Definition SkRegion.h:367
static Result MakeForShader(SkString sksl, const Options &)
const char * c_str() const
Definition SkString.h:133
@ kHairline_InitStyle
Definition SkStrokeRec.h:25
SkPaint::Cap getCap() const
Definition SkStrokeRec.h:44
T * init(Args &&... args)
Definition SkTLazy.h:45
T * get()
Definition SkTLazy.h:83
static Domain GenerateDomain()
const GrCaps * caps() const
const GrColorInfo & colorInfo() const
void fillPixelsWithLocalMatrix(const GrClip *clip, GrPaint &&paint, const SkIRect &bounds, const SkMatrix &localMatrix)
void drawShape(const GrClip *, GrPaint &&, GrAA, const SkMatrix &viewMatrix, GrStyledShape &&)
GrAA chooseAA(const SkPaint &paint)
const SkSurfaceProps & surfaceProps() const
static std::unique_ptr< SurfaceDrawContext > Make(GrRecordingContext *, GrColorType, sk_sp< GrSurfaceProxy >, sk_sp< SkColorSpace >, GrSurfaceOrigin, const SkSurfaceProps &)
void drawRect(const GrClip *, GrPaint &&paint, GrAA, const SkMatrix &viewMatrix, const SkRect &, const GrStyle *style=nullptr)
static std::unique_ptr< SurfaceDrawContext > MakeWithFallback(GrRecordingContext *, GrColorType, sk_sp< SkColorSpace >, SkBackingFit, SkISize dimensions, const SkSurfaceProps &, int sampleCnt, skgpu::Mipmapped, skgpu::Protected, GrSurfaceOrigin=kBottomLeft_GrSurfaceOrigin, skgpu::Budgeted=skgpu::Budgeted::kYes)
void fillRectToRectWithFP(const SkRect &srcRect, const SkIRect &dstRect, std::unique_ptr< GrFragmentProcessor > fp)
const Paint & paint
float SkScalar
Definition extension.cpp:12
GAsyncResult * result
static void mask_release_proc(void *addr, void *)
static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > 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)
static constexpr int kBlurRRectMaxDivisions
Definition GrBlurUtils.h:39
static std::unique_ptr< GrFragmentProcessor > make_rrect_blur(GrRecordingContext *context, float sigma, float xformedSigma, const SkRRect &srcRRect, const SkRRect &devRRect)
static SkIRect extract_draw_rect_from_data(SkData *data, const SkIRect &origDevBounds)
static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > create_mask_GPU(GrRecordingContext *rContext, const SkIRect &maskRect, const SkMatrix &origViewMatrix, const GrStyledShape &shape, int sampleCnt)
bool 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])
static bool get_unclipped_shape_dev_bounds(const GrStyledShape &shape, const SkMatrix &matrix, SkIRect *devBounds)
static bool draw_mask(skgpu::ganesh::SurfaceDrawContext *sdc, const GrClip *clip, const SkMatrix &viewMatrix, const SkIRect &maskBounds, GrPaint &&paint, GrSurfaceProxyView mask)
static GrSurfaceProxyView filter_mask(GrRecordingContext *context, const SkMaskFilterBase *maskFilter, GrSurfaceProxyView srcView, GrColorType srcColorType, SkAlphaType srcAlphaType, const SkMatrix &ctm, const SkIRect &maskRect)
void DrawShapeWithMaskFilter(GrRecordingContext *rContext, skgpu::ganesh::SurfaceDrawContext *sdc, const GrClip *clip, const GrStyledShape &shape, GrPaint &&paint, const SkMatrix &viewMatrix, const SkMaskFilter *mf)
static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > reexpand(GrRecordingContext *rContext, std::unique_ptr< skgpu::ganesh::SurfaceContext > src, const SkRect &srcBounds, SkISize dstSize, sk_sp< SkColorSpace > colorSpace, SkBackingFit fit)
static std::unique_ptr< GrFragmentProcessor > make_circle_blur(GrRecordingContext *context, const SkRect &circle, float sigma)
static bool can_filter_mask(const SkMaskFilterBase *maskFilter, const GrStyledShape &shape, const SkIRect &devSpaceShapeBounds, const SkIRect &clipBounds, const SkMatrix &ctm, SkIRect *maskRect)
static constexpr auto kMaskOrigin
static GrSurfaceProxyView 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)
static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > 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)
static void 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)
static bool 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)
static bool clip_bounds_quick_reject(const SkIRect &clipBounds, const SkIRect &rect)
static constexpr auto kBlurredRRectMaskOrigin
static std::unique_ptr< GrFragmentProcessor > make_rect_blur(GrRecordingContext *context, const GrShaderCaps &caps, const SkRect &srcRect, const SkMatrix &viewMatrix, float transformedSigma)
static GrSurfaceProxyView 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)
static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > 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)
std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > 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)
static std::unique_ptr< GrFragmentProcessor > find_or_create_rrect_blur_mask_fp(GrRecordingContext *rContext, const SkRRect &rrectToDraw, const SkISize &dimensions, float xformedSigma)
static bool direct_filter_mask(GrRecordingContext *context, const SkMaskFilterBase *maskFilter, skgpu::ganesh::SurfaceDrawContext *sdc, GrPaint &&paint, const GrClip *clip, const SkMatrix &viewMatrix, const GrStyledShape &shape)
static GrSurfaceProxyView create_mask_on_cpu(GrRecordingContext *rContext, const SkRRect &rrectToDraw, const SkISize &dimensions, float xformedSigma)
static sk_sp< SkData > create_data(const SkIRect &drawRect, const SkIRect &origDevBounds)
static bool get_shape_and_clip_bounds(skgpu::ganesh::SurfaceDrawContext *sdc, const GrClip *clip, const GrStyledShape &shape, const SkMatrix &matrix, SkIRect *unclippedDevShapeBounds, SkIRect *devClipBounds)
static void make_blurred_rrect_key(skgpu::UniqueKey *key, const SkRRect &rrectToDraw, float xformedSigma)
static bool fillin_view_on_gpu(GrDirectContext *dContext, const GrSurfaceProxyView &lazyView, GrThreadSafeCache::Trampoline *trampoline, const SkRRect &rrectToDraw, const SkISize &dimensions, float xformedSigma)
static std::unique_ptr< GrFragmentProcessor > make_rect_integral_fp(GrRecordingContext *rContext, float sixSigma)
static void 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)
static std::unique_ptr< GrFragmentProcessor > create_profile_effect(GrRecordingContext *rContext, const SkRect &circle, float sigma, float *solidRadius, float *textureRadius)
bool IsSupported(const SkMaskFilter *maskfilter)
constexpr bool BlurIsEffectivelyIdentity(float sigma)
Definition BlurUtils.h:37
SkBitmap CreateRRectBlurMask(const SkRRect &rrectToDraw, const SkISize &dimensions, float sigma)
static constexpr int kMaxBlurSamples
Definition BlurUtils.h:52
SkBitmap CreateIntegralTable(float sixSigma)
SkISize GetApproxSize(SkISize size)
int ComputeIntegralTableWidth(float sixSigma)
static constexpr float kMaxLinearBlurSigma
Definition BlurUtils.h:58
void Compute2DBlurOffsets(SkISize radius, std::array< SkV4, kMaxBlurSamples/2 > &offsets)
Definition BlurUtils.cpp:93
SkBitmap CreateCircleProfile(float sigma, float radius, int profileWidth)
const SkRuntimeEffect * GetLinearBlur1DEffect(int radius)
int BlurSigmaRadius(float sigma)
Definition BlurUtils.h:41
constexpr int BlurKernelWidth(int radius)
Definition BlurUtils.h:28
void Compute2DBlurKernel(SkSize sigma, SkISize radius, SkSpan< float > kernel)
Definition BlurUtils.cpp:35
constexpr int BlurLinearKernelWidth(int radius)
Definition BlurUtils.h:32
SkBitmap CreateHalfPlaneProfile(int profileWidth)
const SkRuntimeEffect * GetBlur2DEffect(const SkISize &radii)
void Compute1DBlurLinearKernel(float sigma, int radius, std::array< SkV4, kMaxBlurSamples/2 > &offsetsAndKernel)
int32_t width
const Scalar scale
SkIRect makeOutset(int32_t dx, int32_t dy) const
Definition SkRect.h:350
bool intersect(const SkIRect &r)
Definition SkRect.h:513
static bool Intersects(const SkIRect &a, const SkIRect &b)
Definition SkRect.h:535
int32_t fBottom
larger y-axis bounds
Definition SkRect.h:36
constexpr int32_t top() const
Definition SkRect.h:120
constexpr SkISize size() const
Definition SkRect.h:172
constexpr int32_t bottom() const
Definition SkRect.h:134
constexpr int32_t height() const
Definition SkRect.h:165
constexpr int32_t right() const
Definition SkRect.h:127
int32_t fTop
smaller y-axis bounds
Definition SkRect.h:34
static constexpr SkIRect MakeSize(const SkISize &size)
Definition SkRect.h:66
static constexpr SkIRect MakeEmpty()
Definition SkRect.h:45
constexpr int32_t width() const
Definition SkRect.h:158
void setEmpty()
Definition SkRect.h:242
constexpr SkIRect makeOffset(int32_t dx, int32_t dy) const
Definition SkRect.h:300
static constexpr SkIRect MakeWH(int32_t w, int32_t h)
Definition SkRect.h:56
bool isEmpty() const
Definition SkRect.h:202
constexpr SkIPoint topLeft() const
Definition SkRect.h:151
static constexpr SkIRect MakeXYWH(int32_t x, int32_t y, int32_t w, int32_t h)
Definition SkRect.h:104
int32_t fLeft
smaller x-axis bounds
Definition SkRect.h:33
void outset(int32_t dx, int32_t dy)
Definition SkRect.h:428
constexpr int32_t left() const
Definition SkRect.h:113
bool contains(int32_t x, int32_t y) const
Definition SkRect.h:463
int32_t fRight
larger x-axis bounds
Definition SkRect.h:35
int32_t fHeight
Definition SkSize.h:18
int32_t fWidth
Definition SkSize.h:17
constexpr int32_t width() const
Definition SkSize.h:36
constexpr int32_t height() const
Definition SkSize.h:37
static SkImageInfo MakeA8(int width, int height)
static void FreeImage(void *image)
Definition SkMask.cpp:57
@ kComputeBoundsAndRenderImage_CreateMode
compute bounds, alloc image and render into it
Definition SkMask.h:301
uint8_t *& image()
Definition SkMask.h:236
const uint32_t fRowBytes
Definition SkMask.h:43
@ kA8_Format
8bits per pixel mask (e.g. antialiasing)
Definition SkMask.h:28
const SkIRect fBounds
Definition SkMask.h:42
const Format fFormat
Definition SkMask.h:44
float fX
x-axis value
float fY
y-axis value
static SkRect Make(const SkISize &size)
Definition SkRect.h:669
SkScalar fBottom
larger y-axis bounds
Definition extension.cpp:17
constexpr float left() const
Definition SkRect.h:734
void inset(float dx, float dy)
Definition SkRect.h:1060
constexpr float top() const
Definition SkRect.h:741
bool intersect(const SkRect &r)
Definition SkRect.cpp:114
SkScalar fLeft
smaller x-axis bounds
Definition extension.cpp:14
constexpr float x() const
Definition SkRect.h:720
static SkRect MakeIWH(int w, int h)
Definition SkRect.h:623
void outset(float dx, float dy)
Definition SkRect.h:1077
SkRect makeOutset(float dx, float dy) const
Definition SkRect.h:1002
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition SkRect.h:659
SkScalar fRight
larger x-axis bounds
Definition extension.cpp:16
void roundOut(SkIRect *dst) const
Definition SkRect.h:1241
constexpr float centerX() const
Definition SkRect.h:776
void offset(float dx, float dy)
Definition SkRect.h:1016
constexpr float height() const
Definition SkRect.h:769
constexpr float right() const
Definition SkRect.h:748
constexpr float centerY() const
Definition SkRect.h:785
constexpr float width() const
Definition SkRect.h:762
bool isEmpty() const
Definition SkRect.h:693
static constexpr SkRect MakeLTRB(float l, float t, float r, float b)
Definition SkRect.h:646
bool isSorted() const
Definition SkRect.h:705
constexpr float bottom() const
Definition SkRect.h:755
SkScalar fTop
smaller y-axis bounds
Definition extension.cpp:15
SkScalar width() const
Definition SkSize.h:76
Definition SkM44.h:19
Definition SkM44.h:98
#define TRACE_EVENT2(category_group, name, arg1_name, arg1_val, arg2_name, arg2_val)