Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Classes | Functions | Variables
GrBlurUtils Namespace Reference

Classes

struct  DrawRectData
 

Functions

static bool clip_bounds_quick_reject (const SkIRect &clipBounds, const SkIRect &rect)
 
static bool draw_mask (skgpu::ganesh::SurfaceDrawContext *sdc, const GrClip *clip, const SkMatrix &viewMatrix, const SkIRect &maskBounds, GrPaint &&paint, GrSurfaceProxyView mask)
 
static void mask_release_proc (void *addr, void *)
 
static sk_sp< SkDatacreate_data (const SkIRect &drawRect, const SkIRect &origDevBounds)
 
static SkIRect extract_draw_rect_from_data (SkData *data, const SkIRect &origDevBounds)
 
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::SurfaceDrawContextcreate_mask_GPU (GrRecordingContext *rContext, const SkIRect &maskRect, const SkMatrix &origViewMatrix, const GrStyledShape &shape, int sampleCnt)
 
static bool get_unclipped_shape_dev_bounds (const GrStyledShape &shape, const SkMatrix &matrix, SkIRect *devBounds)
 
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 bool can_filter_mask (const SkMaskFilterBase *maskFilter, const GrStyledShape &shape, const SkIRect &devSpaceShapeBounds, const SkIRect &clipBounds, const SkMatrix &ctm, SkIRect *maskRect)
 
static std::unique_ptr< GrFragmentProcessorcreate_profile_effect (GrRecordingContext *rContext, const SkRect &circle, float sigma, float *solidRadius, float *textureRadius)
 
static std::unique_ptr< GrFragmentProcessormake_circle_blur (GrRecordingContext *context, const SkRect &circle, float sigma)
 
static std::unique_ptr< GrFragmentProcessormake_rect_integral_fp (GrRecordingContext *rContext, float sixSigma)
 
static std::unique_ptr< GrFragmentProcessormake_rect_blur (GrRecordingContext *context, const GrShaderCaps &caps, const SkRect &srcRect, const SkMatrix &viewMatrix, float transformedSigma)
 
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 GrSurfaceProxyView create_mask_on_cpu (GrRecordingContext *rContext, const SkRRect &rrectToDraw, const SkISize &dimensions, float xformedSigma)
 
static std::unique_ptr< GrFragmentProcessorfind_or_create_rrect_blur_mask_fp (GrRecordingContext *rContext, const SkRRect &rrectToDraw, const SkISize &dimensions, float xformedSigma)
 
static std::unique_ptr< GrFragmentProcessormake_rrect_blur (GrRecordingContext *context, float sigma, float xformedSigma, const SkRRect &srcRRect, const SkRRect &devRRect)
 
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 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 GrSurfaceProxyView filter_mask (GrRecordingContext *context, const SkMaskFilterBase *maskFilter, GrSurfaceProxyView srcView, GrColorType srcColorType, SkAlphaType srcAlphaType, const SkMatrix &ctm, const SkIRect &maskRect)
 
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 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)
 
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])
 
void DrawShapeWithMaskFilter (GrRecordingContext *rContext, skgpu::ganesh::SurfaceDrawContext *sdc, const GrClip *clip, const GrStyledShape &shape, GrPaint &&paint, const SkMatrix &viewMatrix, const SkMaskFilter *mf)
 
void DrawShapeWithMaskFilter (GrRecordingContext *rContext, skgpu::ganesh::SurfaceDrawContext *sdc, const GrClip *clip, const SkPaint &paint, const SkMatrix &ctm, const GrStyledShape &shape)
 
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< skgpu::ganesh::SurfaceDrawContextconvolve_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)
 
static std::unique_ptr< skgpu::ganesh::SurfaceDrawContextconvolve_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 std::unique_ptr< skgpu::ganesh::SurfaceDrawContextreexpand (GrRecordingContext *rContext, std::unique_ptr< skgpu::ganesh::SurfaceContext > src, const SkRect &srcBounds, SkISize dstSize, sk_sp< SkColorSpace > colorSpace, SkBackingFit fit)
 
static std::unique_ptr< skgpu::ganesh::SurfaceDrawContexttwo_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)
 
std::unique_ptr< skgpu::ganesh::SurfaceDrawContextGaussianBlur (GrRecordingContext *rContext, GrSurfaceProxyView srcView, GrColorType srcColorType, SkAlphaType srcAlphaType, sk_sp< SkColorSpace > colorSpace, SkIRect dstBounds, SkIRect srcBounds, float sigmaX, float sigmaY, SkTileMode mode, SkBackingFit fit)
 

Variables

static constexpr auto kMaskOrigin = kTopLeft_GrSurfaceOrigin
 
static constexpr auto kBlurredRRectMaskOrigin = kTopLeft_GrSurfaceOrigin
 
static constexpr int kBlurRRectMaxDivisions = 6
 

Detailed Description

Blur utilities.

Function Documentation

◆ can_filter_mask()

static bool GrBlurUtils::can_filter_mask ( const SkMaskFilterBase maskFilter,
const GrStyledShape shape,
const SkIRect devSpaceShapeBounds,
const SkIRect clipBounds,
const SkMatrix ctm,
SkIRect maskRect 
)
static

If we cannot create a FragmentProcess for a mask filter, we might have special logic for it here. That code path requires constructing a src mask as input. Since that is a potentially expensive operation, this function tests if filter_mask would succeed if the mask were to be created.

