Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,45 @@ from the other side of the wire. Decisions:
single-screen box — sends nothing new and is composited byte for byte as before.

Detail: `docs/architecture/comp-segments.md` § *Service / IPC path*.

## Amendment 4 (2026-10-10): present-owner weave rects are segmented per screen

The DisplayXR Browser does not reach any of the sites above: it is a present-owner on the
`XR_DXR_weave` service path. It binds its HWND, submits pre-weave pixels plus window-relative
rects, and composites the woven handback itself, with the eyes flowing back out to it. That site
wove with the one session DP and returned one eye pair, so an inline-3D element straddling two
panels was woven on the primary screen's lattice and rendered from the primary screen's viewer on
both halves. Decisions:

- **`weave_submit` segments the bound window exactly as the direct pipeline does.** Same opt-in
(`compositor_segments_enable`, the service's own screen list), same manager
(`comp_d3d11_segments`), same table (`comp_segments_compute` over the window's client area
with canvas = the whole window), same rules: each screen's DP weaves its segment with
canvas, viewport and scissor = the segment and its own present origin; flat 2D where a DP
cannot weave; the session's weave DP keeps the HWND and the primary screen. The manager is the
present-owner's own, made on the device its woven handback lives on (`sys->device`, where the
#1172 ingest DP is), the same rule as Amendment 1 (a segment DP lives where its target is).
- **Only the window-canvas layouts are segmented**: the v3 batch (window-sized SBS scratch) and
the v6 N-view atlas. The legacy single-rect submit weaves one element-sized canvas per call and
stays single-DP. A rect is split implicitly: every pixel of it is woven by the screen it lands
on.
- **The 2D overlay is composited after the per-screen weave** by the runtime's blit, not handed to
a DP: it is window-sized and no segment DP covers the whole window.
- **Eyes out per screen: an `XR_DXR_weave` revision (spec v19).** `XrWeaveOutputRectPartsDXR`,
chained on `XrWeaveOutputDXR`, returns the window's segments (each with its screen's eyes in
that screen's display space, its physical size and whether it is woven) and every submitted
rect cut at the seams (`u_weave_rect_parts`: per part, the window rect, the rect-relative rect
and the on-screen rect). The table crosses the wire by value in an appended call,
`weave_get_segments` (the per-segment views' `xrt_segment_metrics`), fetched only when the
caller chains the struct; `weave_submit`'s reply is unchanged. The client cuts the rects
itself, so they cross the wire once. A caller that does not chain the struct, and every window
on one screen, sees exactly the pre-v19 behaviour.
- **Drag snap follows the majority screen's lattice.** `xrWeaveSnapWindowRectDXR` asks the
segment DP of the screen holding the majority of the window at the PROPOSED position, with
Amendment 2's hysteresis (`comp_segments_owner_*`, 20 % margin held 0.5 s) kept separately
from the handle owner because nothing is rebuilt; it falls back to the panel DP when that
screen has no DP yet or its DP has no snap.

The browser's half (rendering a straddling rect's two parts from two viewers) lives in
`displayxr-browser-pvt`. Detail: `docs/architecture/comp-segments.md` § *Present-owners*;
contract: `docs/specs/extensions/XR_DXR_weave.md` §5g.
42 changes: 40 additions & 2 deletions docs/architecture/comp-segments.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,9 @@ An `_ipc` client (`XRT_FORCE_MODE=ipc`, or any app under a running service) is c
device px. One client's segment DPs exist at a time: a focus change releases the outgoing
presenter's, and a table older than 250 ms (the client no longer weaves directly — focus
moved, the workspace took over) is not handed out, so that client locates one view set. A hosted client presents into the service's window on the panel, a
present-owner / client-texture client weaves on its own thread and a zones frame keeps the
single DP: none is segmented.
client-texture client weaves on its own and a zones frame keeps the single DP: none is
segmented here. A present-owner weaves on its own thread and is segmented there (next
section).
- **Compose / shell mode** (`multi_compositor_render`) is unchanged: one presenter, the
service window on the panel, so a workspace window is never segmented and its table stays
empty. Per-screen workspace presenters are a separate decision.
Expand All @@ -365,3 +366,40 @@ An `_ipc` client (`XRT_FORCE_MODE=ipc`, or any app under a running service) is c
drawn once per tile with that tile's camera (in-process draws them per segment), and a
second projection layer is routed only where the per-tile pass visits it (segment 0).

## Present-owners on the weave service (Windows, ADR-047 Amendment 4)

An `XR_DXR_weave` present-owner (the DisplayXR Browser, `weave_rpc_probe_d3d11_win`) never reaches
the direct pipeline: it is `PRESENTER_SELF`, and `comp_d3d11_service_weave_submit` weaves its
pixels into a handback texture on its own IPC thread. That submit is segmented by the same rules:

