Handle hairline strokes in UberSDF#184895
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for hairline strokes in the Uber SDF shader and geometry. Changes include adding a helper to identify hairline strokes, updating geometry calculations to account for hairline padding, and implementing a new hairlineSDF function in the fragment shader. Feedback was provided regarding the use of float literals and type consistency in C++, as well as a typo in a shader comment.
| // AA padding. | ||
| Vector2 aa_padding = UberSDFParameters::kAntialiasPixels * device_pixel_size; | ||
| Vector2 hairline_stroke_padding = | ||
| params_.IsHairlineStroked() ? 0.5 * device_pixel_size : Point{0, 0}; |
There was a problem hiding this comment.
Use 0.5f instead of 0.5 to avoid double precision literal and potential promotion. Also, use Vector2{} for consistency with the variable type and to avoid mixing Point and Vector2 types.
| params_.IsHairlineStroked() ? 0.5 * device_pixel_size : Point{0, 0}; | |
| params_.IsHairlineStroked() ? 0.5f * device_pixel_size : Vector2{}; |
| float hairlineSDF(vec2 p) { | ||
| float base_sdf = filledSDF(p); | ||
| float pixel_size = fwidth(base_sdf); | ||
| // Harline SDF is a 1-pixel wide band around the edge of the base SDF. |
|
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. |
flar
left a comment
There was a problem hiding this comment.
This is a bad implementation. We don't special case 0.0.
The stroke should always be at least a pixel wide everywhere. The default value of 0.0 relies on that rule to produce hairlines, but the same issue would come into play if the developer specified a width of 10 and a scale of 0.0001...
There should be no "test" on the width, there should be a clamp on the smallest stroke value to be a pixel, and that clamp would be computing the width perpendicular to the shape at its location.
Look at the original Circle SDF at the work done with dFdxy and dot products to make sure we did not have a width value that went less than 1 pixel.
|
Also, does this implementation work if the scale is 0.000001? The work done elsewhere will work regardless of scale. Neither the padding nor the minimum stroke should be computed globally for the operation. What if there is a 10x scale in one direction and a 1x scale in the other? You can't pick a single width or aa pixel size that won't be stretched too much in one direction or squeezed too much in the other. This must be computed in the SDF calculations perpendicular to the outline. |
I actually did check the behavior of 0.0 vs small values (less than a device pixel). From my tests, it did seem like 0.0 was special cased to render at 1 device pixel, and would render thicker than non-zero stroke width values that are less than 1 device pixel. Flutter web example showing this: https://dartpad.dev/?id=c4c1fe8149859f9c743317820a25a3e6 I now realize why my tests showed this. I was testing with a Skia backend, not Impeller (I was testing with Mac app, not Flutter web like the link above. But both of those use Skia). I do see that Impeller does force a minimum stroke width of a device pixel, and running the linked app above with impeller enabled does show this minimum stroke width. This is different from Skia's behavior, but I think it makes sense. I'll update my PR to enforce a minimum value. |
I think it properly supports all of this. This PR modifies the padding to treat the X and Y components separately. So if there's a 10x scale in one direction and a 1x scale in the other, the padding should be properly handled in both axes. And the SDF calculation uses |
|
Ugh, I missed the X/Y scale adjustments. But I'm not sure they are the right equation. We need "width across the outline" and so I'd need to think more about the math to consider if those are the right values. WRT I like that Skia does try to represent "less than a pixel width" using ?? alpha ?? reducing coverage ?? I'm not sure. I think we also have parts of Impeller that adjust the alpha of the color when objects get small, so I'm not sure we are 100% consistent. If apps have depended on Skia for a long time then they may be used to its behavior, so is the Impeller enforcement of "always at least a pixel" really correct? We also have a year or two of app developers getting used to the Impeller rules, so perhaps we should keep going with this philosophy. Also, we've been dealing with MSAA in the past so enforcing a minimum width is almost needed to keep the width from disappearing entirely if we miss all of the sub-samples in a pixel - it's the only tool we have to bring to bear. But with SDF we have a choice to compute smaller coverages instead. I need to review more of the math here. I think we are better computing this stuff in the shader. If the code here is already doing fwidth then it is already paying the cost of dFdx/y and those directional functions plus a dot product can compute the values anyway, so the CPU adjustments are sort of unnecessary. |
|
The SDF shaders have 2 issues where we need to adjust for (relative) pixel dimensions.
One approach is that we could just have the SDF computations compute the distance to the outline. The differential of that value We can fold those 2 cases together by:
That technique doesn't work for primitive that have a join consideration, currently only rects, but it should work for other primitives. For round joins, the above results might work, but for miter and bevel we need different geometries and the regular outer - inner calculation and making sure it honors "minimum stroke width". |
…um 1-pixel width for strokes.
|
Those are good suggestions. I implemented them and I think the results are improved. I think the suggestion to change our pixel size measurement to use euclidean distance ( I want to decouple the pixel size change from this hairline stroke change, so I'm going to make a separate PR for the pixel size change. I'm going to try to get that PR submitted first, then I'll update this PR once the other one is merged. This PR on hold for now. |
|
This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again. 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. |
|
autosubmit label was removed for flutter/flutter/184895, because - The status or check suite Linux linux_fuchsia has failed. Please fix the issues identified (or deflake) before re-applying this label. |
|
An existing Git SHA, To re-trigger presubmits after closing or re-opeing a PR, or pushing a HEAD commit (i.e. with |
Roll Flutter from 5e4f16931847 to aeb96234de86 (42 revisions) flutter/flutter@5e4f169...aeb9623 2026-04-24 robert.ancell@canonical.com Fix leak on error case (flutter/flutter#185516) 2026-04-24 engine-flutter-autoroll@skia.org Roll Skia from 04c6c369cfd0 to 1c9b0b9141e9 (2 revisions) (flutter/flutter#185529) 2026-04-24 engine-flutter-autoroll@skia.org Roll Dart SDK from f386b11262f6 to c26627715892 (1 revision) (flutter/flutter#185526) 2026-04-24 engine-flutter-autoroll@skia.org Roll Skia from 290a056fcd0e to 04c6c369cfd0 (2 revisions) (flutter/flutter#185525) 2026-04-24 chris@bracken.jp [ios] Extract SplashScreenManager from FlutterViewController (flutter/flutter#185405) 2026-04-24 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from j3UCWZhWx7zSl9Asy... to 9fPnyEo9PaNdXtasl... (flutter/flutter#185523) 2026-04-24 engine-flutter-autoroll@skia.org Roll Skia from 4c8bedd3c932 to 290a056fcd0e (1 revision) (flutter/flutter#185518) 2026-04-24 engine-flutter-autoroll@skia.org Roll Dart SDK from 70665fc3fd2e to f386b11262f6 (2 revisions) (flutter/flutter#185512) 2026-04-24 97480502+b-luk@users.noreply.github.com Handle hairline strokes in UberSDF (flutter/flutter#184895) 2026-04-24 engine-flutter-autoroll@skia.org Roll Skia from ea20c73ac72c to 4c8bedd3c932 (3 revisions) (flutter/flutter#185509) 2026-04-24 58529443+srujzs@users.noreply.github.com Use relative path for reloadedSourcesUri and reloaded modules (flutter/flutter#184598) 2026-04-24 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Run all flutter/flutter macOS tests using Xcode 26 and iOS 26 simulator (#185431)" (flutter/flutter#185513) 2026-04-24 engine-flutter-autoroll@skia.org Roll Skia from e8d00d634c22 to ea20c73ac72c (8 revisions) (flutter/flutter#185500) 2026-04-23 okorohelijah@google.com Run all flutter/flutter macOS tests using Xcode 26 and iOS 26 simulator (flutter/flutter#185431) 2026-04-23 rmolivares@renzo-olivares.dev update team-text-input pr triage link to filter out "waiting for response" label (flutter/flutter#185499) 2026-04-23 jason-simmons@users.noreply.github.com Check for overflow when computing the pixel buffer size for an animated PNG frame (flutter/flutter#185442) 2026-04-23 reinar@crypt.ws Impeller: Recreate Vulkan transients on surface size change (flutter/flutter#185122) 2026-04-23 matt.kosarek@canonical.com Updating the windowing API for sized to content regular and dialog windows + removing the decorated flag (flutter/flutter#184977) 2026-04-23 engine-flutter-autoroll@skia.org Roll Dart SDK from 634991935e9a to 70665fc3fd2e (2 revisions) (flutter/flutter#185488) 2026-04-23 louisehsu@google.com Updating ios triage link (flutter/flutter#185437) 2026-04-23 34871572+gmackall@users.noreply.github.com Revert "Preprovision Android NDK for flavored builds and reuse matchi… (flutter/flutter#185439) 2026-04-23 1961493+harryterkelsen@users.noreply.github.com fix(web): Fix LateInitializationError in CkSurface and SkwasmSurface (flutter/flutter#185116) 2026-04-23 1961493+harryterkelsen@users.noreply.github.com [web] Implement stepped image downscaling for CanvasKit and Skwasm (flutter/flutter#184741) 2026-04-23 30870216+gaaclarke@users.noreply.github.com Made wide_gamut_macos only run on arm64 machines (flutter/flutter#185486) 2026-04-23 chris@bracken.jp [ios] Update documentation for FlutterAppDelegate.pluginRegistrant (flutter/flutter#185201) 2026-04-23 engine-flutter-autoroll@skia.org Roll Dart SDK from bdf48933f3cf to 634991935e9a (1 revision) (flutter/flutter#185462) 2026-04-23 engine-flutter-autoroll@skia.org Roll Skia from 5fe6162546b1 to e8d00d634c22 (3 revisions) (flutter/flutter#185459) 2026-04-23 engine-flutter-autoroll@skia.org Roll Skia from 0049c5d91b08 to 5fe6162546b1 (1 revision) (flutter/flutter#185455) 2026-04-23 engine-flutter-autoroll@skia.org Roll Dart SDK from 9648f446f131 to bdf48933f3cf (19 revisions) (flutter/flutter#185451) 2026-04-23 engine-flutter-autoroll@skia.org Roll Skia from 11640d1cbc5c to 0049c5d91b08 (11 revisions) (flutter/flutter#185453) 2026-04-23 ishaquehassan@gmail.com Add disposal guidance to CurvedAnimation and CurveTween docs (flutter/flutter#184569) 2026-04-23 ishaquehassan@gmail.com Add `clipBehavior` parameter to `AnimatedCrossFade` (flutter/flutter#184545) 2026-04-23 rmacnak@google.com [fuchsia] Ask for only VMEX, not ambient-replace-as-executable. (flutter/flutter#185099) 2026-04-23 47866232+chunhtai@users.noreply.github.com Unify SemanticUpdateBuilder API for web and non-web (flutter/flutter#185433) 2026-04-22 68919043+Istiak-Ahmed78@users.noreply.github.com Fix ImageInfo.isCloneOf tests by moving async work to setUp (flutter/flutter#185254) 2026-04-22 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from UdpQnaP5eSaDZd3-i... to j3UCWZhWx7zSl9Asy... (flutter/flutter#185438) 2026-04-22 30870216+gaaclarke@users.noreply.github.com Adds script to run malioc locally (flutter/flutter#185371) 2026-04-22 47866232+chunhtai@users.noreply.github.com Add await mechanism to setClipboard in android_semantics_integration test (flutter/flutter#185428) 2026-04-22 43054281+camsim99@users.noreply.github.com Add ability to pass flags to `et run` (flutter/flutter#185109) 2026-04-22 47866232+chunhtai@users.noreply.github.com Add more guidelines for code review bot (flutter/flutter#185367) 2026-04-22 danny@tuppeny.com Roll pub packages (flutter/flutter#185274) 2026-04-22 saurabhmirajkar000@gmail.com Fix incorrect scale parameter reference in Image constructor docs (flutter/flutter#185403) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages ...
Part of #184352
fixes #185162
AiksTest.CanRenderSkewedHairlineSdfCircle
Before:
After:
Non-SDF:
Note: Non-SDF version has inconsistent hairline width #185222
AiksTest.StrokedRectsRenderCorrectly
Before:
After:
Non-SDF
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.