'maskRect' returns the device space portion of the mask that the filter needs. The mask passed into 'filter_mask' should have the same extent as 'maskRect' but be translated to the upper-left corner of the mask (i.e., (maskRect.fLeft, maskRect.fTop) appears at (0, 0) in the mask).

Logically, how this works is: can_filter_mask is called if (it returns true) the returned mask rect is used for quick rejecting the mask rect is used to generate the mask filter_mask is called to filter the mask

TODO: this should work as: if (can_filter_mask(devShape, ...)) // rect, rrect, drrect, path filter_mask(devShape, ...) this would hide the RRect special case and the mask generation

Definition at line 372 of file GrBlurUtils.cpp.

377 {
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}
#define SkIntToScalar(x)
Definition SkScalar.h:57
SkScalar computeXformedSigma(const SkMatrix &ctm) const
virtual Type type() const =0
float SkScalar
Definition extension.cpp:12
clipRect(r.rect, r.opAA.op(), r.opAA.aa())) template<> void Draw
constexpr bool BlurIsEffectivelyIdentity(float sigma)
Definition BlurUtils.h:37
SkIRect makeOutset(int32_t dx, int32_t dy) const
Definition SkRect.h:350
bool intersect(const SkIRect &r)
Definition SkRect.h:513
constexpr int32_t height() const
Definition SkRect.h:165
constexpr int32_t width() const
Definition SkRect.h:158
void setEmpty()
Definition SkRect.h:242

◆ clip_bounds_quick_reject()

static bool GrBlurUtils::clip_bounds_quick_reject ( const SkIRect clipBounds,
const SkIRect rect 
)
static

Definition at line 99 of file GrBlurUtils.cpp.

99 {
100 return clipBounds.isEmpty() || rect.isEmpty() || !SkIRect::Intersects(clipBounds, rect);
101}
static bool Intersects(const SkIRect &a, const SkIRect &b)
Definition SkRect.h:535
bool isEmpty() const
Definition SkRect.h:202

◆ compute_key_and_clip_bounds()

static bool GrBlurUtils::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

Definition at line 1136 of file GrBlurUtils.cpp.

1144 {
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}
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkASSERT(cond)
Definition SkAssert.h:116
int32_t SkFixed
Definition SkFixed.h:25
#define SkScalarToFixed(x)
Definition SkFixed.h:125
static uint32_t SkFloat2Bits(float value)
Definition SkFloatBits.h:41
SkMaskFilterBase * as_MFB(SkMaskFilter *mf)
static int64_t sk_64_mul(int64_t a, int64_t b)
Definition SkMath.h:33
static SkScalar SkScalarFraction(SkScalar x)
Definition SkScalar.h:67
int maxTextureSize() const
Definition GrCaps.h:229
bool isSimpleHairline() const
Definition GrStyle.h:117
const SkStrokeRec & strokeRec() const
Definition GrStyle.h:140
void writeUnstyledKey(uint32_t *key) const
int unstyledKeySize() const
bool hasUnstyledKey() const
const GrStyle & style() const
virtual bool asABlur(BlurRec *) const
static constexpr int kMScaleX
horizontal scale factor
Definition SkMatrix.h:353
static constexpr int kMTransY
vertical translation
Definition SkMatrix.h:358
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
static constexpr int kMSkewX
horizontal skew factor
Definition SkMatrix.h:354
SkPaint::Cap getCap() const
Definition SkStrokeRec.h:44
static Domain GenerateDomain()
static bool can_filter_mask(const SkMaskFilterBase *maskFilter, const GrStyledShape &shape, const SkIRect &devSpaceShapeBounds, const SkIRect &clipBounds, const SkMatrix &ctm, SkIRect *maskRect)

◆ ComputeBlurredRRectParams()

bool GrBlurUtils::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] 
)

This method computes all the parameters for drawing a partially occluded nine-patched blurred rrect mask: rrectToDraw - the integerized rrect to draw in the mask widthHeight - how large to make the mask (rrectToDraw will be centered in this coord system). rectXs, rectYs - the x & y coordinates of the covering geometry lattice texXs, texYs - the texture coordinate at each point in rectXs & rectYs It returns true if 'devRRect' is nine-patchable

Definition at line 1465 of file GrBlurUtils.cpp.

1474 {
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}
#define SkScalarCeilToInt(x)
Definition SkScalar.h:36
#define SkScalarCeilToScalar(x)
Definition SkScalar.h:31
SkVector radii(Corner corner) const
Definition SkRRect.h:271
@ 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
void setRectRadii(const SkRect &rect, const SkVector radii[4])
Definition SkRRect.cpp:189
const SkRect & getBounds() const
Definition SkRRect.h:279
int32_t fHeight
Definition SkSize.h:18
int32_t fWidth
Definition SkSize.h:17
float fX
x-axis value
float fY
y-axis value
SkScalar fBottom
larger y-axis bounds
Definition extension.cpp:17
SkScalar fLeft
smaller x-axis bounds
Definition extension.cpp:14
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
SkScalar fTop
smaller y-axis bounds
Definition extension.cpp:15

◆ convolve_gaussian()

static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > GrBlurUtils::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

Definition at line 1742 of file GrBlurUtils.cpp.

