Skip to content

Per-view-swapchain blit drops content for Unity/Unreal D3D12 clients #184

@dfattal

Description

@dfattal

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 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.

Reproduction

  1. On a hybrid Optimus laptop with the latest shell build (Shell: drop-in manifest discovery + GPU pref + CWD fixes (#182) #183 merged):
  2. Hand-author a registered manifest in %LOCALAPPDATA%\DisplayXR\apps\ pointing at a stock Unity D3D12 build (or Unreal D3D12 build).
  3. displayxr-service.exe → Ctrl+Space → Ctrl+L → click the Unity tile.
  4. 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.
  5. Repeat with cube_handle_d3d12_win in the same session — renders correctly.

What the logs show (Unity D3D12)

Client side (displayxr.log next to the exe):

xrCreateSwapchain: format=29 size=1920x1080 samples=1 faces=1 arrays=1 mips=1
xrCreateSwapchain: OK swapchain=...
xrCreateSwapchain: format=29 size=1920x1080 samples=1 faces=1 arrays=1 mips=1     ← TWO swapchains
xrCreateSwapchain: OK swapchain=...
...
xrEndFrame: 1 layers
  layer[0] PROJECTION: viewCount=2
    view[0]: pos=(-2.05, 2.26, 7.35) hfov=13.2 arrayIdx=0 rect=(0,0 1920x1080)
    view[1]: pos=(-1.22, 2.27, 7.40) hfov=13.8 arrayIdx=0 rect=(0,0 1920x1080)

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:

  • Zero-copy (~9479) — single tiled swapchain matching atlas dims exactly. cube_handle_d3d12_win likely hits this.
  • use_srgb_shader (~9612, gated by !sys->shell_mode) — non-shell SRGB linearization.
  • use_scale_shader (~9613, gated by sys->shell_mode && needs_scale) — shell-mode oversize content.
  • 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

Not affected

  • cube_handle_d3d12_win (single atlas-tiled swapchain)
  • All D3D11 handle apps (different code path)
  • Vulkan / Metal / GL handle apps (different compositors)

Out of scope

Metadata

Metadata

Assignees

No one assigned

    Labels

    shell3D Shell / spatial window manager

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions