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
8 changes: 8 additions & 0 deletions docs/architecture/comp-segments.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ deferred-release list because the immediate context executes in order). What dif
The service / hosted path is not segmented yet (ADR-035 amendment, M6 second half).
- **sim_display** implements both slots on D3D11 too, so a two-monitor Windows box with no vendor
hardware exercises the split path (anaglyph on both halves).
- **Per-segment views (M3) on Windows** ride the same state-tracker path as Linux: `oxr_system` counts
the registry's D3D11 factories for the view-set capacity, `xrLocateViews` reads
`comp_d3d11_compositor_get_segment_metrics` (the last weave's table + each segment's eyes, the
secondary screen's from its own DP — on a Leia panel the pinned nominal viewer), `xrEndFrame` hands
the routing to `comp_d3d11_compositor_set_view_routing`, and the renderer paints each segment's
views at that segment's rect inside every tile (the mosaic; quads and equirect2 once per segment
with that segment's camera). A routed frame is never zero-copy. This is what makes an untracked
second panel render from its own viewer instead of the tracked panel's.

## Per-segment views (M3)

Expand Down
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). HWND stays with the primary screen's DP (majority hand-off is a follow-up); service/hosted half not started.** | 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). HWND stays with the primary screen's DP (majority hand-off is a follow-up); 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; service/hosted half not started.** | 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
187 changes: 178 additions & 9 deletions src/xrt/compositor/d3d11/comp_d3d11_compositor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,16 @@ struct comp_d3d11_compositor
uint64_t seg_pinned_display_id;
struct comp_seg_rect seg_window; //!< the window's client area in desktop device px
bool have_seg_window;
/*!
* Multi-screen M3: what the app thread reads (xrLocateViews) and hands
* back (xrEndFrame) while the weave thread owns the segment manager —
* a small lock of its own, never c->mutex (the repaint thread holds that
* across its whole replay).
*/
std::mutex seg_pub_mutex;
struct xrt_segment_metrics seg_pub; //!< the table the last weave took (count 0 = one view set)
struct xrt_segment_view_routing seg_route; //!< the routing the last locate handed out
struct comp_d3d11_eff_layout seg_route_logged; //!< change-only logging of the routed layout