1754 {
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}
constexpr SkPMColor4f SK_PMColor4fTRANSPARENT
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
const GrCaps * caps() const
bool reducedShaderMode() const
Definition GrCaps.h:190
bool clampToBorderSupport() const
Definition GrCaps.h:484
GrRecordingContextPriv priv()
GrSurfaceOrigin origin() const
GrSurfaceProxy * proxy() const
SkISize backingStoreDimensions() const
GrProtected isProtected() const
static std::unique_ptr< SurfaceDrawContext > Make(GrRecordingContext *, GrColorType, sk_sp< GrSurfaceProxy >, sk_sp< SkColorSpace >, GrSurfaceOrigin, const SkSurfaceProps &)
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)
sk_sp< SkBlender > blender SkRect rect
Definition SkRecords.h:350
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 right() const
Definition SkRect.h:127
static constexpr SkIRect MakeSize(const SkISize &size)
Definition SkRect.h:66
static constexpr SkIRect MakeEmpty()
Definition SkRect.h:45
constexpr SkIPoint topLeft() const
Definition SkRect.h:151
constexpr int32_t left() const
Definition SkRect.h:113
bool contains(int32_t x, int32_t y) const
Definition SkRect.h:463

◆ convolve_gaussian_1d()

static void GrBlurUtils::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

Draws 'dstRect' into 'surfaceFillContext' evaluating a 1D Gaussian over 'srcView'. The src rect is 'dstRect' offset by 'dstToSrcOffset'. 'mode' and 'bounds' are applied to the src coords.

Definition at line 1627 of file GrBlurUtils.cpp.

1636 {
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}
static constexpr GrSamplerState::WrapMode SkTileModeToWrapMode(SkTileMode tileMode)
Definition SkGr.h:77
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
const GrCaps * caps() const
void fillRectToRectWithFP(const SkRect &srcRect, const SkIRect &dstRect, std::unique_ptr< GrFragmentProcessor > fp)
static constexpr int kMaxBlurSamples
Definition BlurUtils.h:52
const SkRuntimeEffect * GetLinearBlur1DEffect(int radius)
void Compute1DBlurLinearKernel(float sigma, int radius, std::array< SkV4, kMaxBlurSamples/2 > &offsetsAndKernel)
constexpr SkIRect makeOffset(int32_t dx, int32_t dy) const
Definition SkRect.h:300
Definition SkM44.h:19
Definition SkM44.h:98

◆ convolve_gaussian_2d()

static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > GrBlurUtils::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 
)
static

Definition at line 1667 of file GrBlurUtils.cpp.

1679 {
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}
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
static const SkMatrix & I()
const Paint & paint
void Compute2DBlurOffsets(SkISize radius, std::array< SkV4, kMaxBlurSamples/2 > &offsets)
Definition BlurUtils.cpp:93
void Compute2DBlurKernel(SkSize sigma, SkISize radius, SkSpan< float > kernel)
Definition BlurUtils.cpp:35
const SkRuntimeEffect * GetBlur2DEffect(const SkISize &radii)
static SkRect Make(const SkISize &size)
Definition SkRect.h:669

◆ create_data()

static sk_sp< SkData > GrBlurUtils::create_data ( const SkIRect drawRect,
const SkIRect origDevBounds 
)
static

Definition at line 142 of file GrBlurUtils.cpp.

142 {
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}
static sk_sp< SkData > MakeWithCopy(const void *data, size_t length)
Definition SkData.cpp:111
int32_t fTop
smaller y-axis bounds
Definition SkRect.h:34
int32_t fLeft
smaller x-axis bounds
Definition SkRect.h:33

◆ create_mask_GPU()

static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > GrBlurUtils::create_mask_GPU ( GrRecordingContext rContext,
const SkIRect maskRect,
const SkMatrix origViewMatrix,
const GrStyledShape shape,
int  sampleCnt 
)
static

Definition at line 249 of file GrBlurUtils.cpp.

254 {
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,
278 kMaskOrigin);
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}
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition SkPath.cpp:3824
void setCoverageSetOpXPFactory(SkRegion::Op, bool invertCoverage=false)
Definition GrPaint.cpp:32
SkMatrix & postTranslate(SkScalar dx, SkScalar dy)
Definition SkMatrix.cpp:281
@ kReplace_Op
replace target with operand
Definition SkRegion.h:372
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)
SkISize GetApproxSize(SkISize size)
static constexpr SkIRect MakeWH(int32_t w, int32_t h)
Definition SkRect.h:56

◆ create_mask_on_cpu()

static GrSurfaceProxyView GrBlurUtils::create_mask_on_cpu ( GrRecordingContext rContext,
const SkRRect rrectToDraw,
const SkISize dimensions,
float  xformedSigma 
)
static

Definition at line 801 of file GrBlurUtils.cpp.

804 {
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}
std::tuple< GrSurfaceProxyView, GrColorType > GrMakeUncachedBitmapProxyView(GrRecordingContext *rContext, const SkBitmap &bitmap, skgpu::Mipmapped mipmapped, SkBackingFit fit, skgpu::Budgeted budgeted)
Definition SkGr.cpp:253
GAsyncResult * result
SkBitmap CreateRRectBlurMask(const SkRRect &rrectToDraw, const SkISize &dimensions, float sigma)

◆ create_profile_effect()

static std::unique_ptr< GrFragmentProcessor > GrBlurUtils::create_profile_effect ( GrRecordingContext rContext,
const SkRect circle,
float  sigma,
float *  solidRadius,
float *  textureRadius 
)
static

Definition at line 418 of file GrBlurUtils.cpp.

