From 8bc32aafec8fb860f5f72744cf2eb8587a820ec6 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 2 Oct 2026 10:33:19 -0700 Subject: [PATCH 1/2] =?UTF-8?q?feat(weave):=20v13=20XrWeaveSubmitMonoIn2DD?= =?UTF-8?q?XR=20=E2=80=94=20one=20flat=20view=20while=20the=20panel=20is?= =?UTF-8?q?=20held=202D=20(#1796)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A desktop-Linux present-owner that drops the lens for a reason of its own (the browser holding the panel 2D while a menu is open over inline 3D) got its content woven through no lens: both views interlaced, soft and doubled. xrRequestDisplayModeDXR is hardware-only by contract, and apps rely on that for fade-to-flat transitions, so the default stays. XR_DXR_weave v13 adds a per-submit opt-in. With XrWeaveSubmitMonoIn2DDXR chained and the session's CONFIRMED hardware state 2D (mc->weave.hw_2d_confirmed), the Linux weave engine skips process_atlas and paints the whole output as one off-panel band: the centre view (batch: each rect's left half unsqueezed; v6: the centre tile) at full output resolution, flat regions and the v4 overlay on top as usual. No effect in 3D, no latch, no session events. Windows, macOS and Android accept and ignore it. No vendor plug-in change: the engine paints the band itself, the DP is not called. Wire: ipc_arg_weave_submit::mono_in_2d (appended; the dma-buf call is 968 of 1024 B). The Linux handlers hand it to the engine per submit (comp_multi_weave_linux_set_mono_in_2d). Test: weave_present_vk_linux gains --mono-in-2d, --hold-2d-at=N, --release-3d-at=M and --expect-mono (woven == the left view unsqueezed, and NOT the anaglyph). Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Claude Opus 5.5 --- docs/specs/extensions/XR_DXR_display_info.md | 4 +- docs/specs/extensions/XR_DXR_weave.md | 54 +++++++++++- src/external/openxr_includes/openxr/README.md | 2 +- .../openxr_includes/openxr/XR_DXR_weave.h | 46 +++++++++- src/xrt/compositor/multi/comp_multi_private.h | 23 +++++ .../compositor/multi/comp_multi_weave_linux.c | 37 +++++++- src/xrt/ipc/client/ipc_client.h | 3 + src/xrt/ipc/client/ipc_client_compositor.c | 15 +++- src/xrt/ipc/server/ipc_server_handler.c | 6 ++ src/xrt/ipc/shared/ipc_protocol.h | 6 ++ src/xrt/state_trackers/oxr/oxr_weave.c | 16 +++- .../weave/weave_present_vk_linux/main.cpp | 84 +++++++++++++++++-- 12 files changed, 278 insertions(+), 18 deletions(-) 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..0dd9880d4a 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) | @@ -776,6 +776,53 @@ 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: centre 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 the centre view of the submitted content at full output resolution, in +register with the window: for a batch rect its left half unsqueezed, for a v6 layout the centre +tile. That is the source the off-panel bands (§5d) use, and the path is theirs: the whole output +becomes one band. Flat regions and the v4 overlay are applied on top as usual. + +- 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 +839,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 (the centre view) 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..f09103ddc7 100644 --- a/src/external/openxr_includes/openxr/XR_DXR_weave.h +++ b/src/external/openxr_includes/openxr/XR_DXR_weave.h @@ -198,6 +198,10 @@ * 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 + * the centre 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 +268,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 +300,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 +957,43 @@ 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 the centre view of the submitted + * content at full output resolution, in register with the window: for a batch + * rect its left half unsqueezed, for a v6 layout the centre tile — the source + * the off-panel 2D bands use (§5d). Flat regions and the v4 overlay are applied + * on top as usual. 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: centre view flat, no weave, while hardware is 2D +} XrWeaveSubmitMonoIn2DDXR; + typedef XrResult (XRAPI_PTR *PFN_xrWeaveBindWindowDXR)( XrSession session, void* windowHandle); diff --git a/src/xrt/compositor/multi/comp_multi_private.h b/src/xrt/compositor/multi/comp_multi_private.h index c0302a2df3..1dda13e030 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,18 @@ 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 the centre view of the submitted content, 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..50290959a8 100644 --- a/src/xrt/compositor/multi/comp_multi_weave_linux.c +++ b/src/xrt/compositor/multi/comp_multi_weave_linux.c @@ -2099,9 +2099,31 @@ 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 centre view painted in register with the window + // (batch: each rect's left half unsqueezed; v6: the centre tile) 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). + 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 centre view is painted flat " + "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 @@ -2830,6 +2852,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..440f358cbf 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 the centre view 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..d49e584c08 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 the centre 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..aaa2d7e181 100644 --- a/test_apps/weave/weave_present_vk_linux/main.cpp +++ b/test_apps/weave/weave_present_vk_linux/main.cpp @@ -90,6 +90,13 @@ * 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). * --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 +240,10 @@ 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) }; static void @@ -265,6 +276,14 @@ 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" " --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 +344,14 @@ 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 (strncmp(a, "--dump-dir=", 11) == 0) { o.dump_dir = a + 11; } else if (strcmp(a, "--help") == 0 || strcmp(a, "-h") == 0) { @@ -848,7 +875,8 @@ 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.has_display_info) { xrGetInstanceProcAddr(g.instance, "xrRequestDisplayModeDXR", (PFN_xrVoidFunction *)&g.pfn_request_display_mode); } @@ -1928,8 +1956,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 +2207,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 +2227,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 +2247,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 +2258,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 +2270,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 +2319,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; } @@ -2396,6 +2441,19 @@ 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; + } + } + if (!g.headless) { g.window.pump_events( [&](const DxrWindowEvent &e) { @@ -2530,12 +2588,24 @@ 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.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) " From 638ad70ce25ef49d63e27ba4fb29a100cc887eb0 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 2 Oct 2026 10:41:32 -0700 Subject: [PATCH 2/2] =?UTF-8?q?feat(weave):=20v13=20review=20follow-ups=20?= =?UTF-8?q?=E2=80=94=20one=20flat-view=20rule,=20confirmed-state=20wording?= =?UTF-8?q?,=20fast=20hold=20test=20(#1796)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - VIEW CHOICE. One helper, u_weave_flat_view_index (u_weave_span2d.h), now picks the view for EVERY flat path of the Linux engine: the off-panel bands, non-batch flat regions and v13 mono. All three are therefore in register. - 1-2 views: view 0, the LEFT view (a batch rect's left half), for every batch submit. This is what the bands always used. It is deterministic, because both eyes are about equidistant from the axis, so a nearest-axis rule would flip with the head. - N > 2: the view whose eye is nearest the display axis (smallest |x| of the DP's per-view eye offset; a tie goes to the lower index). - N > 2 with no offsets: the centre index, (N-1)/2. The header and spec no longer say "centre view". Unit-tested in tests_aux_weave_span2d (stereo invariance, 2x2 and 8-view nearest-axis, fallbacks). The test app cannot submit an N-view grid. - SPEC. §5f states that mono follows the DP-CONFIRMED hardware-2D state for the session, not the request. It starts on the first submit after the confirmation and ends on the first submit after the confirmed state is 3D again. It also gains the view table. - TEST. weave_present_vk_linux --test-hold-flicker (implies --mono-in-2d) runs frames 10..49 in six-frame cycles: a 1-frame hold, a 2-frame hold, and a hold opened and closed inside one frame. Every frame is read back. Each frame must be mono exactly when the last request before its submit was 2D: no woven frame while held, no stale mono after release. - D3D11-service parity: #1798. Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Claude Opus 5.5 --- docs/specs/extensions/XR_DXR_weave.md | 32 +++++-- .../openxr_includes/openxr/XR_DXR_weave.h | 21 +++-- src/xrt/auxiliary/util/u_weave_span2d.h | 38 ++++++++ src/xrt/compositor/multi/comp_multi_private.h | 3 +- .../compositor/multi/comp_multi_weave_linux.c | 56 ++++++++---- src/xrt/ipc/client/ipc_client.h | 2 +- src/xrt/ipc/shared/ipc_protocol.h | 2 +- .../weave/weave_present_vk_linux/main.cpp | 86 ++++++++++++++++++- tests/tests_aux_weave_span2d.cpp | 29 +++++++ 9 files changed, 230 insertions(+), 39 deletions(-) diff --git a/docs/specs/extensions/XR_DXR_weave.md b/docs/specs/extensions/XR_DXR_weave.md index 0dd9880d4a..00d11c2a21 100644 --- a/docs/specs/extensions/XR_DXR_weave.md +++ b/docs/specs/extensions/XR_DXR_weave.md @@ -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) @@ -784,7 +784,7 @@ verbatim and the serial strictly increases. typedef struct XrWeaveSubmitMonoIn2DDXR { XrStructureType type; // XR_TYPE_WEAVE_SUBMIT_MONO_IN_2D_DXR const void* XR_MAY_ALIAS next; - XrBool32 monoIn2D; // XR_TRUE: centre view flat, no weave, while hardware is 2D + XrBool32 monoIn2D; // XR_TRUE: one view flat, no weave, while hardware is 2D } XrWeaveSubmitMonoIn2DDXR; ``` @@ -806,10 +806,24 @@ 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 the centre view of the submitted content at full output resolution, in -register with the window: for a batch rect its left half unsqueezed, for a v6 layout the centre -tile. That is the source the off-panel bands (§5d) use, and the path is theirs: the whole output -becomes one band. Flat regions and the v4 overlay are applied on top as usual. +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 @@ -839,7 +853,7 @@ any unknown chained struct. One `weave(v13)` WARN in the service log per edge. | 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 (the centre view) instead of the weave while the session's hardware state is 2D (§5f). Desktop Linux; accepted and ignored elsewhere. | +| 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 diff --git a/src/external/openxr_includes/openxr/XR_DXR_weave.h b/src/external/openxr_includes/openxr/XR_DXR_weave.h index f09103ddc7..564acdb68a 100644 --- a/src/external/openxr_includes/openxr/XR_DXR_weave.h +++ b/src/external/openxr_includes/openxr/XR_DXR_weave.h @@ -200,7 +200,8 @@ * * Mono in hardware 2D (SPEC_VERSION 13). XrWeaveSubmitMonoIn2DDXR, chained on * XrWeaveSubmitInfoDXR: while the session's hardware state is 2D, the output is - * the centre view, flat, instead of the weave. Desktop Linux; ignored elsewhere. + * 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 @@ -971,11 +972,17 @@ typedef struct XrWeaveOutputOriginDXR { * 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 the centre view of the submitted - * content at full output resolution, in register with the window: for a batch - * rect its left half unsqueezed, for a v6 layout the centre tile — the source - * the off-panel 2D bands use (§5d). Flat regions and the v4 overlay are applied - * on top as usual. While the hardware state is 3D the struct has no effect, so a + * 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. @@ -991,7 +998,7 @@ typedef struct XrWeaveOutputOriginDXR { typedef struct XrWeaveSubmitMonoIn2DDXR { XrStructureType type; //!< XR_TYPE_WEAVE_SUBMIT_MONO_IN_2D_DXR const void* XR_MAY_ALIAS next; - XrBool32 monoIn2D; //!< XR_TRUE: centre view flat, no weave, while hardware is 2D + XrBool32 monoIn2D; //!< XR_TRUE: one view flat, no weave, while hardware is 2D } XrWeaveSubmitMonoIn2DDXR; typedef XrResult (XRAPI_PTR *PFN_xrWeaveBindWindowDXR)( 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 1dda13e030..2a9ed6b1d7 100644 --- a/src/xrt/compositor/multi/comp_multi_private.h +++ b/src/xrt/compositor/multi/comp_multi_private.h @@ -1544,7 +1544,8 @@ comp_multi_weave_linux_request_display_mode(struct multi_compositor *mc, bool en * 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 the centre view of the submitted content, the + * 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 diff --git a/src/xrt/compositor/multi/comp_multi_weave_linux.c b/src/xrt/compositor/multi/comp_multi_weave_linux.c index 50290959a8..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. @@ -2104,17 +2104,19 @@ weave_run_frame(struct vk_bundle *vk, // 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 centre view painted in register with the window - // (batch: each rect's left half unsqueezed; v6: the centre tile) 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). + // 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 centre view is painted flat " - "over the whole output, no weave" + 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)")); } @@ -2190,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); } @@ -2209,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]; @@ -2230,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); } } } diff --git a/src/xrt/ipc/client/ipc_client.h b/src/xrt/ipc/client/ipc_client.h index 440f358cbf..ba85c9002b 100644 --- a/src/xrt/ipc/client/ipc_client.h +++ b/src/xrt/ipc/client/ipc_client.h @@ -239,7 +239,7 @@ comp_ipc_client_compositor_get_transparent_output_fence(struct xrt_compositor *x * 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 the centre view flat instead of weaving. + * 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 diff --git a/src/xrt/ipc/shared/ipc_protocol.h b/src/xrt/ipc/shared/ipc_protocol.h index d49e584c08..69e7b6c300 100644 --- a/src/xrt/ipc/shared/ipc_protocol.h +++ b/src/xrt/ipc/shared/ipc_protocol.h @@ -895,7 +895,7 @@ struct ipc_arg_weave_submit 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 the centre view flat instead of the weave + //! 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/test_apps/weave/weave_present_vk_linux/main.cpp b/test_apps/weave/weave_present_vk_linux/main.cpp index aaa2d7e181..a633a4eefb 100644 --- a/test_apps/weave/weave_present_vk_linux/main.cpp +++ b/test_apps/weave/weave_present_vk_linux/main.cpp @@ -97,6 +97,12 @@ * 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 @@ -244,6 +250,7 @@ struct Options 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 @@ -284,6 +291,8 @@ usage(const char *argv0) "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 " @@ -352,6 +361,9 @@ parse_args(int argc, char **argv, Options &o) 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) { @@ -504,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 @@ -875,7 +894,8 @@ 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.opt.hold_2d_frame >= 0 || g.opt.release_3d_frame >= 0) && + 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); @@ -2385,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; @@ -2452,6 +2489,37 @@ run() 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) { @@ -2550,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) { @@ -2604,6 +2678,14 @@ run() 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"); } 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); +}