//! System devices (for qwerty driver keyboard input and display mode toggle).
struct xrt_system_devices *xsysd;
Expand Down Expand Up @@ -2156,6 +2166,85 @@ d3d11_hash_target(ID3D11Device *device,
* re-reads an app-owned texture and never ticks a per-frame state machine — see
* d3d11_composite_zone_mask, and the D3D12 leg for what happens when it does.
*/
/*!
* Multi-screen M3: turn the routing the last xrLocateViews handed out into
* per-segment tile rects on this frame's effective layout. The D3D11 twin of
* vk_route_effective_layout: on any inconsistency (a segment's views past
* what the layer submitted, a degenerate rect) the routing is dropped for the
* frame and the tiles are painted unrouted.
*/
static void
d3d11_route_effective_layout(struct comp_d3d11_compositor *c, uint32_t layer_view_count)
{
struct xrt_segment_view_routing r;
{
std::lock_guard<std::mutex> seg_lock(c->seg_pub_mutex);
r = c->seg_route;
}
struct comp_d3d11_eff_layout *L = &c->eff_layout;
L->route_count = 0;
if (r.count == 0 || r.count > XRT_MAX_SEGMENTS || L->views == 0 || L->tile_w == 0 || L->tile_h == 0) {
return;
}
const struct comp_seg_rect canvas = {
r.canvas.offset.w,
r.canvas.offset.h,
(uint32_t)(r.canvas.extent.w > 0 ? r.canvas.extent.w : 0),
(uint32_t)(r.canvas.extent.h > 0 ? r.canvas.extent.h : 0),
};
for (uint32_t k = 0; k < r.count; k++) {
// Across a 2D<->3D toggle the mode (tile count) can change between
// the locate and this commit: route the views the tiles can hold.
const uint32_t n = r.view_count[k] < L->views ? r.view_count[k] : L->views;
if (n == 0 || r.first_view[k] + n > layer_view_count) {
return;
}
const struct comp_seg_rect seg = {
r.rect[k].offset.w,
r.rect[k].offset.h,
(uint32_t)(r.rect[k].extent.w > 0 ? r.rect[k].extent.w : 0),
(uint32_t)(r.rect[k].extent.h > 0 ? r.rect[k].extent.h : 0),
};
struct comp_seg_rect tr;
if (!comp_segments_tile_rect(&seg, &canvas, L->tile_w, L->tile_h, &tr)) {
return;
}
L->route[k].first_view = r.first_view[k];
L->route[k].view_count = n;
L->route[k].x = tr.x;
L->route[k].y = tr.y;
L->route[k].w = tr.w;
L->route[k].h = tr.h;
}
L->route_count = r.count;

// One INFO line per routing change (a move, a resize, a mode switch).
if (memcmp(L->route, c->seg_route_logged.route, sizeof(L->route)) != 0 ||
L->route_count != c->seg_route_logged.route_count || L->tile_w != c->seg_route_logged.tile_w ||
L->tile_h != c->seg_route_logged.tile_h) {
c->seg_route_logged = *L;
U_LOG_I("segments: per-segment views routed — %u segment(s) x %u view(s), tile %ux%u; [0] views %u.. "
"at %d,%d %ux%u; [1] views %u.. at %d,%d %ux%u",
L->route_count, L->views, L->tile_w, L->tile_h, L->route[0].first_view, L->route[0].x,
L->route[0].y, L->route[0].w, L->route[0].h, L->route_count > 1 ? L->route[1].first_view : 0,
L->route_count > 1 ? L->route[1].x : 0, L->route_count > 1 ? L->route[1].y : 0,
L->route_count > 1 ? L->route[1].w : 0, L->route_count > 1 ? L->route[1].h : 0);
}
}

//! The projection-class layer's submitted view count (what routing may index).
static uint32_t
d3d11_projection_layer_view_count(const struct comp_layer_accum *layers)
{
for (uint32_t i = 0; i < layers->layer_count; i++) {
const enum xrt_layer_type t = layers->layers[i].data.type;
if (t == XRT_LAYER_PROJECTION || t == XRT_LAYER_PROJECTION_DEPTH) {
return layers->layers[i].data.view_count;
}
}
return 0;
}

static bool
d3d11_dp_weave(struct comp_d3d11_compositor *c, bool is_repaint)
{
Expand Down Expand Up @@ -2591,15 +2680,19 @@ d3d11_dp_weave(struct comp_d3d11_compositor *c, bool is_repaint)
bool seg_split = false;
if (!zero_copy && !c->split_active && !eff_canvas.valid && c->have_seg_window && c->seg_screens != nullptr &&
atlas_srv != nullptr) {
if (c->seg_rebuild) {
comp_d3d11_segments_destroy(&c->segments);
c->seg_rebuild = false;
}
if (c->segments == nullptr) {
c->segments = comp_d3d11_segments_create(d3d11_out_device(c));
if (c->segments != nullptr) {
comp_d3d11_segments_set_screens(c->segments, c->seg_screens, c->seg_sys_info,
c->seg_pinned_display_id);
if (c->seg_rebuild || c->segments == nullptr) {
// The app thread reads c->segments for eyes (M3): swap under the lock.
std::lock_guard<std::mutex> seg_lock(c->seg_pub_mutex);
if (c->seg_rebuild) {
comp_d3d11_segments_destroy(&c->segments);
c->seg_rebuild = false;
}
if (c->segments == nullptr) {
c->segments = comp_d3d11_segments_create(d3d11_out_device(c));
if (c->segments != nullptr) {
comp_d3d11_segments_set_screens(c->segments, c->seg_screens, c->seg_sys_info,
c->seg_pinned_display_id);
}
}
}
if (comp_d3d11_segments_enabled(c->segments)) {
Expand All @@ -2613,6 +2706,29 @@ d3d11_dp_weave(struct comp_d3d11_compositor *c, bool is_repaint)
}
}

/*
* Multi-screen M3: publish the table this weave took, for the next
* xrLocateViews to frame per-segment views from. Nothing is published
* (count 0 = one view set) unless the window really is woven per segment.
*/
{
struct xrt_segment_metrics m;
memset(&m, 0, sizeof(m));
if (seg_split) {
const struct comp_seg_rect canvas = {0, 0, c->seg_window.w, c->seg_window.h};
(void)comp_d3d11_segments_get_metrics(c->segments, &c->seg_window, &canvas,
c->display_processor != nullptr, &m);
}
std::lock_guard<std::mutex> seg_lock(c->seg_pub_mutex);
m.generation = c->seg_pub.generation;
struct xrt_segment_metrics prev = c->seg_pub;
prev.generation = m.generation;
if (memcmp(&prev, &m, sizeof(m)) != 0) {
m.generation++;
}
c->seg_pub = m;
}

if (seg_split) {
struct comp_d3d11_segments_frame sf = {};
sf.context = d3d11_out_context(c);
Expand Down Expand Up @@ -3464,6 +3580,8 @@ d3d11_compositor_layer_commit(struct xrt_compositor *xc, xrt_graphics_sync_handl
// and the capture providers — they must all agree on the frame's
// geometry.
comp_d3d11_renderer_compute_effective_layout(c->renderer, &c->layer_accum, &c->eff_layout);
// Multi-screen M3: per-segment views -> a mosaic per tile (no-op unless routed).
d3d11_route_effective_layout(c, d3d11_projection_layer_view_count(&c->layer_accum));

// Zero-copy check: can we pass the app's swapchain directly to the DP?
//
Expand Down Expand Up @@ -3568,6 +3686,13 @@ d3d11_compositor_layer_commit(struct xrt_compositor *xc, xrt_graphics_sync_handl
}


// Multi-screen M3: a routed frame is a mosaic the renderer must paint; the
// app's swapchain image can never be handed to the DP as-is.
if (c->eff_layout.route_count > 0) {
zero_copy = false;
zc_srv = nullptr;
}

// No CPU-side wait for the app's GPU work here, by design: the app renders
// on this same immediate context, and D3D11 executes it in submission order,
// so every read of an app swapchain image below is already ordered after
Expand Down Expand Up @@ -8390,6 +8515,50 @@ comp_d3d11_compositor_set_screens(struct xrt_compositor *xc,
free(c->seg_sys_info);
c->seg_sys_info = info_copy;
c->seg_pinned_display_id = pinned_display_id;
// ...and nothing may frame views from the old table meanwhile.
std::lock_guard<std::mutex> seg_lock(c->seg_pub_mutex);
memset(&c->seg_pub, 0, sizeof(c->seg_pub));
}

extern "C" bool
comp_d3d11_compositor_get_segment_metrics(struct xrt_compositor *xc, struct xrt_segment_metrics *out)
{
if (out == nullptr) {
return false;
}
memset(out, 0, sizeof(*out));
if (xc == nullptr) {
return false;
}
struct comp_d3d11_compositor *c = d3d11_comp(xc);
std::lock_guard<std::mutex> seg_lock(c->seg_pub_mutex);
*out = c->seg_pub;
// The eyes are predicted NOW, per segment: the primary from the session's
// own DP, every other screen from its segment DP (guarded against the
// weave destroying it: the manager is swapped under this same lock).
for (uint32_t k = 0; k < out->count && k < XRT_MAX_SEGMENTS; k++) {
struct xrt_segment_metric *s = &out->seg[k];
if (s->is_primary) {
s->have_eyes = c->display_processor != nullptr &&
xrt_display_processor_d3d11_get_predicted_eye_positions(c->display_processor,
&s->eyes) &&
s->eyes.valid;
} else {
s->have_eyes = comp_d3d11_segments_get_eyes(c->segments, s->screen_id, &s->eyes);
}
}
return out->count > 0;
}

extern "C" void
comp_d3d11_compositor_set_view_routing(struct xrt_compositor *xc, const struct xrt_segment_view_routing *routing)
{
if (xc == nullptr || routing == nullptr) {
return;
}
struct comp_d3d11_compositor *c = d3d11_comp(xc);
std::lock_guard<std::mutex> seg_lock(c->seg_pub_mutex);
c->seg_route = *routing;
}

extern "C" void
Expand Down
22 changes: 22 additions & 0 deletions src/xrt/compositor/d3d11/comp_d3d11_compositor.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,28 @@ comp_d3d11_compositor_set_screens(struct xrt_compositor *xc,
const struct xrt_system_compositor_info *info,
uint64_t pinned_display_id);

/*!
* Multi-screen M3 (Windows): the segment table the last weave took, with each
* segment's eyes predicted NOW (the primary from the session's DP, the others
* from their segment DP), for xrLocateViews to frame per-segment views.
* Thread-safe against the weave. `out->count == 0` = one view set.
*
* @ingroup comp_d3d11
*/
bool
comp_d3d11_compositor_get_segment_metrics(struct xrt_compositor *xc, struct xrt_segment_metrics *out);

/*!
* Multi-screen M3 (Windows): which views of the next frame belong to which
* window segment — what the app's last xrLocateViews handed out. The renderer
* paints each segment's views into that segment's rect of every tile (a
* mosaic) and the segment path crops them out per screen. Thread-safe.
*
* @ingroup comp_d3d11
*/
void
comp_d3d11_compositor_set_view_routing(struct xrt_compositor *xc, const struct xrt_segment_view_routing *routing);

/*!
* XR_DXR_depth_budget: whether this session asked for the rear depth budget.
*
Expand Down
Loading
Loading