422 {
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}
@ kTopLeft_GrSurfaceOrigin
Definition GrTypes.h:148
#define SkFixedToScalar(x)
Definition SkFixed.h:124
static bool SkIsFinite(T x, Pack... values)
#define SK_ScalarNearlyZero
Definition SkScalar.h:99
GrThreadSafeCache * threadSafeCache()
GrTextureProxy * asTextureProxy() const
static std::unique_ptr< GrFragmentProcessor > Make(GrSurfaceProxyView, SkAlphaType, const SkMatrix &=SkMatrix::I(), GrSamplerState::Filter=GrSamplerState::Filter::kNearest, GrSamplerState::MipmapMode mipmapMode=GrSamplerState::MipmapMode::kNone)
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition SkMatrix.h:75
SkBitmap CreateCircleProfile(float sigma, float radius, int profileWidth)
SkBitmap CreateHalfPlaneProfile(int profileWidth)
const Scalar scale
constexpr float width() const
Definition SkRect.h:762

◆ direct_filter_mask()

static bool GrBlurUtils::direct_filter_mask ( GrRecordingContext context,
const SkMaskFilterBase maskFilter,
skgpu::ganesh::SurfaceDrawContext sdc,
GrPaint &&  paint,
const GrClip clip,
const SkMatrix viewMatrix,
const GrStyledShape shape 
)
static

Try to directly render the mask filter into the target. Returns true if drawing was successful. If false is returned then paint is unmodified.

Definition at line 1009 of file GrBlurUtils.cpp.

1015 {
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}
@ kNormal_SkBlurStyle
fuzzy inside and outside
Definition SkBlurTypes.h:12
#define SkScalarAbs(x)
Definition SkScalar.h:39
static SkScalar center(float pos0, float pos1)
const GrShaderCaps * shaderCaps() const
Definition GrCaps.h:63
bool isSimpleFill() const
Definition GrStyle.h:114
bool asRRect(SkRRect *rrect, SkPathDirection *dir, unsigned *start, bool *inverted) const
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
SkScalar getScaleX() const
Definition SkMatrix.h:415
bool decomposeScale(SkSize *scale, SkMatrix *remaining=nullptr) const
SkScalar getScaleY() const
Definition SkMatrix.h:422
bool isScaleTranslate() const
Definition SkMatrix.h:236
bool isSimilarity(SkScalar tol=SK_ScalarNearlyZero) const
Definition SkMatrix.cpp:180
void mapVector(SkScalar dx, SkScalar dy, SkVector *result) const
Definition SkMatrix.h:1524
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
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 fillPixelsWithLocalMatrix(const GrClip *clip, GrPaint &&paint, const SkIRect &bounds, const SkMatrix &localMatrix)
void drawShape(const GrClip *, GrPaint &&, GrAA, const SkMatrix &viewMatrix, GrStyledShape &&)
void drawRect(const GrClip *, GrPaint &&paint, GrAA, const SkMatrix &viewMatrix, const SkRect &, const GrStyle *style=nullptr)
static std::unique_ptr< GrFragmentProcessor > make_rrect_blur(GrRecordingContext *context, float sigma, float xformedSigma, const SkRRect &srcRRect, const SkRRect &devRRect)
static std::unique_ptr< GrFragmentProcessor > make_circle_blur(GrRecordingContext *context, const SkRect &circle, float sigma)
static std::unique_ptr< GrFragmentProcessor > make_rect_blur(GrRecordingContext *context, const GrShaderCaps &caps, const SkRect &srcRect, const SkMatrix &viewMatrix, float transformedSigma)
const uint32_t fp
constexpr float x() const
Definition SkRect.h:720
void outset(float dx, float dy)
Definition SkRect.h:1077
void roundOut(SkIRect *dst) const
Definition SkRect.h:1241
constexpr float centerX() const
Definition SkRect.h:776
constexpr float centerY() const
Definition SkRect.h:785
SkScalar width() const
Definition SkSize.h:76

◆ draw_mask()

static bool GrBlurUtils::draw_mask ( skgpu::ganesh::SurfaceDrawContext sdc,
const GrClip clip,
const SkMatrix viewMatrix,
const SkIRect maskBounds,
GrPaint &&  paint,
GrSurfaceProxyView  mask 
)
static

Definition at line 108 of file GrBlurUtils.cpp.

113 {
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}
@ kUnknown_SkAlphaType
uninitialized
Definition SkAlphaType.h:27
void concatSwizzle(skgpu::Swizzle swizzle)
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition SkMatrix.h:91

◆ draw_shape_with_mask_filter()

static void GrBlurUtils::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

Definition at line 1376 of file GrBlurUtils.cpp.

1382 {
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}
bool GrIsStrokeHairlineOrEquivalent(const GrStyle &style, const SkMatrix &matrix, SkScalar *outCoverage)
Definition GrUtil.cpp:65
virtual GrDirectContext * asDirectContext()
bool applies() const
Definition GrStyle.h:143
static SkScalar MatrixToScaleFactor(const SkMatrix &matrix)
Definition GrStyle.h:147
bool inverseFilled() const
GrStyledShape applyStyle(GrStyle::Apply apply, SkScalar scale) const
T * init(Args &&... args)
Definition SkTLazy.h:45
T * get()
Definition SkTLazy.h:83
static bool draw_mask(skgpu::ganesh::SurfaceDrawContext *sdc, const GrClip *clip, const SkMatrix &viewMatrix, const SkIRect &maskBounds, GrPaint &&paint, GrSurfaceProxyView mask)
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 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 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 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 bool get_shape_and_clip_bounds(skgpu::ganesh::SurfaceDrawContext *sdc, const GrClip *clip, const GrStyledShape &shape, const SkMatrix &matrix, SkIRect *unclippedDevShapeBounds, SkIRect *devClipBounds)

