Skip to content

feat(d3d12): multi-screen segments + per-segment views for the in-process D3D12 compositor (M6 + M3) - #1879

Merged
dfattal merged 4 commits into
mainfrom
feat/win-d3d12-segments
Oct 9, 2026
Merged

dfattal merged 4 commits into
mainfrom
feat/win-d3d12-segments

Conversation

@dfattal

@dfattal dfattal commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

What

Multi-screen segments (ADR-047 D2, M6) and per-segment views (M3) for the in-process D3D12 compositor — the path the Unity display provider runs on. The twin of #1857 / #1863 for D3D11, mirroring the pre-split D3D11 behaviour (the #918 split path is untouched; nothing in compositor/d3d11/ changes).

ABI (append-only, no XRT_PLUGIN_API_VERSION_CURRENT bump — ABI stays 5)

  • xrt_plugin_iface::create_dp_d3d12_for_screen after create_dp_d3d11_for_screen; announced by XRT_PLUGIN_IFACE_HAS_CREATE_DP_D3D12_FOR_SCREEN (same CREATE_ naming as the D3D11 / Vulkan macros), probed by xrt_plugin_iface_has_create_dp_d3d12_for_screen().
  • xrt_display_processor_d3d12::set_present_origin after get_background_preview (slot 24); announced by XRT_DP_D3D12_HAS_PRESENT_ORIGIN, struct_size-gated helper. Plus xrt_display_processor_d3d12_tolerates_resample().
  • tests_stereo_camera pins the new iface tail; docs/reference/xrt_plugin_iface.md documents the Windows per-screen slots.
  • sim_display implements both on D3D12 (screen-bound instance answers its own screen's metrics and confines viewport + scissor to the canvas), so the CI self-test keeps a complete sim-display.

Runtime

Tests

  • Local build (scripts\build_windows.bat build, then test-apps): both exit 0. check_displayxr_app.py on the D3D12 cube: clean.
  • ctest -C Release: 100/103 pass; new/changed tests_oxr_segment_views, tests_stereo_camera, tests_comp_segments pass. The 3 failures (tests_oxr_view_space_mode_floor, _pinned, _floor_off) are environmental on this box (they need SIM_DISPLAY_OUTPUT=quad to take on the registered DP) and fail identically on a peer worktree build without this change.
  • Not yet run on hardware (rig recipe below).

Rig check (two monitors, sim_display or Leia)

  1. Plug-in pairing: the Leia side needs DisplayXR/displayxr-leia-plugin feat/win-d3d12-dp-segments built against this branch; sim_display needs nothing.
  2. _package\run_cube_handle_d3d12_win.bat, drag the window so it straddles the seam between the primary (system-default) screen and the second one (at least 2 px on each side).
  3. Runtime log (%LOCALAPPDATA%\DisplayXR\DisplayXR_cube_handle_d3d12_win.*.log):
    • segments: N screens, plug-in '…' — a window spanning monitors is woven per monitor (multi-screen M6, D3D12) (session start)
    • segments: created a segment DP for screen 0x… ('\\.\DISPLAYn', plug-in '…') via create_dp_d3d12_for_screen (two DPs: the primary + this one)
    • segments: D3D12 window x,y WxH -> … (INFO, the table)
    • segments: D3D12 per-segment views routed — 2 segment(s) x 2 view(s) … (INFO, M3) and the diag(oxr): log the per-segment view-set decision once per change (multi-screen M3) #1870 WARN per-segment views: segments=2 capacity=2 active=… -> total=4; … (a 0.000x0.000 m segment there means the screen has no physical size and the layout refused)
    • App log: view sets: routed [display 0x… views 0..1 at …] [display 0x… views 2..3 at …] (activeViewCount=4, rendering 4)
  4. Dragging back onto one screen: segments: D3D12 window … is on the primary screen only — single-DP path, then after ~0.5 s retiring the D3D12 segment DP (INFO).
  5. Kill switch: DXR_SEGMENTS=0 → single DP, view sets: single.

🤖 Generated with Claude Code

dfattal and others added 4 commits October 9, 2026 03:14
…rigin (multi-screen M6)

The D3D12 twins of the D3D11 per-screen slots, appended per ADR-020 with
no XRT_PLUGIN_API_VERSION_CURRENT bump (ABI stays 5):

- xrt_plugin_iface::create_dp_d3d12_for_screen, after
  create_dp_d3d11_for_screen; announced by
  XRT_PLUGIN_IFACE_HAS_CREATE_DP_D3D12_FOR_SCREEN, probed with
  xrt_plugin_iface_has_create_dp_d3d12_for_screen().
- xrt_display_processor_d3d12::set_present_origin, after
  get_background_preview; announced by XRT_DP_D3D12_HAS_PRESENT_ORIGIN,
  called through a struct_size-gated helper. Plus a D3D12
  tolerates_resample() helper mirroring the D3D11 one.

The D3D12 contract differs in one place: every segment records onto one
command list, which carries no viewport/scissor a DP could inherit, so a
screen-bound DP sets both from the canvas itself.

sim_display implements both on D3D12 (screen-bound instance answers its
own screen's metrics and confines viewport + scissor to the canvas) so
the CI self-test keeps a complete sim-display. tests_stereo_camera pins
the new iface tail; xrt_plugin_iface.md documents the Windows slots.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…gment views (multi-screen M6 + M3)

The in-process D3D12 compositor (the Unity display provider's path) gets
the pre-split D3D11 segment model:

- comp_d3d12_segments.{h,cpp}: the twin of comp_d3d11_segments — screen
  table join, DP lifecycle with hysteresis, decide loop, primary-first
  weave order, flat-2D fill. GPU work in D3D12 terms: per-screen crop
  textures (COMMON -> COPY_DEST -> COMMON), each with its own slot in a
  module-owned shader-visible SRV heap (descriptors are read at execute
  time, so one slot cannot serve two DPs on one list), explicit source
  barriers, viewport + scissor = segment before every DP.
- comp_d3d12_compositor_set_screens + d3d12_segments_tick at the weave
  site: off zero-copy, the #918 split, the #1264 reroute, a zones /
  Local2D canvas, and a back buffer that does not match the client area;
  the window rect comes from the HWND in device px. Repaints take the
  same path.
- M3: comp_d3d12_compositor_get_segment_metrics / set_view_routing, the
  renderer's mosaic routing (projection layers per segment rect with the
  tile's blend mode decided once; quads + equirect2 once per segment with
  that segment's camera), comp_d3d12_renderer_blit_rect for the flat fill,
  and a routed frame is never zero-copy.
- oxr: session create / xrLocateViews / xrEndFrame hand-offs for D3D12;
  the Windows view-set capacity counts a D3D11 OR D3D12 factory
  (oxr_segment_views_win_entry_has_dp, host-tested).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ti-screen M3)

Port of the cube_handle_d3d11_win M3 change: chain XrViewActivityStateDXR
+ XrViewDisplayBindingsDXR, render every view set the runtime hands out,
each at its display's segment rect inside its tile, and log the bindings
once per change ("view sets: ...") — the rig check's app-side proof.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <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.

1 participant