-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Use depth buffer to implement geometry overdraw protection #179426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| pass.SetCommandLabel("Clip stencil preparation (Increment)"); | ||
| options.stencil_mode = | ||
| ContentContextOptions::StencilMode::kOverdrawPreventionIncrement; | ||
| break; |
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.
I'm a bit confused about this section of code.
Do I need anything here to replace the old code?
GeometryResult::Mode::kNormal used to fall through to the GeometryResult::Mode::kPreventOverdraw case and use the kOverdrawPreventionIncrement stencil mode. Was that intentional? What's the purpose of this?
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.
I'm not 100% sure on this. It may have been that kNormal just didn't care about these particular settings because, for instance, it didn't actually run the stencil pass anyway.
@gaaclarke @chinmaygarde ?
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.
If memory serves, GeometryResult::Mode::kNormal means there are no overlapping triangles. Now I don't think you can entirely disregard whats on the stencil buffer and ignore the stencil test directly. Since normal is the simplest case of the lot, I suspect it was lumped together with PreventOverdraw. But disregarding the test entirely does seem off though.
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.
Now that you are no longer using the stencil buffer, lumping it together with either the EO or NZ modes should be fine I think.
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.
I think you're right. I moved the kNormal case to be lumped with kNonZero, and I've verified locally that the broken iOS app scenario tests are now passing with with change applied. I updated the PR to do this.
Should I do anything with the kPreventOverdraw case here? Is that case never expected to happen for a ClipContents? That's my initial thought, but my understanding how all this code works is very incomplete. If that case is unexpected and incompatible with ClipContents, I can add a FML_UNREACHABLE() here. Otherwise, should I also lump kPreventOverdraw in with one of the other cases?
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.
Ok, after studying this a bit, I think I have a much better understanding of how things work for ClipContents.
With the existing behavior, ClipContents always performs a clip by using stencil-and-cover. It does a render pass for the preparation stencil, which sets the stencil buffer according to the geometry. And then it does a second cover render pass that writes the depth (performing the actual clip) and resets the stencil buffer. It uses this stencil even for geometries kNormal and kPreventOverdraw. For those geometries, it uses a stencil mode that always fills in a 1 value regardless of any triangle overlap in the geometry.
My first PR revision removed the stencil step for kNormal and kPreventOverdraw. This was incorrect because it would render an empty stencil for the stencil step. So the cover step would clip everything. This caused the test breakages in my first revision, where the goldens showed a lot of content was inadvertently clipped out completely.
In a subsequent revision, I followed Chinmay's advice to make kNormal and kPreventOverdraw fall back to the kNonZero stencil case. The stencil is now properly filled out and these cases now work pretty close to how they did before.
I realize that we can do a little better specifically for difference clips.
Clipping needs to use stencil-and-cover for nonzero or evenodd geometry cases because those cases have overlapping triangles, and the stencil pass is needed to determine whether each of these overlaps should be included in the depth write.
But for kNormal and kPreventOverdraw, we don't need stencil-and-cover. The kNormal case has no overlapping triangles, so the depth write has no overlapping triangles to deal with. For the kPreventOverdraw case, any overlapping triangles should unconditionally be included in the depth write (unlike the nonzero or evenodd cases). So kNormal and kPreventOverdraw geometries can do a difference clip with a single render pass and does not need to use a stencil.
I just updated my PR to do this.
Edit: It turns out there are slight differences between the kStencilNonZeroFill stencil mode and what we want for kNormal/kPreventOverdraw geometries, which show up as a few small pixel differences in the golden tests. This means we can't fall through to kStencilNonZeroFill for kNormal/kPreventOverdraw. Instead, I made a new kStencilIncrementAll stencil mode which does what we want: set the stencil for everything covered by a triangle regardless of any triangle overlaps. This is the same as what the old kOverdrawPreventionIncrement stencil mode did.
flar
left a comment
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.
I would say LGTM, but this is mostly as far as I understand how things work. I'd like to see reviews from @gaaclarke or @chinmaygarde as well.
| pass.SetCommandLabel("Clip stencil preparation (Increment)"); | ||
| options.stencil_mode = | ||
| ContentContextOptions::StencilMode::kOverdrawPreventionIncrement; | ||
| break; |
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.
I'm not 100% sure on this. It may have been that kNormal just didn't care about these particular settings because, for instance, it didn't actually run the stencil pass anyway.
@gaaclarke @chinmaygarde ?
| // up the depth buffer and depth comparison function to prevent the same | ||
| // pixel from being painted multiple times. | ||
| if (geometry_result.mode == GeometryResult::Mode::kPreventOverdraw && | ||
| options.blend_mode != BlendMode::kSrc) { |
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.
Since the new rendering approach uses depth testing to eliminate overdraw, we no longer need to check whether the BlendMode is kSrc. In the past, this check helped skip an extra stencil pass when drawing opaque stroke paths, but overdraw still occurred (Some drivers may optimize this away, but such behavior is not guaranteed). With depth testing, overdraw for opaque stroke paths is avoided entirely.
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.
Actually, it can potentially make things a little faster for blend mode since it avoids storing a new pixel value on a previously rendered pixel.
If we trust this approach well enough then we can eventually just make this all unconditional.
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.
So is the suggestion to remove the blend_mode != kSrc check here?
In line 213 above, when BlendMode is kSrc, then depth_write_enabled is set to true (and depth_compare remains its default kGreaterEqual). The comment says this is "to allow reordering". But I don't fully understand what this means or what this does.
Keeping the blend_mode != kSrc check here preserves this existing behavior when BlendMode is kSrc. I didn't want to change the existing behavior because I didn't know whether changing this behavior would do something unintended.
So current behavior with this PR is:
| BlendMode != kSrc | BlendMode == kSrc | |
|---|---|---|
| kPreventOverdraw false | use default options | depth_write_enabled=true |
| kPreventOverdraw true | depth_write_enabled=true depth_compare=kGreater | depth_write_enabled=true |
If the options.blend_mode != BlendMode::kSrc here is removed, the behavior would be:
| BlendMode != kSrc | BlendMode == kSrc | |
|---|---|---|
| kPreventOverdraw false | use default options | depth_write_enabled=true |
| kPreventOverdraw true | depth_write_enabled=true depth_compare=kGreater | depth_write_enabled=true depth_compare=kGreater |
Is this change in the behavior for the (BlendMode == kSrc) && (kPreventOverdraw == true) case acceptable and/or desired?
Or is the suggestion to both remove the blend_mode != kSrc check here, and also remove setting depth_write_enabled when BlendMode is kSrc? The behavior would be:
| BlendMode != kSrc | BlendMode == kSrc | |
|---|---|---|
| kPreventOverdraw false | use default options | use default options |
| kPreventOverdraw true | depth_write_enabled=true depth_compare=kGreater | depth_write_enabled=true depth_compare=kGreater |
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.
So is the suggestion to remove the blend_mode != kSrc check here?
Yes, it is.
In line 213 above, when BlendMode is kSrc, then depth_write_enabled is set to true (and depth_compare remains its default kGreaterEqual). The comment says this is "to allow reordering". But I don't fully understand what this means or what this does.
Impeller automatically rewrites the BlendMode from kSrcOver to kSrc when it detects that the content being drawn is fully opaque. In kSrc mode, the Render backend turns blending off entirely.
Given that, imagine we’re drawing three opaque shapes A → B → C (from back to front). Because blending is disabled for these draws, their visual result no longer depends on the draw order. Once depth writes are enabled, we can safely reorder them—for example, drawing C → B → A instead of the original order.
The benefit of this reordering is that it allows the GPU to reject more fragments early. Drawing the frontmost opaque geometry first lets early-z eliminate a large portion of the fragments from the shapes behind it, which reduces overdraw and lowers fragment-shader cost overall.
If the options.blend_mode != BlendMode::kSrc here is removed, the behavior would be:
| BlendMode != kSrc | BlendMode == kSrc | |
|---|---|---|
| kPreventOverdraw = false | use default options | depth_write_enabled = true |
| kPreventOverdraw = true | depth_write_enabled = true; depth_compare = kGreater | depth_write_enabled = true; depth_compare = kGreater |
Is this change in the behavior for the (BlendMode == kSrc) && (kPreventOverdraw == true) case acceptable and/or desired?
Yes, this change is acceptable — in fact, it’s exactly the behavior we want.
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.
Thanks for the thorough explanation. I removed the blend_mode != kSrc check as you suggested.
|
The test failures do seem to indicate that this is breaking some scenarios. I'll continue to investigate this. |
|
Hmm, did you already get access to the goldens results? I couldn't find the link. In any case, I think the stencil mode setter might be off for the reason I mentioned in the inline comment. |
The golden results are here on FRoB. The TAP results for the original revision of this PR are here. The old TAP results show some failures where it looks like certain UI got clipped out or aren't rendering. Maybe the most recent fix that you suggested will fix it in the new in progress TAP run. |
When I made this reply, I mixed up "Golden results" and "Google testing results" in my head. So I replied talking about Google test results. As for the golden results, it says it's "waiting for status to be reported" for me. But clicking through does show results with diffs here. I'll look into what might be causing these diffs. |
This comment was marked as outdated.
This comment was marked as outdated.
|
I tracked down the golden test failures. These tests draw a stroked rounded rectangle with a MaskFilter which blurs parts of the rectangle. The blurring process is outlined here: flutter/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc Lines 703 to 711 in 775e23d
The test was failing specifically when using a BlurStyle of
So this case uses a The problem with this is the same as the issue Chinmay pointed out above, where I originally changed the |
|
Another way to fix that scenario is to make sure we've allocated enough clip depth for that rendering primitive. Normally we allocate 1 clip depth per primitive, but some primitives allocate more than 1. Each SaveLayer will indicate the total accumulated clip depth of its contents and the entire DisplayList also provides the total clip depth of all of its content. One thing that is likely getting in the way here is the number of times we specify Ooh, I think I know why it needed the cleanup step in some cases - is that because it had modified the depth buffer for the temp clip and then it needed to restore it back to the depth state before it did the temp clip? Really a temp clip should be executed at "render_op_depth + 1" and not whatever clip depth is on the transform(_and_clip)_stack. But to do this, we'd need to have done the accounting up front to be aware that these operations would need an extra clip depth allocated to them, which means modifying the way we do the clip depth accounting in DisplayListBuilder. |
|
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. |
|
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. |
|
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. |
|
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. |
Roll Flutter from 6e1aa82 to d81baab (47 revisions) flutter/flutter@6e1aa82...d81baab 2025-12-16 68449066+zijiehe-google-com@users.noreply.github.com Revert "chore: linux fuchsia tests are flaking" (flutter/flutter#179897) 2025-12-15 planetmarshall@users.noreply.github.com add default-linux-sysroot to gn frontend args (flutter/flutter#179303) 2025-12-15 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#179751) 2025-12-15 engine-flutter-autoroll@skia.org Roll Skia from 783ce2077e46 to 6903a4e65c3f (2 revisions) (flutter/flutter#179903) 2025-12-15 adilhanney@disroot.org Add `Adwaita Sans` as a font fallback on Linux (flutter/flutter#179144) 2025-12-15 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Unmodified android sdk bundle (#179647)" (flutter/flutter#179904) 2025-12-15 robert.ancell@canonical.com Update window settings as they change rather than the more outdated "Apply" pattern. (flutter/flutter#179861) 2025-12-15 49699333+dependabot[bot]@users.noreply.github.com Bump dessant/lock-threads from 5.0.1 to 6.0.0 in the all-github-actions group (flutter/flutter#179901) 2025-12-15 engine-flutter-autoroll@skia.org Roll Skia from 9decbd4c216b to 783ce2077e46 (2 revisions) (flutter/flutter#179896) 2025-12-15 34871572+gmackall@users.noreply.github.com Unmodified android sdk bundle (flutter/flutter#179647) 2025-12-15 mdebbar@google.com [web] Fix `resizeToAvoidBottomInset` on Android web (flutter/flutter#179581) 2025-12-15 iliyazelenkog@gmail.com Suppress deprecation warning for AChoreographer_postFrameCallback (flutter/flutter#178580) 2025-12-15 planetmarshall@users.noreply.github.com remove unnecessary virtual destructor from VertexDescriptor (flutter/flutter#178682) 2025-12-15 engine-flutter-autoroll@skia.org Roll Dart SDK from b245f6022727 to 20d114f951db (1 revision) (flutter/flutter#179893) 2025-12-15 jason-simmons@users.noreply.github.com [Impeller] Delete GLES framebuffer objects only on the thread where they were created (flutter/flutter#179768) 2025-12-15 jason-simmons@users.noreply.github.com [Impeller] Convert paths in ImpellerC command line flags to std::filesystem::path objects (flutter/flutter#179717) 2025-12-15 engine-flutter-autoroll@skia.org Roll Skia from 5e1ff611b36b to 9decbd4c216b (2 revisions) (flutter/flutter#179890) 2025-12-15 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from DtfDWAx6t_CsD2e1W... to 433KtmJvbMyaDMJvD... (flutter/flutter#179889) 2025-12-15 116356835+AbdeMohlbi@users.noreply.github.com Remove unnecessary unboxing in `FlutterLoader.java` (flutter/flutter#179869) 2025-12-15 engine-flutter-autoroll@skia.org Roll Skia from f04f8ca3b284 to 5e1ff611b36b (1 revision) (flutter/flutter#179882) 2025-12-15 engine-flutter-autoroll@skia.org Roll Packages from 0ac7a03 to 2cd921c (1 revision) (flutter/flutter#179885) 2025-12-15 bkonyi@google.com [ Widget Preview ] Pass DTD URI as a constant in a generated file (flutter/flutter#179821) 2025-12-15 116356835+AbdeMohlbi@users.noreply.github.com Bump minSdk to `24` in `engine` (flutter/flutter#175508) 2025-12-15 engine-flutter-autoroll@skia.org Roll Dart SDK from bca8bb37d95f to b245f6022727 (1 revision) (flutter/flutter#179879) 2025-12-15 engine-flutter-autoroll@skia.org Roll Skia from eb1347d18957 to f04f8ca3b284 (1 revision) (flutter/flutter#179876) 2025-12-15 engine-flutter-autoroll@skia.org Roll Skia from 41bcfb848b13 to eb1347d18957 (1 revision) (flutter/flutter#179871) 2025-12-15 engine-flutter-autoroll@skia.org Roll Skia from d194c3b7a9a9 to 41bcfb848b13 (6 revisions) (flutter/flutter#179866) 2025-12-14 robert.ancell@canonical.com Remove obsolete windowing channel (flutter/flutter#179718) 2025-12-14 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from SCLq31whvg8nJvuYE... to DtfDWAx6t_CsD2e1W... (flutter/flutter#179856) 2025-12-14 engine-flutter-autoroll@skia.org Roll Skia from 5d98ce0af031 to d194c3b7a9a9 (1 revision) (flutter/flutter#179854) 2025-12-13 engine-flutter-autoroll@skia.org Roll Skia from f9b242d9a365 to 5d98ce0af031 (1 revision) (flutter/flutter#179843) 2025-12-13 engine-flutter-autoroll@skia.org Roll Skia from abe90c72cf56 to f9b242d9a365 (1 revision) (flutter/flutter#179838) 2025-12-13 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from fppT9ZrwbFx7iYrIh... to SCLq31whvg8nJvuYE... (flutter/flutter#179836) 2025-12-13 engine-flutter-autoroll@skia.org Roll Dart SDK from 34654f2a3baa to bca8bb37d95f (1 revision) (flutter/flutter#179835) 2025-12-13 engine-flutter-autoroll@skia.org Roll Skia from 9e7c893bb593 to abe90c72cf56 (1 revision) (flutter/flutter#179830) 2025-12-13 engine-flutter-autoroll@skia.org Roll Dart SDK from f105c2168574 to 34654f2a3baa (1 revision) (flutter/flutter#179823) 2025-12-13 97480502+b-luk@users.noreply.github.com Add FilterQuality parameter to FragmentShader.setImageSampler (flutter/flutter#179760) 2025-12-13 evanwall@buffalo.edu Add profiling counts to pipeline uses (flutter/flutter#179374) 2025-12-13 engine-flutter-autoroll@skia.org Roll Skia from edf52cb27f29 to 9e7c893bb593 (3 revisions) (flutter/flutter#179819) 2025-12-12 jacksongardner@google.com Update Skwasm to engine style guidelines. (flutter/flutter#179756) 2025-12-12 engine-flutter-autoroll@skia.org Roll Dart SDK from 9a65db770758 to f105c2168574 (5 revisions) (flutter/flutter#179814) 2025-12-12 engine-flutter-autoroll@skia.org Roll Skia from f23b00a407a4 to edf52cb27f29 (5 revisions) (flutter/flutter#179807) 2025-12-12 97480502+b-luk@users.noreply.github.com Use depth buffer to implement geometry overdraw protection (flutter/flutter#179426) 2025-12-12 nshahan@google.com [flutter_tool] Force UTF-8 character set for dev (flutter/flutter#179419) 2025-12-12 engine-flutter-autoroll@skia.org Roll Skia from e66816c3645e to f23b00a407a4 (2 revisions) (flutter/flutter#179798) 2025-12-12 30870216+gaaclarke@users.noreply.github.com Implements decodeImageFromPixelsSync (flutter/flutter#179519) ...
Use depth buffer to implement geometry overdraw protection as described in #179107.
Also deletes code that was used only for the old stencil-based overdraw protection.
Fixes #179107
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
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.