◆ DrawShapeWithMaskFilter() [1/2]

void GrBlurUtils::DrawShapeWithMaskFilter ( GrRecordingContext ,
skgpu::ganesh::SurfaceDrawContext ,
const GrClip ,
const GrStyledShape ,
GrPaint &&  ,
const SkMatrix viewMatrix,
const SkMaskFilter  
)

Draw a shape handling the mask filter. The mask filter is not optional. The GrPaint will be modified after return.

Definition at line 1546 of file GrBlurUtils.cpp.

1552 {
1553 draw_shape_with_mask_filter(rContext, sdc, clip, std::move(paint),
1554 viewMatrix, as_MFB(mf), shape);
1555}
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)

◆ DrawShapeWithMaskFilter() [2/2]

void GrBlurUtils::DrawShapeWithMaskFilter ( GrRecordingContext ,
skgpu::ganesh::SurfaceDrawContext ,
const GrClip ,
const SkPaint ,
const SkMatrix ,
const GrStyledShape  
)

Draw a shape handling the mask filter if present.

Definition at line 1557 of file GrBlurUtils.cpp.

1562 {
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}
bool SkPaintToGrPaint(GrRecordingContext *context, const GrColorInfo &dstColorInfo, const SkPaint &skPaint, const SkMatrix &ctm, const SkSurfaceProps &surfaceProps, GrPaint *grPaint)
Definition SkGr.cpp:553
bool abandoned() override
const GrColorInfo & colorInfo() const
GrAA chooseAA(const SkPaint &paint)
const SkSurfaceProps & surfaceProps() const
bool IsSupported(const SkMaskFilter *maskfilter)

◆ extract_draw_rect_from_data()

static SkIRect GrBlurUtils::extract_draw_rect_from_data ( SkData data,
const SkIRect origDevBounds 
)
static

Definition at line 151 of file GrBlurUtils.cpp.

151 {
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}
static constexpr SkIRect MakeXYWH(int32_t x, int32_t y, int32_t w, int32_t h)
Definition SkRect.h:104

◆ fillin_view_on_gpu()

static bool GrBlurUtils::fillin_view_on_gpu ( GrDirectContext dContext,
const GrSurfaceProxyView lazyView,
GrThreadSafeCache::Trampoline trampoline,
const SkRRect rrectToDraw,
const SkISize dimensions,
float  xformedSigma 
)
static

Definition at line 737 of file GrBlurUtils.cpp.

742 {
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,
759 kBlurredRRectMaskOrigin);
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}
static const GrStyle & SimpleFill()
Definition GrStyle.h:30
skgpu::Swizzle swizzle() const
sk_sp< GrTextureProxy > fProxy

◆ filter_mask()

static GrSurfaceProxyView GrBlurUtils::filter_mask ( GrRecordingContext context,
const SkMaskFilterBase maskFilter,
GrSurfaceProxyView  srcView,
GrColorType  srcColorType,
SkAlphaType  srcAlphaType,
const SkMatrix ctm,
const SkIRect maskRect 
)
static

This function is used to implement filters that require an explicit src mask. It should only be called if can_filter_mask returned true and the maskRect param should be the output from that call. Implementations are free to get the GrContext from the src texture in order to create additional textures and perform multiple passes.

Definition at line 1228 of file GrBlurUtils.cpp.

1234 {
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}
@ 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
SkISize dimensions() const
@ kUnion_Op
target unioned with operand
Definition SkRegion.h:369
@ kIntersect_Op
target intersected with operand
Definition SkRegion.h:368
@ kDifference_Op
target minus operand
Definition SkRegion.h:367
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)

◆ find_or_create_rrect_blur_mask_fp()

static std::unique_ptr< GrFragmentProcessor > GrBlurUtils::find_or_create_rrect_blur_mask_fp ( GrRecordingContext rContext,
const SkRRect rrectToDraw,
const SkISize dimensions,
float  xformedSigma 
)
static

Definition at line 819 of file GrBlurUtils.cpp.

823 {
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,
846 kBlurredRRectMaskOrigin,
848 if (!lazyView) {
849 return nullptr;
850 }
851
852 view = threadSafeCache->findOrAdd(key, lazyView);
853 if (view != lazyView) {
854 SkASSERT(view.asTextureProxy());
855 SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
856 return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
857 }
858
859 if (!fillin_view_on_gpu(dContext,
860 lazyView,
861 trampoline.get(),
862 rrectToDraw,
863 dimensions,
864 xformedSigma)) {
865 // In this case something has gone disastrously wrong so set up to drop the draw
866 // that needed this resource and reduce future pollution of the cache.
867 threadSafeCache->remove(key);
868 return nullptr;
869 }
870 } else {
871 view = threadSafeCache->find(key);
872 if (view) {
873 SkASSERT(view.asTextureProxy());
874 SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
875 return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
876 }
877
878 view = create_mask_on_cpu(rContext, rrectToDraw, dimensions, xformedSigma);
879 if (!view) {
880 return nullptr;
881 }
882
883 view = threadSafeCache->add(key, view);
884 }
885
886 SkASSERT(view.asTextureProxy());
887 SkASSERT(view.origin() == kBlurredRRectMaskOrigin);
888 return GrTextureEffect::Make(std::move(view), kPremul_SkAlphaType, m);
889}
static std::tuple< GrSurfaceProxyView, sk_sp< Trampoline > > CreateLazyView(GrDirectContext *, GrColorType, SkISize dimensions, GrSurfaceOrigin, SkBackingFit)
static GrSurfaceProxyView create_mask_on_cpu(GrRecordingContext *rContext, const SkRRect &rrectToDraw, const SkISize &dimensions, float xformedSigma)
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)
constexpr int32_t width() const
Definition SkSize.h:36
constexpr int32_t height() const
Definition SkSize.h:37

