Add oval drawing support to the SDF uber shader#184903
Conversation
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request adds support for rendering ovals using Signed Distance Fields (SDFs) in Impeller, with changes spanning the canvas, parameter structures, and fragment shaders. Review feedback identifies a potential division by zero in the distanceFromOval shader function, recommends using a final else block in strokedSDF to prevent uninitialized variables, and notes a style guide violation where documentation for the public size member should use triple-slash comments.
There was a problem hiding this comment.
Code Review
This pull request introduces support for rendering ovals via the Uber SDF path in Impeller. The changes include adding oval-specific parameters, implementing a new distance function in the uber_sdf.frag shader, and updating the canvas logic to utilize these features when the SDF flag is enabled. Review feedback identified a critical numerical instability and potential division-by-zero in the shader's oval distance calculation, recommending a more robust vector-based approach. Additionally, it was noted that the canvas logic should explicitly check for the absence of a color source, as the current SDF implementation only supports solid colors.
b-luk
left a comment
There was a problem hiding this comment.
The SDF doesn't actually find the minimum distance to the oval, it find the distance from the test point to the center of the oval along a straight line - visually looks good though.
There's an ellipse SDF function at https://iquilezles.org/articles/distfunctions2d/. It's much more complicated than I initially would have thought. But apparently it's very complicated to get a true SDF for an ellipse. iquilezles has an entire article just about this: https://iquilezles.org/articles/ellipsedist/.
I made a shadertoy demo that compares your function to the one from iquilezles: https://www.shadertoy.com/view/7ffXDl. It switches between the two every second.
Comparing your function to the true SDF, the contours in the interior of your SDF bulge inwards towards the center at angles close to the minor axis. And on the exterior of the SDF, the contours are more diamond-shaped: at angles near the axes the contours are more sharply rounded, and at angles near the diagonals the contours are flatter. I think these distortions would show up if you draw an oval with a wide stroke width.
Interestingly, the true SDF has contours with sharp angles in the interior, along the long axis. That's not what I'd initially think the SDF would look like, but it makes sense after thinking about it for a while. This means that the interior edge of a stroked oval would have sharp corners rather than being a smooth curve, especially as the stroke width gets larger. Testing this out with a flutter app verifies that this matches what is rendered: https://dartpad.dev/?id=36e089005ba8bc1e2cefd4fc4ad7ca93
| float m2 = m * m; | ||
|
|
||
| // q is the point along that line that intersects the oval. | ||
| vec2 q = vec2(a * b / sqrt((a2 * m2) + b2), a * b * m / sqrt((a2 * m2) + b2)); |
There was a problem hiding this comment.
This math is hard for me to follow. Maybe break it down a bit more?
There was a problem hiding this comment.
I added some more comments
|
Actually, looking at how this change draws stroked ovals, it draws the interior between two ovals with different sizes: So it doesn't actually use the SDF contours for drawing the stroke. So I don't think you'd see the distortions I mentioned in my previous comment. But I do think using this SDF for ovals with wide stroke widths will look significantly different from the current implementation. The SDF draws a stroked shape in which the inner and outer edges are ellipses. But with a real stroked ellipse, its inner and outer edges are not ellipses. |
|
Here's a comparison app showing the current drawOval vs a simulated version of your SDF by drawing ovals with size +/- half_width: https://dartpad.dev/?id=57c1b2dc7af38a63f899fb2d258703e5. I put the canvas in an InteractiveViewer widget, so you can zoom and scroll in it. Try playing around with it. |
This is a great point that I didn't consider. I've backed this out to only operate on filled ovals, I think the stroked case will need more consideration |
Any reason we wouldn't want to just use the ellipse SDF from iquilezles? With a true SDF, the inner and outer edges of the stroked shape would just be SDF -/+ half_stroke_width. |
Both SDFs from iquilezles are approximations with different shortcomings. I don't really want to ctrl-c + ctrl-v without understanding those limitations first.
I'm not sure what you mean - you're saying to just add the stroke width to the result of the SDF? I don't think that works - we'd have to do an offset by the normal of the oval, no? |
The normal of the oval doesn't need to be considered for making it stroked. The SDF gives us the distance to the edge of the shape. So looking at the set of all points where SDF = 1, for example, would give us a shape that is exactly distance 1 outside of the base oval at every point. And at SDF = -1, we get a shape of distance 1 inside the base oval. These are exactly the outer and inner edges of stroking the base oval with stroke width 2. We get an SDF for the outer edge shape with |
|
Computing an ellipse with bounds that are +/- the stroke width doesn't work as mentioned before, but I think what @b-luk is referring to is doing a +/- on the actual distance function. I've suggested elsewhere that most stroke SDFs could be done by taking the filled value and performing |
|
Per discussion with @gaaclarke and @b-luk , I've lifted the SDF code directly from https://iquilezles.org/articles/ellipsedist and threw back in stroked rendering following Flar's comment |
flar
left a comment
There was a problem hiding this comment.
Looks fine, but some suggestions to take or leave...
| !paint.mask_blur_descriptor.has_value()) { | ||
| UberSDFParameters params; | ||
|
|
||
| if (paint.style == Paint::Style::kStroke) { |
There was a problem hiding this comment.
For simplicity we could add a std::opt<StrokeParams> getter to Paint.
There was a problem hiding this comment.
Good suggestion. Will do this in a follow up PR
| // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR | ||
| // THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| // https://www.youtube.com/c/InigoQuilez | ||
| // https://iquilezles.org |
There was a problem hiding this comment.
I think Aaron said it was acceptable to have these copyright claims in the middle of a source file, but I also like the idea of moving some of these functions to an included source file like we do with the gaussian functions. If we make one for all of these Inigo functions, we'd just need the copyright header in that included file for all of the functions attributed to them.
There was a problem hiding this comment.
Agree. Will also do this in a follow up PR
| inner = distanceFromRect(p, frag_info.size - half_stroke); | ||
| } else { | ||
| outer = distanceFromOval(p, frag_info.size) - half_stroke; | ||
| inner = distanceFromOval(p, frag_info.size) + half_stroke; |
There was a problem hiding this comment.
For this and circle and rect we could do return abs(distanceFromShape(...) - half_stroke
There was a problem hiding this comment.
Will update in a follow up PR (I think the record is scratched)
|
Took some time to understand and annotate the Inigo function. Kept the code as-is because it's really optimized. |
|
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. 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. |
Roll Flutter from c1b14e92dcfb to 31f1802cb859 (46 revisions) flutter/flutter@c1b14e9...31f1802 2026-04-16 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Run all flutter/flutter macOS tests using Xcode 26 and iOS 26 simulator (#185083)" (flutter/flutter#185145) 2026-04-16 [email protected] Add oval drawing support to the SDF uber shader (flutter/flutter#184903) 2026-04-16 [email protected] Run all flutter/flutter macOS tests using Xcode 26 and iOS 26 simulator (flutter/flutter#185083) 2026-04-16 [email protected] Roll Skia from 2c49b3f9c3c2 to 391cdbe3ffe9 (2 revisions) (flutter/flutter#185138) 2026-04-16 [email protected] Roll Dart SDK from 4ee990654146 to fbddcbe0cd96 (1 revision) (flutter/flutter#185137) 2026-04-16 [email protected] Roll Skia from f4e3cd2c2159 to 2c49b3f9c3c2 (14 revisions) (flutter/flutter#185131) 2026-04-16 [email protected] Roll Dart SDK from 87b7c87e7207 to 4ee990654146 (5 revisions) (flutter/flutter#185108) 2026-04-15 [email protected] Use the `flutteractionsbot` token to push the release branch. (flutter/flutter#184833) 2026-04-15 [email protected] Allow period characters in iOS and macOS framework names (flutter/flutter#184335) 2026-04-15 [email protected] Fix SliverResizingHeader semantic focus (flutter/flutter#179690) 2026-04-15 [email protected] ignore avoid_type_to_string lint rule in flutter_tools (flutter/flutter#184766) 2026-04-15 [email protected] Roll Skia from bda7232e6772 to f4e3cd2c2159 (4 revisions) (flutter/flutter#185063) 2026-04-15 [email protected] Add initial AI guidance for issues (flutter/flutter#184885) 2026-04-15 [email protected] Roll Fuchsia Linux SDK from rB8LAuZL_DwHMssTU... to IdBT8fSMYrYSip65j... (flutter/flutter#185064) 2026-04-15 [email protected] Fix an ordering dependency in the services/system_chrome_test.dart test suite (flutter/flutter#185086) 2026-04-15 98614782+auto-submit[bot]@users.noreply.github.com Reverts "[ios][platform_view]Reland hitTest approach (with a few 2026 update) (#183484)" (flutter/flutter#185082) 2026-04-15 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Run all flutter/flutter macOS tests using Xcode 26 and iOS 26 simulator (#179810)" (flutter/flutter#185067) 2026-04-15 [email protected] Agent rule: Dart editing (flutter/flutter#185045) 2026-04-15 [email protected] [ios][platform_view]Reland hitTest approach (with a few 2026 update) (flutter/flutter#183484) 2026-04-15 [email protected] Run all flutter/flutter macOS tests using Xcode 26 and iOS 26 simulator (flutter/flutter#179810) 2026-04-15 [email protected] Roll Dart SDK from ee5afcef0596 to 87b7c87e7207 (4 revisions) (flutter/flutter#185060) 2026-04-15 [email protected] Roll Skia from 4c382df6a25a to bda7232e6772 (7 revisions) (flutter/flutter#185057) 2026-04-15 [email protected] Remove material import from toggleable_test.dart + draggable_test.dart + obscured_animated_image_test.dart + sliver_constraints_test.dart (flutter/flutter#181774) 2026-04-15 [email protected] refactor: Remove material imports from Widget tests (flutter/flutter#184877) 2026-04-14 [email protected] Adds missing `await`s on forgotten cases (flutter/flutter#183466) 2026-04-14 [email protected] Use an if-element in a collection literal instead of a conditional expression (flutter/flutter#184830) 2026-04-14 [email protected] update popular issues documentation (flutter/flutter#183196) 2026-04-14 [email protected] [Android] Add integration test for setting engine flags via the manifest (flutter/flutter#182241) 2026-04-14 [email protected] [fuchsia] Ask for both ambient-replace and VMEX to allow for a softer transition. (flutter/flutter#185042) 2026-04-14 [email protected] Make `multiple_windows` follow repo analyzer rules (flutter/flutter#184753) 2026-04-14 [email protected] Ignore incoming deprecated_web_configuration lint (flutter/flutter#184130) 2026-04-14 [email protected] [AGP 9] Update AGP Error (flutter/flutter#185043) 2026-04-14 [email protected] Move widget_preview_scaffold into pub workspace (flutter/flutter#182627) 2026-04-14 [email protected] Fix gles interactive tests (flutter/flutter#181389) 2026-04-14 [email protected] Update customer tests.version (flutter/flutter#185044) 2026-04-14 [email protected] [SKILL] upgrade-browser (flutter/flutter#184894) 2026-04-14 [email protected] [ci] Split up integration.shard dart_data_asset_test.dart (flutter/flutter#185021) 2026-04-14 [email protected] Hold startup lock until after `pub get` to prevent races (flutter/flutter#184294) 2026-04-14 6226493[email protected] Add `--include-example` flag to `flutter clean` for package example projects (flutter/flutter#183455) 2026-04-14 [email protected] Disable multi-pack-index when calling flutter from Xcode (flutter/flutter#184998) 2026-04-14 [email protected] Fix icon tree shaking when building for desktop (flutter/flutter#184249) 2026-04-14 [email protected] Fix killing wrong xcrun command (flutter/flutter#184831) 2026-04-14 [email protected] Allow Xcode build configuration to not contain flavor name (flutter/flutter#183398) 2026-04-14 [email protected] [web] Async rendering for benchmarks (flutter/flutter#184677) 2026-04-14 [email protected] [ci] Split up integration.shard native_assets_test.dart (flutter/flutter#185020) 2026-04-14 [email protected] Skip flutter widget-preview test that times out frequently (flutter/flutter#184988) ...
Adds Oval rendering to the UberSDF shader, uses UberSDF oval rendering in the canvas behind the feature flag.
The SDF doesn't actually find the minimum distance to the oval, it find the distance from the test point to the center of the oval along a straight line - visually looks good though.
Fixes #183037.
Before this change:
After this change:

Diffs highlighted:
Pre-launch Checklist
///).