[graphite] Analytic blur for rrects
Bug: b/238762890
Change-Id: I9be704ade0247c04a0a9f6ca5dd5735d5cbf3c19
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/844837
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: James Godfrey-Kittle <jamesgk@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
diff --git a/src/gpu/graphite/geom/AnalyticBlurMask.cpp b/src/gpu/graphite/geom/AnalyticBlurMask.cpp
index 07f907e..9d08f6f 100644
--- a/src/gpu/graphite/geom/AnalyticBlurMask.cpp
+++ b/src/gpu/graphite/geom/AnalyticBlurMask.cpp
@@ -73,7 +73,11 @@
return MakeCircle(recorder, localToDevice, deviceSigma, srcRect, devRect);
}
- // TODO(b/238762890) Support analytic blurring with rrects.
+ if (devRRectIsValid && SkRRectPriv::IsSimpleCircular(devRRect) &&
+ localToDevice.isScaleTranslate()) {
+ return MakeRRect(recorder, localToDevice, deviceSigma, srcRRect, devRRect);
+ }
+
return std::nullopt;
}
@@ -157,10 +161,9 @@
return AnalyticBlurMask(*drawBounds,
SkM44(devToScaledShape),
- shapeData,
ShapeType::kRect,
- isFast,
- invSixSigma,
+ shapeData,
+ {static_cast<float>(isFast), invSixSigma},
integral);
}
@@ -233,11 +236,80 @@
constexpr float kUnusedBlurData = 0.0f;
return AnalyticBlurMask(*drawBounds,
SkM44(),
- shapeData,
ShapeType::kCircle,
- kUnusedBlurData,
- kUnusedBlurData,
+ shapeData,
+ {kUnusedBlurData, kUnusedBlurData},
profile);
}
+std::optional<AnalyticBlurMask> AnalyticBlurMask::MakeRRect(Recorder* recorder,
+ const SkMatrix& localToDevice,
+ float devSigma,
+ const SkRRect& srcRRect,
+ const SkRRect& devRRect) {
+ const int devBlurRadius = 3 * SkScalarCeilToInt(devSigma - 1.0f / 6.0f);
+
+ const SkVector& devRadiiUL = devRRect.radii(SkRRect::kUpperLeft_Corner);
+ const SkVector& devRadiiUR = devRRect.radii(SkRRect::kUpperRight_Corner);
+ const SkVector& devRadiiLR = devRRect.radii(SkRRect::kLowerRight_Corner);
+ const SkVector& devRadiiLL = devRRect.radii(SkRRect::kLowerLeft_Corner);
+
+ const int devLeft = SkScalarCeilToInt(std::max<float>(devRadiiUL.fX, devRadiiLL.fX));
+ const int devTop = SkScalarCeilToInt(std::max<float>(devRadiiUL.fY, devRadiiUR.fY));
+ const int devRight = SkScalarCeilToInt(std::max<float>(devRadiiUR.fX, devRadiiLR.fX));
+ const int devBot = SkScalarCeilToInt(std::max<float>(devRadiiLL.fY, devRadiiLR.fY));
+
+ // This is a conservative check for nine-patchability.
+ const SkRect& devOrig = devRRect.getBounds();
+ if (devOrig.fLeft + devLeft + devBlurRadius >= devOrig.fRight - devRight - devBlurRadius ||
+ devOrig.fTop + devTop + devBlurRadius >= devOrig.fBottom - devBot - devBlurRadius) {
+ return std::nullopt;
+ }
+
+ const int newRRWidth = 2 * devBlurRadius + devLeft + devRight + 1;
+ const int newRRHeight = 2 * devBlurRadius + devTop + devBot + 1;
+
+ const SkRect newRect = SkRect::MakeXYWH(SkIntToScalar(devBlurRadius),
+ SkIntToScalar(devBlurRadius),
+ SkIntToScalar(newRRWidth),
+ SkIntToScalar(newRRHeight));
+ SkVector newRadii[4];
+ newRadii[0] = {SkScalarCeilToScalar(devRadiiUL.fX), SkScalarCeilToScalar(devRadiiUL.fY)};
+ newRadii[1] = {SkScalarCeilToScalar(devRadiiUR.fX), SkScalarCeilToScalar(devRadiiUR.fY)};
+ newRadii[2] = {SkScalarCeilToScalar(devRadiiLR.fX), SkScalarCeilToScalar(devRadiiLR.fY)};
+ newRadii[3] = {SkScalarCeilToScalar(devRadiiLL.fX), SkScalarCeilToScalar(devRadiiLL.fY)};
+
+ SkRRect rrectToDraw;
+ rrectToDraw.setRectRadii(newRect, newRadii);
+ const SkISize dimensions =
+ SkISize::Make(newRRWidth + 2 * devBlurRadius, newRRHeight + 2 * devBlurRadius);
+ SkBitmap ninePatchBitmap = skgpu::CreateRRectBlurMask(rrectToDraw, dimensions, devSigma);
+ if (ninePatchBitmap.empty()) {
+ return std::nullopt;
+ }
+
+ sk_sp<TextureProxy> ninePatch = RecorderPriv::CreateCachedProxy(recorder, ninePatchBitmap);
+ if (!ninePatch) {
+ return std::nullopt;
+ }
+
+ const float blurRadius = 3.0f * SkScalarCeilToScalar(devSigma - 1.0f / 6.0f);
+ const float edgeSize = 2.0f * blurRadius + SkRRectPriv::GetSimpleRadii(devRRect).fX + 0.5f;
+ const Rect shapeData = devRRect.rect().makeOutset(blurRadius, blurRadius);
+
+ // Determine how much to outset the draw bounds to ensure we hit pixels within 3*sigma.
+ std::optional<Rect> drawBounds = outset_bounds(localToDevice, devSigma, srcRRect.rect());
+ if (!drawBounds) {
+ return std::nullopt;
+ }
+
+ constexpr float kUnusedBlurData = 0.0f;
+ return AnalyticBlurMask(*drawBounds,
+ SkM44(),
+ ShapeType::kRRect,
+ shapeData,
+ {edgeSize, kUnusedBlurData},
+ ninePatch);
+}
+
} // namespace skgpu::graphite
diff --git a/src/gpu/graphite/geom/AnalyticBlurMask.h b/src/gpu/graphite/geom/AnalyticBlurMask.h
index 762abbb..d6a8b85 100644
--- a/src/gpu/graphite/geom/AnalyticBlurMask.h
+++ b/src/gpu/graphite/geom/AnalyticBlurMask.h
@@ -52,24 +52,21 @@
const SkM44& deviceToScaledShape() const { return fDevToScaledShape; }
const Rect& shapeData() const { return fShapeData; }
ShapeType shapeType() const { return fShapeType; }
- bool isFast() const { return fIsFast; }
- float invSixSigma() const { return fInvSixSigma; }
+ const SkV2& blurData() const { return fBlurData; }
sk_sp<TextureProxy> refProxy() const { return fProxy; }
private:
AnalyticBlurMask(const Rect& drawBounds,
const SkM44& devToScaledShape,
- const Rect& shapeData,
ShapeType shapeType,
- bool isFast,
- float invSixSigma,
+ const Rect& shapeData,
+ const SkV2& blurData,
sk_sp<TextureProxy> proxy)
: fDrawBounds(drawBounds)
, fDevToScaledShape(devToScaledShape)
, fShapeData(shapeData)
+ , fBlurData(blurData)
, fShapeType(shapeType)
- , fIsFast(isFast)
- , fInvSixSigma(invSixSigma)
, fProxy(std::move(proxy)) {}
static std::optional<AnalyticBlurMask> MakeRect(Recorder*,
@@ -83,6 +80,12 @@
const SkRect& srcRect,
const SkRect& devRect);
+ static std::optional<AnalyticBlurMask> MakeRRect(Recorder* recorder,
+ const SkMatrix& localToDevice,
+ float devSigma,
+ const SkRRect& srcRRect,
+ const SkRRect& devRRect);
+
// Draw bounds in local space.
Rect fDrawBounds;
@@ -94,9 +97,19 @@
// fDevToScaledShape.
Rect fShapeData;
+ // "fBlurData" holds different data depending on the shape type, for the unique needs of the
+ // shape types' respective shaders.
+ // In the rectangle case, it holds:
+ // x = a boolean indicating whether we can use a fast path for sampling the blur integral
+ // because the rectangle is larger than 6*sigma in both dimensions, and
+ // y = the value "1 / (6*sigma)".
+ // In the rounded rectangle case, it holds:
+ // x = the size of the blurred edge, defined as "2*blurRadius + cornerRadius", and
+ // y is unused.
+ // In the circle case, this data is unused.
+ SkV2 fBlurData;
+
ShapeType fShapeType;
- bool fIsFast;
- float fInvSixSigma;
sk_sp<TextureProxy> fProxy;
};
diff --git a/src/gpu/graphite/render/AnalyticBlurRenderStep.cpp b/src/gpu/graphite/render/AnalyticBlurRenderStep.cpp
index 7699661..9abdc89 100644
--- a/src/gpu/graphite/render/AnalyticBlurRenderStep.cpp
+++ b/src/gpu/graphite/render/AnalyticBlurRenderStep.cpp
@@ -23,10 +23,9 @@
{{"localToDevice", SkSLType::kFloat4x4},
{"deviceToScaledShape", SkSLType::kFloat3x3},
{"shapeData", SkSLType::kFloat4},
- {"depth", SkSLType::kFloat},
+ {"blurData", SkSLType::kHalf2},
{"shapeType", SkSLType::kInt},
- {"isFast", SkSLType::kInt},
- {"invSixSigma", SkSLType::kHalf}},
+ {"depth", SkSLType::kFloat}},
PrimitiveType::kTriangleStrip,
kDirectDepthGreaterPass,
/*vertexAttrs=*/
@@ -54,9 +53,8 @@
const char* AnalyticBlurRenderStep::fragmentCoverageSkSL() const {
return "outputCoverage = blur_coverage_fn(scaledShapeCoords, "
"shapeData, "
+ "blurData, "
"shapeType, "
- "isFast, "
- "invSixSigma, "
"s);";
}
@@ -80,10 +78,9 @@
const AnalyticBlurMask& blur = params.geometry().analyticBlurMask();
gatherer->write(blur.deviceToScaledShape().asM33());
gatherer->write(blur.shapeData().asSkRect());
- gatherer->write(params.order().depthAsFloat());
+ gatherer->writeHalf(blur.blurData());
gatherer->write(static_cast<int>(blur.shapeType()));
- gatherer->write(blur.isFast());
- gatherer->writeHalf(blur.invSixSigma());
+ gatherer->write(params.order().depthAsFloat());
SkSamplingOptions samplingOptions = blur.shapeType() == AnalyticBlurMask::ShapeType::kRect
? SkFilterMode::kLinear
diff --git a/src/sksl/generated/sksl_graphite_frag.minified.sksl b/src/sksl/generated/sksl_graphite_frag.minified.sksl
index d5f3f27..bafd296 100644
--- a/src/sksl/generated/sksl_graphite_frag.minified.sksl
+++ b/src/sksl/generated/sksl_graphite_frag.minified.sksl
@@ -213,13 +213,17 @@
"(k,h,f,c,d,e);float n=min(g.y,0.)*a.w;float o=l*(min(k.x+n,-k.y)+m);return half4"
"(half(saturate(o)));}}$pure half4 per_edge_aa_quad_coverage_fn(float4 a,float4"
" b){float2 d=min(b.xy,b.zw);float e=min(d.x,d.y)*a.w;return half4(half(saturate"
-"(e)));}$pure half4 $G(float2 a,float4 b,int c,half d,sampler2D e){half f;half"
-" g;if(bool(c)){half2 h=max(half2(b.xy-a),half2(a-b.zw));f=sample(e,float2(float"
+"(e)));}$pure half4 $G(float2 a,float4 b,half c,half d,sampler2D e){half f;half"
+" g;if(c!=0.){half2 h=max(half2(b.xy-a),half2(a-b.zw));f=sample(e,float2(float"
"(d*h.x),.5)).x;g=sample(e,float2(float(d*h.y),.5)).x;}else{half4 h=half4(half2"
"(b.xy-a),half2(a-b.zw));f=(1.-sample(e,float2(float(d*h.x),.5)).x)-sample(e"
",float2(float(d*h.z),.5)).x;g=(1.-sample(e,float2(float(d*h.y),.5)).x)-sample"
"(e,float2(float(d*h.w),.5)).x;}return half4(f*g);}$pure half4 $H(float2 a,float4"
" b,sampler2D c){float d=b.z;float e=b.w;half2 f=half2((a-b.xy)*d);float g=float"
-"(length(f))-e;return sample(c,float2(g,.5)).xxxx;}$pure half4 blur_coverage_fn"
-"(float2 a,float4 b,int c,int d,half e,sampler2D f){switch(c){case 0:{return"
-" $G(a,b,d,e,f);}case 2:{return $H(a,b,f);}}return half4(0.);}";
+"(length(f))-e;return sample(c,float2(g,.5)).xxxx;}$pure half4 $I(float2 a,float4"
+" b,half c,sampler2D d){float2 e=a-b.xy;float2 f=(b.zw-b.xy)*.5;e-=f;half2 g"
+"=half2(sign(e));e=abs(e);half2 h=half2(e-(f-float(c)));h=max(h,0.);h*=g;h+="
+"half2(c);half2 i=half2(2.*c);half2 j=h/i;return sample(d,float2(j)).xxxx;}$pure"
+" half4 blur_coverage_fn(float2 a,float4 b,half2 c,int d,sampler2D e){switch"
+"(d){case 0:{return $G(a,b,c.x,c.y,e);}case 2:{return $H(a,b,e);}case 1:{return"
+" $I(a,b,c.x,e);}}return half4(0.);}";
diff --git a/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl b/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl
index 7031e57..00f374a 100644
--- a/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl
+++ b/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl
@@ -382,9 +382,9 @@
")));}}$pure half4 per_edge_aa_quad_coverage_fn(float4 coords,float4 edgeDistances"
"){float2 outerDist=min(edgeDistances.xy,edgeDistances.zw);float c=min(outerDist"
".x,outerDist.y)*coords.w;return half4(half(saturate(c)));}$pure half4 $rect_blur_coverage_fn"
-"(float2 coords,float4 rect,int isFast,half invSixSigma,sampler2D integral){"
-"half xCoverage;half yCoverage;if(bool(isFast)){half2 pos=max(half2(rect.xy-"
-"coords),half2(coords-rect.zw));xCoverage=sample(integral,float2(float(invSixSigma"
+"(float2 coords,float4 rect,half isFast,half invSixSigma,sampler2D integral)"
+"{half xCoverage;half yCoverage;if(isFast!=0.){half2 pos=max(half2(rect.xy-coords"
+"),half2(coords-rect.zw));xCoverage=sample(integral,float2(float(invSixSigma"
"*pos.x),.5)).x;yCoverage=sample(integral,float2(float(invSixSigma*pos.y),.5"
")).x;}else{half4 rect=half4(half2(rect.xy-coords),half2(coords-rect.zw));xCoverage"
"=(1.-sample(integral,float2(float(invSixSigma*rect.x),.5)).x)-sample(integral"
@@ -394,8 +394,17 @@
"(float2 coords,float4 circle,sampler2D blurProfile){float invTextureRadius="
"circle.z;float normSolidRadius=circle.w;half2 vec=half2((coords-circle.xy)*"
"invTextureRadius);float dist=float(length(vec))-normSolidRadius;return sample"
-"(blurProfile,float2(dist,.5)).xxxx;}$pure half4 blur_coverage_fn(float2 coords"
-",float4 shapeData,int shapeType,int isFast,half invSixSigma,sampler2D s){switch"
-"(shapeType){case 0:{return $rect_blur_coverage_fn(coords,shapeData,isFast,invSixSigma"
-",s);}case 2:{return $circle_blur_coverage_fn(coords,shapeData,s);}}return half4"
-"(0.);}";
+"(blurProfile,float2(dist,.5)).xxxx;}$pure half4 $rrect_blur_coverage_fn(float2"
+" coords,float4 proxyRect,half edgeSize,sampler2D ninePatch){float2 translatedFragPosFloat"
+"=coords-proxyRect.xy;float2 proxyCenter=(proxyRect.zw-proxyRect.xy)*.5;translatedFragPosFloat"
+"-=proxyCenter;half2 fragDirection=half2(sign(translatedFragPosFloat));translatedFragPosFloat"
+"=abs(translatedFragPosFloat);half2 translatedFragPosHalf=half2(translatedFragPosFloat"
+"-(proxyCenter-float(edgeSize)));translatedFragPosHalf=max(translatedFragPosHalf"
+",0.);translatedFragPosHalf*=fragDirection;translatedFragPosHalf+=half2(edgeSize"
+");half2 proxyDims=half2(2.*edgeSize);half2 texCoord=translatedFragPosHalf/proxyDims"
+";return sample(ninePatch,float2(texCoord)).xxxx;}$pure half4 blur_coverage_fn"
+"(float2 coords,float4 shapeData,half2 blurData,int shapeType,sampler2D s){switch"
+"(shapeType){case 0:{return $rect_blur_coverage_fn(coords,shapeData,blurData"
+".x,blurData.y,s);}case 2:{return $circle_blur_coverage_fn(coords,shapeData,"
+"s);}case 1:{return $rrect_blur_coverage_fn(coords,shapeData,blurData.x,s);}"
+"}return half4(0.);}";
diff --git a/src/sksl/sksl_graphite_frag.sksl b/src/sksl/sksl_graphite_frag.sksl
index 7914fde..f04f2cc 100644
--- a/src/sksl/sksl_graphite_frag.sksl
+++ b/src/sksl/sksl_graphite_frag.sksl
@@ -1439,13 +1439,13 @@
$pure half4 $rect_blur_coverage_fn(float2 coords,
float4 rect,
- int isFast,
+ half isFast,
half invSixSigma,
sampler2D integral) {
half xCoverage;
half yCoverage;
- if (bool(isFast)) {
+ if (isFast != 0.0) {
// Get the smaller of the signed distance from the frag coord to the left and right
// edges and similar for y.
// The integral texture goes "backwards" (from 3*sigma to -3*sigma), So, the below
@@ -1494,20 +1494,63 @@
return sample(blurProfile, float2(dist, 0.5)).rrrr;
}
+$pure half4 $rrect_blur_coverage_fn(float2 coords,
+ float4 proxyRect,
+ half edgeSize,
+ sampler2D ninePatch) {
+ // Warp the fragment position to the appropriate part of the 9-patch blur texture by
+ // snipping out the middle section of the proxy rect.
+ float2 translatedFragPosFloat = coords - proxyRect.LT;
+ float2 proxyCenter = (proxyRect.RB - proxyRect.LT) * 0.5;
+
+ // Position the fragment so that (0, 0) marks the center of the proxy rectangle.
+ // Negative coordinates are on the left/top side and positive numbers are on the
+ // right/bottom.
+ translatedFragPosFloat -= proxyCenter;
+
+ // Temporarily strip off the fragment's sign. x/y are now strictly increasing as we
+ // move away from the center.
+ half2 fragDirection = half2(sign(translatedFragPosFloat));
+ translatedFragPosFloat = abs(translatedFragPosFloat);
+
+ // Our goal is to snip out the "middle section" of the proxy rect (everything but the
+ // edge). We've repositioned our fragment position so that (0, 0) is the centerpoint
+ // and x/y are always positive, so we can subtract here and interpret negative results
+ // as being within the middle section.
+ half2 translatedFragPosHalf = half2(translatedFragPosFloat - (proxyCenter - edgeSize));
+
+ // Remove the middle section by clamping to zero.
+ translatedFragPosHalf = max(translatedFragPosHalf, 0);
+
+ // Reapply the fragment's sign, so that negative coordinates once again mean left/top
+ // side and positive means bottom/right side.
+ translatedFragPosHalf *= fragDirection;
+
+ // Offset the fragment so that (0, 0) marks the upper-left again, instead of the center
+ // point.
+ translatedFragPosHalf += half2(edgeSize);
+
+ half2 proxyDims = half2(2.0 * edgeSize);
+ half2 texCoord = translatedFragPosHalf / proxyDims;
+
+ return sample(ninePatch, texCoord).rrrr;
+}
+
$pure half4 blur_coverage_fn(float2 coords,
float4 shapeData,
+ half2 blurData,
int shapeType,
- int isFast,
- half invSixSigma,
sampler2D s) {
switch (shapeType) {
case $kShapeTypeRect: {
- return $rect_blur_coverage_fn(coords, shapeData, isFast, invSixSigma, s);
+ return $rect_blur_coverage_fn(coords, shapeData, blurData.x, blurData.y, s);
}
case $kShapeTypeCircle: {
return $circle_blur_coverage_fn(coords, shapeData, s);
}
- // TODO(b/238762890) Handle $kShapeTypeRRect.
+ case $kShapeTypeRRect: {
+ return $rrect_blur_coverage_fn(coords, shapeData, blurData.x, s);
+ }
}
return half4(0);
}