Skip to content

feat(#336): several window-space UI layers at once (wsui slots) - #341

Merged
dfattal merged 2 commits into
DisplayXR:mainfrom
byungjul:feat/wsui-multi-slot
Oct 1, 2026
Merged

dfattal merged 2 commits into
DisplayXR:mainfrom
byungjul:feat/wsui-multi-slot

Conversation

@byungjul

@byungjul byungjul commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Several DisplayXRWindowSpaceUI HUDs can now be shown at the same time. Before this change the native wsui registry had a single slot. When two components were enabled together, the last one to register won and the others never showed. lenovo-avatar opens its radio and tuning HUDs on the same key and only ever saw one of them, on every backend.

Depends on #339 (wsui on Vulkan). This branch is stacked on it, so the diff includes #339's commit until it merges. Review only the last commit.

Changes

  • wsui module (displayxr_window_space_ui.*):
    • DXR_WSUI_MAX_SLOTS (4) entries.
    • New acquire_slot / release_slot, plus _slot variants of set_texture, set_layer, clear and get_pending.
    • The slot-less API is kept and addresses slot 0.
  • Session:
    • Per-slot state (ProviderWsui wsui[PS_MAX_WSUI]).
    • Create, the getter (dxr_prov_get_wsui_bridge_slot), submit and teardown take a slot, and one window-space layer is submitted per registered slot.
    • The layer array is sized for zones + Local2D + every wsui slot.
  • Vulkan: one overlay kind per slot (DXR_PVK_OVERLAY_WSUI0 + N) and one copy render event per slot (DXR_EVENT_VK_OVERLAY_COPY_WSUI0 + N). dxr_prov_wsui_needs_copy / request_copy became _slot.
  • C#:
    • Each DisplayXRWindowSpaceUI acquires its own slot in OnEnable and releases it in OnDisable.
    • Fixes an existing bug: OnDisable used to clear the one shared layer even while another HUD was still up.
    • If the native plugin has no slot exports (a stale local native build; managed and native ship together), it logs one warning and window-space UI stays disabled. It does not fall back to the slot-less API.

⚠️ Behaviour changes on every backend (D3D11, D3D12, Metal, Vulkan)

  • When two or more DisplayXRWindowSpaceUI components are enabled, all of them are composited. Before, only the last one registered was.
  • Disabling one HUD no longer removes the others.
  • A fifth simultaneous HUD gets no slot: it logs a warning and isn't shown until another HUD is disabled (it retries about once a second, then appears).
  • Apps with a single HUD behave as before.

Testing

Linux, RTX 4090 + DS1, lenovo-avatar (whose app-side layout puts the two HUDs side by side without overlap):

  • Shift+Tab shows both HUDs on the panel without overlap (checked by eye). Before this change only one appeared. Shift+Tab again removes both. Local2D, the 3D content and the transparent background are unaffected.
  • native~/build-linux.sh builds cleanly. The new slot exports are present in the .so.
  • Windows and macOS are compile-checked by CI only. Their wsui arms went through a mechanical s_ps.wsui_* → per-slot refactor with no logic change, but they haven't run on hardware.

Part of #336

Edit (after review): corrected two lines to match the code. A native plugin without the slot exports disables window-space UI (it does not fall back to the slot-less API), and a fifth HUD appears once a slot frees up rather than staying hidden.

🤖 Generated with Claude Code

@byungjul
byungjul requested a review from dfattal as a code owner October 1, 2026 08:10
@byungjul
byungjul force-pushed the feat/wsui-multi-slot branch from 520bc4d to ae7af5f Compare October 1, 2026 08:21
byungjul and others added 2 commits October 1, 2026 07:57
The native wsui registry had a single slot, so when two
DisplayXRWindowSpaceUI components were enabled together (lenovo-avatar opens
its radio and tuning HUDs on the same key) the last one to register won and
the other never showed. Same on every backend.

- wsui module: DXR_WSUI_MAX_SLOTS (4) entries; acquire/release_slot plus
  _slot variants of set_texture / set_layer / clear / get_pending. The
  slot-less API is kept and addresses slot 0.
- session: per-slot state (ProviderWsui wsui[PS_MAX_WSUI]); create, getter
  (dxr_prov_get_wsui_bridge_slot), submit and teardown take a slot, and one
  window-space layer is submitted per registered slot. Layer array sized for
  zones + Local2D + all wsui slots.
- Vulkan: one overlay kind per slot (DXR_PVK_OVERLAY_WSUI0 + N) and one copy
  render event per slot.
- C#: each DisplayXRWindowSpaceUI acquires its own slot in OnEnable and
  releases it in OnDisable (which used to clear the one shared layer even
  while another HUD was still up). Falls back to the slot-less API on a
  native plugin without slots.

Behaviour change on all backends: two or more enabled
DisplayXRWindowSpaceUI components are now all composited (previously only
the last registered one), and disabling one no longer clears the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A slot taken over by another component never shows the previous owner's
  last image: the Vulkan overlay remembers which registered texture its
  content was copied from (dxr_pvk_overlay_content_source) and submit only
  composites content from the texture registered NOW. Same check for Local2D.
- One slot count: DXR_WSUI_MAX_SLOTS (displayxr_window_space_ui.h) now drives
  PS_MAX_WSUI, DXR_PVK_OVERLAY_COUNT, the per-slot render events and the
  overlay labels; the duplicate get_pending_slot externs are gone.
- C#: a component that found every slot taken retries about once a second
  and appears when another HUD frees one; slots are only taken in Play mode
  (an [ExecuteAlways] edit-mode or prefab-stage instance would starve the
  play-mode ones); the legacy slot-less fallback is replaced by going inert
  (with a warning) on a native plugin without the slot API; the Vulkan-copy
  EntryPointNotFound disable now latches instead of re-arming every frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dfattal
dfattal force-pushed the feat/wsui-multi-slot branch from ae7af5f to be788da Compare October 1, 2026 14:57
@dfattal
dfattal merged commit 81f80f0 into DisplayXR:main Oct 1, 2026
8 checks passed
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>
dfattal added a commit that referenced this pull request Oct 2, 2026
…ger washed out (#348)

* fix(#347): request _SRGB swapchains on format-honest runtimes so colour isn't washed out

Since runtime v2.21.0 (D3D11), v2.21.1 (D3D12) and v2.21.7 (Vulkan) the runtime
believes the swapchain format (ADR-044 / INV-4.6, pinned by the Khronos CTS): an
_SRGB swapchain holds encoded colour, and a UNORM one holds LINEAR values that it
sRGB-encodes on the way to the panel. The plugin put encoded bytes into UNORM
swapchains (the primary layer of every Gamma project, plus every wsui/Local2D canvas
and the Gamma extra zones), so they were encoded twice and looked washed out.

On a format-honest runtime (version from xrGetInstanceProperties, per graphics API;
off under the runtime's own DXR_COLOR_LEGACY_UNORM_ENCODED=1 hatch):
- Primary: _SRGB in both colour spaces. What Unity renders into is split from the
  swapchain format: a Linear project keeps encoding on store; a Gamma project renders
  into the UNORM sibling and the existing raw copy (CopyResource / CopyTextureRegion /
  vkCmdCopyImage) moves the encoded bytes into the _SRGB image unchanged.
- wsui / Local2D (D3D11, D3D12, Vulkan): the _SRGB sibling; bridges stay UNORM.
- Extra 3D zones (D3D, Gamma projects): the _SRGB sibling.
Metal is not format-honest yet (ADR-044 section 7) and older runtimes keep the
previous formats, byte for byte.

Also refreshes the Windows x64 DLL, which predated #337/#339/#341.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(#347): read the runtime version from runtimeName when runtimeVersion is 1.x (Linux)

Every Linux runtime shipped so far reports runtimeVersion 1.6.0
(DisplayXR/displayxr-runtime#1790), so the format-honest gate never engaged on Linux
and apps stayed washed out. Fall back to the 'vX.Y.Z' tag in runtimeName when the
major is < 2. Verified by Byungju on the DS1 (Ubuntu, RTX 4090, runtime v2.21.11):
_SRGB swapchains (43 primary, 50 wsui/Local2D), raw bridge copies, no errors, and
correct colour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants