Bugfix: Exclude PostFx from UI by default (Issue #1154)#3128
Bugfix: Exclude PostFx from UI by default (Issue #1154)#3128yuechen-li-dev wants to merge 8 commits intostride3d:masterfrom
Conversation
Ethereal77
left a comment
There was a problem hiding this comment.
Looks good overall, but a few comments:
-
If this PR is a proper fix that improves the defaults to not need the post-FX Clean UI "hack", why mention it all the time in comments and tests? Just do your thing and verify it is working!
-
Naming is ambiguous in my opinion. Look at the comment I left.
-
Some of the comments and logs seem like a WIP. Maybe a little cleanup is needed.
I'll massage the tests a bit to check the rendering is working and the render stage is being invoked in the correct order, excluding any details or specific behavior that may come from the Clean UI patch. However were it implemented it were a "hacky" solution. If this PR is the proper fix, forget about that solution.
There was a problem hiding this comment.
This PR should not touch the solution file
There was a problem hiding this comment.
A new xunit test project was added to the solution, which is why the .sln file was changed.
There was a problem hiding this comment.
My bad, I missed that. Sorry
| // ForwardRendererCompositorTests.cs | ||
| // Place at: sources/engine/Stride.Rendering.Tests/Compositing/ForwardRendererCompositorTests.cs | ||
| // | ||
| // Structural tests confirming the UI-before-PostFx ordering bug. | ||
| // No GPU required — pure object-graph assertions. |
There was a problem hiding this comment.
What are these comments?
| // ---------------------------------------------------------------- | ||
| // 2. REGRESSION GUARD (currently expected to fail — uncomment after fix): | ||
| // Once the fix is in, ForwardRenderer should expose a | ||
| // PostEffectsUIRenderStage property that is drawn AFTER PostEffects. | ||
| // ---------------------------------------------------------------- |
There was a problem hiding this comment.
Seems like in-progress comments. Can be removed?
| // ---------------------------------------------------------------- | ||
| // 3. CONFIRM: UIRenderFeature inherits RenderStageSelectors from | ||
| // RootRenderFeature — the collection used by CleanUiPostFxPatch | ||
| // to route Group31 to a separate stage. | ||
| // ---------------------------------------------------------------- |
|
|
||
| Assert.NotNull(feature.RenderStageSelectors); | ||
|
|
||
| _output.WriteLine($"UIRenderFeature.RenderStageSelectors is present (type: {feature.RenderStageSelectors.GetType().Name})."); |
There was a problem hiding this comment.
It is a bit weird to write to the console inside a test. Imho tests should not talk, just verify things work as intended
There was a problem hiding this comment.
Removed. However, I'd like to note that writing console output inside unit tests does significantly reduce tendency for LLMs to hallucinate while generating code, in case Stride does decide to use AI coding more extensively in the future.
There was a problem hiding this comment.
I would prefer to add comments inside the tests detailing what they are trying to test than to write the results to the console.
However, it hadn’t occurred to me that LLMs could understand better the context from console output. Makes sense, I have to try it 😁
|
|
||
| Assert.True(hasStage); | ||
| Assert.True(hasSelector); | ||
| _output.WriteLine("IsAlreadyPatched: correctly returns true when stage and selector both exist."); |
There was a problem hiding this comment.
Same as above: logging in tests
| // ---------------------------------------------------------------- | ||
| // 7. DOCUMENT: The null-conditional assignment bug in the original | ||
| // CleanUiPostFxPatch.MoveUiRecursive. | ||
| // `ui?.RenderGroup = value` is a compile error in C# — it cannot | ||
| // assign through a null-conditional. The correct form is guarded | ||
| // with an explicit null check. | ||
| // ---------------------------------------------------------------- |
| // WRONG (compile error if used on real UIComponent): | ||
| // fakeUi?.RenderGroup = RenderGroup.Group31; | ||
| // | ||
| // CORRECT: | ||
| if (fakeUi != null) | ||
| fakeUi.RenderGroup = RenderGroup.Group31; |
There was a problem hiding this comment.
Why is this wrong? In theory, C# 14 allows null-conditional assignments, so both should be equivalent, no?
| _output.WriteLine("Null-conditional assignment bug documented."); | ||
| _output.WriteLine(" Correct pattern: if (ui != null) ui.RenderGroup = RenderGroup.Group31;"); |
There was a problem hiding this comment.
Same as above: logging in tests
| /// making it suitable for HUD and screen-space UI. | ||
| /// Leave null (the default) when no post-FX-immune UI is required. | ||
| /// </summary> | ||
| public RenderStage PostEffectsUIRenderStage { get; set; } |
There was a problem hiding this comment.
PostEffectsUIRenderStage naming is a bit ambiguous imho. It is a render stage that draws UI after post-FX, but he naming does not implies it occurs after post-FX, but in post-FX.
There was a problem hiding this comment.
I changed it to "AfterPostEffectUIRenderStage" locally, before I commit it, would that be a good name?
There was a problem hiding this comment.
I suppose it is. I'm bad myself at naming things, but sounds right.
Is there something in your logic that makes this render-stage UI-specific? I ask because I'm thinking about other effects that may benefit from no-post-fx (for example, debug rendering, outlines, etc). Nevermind if this is specific for UI rendering.
There was a problem hiding this comment.
Is there something in your logic that makes this render-stage UI-specific?
Nothing in particular, I just wanted to close this bug. I've done some research on Stride's graphic compositor in the past, and I think it could be better if there is more of a code-first approach to writing a custom graphics compositor, maybe with an interface or JSON profile or something, as it is a bit difficult to tinker with it in GameStudio the last time I've tried.
There was a problem hiding this comment.
Yes, I know. I think the graphics compositor editor is/was a half-way solution to a more complete version Silicon had planned that never actually ended happening. There are several places and features like that in the codebase. A pity.
Anyway, my question was that if there is no UI-specific limitation to the use of this render stage, maybe just calling it AfterPostEffectRenderStage is good enough. That way it can be used for other things without implying it is for UI.
Unit tests were generated by Claude. It's very good at debugging if you use it properly, but it gets weirdly hung up about random stuff and it has a tendency to be confidently wrong, so you always have to check its answers. I built GameStudio on my own VS locally and confirmed that it works though. My own CleanUI patch pretty much automated the RenderGroup 31 bypass documented by others which was the "hacky" solution. This PR should be the more proper fix of the root cause and does not use the same method as the Clean UI patch. |
Yeah, I can relate. I find it both funny and infuriating how stubborn LLMs can get about things sometimes 😁 |
|
Yeah, I think this is pretty much done. It's a simple patch, so I think it's ready if there are no other concerns. |
Ethereal77
left a comment
There was a problem hiding this comment.
It is in good shape overall. I left a few comments
| 87ff1d9cdd52418daf76385176a0e316: ref!! 555e84b4-b68a-4f38-ac3a-f0f563028ef0 | ||
| 5e059d4cc2db4ee8a1f28a40f4ac3ae8: ref!! b03a45c6-7a56-417c-8a80-69cc608671f1 | ||
| GBufferRenderStage: ref!! ecab139e-5f55-42b5-a324-310c195a9c89 | ||
| PostEffectsUIRenderStage: ref!! 5b5e4f7e-e8b5-497f-9d3b-418d06823b14 |
There was a problem hiding this comment.
Here it still has the old name. It should be AfterPostEffectsUIRenderStage.
There was a problem hiding this comment.
Changed to “AfterPostEffectsRenderStage" now.
| ShadowMapRenderStages: | ||
| fc4d1e0de5c2b0bbc27bcf96e9a848fd: ref!! c0524e55-4061-464d-84dd-7c4c70f70e0e | ||
| GBufferRenderStage: null | ||
| PostEffectsUIRenderStage: ref!! 62af1128-cc9c-49a0-9d81-3f7f598a4b16 |
There was a problem hiding this comment.
Here it still has the old name. It should be AfterPostEffectsUIRenderStage.
There was a problem hiding this comment.
Changed to “AfterPostEffectsRenderStage" now.
| /// <summary> | ||
| /// A UI render stage drawn directly onto <see cref="viewOutputTarget"/> after | ||
| /// <see cref="PostEffects"/> have resolved. Geometry assigned to this stage is | ||
| /// never tone-mapped, bloomed, or otherwise affected by post-processing effects, | ||
| /// making it suitable for HUD and screen-space UI. | ||
| /// Leave null (the default) when no post-FX-immune UI is required. | ||
| /// </summary> | ||
| public RenderStage AfterPostEffectsUIRenderStage { get; set; } | ||
|
|
There was a problem hiding this comment.
Just my opinion, but I'd remove UI from the name. The more I rhink of it, the more I believe it would be useful for more than just UI (gizmos, debug rendering, outlines, maybe even some custom material unaffected by post-FX).
There was a problem hiding this comment.
Done. Changed to “AfterPostEffectsRenderStage" now.
There was a problem hiding this comment.
Nit: some double blank lines between methods and blank lines before closing braces
There was a problem hiding this comment.
Should be corrected in latest PR as well.
|
Btw, Your new @xen2 You have been refactoring tests lately. What do you think? |
…rStage", fix spacing inconsistencies in unit test.
Strides.Graphics.Test project is very heavy to run, since it has what I assumed to be image generation checks via DX11. So, that's why I would like to keep a separate project to keep things that doesn't require those image checks separate and maybe move some other tests over as well, if you guys would like, for separation of test concerns. |
|
LGTM. Thanks for your work. A much needed feature imho (and one that took a lot longer than needed).
Wrt to that I'll wait for other mantainers' take on this. If it eases the workflow of testing the rendering without drawbacks, why not? |
ab329f3 to
482bd28
Compare
PR Details
The blurry, washed out UI stemming from the PostFx being applied on UI elements has been a bug since 2021, and the RenderGroup 31 bypass is well known, and I've even made a script that is specifically designed to work around it.
So, I've decided to fix it today.
Details:
ForwardRenderer.cs
Added a
PostEffectsUIRenderStageproperty. When set, this stage is drawn onto the final resolved output target afterPostEffectshas already run, meaning UI geometry assigned to it is never touched by tone-mapping, bloom, or any other post-processing effect.DefaultGraphicsCompositorLevel10/9.sdgfxcomp
Added
UIRenderStageas a new render stage and pointed theUIRenderFeatureselector at it instead ofTransparent. WiredPostEffectsUIRenderStageon theForwardRendererto this new stage. The result is that all UI renders after post-processing by default, with no configuration required from the user.Added a separate unit test in new group: Stride.Rendering.Tests to guard against regression.
Related Issue
Fixes #1154
Types of changes
Checklist