◆ GaussianBlur()

std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > GrBlurUtils::GaussianBlur ( GrRecordingContext ,
GrSurfaceProxyView  srcView,
GrColorType  srcColorType,
SkAlphaType  srcAlphaType,
sk_sp< SkColorSpace srcColorSpace,
SkIRect  dstBounds,
SkIRect  srcBounds,
float  sigmaX,
float  sigmaY,
SkTileMode  mode,
SkBackingFit  fit = SkBackingFit::kApprox 
)

Applies a 2D Gaussian blur to a given texture. The blurred result is returned as a surfaceDrawContext in case the caller wishes to draw into the result. The GrSurfaceOrigin of the result will watch the GrSurfaceOrigin of srcView. The output color type, color space, and alpha type will be the same as the src.

Note: one of sigmaX and sigmaY should be non-zero!

Parameters
contextThe GPU context
srcViewThe source to be blurred.
srcColorTypeThe colorType of srcProxy
srcAlphaTypeThe alphaType of srcProxy
srcColorSpaceColor space of the source.
dstBoundsThe destination bounds, relative to the source texture.
srcBoundsThe source bounds, relative to the source texture's offset. No pixels will be sampled outside of this rectangle.
sigmaXThe blur's standard deviation in X.
sigmaYThe blur's standard deviation in Y.
tileModeThe mode to handle samples outside bounds.
fitbacking fit for the returned render target context
Returns
The surfaceDrawContext containing the blurred result.

Definition at line 2107 of file GrBlurUtils.cpp.

2117 {
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}
constexpr SkPMColor4f SK_PMColor4fWHITE
#define sk_float_floor2int(x)
int maxRenderTargetSize() const
Definition GrCaps.h:223
std::unique_ptr< skgpu::ganesh::SurfaceContext > makeSC(GrSurfaceProxyView readView, const GrColorInfo &)
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)
@ kFast_SrcRectConstraint
sample outside bounds; faster
Definition SkCanvas.h:1543
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 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< 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)
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition switches.h:228
static constexpr float kMaxLinearBlurSigma
Definition BlurUtils.h:58
int BlurSigmaRadius(float sigma)
Definition BlurUtils.h:41
constexpr int BlurKernelWidth(int radius)
Definition BlurUtils.h:28
constexpr int BlurLinearKernelWidth(int radius)
Definition BlurUtils.h:32
int32_t fBottom
larger y-axis bounds
Definition SkRect.h:36
int32_t fRight
larger x-axis bounds
Definition SkRect.h:35
void offset(float dx, float dy)
Definition SkRect.h:1016
#define TRACE_EVENT2(category_group, name, arg1_name, arg1_val, arg2_name, arg2_val)

◆ get_shape_and_clip_bounds()

static bool GrBlurUtils::get_shape_and_clip_bounds ( skgpu::ganesh::SurfaceDrawContext sdc,
const GrClip clip,
const GrStyledShape shape,
const SkMatrix matrix,
SkIRect unclippedDevShapeBounds,
SkIRect devClipBounds 
)
static

Definition at line 331 of file GrBlurUtils.cpp.

336 {
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}
static bool get_unclipped_shape_dev_bounds(const GrStyledShape &shape, const SkMatrix &matrix, SkIRect *devBounds)

◆ get_unclipped_shape_dev_bounds()

static bool GrBlurUtils::get_unclipped_shape_dev_bounds ( const GrStyledShape shape,
const SkMatrix matrix,
SkIRect devBounds 
)
static

Definition at line 299 of file GrBlurUtils.cpp.

300 {
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}
#define SkScalarRoundToInt(x)
Definition SkScalar.h:37
#define SK_ScalarInfinity
Definition SkScalar.h:26
#define SK_ScalarNegativeInfinity
Definition SkScalar.h:27
SkRect styledBounds() const
unsigned useCenter Optional< SkMatrix > matrix
Definition SkRecords.h:258
constexpr int kMaxInt
Definition globals.h:490
bool intersect(const SkRect &r)
Definition SkRect.cpp:114
constexpr float height() const
Definition SkRect.h:769
bool isEmpty() const
Definition SkRect.h:693
static constexpr SkRect MakeLTRB(float l, float t, float r, float b)
Definition SkRect.h:646

◆ hw_create_filtered_mask()

static GrSurfaceProxyView GrBlurUtils::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

Definition at line 1288 of file GrBlurUtils.cpp.

