fix: overlay canvases bleed into each other; wsui blank under the URP foreground clip - #342
Merged
Merged
Conversation
DisplayXRLocal2D and DisplayXRWindowSpaceUI park their offscreen canvas at one shared world position on the shared private layer 30, and each overlay camera culls to that layer. With two or more overlays every camera also rendered the other canvases: in lenovo-avatar the speech bubble's Local2D texture held the radio and tuning HUDs on top of the bubble text (verified by reading the RT back). The layer can't separate them because they share it. DisplayXROverlayStage hands each live overlay its own stage, 1000 world units apart (a 4096-unit canvas at scale 0.01 is ~41 units; the cameras' far plane is 10), acquired in OnEnable and released in OnDisable. Behaviour change on every platform: an overlay's texture now contains only its own canvas. Scenes with a single overlay are unaffected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The transparent-overlay foreground clip (DisplayXR/ForegroundClipURP) is a full-screen pass on the URP renderer, so it also ran for DisplayXRWindowSpaceUI's offscreen overlay camera and discarded the whole canvas: the HUD RT came out fully transparent and nothing ever showed (verified by reading the RT back: all zero; with this change both lenovo-avatar HUDs render). DisplayXRLocal2D already zeroes _DXRForegroundFar around its own camera's render; do the same here. Behaviour change on every platform for apps that wire the URP foreground clip: their window-space UI now renders. Apps without it are unaffected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dfattal
force-pushed
the
fix/overlay-canvas-isolation
branch
from
October 1, 2026 15:00
106f1a8 to
9f980d5
Compare
dfattal
pushed a commit
that referenced
this pull request
Oct 2, 2026
* chore(#332): put UpdateBakedHitColliders' doc comment back above it The Linux UpdatePointerFromInputSystem() method (#343) was inserted between UpdateBakedHitColliders' doc comment and the method, so the comment read as if it described the new method. Move the Linux method above the comment. No code change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(#332): round Linux PointerPosition/PointerDelta like macOS The macOS path rounds the cursor to whole window-client pixels (Mathf.RoundToInt on x and on the flipped y) before computing PointerPosition and PointerDelta; the Linux path (#343) did not, so Linux reported fractional pixels. Round the same way. Linux only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(#336): each overlay canvas has its own stage, not (0,100000,0) Since #342 every Local2D / window-space UI canvas gets its own stage from DisplayXROverlayStage (first at (0,100000,0), then 1000 units apart along x). The WindowSpaceUI sample README and router comment, window-space-ui.md and a leftover comment in DisplayXRWindowSpaceUI still said every canvas sits at (0,100000,0). Also note in DisplayXROverlayStage that the spacing assumes an overlay camera ortho half-width under 1000 world units. Comment/doc only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(#336): say how several window-space UIs stack Several DisplayXRWindowSpaceUI components are composited in slot order (lowest slot at the bottom), and each takes the lowest free slot on enable, so the draw order of overlapping HUDs follows enable/disable history, not anything the app sets. Document that (class doc, the slot-acquire comment, window-space-ui.md), advise keeping HUDs from overlapping, and note that under the DisplayXR shell only the first window-space layer is composited. Doc only, no behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two fixes for apps that use more than one 2D overlay, or the URP transparent-overlay foreground clip. Both showed up in lenovo-avatar, which has a Local2D speech bubble and two window-space HUDs.
1. Each overlay canvas gets its own stage
DisplayXRLocal2DandDisplayXRWindowSpaceUIall parked their offscreen canvas at one shared world position ((0, 100000, 0)), on the shared private layer 30. Each overlay camera culls to that layer, so every camera also rendered the other overlays' canvases. Reading the bubble's Local2D RT back showed the radio and tuning HUDs drawn on top of the bubble text. Sharing the layer means it can't keep them apart.The new
DisplayXROverlayStagegives each live overlay its own stage, 1000 world units apart. That spacing is well beyond any canvas (a 4096-unit canvas at scale 0.01 is about 41 units) and the cameras' 10-unit far plane. Each component acquires a stage inOnEnableand releases it inOnDisable.2. The window-space UI camera no longer gets the foreground clip
The URP foreground clip (
DisplayXR/ForegroundClipURP) is a full-screen pass on the renderer. It ran forDisplayXRWindowSpaceUI's overlay camera too, and with_DXRForegroundFararmed it discarded the whole canvas. The HUD RT came out fully transparent (read back as all zeros), so no HUD ever appeared.DisplayXRLocal2Dalready zeroes the global around its own camera's render.DisplayXRWindowSpaceUInow does the same.Testing
Linux, RTX 4090 + DS1, lenovo-avatar (one Local2D, two wsui):
Related: #336
🤖 Generated with Claude Code