diff --git a/docs/specs/extensions/XR_DXR_display_info.md b/docs/specs/extensions/XR_DXR_display_info.md index 44f1effce4..b66b9e37d2 100644 --- a/docs/specs/extensions/XR_DXR_display_info.md +++ b/docs/specs/extensions/XR_DXR_display_info.md @@ -941,7 +941,9 @@ XrResult xrRequestDisplayModeDXR( changes. Requesting `XR_DISPLAY_MODE_2D_DXR` over an active 3D mode therefore shows the *woven* atlas flat (blurry); an application fading its parallax to zero converges back to a sharp image — the building block for app-authored 2D↔3D transitions such as the - MANUAL eye-tracking loss flow. + MANUAL eye-tracking loss flow. (A weave present-owner that wants the content flat instead — + one view, sharp — opts in per submit with `XrWeaveSubmitMonoIn2DDXR`, `XR_DXR_weave` v13 §5f; + this function's own semantics do not change.) - **Lifetime of the override.** The override holds until the next `xrRequestDisplayRenderingModeDXR` call, whose mode's default hardware state then applies (re-requesting the current mode index restores its default). diff --git a/docs/specs/extensions/XR_DXR_weave.md b/docs/specs/extensions/XR_DXR_weave.md index 818cb0d35e..00d11c2a21 100644 --- a/docs/specs/extensions/XR_DXR_weave.md +++ b/docs/specs/extensions/XR_DXR_weave.md @@ -3,7 +3,7 @@ | Field | Value | |---|---| | **Extension Name** | `XR_DXR_weave` | -| **Spec Version** | 12 | +| **Spec Version** | 13 | | **Extension Type** | Instance extension (service path — Windows/D3D11, macOS/comp_multi-Vulkan #759, Android/comp_multi-Vulkan #1036, desktop Linux/comp_multi-Vulkan dma-buf #1699 when the service carries its engine; the snap and its bulk grid form also work in-process on desktop Linux, §5c / #1588 / #1723) | | **Header** | `src/external/openxr_includes/openxr/XR_DXR_weave.h` (canonical; auto-syncs to `displayxr-extensions`) | | **Status** | Provisional (`1004999190–198` type block, pending Khronos registry; `199` reserved, see §2c; v9+ additions in a fresh `1004999240–249` decade — 240 v9, 241–245 v10, 246 v11, 247–248 v12) | @@ -681,8 +681,8 @@ duplicates rather than transfers. Engine contract: `src/xrt/include/xrt/xrt_weav **Off-panel 2D (#1654).** The part of the bound window that the published geometry puts off the panel comes back flat, not woven: the runtime paints each off-panel band of the output with the -centre view of the submitted content (for a batch rect, its left half unsqueezed; for v6, the -centre tile), in register with the window and under the v4 overlay, and skips the weave entirely +flat view of the submitted content (§5f's table: for a stereo pair the left view — a batch rect's +left half unsqueezed; for v6 with more views, the view nearest the display axis), in register with the window and under the v4 overlay, and skips the weave entirely for a window wholly off the panel — so a caller composites the output as usual and never clips it itself. `DXR_SPAN_2D=0` restores the fully woven output. @@ -690,7 +690,7 @@ itself. `DXR_SPAN_2D=0` restores the fully woven output. processor to wish to, the runtime instead paints the union of the submit's `XrWeaveSubmitFlatRegionsDXR` rects and the latched `xrWeaveSetScreenFlatRegionsDXR` rects (clipped to the bound window, taking effect at the next submit) flat in the woven output — the -input pixels 1:1 for a batch submit, the centre view for v6 — a platform exception to §2c's +input pixels 1:1 for a batch submit, the flat view (§5f) for v6 — a platform exception to §2c's "pixels unaffected" that `DXR_WEAVE_FLAT_2D=0` switches off. ## 5e. Woven origin per output — present-owner move sync (v12, browser-pvt#180) @@ -776,6 +776,67 @@ service): the app re-binds a stepped origin every 4 frames with a logical origin checks on every output that the reported origin equals the last bind, the logical pair is echoed verbatim and the serial strictly increases. +## 5f. One flat view while the panel is in hardware 2D (v13) + +```c +#define XR_TYPE_WEAVE_SUBMIT_MONO_IN_2D_DXR ((XrStructureType)1004999249) + +typedef struct XrWeaveSubmitMonoIn2DDXR { + XrStructureType type; // XR_TYPE_WEAVE_SUBMIT_MONO_IN_2D_DXR + const void* XR_MAY_ALIAS next; + XrBool32 monoIn2D; // XR_TRUE: one view flat, no weave, while hardware is 2D +} XrWeaveSubmitMonoIn2DDXR; +``` + +**Why.** `xrRequestDisplayModeDXR` is hardware-only (`XR_DXR_display_info`): 2D over woven +content turns the lens off and keeps the weave, so every view shows interlaced through no lens, +soft and doubled. Apps build fade-to-flat transitions on exactly that, so it stays the default. +A present-owner that drops the lens for its OWN reason — the browser holding the panel 2D while +one of its menus is open over an inline-3D page — wants the content flat and sharp instead, as if +a 2D rendering mode were active, without the page being told anything. + +**Why not a 2D rendering mode.** `xrRequestDisplayRenderingModeDXR` is the page's channel and is +display-global: on Windows the service broadcasts `XrEventDataRenderingModeChangedDXR` to every +session and re-forces a 3D mode for a weave client that keeps submitting; on desktop Linux the +content mode a present-owner pushes is recorded and read by nothing in the weave engine, and its +seed at `xrBeginSession` is whatever mode the head device holds. Neither weave engine reads the +content mode at all. A per-submit flag scoped to the weave keeps the page's mode, the events and +every other session out of it. + +**Semantics.** Chained on `XrWeaveSubmitInfoDXR`. While `monoIn2D` is `XR_TRUE` **and** this +session's hardware display state is 2D — the state the runtime last confirmed to it, the one its +`XrEventDataHardwareDisplayStateChangedDXR` events report — the runtime skips the weave and paints +the whole output with ONE view of the submitted content at full output resolution, in register with +the window. The path is the off-panel bands' (§5d): the whole output becomes one band, painted from +the same view the bands use, so the two flat paths always match. Flat regions and the v4 overlay are +applied on top as usual. + +**Mono follows the runtime's display-processor-CONFIRMED hardware-2D state for this session, not +the request.** It starts on the first submit after the display processor has accepted the 2D +request, and it ends on the first submit after the confirmed state is 3D again — no stale flat frame +after a release, no woven frame while held. A 2D request the display processor rejects never +starts it. + +**Which view.** One rule for every flat path of the engine (`u_weave_flat_view_index`): + +| submitted views | flat view | +|---|---| +| 1 or 2 — every batch submit, and a 2-view v6 layout | **view 0, the LEFT view** (a batch rect's left half, unsqueezed). Deterministic: both eyes sit about equally far from the display axis, so a nearest-axis rule would flip between them as the head moves and the flat image would jump by the disparity. | +| more than 2 (v6) | the view whose eye is **nearest the display axis** — smallest \|x\| of the per-view eye offset the display processor reports; a tie goes to the lower index | +| more than 2, no per-view eye offsets | the centre index, `(N − 1) / 2` | + +- In hardware 3D the struct has no effect: the output is woven. A caller can therefore chain it for + the whole of its own 2D hold without racing the lens, and a 2D request the display processor + rejected keeps the weave. +- Per submit, no latch. Omitting the struct, or `monoIn2D = XR_FALSE`, is the pre-v13 behaviour. +- Nothing is reported to the session: no event, no change to the active rendering mode, to + `xrLocateViews`, or to the hardware state. + +**Platforms.** The desktop-Linux weave engine implements it (`comp_multi_weave_linux.c`, +`weave_run_frame`; the IPC submit carries it as `ipc_arg_weave_submit::mono_in_2d`). Windows, +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. + ## 6. Version history | Version | Change | @@ -792,6 +853,11 @@ verbatim and the serial strictly increases. | 10 | Desktop-Linux dma-buf transport (§5d, #1699): `XR_WEAVE_HANDLE_KIND_DMABUF_DXR` / `_OPAQUE_FD_DXR`, `XrWeaveDmabufDescDXR` + `XrWeaveOverlayDmabufDescDXR` in, `XrWeaveOutputDmabufDXR` out, `sync_file` fences `XrWeaveSubmitSyncDXR` (acquire) / `XrWeaveOutputSyncDXR` (release, per frame). Desktop Linux becomes a full weave platform when the service carries its engine. | | 11 | `xrWeaveSnapWindowGridDXR` + `XrWeaveSnapGridInfoDXR` / `XrWeaveSnapGridPointDXR` — bulk grid snap: the per-point snap evaluated over a grid by the runtime, one call (one IPC round trip) instead of one per point (§5c, #1723). | | 12 | `XrWeaveOutputOriginDXR` (out, per frame: the origin each woven output was woven for + a serial) and `XrWeaveWindowLogicalOriginDXR` (in: the caller's logical origin + scale, echoed verbatim) — so a present-owner can tag its own Wayland commit for move-synchronised drag (§5e, browser-pvt#180). | +| 13 | `XrWeaveSubmitMonoIn2DDXR` — one flat view (stereo: the left view; N > 2: the view nearest the display axis) instead of the weave while the session's hardware state is 2D (§5f). Desktop Linux; accepted and ignored elsewhere. | + +**v13 is a bump**: one structure type a caller gates on `extensionVersion >= 13`. Against an +older runtime the struct is skipped and the output stays woven, which is also what every +non-Linux v13 runtime does — so the gate decides only whether the caller can expect the flat view. **v12 is a bump**: two structure types a caller must be able to test for. An older runtime skips both chains — the logical origin is simply not latched, and the OUT struct keeps whatever the diff --git a/src/external/openxr_includes/openxr/README.md b/src/external/openxr_includes/openxr/README.md index 69b1720a60..cad47b8b9a 100644 --- a/src/external/openxr_includes/openxr/README.md +++ b/src/external/openxr_includes/openxr/README.md @@ -43,7 +43,7 @@ Rules: | 1004999210–219 | `XR_DXR_display_info` (v16+ additions) | 210 = `XR_TYPE_DISPLAY_DESKTOP_POSITION_DXR` (#715); fresh decade rather than reusing the 005/009 gaps in the original block. 211 = `XR_TYPE_DISPLAY_DESKTOP_INFO_DXR` (spec v18, #1301) — full panel desktop rect + GDI device name, superseding 210. 212 = `XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MULTIVIEW_DXR` (spec v19, #1486) — an `XrViewConfigurationType`, not an `XrStructureType`: the block is shared across every extended core enum. 213 = `XR_TYPE_VIEW_ACTIVITY_STATE_DXR` (spec v21, ADR-041) — per-frame `activeViewCount` chained on `XrViewState`, the other half of the fixed-view-count submission contract | | 1004999220–229 | `XR_DXR_android_surface_binding` | 220 = `XR_TYPE_ANDROID_WINDOW_GEOMETRY_DXR` (#1037), 221 = `XR_TYPE_EVENT_DATA_ANDROID_WINDOW_LAYOUT_HINT_DXR` (spec v2, #1396). The extension's `XR_TYPE_ANDROID_SURFACE_BINDING_CREATE_INFO_DXR` keeps **1004999005** — one of the two unused gaps in the `XR_DXR_display_info` decade — because that value was already published in the (then unimplemented) sketch in `docs/specs/extensions/XR_DXR_display_info.md` §4 | | 1004999230–239 | `XR_DXR_result_codes` | Not an extension — runtime-wide `XrResult` values returned from core entry points, where no extension can have been enabled. 230 = `XR_ERROR_RUNTIME_VERSION_SKEW_DXR` (browser#103), an **error**-class result, so it appears in the header negated as `-1004999230` | -| 1004999240–249 | `XR_DXR_weave` (v9+ additions) | 240 = `XR_TYPE_WEAVE_IPC_CONNECTION_DXR` (`xrWeaveExportIpcConnectionDXR`, spec v9, browser#103); 241 = `XR_TYPE_WEAVE_DMABUF_DESC_DXR`, 242 = `XR_TYPE_WEAVE_OVERLAY_DMABUF_DESC_DXR`, 243 = `XR_TYPE_WEAVE_OUTPUT_DMABUF_DXR`, 244 = `XR_TYPE_WEAVE_SUBMIT_SYNC_DXR`, 245 = `XR_TYPE_WEAVE_OUTPUT_SYNC_DXR` (desktop-Linux dma-buf transport + sync_file fences, spec v10, #1699); 246 = `XR_TYPE_WEAVE_SNAP_GRID_INFO_DXR` (`xrWeaveSnapWindowGridDXR`, bulk grid snap, spec v11, #1723); 247 = `XR_TYPE_WEAVE_OUTPUT_ORIGIN_DXR`, 248 = `XR_TYPE_WEAVE_WINDOW_LOGICAL_ORIGIN_DXR` (woven origin per output for present-owner move sync, spec v12, browser-pvt#180). A fresh decade because the original 190–199 block is exhausted (199 is reserved for the wish mask) | +| 1004999240–249 | `XR_DXR_weave` (v9+ additions) | 240 = `XR_TYPE_WEAVE_IPC_CONNECTION_DXR` (`xrWeaveExportIpcConnectionDXR`, spec v9, browser#103); 241 = `XR_TYPE_WEAVE_DMABUF_DESC_DXR`, 242 = `XR_TYPE_WEAVE_OVERLAY_DMABUF_DESC_DXR`, 243 = `XR_TYPE_WEAVE_OUTPUT_DMABUF_DXR`, 244 = `XR_TYPE_WEAVE_SUBMIT_SYNC_DXR`, 245 = `XR_TYPE_WEAVE_OUTPUT_SYNC_DXR` (desktop-Linux dma-buf transport + sync_file fences, spec v10, #1699); 246 = `XR_TYPE_WEAVE_SNAP_GRID_INFO_DXR` (`xrWeaveSnapWindowGridDXR`, bulk grid snap, spec v11, #1723); 247 = `XR_TYPE_WEAVE_OUTPUT_ORIGIN_DXR`, 248 = `XR_TYPE_WEAVE_WINDOW_LOGICAL_ORIGIN_DXR` (woven origin per output for present-owner move sync, spec v12, browser-pvt#180); 249 = `XR_TYPE_WEAVE_SUBMIT_MONO_IN_2D_DXR` (one flat view instead of the weave while the panel is in hardware 2D, spec v13). The decade is now full. A fresh decade because the original 190–199 block is exhausted (199 is reserved for the wish mask) | | 1004999250–259 | `XR_DXR_wayland_surface_binding` | 250 = `XR_TYPE_WAYLAND_SURFACE_BINDING_CREATE_INFO_DXR` (#757), 251 = `XR_TYPE_WAYLAND_SURFACE_GEOMETRY_DXR` (spec v2) — the app-declared surface size + output refresh, because a `wl_surface` has no intrinsic size and the compositor geometry service cannot bootstrap one. **250 was renumbered from 1004999210**, which collided with `XR_TYPE_DISPLAY_DESKTOP_POSITION_DXR` — the 210–219 decade was already `XR_DXR_display_info`'s. Safe to move: SPEC_VERSION 1, no shipped app chains it | | 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) | diff --git a/src/external/openxr_includes/openxr/XR_DXR_weave.h b/src/external/openxr_includes/openxr/XR_DXR_weave.h index 2a192e8e7b..564acdb68a 100644 --- a/src/external/openxr_includes/openxr/XR_DXR_weave.h +++ b/src/external/openxr_includes/openxr/XR_DXR_weave.h @@ -198,6 +198,11 @@ * back verbatim), so a present-owner can tag its own Wayland commit for * move-synchronised drag. * + * Mono in hardware 2D (SPEC_VERSION 13). XrWeaveSubmitMonoIn2DDXR, chained on + * XrWeaveSubmitInfoDXR: while the session's hardware state is 2D, the output is + * one view (stereo: the left view), flat, instead of the weave. Desktop Linux; + * ignored elsewhere. + * * Woven origin per output (SPEC_VERSION 12, browser-pvt#180). On native Wayland * (GNOME extension version 9+) a drag is MOVE-SYNCHRONISED: the extension holds * the window's actor at the position the buffer it is about to paint was woven @@ -264,7 +269,7 @@ extern "C" { #endif #define XR_DXR_weave 1 -#define XR_DXR_weave_SPEC_VERSION 12 +#define XR_DXR_weave_SPEC_VERSION 13 #define XR_DXR_WEAVE_EXTENSION_NAME "XR_DXR_weave" // Reserved 1004999190..199. Final values reconcile with the Khronos registry @@ -296,6 +301,9 @@ extern "C" { // Spec v12 (browser-pvt#180): woven origin per output, for present-owner move sync. #define XR_TYPE_WEAVE_OUTPUT_ORIGIN_DXR ((XrStructureType)1004999247) #define XR_TYPE_WEAVE_WINDOW_LOGICAL_ORIGIN_DXR ((XrStructureType)1004999248) +// Spec v13: one flat view instead of the weave while the panel is in hardware 2D. +// Last value of the 240..249 decade. +#define XR_TYPE_WEAVE_SUBMIT_MONO_IN_2D_DXR ((XrStructureType)1004999249) //! Upper bound on eye positions carried by XrWeaveSubmitInfoDXR (mirrors the //! runtime's XRT_MAX_VIEWS). Phase 1: carried but unused. @@ -950,6 +958,49 @@ typedef struct XrWeaveOutputOriginDXR { uint64_t serial; //!< monotonic per woven output; 0 = not reported } XrWeaveOutputOriginDXR; +/*! + * @brief Spec v13: present ONE flat view instead of the weave while the panel is + * in hardware 2D (chain on XrWeaveSubmitInfoDXR::next). + * + * xrRequestDisplayModeDXR is hardware-only (XR_DXR_display_info): a 2D request + * over woven content turns the lens off and keeps the weave running, so the + * panel shows every view interlaced through no lens — soft and doubled. Apps + * rely on that for transitions that fade parallax to zero. A present-owner that + * drops the lens for a reason of its OWN (a browser holding the panel 2D while + * one of its menus is open) wants the opposite: the content flat and sharp. + * + * With @c monoIn2D XR_TRUE, and while THIS session's hardware display state is + * 2D (the last state the runtime confirmed to it — the one its + * XrEventDataHardwareDisplayStateChangedDXR events report), the runtime skips + * the weave and paints the whole output with ONE view of the submitted content + * at full output resolution, in register with the window — the same view the + * off-panel 2D bands use (§5d), so the two flat paths match: for a stereo pair + * (every batch submit, and a 2-view v6 layout) view 0, the LEFT view — a batch + * rect's left half, unsqueezed; for a v6 layout of more than two views, the + * view whose eye is nearest the display axis (smallest |x| of the per-view eye + * offset). Flat regions and the v4 overlay are applied on top as usual. Mono + * follows the runtime's display-processor-CONFIRMED hardware-2D state for this + * session, not the request: it starts on the first submit after the 2D state + * is confirmed and ends on the first submit after the confirmed state is 3D + * again. While the hardware state is 3D the struct has no effect, so a + * caller can chain it for the whole of its own 2D hold without racing the + * lens: the output is flat exactly while the runtime holds the panel 2D, and a + * request the display processor rejected keeps the weave. + * + * Per submit, no latch: omitting the struct (or @c monoIn2D XR_FALSE) is the + * pre-v13 behaviour. Nothing is reported to the session — no rendering-mode + * event, no change to the active mode or to xrLocateViews. + * + * Honoured by the desktop-Linux weave engine. Every other platform of a v13 + * runtime accepts and ignores it (the output stays woven), as a pre-v13 runtime + * does with any unknown chained struct. + */ +typedef struct XrWeaveSubmitMonoIn2DDXR { + XrStructureType type; //!< XR_TYPE_WEAVE_SUBMIT_MONO_IN_2D_DXR + const void* XR_MAY_ALIAS next; + XrBool32 monoIn2D; //!< XR_TRUE: one view flat, no weave, while hardware is 2D +} XrWeaveSubmitMonoIn2DDXR; + typedef XrResult (XRAPI_PTR *PFN_xrWeaveBindWindowDXR)( XrSession session, void* windowHandle); diff --git a/src/xrt/auxiliary/util/u_weave_span2d.h b/src/xrt/auxiliary/util/u_weave_span2d.h index 9d3536cda4..bee14f1393 100644 --- a/src/xrt/auxiliary/util/u_weave_span2d.h +++ b/src/xrt/auxiliary/util/u_weave_span2d.h @@ -96,6 +96,44 @@ u_wl_offpanel_bands_to_output(const struct u_wl_rect_px *bands, return n; } +/*! + * Which view of the atlas the weave engine paints FLAT — the single source of + * every flat path on the desktop-Linux weave engine: the off-panel bands + * (#1654), the flat regions of a non-batch submit (v8), and mono in hardware 2D + * (XR_DXR_weave v13), so all three are in register with each other. + * + * - 1 or 2 views: view 0. For a stereo pair that is the LEFT view (a batch + * rect's left half). Deterministic on purpose: both eyes sit about equally + * far from the display axis, so a nearest-axis rule would flip between the + * two as the head moves and the flat image would jump by the disparity. + * - N > 2 views with one eye offset per view (@p eye_x, metres, display-plane + * x, 0 = the display's centre axis): the view whose eye is NEAREST THE + * DISPLAY AXIS (smallest |x|; a tie goes to the lower index). + * - N > 2 without a matching offset per view: the centre index, (N - 1) / 2. + * + * @return a view index in [0, @p view_count), or 0 when @p view_count is 0. + */ +static inline uint32_t +u_weave_flat_view_index(uint32_t view_count, const float *eye_x, uint32_t eye_count) +{ + if (view_count <= 2) { + return 0; + } + if (eye_x == NULL || eye_count != view_count) { + return (view_count - 1) / 2; + } + uint32_t best = 0; + float best_abs = eye_x[0] < 0.0f ? -eye_x[0] : eye_x[0]; + for (uint32_t i = 1; i < view_count; i++) { + const float a = eye_x[i] < 0.0f ? -eye_x[i] : eye_x[i]; + if (a < best_abs) { + best_abs = a; + best = i; + } + } + return best; +} + #ifdef __cplusplus } #endif diff --git a/src/xrt/compositor/multi/comp_multi_private.h b/src/xrt/compositor/multi/comp_multi_private.h index c0302a2df3..2a9ed6b1d7 100644 --- a/src/xrt/compositor/multi/comp_multi_private.h +++ b/src/xrt/compositor/multi/comp_multi_private.h @@ -924,6 +924,17 @@ struct multi_compositor */ bool hw_2d_confirmed; + /*! + * Spec v13 (XrWeaveSubmitMonoIn2DDXR): the caller asked, on the submit + * being processed, for one flat view instead of the weave while + * hw_2d_confirmed. Set by comp_multi_weave_linux_set_mono_in_2d right + * before each submit (per submit, never sticky); @c mono_active is the + * state the last submit actually rendered, so the WARN fires on an edge + * only. + */ + bool mono_in_2d; + bool mono_active; + //! @name Flat regions (spec v8, browser#88, on desktop Linux) //! The sticky screen-space latch (xrWeaveSetScreenFlatRegionsDXR), //! absolute screen device px; a SET, count 0 clears. Unioned with @@ -1526,6 +1537,19 @@ comp_multi_weave_set_screen_flat_regions(struct xrt_compositor *xc, */ bool comp_multi_weave_linux_request_display_mode(struct multi_compositor *mc, bool enable_3d); + +/*! + * Spec v13 (XrWeaveSubmitMonoIn2DDXR): what the NEXT submit asked for. The IPC + * handler calls this with the submit's own flag immediately before handing the + * submit to the engine, so the value is per submit (a submit without the chain + * sets false). While it is set AND this session's hardware state is 2D (as last + * confirmed by mc->weave.dp — hw_2d_confirmed), the frame skips the weave and + * paints the whole output with one view of the submitted content (stereo: the + * left view; N > 2: nearest the display axis, u_weave_flat_view_index), the + * source the off-panel bands use. In 3D it has no effect. + */ +void +comp_multi_weave_linux_set_mono_in_2d(struct xrt_compositor *xc, bool mono_in_2d); /*! @} */ #endif // XRT_OS_LINUX_DESKTOP diff --git a/src/xrt/compositor/multi/comp_multi_weave_linux.c b/src/xrt/compositor/multi/comp_multi_weave_linux.c index fc2775d7d8..b31f8beffc 100644 --- a/src/xrt/compositor/multi/comp_multi_weave_linux.c +++ b/src/xrt/compositor/multi/comp_multi_weave_linux.c @@ -1533,7 +1533,7 @@ weave_flat_2d_enabled(void) * would stay woven — a browser popup over inline 3D shown through the * interlace, split into two "eyes" of page pixels. Instead the runtime paints * those rects flat, from the same source the pixels came from (the input page - * for a batch submit; the centre view otherwise), exactly as the off-panel + * for a batch submit; the flat view otherwise), exactly as the off-panel * bands are painted. The window geometry is needed for the sticky latch (it * is screen-absolute) and for the v6 scale; without it only the per-submit * rects apply, 1:1 on the output. @@ -2099,9 +2099,33 @@ weave_run_frame(struct vk_bundle *vk, // and then the weave is skipped — nothing of it would be seen. struct u_wl_rect_px offpanel[4]; bool skip_weave = false; - const uint32_t offpanel_count = weave_offpanel_bands(mc, nview, offpanel, &skip_weave); + uint32_t offpanel_count = weave_offpanel_bands(mc, nview, offpanel, &skip_weave); bool blend_begun = false; + // Spec v13 (XrWeaveSubmitMonoIn2DDXR): mono while the hardware is 2D. The + // whole output becomes ONE flat band — exactly the "entirely off the panel" + // case above: no weave, the flat view (below: a stereo pair's LEFT view — + // each batch rect's left half unsqueezed; N > 2 views, the one nearest the + // display axis) painted in register with the window at full output + // resolution. Gated on the CONFIRMED hardware state, not on the caller's + // own request, so the content goes flat only while the lens is really + // dropped (a DP that rejected the 2D request keeps the weave); it ends on + // the first submit after the confirmed state is 3D again. + const bool mono = mc->weave.mono_in_2d && mc->weave.hw_2d_confirmed; + if (mono != mc->weave.mono_active) { + mc->weave.mono_active = mono; + U_LOG_W("weave(v13): %s", + mono ? "MONO — hardware 2D and the caller asked for one view: the flat view (stereo: the left " + "view) is painted over the whole output, no weave" + : (mc->weave.mono_in_2d ? "weave resumed (the hardware is 3D again)" + : "weave resumed (the caller no longer asks for mono)")); + } + if (mono) { + offpanel[0] = (struct u_wl_rect_px){0, 0, (int32_t)mc->weave.out_w, (int32_t)mc->weave.out_h}; + offpanel_count = 1; + skip_weave = true; + } + // SELF-SUBMITTING DP ORDERING (Android #1036's one-frame trail fix): a // DP that submits its own batch during process_atlas would otherwise // execute BEFORE this frame's blits (still unsubmitted in cmd). Flush @@ -2168,17 +2192,37 @@ weave_run_frame(struct vk_bundle *vk, 0, 0, 0, 0); } - // Off-panel bands -> flat 2D, from the centre tile of the atlas the DP - // consumed (for a stereo pair, the left eye). After the weave and BEFORE + // THE flat view, shared by every flat path below (off-panel bands, mono in + // 2D, non-batch flat regions) so they are in register with each other: + // view 0 for 1-2 views (a stereo pair's LEFT view), else the view whose eye + // is nearest the display axis (u_weave_flat_view_index). Eye offsets are + // read only on an N > 2 grid (one DP query, the same one the reply makes). + uint32_t flat_view = 0; + { + const uint32_t tiles = grid_cols * grid_rows; + if (tiles > 2) { + struct xrt_eye_positions fe = {0}; + float fx[ARRAY_SIZE(fe.eyes)]; + uint32_t fn = 0; + if (xrt_display_processor_get_predicted_eye_positions(mc->weave.dp, &fe) && fe.valid) { + fn = fe.count < ARRAY_SIZE(fe.eyes) ? fe.count : (uint32_t)ARRAY_SIZE(fe.eyes); + for (uint32_t i = 0; i < fn; i++) { + fx[i] = fe.eyes[i].x; + } + } + flat_view = u_weave_flat_view_index(tiles, fn > 0 ? fx : NULL, fn); + } + } + + // Off-panel bands -> flat 2D, from the flat view of the atlas the DP + // consumed (for a stereo pair, the left view). After the weave and BEFORE // the v4 overlay, as in-process (#1654): 2D overlays are composited over // the bands too, so they stay readable on both sides of the seam. Here, // while the input is still in the layout the weave sampled it in (the // zero-copy v6 source IS the input). if (offpanel_count > 0) { - const uint32_t tiles = grid_cols * grid_rows; - const uint32_t view_index = tiles > 0 ? (tiles - 1) / 2 : 0; weave_paint_offpanel(vk, mc, cmd, offpanel, offpanel_count, dp_src_view, dp_src_w, dp_src_h, - (view_index % grid_cols) * atlas_view_w, (view_index / grid_cols) * atlas_view_h, + (flat_view % grid_cols) * atlas_view_w, (flat_view / grid_cols) * atlas_view_h, &blend_begun); } @@ -2187,7 +2231,7 @@ weave_run_frame(struct vk_bundle *vk, // overlay. Source: for a batch submit the input page itself, 1:1 — the // flat region IS page content (a popup / menu / DOM over 3D), and the // input is window-sized in register with the output; otherwise (v6 / - // legacy, where the input holds views, not a page) the centre view the + // legacy, where the input holds views, not a page) the flat view the // off-panel bands use. { struct u_wl_rect_px flat[WEAVE_FLAT_MAX_RECTS]; @@ -2208,11 +2252,9 @@ weave_run_frame(struct vk_bundle *vk, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); } else { - const uint32_t tiles = grid_cols * grid_rows; - const uint32_t view_index = tiles > 0 ? (tiles - 1) / 2 : 0; weave_paint_offpanel(vk, mc, cmd, flat, flat_count, dp_src_view, dp_src_w, dp_src_h, - (view_index % grid_cols) * atlas_view_w, - (view_index / grid_cols) * atlas_view_h, &blend_begun); + (flat_view % grid_cols) * atlas_view_w, + (flat_view / grid_cols) * atlas_view_h, &blend_begun); } } } @@ -2830,6 +2872,19 @@ comp_multi_weave_linux_request_display_mode(struct multi_compositor *mc, bool en return ok; } +void +comp_multi_weave_linux_set_mono_in_2d(struct xrt_compositor *xc, bool mono_in_2d) +{ + struct multi_compositor *mc = multi_compositor(xc); + if (mc == NULL || mc->msc == NULL) { + return; + } + weave_ensure_mutex(mc); + os_mutex_lock(&mc->weave.mutex); + mc->weave.mono_in_2d = mono_in_2d; + os_mutex_unlock(&mc->weave.mutex); +} + void comp_multi_weave_fini(struct multi_compositor *mc) { diff --git a/src/xrt/ipc/client/ipc_client.h b/src/xrt/ipc/client/ipc_client.h index cf08afcf3e..ba85c9002b 100644 --- a/src/xrt/ipc/client/ipc_client.h +++ b/src/xrt/ipc/client/ipc_client.h @@ -238,6 +238,8 @@ comp_ipc_client_compositor_get_transparent_output_fence(struct xrt_compositor *x * must be physically FLAT; the service publishes union(rects) minus * union(flat_rects) as the per-region hardware wish. Advisory and hardware-only * — 0 is byte-for-byte pre-v8. + * @p mono_in_2d (spec v13): while the session's hardware state is 2D, the + * desktop-Linux engine presents one view (stereo: the left) flat instead of weaving. * @c weave_set_screen_flat_regions latches the STICKY screen-space flat regions * (spec v8, absolute physical screen px, held until the next call). * @c weave_get_output / @c weave_get_fence fetch the persistent server-allocated @@ -264,6 +266,7 @@ comp_ipc_client_compositor_weave_submit(struct xrt_compositor *xc, const struct xrt_weave_atlas_layout *layout, uint32_t flat_rect_count, const struct xrt_rect *flat_rects, + bool mono_in_2d, bool *out_have_output, uint32_t *out_width, uint32_t *out_height, diff --git a/src/xrt/ipc/client/ipc_client_compositor.c b/src/xrt/ipc/client/ipc_client_compositor.c index 52af8d9a76..fc5e26094d 100644 --- a/src/xrt/ipc/client/ipc_client_compositor.c +++ b/src/xrt/ipc/client/ipc_client_compositor.c @@ -567,7 +567,8 @@ weave_pack_submit_args(struct ipc_arg_weave_submit *args, bool weave_frame_first, const struct xrt_weave_atlas_layout *layout, uint32_t flat_rect_count, - const struct xrt_rect *flat_rects) + const struct xrt_rect *flat_rects, + bool mono_in_2d) { U_ZERO(args); args->rect_x = rect_x; @@ -621,6 +622,10 @@ weave_pack_submit_args(struct ipc_arg_weave_submit *args, args->flat_rects[i].w = (uint32_t)flat_rects[i].extent.w; args->flat_rects[i].h = (uint32_t)flat_rects[i].extent.h; } + + // v13: one flat view instead of the weave while the hardware is 2D. 0 (a + // pre-v13 caller) is byte-for-byte the old behaviour. + args->mono_in_2d = mono_in_2d ? 1u : 0u; } xrt_result_t @@ -641,6 +646,7 @@ comp_ipc_client_compositor_weave_submit(struct xrt_compositor *xc, const struct xrt_weave_atlas_layout *layout, uint32_t flat_rect_count, const struct xrt_rect *flat_rects, + bool mono_in_2d, bool *out_have_output, uint32_t *out_width, uint32_t *out_height, @@ -674,7 +680,8 @@ comp_ipc_client_compositor_weave_submit(struct xrt_compositor *xc, struct ipc_arg_weave_submit args = {0}; const bool have_overlay = (overlay_handle != XRT_GRAPHICS_BUFFER_HANDLE_INVALID); weave_pack_submit_args(&args, rect_x, rect_y, rect_w, rect_h, rect_count, rects, have_overlay, - overlay_rect_count, overlay_rects, weave_frame_first, layout, flat_rect_count, flat_rects); + overlay_rect_count, overlay_rects, weave_frame_first, layout, flat_rect_count, + flat_rects, mono_in_2d); xrt_graphics_buffer_handle_t handles[2] = {in_handle, overlay_handle}; uint32_t handle_count = have_overlay ? 2u : 1u; @@ -852,6 +859,7 @@ comp_ipc_client_compositor_weave_submit_dmabuf(struct xrt_compositor *xc, const struct xrt_weave_atlas_layout *layout, uint32_t flat_rect_count, const struct xrt_rect *flat_rects, + bool mono_in_2d, int *out_release_fence_fd, bool *out_have_output, uint32_t *out_width, @@ -893,7 +901,8 @@ comp_ipc_client_compositor_weave_submit_dmabuf(struct xrt_compositor *xc, struct ipc_arg_weave_submit args; weave_pack_submit_args(&args, rect_x, rect_y, rect_w, rect_h, rect_count, rects, overlay != NULL, - overlay_rect_count, NULL, weave_frame_first, layout, flat_rect_count, flat_rects); + overlay_rect_count, NULL, weave_frame_first, layout, flat_rect_count, flat_rects, + mono_in_2d); struct ipc_arg_weave_dmabuf dmabuf = {0}; weave_pack_dmabuf(&dmabuf.input, in); diff --git a/src/xrt/ipc/server/ipc_server_handler.c b/src/xrt/ipc/server/ipc_server_handler.c index cbc8de2673..f8e1ccec35 100644 --- a/src/xrt/ipc/server/ipc_server_handler.c +++ b/src/xrt/ipc/server/ipc_server_handler.c @@ -6645,6 +6645,10 @@ ipc_handle_weave_submit(volatile struct ipc_client_state *ics, uint32_t w = 0, h = 0; uint64_t fv = 0; struct xrt_eye_positions eyes = {0}; +#ifdef XRT_OS_LINUX_DESKTOP + // v13: per submit — a submit without XrWeaveSubmitMonoIn2DDXR clears it. + comp_multi_weave_linux_set_mono_in_2d(ics->xc, args->mono_in_2d != 0); +#endif bool ok = comp_multi_weave_submit( // ics->xc, handles[0], // args->rect_x, args->rect_y, args->rect_w, args->rect_h, // @@ -6959,6 +6963,8 @@ ipc_handle_weave_submit_dmabuf(volatile struct ipc_client_state *ics, uint64_t fv = 0; struct xrt_eye_positions eyes = {0}; struct xrt_weave_woven_origin woven = {0}; + // v13: per submit — a submit without XrWeaveSubmitMonoIn2DDXR clears it. + comp_multi_weave_linux_set_mono_in_2d(ics->xc, args->mono_in_2d != 0); bool ok = comp_multi_weave_submit_dmabuf( // ics->xc, &in_desc, // args->have_overlay ? &overlay_desc : NULL, // diff --git a/src/xrt/ipc/shared/ipc_protocol.h b/src/xrt/ipc/shared/ipc_protocol.h index f8d5a985cc..69e7b6c300 100644 --- a/src/xrt/ipc/shared/ipc_protocol.h +++ b/src/xrt/ipc/shared/ipc_protocol.h @@ -893,6 +893,12 @@ struct ipc_arg_weave_submit //! xrCreateInstance, so a mismatched pair never connects in the first place. uint32_t flat_rect_count; //!< 0..IPC_WEAVE_SUBMIT_FLAT_RECTS_MAX (0 = no flat regions) struct ipc_weave_rect flat_rects[IPC_WEAVE_SUBMIT_FLAT_RECTS_MAX]; //!< first flat_rect_count valid + + //! XR_DXR_weave v13 (XrWeaveSubmitMonoIn2DDXR): 1 = while this session's + //! hardware state is 2D, present one view flat instead of the weave + //! (desktop-Linux engine; ignored elsewhere). Appended, same accounting as + //! v8: 824 + 4 = 828 B; the dma-buf call is 4 + 828 + 136 = 968 of 1024. + uint32_t mono_in_2d; }; /*! diff --git a/src/xrt/state_trackers/oxr/oxr_weave.c b/src/xrt/state_trackers/oxr/oxr_weave.c index 50ecb43a37..68f6553e6a 100644 --- a/src/xrt/state_trackers/oxr/oxr_weave.c +++ b/src/xrt/state_trackers/oxr/oxr_weave.c @@ -149,6 +149,7 @@ comp_ipc_client_compositor_weave_submit(struct xrt_compositor *xc, const struct xrt_weave_atlas_layout *layout, uint32_t flat_rect_count, const struct xrt_rect *flat_rects, + bool mono_in_2d, bool *out_have_output, uint32_t *out_width, uint32_t *out_height, @@ -190,6 +191,7 @@ comp_ipc_client_compositor_weave_submit_dmabuf(struct xrt_compositor *xc, const struct xrt_weave_atlas_layout *layout, uint32_t flat_rect_count, const struct xrt_rect *flat_rects, + bool mono_in_2d, int *out_release_fence_fd, bool *out_have_output, uint32_t *out_width, @@ -565,6 +567,14 @@ oxr_xrWeaveSubmitDXR(XrSession session, const XrWeaveSubmitInfoDXR *submitInfo, } } + // Spec v13: a chained XrWeaveSubmitMonoIn2DDXR asks for one flat view + // instead of the weave while this session's hardware state is 2D. The + // desktop-Linux engine honours it; every other service ignores it. Per + // submit, no latch — an absent chain is byte-for-byte pre-v13. + const XrWeaveSubmitMonoIn2DDXR *mono = + OXR_GET_INPUT_FROM_CHAIN(submitInfo, XR_TYPE_WEAVE_SUBMIT_MONO_IN_2D_DXR, XrWeaveSubmitMonoIn2DDXR); + const bool mono_in_2d = mono != NULL && mono->monoIn2D == XR_TRUE; + // Spec v6 (#774): a chained XrWeaveSubmitLayoutDXR declares that the input // is a worst-case-sized N-view atlas (tiles packed contiguously from the // top-left at contentViewWidth/Height) instead of per-rect squeezed SBS. @@ -794,8 +804,8 @@ oxr_xrWeaveSubmitDXR(XrSession session, const XrWeaveSubmitInfoDXR *submitInfo, submitInfo->rect.offset.x, submitInfo->rect.offset.y, (uint32_t)submitInfo->rect.extent.width, (uint32_t)submitInfo->rect.extent.height, rect_count, rect_count > 0 ? rects : NULL, overlay_rect_count, submitInfo->firstChunk == XR_TRUE, layout.view_count > 0 ? &layout : NULL, - flat_rect_count, flat_rect_count > 0 ? flat_rects : NULL, &release_fd, &have_out, &w, &h, - &fence_value, &eyes, &woven); + flat_rect_count, flat_rect_count > 0 ? flat_rects : NULL, mono_in_2d, &release_fd, &have_out, &w, + &h, &fence_value, &eyes, &woven); } else #endif { @@ -806,7 +816,7 @@ oxr_xrWeaveSubmitDXR(XrSession session, const XrWeaveSubmitInfoDXR *submitInfo, rect_count > 0 ? rects : NULL, overlay_handle, overlay_is_dxgi, overlay_rect_count, overlay_rect_count > 0 ? overlay_rects : NULL, submitInfo->firstChunk == XR_TRUE, layout.view_count > 0 ? &layout : NULL, flat_rect_count, flat_rect_count > 0 ? flat_rects : NULL, - &have_out, &w, &h, &fence_value, &eyes); + mono_in_2d, &have_out, &w, &h, &fence_value, &eyes); } if (xret == XRT_ERROR_FEATURE_NOT_SUPPORTED) { // The service has no weave engine for this platform (desktop Linux diff --git a/test_apps/weave/weave_present_vk_linux/main.cpp b/test_apps/weave/weave_present_vk_linux/main.cpp index c25f18ff64..a633a4eefb 100644 --- a/test_apps/weave/weave_present_vk_linux/main.cpp +++ b/test_apps/weave/weave_present_vk_linux/main.cpp @@ -90,6 +90,19 @@ * at 50 -> events 2D,3D,2D. Pair it with a service * started with SIM_DISPLAY_FAKE_LENS=1 so the weave * engine's DP records + logs each request. + * --mono-in-2d chain XrWeaveSubmitMonoIn2DDXR (spec v13) on every + * submit: while the hardware is 2D the service presents + * the centre view flat instead of the weave. + * --hold-2d-at=N / --release-3d-at=M (headless) request hardware 2D at + * frame N (and 3D at frame M) — a browser's menu hold. + * --expect-mono (headless) require woven == the SBS input's LEFT view, + * unsqueezed to full width (one view, no weave). + * --test-hold-flicker (headless, implies --mono-in-2d) open/close the 2D + * hold FAST over frames 10..49 — a 1-frame hold, a + * 2-frame hold, and 2D+3D inside one frame, repeated — and + * read back EVERY frame of that window: each must be mono + * exactly when the last request before its submit was 2D + * (no woven frame while held, no stale mono after release). * --lattice-selftest one Wayland drag-lattice table build per output scale * (100 %, 200 %, 150 %) through the helper's own table * code (dxr_wl_lattice::probe_via_grid) and the SAME @@ -233,6 +246,11 @@ struct Options bool lattice_selftest = false; //!< --lattice-selftest bool test_display_mode = false; //!< --test-display-mode (headless) bool test_display_deferred = false; //!< --test-display-mode=deferred + bool mono_in_2d = false; //!< --mono-in-2d (spec v13) + int hold_2d_frame = -1; //!< --hold-2d-at=N (headless) + int release_3d_frame = -1; //!< --release-3d-at=M (headless) + bool expect_mono = false; //!< --expect-mono (headless) + bool test_hold_flicker = false; //!< --test-hold-flicker (headless) }; static void @@ -265,6 +283,16 @@ usage(const char *argv0) "like a window resize\n" " --test-display-mode[=stale|deferred] (headless) request hardware " "2D/3D sequences and require the matching events\n" + " --mono-in-2d chain XrWeaveSubmitMonoIn2DDXR " + "(v13): one flat view while hardware 2D\n" + " --hold-2d-at=N (headless) request hardware 2D at " + "frame N\n" + " --release-3d-at=M (headless) request hardware 3D at " + "frame M\n" + " --expect-mono (headless) require woven == the " + "left view, unsqueezed\n" + " --test-hold-flicker (headless) fast 2D hold open/close, " + "every frame checked mono/woven\n" " --lattice-selftest build one drag-lattice table per " "scale via the grid provider (exit 1 = FAIL)\n" " --dump-dir=DIR where PNGs go (default $TMPDIR, else " @@ -325,6 +353,17 @@ parse_args(int argc, char **argv, Options &o) } else if (strcmp(a, "--test-display-mode=deferred") == 0) { o.test_display_mode = true; o.test_display_deferred = true; + } else if (strcmp(a, "--mono-in-2d") == 0) { + o.mono_in_2d = true; + } else if (strncmp(a, "--hold-2d-at=", 13) == 0) { + o.hold_2d_frame = atoi(a + 13); + } else if (strncmp(a, "--release-3d-at=", 16) == 0) { + o.release_3d_frame = atoi(a + 16); + } else if (strcmp(a, "--expect-mono") == 0) { + o.expect_mono = true; + } else if (strcmp(a, "--test-hold-flicker") == 0) { + o.test_hold_flicker = true; + o.mono_in_2d = true; } else if (strncmp(a, "--dump-dir=", 11) == 0) { o.dump_dir = a + 11; } else if (strcmp(a, "--help") == 0 || strcmp(a, "-h") == 0) { @@ -477,6 +516,13 @@ struct App int64_t hud_t = 0; bool dump_requested = false; int submit_failures = 0; + //! --test-hold-flicker: the per-frame readback in flight, what that frame + //! must be, and the tallies. + bool flicker_pending = false; + bool flicker_expect_mono = false; + int flicker_frame = -1; + bool hold_2d_now = false; //!< the last hardware request this run made was 2D + int flicker_checked = 0, flicker_mono = 0, flicker_woven = 0, flicker_bad = 0; // ---- Wayland drop snap (see wl_drop_snap) struct @@ -848,7 +894,9 @@ init_session() xrGetInstanceProcAddr(g.instance, "xrWeaveBindWindow2DXR", (PFN_xrVoidFunction *)&g.pfn_bind2); xrGetInstanceProcAddr(g.instance, "xrWeaveSubmitDXR", (PFN_xrVoidFunction *)&g.pfn_submit); xrGetInstanceProcAddr(g.instance, "xrWeaveSnapWindowRectDXR", (PFN_xrVoidFunction *)&g.pfn_snap); - if (g.opt.test_display_mode && g.has_display_info) { + if ((g.opt.test_display_mode || g.opt.hold_2d_frame >= 0 || g.opt.release_3d_frame >= 0 || + g.opt.test_hold_flicker) && + g.has_display_info) { xrGetInstanceProcAddr(g.instance, "xrRequestDisplayModeDXR", (PFN_xrVoidFunction *)&g.pfn_request_display_mode); } @@ -1928,8 +1976,17 @@ weave_submit(int k, bool with_overlay, int acq_fd, int *release_fd) } else { batch.next = &in_desc; } + // v13: one flat view while the hardware is 2D. Chained first; a runtime + // older than v13 skips it. + XrWeaveSubmitMonoIn2DDXR mono = {XR_TYPE_WEAVE_SUBMIT_MONO_IN_2D_DXR}; + mono.monoIn2D = XR_TRUE; XrWeaveSubmitInfoDXR submit = {XR_TYPE_WEAVE_SUBMIT_INFO_DXR}; - submit.next = &batch; + if (g.opt.mono_in_2d) { + mono.next = &batch; + submit.next = &mono; + } else { + submit.next = &batch; + } submit.firstChunk = XR_TRUE; XrWeaveOutputOriginDXR out_origin = {XR_TYPE_WEAVE_OUTPUT_ORIGIN_DXR}; // v12, zeroed @@ -2170,6 +2227,7 @@ struct Verdict double diff_vs_sbs = 0.0; //!< mean |woven - sbs| per channel: did the weave //!< transform anything? double anaglyph_err = 0.0; //!< mean |woven - anaglyph(sbs)| per channel, outside the HUD + double mono_err = 0.0; //!< mean |woven - left view of sbs, unsqueezed| per channel, outside the HUD bool hud_ok = true; //!< a HUD-box pixel reads the HUD colour (2D composited //!< over the weave) double presented_err = -1.0; //!< (headless) mean |presented - (sbs mode ? sbs : woven)| @@ -2189,7 +2247,7 @@ analyze(const uint8_t *woven, const uint8_t *sbs, uint32_t w, uint32_t h) { Verdict v; const uint32_t ew = w / 2; - double luma = 0.0, diff = 0.0, aerr = 0.0; + double luma = 0.0, diff = 0.0, aerr = 0.0, merr = 0.0; uint64_t n = 0, nb = 0, na = 0; const bool hud = g.opt.hud && g.hud_w > 0; for (uint32_t y = 1; y + 1 < h; y += 2) { @@ -2209,7 +2267,10 @@ analyze(const uint8_t *woven, const uint8_t *sbs, uint32_t w, uint32_t h) // sim_display anaglyph: red from the left tile, green+blue from // the right, each sampled (bilinear) at u/2 — take the best of the // two nearest source columns. - double best = 1e9; + // Mono (spec v13): the left view alone, unsqueezed to full width + // (a LINEAR stretch of the left half) — same best-of-neighbours + // tolerance. + double best = 1e9, best_mono = 1e9; for (int dx = -1; dx <= 1; dx++) { const int lx = std::clamp((int)(x / 2) + dx, 0, (int)ew - 1); int lr, lg, lb, rr, rg, rb; @@ -2217,8 +2278,11 @@ analyze(const uint8_t *woven, const uint8_t *sbs, uint32_t w, uint32_t h) px(sbs, w, (int)ew + lx, (int)y, &rr, &rg, &rb); const double e = (std::abs(wr - lr) + std::abs(wg - rg) + std::abs(wb - rb)) / 3.0; best = std::min(best, e); + const double em = (std::abs(wr - lr) + std::abs(wg - lg) + std::abs(wb - lb)) / 3.0; + best_mono = std::min(best_mono, em); } aerr += best; + merr += best_mono; na++; } } @@ -2226,6 +2290,7 @@ analyze(const uint8_t *woven, const uint8_t *sbs, uint32_t w, uint32_t h) v.nonblack = n ? (double)nb / (double)n : 0.0; v.diff_vs_sbs = n ? diff / (double)n : 0.0; v.anaglyph_err = na ? aerr / (double)na : 0.0; + v.mono_err = na ? merr / (double)na : 0.0; if (hud) { // A pixel inside the HUD box, below the text lines' left margin. int r, gg, b; @@ -2274,9 +2339,9 @@ dump_and_analyze(const char *tag) } LOGI( "%s: woven mean luma %.1f, non-black %.1f%%, mean |woven - sbs| %.1f, " - "mean |woven - anaglyph(sbs)| %.1f, " + "mean |woven - anaglyph(sbs)| %.1f, mean |woven - left view| %.1f, " "HUD %s", - tag, v.woven_luma, v.nonblack * 100.0, v.diff_vs_sbs, v.anaglyph_err, + tag, v.woven_luma, v.nonblack * 100.0, v.diff_vs_sbs, v.anaglyph_err, v.mono_err, !g.opt.hud ? "off" : (v.hud_ok ? "composited" : "MISSING")); return v; } @@ -2340,6 +2405,23 @@ run() if (!wait_frame_fences()) { return 1; } + // --test-hold-flicker: classify the previous frame's readback. + if (g.flicker_pending) { + g.flicker_pending = false; + const Verdict fv = analyze(g.rb_woven.map, g.rb_sbs.map, g.w, g.h); + const bool is_mono = fv.mono_err + 2.0 < fv.anaglyph_err; + const bool is_woven = fv.anaglyph_err + 2.0 < fv.mono_err; + g.flicker_checked++; + g.flicker_mono += is_mono ? 1 : 0; + g.flicker_woven += is_woven ? 1 : 0; + const bool ok = g.flicker_expect_mono ? is_mono : is_woven; + if (!ok) { + g.flicker_bad++; + LOGE("hold flicker: frame %d expected %s, got %s (|w-anaglyph| %.1f, |w-left| %.1f)", + g.flicker_frame, g.flicker_expect_mono ? "MONO (held 2D)" : "WOVEN (released)", + is_mono ? "mono" : (is_woven ? "woven" : "neither"), fv.anaglyph_err, fv.mono_err); + } + } // A frame's dump readback is complete once its fences are waited. if (g.dump_requested) { g.dump_requested = false; @@ -2396,6 +2478,50 @@ run() } } + if (g.headless && g.pfn_request_display_mode != nullptr && + (f == g.opt.hold_2d_frame || f == g.opt.release_3d_frame)) { + // A browser's menu hold: the lens off at N, back on at M. + const bool want_3d = (f == g.opt.release_3d_frame); + const XrResult r = g.pfn_request_display_mode(g.session, want_3d ? XR_DISPLAY_MODE_3D_DXR + : XR_DISPLAY_MODE_2D_DXR); + LOGI("frame %d: hold %s -> xrRequestDisplayModeDXR(%s) -> %d", f, want_3d ? "released" : "taken", + want_3d ? "3D" : "2D", (int)r); + if (XR_FAILED(r)) { + g.dm_order_bad = true; + } + g.hold_2d_now = !want_3d; + } + if (g.headless && g.opt.test_hold_flicker && g.pfn_request_display_mode != nullptr && f >= 10 && + f < 50) { + // Six-frame cycle: a 1-frame hold (2D at +0, 3D at +1), a 2-frame + // hold (2D at +2, 3D at +4), and a hold opened and closed inside + // ONE frame (+5: 2D then 3D before the submit -> woven). Every + // request is answered inline (the DP confirms on the IPC call), so + // the submit right after it must already show the new state. + std::vector reqs; // true = 3D + switch ((f - 10) % 6) { + case 0: reqs = {false}; break; + case 1: reqs = {true}; break; + case 2: reqs = {false}; break; + case 4: reqs = {true}; break; + case 5: reqs = {false, true}; break; + default: break; + } + for (bool want_3d : reqs) { + const XrResult r = g.pfn_request_display_mode(g.session, want_3d ? XR_DISPLAY_MODE_3D_DXR + : XR_DISPLAY_MODE_2D_DXR); + if (XR_FAILED(r)) { + g.dm_order_bad = true; + } + g.hold_2d_now = !want_3d; + } + if (f == 49 && g.hold_2d_now) { + // End released, so the final dump is woven. + g.pfn_request_display_mode(g.session, XR_DISPLAY_MODE_3D_DXR); + g.hold_2d_now = false; + } + } + if (!g.headless) { g.window.pump_events( [&](const DxrWindowEvent &e) { @@ -2492,10 +2618,16 @@ run() } else if (g.opt.anaglyph_check && dump_trigger_present()) { dump = true; } - if (!present(release_fd, dump)) { + const bool flicker_dump = g.headless && g.opt.test_hold_flicker && f >= 10 && f < 50; + if (!present(release_fd, dump || flicker_dump)) { return 1; } g.dump_requested = dump; + if (flicker_dump) { + g.flicker_pending = true; + g.flicker_expect_mono = g.hold_2d_now; + g.flicker_frame = f; + } g.frame++; if (g.headless && f == 0) { @@ -2530,12 +2662,32 @@ run() check(v.presented_err >= 0.0 && v.presented_err < 0.01, g.opt.sbs ? "presented frame == SBS input (--sbs)" : "presented frame == woven output"); check(v.nonblack > 0.5 && v.woven_luma > 10.0, "woven frame is not black"); - check(v.diff_vs_sbs > 5.0, "woven frame differs from the SBS input (weave ran)"); + check(v.diff_vs_sbs > 5.0, "woven frame differs from the SBS input (not the raw SBS)"); if (g.opt.hud) { check(v.hud_ok, "HUD overlay composited over the weave"); } if (g.opt.expect_anaglyph) { check(v.anaglyph_err < 10.0, "woven == red/cyan anaglyph of the SBS input"); + if (g.opt.mono_in_2d) { + // The opt-in is chained: proving the weave ran needs the output to + // be told apart from the mono one, not only matched to anaglyph. + check(v.anaglyph_err + 2.0 < v.mono_err, "woven is the anaglyph, NOT the mono view"); + } + } + if (g.opt.expect_mono) { + check(v.mono_err < 10.0, "v13 mono: woven == the left view, unsqueezed"); + check(v.mono_err + 2.0 < v.anaglyph_err, "v13 mono: woven is NOT the anaglyph (no weave)"); + } + if (g.opt.test_hold_flicker) { + LOGI("hold flicker: %d frames checked, %d mono, %d woven, %d wrong", g.flicker_checked, g.flicker_mono, + g.flicker_woven, g.flicker_bad); + check(g.flicker_checked == 40, "hold flicker: every frame of 10..49 read back"); + check(g.flicker_bad == 0, "hold flicker: no woven frame while held, no stale mono after release"); + check(g.flicker_mono > 0 && g.flicker_woven > 0, "hold flicker: both states seen"); + check(!g.dm_order_bad, "hold flicker: every xrRequestDisplayModeDXR succeeded"); + } + if (g.opt.hold_2d_frame >= 0 || g.opt.release_3d_frame >= 0) { + check(g.pfn_request_display_mode != nullptr && !g.dm_order_bad, "hold: xrRequestDisplayModeDXR resolved + succeeded"); } LOGI( "fd counts (steady state) -> frame %d: app %d -> %d, service(pid %ld) " diff --git a/tests/tests_aux_weave_span2d.cpp b/tests/tests_aux_weave_span2d.cpp index 06a07c0a9e..6ff94ee5aa 100644 --- a/tests/tests_aux_weave_span2d.cpp +++ b/tests/tests_aux_weave_span2d.cpp @@ -82,3 +82,32 @@ TEST_CASE("a window entirely off the panel maps to one whole-output band") REQUIRE(u_wl_offpanel_bands_to_output(win, 0, 1280, 720, 640, 360, true, o, &whole) == 0); REQUIRE_FALSE(whole); } + +TEST_CASE("the flat view: view 0 for stereo, nearest the display axis for N > 2") +{ + // Stereo: always view 0 (the left view), whatever the eye offsets — the + // choice must not flip as the head moves. + const float stereo_right_nearer[2] = {-0.040f, 0.010f}; + REQUIRE(u_weave_flat_view_index(2, stereo_right_nearer, 2) == 0); + REQUIRE(u_weave_flat_view_index(2, NULL, 0) == 0); + REQUIRE(u_weave_flat_view_index(1, NULL, 0) == 0); + REQUIRE(u_weave_flat_view_index(0, NULL, 0) == 0); + + // 2x2 quad: view order is NOT spatial order — the nearest-axis eye wins. + const float quad[4] = {-0.096f, 0.032f, -0.032f, 0.096f}; + REQUIRE(u_weave_flat_view_index(4, quad, 4) == 1); // |0.032| first at index 1 (tie -> lower) + const float quad_shifted[4] = {-0.120f, 0.008f, -0.056f, 0.072f}; // head moved right + REQUIRE(u_weave_flat_view_index(4, quad_shifted, 4) == 1); + const float quad_left[4] = {-0.070f, 0.058f, -0.006f, 0.122f}; // head moved left + REQUIRE(u_weave_flat_view_index(4, quad_left, 4) == 2); + + // 8 views spread left->right, head off to the right: not the centre index. + const float eight[8] = {-0.10f, -0.07f, -0.04f, -0.01f, 0.02f, 0.05f, 0.08f, 0.11f}; + REQUIRE(u_weave_flat_view_index(8, eight, 8) == 3); + + // N > 2 without one offset per view: the centre index. + REQUIRE(u_weave_flat_view_index(4, NULL, 0) == 1); + REQUIRE(u_weave_flat_view_index(4, quad, 2) == 1); + REQUIRE(u_weave_flat_view_index(8, NULL, 0) == 3); + REQUIRE(u_weave_flat_view_index(3, NULL, 0) == 1); +}