1296 {
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(),
1317 kMaskOrigin, SkBackingFit::kApprox);
1318 if (!lazyView) {
1319 return {}; // fall back to a SW-created mask - 'create_mask_GPU' probably won't succeed
1320 }
1321
1322 key->setCustomData(create_data(*maskRect, unclippedDevShapeBounds));
1323 auto [cachedView, data] = threadSafeCache->findOrAddWithData(*key, lazyView);
1324 if (cachedView != lazyView) {
1325 // In this case, the gpu-thread lost out to a recording thread - use its result.
1326 SkASSERT(data);
1327 SkASSERT(cachedView.asTextureProxy());
1328 SkASSERT(cachedView.origin() == kMaskOrigin);
1329
1330 *maskRect = extract_draw_rect_from_data(data.get(), unclippedDevShapeBounds);
1331 return cachedView;
1332 }
1333 }
1334
1335 std::unique_ptr<skgpu::ganesh::SurfaceDrawContext> maskSDC(
1336 create_mask_GPU(dContext, *maskRect, viewMatrix, shape, sdc->numSamples()));
1337 if (!maskSDC) {
1338 if (key->isValid()) {
1339 // It is very unlikely that 'create_mask_GPU' will fail after 'CreateLazyView'
1340 // succeeded but, if it does, remove the lazy-view from the cache and fallback to
1341 // a SW-created mask. Note that any recording threads that glommed onto the
1342 // lazy-view will have to, later, drop those draws.
1343 threadSafeCache->remove(*key);
1344 }
1345 return {};
1346 }
1347
1348 auto filteredMaskView = filter_mask(dContext, filter,
1349 maskSDC->readSurfaceView(),
1350 maskSDC->colorInfo().colorType(),
1351 maskSDC->colorInfo().alphaType(),
1352 viewMatrix,
1353 *maskRect);
1354 if (!filteredMaskView) {
1355 if (key->isValid()) {
1356 // Remove the lazy-view from the cache and fallback to a SW-created mask. Note that
1357 // any recording threads that glommed onto the lazy-view will have to, later, drop
1358 // those draws.
1359 threadSafeCache->remove(*key);
1360 }
1361 return {};
1362 }
1363
1364 if (key->isValid()) {
1365 SkASSERT(filteredMaskView.dimensions() == lazyView.dimensions());
1366 SkASSERT(filteredMaskView.swizzle() == lazyView.swizzle());
1367 SkASSERT(filteredMaskView.origin() == lazyView.origin());
1368
1369 trampoline->fProxy = filteredMaskView.asTextureProxyRef();
1370 return lazyView;
1371 }
1372
1373 return filteredMaskView;
1374}
GrDirectContextPriv priv()
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)
static GrSurfaceProxyView filter_mask(GrRecordingContext *context, const SkMaskFilterBase *maskFilter, GrSurfaceProxyView srcView, GrColorType srcColorType, SkAlphaType srcAlphaType, const SkMatrix &ctm, const SkIRect &maskRect)
static bool clip_bounds_quick_reject(const SkIRect &clipBounds, const SkIRect &rect)
static sk_sp< SkData > create_data(const SkIRect &drawRect, const SkIRect &origDevBounds)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switches.h:41

◆ make_blurred_rrect_key()

static void GrBlurUtils::make_blurred_rrect_key ( skgpu::UniqueKey key,
const SkRRect rrectToDraw,
float  xformedSigma 
)
static

Definition at line 715 of file GrBlurUtils.cpp.

717 {
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}
static bool SkScalarIsInt(SkScalar x)
Definition SkScalar.h:80

◆ make_circle_blur()

static std::unique_ptr< GrFragmentProcessor > GrBlurUtils::make_circle_blur ( GrRecordingContext context,
const SkRect circle,
float  sigma 
)
static

Definition at line 495 of file GrBlurUtils.cpp.

497 {
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}
SkRuntimeEffect * SkMakeRuntimeEffect(SkRuntimeEffect::Result(*make)(SkString, const SkRuntimeEffect::Options &), const char *sksl, SkRuntimeEffect::Options options=SkRuntimeEffect::Options{})
static GrIgnoreOptFlags IgnoreOptFlags(std::unique_ptr< GrFragmentProcessor > child)
Definition GrSkSLFP.h:96
static Result MakeForShader(SkString sksl, const Options &)

◆ make_rect_blur()

static std::unique_ptr< GrFragmentProcessor > GrBlurUtils::make_rect_blur ( GrRecordingContext context,
const GrShaderCaps caps,
const SkRect srcRect,
const SkMatrix viewMatrix,
float  transformedSigma 
)
static

Definition at line 577 of file GrBlurUtils.cpp.

