-
Notifications
You must be signed in to change notification settings - Fork 6k
[Impeller] Use a squircle-sdf-based algorithm for fast blurs #55604
Conversation
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group. |
Here is the golden changes: https://flutter-engine-gold.skia.org/search?issue=55604&crs=github&patchsets=2&corpus=flutter-engine. It looks really close. Here is one of the tests that show the oval not working though (see green shapes): ![]() |
I was chatting with jonah in office. He was saying for ovals we should maybe just fall back to the full gaussian blur and drop the old rrect_blur completely (instead of keeping it around for ovals). |
@flar I can't remember where you asked but we do have a rrect blur benchmark in flutter/dev/benchmarks/macrobenchmarks, it was added here: flutter/flutter#149261 |
There are artifacts in the benchmark too, this is probably the same issue we see above with the small sigma. sweepartifact.mov |
The render problem seems to happen around sigma=0.57 and I believe it is because of
In
|
Funny thing though is there is a bug in skia like the one I had to find for rendering small sigmas. |
Skia issue filed: https://g-issues.skia.org/issues/371571787 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
@jonahwilliams do you want to give this another look? I've contributed to it quite a bit so might be good to have @flar look over my additions or another person. I tried to avoid touching jims code as much as possible. I made the following changes:
|
I'm not sure what is going on with the golden images. They aren't showing up yet but I think they should have been processed. I looked at previous versions of this PR and they all looked good to me except when the corner radii are not equal, of course. |
Taking a look |
They bot won't ding until Mac mac_unopt finished. Otherwise maybe they are identical? |
Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change). If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review. |
A suggestion based on @gaaclarke 's work to vectorize the previous blur: diff --git a/impeller/entity/shaders/rrect_blur.frag b/impeller/entity/shaders/rrect_blur.frag
index d0bba43561..c5368daf6a 100644
--- a/impeller/entity/shaders/rrect_blur.frag
+++ b/impeller/entity/shaders/rrect_blur.frag
@@ -12,7 +12,7 @@ precision highp float;
#include <impeller/types.glsl>
uniform FragInfo {
- f16vec4 color;
+ vec4 color;
vec2 center;
vec2 adjust;
float minEdge;
@@ -26,7 +26,7 @@ frag_info;
in vec2 v_position;
-out f16vec4 frag_color;
+out vec4 frag_color;
const float kTwoOverSqrtPi = 2.0 / sqrt(3.1415926);
@@ -34,19 +34,17 @@ float maxXY(vec2 v) {
return max(v.x, v.y);
}
-// use crate::math::compute_erf7;
-float computeErf7(float x) {
- x *= kTwoOverSqrtPi;
- float xx = x * x;
- x = x + (0.24295 + (0.03395 + 0.0104 * xx) * xx) * (x * xx);
+vec2 computeErf7(vec2 x) {
+ x *= vec2(kTwoOverSqrtPi);
+ vec2 xx = x * x;
+ x = x + (vec2(0.24295) + (vec2(0.03395) + vec2(0.0104) * xx) * xx) * (x * xx);
return x / sqrt(1.0 + x * x);
}
// The length formula, but with an exponent other than 2
float powerDistance(vec2 p) {
- float xp = pow(p.x, frag_info.exponent);
- float yp = pow(p.y, frag_info.exponent);
- return pow(xp + yp, frag_info.exponentInv);
+ vec2 pp = pow(p, vec2(frag_info.exponent));
+ return pow(pp.x + pp.y, frag_info.exponentInv);
}
void main() {
@@ -55,9 +53,10 @@ void main() {
float dPos = powerDistance(max(adjusted, 0.0));
float dNeg = min(maxXY(adjusted), 0.0);
float d = dPos + dNeg - frag_info.r1;
+
+ vec2 erfs = computeErf7(vec2(frag_info.sInv * (frag_info.minEdge + d), frag_info.sInv * d));
float z =
- frag_info.scale * (computeErf7(frag_info.sInv * (frag_info.minEdge + d)) -
- computeErf7(frag_info.sInv * d));
+ frag_info.scale * (erfs.x - erfs.y);
- frag_color = frag_info.color * float16_t(z);
+ frag_color = frag_info.color * z;
} |
@@ -498,6 +498,11 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, | |||
return false; | |||
} | |||
|
|||
// The current rrect blur math doesn't work on ovals. | |||
if (fabsf(corner_radii.width - corner_radii.height) > kEhCloseEnough) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding this case you could also adjust the logic in IsNearlySimpleRRect to force all corner radii to be equal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that would also eliminate fast tessellation of simple rrects. The only deficiency here is that we can no longer fast_blur them so it's a test specific to this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agh, yes don't do that then! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Jim and I were looking at this a bit yesterday and I poked around a bit this morning. We should do this in a follow up PR to establish this as the baseline. |
Another optimization is that we don't need to subtract the center inside the shader, that should be done by changing the initial mapping of the coordinates, then the shader just needs to do Actually, if we were willing to emit 4 quads then we could do that |
I would do the subtraction in the vertex shader, that would only execute ~4 times or so. But the subtraction is likely not going to add much overhead |
That's not needed, you just have to change 4 values in the C++ code and all of the coordinates will be correct without any more work in the vertex shader. Right now it has to do math to make everything relative to the rect origin, just swap in rect center instead and it's good to go. Could the vertex shader convert the 1 quad into 4 to do the quadrant slicing? Actually if it could slice it (1 -> 9) then we could get rid of the abs, the |
You could slice up the geometry however you'd like |
If you want data to vary per vertex or group of vertexes you'd usually do that by defining a new varying instead of using a uniform. |
So I tried to vectorize this and tried some other tricks. Vectorization didn't pay off. The patch above actually makes things slower, but for a large part of the output space I tried to branch ( For reference, our exponents appear to be in the range [2, 3.5]. --- a/impeller/entity/shaders/rrect_blur.frag
+++ b/impeller/entity/shaders/rrect_blur.frag
@@ -44,9 +44,13 @@ float computeErf7(float x) {
// The length formula, but with an exponent other than 2
float powerDistance(vec2 p) {
- float xp = pow(p.x, frag_info.exponent);
- float yp = pow(p.y, frag_info.exponent);
- return pow(xp + yp, frag_info.exponentInv);
+ return length(p);
} |
You could also use specialization constants to avoid having a separate shader but those only exist in vulkan I think. |
spec constants are supported for all backends. I would not pursue a specialization constant for this. |
What's your take in general on trying to get that 15% boost on circles and really blurry rounded rects? I know adding shaders has a maintenance and startup time cost. |
Doesn't seem worthwhile |
…156252) flutter/engine@eece6c3...bc5f9fc 2024-10-04 flar@google.com [Impeller] Use a squircle-sdf-based algorithm for fast blurs (flutter/engine#55604) 2024-10-04 skia-flutter-autoroll@skia.org Roll Skia from 59f512b47cc0 to 4aff9603622d (1 revision) (flutter/engine#55663) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Please CC matanl@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
For future reference I measured with the fragment shader just setting a solid color and the GPU time was 1.6ms, so that's the floor. Considering that; the
I'm fine with that estimation, we can always revisit if we need to. |
Ah, I see, you are suggesting testing the constant to see if it happens to be "2", how often is that the case? |
The exponent is 2 when:
So, about half of the output space. |
44% faster than the old rrect_blur, 37% faster than the vectorized rrect_blur. |
This is a phenomenal improvement - Great work @flar + @gaaclarke ! |
…/engine#55604) Use a Squircle Signed Distance Field based algorithm to do a very fast approximation of rrect blurs. This PR is to provide reference within the team to discuss the future of the algorithm compared to the Gaussian approximation functions that are currently in use. It isn't a complete solution, but can be completed easily with a little more work. Notably, it doesn't handle elliptical round rects, only circular corners. Could stand to include an attribution to the source (https://raphlinus.github.io/graphics/2020/04/21/blurred-rounded-rects.html)
Use a Squircle Signed Distance Field based algorithm to do a very fast approximation of rrect blurs.
This PR is to provide reference within the team to discuss the future of the algorithm compared to the Gaussian approximation functions that are currently in use. It isn't a complete solution, but can be completed easily with a little more work.
Notably, it doesn't handle elliptical round rects, only circular corners.
Could stand to include an attribution to the source (https://raphlinus.github.io/graphics/2020/04/21/blurred-rounded-rects.html)