- **Opt-in and manager.** The same `compositor_segments_enable` list, but a manager of the
present-owner's own (`weave_segs`, rebuilt on `weave_seg_rebuild` / a device change), made on
`sys->device` — the device the woven handback and the #1172 ingest DP live on. It never shares
the direct pipeline's manager: different device, different thread, and a focus change does not
release it. Released with the compositor (under `render_mutex`).
- **Which submits.** v3 batch and v6 N-view: both weave the whole window as one canvas (the SBS
scratch at window size, or the content-sized atlas), which is what `comp_segments_compute` cuts.
`weave_seg_update` reads the bound HWND's client area (`svc_seg_window_rect`) and runs the table +
lifecycle; a client area that does not match the handback size (a resize in flight) is woven
whole that frame. Legacy single-rect submits (canvas = one element) are never segmented.
- **The weave.** `weave_seg_record` replaces the single `process_atlas`: `comp_d3d11_segments_record`
crops each segment's views out of the canvas atlas, the session's weave DP weaves the primary
screen's segment, each other screen's windowless DP its own (present origin, viewport, scissor
= the segment), transparent background and `client_presents` declared to the segment DPs as to
the session DP (`comp_d3d11_segments_frame::client_presents`), flat 2D (one view) where nothing
can weave. The v4 overlay is told "none" for that frame and composited by the runtime's
post-weave blit over the whole handback instead.
- **Eyes out.** After the fence, `weave_seg_publish` stores the table (`comp_segments_publish`:
the generation moves on geometry only) with each segment's eyes — the primary's are the
submit's own base eyes, the others predicted from their segment DPs.
`weave_get_segments` (appended to `proto.json`) hands it to the client, which fills
`XrWeaveOutputRectPartsDXR` and cuts the submitted rects at the seams with
`u_weave_rect_parts` (`auxiliary/util/u_weave_rect_parts.h`, unit-tested by
`tests/tests_weave_segments.cpp`). Fetched only when the caller chains the struct.
- **Drag snap.** `comp_d3d11_service_weave_snap_window_rect` first asks
`comp_d3d11_segments_snap_dp` for the DP of the screen holding the majority of the window at the
proposed position (a separate `comp_segments_owner`, Amendment 2's margin and hold, no failure
latch since nothing is torn down), and falls back to the panel DP's snap.
- **Not covered:** the v8 hardware wish is still published to the panel DP only (the primary
screen's lens); a window across more than `XRT_MAX_SEGMENTS` screens is woven per screen but
reported unsegmented (one eye set).
2 changes: 1 addition & 1 deletion docs/roadmap/multi-screen.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ multiview + display_info v22. An app may also pin itself to one display
| **M3** Per-segment views | Same window: `xrLocateViews` returns 4 views, each half rendered from its own frustum | oxr: per-segment Kooima + eyes from the segment DP; `XrViewDisplayBindingsDXR`; `activeViewCount`; tile routing by segment; legacy stereo = majority segment + 2D blit | none | cube renders the correct perspective on each half; `check_displayxr_app.py` rule for v22 |
| **M4 = T1** Mixed vendor on `ds1-linux` | **Leia weave + DS1 tracking on the DS1 half, sim anaglyph on the laptop half** | loader: two plug-ins resident, DPs created from each screen's factory; plug-in display info per screen | leia-linux: `window = 0` always (one line, `leia_sr_linux_sdk.c:1470`); per-DP display state instead of `g_ctx.display` / statics; `srLensDisable` right after `srCreateLens` if the lens must start off. Confirmed by the SR side: a windowless Linux Vulkan weaver fed the DS1 segment + present origin weaves exactly that viewport, votes the lens on once, then the DP owns it. Works on today's SDK for **one** Leia panel. With SR-P0 (verified on the DS1 2026-10-07, branch `ST-5481-weaver-external-routing`): check `SrWeaverRoutingCapabilities` (tag 24) chained on `SrRuntimeCapabilities.pNext`, then chain `SrWeaverRoutingInfo{mode=SR_WEAVER_ROUTING_EXTERNAL}` (tag 22) on `SrWeaverCreateInfoVulkan.pNext`, `window = 0`, present origin per frame, lens via `srLensEnable/Disable` | David eyeballs it; screenshot both halves |
| **M5 = T2** Two Leia panels | Each panel tracked by its own camera | per-screen tracker plumbing already in place from M3 | **SR-P1** enumeration + identity, **SR-P2** bind display/weaver/lens/tracker by `displayId`, **SR-P3** N trackers + lens per display (SRService refactor, ST-3160 camera pairing). Plug-in: per-DP SR display + tracker, claims from `srEnumerateDisplays` | needs a second panel on a dev box |
| **M6 = T3** Service / hosted / Windows | Shell and hosted apps on N screens; Windows parity | ADR-035 amendment: one presenter per 3D screen in the service, clients segmented across presenters; hosted window per screen (#715 fix folds in); Windows: HWND to majority segment DP, windowless secondaries | SR-P0 **already ships on q3**, so no SDK gate: plug-in Windows DP chains `SrWeaverRoutingInfo{EXTERNAL, KEEP_DRAG_SNAP}` on the HWND DP and `{EXTERNAL}` + NULL HWND on secondaries, forwards `set_present_origin` → `srWeaverSetPresentOrigin` in **device px relative to that SR display, rotation applied** (a DPI-virtualised origin weaves on the wrong lattice and looks plausible — see `docs/reference/dpi-awareness.md`). **In-process D3D11 half: runtime `comp_d3d11_segments` + two appended slots (`create_dp_d3d11_for_screen`, D3D11 `set_present_origin`, no ABI bump) + sim_display D3D11; plug-in: screen-bound D3D11 DP chains EXTERNAL (+KEEP_DRAG_SNAP with the HWND) + `SrDisplayBindingInfo`, per-frame `srWeaverSetPresentOrigin` when windowless, scissor = canvas, and pins `srWeaverSetSimulatedViewer` on a non-active display (lens left to the active panel until SR D3). the HWND follows the majority screen (ADR-047 Amendment 2: 20 % margin held 0.5 s, windowless replacement first, rollback on failure; rig-unverified); M3 per-segment views ported too (D3D11 mosaic routing, `get_segment_metrics` / `set_view_routing`), so a straddling window's second panel renders from its own viewer; works under the #918 weave-on-scanout split (ADR-039, the hybrid-box default — segment DPs on the output device, fed from the egress slot; ADR-047 Amendment 1), so no `DXR_D3D_FORCE_GPU` / `DXR_SPLIT_SAME_ADAPTER` knobs are needed. In-process D3D12 (the Unity provider's path): the same, pre-split — `comp_d3d12_segments`, appended `create_dp_d3d12_for_screen` + D3D12 `set_present_origin`, sim_display D3D12, D3D12 mosaic routing; rig-checked with the split off; single-DP under the #918 split for now. Service half: a direct (non-workspace) IPC client's window is segmented by the service's direct pipeline, with per-segment views over IPC (ADR-047 Amendment 3); compose/shell mode and hosted windows stay single-screen.** | shell with a window straddling two panels |
| **M6 = T3** Service / hosted / Windows | Shell and hosted apps on N screens; Windows parity | ADR-035 amendment: one presenter per 3D screen in the service, clients segmented across presenters; hosted window per screen (#715 fix folds in); Windows: HWND to majority segment DP, windowless secondaries | SR-P0 **already ships on q3**, so no SDK gate: plug-in Windows DP chains `SrWeaverRoutingInfo{EXTERNAL, KEEP_DRAG_SNAP}` on the HWND DP and `{EXTERNAL}` + NULL HWND on secondaries, forwards `set_present_origin` → `srWeaverSetPresentOrigin` in **device px relative to that SR display, rotation applied** (a DPI-virtualised origin weaves on the wrong lattice and looks plausible — see `docs/reference/dpi-awareness.md`). **In-process D3D11 half: runtime `comp_d3d11_segments` + two appended slots (`create_dp_d3d11_for_screen`, D3D11 `set_present_origin`, no ABI bump) + sim_display D3D11; plug-in: screen-bound D3D11 DP chains EXTERNAL (+KEEP_DRAG_SNAP with the HWND) + `SrDisplayBindingInfo`, per-frame `srWeaverSetPresentOrigin` when windowless, scissor = canvas, and pins `srWeaverSetSimulatedViewer` on a non-active display (lens left to the active panel until SR D3). the HWND follows the majority screen (ADR-047 Amendment 2: 20 % margin held 0.5 s, windowless replacement first, rollback on failure; rig-unverified); M3 per-segment views ported too (D3D11 mosaic routing, `get_segment_metrics` / `set_view_routing`), so a straddling window's second panel renders from its own viewer; works under the #918 weave-on-scanout split (ADR-039, the hybrid-box default — segment DPs on the output device, fed from the egress slot; ADR-047 Amendment 1), so no `DXR_D3D_FORCE_GPU` / `DXR_SPLIT_SAME_ADAPTER` knobs are needed. In-process D3D12 (the Unity provider's path): the same, pre-split — `comp_d3d12_segments`, appended `create_dp_d3d12_for_screen` + D3D12 `set_present_origin`, sim_display D3D12, D3D12 mosaic routing; rig-checked with the split off; single-DP under the #918 split for now. Service half: a direct (non-workspace) IPC client's window is segmented by the service's direct pipeline, with per-segment views over IPC (ADR-047 Amendment 3); compose/shell mode and hosted windows stay single-screen. Present-owners (the browser, `XR_DXR_weave`): `weave_submit` segments a bound window per screen for batch / v6 submits, returns every screen's eyes and each weave rect cut at the seams (`XrWeaveOutputRectPartsDXR`, spec v19, ADR-047 Amendment 4, #1884), and the drag snap follows the majority screen's lattice; the browser's per-part rendering is in displayxr-browser-pvt.** | shell with a window straddling two panels |

Ordering rationale: M0–M3 are hardware-free beyond "two monitors" and exercise everything
with sim_display, so the model is proven before any SR dependency. M4 is David's stated
Expand Down
Loading
Loading