581 {
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}
static std::unique_ptr< GrFragmentProcessor > DeviceSpace(std::unique_ptr< GrFragmentProcessor >)
static std::unique_ptr< GrFragmentProcessor > Make(const SkMatrix &matrix, std::unique_ptr< GrFragmentProcessor > child)
bool rectStaysRect() const
Definition SkMatrix.h:271
bool mapRect(SkRect *dst, const SkRect &src, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
bool isIdentity() const
Definition SkMatrix.h:223
static std::unique_ptr< GrFragmentProcessor > make_rect_integral_fp(GrRecordingContext *rContext, float sixSigma)
constexpr float left() const
Definition SkRect.h:734
constexpr float top() const
Definition SkRect.h:741
constexpr float right() const
Definition SkRect.h:748
bool isSorted() const
Definition SkRect.h:705
constexpr float bottom() const
Definition SkRect.h:755

◆ make_rect_integral_fp()

static std::unique_ptr< GrFragmentProcessor > GrBlurUtils::make_rect_integral_fp ( GrRecordingContext rContext,
float  sixSigma 
)
static

Definition at line 537 of file GrBlurUtils.cpp.

538 {
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}
SkBitmap CreateIntegralTable(float sixSigma)
int ComputeIntegralTableWidth(float sixSigma)
int32_t width

◆ make_rrect_blur()

static std::unique_ptr< GrFragmentProcessor > GrBlurUtils::make_rrect_blur ( GrRecordingContext context,
float  sigma,
float  xformedSigma,
const SkRRect srcRRect,
const SkRRect devRRect 
)
static

Definition at line 891 of file GrBlurUtils.cpp.

895 {
897 "Unexpected circle. %d\n\t%s\n\t%s",
898 SkRRectPriv::IsCircle(srcRRect),
899 srcRRect.dumpToString(true).c_str(),
900 devRRect.dumpToString(true).c_str());
901 SkASSERTF(!devRRect.isRect(),
902 "Unexpected rect. %d\n\t%s\n\t%s",
903 srcRRect.isRect(),
904 srcRRect.dumpToString(true).c_str(),
905 devRRect.dumpToString(true).c_str());
906
907 // TODO: loosen this up
908 if (!SkRRectPriv::IsSimpleCircular(devRRect)) {
909 return nullptr;
910 }
911
912 if (skgpu::BlurIsEffectivelyIdentity(xformedSigma)) {
913 return nullptr;
914 }
915
916 // Make sure we can successfully ninepatch this rrect -- the blur sigma has to be sufficiently
917 // small relative to both the size of the corner radius and the width (and height) of the rrect.
918 SkRRect rrectToDraw;
919 SkISize dimensions;
920 SkScalar ignored[kBlurRRectMaxDivisions];
921
922 bool ninePatchable = ComputeBlurredRRectParams(srcRRect,
923 devRRect,
924 sigma,
925 xformedSigma,
926 &rrectToDraw,
927 &dimensions,
928 ignored,
929 ignored,
930 ignored,
931 ignored);
932 if (!ninePatchable) {
933 return nullptr;
934 }
935
936 std::unique_ptr<GrFragmentProcessor> maskFP =
937 find_or_create_rrect_blur_mask_fp(context, rrectToDraw, dimensions, xformedSigma);
938 if (!maskFP) {
939 return nullptr;
940 }
941
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}
#define SkASSERTF(cond, fmt,...)
Definition SkAssert.h:117
static bool IsSimpleCircular(const SkRRect &rr)
Definition SkRRectPriv.h:27
static SkVector GetSimpleRadii(const SkRRect &rr)
Definition SkRRectPriv.h:22
SkString dumpToString(bool asHex) const
Definition SkRRect.cpp:632
const char * c_str() const
Definition SkString.h:133
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 std::unique_ptr< GrFragmentProcessor > find_or_create_rrect_blur_mask_fp(GrRecordingContext *rContext, const SkRRect &rrectToDraw, const SkISize &dimensions, float xformedSigma)

◆ mask_release_proc()

static void GrBlurUtils::mask_release_proc ( void *  addr,
void *   
)
static

Definition at line 131 of file GrBlurUtils.cpp.

131 {
133}
static void FreeImage(void *image)
Definition SkMask.cpp:57

◆ reexpand()

static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > GrBlurUtils::reexpand ( GrRecordingContext rContext,
std::unique_ptr< skgpu::ganesh::SurfaceContext src,
const SkRect srcBounds,
SkISize  dstSize,
sk_sp< SkColorSpace colorSpace,
SkBackingFit  fit 
)
static

Definition at line 1919 of file GrBlurUtils.cpp.

1925 {
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}
GrColorType
SkAlphaType
Definition SkAlphaType.h:26
static SkRect MakeIWH(int w, int h)
Definition SkRect.h:623

◆ sw_create_filtered_mask()

static GrSurfaceProxyView GrBlurUtils::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

Definition at line 160 of file GrBlurUtils.cpp.

167 {
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}
std::unique_ptr< uint8_t, SkFunctionObject< SkMaskBuilder::FreeImage > > SkAutoMaskFreeImage
Definition SkMask.h:316
void asPath(SkPath *out) const
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
static bool DrawToMask(const SkPath &devPath, const SkIRect &clipBounds, const SkMaskFilter *, const SkMatrix *filterMatrix, SkMaskBuilder *dst, SkMaskBuilder::CreateMode mode, SkStrokeRec::InitStyle style)
void transform(const SkMatrix &matrix, SkPath *dst, SkApplyPerspectiveClip pc=SkApplyPerspectiveClip::kYes) const
Definition SkPath.cpp:1647
@ kHairline_InitStyle
Definition SkStrokeRec.h:25
static SkImageInfo MakeA8(int width, int height)
@ 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

◆ two_pass_gaussian()

static std::unique_ptr< skgpu::ganesh::SurfaceDrawContext > GrBlurUtils::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

Definition at line 1980 of file GrBlurUtils.cpp.

1993 {
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}
SkBackingFit
#define sk_float_floor2int_no_saturate(x)
#define sk_float_ceil2int_no_saturate(x)
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)
void outset(int32_t dx, int32_t dy)
Definition SkRect.h:428

Variable Documentation

◆ kBlurredRRectMaskOrigin

constexpr auto GrBlurUtils::kBlurredRRectMaskOrigin = kTopLeft_GrSurfaceOrigin
staticconstexpr

Definition at line 713 of file GrBlurUtils.cpp.

◆ kBlurRRectMaxDivisions

constexpr int GrBlurUtils::kBlurRRectMaxDivisions = 6
staticconstexpr

Definition at line 39 of file GrBlurUtils.h.

◆ kMaskOrigin

constexpr auto GrBlurUtils::kMaskOrigin = kTopLeft_GrSurfaceOrigin
staticconstexpr

Definition at line 103 of file GrBlurUtils.cpp.