You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Apps that create two separate per-view swapchains (typical of Unity and Unreal builds) render their slot in the shell launcher as solid black/grey, even though xrEndFrame calls succeed and the runtime log shows valid view poses + frames flowing for the entire session.
Apps that use a single atlas-tiled swapchain with per-view sub-rects (e.g. cube_handle_d3d12_win) render correctly in the same session, on the same hardware, with the same shell binary.
Surfaced during testing of #182. The Per-App GPU Preference fix in #183 puts the client on the same adapter as the dGPU-pinned service (verified via registry + Task Manager), but the slot still renders black for Unity, so this is not a hybrid-laptop / cross-adapter issue.
Hand-author a registered manifest in %LOCALAPPDATA%\DisplayXR\apps\ pointing at a stock Unity D3D12 build (or Unreal D3D12 build).
displayxr-service.exe → Ctrl+Space → Ctrl+L → click the Unity tile.
Loading spinner shows briefly, then the slot's content area is solid black/very dark grey for the entire session, while the slot chrome (title bar, close button) renders fine.
Repeat with cube_handle_d3d12_win in the same session — renders correctly.
Both views with arrayIdx=0 rect=(0,0 1920x1080) — not multiview, two distinct 1920×1080 swapchains.
Service side: compositor_layer_commit: atlas_texture=1920x1080, view_width=960, view_height=1080 — atlas blit appears to run, but the resulting atlas slot reads as black/grey downstream.
What cube_handle_d3d12_win does differently (works)
Client side:
[client_d3d12_create_swapchain] format=28 (DXGI=28) w=3840 h=2160 arraySize=1 mipCount=1 sampleCount=0 ...
[client_d3d12_create_swapchain] Service created swapchain with 1 images
Single 3840×2160 swapchain, format 28 (UNORM, not sRGB). Both views are sub-rects of this one atlas-tiled texture. This hits a different (working) code path in the compositor.
Where to look
src/xrt/compositor/d3d11_service/comp_d3d11_service.cpp::compositor_layer_commit, around lines 9510-9670. The function has three blit paths:
Else (~9655) — raw CopySubresourceRegion per view.
For Unity (per-view swapchains, content not oversized, shell mode), the else branch (raw copy) should run. Either:
The raw copy is reading from a texture in the wrong D3D12 resource state (Unity's command list transitions vs the service's D3D11 read expectations).
The shared NT handle interpretation differs between Unity (writer, D3D12) and the service (reader, D3D11) for sRGB-formatted textures.
Some D3D12 fence wait is missing, so the service reads before Unity's GPU work completes.
The fact that cube_handle_d3d12_win works (D3D12 client + D3D11 service) rules out a fundamental D3D12-vs-D3D11 incompatibility — the difference is purely in the swapchain layout / submission pattern.
Affected apps
Unity D3D12 builds (verified)
Unreal D3D12 builds (user-reported)
Any other engine that creates one swapchain per view rather than an atlas-tiled single swapchain
Symptom
Apps that create two separate per-view swapchains (typical of Unity and Unreal builds) render their slot in the shell launcher as solid black/grey, even though
xrEndFramecalls succeed and the runtime log shows valid view poses + frames flowing for the entire session.Apps that use a single atlas-tiled swapchain with per-view sub-rects (e.g.
cube_handle_d3d12_win) render correctly in the same session, on the same hardware, with the same shell binary.Surfaced during testing of #182. The Per-App GPU Preference fix in #183 puts the client on the same adapter as the dGPU-pinned service (verified via registry + Task Manager), but the slot still renders black for Unity, so this is not a hybrid-laptop / cross-adapter issue.
Reproduction
%LOCALAPPDATA%\DisplayXR\apps\pointing at a stock Unity D3D12 build (or Unreal D3D12 build).displayxr-service.exe→ Ctrl+Space → Ctrl+L → click the Unity tile.cube_handle_d3d12_winin the same session — renders correctly.What the logs show (Unity D3D12)
Client side (
displayxr.lognext to the exe):Both views with
arrayIdx=0 rect=(0,0 1920x1080)— not multiview, two distinct 1920×1080 swapchains.Service side:
compositor_layer_commit: atlas_texture=1920x1080, view_width=960, view_height=1080— atlas blit appears to run, but the resulting atlas slot reads as black/grey downstream.What
cube_handle_d3d12_windoes differently (works)Client side:
Single 3840×2160 swapchain, format 28 (UNORM, not sRGB). Both views are sub-rects of this one atlas-tiled texture. This hits a different (working) code path in the compositor.
Where to look
src/xrt/compositor/d3d11_service/comp_d3d11_service.cpp::compositor_layer_commit, around lines 9510-9670. The function has three blit paths:use_srgb_shader(~9612, gated by!sys->shell_mode) — non-shell SRGB linearization.use_scale_shader(~9613, gated bysys->shell_mode && needs_scale) — shell-mode oversize content.CopySubresourceRegionper view.For Unity (per-view swapchains, content not oversized, shell mode), the else branch (raw copy) should run. Either:
The fact that
cube_handle_d3d12_winworks (D3D12 client + D3D11 service) rules out a fundamental D3D12-vs-D3D11 incompatibility — the difference is purely in the swapchain layout / submission pattern.Affected apps
Not affected
cube_handle_d3d12_win(single atlas-tiled swapchain)Out of scope
UserGpuPreferencesregistry).