Repository navigation
uber_sdf: removes derivatives from rect path - #192267
Conversation
|
It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging. 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. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). 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. |
There was a problem hiding this comment.
Code Review
This pull request updates Impeller's SDF rendering path to disable SDF rendering under perspective transforms, refactors gradient parameter calculations to avoid shader-side divisions, and optimizes pixel size calculations in the fragment shader by passing pre-calculated values from the host. Feedback points out that a removed assertion (FML_DCHECK(gradient.texture)) should be restored in SetupGradientParameters to prevent potential null pointer dereferences when retrieving the texture size.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
| float t; | ||
| if (frag_info.color_source_type < 1.5) { | ||
| // Linear gradient | ||
| t = dot(v_position - frag_info.gradient_coords.xy, | ||
| frag_info.gradient_coords.zw) * | ||
| frag_info.inv_gradient_length; | ||
| } else { | ||
| // Radial gradient | ||
| t = length(v_position - frag_info.gradient_coords.xy) * | ||
| frag_info.inv_gradient_length; | ||
| } |
There was a problem hiding this comment.
This code re-implements the t-calculation of the existing shared IPSampleLinearGradient and IPSampleRadialGradient functions, but with some optimizations and by passing in inv_gradient_length.
Can we move these changes up to the shared IPSampleLinearGradient and IPSampleRadialGradient functions, so that the other existing linear and radial gradient texture-sampling shaders do the same thing? You may end up having to change those existing shaders to do stuff like calculating inv_gradient_length. I much prefer that option, so the logic can continue to be shared, rather than adding a divergent re-implementation of the logic here. It also means the non-ubersdf case would benefit from this change as well.
There was a problem hiding this comment.
done, pulled out shared math
| Vector2 transform_scaling = entity.GetTransform().GetBasisScaleXY(); | ||
| frag_info.pixel_size = | ||
| Point(transform_scaling.x != 0.0f ? 1.0f / transform_scaling.x : 0.0f, | ||
| transform_scaling.y != 0.0f ? 1.0f / transform_scaling.y : 0.0f); |
There was a problem hiding this comment.
Using GetBasisScaleXY to get the pixel size in local space works if the transformed x and y axes remain at 90 degrees (scaling and rotation). But I think it will not be accurate if the transformed axes are not at 90 degrees (skews). You can see this playing around with AiksTest.PrimitiveShapePlayground
There was a problem hiding this comment.
Based on how big your screenshot is, I believe you're running this on a screen with high dpi scaling. If you run it with non-scaled native resolution, I think it will match my screenshots.
There was a problem hiding this comment.
done, fixed the math to consider the determinate for skews
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
f237bbe to
e775588
Compare
| frag_info.gradient_coords = Vector4(); | ||
| frag_info.half_texel = 0.0f; | ||
| frag_info.tile_mode = 0.0f; | ||
| frag_info.inv_gradient_length = 0.0f; |
There was a problem hiding this comment.
Can these be omitted, if they are the default values?
If they can't be omitted, maybe put it in the "else" branch of "if (params_.gradient)" so it's clear that this is mutually exclusive with these fields getting set by SetupGradientParameters.
There was a problem hiding this comment.
They can't be omitted, they aren't the default values. The struct that defines them is autogenerated code, so we can't override that.
There was a problem hiding this comment.
Sorry, didn't respond to the second suggestion. I think it's best to keep some consistent value in there instead of junk values.
| /// pixel_size.x = ||\nabla x|| = ||Basis Y 2D|| / |det 2D| | ||
| /// pixel_size.y = ||\nabla y|| = ||Basis X 2D|| / |det 2D| |
There was a problem hiding this comment.
What is "\nabla"? edit: it's how to tell LaTeX to write the "∇" symbol. Maybe we can just use the ∇ symbol directly?
I don't know enough about math to know if the math in this function is correct. But if the unit tests and golden tests look good then I guess it LGTM.
There was a problem hiding this comment.
Nabla is the name of the symbol. It's best to keep thing in ASCII characters for compatibility with compilers. I think in practice it probably doesn't matter for the compilers we are using but doing this doesn't seem problematic enough to add a potential friction point.
The intuition you need to understand the math is:
- The area of a parallelogram defined by an affine transform is (det M)
- The area of a parallelogram is Base*height
- We have the area, and the lengths of the sides, we use that to get the axis aligned size of the pixels.
There was a problem hiding this comment.
If you want the height then you need area/base, but you are computing base/area. I don't think base/area has any geometric meaning, does it?
There was a problem hiding this comment.
(Similarly if you want base then you need area/height, not height/area.)
There was a problem hiding this comment.
Yea, it's the inverse which is what we want here.
|
@b-luk sorry, i had to update malioc. The results are better than previously. I thought i had updated them already. |
| if (paint.mask_blur_descriptor.has_value()) { | ||
| return false; | ||
| } | ||
| if (transform.HasPerspective()) { |
There was a problem hiding this comment.
Alternatively I've been thinking it might make sense to treat perspective as "distorted affine rendering". During a 3D flip transition I think the developer might want the flip to be performant rather than pixel perfect.
There was a problem hiding this comment.
I was thinking we could make a different uber_sdf shader that uses derivatives for those. It didn't seem a high priority to address.
|
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
A potential key to why the math isn't mathing is that you probably want the basis vector lengths of the inverted matrix. A matrix inversion divides by the determinant, but it also swizzles the main 2x2 entries from: | a b | to | d -b | So, GetBasisX2D() is (m[0], m[1]) and GetBasisY2D() is (m[4], m[5]) but you'd want to use the vectors (m[5], -m[1]) and (-m[4], m[0]). (Some liberty may be taken by avoiding the negations if you are just squaring the value.) Hmmm, on the other hand, the code in the shader was mixing and matching the dFdx and dFdy vector elements so that also comes into play... |
|
Why only change rect rendering? |
To make the PR simple. Every change requires a bit of fiddling to make sure the uniform register count is down. |
I believe the bad hairline stroking is due to uber_sdf.frag calculating pixel size for stroked shapes using I think for stroked shapes, we can simply use the pixel size of the base filled shape and it should just work. Testing this out: Patchdiff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag
index 1bcb66c42a0..eec8ac378c0 100644
--- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag
+++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag
@@ -307,19 +307,17 @@ vec2 filledSDF(vec2 p) {
vec2 strokedSDF(vec2 p) {
vec2 base_sdf_and_pixel_size = filledSDF(p);
float base_sdf = base_sdf_and_pixel_size.x;
- float base_pixel_size = base_sdf_and_pixel_size.y;
+ float pixel_size = base_sdf_and_pixel_size.y;
- float half_stroke = max(frag_info.stroke_width, base_pixel_size) * 0.5;
+ float half_stroke = max(frag_info.stroke_width, pixel_size) * 0.5;
float sdf;
- float pixel_size;
if (frag_info.type >= 0.5 && frag_info.type < 1.5 &&
frag_info.stroke_join < 0.5) {
// Rect with Miter join
float outer = distanceFromRect(p, frag_info.size + half_stroke);
float inner = base_sdf + half_stroke;
sdf = max(outer, -inner);
- pixel_size = pixelSize(sdf);
} else if (frag_info.type >= 0.5 && frag_info.type < 1.5 &&
frag_info.stroke_join >= 0.5 && frag_info.stroke_join < 1.5) {
// Rect with Bevel join
@@ -327,13 +325,11 @@ vec2 strokedSDF(vec2 p) {
distanceFromChamferRect(p, frag_info.size + half_stroke, half_stroke);
float inner = base_sdf + half_stroke;
sdf = max(outer, -inner);
- pixel_size = pixelSize(sdf);
} else {
// All other shapes
vec2 sdf_and_pixel_size =
- SDFStroke(base_sdf, base_pixel_size, frag_info.stroke_width);
+ SDFStroke(base_sdf, pixel_size, frag_info.stroke_width);
sdf = sdf_and_pixel_size.x;
- pixel_size = sdf_and_pixel_size.y;
}
return vec2(sdf, pixel_size);
}Screenshots Before/After 4x zoom on left stroke: I'll create a PR to fix this. I'll wait until after this current PR is merged to create my PR in order to avoid merge conflicts. |
|
OK, so combining the fact that the dFdxy version didn't use the vectors directly, but swapped their values around then I can see that the current implementation of the GetPixelSize function is doing something similar, but both are wrong. Also "GetPixelSize" is a misleading name for the method as it should be "GetThoseValuesWeWereUsingInTheUberSDFShader" or something that is closer to that than "PixelSize". I don't see how this PR would make the results worse, but I don't like it introducing a method that implies a return value that it isn't really delivering. And if we are happier with Benson's results, why not just go directly to that math rather than merging this method with a poorly named method? |
Maybe it's not the perfect name, but it does match the nomenclature used in uber_sdf, so it's probably better to stick with that for now.
Benson's fix is something that can be applied on either HEAD or this PR. It addresses a whole different issue that is mutually exclusive from this change. We'll land his fix after this to avoid merge conflicts (but technically it could have landed on HEAD). |
flar
left a comment
There was a problem hiding this comment.
Seems to maintain status quo without the dFdxy functions...
See flutter#192267 (comment): We get bad hairline stroking due to uber_sdf.frag calculating pixel size for stroked shapes using `pixelSize(sdf)` [here](https://github.com/flutter/flutter/blob/63b9518f16bc0548f0d9daeaa3966ffcb0a7e28b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag#L322). `pixelSize(sdf)` can give incorrect results when dealing with shapes/strokes with width that is close to or less than 1 pixel (due to resolution limits of the `dFdx(sdf), dFdy(sdf)` that it uses). For stroked shapes, we can simply use the pixel size of the base filled shape. Fixes flutter#192658 Added a new golden test that draws the shape from those comments. ### Before: <img width="1024" height="768" alt="impeller_Play_AiksTest_CanRenderTransformedRectWithNearVerticalEdgeHairline_MetalSDF" src="/sitelet?url=https%3A%2F%2Fgithub.com%2Fflutter%2Fflutter%2Fpull%2F%253Ca%2520href%3D"/sitelet?url=https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fc80c13ce-cc8d-4639-a806-e128981f8fa6">https://github.com/user-attachments/assets/c80c13ce-cc8d-4639-a806-e128981f8fa6" /> ### After: <img width="1024" height="768" alt="impeller_Play_AiksTest_CanRenderTransformedRectWithNearVerticalEdgeHairline_MetalSDF" src="/sitelet?url=https%3A%2F%2Fgithub.com%2Fflutter%2Fflutter%2Fpull%2F%253Ca%2520href%3D"/sitelet?url=https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fdd0b10bf-6bb1-4c11-9367-dfdaf74a56a7">https://github.com/user-attachments/assets/dd0b10bf-6bb1-4c11-9367-dfdaf74a56a7" /> ### Zoomed in version of before/after from flutter#192267 (comment): <img width="366" height="1028" alt="image" src="/sitelet?url=https%3A%2F%2Fgithub.com%2Fflutter%2Fflutter%2Fpull%2F%253Ca%2520href%3D"/sitelet?url=https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F09f1ebf9-d14f-4b31-bc65-a545d3a3317b">https://github.com/user-attachments/assets/09f1ebf9-d14f-4b31-bc65-a545d3a3317b" /> ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [flutter/website]: https://github.com/flutter/website [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md








This removes derivative calls from the rect renderer in uber_sdf. It rejiggers uniforms for gradients as well to make space for the new uniform. It also removes uber_sdf from the codepath for rendering perspective matrices. That allows us to have a stable derivative when rendering with uber_sdf.
spawned from #192204
Testing: see malioc results for better static analysis
Local performance testing on macos where I removed the derivatives from rects, rrects and circles then drew a lot of those gave this about an 8% improvement in raster time.
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.