From 0f9ec123321c4b160142af580de15ea1e3c9d0fd Mon Sep 17 00:00:00 2001 From: dfattal Date: Sat, 10 Oct 2026 11:22:04 -0700 Subject: [PATCH 1/3] feat(weave): segment present-owner weave rects per screen (XR_DXR_weave v19) A present-owner's bound window that straddles two 3D screens is now woven per screen by comp_d3d11_service_weave_submit, each screen's part by that screen's display processor, and the caller gets one eye set per screen plus every weave rect cut at the seams (#1884, ADR-047 Amendment 4). Service: batch (v3) and v6 submits from a client that sent compositor_segments_enable run the bound window through a segment manager of the present-owner's own (comp_d3d11_segments on sys->device, where the handback and the #1172 ingest DP live): comp_segments_compute over the client area, the session's weave DP on the primary screen, windowless per-screen DPs elsewhere (scissor = the segment, present origin), flat 2D where nothing can weave, the v4 overlay composited by the post-weave blit. Single-screen windows, legacy single-rect submits and clients that never opted in take the existing path unchanged. The drag snap asks the majority screen's segment DP (comp_d3d11_segments_snap_dp, Amendment 2's hysteresis) and falls back to the panel DP. Extension: XR_DXR_weave spec 19 adds XrWeaveOutputRectPartsDXR (chained on XrWeaveOutputDXR) with XrWeaveScreenSegmentDXR / XrWeaveRectPartDXR; type 1004999292. IPC: weave_get_segments appended to proto.json (the xrt_segment_metrics table with each screen's eyes), fetched only when the caller chains the struct; the client cuts its rects with u_weave_rect_parts. Tests: tests_weave_segments (straddling / single-screen / off-primary / negative-desktop / cap cases, and the weave_get_segments reply round trip), tests_ipc_proto pins the append. Part of #1884 Co-Authored-By: Claude Opus 5.5 --- src/external/openxr_includes/openxr/README.md | 2 +- .../openxr_includes/openxr/XR_DXR_weave.h | 115 +++++- src/xrt/auxiliary/util/u_weave_rect_parts.h | 127 ++++++ .../compositor/d3d11/comp_d3d11_segments.cpp | 55 ++- .../compositor/d3d11/comp_d3d11_segments.h | 27 ++ .../d3d11_service/comp_d3d11_service.cpp | 368 +++++++++++++++++- .../d3d11_service/comp_d3d11_service.h | 15 + src/xrt/ipc/client/ipc_client.h | 9 + src/xrt/ipc/client/ipc_client_compositor.c | 26 ++ src/xrt/ipc/server/ipc_server_handler.c | 27 ++ src/xrt/ipc/shared/proto.json | 6 + src/xrt/state_trackers/oxr/oxr_weave.c | 127 ++++++ tests/CMakeLists.txt | 13 + tests/tests_ipc_proto.py | 31 +- tests/tests_weave_segments.cpp | 305 +++++++++++++++ 15 files changed, 1228 insertions(+), 25 deletions(-) create mode 100644 src/xrt/auxiliary/util/u_weave_rect_parts.h create mode 100644 tests/tests_weave_segments.cpp diff --git a/src/external/openxr_includes/openxr/README.md b/src/external/openxr_includes/openxr/README.md index 26ecdf3e87..18cec30db1 100644 --- a/src/external/openxr_includes/openxr/README.md +++ b/src/external/openxr_includes/openxr/README.md @@ -48,7 +48,7 @@ Rules: | 1004999260–269 | `XR_DXR_depth_budget` | 260 = `XR_TYPE_REAR_DEPTH_BUDGET_DXR`, 261 = `XR_TYPE_CONTENT_BOUNDS_DXR`, 262 = `XR_TYPE_EVENT_DATA_REAR_DEPTH_BUDGET_STATE_CHANGED_DXR`, 263 = `XR_TYPE_CONTENT_MASK_DXR` (ADR-040). Was taken without a registry row — recorded retroactively in #1486 PR 2 | | 1004999270–279 | `XR_DXR_lift` | 270 = `XR_TYPE_LIFT_PROPERTIES_DXR`, 271 = `XR_TYPE_LIFT_STREAM_CREATE_INFO_DXR`, 272 = `XR_TYPE_LIFT_FRAME_SUBMIT_INFO_DXR`, 273 = `XR_TYPE_LIFT_OPTIONS_DXR`, 274 = `XR_TYPE_LIFT_RESULT_DXR`, 275 = `XR_TYPE_WEAVE_SUBMIT_LIFT_RECTS_DXR`, 276 = `XR_TYPE_WEAVE_RECT_LIFT_DXR`, 277 = `XR_TYPE_LIFT_BLOB_DXR` (spec v1, ADR-042). 278 = `XR_OBJECT_TYPE_LIFT_STREAM_DXR` — an `XrObjectType`, not an `XrStructureType`. 279 = `XR_LIFT_NOT_READY_DXR` — a **success**-class `XrResult` (positive) | | 1004999280–289 | `XR_DXR_lift` (continued) | 280 = `XR_TYPE_LIFT_STREAM_STATS_DXR` (spec v1). A second decade because 270–279 is full. Spec v2 (ADR-048): 281 = `XR_TYPE_LIFT_VIEW_CONTROL_DXR`, 282 = `XR_TYPE_LIFT_RESULT_VIEWPOINTS_DXR`. Spec v3 (ADR-048 Addendum A): 283 = `XR_TYPE_LIFT_DEPTH_REQUEST_DXR`, 284 = `XR_TYPE_LIFT_DEPTH_RESULT_DXR`, 285 = `XR_TYPE_LIFT_DEPTH_PROPERTIES_DXR` | -| 1004999290–299 | `XR_DXR_weave` (v14+ additions) | 290 = `XR_TYPE_WEAVE_SUBMIT_OVERLAY_UNCHANGED_DXR` (the v4 overlay atlas is unchanged since the previous submit, so a DP may reuse its lens prefilter of the 2D layer, spec v14, ADR-027 Amendment); 291 = `XR_TYPE_WEAVE_SUBMIT_OVERLAY_FILTER_DXR` (the caller's lens-filter strength for the 2D layer, 0..1, omitted = the DP's default, spec v15). The third weave decade (240–249 is full) | +| 1004999290–299 | `XR_DXR_weave` (v14+ additions) | 290 = `XR_TYPE_WEAVE_SUBMIT_OVERLAY_UNCHANGED_DXR` (the v4 overlay atlas is unchanged since the previous submit, so a DP may reuse its lens prefilter of the 2D layer, spec v14, ADR-027 Amendment); 291 = `XR_TYPE_WEAVE_SUBMIT_OVERLAY_FILTER_DXR` (the caller's lens-filter strength for the 2D layer, 0..1, omitted = the DP's default, spec v15); 292 = `XR_TYPE_WEAVE_OUTPUT_RECT_PARTS_DXR` (per-screen segments of the bound window with per-screen eyes, and each weave rect split into per-screen parts, spec v19, #1884). The third weave decade (240–249 is full) | | 1004999300–309 | `XR_DXR_stereo_camera` | 300 = `XR_TYPE_STEREO_CAMERA_PROPERTIES_DXR`, 301 = `XR_TYPE_STEREO_CAMERA_CALIBRATION_DXR`, 302 = `XR_TYPE_STEREO_CAMERA_STREAM_CREATE_INFO_DXR`, 303 = `XR_TYPE_STEREO_CAMERA_FRAME_DXR`, 304 = `XR_TYPE_STEREO_CAMERA_STREAM_INFO_DXR`, 305 = `XR_TYPE_STEREO_CAMERA_STREAM_TRANSPORT_DXR`, 306 = `XR_TYPE_STEREO_CAMERA_STREAM_STATS_DXR`, 307 = `XR_TYPE_EVENT_DATA_STEREO_CAMERA_STATE_CHANGED_DXR`, 308 = `XR_TYPE_EVENT_DATA_STEREO_CAMERAS_CHANGED_DXR` (spec v1, ADR-043). 309 = `XR_OBJECT_TYPE_STEREO_CAMERA_STREAM_DXR` — an `XrObjectType`, not an `XrStructureType`. **Relocated from 290–299** on the rebase onto post-lift `main`: 290 had meanwhile been taken by `XR_TYPE_WEAVE_SUBMIT_OVERLAY_UNCHANGED_DXR` (weave spec v14). Safe to move: spec v1, draft PR, no shipped app chains it | | 1004999310–319 | `XR_DXR_stereo_camera` (continued) | 310 = `XR_STEREO_CAMERA_FRAME_NOT_READY_DXR` — a **success**-class `XrResult` (positive). A second decade because 300–309 is full. Spec v2 (R3, ADR-043 §7): 311 = `XR_TYPE_STEREO_CAMERA_CLIENT_INFO_DXR` (camera-only client declaration on `XrInstanceCreateInfo`); 312 = `XR_ERROR_STEREO_CAMERA_CONSENT_REFUSED_DXR`, 313 = `XR_ERROR_STEREO_CAMERA_DISABLED_DXR`, 314 = `XR_ERROR_STEREO_CAMERA_BUSY_DXR`, 316 = `XR_ERROR_STEREO_CAMERA_STREAM_ENDED_DXR` — **error**-class `XrResult`s (negated in the header); 315 = `XR_TYPE_EVENT_DATA_STEREO_CAMERA_STREAM_ENDED_DXR` | | 1004999320–329 | `XR_DXR_cursor_depth` | 320 = `XR_TYPE_CURSOR_DEPTH_HINT_DXR`, 321 = `XR_TYPE_CURSOR_DEPTH_PLACEMENT_DXR` (spec v1, ADR-046). 322 = `XR_TYPE_CURSOR_DEPTH_SOURCE_DXR` (spec v2, Phase 3a depth-layer source). 323–329 held for the Phase 2/3b draw + measure requests | diff --git a/src/external/openxr_includes/openxr/XR_DXR_weave.h b/src/external/openxr_includes/openxr/XR_DXR_weave.h index 301b677440..ecf04d8e34 100644 --- a/src/external/openxr_includes/openxr/XR_DXR_weave.h +++ b/src/external/openxr_includes/openxr/XR_DXR_weave.h @@ -220,6 +220,24 @@ * overlay IS its 2D gates on extensionVersion >= 17 before relying on it, and * otherwise keeps a mismatched overlay off the wire itself. * + * Per-screen parts of each weave rect (SPEC_VERSION 19, #1884, ADR-047 + * Amendment 4). On a system with two or more 3D screens the bound window can + * straddle a seam. The runtime then weaves the window PER SCREEN: each screen's + * part is woven by that screen's own display processor, at that screen's own + * interlace phase, and each screen's part has its own viewer (that screen's eye + * tracker). One eye pair per submit is then wrong for the part on the other + * screen, so v19 adds XrWeaveOutputRectPartsDXR, chained on + * XrWeaveOutputDXR::next: the window's per-screen segments, each with its own + * eyes (in THAT screen's display space), and every submitted rect split into + * per-screen parts. The caller renders the pixels of each part from its + * segment's eyes. On a window entirely on one screen (every window on a + * one-screen system) segmentCount is 0 and the base XrWeaveOutputDXR eyes apply + * to everything, exactly as before. A caller that does not chain the struct sees + * no change at all. Batch (v3) and N-view (v6) submits are segmented; a legacy + * single-rect submit is not. Windows (D3D11 service) only; segmentCount is 0 + * elsewhere. xrWeaveSnapWindowRectDXR snaps against the lattice of the screen + * holding the majority of the window (same hysteresis as ADR-047 Amendment 2). + * * Android: an overlay is per submit, and an unreadable one refuses (SPEC_VERSION * 18). No new structure: from v18 the Android engine applies the v16 rule too * (a chained overlay it cannot import refuses the submit, XR_ERROR_RUNTIME_FAILURE, @@ -312,7 +330,7 @@ extern "C" { #endif #define XR_DXR_weave 1 -#define XR_DXR_weave_SPEC_VERSION 18 +#define XR_DXR_weave_SPEC_VERSION 19 #define XR_DXR_WEAVE_EXTENSION_NAME "XR_DXR_weave" // Reserved 1004999190..199. Final values reconcile with the Khronos registry @@ -353,6 +371,8 @@ extern "C" { #define XR_TYPE_WEAVE_SUBMIT_OVERLAY_UNCHANGED_DXR ((XrStructureType)1004999290) // Spec v15 (ADR-027 Amendment): the caller's lens-filter strength for the 2D layer. #define XR_TYPE_WEAVE_SUBMIT_OVERLAY_FILTER_DXR ((XrStructureType)1004999291) +// Spec v19 (#1884): per-screen parts of each weave rect + per-screen eyes. +#define XR_TYPE_WEAVE_OUTPUT_RECT_PARTS_DXR ((XrStructureType)1004999292) //! Upper bound on eye positions carried by XrWeaveSubmitInfoDXR (mirrors the //! runtime's XRT_MAX_VIEWS). Phase 1: carried but unused. @@ -363,6 +383,15 @@ extern "C" { //! visible elements split into multiple batched submits. #define XR_WEAVE_SUBMIT_MAX_RECTS_DXR 32 +//! Upper bound on the per-screen segments one XrWeaveOutputRectPartsDXR reports +//! (spec v19; mirrors the runtime's XRT_MAX_SEGMENTS). A window across more +//! screens than this is reported unsegmented (segmentCount 0). +#define XR_WEAVE_MAX_SEGMENTS_DXR 2 + +//! Upper bound on rect parts one XrWeaveOutputRectPartsDXR reports (spec v19): +//! every submitted rect split at every seam. +#define XR_WEAVE_MAX_RECT_PARTS_DXR (XR_WEAVE_SUBMIT_MAX_RECTS_DXR * XR_WEAVE_MAX_SEGMENTS_DXR) + //! Upper bound on flat rects carried by one XrWeaveSubmitFlatRegionsDXR (spec //! v8). Smaller than XR_WEAVE_SUBMIT_MAX_RECTS_DXR on purpose: the flat list //! describes PANEL FURNITURE (bands, gutters, chrome), not per-element geometry, @@ -1104,6 +1133,90 @@ typedef struct XrWeaveSubmitOverlayFilterDXR { float filterStrength; //!< 0 = no lens filtering .. 1 = full; omit = DP default } XrWeaveSubmitOverlayFilterDXR; +/*! + * @brief One screen's segment of the bound window (spec v19, #1884). + * + * The part of the window's client area that lands on one screen, the display + * processor that weaves it, and that screen's viewer. @c eyes are in THAT + * screen's display space (metres, origin at the screen's centre, the same + * convention XrWeaveOutputDXR::eyes use for the primary screen), so an + * off-axis projection for this segment is built against THIS screen's + * physical rectangle (@c screenSizeMeters), with the part's position on it + * taken from XrWeaveRectPartDXR::screenRect. + * + * @c woven is XR_FALSE when the runtime cannot weave this segment (the screen + * has no display processor, or its DP needs 1:1 pixels on a resampled screen): + * the runtime then shows one view of it flat (the left view of a stereo pair; + * the middle view of an N-view atlas) and @c eyesValid is usually XR_FALSE. + */ +typedef struct XrWeaveScreenSegmentDXR { + uint64_t displayId; //!< the screen (XrDisplayDXR::displayId, XR_DXR_display_info) + XrRect2Di windowRect; //!< window-and-screen intersection, window-relative device px (y-down) + XrRect2Di screenRect; //!< the same pixels, relative to the screen's own top-left + XrExtent2Di screenSize; //!< the screen's size, device px + XrExtent2Df screenSizeMeters; //!< the screen's physical size, metres (0 = unknown) + XrBool32 isPrimary; //!< the system-default screen (the one XrWeaveOutputDXR::eyes describe) + XrBool32 woven; //!< XR_TRUE = woven in 3D by this screen's DP; XR_FALSE = one view, flat + uint32_t eyeCount; + XrVector3f eyes[XR_WEAVE_MAX_EYES_DXR]; //!< this screen's tracked eyes, THIS screen's display space + XrBool32 eyesValid; + XrBool32 eyesTracking; +} XrWeaveScreenSegmentDXR; + +/*! + * @brief The part of one submitted weave rect that lands on one screen (spec v19). + * + * @c windowRect is the part in the window's space (the space of + * XrWeaveSubmitRectsDXR::rects); @c rectRelative is the same part relative to + * its rect's own top-left, i.e. where those pixels sit inside the rect's + * content (inside each SBS half, at full rect scale, on the batch layout); + * @c screenRect is the same part relative to its screen's top-left. The caller + * renders exactly @c rectRelative of rect @c rectIndex from + * segments[@c segmentIndex].eyes. + */ +typedef struct XrWeaveRectPartDXR { + uint32_t rectIndex; //!< index into the submitted XrWeaveSubmitRectsDXR::rects + uint32_t segmentIndex; //!< index into XrWeaveOutputRectPartsDXR::segments + XrRect2Di windowRect; //!< the part, window-relative device px + XrRect2Di rectRelative; //!< the part, relative to the rect's top-left + XrRect2Di screenRect; //!< the part, relative to the screen's top-left +} XrWeaveRectPartDXR; + +/*! + * @brief Per-screen segments and per-screen rect parts of a submit (spec v19, #1884). + * + * Chain onto XrWeaveOutputDXR::next (zero-initialised). Every successful + * xrWeaveSubmitDXR fills it with what THIS submit's weave did: + * + * - @c segmentCount 0: the window was woven by one display processor (it is + * on one screen, the submit used the legacy single-rect layout, or the + * system has one 3D screen). The base XrWeaveOutputDXR::eyes apply to every + * rect and @c partCount is 0. This is every pre-v19 frame. + * - @c segmentCount >= 1: the window was woven per screen. @c segments lists + * the window's screens left to right, each with its own eyes; @c parts lists + * every non-empty (rect, segment) intersection, ordered by rect, then left to + * right. A rect wholly on one screen has exactly one part covering all of it. + * Render each part from its segment's eyes for the NEXT frame (the same + * one-frame lag the base eyes have). The base eyes still describe the + * primary screen. + * + * @c generation changes whenever the segment geometry changes (a move, a + * resize, a screen change) and not when only the eyes do, so a caller can + * re-plan its per-part render targets on a change only. + * + * The parts are computed for the rects of THIS submit, in submission order; a + * frame split across several submits gets each submit's parts separately. + */ +typedef struct XrWeaveOutputRectPartsDXR { + XrStructureType type; //!< XR_TYPE_WEAVE_OUTPUT_RECT_PARTS_DXR + void* XR_MAY_ALIAS next; + uint32_t segmentCount; //!< 0 = not segmented (use XrWeaveOutputDXR::eyes) + XrWeaveScreenSegmentDXR segments[XR_WEAVE_MAX_SEGMENTS_DXR]; + uint32_t partCount; + XrWeaveRectPartDXR parts[XR_WEAVE_MAX_RECT_PARTS_DXR]; + uint64_t generation; //!< bumped on a segment-geometry change +} XrWeaveOutputRectPartsDXR; + typedef XrResult (XRAPI_PTR *PFN_xrWeaveBindWindowDXR)( XrSession session, void* windowHandle); diff --git a/src/xrt/auxiliary/util/u_weave_rect_parts.h b/src/xrt/auxiliary/util/u_weave_rect_parts.h new file mode 100644 index 0000000000..4d6815632a --- /dev/null +++ b/src/xrt/auxiliary/util/u_weave_rect_parts.h @@ -0,0 +1,127 @@ +// Copyright 2026, The DisplayXR Project +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief XR_DXR_weave v19 (#1884): split each submitted weave rect into its + * per-screen parts. + * @ingroup aux_util + * + * A present-owner's bound window can straddle a seam between two 3D screens. + * The D3D11 service then weaves the window per screen (ADR-047 Amendment 4): + * each screen's segment (window ∩ screen) by that screen's display processor. + * The service hands the client its segment table (@ref xrt_segment_metrics, the + * same table per-segment views use); the client's state tracker cuts every rect + * it submitted at the table's seams, so the caller knows which pixels of which + * rect to render from which screen's eyes. The rects never cross the wire twice. + * + * Header-only and backend-agnostic, so it is host-tested + * (`tests/tests_weave_segments.cpp`). + */ + +#pragma once + +#include "xrt/xrt_defines.h" +#include "xrt/xrt_display_metrics.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * One part of one weave rect: the pixels of rect @ref rect_index that land on + * segment @ref segment_index's screen. + */ +struct u_weave_rect_part +{ + uint32_t rect_index; //!< Index into the submitted rects + uint32_t segment_index; //!< Index into xrt_segment_metrics::seg + struct xrt_rect window_rect; //!< The part, window px + struct xrt_rect rect_rel; //!< The part, relative to its rect's top-left + struct xrt_rect screen_rect; //!< The part, relative to its screen's top-left +}; + +/*! + * Intersect two rects (half-open). @return false when they do not overlap. + */ +static inline bool +u_weave_rect_intersect(const struct xrt_rect *a, const struct xrt_rect *b, struct xrt_rect *out) +{ + if (a->extent.w <= 0 || a->extent.h <= 0 || b->extent.w <= 0 || b->extent.h <= 0) { + return false; + } + const int64_t ax1 = (int64_t)a->offset.w + a->extent.w; + const int64_t ay1 = (int64_t)a->offset.h + a->extent.h; + const int64_t bx1 = (int64_t)b->offset.w + b->extent.w; + const int64_t by1 = (int64_t)b->offset.h + b->extent.h; + const int64_t x0 = a->offset.w > b->offset.w ? a->offset.w : b->offset.w; + const int64_t y0 = a->offset.h > b->offset.h ? a->offset.h : b->offset.h; + const int64_t x1 = ax1 < bx1 ? ax1 : bx1; + const int64_t y1 = ay1 < by1 ? ay1 : by1; + if (x1 <= x0 || y1 <= y0) { + return false; + } + out->offset.w = (int)x0; + out->offset.h = (int)y0; + out->extent.w = (int)(x1 - x0); + out->extent.h = (int)(y1 - y0); + return true; +} + +/*! + * Split @p rects at the seams of segment table @p m. + * + * Parts are emitted per rect in submission order, and within a rect in the + * table's order (left to right). A rect wholly on one segment yields one part + * equal to the rect; a rect on no segment (off every screen) yields none; an + * empty rect yields none. + * + * A table with @c count 0 (the window is woven by one display processor) or + * a count past XRT_MAX_SEGMENTS yields no parts at all: the caller then keeps + * one eye set for everything, which is the pre-split contract. + * + * @return the number of parts written (excess dropped at @p cap). + */ +static inline uint32_t +u_weave_rect_parts(const struct xrt_segment_metrics *m, + const struct xrt_rect *rects, + uint32_t rect_count, + struct u_weave_rect_part *out, + uint32_t cap) +{ + if (m == NULL || rects == NULL || out == NULL || m->count == 0 || m->count > XRT_MAX_SEGMENTS) { + return 0; + } + uint32_t n = 0; + for (uint32_t r = 0; r < rect_count; r++) { + for (uint32_t k = 0; k < m->count; k++) { + const struct xrt_segment_metric *s = &m->seg[k]; + struct xrt_rect isect; + if (!u_weave_rect_intersect(&rects[r], &s->window_rect, &isect)) { + continue; + } + if (n >= cap) { + return n; + } + struct u_weave_rect_part *p = &out[n++]; + p->rect_index = r; + p->segment_index = k; + p->window_rect = isect; + p->rect_rel.offset.w = isect.offset.w - rects[r].offset.w; + p->rect_rel.offset.h = isect.offset.h - rects[r].offset.h; + p->rect_rel.extent = isect.extent; + // The segment's window rect and screen rect are the same pixels: + // shift by the part's offset inside the segment. + p->screen_rect.offset.w = s->screen_rect.offset.w + (isect.offset.w - s->window_rect.offset.w); + p->screen_rect.offset.h = s->screen_rect.offset.h + (isect.offset.h - s->window_rect.offset.h); + p->screen_rect.extent = isect.extent; + } + } + return n; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp b/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp index cd1ab8639d..52e2fa7977 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp +++ b/src/xrt/compositor/d3d11/comp_d3d11_segments.cpp @@ -52,7 +52,7 @@ struct seg_screen_state struct xrt_display_processor_d3d11 *dp; //!< NULL until the lifecycle creates it. bool configured; //!< set_background_2d(NULL) sent once. int encoding_latched; //!< last atlas encoding declared, -1 = none. - int transparent_latched; //!< last transparency declared, -1 = none. + int transparent_latched; //!< last transparency (bit 0 bg, bit 1 client presents), -1 = none. int mode_sent; //!< last 2D/3D mode sent, -1 = none. bool tolerates_resample; bool has_hwnd; //!< this DP holds the session's window (the owner). @@ -83,6 +83,9 @@ struct comp_d3d11_segments */ struct comp_d3d11_segments_hwnd_hooks hooks; struct comp_segments_owner owner; + //! Whose lattice a present-owner's drag snaps to (#1884): the majority + //! screen, same hysteresis as the handle owner, but nothing is rebuilt. + struct comp_segments_owner snap_owner; uint32_t primary_index; //!< index of the primary screen, UINT32_MAX = none int owner_index; //!< index of the screen holding the handle, -1 = none bool primary_for_screen; //!< the primary's plug-in has create_dp_d3d11_for_screen @@ -641,6 +644,7 @@ comp_d3d11_segments_set_screens(struct comp_d3d11_segments *segs, if (segs->primary_index != UINT32_MAX) { segs->owner_index = (int)segs->primary_index; comp_segments_owner_init(&segs->owner, segs->screens[segs->primary_index].id, 0, 0); + comp_segments_owner_init(&segs->snap_owner, segs->screens[segs->primary_index].id, 0, 0); } const bool handoff = segs->hooks.swap_primary != nullptr && segs->primary_for_screen; U_LOG_W( @@ -880,10 +884,12 @@ comp_d3d11_segments_record(struct comp_d3d11_segments *segs, const struct comp_d xrt_display_processor_d3d11_set_atlas_encoding( dp, (enum xrt_atlas_encoding)f->atlas_encoding); } - if (st->transparent_latched != (int)f->transparent_background) { - st->transparent_latched = (int)f->transparent_background; + const int transparency = + (f->transparent_background ? 1 : 0) | (f->client_presents ? 2 : 0); + if (st->transparent_latched != transparency) { + st->transparent_latched = transparency; xrt_display_processor_d3d11_set_transparent_background( - dp, f->transparent_background, false); + dp, f->transparent_background, f->client_presents); } if (st->mode_sent != (int)segs->mode_3d) { st->mode_sent = (int)segs->mode_3d; @@ -1048,6 +1054,47 @@ comp_d3d11_segments_get_owner(const struct comp_d3d11_segments *segs) return segs->screens[segs->owner_index].id; } +extern "C" struct xrt_display_processor_d3d11 * +comp_d3d11_segments_snap_dp(struct comp_d3d11_segments *segs, + const struct comp_seg_rect *window_desktop, + uint64_t now_ns, + uint64_t *out_screen_id) +{ + if (out_screen_id != nullptr) { + *out_screen_id = 0; + } + if (segs == nullptr || !segs->enabled || window_desktop == nullptr || segs->primary_index == UINT32_MAX) { + return nullptr; + } + // The table at the PROPOSED position, not the last weave's: the snap + // answers "where may the window land", ahead of any weave there. + struct comp_segment_table t; + comp_segments_compute(window_desktop, nullptr, segs->screens, segs->screen_count, &t); + const uint64_t target = comp_segments_owner_update(&segs->snap_owner, &t, now_ns); + if (target != 0) { + const int ti = screen_index_of(segs, target); + const bool is_primary = ti >= 0 && (uint32_t)ti == segs->primary_index; + if (ti >= 0 && (is_primary || segs->st[ti].dp != nullptr)) { + comp_segments_owner_set_result(&segs->snap_owner, target, true, target); + // A drag crossing the dead band: an event, not per frame. + U_LOG_I("segments: drag snap follows screen 0x%016llx's lattice (majority, #1884)", + (unsigned long long)target); + } + // Else that screen has no DP yet (its segment has not lived through + // the lifecycle's create delay): keep the current lattice and ask + // again next snap. No failure latch — nothing was torn down. + } + const uint64_t owner = segs->snap_owner.owner_id; + if (out_screen_id != nullptr) { + *out_screen_id = owner; + } + const int oi = owner != 0 ? screen_index_of(segs, owner) : -1; + if (oi < 0 || (uint32_t)oi == segs->primary_index) { + return nullptr; // the primary: the session's own DP snaps + } + return segs->st[oi].dp; // NULL if retired meanwhile: the caller falls back +} + extern "C" bool comp_d3d11_segments_get_eyes(struct comp_d3d11_segments *segs, uint64_t screen_id, struct xrt_eye_positions *out) { diff --git a/src/xrt/compositor/d3d11/comp_d3d11_segments.h b/src/xrt/compositor/d3d11/comp_d3d11_segments.h index 22ddd35ac7..3f9fe0d033 100644 --- a/src/xrt/compositor/d3d11/comp_d3d11_segments.h +++ b/src/xrt/compositor/d3d11/comp_d3d11_segments.h @@ -175,6 +175,11 @@ struct comp_d3d11_segments_frame //! For the flat-2D fill (comp_d3d11_outcomp_blit_rect): the output //! composite unit, which lives on the same device as the target. struct comp_d3d11_outcomp *outcomp; + //! The client presents the target itself over its own window (an + //! XR_DXR_weave present-owner, #1884): declared to segment DPs as + //! set_transparent_background's `client_presents`, like the session DP's. + //! False (zero-init) for a compositor that presents its own back buffer. + bool client_presents; }; /*! @@ -205,6 +210,28 @@ comp_d3d11_segments_set_display_mode(struct comp_d3d11_segments *segs, bool enab bool comp_d3d11_segments_get_eyes(struct comp_d3d11_segments *segs, uint64_t screen_id, struct xrt_eye_positions *out); +/*! + * Which display processor a drag snap of the window should use (#1884, the + * XR_DXR_weave present-owner's `xrWeaveSnapWindowRectDXR`): the screen holding + * the majority of the window at its PROPOSED desktop rect @p window_desktop, + * with the same hysteresis as the window-handle owner (ADR-047 Amendment 2 — + * a clear margin held for 0.5 s), so a drag across a seam does not flip the + * lattice under the cursor. Nothing is created or destroyed: a screen whose + * segment DP does not exist yet keeps the current lattice until it does. + * + * Caller serialises with @ref comp_d3d11_segments_update (the service holds + * its render mutex for both). + * + * @param[out] out_screen_id The screen whose lattice applies (0 = none / off). + * @return that screen's segment DP, or NULL when it is the primary screen (the + * session's own DP snaps) or segmentation is off. + */ +struct xrt_display_processor_d3d11 * +comp_d3d11_segments_snap_dp(struct comp_d3d11_segments *segs, + const struct comp_seg_rect *window_desktop, + uint64_t now_ns, + uint64_t *out_screen_id); + /*! * The screen whose DP holds the session's window handle right now (ADR-047 * Amendment 2), 0 when none does or segmentation is off. Status read for the diff --git a/src/xrt/compositor/d3d11_service/comp_d3d11_service.cpp b/src/xrt/compositor/d3d11_service/comp_d3d11_service.cpp index fd3a164d84..2d87275fed 100644 --- a/src/xrt/compositor/d3d11_service/comp_d3d11_service.cpp +++ b/src/xrt/compositor/d3d11_service/comp_d3d11_service.cpp @@ -995,6 +995,31 @@ struct d3d11_service_compositor int64_t seg_pub_ns; struct xrt_segment_view_routing seg_route; + /*! + * Multi-screen M6 for a present-owner (#1884, ADR-047 Amendment 4): an + * XR_DXR_weave caller's bound window, when it spans monitors, is woven per + * screen by weave_submit — a segment manager of its own, separate from the + * direct pipeline's above, because the woven handback lives on the RENDER + * device (`sys->device`, where the #1172 ingest DP is), not the output + * device, and because the two paths run on different threads. Built from + * the same opt-in screen list (`seg_screens`), so a client that never sent + * `compositor_segments_enable` is never segmented here either. + * + * Threads. `weave_segs`, `weave_seg_outcomp` and `weave_seg_device` belong + * to weave_submit / the weave snap, which both hold render_mutex; + * compositor_destroy frees them under it too. `weave_seg_rebuild` and + * `weave_seg_pub` / `weave_seg_out` are guarded by `seg_mutex` (the list is + * written by any IPC thread; the table is read by `weave_get_segments`). + */ + bool weave_seg_rebuild; //!< a new list arrived: rebuild the weave manager on the next submit + struct comp_d3d11_segments *weave_segs; + struct comp_d3d11_outcomp *weave_seg_outcomp; //!< flat-2D fill, on @ref weave_seg_device + ID3D11Device *weave_seg_device; + //! The last accepted submit's table, eyes zeroed: the change-only generation source. + struct xrt_segment_metrics weave_seg_pub; + //! The same table with every segment's eyes, as the submit predicted them. + struct xrt_segment_metrics weave_seg_out; + //! #1140: the recipe the per-client atlas ACTUALLY HOLDS — the per-view //! content dims and the view count of the last commit that PAINTED it. //! @@ -14112,6 +14137,10 @@ svc_seg_release(struct d3d11_service_compositor *c) c->seg_device = nullptr; } +//! #1884: the present-owner's weave segment manager (defined with weave_submit). +static void +weave_seg_release(struct d3d11_service_compositor *c); + /*! * The direct pipeline's segment path for the focused client: when its window * spans monitors, weave it per screen into the presenter's back buffer — the @@ -24794,9 +24823,11 @@ compositor_destroy(struct xrt_compositor *xc) // Multi-screen M6: the segment DPs are the render thread's; tear them down // under render_mutex so no weave of this client is in flight. - if (c->segments != nullptr || c->seg_outcomp != nullptr) { + if (c->segments != nullptr || c->seg_outcomp != nullptr || c->weave_segs != nullptr || + c->weave_seg_outcomp != nullptr) { render_mutex_fair_lock lock(sys); svc_seg_release(c); + weave_seg_release(c); // #1884: the present-owner's weave segment DPs } free(c->seg_screens); c->seg_screens = nullptr; @@ -26290,6 +26321,251 @@ weave_timing_account(struct d3d11_service_system *sys, r->weave_cpu_ov_unchanged = 0; } +/* + * + * Multi-screen M6 for a present-owner (#1884, ADR-047 Amendment 4): an + * XR_DXR_weave caller's bound window that spans monitors is woven per screen, + * each screen's part by that screen's display processor, into the one woven + * handback texture; every screen's eyes go back to the caller. + * + */ + +//! Drop the present-owner's weave segment manager. Holding render_mutex (no +//! weave or snap of this client is in flight). +static void +weave_seg_release(struct d3d11_service_compositor *c) +{ + comp_d3d11_segments_destroy(&c->weave_segs); + comp_d3d11_outcomp_destroy(&c->weave_seg_outcomp); + c->weave_seg_device = nullptr; + std::lock_guard lk(c->seg_mutex); + struct xrt_segment_metrics none; + memset(&none, 0, sizeof(none)); // memcmp'd by the publish: padding too + (void)comp_segments_publish(&c->weave_seg_pub, &none); + c->weave_seg_out = c->weave_seg_pub; +} + +/*! + * One metric update of the present-owner's weave segments: (re)build the + * manager from the opt-in screen list when it changed (or the render device + * did), recompute the table for the bound window, run the DP lifecycle. + * + * The manager lives on `sys->device` — the device the woven handback and the + * #1172 ingest DP live on, i.e. the device this weave writes. (The direct + * pipeline's manager is on the OUTPUT device for the same reason: a segment + * DP is made where its target is.) + * + * Holding render_mutex and immediate_ctx_mutex. + * + * @return true when this submit must be woven per segment; @p out_win then + * holds the window's client area in desktop px. + */ +static bool +weave_seg_update(struct d3d11_service_system *sys, + struct d3d11_service_compositor *c, + HWND wnd, + uint32_t win_w, + uint32_t win_h, + struct comp_seg_rect *out_win) +{ + ID3D11Device *dev = sys->device.get(); + ID3D11DeviceContext *ctx = sys->context.get(); + bool rebuild = false; + { + std::lock_guard lk(c->seg_mutex); + if (c->seg_screens == nullptr) { + return false; // never enabled: the single-DP weave, untouched + } + rebuild = c->weave_seg_rebuild || c->weave_seg_device != dev; + } + if (rebuild) { + weave_seg_release(c); + struct comp_d3d11_segments *ns = comp_d3d11_segments_create(dev); + bool enabled = false; + { + std::lock_guard lk(c->seg_mutex); + if (ns != nullptr) { + comp_d3d11_segments_set_screens(ns, c->seg_screens, c->seg_sys_info, + c->seg_pinned_display_id); + enabled = comp_d3d11_segments_enabled(ns); + } + c->weave_seg_rebuild = false; + } + c->weave_segs = ns; + if (comp_d3d11_outcomp_create(dev, ctx, &c->weave_seg_outcomp) != XRT_SUCCESS) { + c->weave_seg_outcomp = nullptr; // segments that cannot weave then stay transparent, not flat 2D + } + c->weave_seg_device = dev; // tried once per device / list, never per frame + if (enabled) { + // Lifecycle (once per manager). + U_LOG_W( + "segments: present-owner %p (pid %ld) — a bound window spanning monitors is woven " + "per screen by weave_submit; segment DPs on the render device %p, per-screen eyes " + "out (XR_DXR_weave v19, #1884)", + (void *)c, c->client_pid, (void *)dev); + } + } + if (c->weave_segs == nullptr || !comp_d3d11_segments_enabled(c->weave_segs)) { + return false; + } + struct comp_seg_rect win = {}; + if (!svc_seg_window_rect(wnd, &win) || win.w != win_w || win.h != win_h) { + return false; // no live window, or a resize in flight: weave this frame whole + } + const struct comp_seg_rect canvas = {0, 0, win.w, win.h}; + const uint32_t mode_index = + (sys->xdev != nullptr && sys->xdev->hmd != nullptr) ? sys->xdev->hmd->active_rendering_mode_index : 0; + const bool split = comp_d3d11_segments_update(c->weave_segs, &win, &canvas, ctx, mode_index); + // Every segment DP follows the panel's 2D/3D state, like the panel DP. + comp_d3d11_segments_set_display_mode(c->weave_segs, sys->hardware_display_3d); + *out_win = win; + return split; +} + +/*! + * Weave a segmented submit: the window-canvas atlas @p src_srv (views + * @p view_w x @p view_h, @p cols x @p rows) cropped per segment and woven by + * each screen's DP into the handback, scissored to its segment; flat 2D where + * a segment cannot be woven. @p dp (the client's weave DP) weaves the primary + * screen's segment. + * + * The v4 overlay is NOT handed to a DP here: it is window-sized and a DP + * composites it over its whole target, which no segment DP covers. The + * runtime's post-weave blit composites it instead (ov_in_dp stays false), the + * pre-ADR-027-Amendment fallback every DP without the slot already gets. + * + * Holding render_mutex and immediate_ctx_mutex. + */ +static void +weave_seg_record(struct d3d11_service_system *sys, + struct d3d11_service_compositor *c, + struct xrt_display_processor_d3d11 *dp, + ID3D11ShaderResourceView *src_srv, + uint32_t view_w, + uint32_t view_h, + uint32_t cols, + uint32_t rows, + uint32_t win_w, + uint32_t win_h) +{ + ID3D11DeviceContext *ctx = sys->context.get(); + // Stateless slot: say "no overlay this frame" so the primary DP does not + // keep compositing a previous frame's layer over its segment. + (void)xrt_display_processor_d3d11_set_overlay_2d(dp, ctx, nullptr, 0, 0, XRT_ATLAS_ENCODING_ENCODED, false); + + struct comp_d3d11_segments_frame sf = {}; + sf.context = ctx; + sf.src_srv = src_srv; + sf.view_width = view_w; + sf.view_height = view_h; + sf.tile_columns = cols; + sf.tile_rows = rows; + sf.src_format = (uint32_t)DXGI_FORMAT_R8G8B8A8_UNORM; // as the single-DP weave declares it + sf.target_rtv = c->render.weave_output_rtv.get(); + sf.target_width = win_w; + sf.target_height = win_h; + sf.canvas.x = 0; + sf.canvas.y = 0; + sf.canvas.w = win_w; + sf.canvas.h = win_h; + // The present-owner composites the handback over its own window: alpha + // carries the 2D-vs-3D split, so the background is transparent and the + // client presents (pipeline_dp_set_transparency(..., client_presents=true) + // for the session's DP). + sf.transparent_background = true; + sf.client_presents = true; + sf.atlas_encoding = (int)service_single_client_atlas_encoding(c); + sf.primary_dp = dp; + sf.outcomp = c->weave_seg_outcomp; + comp_d3d11_segments_record(c->weave_segs, &sf); + + // Leave exactly the state the single-DP weave leaves: the handback bound, + // full-target viewport and scissor. + ID3D11RenderTargetView *rtvs[] = {c->render.weave_output_rtv.get()}; + ctx->OMSetRenderTargets(1, rtvs, nullptr); + D3D11_VIEWPORT vp = {}; + vp.Width = (float)win_w; + vp.Height = (float)win_h; + vp.MaxDepth = 1.0f; + ctx->RSSetViewports(1, &vp); + D3D11_RECT sc = {0, 0, (LONG)win_w, (LONG)win_h}; + ctx->RSSetScissorRects(1, &sc); +} + +/*! + * Publish what this submit's weave did, for `weave_get_segments`: the table + * (count 0 = woven by one DP) with every segment's eyes — the primary's from + * @p primary_eyes (what the submit returns as its base eyes), the others + * predicted now from their segment DPs. The generation moves on a geometry + * change only (the eyes are not part of what is compared). + * + * Holding render_mutex. + */ +static void +weave_seg_publish(struct d3d11_service_compositor *c, + bool split, + const struct comp_seg_rect *win, + const struct xrt_eye_positions *primary_eyes) +{ + struct xrt_segment_metrics m; + memset(&m, 0, sizeof(m)); // memcmp'd by the publish: padding too + if (split && c->weave_segs != nullptr) { + const struct comp_seg_rect canvas = {0, 0, win->w, win->h}; + (void)comp_d3d11_segments_get_metrics(c->weave_segs, win, &canvas, /*primary_has_dp*/ true, &m); + } + struct xrt_segment_metrics out; + { + std::lock_guard lk(c->seg_mutex); + (void)comp_segments_publish(&c->weave_seg_pub, &m); + out = c->weave_seg_pub; + } + 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 = primary_eyes != nullptr && primary_eyes->valid; + if (s->have_eyes) { + s->eyes = *primary_eyes; + } + } else { + s->have_eyes = comp_d3d11_segments_get_eyes(c->weave_segs, s->screen_id, &s->eyes); + } + } + std::lock_guard lk(c->seg_mutex); + c->weave_seg_out = out; +} + +/*! + * The display processor whose lattice a drag snap of the present-owner's + * window uses (#1884): the majority screen's segment DP at the PROPOSED + * position, with the handle owner's hysteresis — NULL for the primary screen + * (the caller's own snap path) or when the window is not segmented. + * + * @p target_x / @p target_y position the window FRAME (WM_WINDOWPOSCHANGING); + * the screens split the CLIENT area, so the frame-to-client offset of the + * window as it stands now is carried to the target. + * + * Holding render_mutex. + */ +static struct xrt_display_processor_d3d11 * +weave_seg_snap_dp(struct d3d11_service_compositor *c, int32_t target_x, int32_t target_y) +{ + if (c->weave_segs == nullptr || !comp_d3d11_segments_enabled(c->weave_segs)) { + return nullptr; + } + HWND wnd = c->render.weave_hwnd; + struct comp_seg_rect client = {}; + RECT wr = {}; + if (!svc_seg_window_rect(wnd, &client) || !GetWindowRect(wnd, &wr)) { + return nullptr; + } + struct comp_seg_rect at = {}; + at.x = target_x + (client.x - wr.left); + at.y = target_y + (client.y - wr.top); + at.w = client.w; + at.h = client.h; + return comp_d3d11_segments_snap_dp(c->weave_segs, &at, os_monotonic_get_ns(), nullptr); +} + bool comp_d3d11_service_weave_submit(struct xrt_compositor *xc, xrt_graphics_buffer_handle_t in_handle, @@ -26895,6 +27171,15 @@ comp_d3d11_service_weave_submit(struct xrt_compositor *xc, * bounded trail instead of an unbounded one. */ const bool legacy_single_rect = (!nview && rect_count == 0); + + // #1884 (ADR-047 Amendment 4): a bound window across a seam is woven per + // screen. Batch and v6 only — both weave the WHOLE window as one canvas, + // which is what the segment table cuts; the legacy branch weaves one + // element-sized canvas per call and stays single-DP. A client that never + // enabled segments (every single-screen box) returns false here before + // anything else, so its submit is the single-DP one, byte for byte. + struct comp_seg_rect seg_win = {}; + const bool seg_split = !legacy_single_rect && weave_seg_update(sys, c, wnd, win_w, win_h, &seg_win); bool clear_output = weave_frame_first; if (weave_frame_first) { c->render.weave_saw_frame_first = true; @@ -27075,12 +27360,17 @@ comp_d3d11_service_weave_submit(struct xrt_compositor *xc, D3D11_RECT weave_scissor = {0, 0, (LONG)win_w, (LONG)win_h}; sys->context->RSSetScissorRects(1, &weave_scissor); gpu_frame.stamp(WEAVE_TS_PRE_WEAVE); - hand_overlay_to_dp(); - xrt_display_processor_d3d11_process_atlas(dp, sys->context.get(), dp_srv, - /*view_w*/ cvw, /*view_h*/ cvh, - layout->tile_columns, layout->tile_rows, - DXGI_FORMAT_R8G8B8A8_UNORM, win_w, win_h, - /*canvas_offset*/ 0, 0, win_w, win_h); + if (seg_split) { + weave_seg_record(sys, c, dp, dp_srv, cvw, cvh, layout->tile_columns, layout->tile_rows, win_w, + win_h); + } else { + hand_overlay_to_dp(); + xrt_display_processor_d3d11_process_atlas(dp, sys->context.get(), dp_srv, + /*view_w*/ cvw, /*view_h*/ cvh, layout->tile_columns, + layout->tile_rows, DXGI_FORMAT_R8G8B8A8_UNORM, win_w, + win_h, + /*canvas_offset*/ 0, 0, win_w, win_h); + } // #625 present-owner: a real panel-DP weave, driven by the client's own // synchronous submit. It presents the woven texture itself, so this // contributes to present/s with no matching panel flip (see header note). @@ -27286,12 +27576,17 @@ comp_d3d11_service_weave_submit(struct xrt_compositor *xc, D3D11_RECT weave_scissor = {0, 0, (LONG)win_w, (LONG)win_h}; sys->context->RSSetScissorRects(1, &weave_scissor); gpu_frame.stamp(WEAVE_TS_PRE_WEAVE); - hand_overlay_to_dp(); - xrt_display_processor_d3d11_process_atlas(dp, sys->context.get(), c->render.weave_sbs_srv.get(), - /*view_w*/ win_w, /*view_h*/ win_h, - /*tile_columns*/ 2, /*tile_rows*/ 1, - DXGI_FORMAT_R8G8B8A8_UNORM, win_w, win_h, - /*canvas_offset*/ 0, 0, win_w, win_h); + if (seg_split) { + weave_seg_record(sys, c, dp, c->render.weave_sbs_srv.get(), win_w, win_h, /*cols*/ 2, + /*rows*/ 1, win_w, win_h); + } else { + hand_overlay_to_dp(); + xrt_display_processor_d3d11_process_atlas(dp, sys->context.get(), c->render.weave_sbs_srv.get(), + /*view_w*/ win_w, /*view_h*/ win_h, + /*tile_columns*/ 2, /*tile_rows*/ 1, + DXGI_FORMAT_R8G8B8A8_UNORM, win_w, win_h, + /*canvas_offset*/ 0, 0, win_w, win_h); + } g_frame_witness_service.count_weave(/*repaint*/ false, sys->hardware_display_3d); // Batch phase split (throttled): blits (shared input -> SBS atlas, @@ -27399,6 +27694,18 @@ comp_d3d11_service_weave_submit(struct xrt_compositor *xc, if (out_eyes != nullptr) { xrt_display_processor_d3d11_get_predicted_eye_positions(dp, out_eyes); } + // #1884: and what this weave did per screen, with every screen's eyes, + // for the caller's XrWeaveOutputRectPartsDXR (weave_get_segments). A + // client that never enabled segments has nothing to publish. + if (seg_split || c->weave_segs != nullptr) { + struct xrt_eye_positions primary_eyes = {}; + if (out_eyes != nullptr) { + primary_eyes = *out_eyes; + } else if (seg_split) { + xrt_display_processor_d3d11_get_predicted_eye_positions(dp, &primary_eyes); + } + weave_seg_publish(c, seg_split, &seg_win, &primary_eyes); + } /* #964: a present-owner never commits a projection layer, so nothing else * would ever mark its slot as PRESENTING — it would sit outside the default @@ -27527,6 +27834,23 @@ comp_d3d11_service_weave_snap_window_rect(struct xrt_compositor *xc, // weaver's. While this client is focused the panel DP is bound to its own // weave_hwnd, so the snap is against the right window. struct xrt_display_processor_d3d11 *sdp = panel_dp(c->sys, c); + + // #1884 (ADR-047 Amendment 4): a present-owner whose window is woven per + // screen snaps to the lattice of the screen holding the majority of the + // window at the proposed position (the handle owner's hysteresis, so a + // drag across the seam does not flip lattices under the cursor). That + // screen's segment DP answers; a DP without snap support falls back to the + // panel DP below, which is the pre-#1884 answer. + struct xrt_display_processor_d3d11 *seg_dp = weave_seg_snap_dp(c, target_x, target_y); + if (seg_dp != nullptr) { + int32_t gx = target_x, gy = target_y; + if (xrt_display_processor_d3d11_snap_window_rect(seg_dp, origin_x, origin_y, target_x, target_y, &gx, + &gy)) { + *out_x = gx; + *out_y = gy; + return true; + } + } if (sdp == nullptr) { return false; } @@ -27540,6 +27864,22 @@ comp_d3d11_service_weave_snap_window_rect(struct xrt_compositor *xc, return true; } +extern "C" bool +comp_d3d11_service_weave_get_segments(struct xrt_compositor *xc, struct xrt_segment_metrics *out) +{ + if (out == nullptr) { + return false; + } + memset(out, 0, sizeof(*out)); + if (xc == nullptr || xc->destroy != compositor_destroy) { + return false; + } + struct d3d11_service_compositor *c = d3d11_service_compositor_from_xrt(xc); + std::lock_guard lk(c->seg_mutex); // leaf + *out = c->weave_seg_out; + return out->count > 0; +} + /* * @@ -29803,6 +30143,7 @@ comp_d3d11_service_set_client_segment_screens(struct xrt_system_compositor *xsys c->seg_sys_info = info_copy; c->seg_pinned_display_id = pinned_display_id; c->seg_rebuild = true; + c->weave_seg_rebuild = true; // #1884: the present-owner's weave manager too // ...and nothing may frame views from the old table meanwhile. struct xrt_segment_metrics none; memset(&none, 0, sizeof(none)); @@ -29923,6 +30264,7 @@ comp_d3d11_service_refresh_segment_screens(struct xrt_system_compositor *xsysc, // client: the old segment DPs are released, and the lifecycle makes // each again from its screen's (new) registry entry. c->seg_rebuild = true; + c->weave_seg_rebuild = true; // #1884: the present-owner's weave manager too struct xrt_segment_metrics none; memset(&none, 0, sizeof(none)); (void)comp_segments_publish(&c->seg_pub, &none); diff --git a/src/xrt/compositor/d3d11_service/comp_d3d11_service.h b/src/xrt/compositor/d3d11_service/comp_d3d11_service.h index de3d496414..e62a7e58b2 100644 --- a/src/xrt/compositor/d3d11_service/comp_d3d11_service.h +++ b/src/xrt/compositor/d3d11_service/comp_d3d11_service.h @@ -1401,6 +1401,21 @@ comp_d3d11_service_weave_snap_window_rect(struct xrt_compositor *xc, int32_t *out_x, int32_t *out_y); +/*! + * XR_DXR_weave v19 (#1884, ADR-047 Amendment 4): what the last accepted + * @ref comp_d3d11_service_weave_submit of this present-owner did per screen — + * the bound window's segment table (count 0 = woven by one display processor) + * with every segment's eyes, the primary's being the submit's own base eyes. + * The client cuts its submitted rects at the table's seams (u_weave_rect_parts). + * + * Only a client that sent `compositor_segments_enable` is ever segmented, and + * only batch / v6 submits; otherwise the table is empty. + * + * @return true when the last submit was woven per segment. + */ +bool +comp_d3d11_service_weave_get_segments(struct xrt_compositor *xc, struct xrt_segment_metrics *out); + /* * XR_DXR_lift (ADR-042) — 2D→3D conversion streams. SYSTEM-level (a stream is * owned by an IPC connection, identified by @p owner, not by a session), so a diff --git a/src/xrt/ipc/client/ipc_client.h b/src/xrt/ipc/client/ipc_client.h index 8b85d4975a..45799c489d 100644 --- a/src/xrt/ipc/client/ipc_client.h +++ b/src/xrt/ipc/client/ipc_client.h @@ -181,6 +181,15 @@ comp_ipc_client_compositor_get_segment_metrics(struct xrt_compositor *xc, struct void comp_ipc_client_compositor_set_view_routing(struct xrt_compositor *xc, const struct xrt_segment_view_routing *routing); +/*! + * XR_DXR_weave v19 (#1884, ADR-047 Amendment 4): the per-screen segment table + * of this present-owner's last weave, every screen's eyes included (count 0 = + * woven by one display processor). Zeroed on any failure; the result is + * XRT_ERROR_IPC_FAILURE only for a dead connection. + */ +xrt_result_t +comp_ipc_client_compositor_weave_get_segments(struct xrt_compositor *xc, struct xrt_segment_metrics *out_metrics); + /*! * Phase 2 workspace_sync_fence bridges. Same gating contract as * comp_ipc_client_compositor_get_window_metrics — only valid when @p xc is diff --git a/src/xrt/ipc/client/ipc_client_compositor.c b/src/xrt/ipc/client/ipc_client_compositor.c index 3b8ac8c951..f6fc6f7a47 100644 --- a/src/xrt/ipc/client/ipc_client_compositor.c +++ b/src/xrt/ipc/client/ipc_client_compositor.c @@ -283,6 +283,32 @@ comp_ipc_client_compositor_get_segment_metrics(struct xrt_compositor *xc, struct return out_metrics->count > 0; } +/* + * XR_DXR_weave v19 (#1884): the per-screen segment table of this session's + * last weave. Asked for only by a caller that chained XrWeaveOutputRectPartsDXR + * (oxr_weave.c), so a pre-v19 caller sends nothing new. + */ +xrt_result_t +comp_ipc_client_compositor_weave_get_segments(struct xrt_compositor *xc, struct xrt_segment_metrics *out_metrics) +{ + if (out_metrics == NULL) { + return XRT_SUCCESS; // nothing to fill + } + memset(out_metrics, 0, sizeof(*out_metrics)); + if (xc == NULL) { + return XRT_SUCCESS; // no compositor: one eye set + } + struct ipc_client_compositor *icc = ipc_client_compositor(xc); + if (icc == NULL || icc->ipc_c == NULL) { + return XRT_SUCCESS; + } + xrt_result_t xret = ipc_call_weave_get_segments(icc->ipc_c, out_metrics); + if (xret != XRT_SUCCESS || out_metrics->count > XRT_MAX_SEGMENTS) { + memset(out_metrics, 0, sizeof(*out_metrics)); // one eye set + } + return xret; +} + void comp_ipc_client_compositor_set_view_routing(struct xrt_compositor *xc, const struct xrt_segment_view_routing *routing) { diff --git a/src/xrt/ipc/server/ipc_server_handler.c b/src/xrt/ipc/server/ipc_server_handler.c index e700f97e64..3e2a67245a 100644 --- a/src/xrt/ipc/server/ipc_server_handler.c +++ b/src/xrt/ipc/server/ipc_server_handler.c @@ -3213,6 +3213,33 @@ ipc_handle_compositor_get_segment_metrics(volatile struct ipc_client_state *ics, return XRT_SUCCESS; } +/* + * XR_DXR_weave v19 (#1884, ADR-047 Amendment 4): the present-owner's per-screen + * segment table of its last weave, with every screen's eyes. Inert (an empty + * table) off the D3D11 service compositor and for a client that never enabled + * segments. + */ +static_assert(sizeof(struct ipc_weave_get_segments_reply) <= IPC_BUF_SIZE, + "weave_get_segments reply exceeds IPC_BUF_SIZE"); + +xrt_result_t +ipc_handle_weave_get_segments(volatile struct ipc_client_state *ics, struct xrt_segment_metrics *out_metrics) +{ + IPC_TRACE_MARKER(); + + memset(out_metrics, 0, sizeof(*out_metrics)); +#if defined(XRT_HAVE_D3D11_SERVICE_COMPOSITOR) + if (ics->server != NULL && ics->server->xsysc != NULL && ics->xc != NULL && + comp_d3d11_service_is_d3d11_service(ics->server->xsysc)) { + (void)comp_d3d11_service_weave_get_segments((struct xrt_compositor *)ics->xc, out_metrics); + } +#else + (void)ics; +#endif + + return XRT_SUCCESS; +} + xrt_result_t ipc_handle_compositor_set_view_routing(volatile struct ipc_client_state *ics, const struct xrt_segment_view_routing *routing) diff --git a/src/xrt/ipc/shared/proto.json b/src/xrt/ipc/shared/proto.json index 62f7e40203..7f3b66ffc1 100644 --- a/src/xrt/ipc/shared/proto.json +++ b/src/xrt/ipc/shared/proto.json @@ -1392,5 +1392,11 @@ "in": [ {"name": "suspend", "type": "bool"} ] + }, + + "weave_get_segments": { + "out": [ + {"name": "metrics", "type": "struct xrt_segment_metrics"} + ] } } diff --git a/src/xrt/state_trackers/oxr/oxr_weave.c b/src/xrt/state_trackers/oxr/oxr_weave.c index 308170459e..779515dc5d 100644 --- a/src/xrt/state_trackers/oxr/oxr_weave.c +++ b/src/xrt/state_trackers/oxr/oxr_weave.c @@ -59,6 +59,7 @@ #include "util/u_trace_marker.h" #include "util/u_logging.h" #include "util/u_snap_grid.h" +#include "util/u_weave_rect_parts.h" #include "oxr_api_funcs.h" #include "oxr_api_verify.h" @@ -106,6 +107,9 @@ static_assert(sizeof(XrWeaveSnapGridPointDXR) == 2 && offsetof(XrWeaveSnapGridPo static_assert(XR_WEAVE_SNAP_GRID_MAX_POINTS_DXR == U_SNAP_GRID_MAX_POINTS, "grid point bound mismatch"); static_assert(XR_WEAVE_SNAP_GRID_MAX_AXIS_DXR == U_SNAP_GRID_MAX_AXIS, "grid axis bound mismatch"); static_assert(XR_WEAVE_SNAP_GRID_NO_DELTA_DXR == U_SNAP_GRID_NO_DELTA, "grid sentinel mismatch"); +// Spec v19 (#1884): the per-screen table is the per-segment views' table. +static_assert(XR_WEAVE_MAX_SEGMENTS_DXR == XRT_MAX_SEGMENTS, "weave segment bound mismatch"); +static_assert(XR_WEAVE_MAX_EYES_DXR <= ARRAY_SIZE(((struct xrt_eye_positions *)0)->eyes), "weave eye bound mismatch"); #ifdef OXR_HAVE_DXR_lift #include "xrt/xrt_lift.h" @@ -134,6 +138,10 @@ ipc_client_connection_export(enum u_logging_level log_level, xrt_ipc_handle_t *o xrt_result_t comp_ipc_client_compositor_weave_bind_window(struct xrt_compositor *xc, uint64_t hwnd); +//! XR_DXR_weave v19 (#1884): the last weave's per-screen segment table. +xrt_result_t +comp_ipc_client_compositor_weave_get_segments(struct xrt_compositor *xc, struct xrt_segment_metrics *out_metrics); + xrt_result_t comp_ipc_client_compositor_weave_set_window_geometry(struct xrt_compositor *xc, int32_t origin_x, @@ -458,6 +466,112 @@ oxr_xrWeaveBindWindow2DXR(XrSession session, const XrWeaveBindWindowInfoDXR *bin return XR_SUCCESS; } +/*! + * Spec v19 (#1884, ADR-047 Amendment 4): fill a chained + * XrWeaveOutputRectPartsDXR from the service's per-screen table of the submit + * that just landed — every screen's eyes, and the submitted @p rects cut at + * the seams (u_weave_rect_parts). segmentCount 0 (the window was woven by one + * display processor, or this platform's service does not segment) leaves the + * caller on the base eyes. + * + * @return XR_SUCCESS, or the lost-connection error when the fetch hit a dead + * pipe. Any other failure is reported as "not segmented". + */ +static XrResult +oxr_weave_fill_rect_parts(struct oxr_logger *log, + struct oxr_session *sess, + uint32_t rect_count, + const struct xrt_rect *rects, + XrWeaveOutputRectPartsDXR *out) +{ + out->segmentCount = 0; + out->partCount = 0; + out->generation = 0; + memset(out->segments, 0, sizeof(out->segments)); + memset(out->parts, 0, sizeof(out->parts)); + if (rect_count == 0) { + return XR_SUCCESS; // legacy single-rect submit: never segmented + } + + struct xrt_segment_metrics m; + memset(&m, 0, sizeof(m)); +#ifdef XRT_OS_WINDOWS + // Only the D3D11 service segments a present-owner's window; elsewhere the + // answer is always "one display processor", so do not pay a round trip. + xrt_result_t xret = comp_ipc_client_compositor_weave_get_segments(&sess->xcn->base, &m); + if (xret == XRT_ERROR_IPC_FAILURE) { + OXR_CHECK_XRET_MSG(log, sess, xret, + "xrWeaveSubmitDXR: weave segment table fetch failed (xrt_result=%d)", (int)xret); + } + if (xret != XRT_SUCCESS) { + memset(&m, 0, sizeof(m)); + } +#else + (void)log; + (void)sess; +#endif + if (m.count == 0 || m.count > XR_WEAVE_MAX_SEGMENTS_DXR) { + return XR_SUCCESS; + } + + for (uint32_t k = 0; k < m.count; k++) { + const struct xrt_segment_metric *g = &m.seg[k]; + XrWeaveScreenSegmentDXR *o = &out->segments[k]; + o->displayId = g->screen_id; + o->windowRect.offset.x = g->window_rect.offset.w; // xrt_offset names its fields w/h + o->windowRect.offset.y = g->window_rect.offset.h; + o->windowRect.extent.width = g->window_rect.extent.w; + o->windowRect.extent.height = g->window_rect.extent.h; + o->screenRect.offset.x = g->screen_rect.offset.w; + o->screenRect.offset.y = g->screen_rect.offset.h; + o->screenRect.extent.width = g->screen_rect.extent.w; + o->screenRect.extent.height = g->screen_rect.extent.h; + o->screenSize.width = (int32_t)g->screen_desktop_width; + o->screenSize.height = (int32_t)g->screen_desktop_height; + o->screenSizeMeters.width = g->screen_width_m; + o->screenSizeMeters.height = g->screen_height_m; + o->isPrimary = g->is_primary ? XR_TRUE : XR_FALSE; + o->woven = g->woven ? XR_TRUE : XR_FALSE; + const bool have = g->have_eyes && g->eyes.valid; + uint32_t ec = have ? g->eyes.count : 0; + if (ec > XR_WEAVE_MAX_EYES_DXR) { + ec = XR_WEAVE_MAX_EYES_DXR; + } + o->eyeCount = ec; + for (uint32_t i = 0; i < ec; i++) { + o->eyes[i].x = g->eyes.eyes[i].x; + o->eyes[i].y = g->eyes.eyes[i].y; + o->eyes[i].z = g->eyes.eyes[i].z; + } + o->eyesValid = have ? XR_TRUE : XR_FALSE; + o->eyesTracking = have && g->eyes.is_tracking ? XR_TRUE : XR_FALSE; + } + out->segmentCount = m.count; + out->generation = m.generation; + + struct u_weave_rect_part parts[XR_WEAVE_MAX_RECT_PARTS_DXR]; + const uint32_t n = u_weave_rect_parts(&m, rects, rect_count, parts, XR_WEAVE_MAX_RECT_PARTS_DXR); + for (uint32_t i = 0; i < n; i++) { + XrWeaveRectPartDXR *o = &out->parts[i]; + o->rectIndex = parts[i].rect_index; + o->segmentIndex = parts[i].segment_index; + o->windowRect.offset.x = parts[i].window_rect.offset.w; + o->windowRect.offset.y = parts[i].window_rect.offset.h; + o->windowRect.extent.width = parts[i].window_rect.extent.w; + o->windowRect.extent.height = parts[i].window_rect.extent.h; + o->rectRelative.offset.x = parts[i].rect_rel.offset.w; + o->rectRelative.offset.y = parts[i].rect_rel.offset.h; + o->rectRelative.extent.width = parts[i].rect_rel.extent.w; + o->rectRelative.extent.height = parts[i].rect_rel.extent.h; + o->screenRect.offset.x = parts[i].screen_rect.offset.w; + o->screenRect.offset.y = parts[i].screen_rect.offset.h; + o->screenRect.extent.width = parts[i].screen_rect.extent.w; + o->screenRect.extent.height = parts[i].screen_rect.extent.h; + } + out->partCount = n; + return XR_SUCCESS; +} + XRAPI_ATTR XrResult XRAPI_CALL oxr_xrWeaveSubmitDXR(XrSession session, const XrWeaveSubmitInfoDXR *submitInfo, XrWeaveOutputDXR *output) { @@ -957,6 +1071,19 @@ oxr_xrWeaveSubmitDXR(XrSession session, const XrWeaveSubmitInfoDXR *submitInfo, output->eyesValid = eyes.valid ? XR_TRUE : XR_FALSE; output->eyesTracking = eyes.is_tracking ? XR_TRUE : XR_FALSE; + // Spec v19 (#1884, ADR-047 Amendment 4): a window across a seam is woven + // per screen; the caller that asks gets every screen's eyes and its rects + // cut at the seams. Fetched only when the struct is chained, so a pre-v19 + // caller sends exactly the pre-v19 messages. + XrWeaveOutputRectPartsDXR *out_parts = + OXR_GET_OUTPUT_FROM_CHAIN(output, XR_TYPE_WEAVE_OUTPUT_RECT_PARTS_DXR, XrWeaveOutputRectPartsDXR); + if (out_parts != NULL) { + XrResult pr = oxr_weave_fill_rect_parts(&log, sess, rect_count, rects, out_parts); + if (pr != XR_SUCCESS) { + return pr; // a dead connection only + } + } + bool need_export = !sess->weave.exported || w != sess->weave.last_w || h != sess->weave.last_h; if (have_out && w != 0 && h != 0 && need_export) { bool have_tex = false; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 26f5c9b6b3..00497c96df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -70,6 +70,7 @@ set(tests tests_oxr_view_config_rule tests_oxr_segment_views tests_segments_ipc + tests_weave_segments tests_target_screen_pin tests_u_setting_per_screen tests_oxr_legacy_mode_rule @@ -664,6 +665,18 @@ if(TARGET ipc_shared) target_compile_definitions(tests_segments_ipc PRIVATE DXR_TESTS_HAVE_IPC_PROTOCOL) endif() +# XR_DXR_weave v19 (#1884, ADR-047 Amendment 4): a present-owner's weave rects +# split per screen -- the segment table (compositor/util), the per-rect parts +# the client cuts from it (aux util, header-only) and, where the IPC protocol +# is generated, the weave_get_segments reply carrying every screen's eyes. +target_sources(tests_weave_segments PRIVATE ${PROJECT_SOURCE_DIR}/src/xrt/compositor/util/comp_segments.c) +target_include_directories(tests_weave_segments PRIVATE ${PROJECT_SOURCE_DIR}/src/xrt/compositor) +target_link_libraries(tests_weave_segments PRIVATE xrt-interfaces aux_util) +if(TARGET ipc_shared) + target_link_libraries(tests_weave_segments PRIVATE ipc_shared) + target_compile_definitions(tests_weave_segments PRIVATE DXR_TESTS_HAVE_IPC_PROTOCOL) +endif() + # Multi-screen M4 — DXR_SCREEN_PLUGIN (per-screen plug-in pin) and the per-monitor # winner rule of the DP registry, pure functions of target_screen_pin.c. target_sources(tests_target_screen_pin PRIVATE ${PROJECT_SOURCE_DIR}/src/xrt/targets/common/target_screen_pin.c) diff --git a/tests/tests_ipc_proto.py b/tests/tests_ipc_proto.py index 4134dcdffe..e46446ab6e 100644 --- a/tests/tests_ipc_proto.py +++ b/tests/tests_ipc_proto.py @@ -160,7 +160,7 @@ def test_segment_calls_are_appended(self): # Only later appends may follow them (ADR-051's status calls, display # dashboard phase 7's re-probe request, phase 8's launch settings). later = ("system_request_display_reprobe", "system_reload_service_config", "system_workspace_launch", - "system_workspace_hotkey_suspend") + "system_workspace_hotkey_suspend", "weave_get_segments") self.assertTrue(all(n.startswith("system_get_") or n in later for n in names[i + 3:]), names[i + 3:]) enable = self._call("compositor_segments_enable") @@ -204,9 +204,9 @@ def test_reprobe_request_is_appended(self): # Display dashboard phase 7: `dp use|reset --screen` asks the running # service to re-probe now. Session-free, DIAG only, no payload — # appended, so every earlier command keeps its enum value (only phase - # 8's three calls follow it). + # 8's three calls and #1884's weave_get_segments follow it). names = [c.name for c in self.p.calls] - self.assertEqual(names[-4], "system_request_display_reprobe") + self.assertEqual(names[-5], "system_request_display_reprobe") call = self._call("system_request_display_reprobe") self.assertEqual(call.in_args, []) self.assertEqual(call.out_args, []) @@ -219,10 +219,11 @@ def test_workspace_launch_calls_are_appended(self): # to re-apply service.json (no payload), `workspace launch ` to # spawn a controller through the hotkey's own path (the id in, a launch # status out), and a hotkey-capture box takes the launch hook out / - # puts it back (one bool in). Session-free, DIAG only, the LAST three. + # puts it back (one bool in). Session-free, DIAG only; only #1884's + # weave_get_segments follows them. names = [c.name for c in self.p.calls] - self.assertEqual(names[-3:], ["system_reload_service_config", "system_workspace_launch", - "system_workspace_hotkey_suspend"]) + self.assertEqual(names[-4:-1], ["system_reload_service_config", "system_workspace_launch", + "system_workspace_hotkey_suspend"]) suspend = self._call("system_workspace_hotkey_suspend") self.assertEqual([(a.name, a.typename) for a in suspend.in_args], [("suspend", "bool")]) self.assertEqual(suspend.out_args, []) @@ -244,6 +245,24 @@ def test_workspace_launch_calls_are_appended(self): self.assertIn("ipc_handle_system_workspace_launch(", text) self.assertIn("ipc_handle_system_workspace_hotkey_suspend(", text) + def test_weave_segments_call_is_appended(self): + # XR_DXR_weave v19 (#1884, ADR-047 Amendment 4): a present-owner's + # per-screen segment table (with every screen's eyes) crosses as its + # own call, APPENDED at the end — weave_submit's reply is untouched, so + # a caller that does not chain XrWeaveOutputRectPartsDXR sends exactly + # the pre-v19 messages. The table is the per-segment views' struct. + names = [c.name for c in self.p.calls] + self.assertEqual(names[-1], "weave_get_segments") + call = self._call("weave_get_segments") + self.assertEqual(call.in_args, []) + self.assertEqual([(a.name, a.typename) for a in call.out_args], + [("metrics", "struct xrt_segment_metrics")]) + self.assertFalse(call.in_handles or call.out_handles or call.varlen) + sub = [a.name for a in self._call("weave_submit").out_args] + self.assertEqual(sub, ["have_output", "width", "height", "fence_value", "eyes"]) + text = self._generate("generate_server_c", "ipc_server_generated.c") + self.assertIn("ipc_handle_weave_get_segments(", text) + if __name__ == "__main__": if len(sys.argv) < 2: diff --git a/tests/tests_weave_segments.cpp b/tests/tests_weave_segments.cpp new file mode 100644 index 0000000000..d8735533f3 --- /dev/null +++ b/tests/tests_weave_segments.cpp @@ -0,0 +1,305 @@ +// Copyright 2026, The DisplayXR Project +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief XR_DXR_weave v19 (#1884, ADR-047 Amendment 4): a present-owner's + * weave rects split per screen. The segment table the service cuts the + * bound window into (comp_segments_compute), the per-rect parts the + * client derives from it (u_weave_rect_parts), and the wire message + * that carries the table with every screen's eyes. + */ + +#include "util/comp_segments.h" +#include "util/u_weave_rect_parts.h" + +#ifdef DXR_TESTS_HAVE_IPC_PROTOCOL +#include "shared/ipc_protocol.h" +#include "ipc_protocol_generated.h" +#endif + +#include "catch_amalgamated.hpp" + +#include + +namespace { + +/* + * Two 1920x1080 3D screens side by side: the primary (system default) at the + * desktop origin, the second to its right. + */ +const uint64_t PRIMARY_ID = 0x1001; +const uint64_t SECOND_ID = 0x2002; + +void +two_screens(comp_segments_screen out[2]) +{ + std::memset(out, 0, sizeof(comp_segments_screen) * 2); + out[0].id = PRIMARY_ID; + out[0].desktop = {0, 0, 1920, 1080}; + out[0].native_w = 1920; + out[0].native_h = 1080; + out[0].is_primary = true; + out[0].has_dp_factory = true; + out[1].id = SECOND_ID; + out[1].desktop = {1920, 0, 1920, 1080}; + out[1].native_w = 1920; + out[1].native_h = 1080; + out[1].has_dp_factory = true; +} + +xrt_rect +xr(int x, int y, int w, int h) +{ + xrt_rect r; + r.offset.w = x; + r.offset.h = y; + r.extent.w = w; + r.extent.h = h; + return r; +} + +xrt_rect +from_seg(const comp_seg_rect &r) +{ + return xr(r.x, r.y, (int)r.w, (int)r.h); +} + +/* + * The table the service publishes for a weave (what comp_d3d11_segments_get_metrics + * makes of a split table): the geometry, eyes left for the caller to fill. A + * table that is not split publishes count 0 — that is the single-DP weave. + */ +xrt_segment_metrics +publish(const comp_seg_rect &window_desktop, const comp_segments_screen *screens, uint32_t n) +{ + xrt_segment_metrics m; + std::memset(&m, 0, sizeof(m)); + if (n < 2) { + return m; // the manager is enabled only with two or more screens (comp_d3d11_segments_set_screens) + } + comp_segment_table t; + const comp_seg_rect canvas = {0, 0, window_desktop.w, window_desktop.h}; + comp_segments_compute(&window_desktop, &canvas, screens, n, &t); + if (!comp_segments_table_is_split(&t, window_desktop.w, window_desktop.h) || t.count > XRT_MAX_SEGMENTS) { + return m; + } + for (uint32_t k = 0; k < t.count; k++) { + m.seg[k].screen_id = t.seg[k].screen_id; + m.seg[k].window_rect = from_seg(t.seg[k].window_rect); + m.seg[k].screen_rect = from_seg(t.seg[k].screen_rect); + m.seg[k].is_primary = t.seg[k].is_primary; + m.seg[k].has_dp = true; + m.seg[k].woven = true; + } + m.count = t.count; + m.canvas = from_seg(canvas); + m.window_screen_left = window_desktop.x; + m.window_screen_top = window_desktop.y; + m.window_pixel_width = window_desktop.w; + m.window_pixel_height = window_desktop.h; + return m; +} + +void +check_rect(const xrt_rect &r, int x, int y, int w, int h) +{ + CHECK(r.offset.w == x); + CHECK(r.offset.h == y); + CHECK(r.extent.w == w); + CHECK(r.extent.h == h); +} + +} // namespace + +TEST_CASE("weave segments: a rect straddling the seam is cut into one part per screen") +{ + comp_segments_screen screens[2]; + two_screens(screens); + // A 1000x600 window whose client area starts at desktop x = 1400: its + // first 520 px are on the primary screen, the rest on the second. + const comp_seg_rect win = {1400, 100, 1000, 600}; + const xrt_segment_metrics m = publish(win, screens, 2); + REQUIRE(m.count == 2); + CHECK(m.seg[0].screen_id == PRIMARY_ID); + CHECK(m.seg[0].is_primary); + check_rect(m.seg[0].window_rect, 0, 0, 520, 600); + check_rect(m.seg[0].screen_rect, 1400, 100, 520, 600); + CHECK(m.seg[1].screen_id == SECOND_ID); + check_rect(m.seg[1].window_rect, 520, 0, 480, 600); + check_rect(m.seg[1].screen_rect, 0, 100, 480, 600); + + // An inline-3D element across the seam (window x 400..700). + const xrt_rect rects[] = {xr(400, 50, 300, 200)}; + u_weave_rect_part parts[8]; + const uint32_t n = u_weave_rect_parts(&m, rects, 1, parts, 8); + REQUIRE(n == 2); + + CHECK(parts[0].rect_index == 0); + CHECK(parts[0].segment_index == 0); + check_rect(parts[0].window_rect, 400, 50, 120, 200); + check_rect(parts[0].rect_rel, 0, 0, 120, 200); + check_rect(parts[0].screen_rect, 1800, 150, 120, 200); + + CHECK(parts[1].rect_index == 0); + CHECK(parts[1].segment_index == 1); + check_rect(parts[1].window_rect, 520, 50, 180, 200); + check_rect(parts[1].rect_rel, 120, 0, 180, 200); + check_rect(parts[1].screen_rect, 0, 150, 180, 200); + + // The two parts tile the rect exactly: no gap, no overlap at the seam. + CHECK(parts[0].rect_rel.extent.w + parts[1].rect_rel.extent.w == rects[0].extent.w); + CHECK(parts[0].rect_rel.offset.w + parts[0].rect_rel.extent.w == parts[1].rect_rel.offset.w); +} + +TEST_CASE("weave segments: a rect wholly on one screen of a split window is one part, the whole rect") +{ + comp_segments_screen screens[2]; + two_screens(screens); + const comp_seg_rect win = {1400, 100, 1000, 600}; + const xrt_segment_metrics m = publish(win, screens, 2); + REQUIRE(m.count == 2); + + const xrt_rect rects[] = {xr(10, 10, 100, 100), xr(800, 300, 150, 120)}; + u_weave_rect_part parts[8]; + const uint32_t n = u_weave_rect_parts(&m, rects, 2, parts, 8); + REQUIRE(n == 2); + CHECK(parts[0].rect_index == 0); + CHECK(parts[0].segment_index == 0); + check_rect(parts[0].window_rect, 10, 10, 100, 100); + check_rect(parts[0].rect_rel, 0, 0, 100, 100); + check_rect(parts[0].screen_rect, 1410, 110, 100, 100); + CHECK(parts[1].rect_index == 1); + CHECK(parts[1].segment_index == 1); + check_rect(parts[1].window_rect, 800, 300, 150, 120); + check_rect(parts[1].rect_rel, 0, 0, 150, 120); + check_rect(parts[1].screen_rect, 280, 400, 150, 120); +} + +TEST_CASE("weave segments: a window on the primary screen alone is not split — no parts, base eyes") +{ + comp_segments_screen screens[2]; + two_screens(screens); + // Wholly on the primary: the single-DP weave, byte for byte (count 0). + const comp_seg_rect win = {100, 100, 1000, 600}; + const xrt_segment_metrics m = publish(win, screens, 2); + CHECK(m.count == 0); + const xrt_rect rects[] = {xr(400, 50, 300, 200)}; + u_weave_rect_part parts[8]; + CHECK(u_weave_rect_parts(&m, rects, 1, parts, 8) == 0); + + // A one-screen system can never split. + const xrt_segment_metrics one = publish({1400, 100, 1000, 600}, screens, 1); + CHECK(one.count == 0); +} + +TEST_CASE("weave segments: a window wholly on the second screen is still per screen") +{ + // Off the primary entirely: one segment, but not the primary's — the + // service weaves it with the second screen's DP and reports it. + comp_segments_screen screens[2]; + two_screens(screens); + const comp_seg_rect win = {2100, 100, 800, 600}; + const xrt_segment_metrics m = publish(win, screens, 2); + REQUIRE(m.count == 1); + CHECK(m.seg[0].screen_id == SECOND_ID); + CHECK_FALSE(m.seg[0].is_primary); + const xrt_rect rects[] = {xr(0, 0, 800, 600)}; + u_weave_rect_part parts[4]; + REQUIRE(u_weave_rect_parts(&m, rects, 1, parts, 4) == 1); + check_rect(parts[0].screen_rect, 180, 100, 800, 600); +} + +TEST_CASE("weave segments: a screen left of the primary (negative desktop x)") +{ + comp_segments_screen screens[2]; + two_screens(screens); + screens[1].desktop = {-1920, 0, 1920, 1080}; + const comp_seg_rect win = {-300, 0, 1000, 500}; + const xrt_segment_metrics m = publish(win, screens, 2); + REQUIRE(m.count == 2); + // Left to right: the left screen first. + CHECK(m.seg[0].screen_id == SECOND_ID); + CHECK(m.seg[1].screen_id == PRIMARY_ID); + const xrt_rect rects[] = {xr(200, 0, 200, 100)}; + u_weave_rect_part parts[4]; + REQUIRE(u_weave_rect_parts(&m, rects, 1, parts, 4) == 2); + check_rect(parts[0].window_rect, 200, 0, 100, 100); + check_rect(parts[0].screen_rect, 1820, 0, 100, 100); + check_rect(parts[1].window_rect, 300, 0, 100, 100); + check_rect(parts[1].screen_rect, 0, 0, 100, 100); +} + +TEST_CASE("weave segments: empty and off-screen rects yield nothing; the cap holds") +{ + comp_segments_screen screens[2]; + two_screens(screens); + const xrt_segment_metrics m = publish({1400, 100, 1000, 600}, screens, 2); + REQUIRE(m.count == 2); + u_weave_rect_part parts[4]; + + const xrt_rect empty[] = {xr(400, 50, 0, 200), xr(400, 50, 300, -1)}; + CHECK(u_weave_rect_parts(&m, empty, 2, parts, 4) == 0); + + const xrt_rect outside[] = {xr(1200, 0, 100, 100)}; + CHECK(u_weave_rect_parts(&m, outside, 1, parts, 4) == 0); + + const xrt_rect two_straddling[] = {xr(400, 0, 300, 100), xr(400, 200, 300, 100)}; + CHECK(u_weave_rect_parts(&m, two_straddling, 2, parts, 3) == 3); // the 4th part dropped + CHECK(u_weave_rect_parts(&m, two_straddling, 2, parts, 4) == 4); + + // A count past the per-segment view sets is "not segmented". + xrt_segment_metrics bad = m; + bad.count = XRT_MAX_SEGMENTS + 1; + CHECK(u_weave_rect_parts(&bad, two_straddling, 2, parts, 4) == 0); +} + +#ifdef DXR_TESTS_HAVE_IPC_PROTOCOL +TEST_CASE("weave segments: weave_get_segments carries the table and every screen's eyes verbatim") +{ + STATIC_REQUIRE(sizeof(ipc_weave_get_segments_reply) <= IPC_BUF_SIZE); + + comp_segments_screen screens[2]; + two_screens(screens); + xrt_segment_metrics m = publish({1400, 100, 1000, 600}, screens, 2); + REQUIRE(m.count == 2); + // Each screen's own tracker: different viewers, each in its own screen's + // display space. + for (uint32_t k = 0; k < 2; k++) { + m.seg[k].screen_width_m = 0.3442f; + m.seg[k].screen_height_m = 0.1936f; + m.seg[k].eyes.count = 2; + m.seg[k].eyes.valid = true; + m.seg[k].eyes.is_tracking = k == 0; + m.seg[k].eyes.eyes[0].x = -0.032f + 0.1f * (float)k; + m.seg[k].eyes.eyes[1].x = 0.032f + 0.1f * (float)k; + m.seg[k].eyes.eyes[0].z = 0.6f; + m.seg[k].eyes.eyes[1].z = 0.6f; + m.seg[k].have_eyes = true; + } + m.generation = 9; + + // The generated proxy memcpys the reply struct off the wire. + ipc_weave_get_segments_reply reply; + std::memset(&reply, 0, sizeof(reply)); + reply.result = XRT_SUCCESS; + reply.metrics = m; + ipc_weave_get_segments_reply wire; + std::memcpy(&wire, &reply, sizeof(wire)); + REQUIRE(std::memcmp(&wire.metrics, &m, sizeof(m)) == 0); + + // The client cuts its rects from what came off the wire: the straddling + // rect's right part carries the SECOND screen's eyes. + const xrt_rect rects[] = {xr(400, 50, 300, 200)}; + u_weave_rect_part parts[4]; + REQUIRE(u_weave_rect_parts(&wire.metrics, rects, 1, parts, 4) == 2); + const xrt_segment_metric &right = wire.metrics.seg[parts[1].segment_index]; + CHECK(right.screen_id == SECOND_ID); + CHECK(right.have_eyes); + CHECK(right.eyes.eyes[0].x == Catch::Approx(0.068f)); + CHECK_FALSE(right.eyes.is_tracking); + const xrt_segment_metric &left = wire.metrics.seg[parts[0].segment_index]; + CHECK(left.screen_id == PRIMARY_ID); + CHECK(left.eyes.eyes[0].x == Catch::Approx(-0.032f)); + CHECK(wire.metrics.generation == 9); +} +#endif From 5def14c048fd55ed6039e8d0a26d09c99065d41b Mon Sep 17 00:00:00 2001 From: dfattal Date: Sat, 10 Oct 2026 11:22:06 -0700 Subject: [PATCH 2/3] test(weave-probe): log the v19 per-screen segments and rect parts weave_rpc_probe_d3d11_win chains XrWeaveOutputRectPartsDXR when the runtime reports XR_DXR_weave spec >= 19 and logs the per-screen table and the weave rect's parts on every geometry change, so a two-panel rig can watch a window dragged across the seam go from one display processor to two and back. Part of #1884 Co-Authored-By: Claude Opus 5.5 --- .../probes/weave_rpc_probe_d3d11_win/main.cpp | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test_apps/probes/weave_rpc_probe_d3d11_win/main.cpp b/test_apps/probes/weave_rpc_probe_d3d11_win/main.cpp index b83c290304..1038085744 100644 --- a/test_apps/probes/weave_rpc_probe_d3d11_win/main.cpp +++ b/test_apps/probes/weave_rpc_probe_d3d11_win/main.cpp @@ -55,6 +55,18 @@ * Closing the probe must destroy only its own DP: the panel keeps its mode and * the split does not change. * + * ── Spec v19 per-screen parts (#1884, ADR-047 Amendment 4) ────────────────── + * + * On a runtime reporting spec >= 19 the probe chains XrWeaveOutputRectPartsDXR + * on every submit and logs, on every geometry change (the struct's + * `generation`), how the service split the window: each screen segment + * (display id, window rect, woven or flat, its eyes) and each part of the + * weave rect. Run with `--rect=full` on a two-panel box and drag the window + * across the seam: the log must go from `segments=0` (one panel) to + * `segments=2` with two parts whose eyes differ (each panel's own tracker), + * and back. The probe itself still renders ONE eye pair (the base eyes) — + * it proves the runtime half; rendering per part is the browser's job. + * * ── Spec v8 per-region hardware wish (browser#88) ──────────────────────────── * * Two flags drive the two ways to declare a region PHYSICALLY FLAT, so the wish @@ -1377,6 +1389,14 @@ wWinMain(HINSTANCE hInst, HINSTANCE, PWSTR pCmdLine, int) } XrWeaveOutputDXR out = {XR_TYPE_WEAVE_OUTPUT_DXR}; + // v19 (#1884): ask for the per-screen split of the weave rect. static: + // the struct is ~4 KB and is re-read every frame. + static XrWeaveOutputRectPartsDXR parts; + if (g_weaveSpecVersion >= 19) { + memset(&parts, 0, sizeof(parts)); + parts.type = XR_TYPE_WEAVE_OUTPUT_RECT_PARTS_DXR; + out.next = &parts; + } LARGE_INTEGER t0, t1; QueryPerformanceCounter(&t0); @@ -1411,6 +1431,38 @@ wWinMain(HINSTANCE hInst, HINSTANCE, PWSTR pCmdLine, int) } } + // v19: log the per-screen split on a geometry change only (generation). + if (g_weaveSpecVersion >= 19) { + static uint64_t s_lastGen = UINT64_MAX; + static uint32_t s_lastSegs = UINT32_MAX; + if (parts.generation != s_lastGen || parts.segmentCount != s_lastSegs) { + s_lastGen = parts.generation; + s_lastSegs = parts.segmentCount; + LOG_INFO("v19 per-screen: segments=%u parts=%u generation=%llu%s", parts.segmentCount, + parts.partCount, (unsigned long long)parts.generation, + parts.segmentCount == 0 ? " (one display processor: base eyes apply)" : ""); + for (uint32_t k = 0; k < parts.segmentCount; k++) { + const XrWeaveScreenSegmentDXR &g = parts.segments[k]; + LOG_INFO(" segment %u: display 0x%016llx%s window %d,%d %dx%d screen %d,%d %s " + "eyes(valid=%d track=%d n=%u L=%.4f,%.4f,%.4f)", + k, (unsigned long long)g.displayId, g.isPrimary ? " (primary)" : "", + g.windowRect.offset.x, g.windowRect.offset.y, g.windowRect.extent.width, + g.windowRect.extent.height, g.screenRect.offset.x, g.screenRect.offset.y, + g.woven ? "woven" : "FLAT 2D", g.eyesValid, g.eyesTracking, g.eyeCount, + g.eyeCount > 0 ? g.eyes[0].x : 0.0f, g.eyeCount > 0 ? g.eyes[0].y : 0.0f, + g.eyeCount > 0 ? g.eyes[0].z : 0.0f); + } + for (uint32_t i = 0; i < parts.partCount; i++) { + const XrWeaveRectPartDXR &p = parts.parts[i]; + LOG_INFO(" part %u: rect %u on segment %u — window %d,%d %dx%d, in-rect %d,%d, " + "on-screen %d,%d", + i, p.rectIndex, p.segmentIndex, p.windowRect.offset.x, p.windowRect.offset.y, + p.windowRect.extent.width, p.windowRect.extent.height, p.rectRelative.offset.x, + p.rectRelative.offset.y, p.screenRect.offset.x, p.screenRect.offset.y); + } + } + } + // Feed the returned tracked eyes into next frame's off-axis render. if (out.eyesValid == XR_TRUE && out.eyeCount >= 2) { g_lastEyeX[0] = out.eyes[0].x; From c3573e52d9f6e888cd7c1f5ffbba1757cc19ab12 Mon Sep 17 00:00:00 2001 From: dfattal Date: Sat, 10 Oct 2026 11:22:07 -0700 Subject: [PATCH 3/3] docs(multi-screen): ADR-047 Amendment 4 and the XR_DXR_weave v19 contract Present-owner weave rects are segmented per screen with per-screen eyes out: ADR-047 Amendment 4, comp-segments.md "Present-owners on the weave service", XR_DXR_weave.md section 5g + version history + consumers, the index.json summary, and the multi-screen roadmap's M6 row. Part of #1884 Co-Authored-By: Claude Opus 5.5 --- ...ti-screen-segments-and-per-screen-views.md | 42 ++++++++++ docs/architecture/comp-segments.md | 42 +++++++++- docs/roadmap/multi-screen.md | 2 +- docs/specs/extensions/XR_DXR_weave.md | 76 +++++++++++++++++++ docs/specs/extensions/index.json | 2 +- 5 files changed, 160 insertions(+), 4 deletions(-) diff --git a/docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md b/docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md index 0b5384de8b..bd0210c044 100644 --- a/docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md +++ b/docs/adr/ADR-047-multi-screen-segments-and-per-screen-views.md @@ -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. diff --git a/docs/architecture/comp-segments.md b/docs/architecture/comp-segments.md index 2f44e1ca78..48cc057a43 100644 --- a/docs/architecture/comp-segments.md +++ b/docs/architecture/comp-segments.md @@ -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. @@ -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). diff --git a/docs/roadmap/multi-screen.md b/docs/roadmap/multi-screen.md index 195c9aa6b0..ead0df7c75 100644 --- a/docs/roadmap/multi-screen.md +++ b/docs/roadmap/multi-screen.md @@ -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 diff --git a/docs/specs/extensions/XR_DXR_weave.md b/docs/specs/extensions/XR_DXR_weave.md index 4075da2aff..867b5b7d70 100644 --- a/docs/specs/extensions/XR_DXR_weave.md +++ b/docs/specs/extensions/XR_DXR_weave.md @@ -981,6 +981,77 @@ starts it. macOS and Android accept and ignore it — the output stays woven — as a pre-v13 runtime does with any unknown chained struct. One `weave(v13)` WARN in the service log per edge. +## 5g. Per-screen parts of each weave rect (v19, #1884) + +```c +#define XR_TYPE_WEAVE_OUTPUT_RECT_PARTS_DXR ((XrStructureType)1004999292) +#define XR_WEAVE_MAX_SEGMENTS_DXR 2 +#define XR_WEAVE_MAX_RECT_PARTS_DXR (XR_WEAVE_SUBMIT_MAX_RECTS_DXR * XR_WEAVE_MAX_SEGMENTS_DXR) + +typedef struct XrWeaveScreenSegmentDXR { // one screen's part of the bound window + uint64_t displayId; // XrDisplayDXR::displayId + XrRect2Di windowRect; // window-relative device px + XrRect2Di screenRect; // the same pixels, relative to the screen + XrExtent2Di screenSize; // device px + XrExtent2Df screenSizeMeters; // 0 = unknown + XrBool32 isPrimary, woven; + uint32_t eyeCount; + XrVector3f eyes[XR_WEAVE_MAX_EYES_DXR]; // THIS screen's display space + XrBool32 eyesValid, eyesTracking; +} XrWeaveScreenSegmentDXR; + +typedef struct XrWeaveRectPartDXR { // one rect's pixels on one screen + uint32_t rectIndex, segmentIndex; + XrRect2Di windowRect, rectRelative, screenRect; +} XrWeaveRectPartDXR; + +typedef struct XrWeaveOutputRectPartsDXR { // chained on XrWeaveOutputDXR::next + XrStructureType type; void* next; + uint32_t segmentCount; // 0 = one display processor: use the base eyes + XrWeaveScreenSegmentDXR segments[XR_WEAVE_MAX_SEGMENTS_DXR]; + uint32_t partCount; + XrWeaveRectPartDXR parts[XR_WEAVE_MAX_RECT_PARTS_DXR]; + uint64_t generation; // moves on a geometry change only +} XrWeaveOutputRectPartsDXR; +``` + +On a system with two or more 3D screens (multi-screen, ADR-047) the bound window can straddle a +seam. The Windows service then weaves the window **per screen**: each screen's part by that +screen's display processor, at that screen's interlace phase, and each part has its own viewer +(that screen's tracker). One eye pair per submit is wrong for the part on the other screen. + +**What the runtime does.** For a batch (v3) or N-view (v6) submit from a session whose window +spans screens, every screen's segment is woven by its own DP into the one handback (scissored to +the segment); a segment no DP can weave shows one view flat (the left view of a stereo pair, the +middle view of an N-view atlas). A legacy single-rect submit is never segmented. The v4 overlay is +composited over the whole handback after the per-screen weave. + +**What the caller gets.** Chain a zero-initialised `XrWeaveOutputRectPartsDXR` on +`XrWeaveOutputDXR::next`: + +- `segmentCount == 0`: the window was woven by one DP (on one screen, a legacy submit, a + one-screen system, a non-Windows runtime, or a pre-v19 runtime that skipped the struct). The + base `XrWeaveOutputDXR::eyes` apply to every rect, exactly as before. +- `segmentCount >= 1`: `segments` lists the window's screens left to right, each with its eyes in + **that screen's display space** (metres, origin at that screen's centre), its physical size and + whether it is woven. `parts` lists every non-empty (rect, segment) intersection, by rect then + left to right. Render the pixels of `parts[i].rectRelative` inside rect `parts[i].rectIndex` + from `segments[parts[i].segmentIndex].eyes`; frame that part's off-axis projection against its + screen's physical rectangle using `parts[i].screenRect` (where those pixels sit on the screen) + and `screenSize` / `screenSizeMeters`. A rect wholly on one screen has one part, the whole rect. + The base eyes still describe the primary screen. + +The eyes are for the NEXT frame, as the base eyes are. `generation` moves on a move / resize / +screen change, so a caller re-plans its per-part render targets on a change only. + +**Drag snap.** From v19 `xrWeaveSnapWindowRectDXR` snaps a segmented window to the lattice of the +screen that holds the majority of it at the proposed position, with the multi-screen hand-off's +hysteresis (a clear 20 % margin held 0.5 s, so a drag across the seam does not flip lattices under +the cursor). No API change. + +**Gate.** A caller gates on `extensionVersion >= 19` only to know it may expect parts; chaining +the struct against an older runtime is harmless (skipped, stays zero = "use the base eyes"). + ## 6. Version history | Version | Change | @@ -1009,6 +1080,10 @@ it keeps such an overlay off the wire there instead. | 18 | No new structure: on Android an overlay is **per submit** (composited only on a submit that chains one; before v18 the last imported overlay was composited on every later submit) and a chained overlay the engine cannot import **refuses the submit before the woven output is touched** — the v16 rule. An Android caller that hands its whole 2D over as the overlay gates on `extensionVersion >= 18`. The macOS engine still drops an unreadable overlay. | **v18 is the same gate on Android**, for the same kind of caller. +| 19 | `XrWeaveOutputRectPartsDXR` + `XrWeaveScreenSegmentDXR` / `XrWeaveRectPartDXR` (out, per submit): a window spanning two 3D screens is woven per screen and the caller gets each screen's eyes and every weave rect cut at the seams; the drag snap follows the majority screen's lattice (§5g, #1884, ADR-047 Amendment 4). Windows service; `segmentCount` 0 elsewhere. | + +**v19 needs no gate to send**: an older runtime skips the struct and it stays zero, which reads as +"one display processor, use the base eyes". **v16 is a gate for one kind of caller**: one whose overlay IS its 2D (a whole-page overlay) must not send it to an older desktop-Linux runtime, which would weave without it and return @@ -1056,6 +1131,7 @@ implicit. §4c's entry point + struct settle it: **v9**. | DisplayXR Browser (Chromium fork) | GPU-process sync weave | Batch (v3) when the runtime reports spec ≥ 3; per-element legacy loop otherwise | | CEF weave host (Step A) | Browser-process sync | Legacy | | DisplayXR Browser on Android | Chromium GPU process → satellite compositor (ADR-036 D3) | Batch (v3/v7) — AHardwareBuffer handles + published window geometry | +| DisplayXR Browser on two 3D screens (planned, #1884) | Chromium GPU process | Batch (v3) or v6 + `XrWeaveOutputRectPartsDXR` (v19): render each rect part from its screen's eyes | | DisplayXR Browser on desktop Linux (planned, #1699) | Chromium GPU process (GL/EGL) → comp_multi service | v10 dma-buf + `sync_file` fences; gate on spec ≥ 10. Move-synchronised Wayland drag: v12 woven origin (§5e), gate on spec ≥ 12 | When changing the header, byte-sync every consumer's vendored copy and rebuild it diff --git a/docs/specs/extensions/index.json b/docs/specs/extensions/index.json index 905e66afcc..777d1e7c5b 100644 --- a/docs/specs/extensions/index.json +++ b/docs/specs/extensions/index.json @@ -80,7 +80,7 @@ "XR_DXR_weave": { "group": "rendering", "title": "Window Weave Service", - "summary": "Window-bound synchronous weave for present-owners: hand the runtime a stereo texture and a window rect, get back a weaved shared texture and a fence" + "summary": "Window-bound synchronous weave for present-owners: hand the runtime a stereo texture and a window rect, get back a weaved shared texture and a fence; a window spanning two 3D screens is woven per screen, with each screen's eyes and each rect's per-screen parts handed back" }, "XR_DXR_lift": { "group": "rendering",