From 596d9aed46092043707c8fa6cc57e7c18ea76d76 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Sep 2026 21:54:14 -0700 Subject: [PATCH 1/2] fix(multi/linux): route a present-owner's hardware 2D/3D request to the weave engine's DP (#1774) On the desktop-Linux service an XR_DXR_weave present-owner (the DisplayXR browser) has no window, so session_render is never initialised and multi_compositor_request_display_mode returned false at its first gate. Every xrRequestDisplayModeDXR from the browser's tab policy (browser#55) was dropped silently, and no hardware-state event was ever sent. The weave engine's own DP (mc->weave.dp), the only DP in the service and the one holding the vendor lens, never heard about it, so the panel stayed lensed over a 2D tab. - comp_multi_compositor.c: under COMP_MULTI_HAVE_WEAVE && XRT_OS_LINUX_DESKTOP, a client without per-session render goes to the weave engine. - comp_multi_weave_linux.c: comp_multi_weave_linux_request_display_mode applies the request inline on the IPC thread under weave.mutex, as the D3D11 service's #815 does: a present-owner asking for 2D has stopped submitting, so a frame-gated apply would never run. Before the engine exists the wish is recorded and applied at engine bring-up. The hardware-state event is sent to the session only once the DP confirmed a change (#961; a DP with no slot is mode-neutral and counts as accepted). There is one WARN per transition, with the DP's get_hardware_3d_state read-back. - sim_display (Vulkan DP): SIM_DISPLAY_FAKE_LENS=1 fills request_display_mode and get_hardware_3d_state with a recording fake lens (one WARN per transition). It is off by default, so default behaviour is unchanged. - weave_present_vk_linux --test-display-mode (headless) requests 2D before the first submit, then 3D, then 2D, and requires exactly the events 2D, 3D, 2D, each one before the next request. Windows, macOS and Android are untouched: the new branch is compiled only on desktop Linux with the weave service. Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Claude Opus 5.5 --- .../compositor/multi/comp_multi_compositor.c | 10 +++ src/xrt/compositor/multi/comp_multi_private.h | 30 +++++++ .../compositor/multi/comp_multi_weave_linux.c | 88 +++++++++++++++++++ .../sim_display/sim_display_processor.c | 51 +++++++++++ .../weave/weave_present_vk_linux/main.cpp | 55 ++++++++++++ 5 files changed, 234 insertions(+) diff --git a/src/xrt/compositor/multi/comp_multi_compositor.c b/src/xrt/compositor/multi/comp_multi_compositor.c index 949de371b..b384acdfa 100644 --- a/src/xrt/compositor/multi/comp_multi_compositor.c +++ b/src/xrt/compositor/multi/comp_multi_compositor.c @@ -2458,6 +2458,16 @@ multi_compositor_get_window_screen_rect(struct multi_compositor *mc, bool multi_compositor_request_display_mode(struct multi_compositor *mc, bool enable_3d) { +#if defined(COMP_MULTI_HAVE_WEAVE) && defined(XRT_OS_LINUX_DESKTOP) + // Desktop-Linux XR_DXR_weave present-owner (browser#55 on Linux): it has no + // window, so session_render is never initialised and the gate below dropped + // every hardware request on the floor — the panel's lens stayed on over a + // 2D tab while the browser believed the request had landed. Its DP is the + // weave engine's own instance (mc->weave.dp), so the request goes there. + if (mc != NULL && !mc->session_render.initialized) { + return comp_multi_weave_linux_request_display_mode(mc, enable_3d); + } +#endif if (mc == NULL || !mc->session_render.initialized) { return false; } diff --git a/src/xrt/compositor/multi/comp_multi_private.h b/src/xrt/compositor/multi/comp_multi_private.h index 871e6dcc6..44fb53e06 100644 --- a/src/xrt/compositor/multi/comp_multi_private.h +++ b/src/xrt/compositor/multi/comp_multi_private.h @@ -916,6 +916,14 @@ struct multi_compositor uint32_t last_offpanel_band_count; //! @} + /*! + * Hardware 2D/3D as last CONFIRMED by mc->weave.dp and reported to + * this client's session (comp_multi_weave_linux_request_display_mode). + * Stored inverted so the zero-initialised struct means 3D, the state + * every session starts in. The standing WISH is mc->hardware_display_3d. + */ + bool hw_2d_confirmed; + //! @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 @@ -1493,6 +1501,28 @@ bool comp_multi_weave_set_screen_flat_regions(struct xrt_compositor *xc, uint32_t rect_count, const struct xrt_rect *screen_rects); + +/*! + * HARDWARE 2D/3D request (xrRequestDisplayModeDXR, #533) for a present-owner + * on desktop Linux — routed here by multi_compositor_request_display_mode, + * whose per-session-render branch a present-owner never reaches (it has no + * window, so session_render is never initialised). Applies the request to the + * weave engine's OWN display processor (mc->weave.dp — the only DP in the + * service process, the one holding the vendor's lens), inline on the calling + * (IPC) thread under mc->weave.mutex: a present-owner that asks for 2D has, by + * definition, stopped submitting, so a frame-gated apply would never run (the + * D3D11 service's #815 for the same browser#55 flow). Before the engine exists + * the wish is recorded and applied at engine bring-up. + * + * Emits XRT_SESSION_EVENT_HARDWARE_DISPLAY_STATE_CHANGE to this client's + * session only once the DP CONFIRMED a change (#961 semantics; a DP with no + * request_display_mode slot is mode-neutral and counts as accepted), and logs + * one WARN per transition with the DP's read-back panel state. + * + * @return true if the request was applied (or recorded for bring-up). + */ +bool +comp_multi_weave_linux_request_display_mode(struct multi_compositor *mc, bool enable_3d); /*! @} */ #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 b72fa0568..ccfeb6bef 100644 --- a/src/xrt/compositor/multi/comp_multi_weave_linux.c +++ b/src/xrt/compositor/multi/comp_multi_weave_linux.c @@ -92,6 +92,7 @@ #include "xrt/xrt_display_processor_vk.h" #include "xrt/xrt_display_metrics.h" #include "xrt/xrt_handles.h" +#include "xrt/xrt_session.h" #include "xrt/xrt_weave_dmabuf.h" #include "util/u_misc.h" @@ -981,6 +982,63 @@ weave_reset_acquire_sem(struct vk_bundle *vk, struct multi_compositor *mc) * in the Linux DP contract ties its creation to a thread, so the factory runs on * the IPC handler thread. */ +/*! + * Apply a HARDWARE 2D/3D request to the engine's own DP and, once the DP + * confirmed a CHANGE, tell this client's session (#961: the event follows the + * DP, never the bookkeeping). Call with mc->weave.mutex held and mc->weave.dp + * non-NULL. The vendor call is one lens-hint call (Leia: srLensDisable / + * srLensEnable under the plug-in's own context lock), so it is cheap enough + * for the IPC thread; the lock order weave.mutex -> vendor lock is the same one + * every submit already takes (process_atlas runs under weave.mutex). + * + * A DP without a request_display_mode slot is mode-neutral (sim_display by + * default): the request counts as accepted, as in the D3D11 service's + * dp_request_display_mode_confirmed. A DP that HAS the slot and returns false + * rejected it — nothing is recorded or reported, so the session never believes + * in a panel state the hardware did not reach. + */ +static bool +weave_apply_display_mode_locked(struct multi_compositor *mc, bool want_3d, const char *why) +{ + struct xrt_display_processor *dp = mc->weave.dp; + const bool has_slot = XRT_DP_HAS_SLOT(dp, request_display_mode) && dp->request_display_mode != NULL; + const bool accepted = has_slot ? xrt_display_processor_request_display_mode(dp, want_3d) : true; + if (!accepted) { + U_LOG_W( + "weave(#1699): the display processor REJECTED hardware %s (%s) — panel state unchanged, no " + "event sent", + want_3d ? "3D" : "2D", why); + return false; + } + + const bool prev_3d = !mc->weave.hw_2d_confirmed; + mc->weave.hw_2d_confirmed = !want_3d; + if (prev_3d == want_3d) { + return true; // already there (a re-assert keeps a drifted vendor honest; nothing to report) + } + + // One WARN per transition (a human-rate event: a tab switch, a page's mode + // choice), with the DP's own read-back so a hardware run can be read from + // the journal alone. + bool panel_3d = false; + const bool have_readback = xrt_display_processor_get_hardware_3d_state(dp, &panel_3d); + U_LOG_W( + "weave(#1699): hardware %s -> %s (%s) on the weave engine's display processor (%s); DP reports the " + "panel %s", + prev_3d ? "3D" : "2D", want_3d ? "3D" : "2D", why, + has_slot ? "request_display_mode accepted" : "mode-neutral DP, nothing to switch", + have_readback ? (panel_3d ? "3D" : "2D") : "state unknown (no read-back)"); + + union xrt_session_event xse = {0}; + xse.hardware_display_state_change.type = XRT_SESSION_EVENT_HARDWARE_DISPLAY_STATE_CHANGE; + xse.hardware_display_state_change.hardware_display_3d = want_3d; + const xrt_result_t xret = multi_compositor_push_event(mc, &xse); + if (xret != XRT_SUCCESS) { + U_LOG_W("weave(#1699): could not push the hardware-state event to the session: %d", xret); + } + return true; +} + static bool weave_ensure_engine(struct vk_bundle *vk, struct multi_compositor *mc) { @@ -1085,6 +1143,14 @@ weave_ensure_engine(struct vk_bundle *vk, struct multi_compositor *mc) vk_dmabuf_supported(vk) ? "yes" : "NO (device lacks dma-buf / DRM modifier / foreign queue)", mc->weave.release_sem != VK_NULL_HANDLE ? "GPU (SYNC_FD semaphores)" : "none (CPU poll + synchronous)", (int)xrt_display_processor_is_self_submitting(mc->weave.dp)); + + // A hardware 2D request that arrived before this DP existed (the page asked + // for a flat panel before the first submit). 3D is the default, so only a + // 2D wish needs acting on — same shape as the Android session_render + // deferral in multi_compositor_init_session_render. + if (!mc->hardware_display_3d) { + (void)weave_apply_display_mode_locked(mc, false, "deferred request, applied at engine bring-up"); + } return true; } @@ -2723,6 +2789,28 @@ comp_multi_weave_snap_window_rect(struct xrt_compositor *xc, return snapped; } +bool +comp_multi_weave_linux_request_display_mode(struct multi_compositor *mc, bool enable_3d) +{ + if (mc == NULL || mc->msc == NULL) { + return false; + } + weave_ensure_mutex(mc); + os_mutex_lock(&mc->weave.mutex); + // The standing wish: read again at engine bring-up if the DP is not there yet. + mc->hardware_display_3d = enable_3d; + bool ok = true; + if (mc->weave.dp != NULL) { + ok = weave_apply_display_mode_locked(mc, enable_3d, "xrRequestDisplayModeDXR"); + } else if (!enable_3d) { + U_LOG_W( + "weave(#1699): hardware 2D requested before the weave engine exists — recorded, applied when " + "the engine comes up"); + } + os_mutex_unlock(&mc->weave.mutex); + return ok; +} + void comp_multi_weave_fini(struct multi_compositor *mc) { diff --git a/src/xrt/drivers/sim_display/sim_display_processor.c b/src/xrt/drivers/sim_display/sim_display_processor.c index 513dac75d..49d170d1d 100644 --- a/src/xrt/drivers/sim_display/sim_display_processor.c +++ b/src/xrt/drivers/sim_display/sim_display_processor.c @@ -64,6 +64,21 @@ DEBUG_GET_ONCE_BOOL_OPTION(sim_display_strict_panel, "SIM_DISPLAY_STRICT_PANEL", */ DEBUG_GET_ONCE_NUM_OPTION(sim_display_interlace_period, "SIM_DISPLAY_INTERLACE_PERIOD", 1) +/*! + * Fake switchable lens (test double for the hardware 2D/3D channel). + * + * sim_display is mode-neutral by default: it has no lens, so its DP leaves the + * request_display_mode / get_hardware_3d_state slots NULL and the runtime + * treats a hardware request as "nothing to switch" (in-process compositors then + * fall back to the device's OUTPUT_MODE property). That keeps it honest, but it + * also makes a headless run blind to WHERE a hardware request lands — e.g. + * whether the desktop-Linux weave engine's own DP receives a present-owner's + * xrRequestDisplayModeDXR at all. With this option the DP fills both slots: it + * records the requested state, logs one WARN per transition and reports it back, + * like a vendor lens would. Off by default, so default behaviour is unchanged. + */ +DEBUG_GET_ONCE_BOOL_OPTION(sim_display_fake_lens, "SIM_DISPLAY_FAKE_LENS", false) + // SPIR-V shader headers (generated at build time by spirv_shaders()) #include "sim_display/shaders/fullscreen.vert.h" #include "sim_display/shaders/anaglyph.frag.h" @@ -145,6 +160,9 @@ struct sim_display_processor uint32_t geom_last_target_w, geom_last_target_h; int32_t geom_last_canvas_x, geom_last_canvas_y; uint32_t geom_last_canvas_w, geom_last_canvas_h; + + //! SIM_DISPLAY_FAKE_LENS — the fake lens state (true = 3D, the default). + bool fake_lens_3d; }; static inline struct sim_display_processor * @@ -686,6 +704,31 @@ sim_dp_get_predicted_eye_positions(struct xrt_display_processor *xdp, struct xrt return true; } +//! SIM_DISPLAY_FAKE_LENS: record the request (one WARN per transition). +static bool +sim_dp_request_display_mode(struct xrt_display_processor *xdp, bool enable_3d) +{ + struct sim_display_processor *sdp = sim_display_processor(xdp); + if (sdp->fake_lens_3d != enable_3d) { + U_LOG_W("sim_display: fake lens %s -> %s (request_display_mode on DP %p)", + sdp->fake_lens_3d ? "3D" : "2D", enable_3d ? "3D" : "2D", (void *)xdp); + } + sdp->fake_lens_3d = enable_3d; + return true; +} + +//! SIM_DISPLAY_FAKE_LENS: report the recorded state. +static bool +sim_dp_get_hardware_3d_state(struct xrt_display_processor *xdp, bool *out_is_3d) +{ + struct sim_display_processor *sdp = sim_display_processor(xdp); + if (out_is_3d == NULL) { + return false; + } + *out_is_3d = sdp->fake_lens_3d; + return true; +} + static VkRenderPass sim_dp_get_render_pass(struct xrt_display_processor *xdp) { @@ -995,6 +1038,14 @@ sim_display_processor_create(enum sim_display_output_mode mode, sdp->base_vk.base.publish_local_zone_mask = sim_dp_publish_local_zone_mask; // #224 / ADR-027 sdp->base_vk.base.clear_local_zone_mask = sim_dp_clear_local_zone_mask; // #224 / ADR-027 + // Fake switchable lens: off by default (the slots stay NULL = mode-neutral). + sdp->fake_lens_3d = true; + if (debug_get_bool_option_sim_display_fake_lens()) { + sdp->base_vk.base.request_display_mode = sim_dp_request_display_mode; + sdp->base_vk.base.get_hardware_3d_state = sim_dp_get_hardware_3d_state; + U_LOG_W("sim_display: SIM_DISPLAY_FAKE_LENS — this DP (%p) reports a switchable lens", (void *)sdp); + } + // #224 / ADR-027 zone test double config (shared parser). sim_zone_config_from_env(&sdp->zone_cfg, "VK"); diff --git a/test_apps/weave/weave_present_vk_linux/main.cpp b/test_apps/weave/weave_present_vk_linux/main.cpp index 97665b2d0..259dc7a4d 100644 --- a/test_apps/weave/weave_present_vk_linux/main.cpp +++ b/test_apps/weave/weave_present_vk_linux/main.cpp @@ -76,6 +76,15 @@ * code 0 = PASS. * --test-resize=WxH@N reallocates mid-run, as a window * resize or F11 does. + * --test-display-mode (headless) drive the hardware 2D/3D channel the way + * the browser's tab policy does: xrRequestDisplayModeDXR + * (2D) BEFORE the first submit (the weave engine does not + * exist yet: recorded, applied at bring-up), 3D at frame + * 30, 2D at frame 50; require exactly the events + * XrEventDataHardwareDisplayStateChangedDXR 2D, 3D, 2D, + * each arriving before the next request. Pair it with a + * service started with SIM_DISPLAY_FAKE_LENS=1 so the + * weave engine's DP records + logs each request. * --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 @@ -217,6 +226,7 @@ struct Options uint32_t resize_w = 0, resize_h = 0; int resize_frame = -1; bool lattice_selftest = false; //!< --lattice-selftest + bool test_display_mode = false; //!< --test-display-mode (headless) }; static void @@ -247,6 +257,8 @@ usage(const char *argv0) "service's fd count to stay flat\n" " --test-resize=WxH@N (headless) reallocate at frame N, " "like a window resize\n" + " --test-display-mode (headless) request hardware 2D/3D/2D " + "and require the matching events\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 " @@ -302,6 +314,8 @@ parse_args(int argc, char **argv, Options &o) } else if (sscanf(a, "--test-resize=%ux%u@%d", &o.resize_w, &o.resize_h, &o.resize_frame) == 3) { } else if (strcmp(a, "--lattice-selftest") == 0) { o.lattice_selftest = true; + } else if (strcmp(a, "--test-display-mode") == 0) { + o.test_display_mode = true; } else if (strncmp(a, "--dump-dir=", 11) == 0) { o.dump_dir = a + 11; } else if (strcmp(a, "--help") == 0 || strcmp(a, "-h") == 0) { @@ -355,6 +369,10 @@ struct App PFN_xrWeaveBindWindow2DXR pfn_bind2 = nullptr; PFN_xrWeaveSubmitDXR pfn_submit = nullptr; PFN_xrWeaveSnapWindowRectDXR pfn_snap = nullptr; + PFN_xrRequestDisplayModeDXR pfn_request_display_mode = nullptr; //!< --test-display-mode + //! --test-display-mode: every XrEventDataHardwareDisplayStateChangedDXR, in order. + std::vector hw_events; + bool dm_order_bad = false; //!< --test-display-mode: an event missing before the next request, or a failed call bool has_display_info = false; int32_t panel_left = 0, panel_top = 0; uint32_t panel_w = 0, panel_h = 0; @@ -821,6 +839,10 @@ 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) { + xrGetInstanceProcAddr(g.instance, "xrRequestDisplayModeDXR", + (PFN_xrVoidFunction *)&g.pfn_request_display_mode); + } if (!g.pfn_bind2 || !g.pfn_submit || !g.pfn_snap) { LOGE("weave entry points missing"); return false; @@ -2270,6 +2292,11 @@ poll_xr_events() } else if (ev.type == XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING) { LOGW("instance loss pending — leaving"); return false; + } else if (ev.type == XR_TYPE_EVENT_DATA_HARDWARE_DISPLAY_STATE_CHANGED_DXR) { + const auto *h = (const XrEventDataHardwareDisplayStateChangedDXR *)&ev; + g.hw_events.push_back(h->hardwareDisplay3D == XR_TRUE); + LOGI("event: hardware display state -> %s (frame %llu)", h->hardwareDisplay3D ? "3D" : "2D", + (unsigned long long)g.frame); } ev = {XR_TYPE_EVENT_DATA_BUFFER}; } @@ -2315,6 +2342,28 @@ run() if (!poll_xr_events()) { break; } + if (g.opt.test_display_mode && g.pfn_request_display_mode != nullptr) { + // The browser's tab policy: 2D before the first weave (the engine + // does not exist yet), 3D on an inline-3D tab, 2D again on a plain tab. + const int step = f == 0 ? 0 : f == 30 ? 1 : f == 50 ? 2 : -1; + if (step >= 0) { + // Every earlier request's event must already be here (the + // runtime answers each on the request's own IPC call, or at + // engine bring-up on the first submit). + if ((int)g.hw_events.size() != step) { + LOGE("display-mode test: %zu event(s) before request #%d, expected %d", g.hw_events.size(), + step, step); + g.dm_order_bad = true; + } + const bool want_3d = step == 1; + 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: xrRequestDisplayModeDXR(%s) -> %d", f, want_3d ? "3D" : "2D", (int)r); + if (XR_FAILED(r)) { + g.dm_order_bad = true; + } + } + } if (!g.headless) { g.window.pump_events( @@ -2468,6 +2517,12 @@ run() check(g.v12.outputs > 0 && g.v12.logical_bad == 0, "v12 logical origin + scale echoed verbatim"); check(g.v12.outputs > 0 && g.v12.serial_bad == 0, "v12 serial strictly increasing per output"); check(total < 8 || g.v12.distinct >= 2, "v12 woven origin followed the re-binds"); + if (g.opt.test_display_mode) { + const std::vector want = {false, true, false}; + check(g.pfn_request_display_mode != nullptr && total > 55, "display-mode: xrRequestDisplayModeDXR resolved"); + check(!g.dm_order_bad, "display-mode: each event arrived before the next request"); + check(g.hw_events == want, "display-mode: events 2D, 3D, 2D (DP-confirmed)"); + } LOGI("%s", pass ? "PASS" : "FAIL"); return pass ? 0 : 1; } From 981ea6e06278855fb8bde24ad903a157e9471969 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Sep 2026 21:59:09 -0700 Subject: [PATCH 2/2] fix(multi/linux): event from the accepted request, time the DP call, cover stale/fast switches (#1774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review refinements on top of the present-owner display-mode routing: - The hardware-state event carries the REQUESTED state once the DP accepted it (#961), never a read-back. A vendor lens may switch asynchronously (Leia SR flips it via LENS_ON/OFF events), so an immediate get_hardware_3d_state can still show the old state. The read-back is now logged as information only: "DP readback: 3D (may lag — the lens switches asynchronously)". - The DP call runs on the IPC thread under weave.mutex, so it is timed with os_monotonic_get_ns and the duration goes into the per-transition (and rejection) WARN, "in N ms", with "(slow)" past 50 ms. No behaviour change. - A 3D request before the engine exists withdraws a recorded 2D (one WARN), so bring-up applies nothing stale. - weave_present_vk_linux --test-display-mode is now table-driven: - stale (default): 2D,3D before the first submit (no event at bring-up), then 2D, the fast triple 3D,2D,3D in one frame, a no-op 3D (no duplicate event) and 2D. It expects the events 2D,3D,2D,3D,2D in order. - =deferred: the original pre-submit 2D applied at bring-up. Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Claude Opus 5.5 --- src/xrt/compositor/multi/comp_multi_private.h | 11 ++- .../compositor/multi/comp_multi_weave_linux.c | 37 +++++-- .../weave/weave_present_vk_linux/main.cpp | 98 +++++++++++++------ 3 files changed, 101 insertions(+), 45 deletions(-) diff --git a/src/xrt/compositor/multi/comp_multi_private.h b/src/xrt/compositor/multi/comp_multi_private.h index 44fb53e06..c0302a2df 100644 --- a/src/xrt/compositor/multi/comp_multi_private.h +++ b/src/xrt/compositor/multi/comp_multi_private.h @@ -1514,10 +1514,13 @@ comp_multi_weave_set_screen_flat_regions(struct xrt_compositor *xc, * D3D11 service's #815 for the same browser#55 flow). Before the engine exists * the wish is recorded and applied at engine bring-up. * - * Emits XRT_SESSION_EVENT_HARDWARE_DISPLAY_STATE_CHANGE to this client's - * session only once the DP CONFIRMED a change (#961 semantics; a DP with no - * request_display_mode slot is mode-neutral and counts as accepted), and logs - * one WARN per transition with the DP's read-back panel state. + * Emits XRT_SESSION_EVENT_HARDWARE_DISPLAY_STATE_CHANGE (carrying the + * REQUESTED state) to this client's session only once the DP ACCEPTED a change + * (#961 semantics; a DP with no request_display_mode slot is mode-neutral and + * counts as accepted); a request for the state already confirmed sends none. + * Logs one WARN per transition with the DP call's duration and, as information + * only, the DP's read-back (a vendor lens may switch asynchronously, so it can + * lag the request). * * @return true if the request was applied (or recorded for bring-up). */ diff --git a/src/xrt/compositor/multi/comp_multi_weave_linux.c b/src/xrt/compositor/multi/comp_multi_weave_linux.c index ccfeb6bef..fc2775d7d 100644 --- a/src/xrt/compositor/multi/comp_multi_weave_linux.c +++ b/src/xrt/compositor/multi/comp_multi_weave_linux.c @@ -95,6 +95,8 @@ #include "xrt/xrt_session.h" #include "xrt/xrt_weave_dmabuf.h" +#include "os/os_time.h" + #include "util/u_misc.h" #include "util/u_logging.h" #include "util/u_handles.h" @@ -996,18 +998,28 @@ weave_reset_acquire_sem(struct vk_bundle *vk, struct multi_compositor *mc) * dp_request_display_mode_confirmed. A DP that HAS the slot and returns false * rejected it — nothing is recorded or reported, so the session never believes * in a panel state the hardware did not reach. + * + * The event carries the REQUESTED state once the DP accepted it (#961), never + * a read-back: a vendor lens may switch asynchronously (Leia SR flips it via + * its own LENS_ON/OFF events), so get_hardware_3d_state right after the + * request can still report the old state. The read-back is logged as + * information only. The DP call is timed, because it runs on the IPC thread + * under weave.mutex (a slow vendor service stalls this client's submits). */ static bool weave_apply_display_mode_locked(struct multi_compositor *mc, bool want_3d, const char *why) { struct xrt_display_processor *dp = mc->weave.dp; const bool has_slot = XRT_DP_HAS_SLOT(dp, request_display_mode) && dp->request_display_mode != NULL; + const int64_t t0_ns = os_monotonic_get_ns(); const bool accepted = has_slot ? xrt_display_processor_request_display_mode(dp, want_3d) : true; + const double dp_ms = (double)(os_monotonic_get_ns() - t0_ns) / 1e6; + const char *slow = dp_ms > 50.0 ? " (slow)" : ""; if (!accepted) { U_LOG_W( - "weave(#1699): the display processor REJECTED hardware %s (%s) — panel state unchanged, no " - "event sent", - want_3d ? "3D" : "2D", why); + "weave(#1699): the display processor REJECTED hardware %s (%s) in %.1f ms%s — panel state " + "unchanged, no event sent", + want_3d ? "3D" : "2D", why, dp_ms, slow); return false; } @@ -1018,16 +1030,18 @@ weave_apply_display_mode_locked(struct multi_compositor *mc, bool want_3d, const } // One WARN per transition (a human-rate event: a tab switch, a page's mode - // choice), with the DP's own read-back so a hardware run can be read from - // the journal alone. + // choice), so a hardware run can be read from the journal alone. The + // read-back is informational only (see above): it may lag the request. bool panel_3d = false; const bool have_readback = xrt_display_processor_get_hardware_3d_state(dp, &panel_3d); U_LOG_W( - "weave(#1699): hardware %s -> %s (%s) on the weave engine's display processor (%s); DP reports the " - "panel %s", + "weave(#1699): hardware %s -> %s (%s) on the weave engine's display processor (%s) in %.1f ms%s; " + "DP readback: %s", prev_3d ? "3D" : "2D", want_3d ? "3D" : "2D", why, - has_slot ? "request_display_mode accepted" : "mode-neutral DP, nothing to switch", - have_readback ? (panel_3d ? "3D" : "2D") : "state unknown (no read-back)"); + has_slot ? "request_display_mode accepted" : "mode-neutral DP, nothing to switch", dp_ms, slow, + have_readback ? (panel_3d ? "3D (may lag — the lens switches asynchronously)" + : "2D (may lag — the lens switches asynchronously)") + : "none (the DP has no get_hardware_3d_state)"); union xrt_session_event xse = {0}; xse.hardware_display_state_change.type = XRT_SESSION_EVENT_HARDWARE_DISPLAY_STATE_CHANGE; @@ -2798,10 +2812,15 @@ comp_multi_weave_linux_request_display_mode(struct multi_compositor *mc, bool en weave_ensure_mutex(mc); os_mutex_lock(&mc->weave.mutex); // The standing wish: read again at engine bring-up if the DP is not there yet. + const bool had_pending_2d = mc->weave.dp == NULL && !mc->hardware_display_3d; mc->hardware_display_3d = enable_3d; bool ok = true; if (mc->weave.dp != NULL) { ok = weave_apply_display_mode_locked(mc, enable_3d, "xrRequestDisplayModeDXR"); + } else if (enable_3d && had_pending_2d) { + U_LOG_W( + "weave(#1699): hardware 3D requested before the weave engine exists — the recorded 2D request is " + "withdrawn, nothing to apply at bring-up"); } else if (!enable_3d) { U_LOG_W( "weave(#1699): hardware 2D requested before the weave engine exists — recorded, applied when " diff --git a/test_apps/weave/weave_present_vk_linux/main.cpp b/test_apps/weave/weave_present_vk_linux/main.cpp index 259dc7a4d..c25f18ff6 100644 --- a/test_apps/weave/weave_present_vk_linux/main.cpp +++ b/test_apps/weave/weave_present_vk_linux/main.cpp @@ -76,15 +76,20 @@ * code 0 = PASS. * --test-resize=WxH@N reallocates mid-run, as a window * resize or F11 does. - * --test-display-mode (headless) drive the hardware 2D/3D channel the way - * the browser's tab policy does: xrRequestDisplayModeDXR - * (2D) BEFORE the first submit (the weave engine does not - * exist yet: recorded, applied at bring-up), 3D at frame - * 30, 2D at frame 50; require exactly the events - * XrEventDataHardwareDisplayStateChangedDXR 2D, 3D, 2D, - * each arriving before the next request. Pair it with a - * service started with SIM_DISPLAY_FAKE_LENS=1 so the - * weave engine's DP records + logs each request. + * --test-display-mode[=stale|deferred] (headless) drive the hardware + * 2D/3D channel the way the browser's tab policy does + * (xrRequestDisplayModeDXR) and require exactly the + * matching XrEventDataHardwareDisplayStateChangedDXR + * events, each batch's events present before the next. + * stale (default): 2D then 3D BEFORE the first submit + * (bring-up must NOT apply the withdrawn 2D: no event), + * 2D at frame 20, the fast triple 3D,2D,3D in one frame + * at 30, a no-op 3D at 40 (no event), 2D at 50 -> + * events 2D,3D,2D,3D,2D. deferred: 2D before the first + * submit (recorded, applied at bring-up), 3D at 30, 2D + * 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. * --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 @@ -226,7 +231,8 @@ struct Options uint32_t resize_w = 0, resize_h = 0; int resize_frame = -1; bool lattice_selftest = false; //!< --lattice-selftest - bool test_display_mode = false; //!< --test-display-mode (headless) + bool test_display_mode = false; //!< --test-display-mode (headless) + bool test_display_deferred = false; //!< --test-display-mode=deferred }; static void @@ -257,8 +263,8 @@ usage(const char *argv0) "service's fd count to stay flat\n" " --test-resize=WxH@N (headless) reallocate at frame N, " "like a window resize\n" - " --test-display-mode (headless) request hardware 2D/3D/2D " - "and require the matching events\n" + " --test-display-mode[=stale|deferred] (headless) request hardware " + "2D/3D sequences and require the matching events\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 " @@ -314,8 +320,11 @@ parse_args(int argc, char **argv, Options &o) } else if (sscanf(a, "--test-resize=%ux%u@%d", &o.resize_w, &o.resize_h, &o.resize_frame) == 3) { } else if (strcmp(a, "--lattice-selftest") == 0) { o.lattice_selftest = true; - } else if (strcmp(a, "--test-display-mode") == 0) { + } else if (strcmp(a, "--test-display-mode") == 0 || strcmp(a, "--test-display-mode=stale") == 0) { o.test_display_mode = true; + } else if (strcmp(a, "--test-display-mode=deferred") == 0) { + o.test_display_mode = true; + o.test_display_deferred = true; } else if (strncmp(a, "--dump-dir=", 11) == 0) { o.dump_dir = a + 11; } else if (strcmp(a, "--help") == 0 || strcmp(a, "-h") == 0) { @@ -2343,25 +2352,47 @@ run() break; } if (g.opt.test_display_mode && g.pfn_request_display_mode != nullptr) { - // The browser's tab policy: 2D before the first weave (the engine - // does not exist yet), 3D on an inline-3D tab, 2D again on a plain tab. - const int step = f == 0 ? 0 : f == 30 ? 1 : f == 50 ? 2 : -1; - if (step >= 0) { - // Every earlier request's event must already be here (the - // runtime answers each on the request's own IPC call, or at - // engine bring-up on the first submit). - if ((int)g.hw_events.size() != step) { - LOGE("display-mode test: %zu event(s) before request #%d, expected %d", g.hw_events.size(), - step, step); - g.dm_order_bad = true; + // The browser's tab policy, as batches of requests at given frames + // (frame 0 runs BEFORE the first submit: the service's weave + // engine does not exist yet). events_before = how many events + // must already have arrived when the batch is issued — the + // runtime answers each request on its own IPC call, or at engine + // bring-up on the first submit. + struct Batch + { + int frame; + size_t events_before; + std::vector reqs; // true = 3D + }; + static const std::vector stale = { + {0, 0, {false, true}}, // 2D then 3D pre-engine: bring-up applies nothing + {20, 0, {false}}, // no stale event so far; 2D + {30, 1, {true, false, true}}, // fast triple in one frame + {40, 4, {true}}, // no-op: already 3D, no event + {50, 4, {false}}, + }; + static const std::vector deferred = { + {0, 0, {false}}, // pre-engine 2D: applied at bring-up + {30, 1, {true}}, + {50, 2, {false}}, + }; + for (const Batch &b : g.opt.test_display_deferred ? deferred : stale) { + if (b.frame != f) { + continue; } - const bool want_3d = step == 1; - 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: xrRequestDisplayModeDXR(%s) -> %d", f, want_3d ? "3D" : "2D", (int)r); - if (XR_FAILED(r)) { + if (g.hw_events.size() != b.events_before) { + LOGE("display-mode test: %zu event(s) before the frame-%d batch, expected %zu", + g.hw_events.size(), f, b.events_before); g.dm_order_bad = true; } + for (bool want_3d : b.reqs) { + 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: xrRequestDisplayModeDXR(%s) -> %d", f, want_3d ? "3D" : "2D", (int)r); + if (XR_FAILED(r)) { + g.dm_order_bad = true; + } + } } } @@ -2518,10 +2549,13 @@ run() check(g.v12.outputs > 0 && g.v12.serial_bad == 0, "v12 serial strictly increasing per output"); check(total < 8 || g.v12.distinct >= 2, "v12 woven origin followed the re-binds"); if (g.opt.test_display_mode) { - const std::vector want = {false, true, false}; + const std::vector want = g.opt.test_display_deferred ? std::vector{false, true, false} + : std::vector{false, true, false, true, false}; check(g.pfn_request_display_mode != nullptr && total > 55, "display-mode: xrRequestDisplayModeDXR resolved"); - check(!g.dm_order_bad, "display-mode: each event arrived before the next request"); - check(g.hw_events == want, "display-mode: events 2D, 3D, 2D (DP-confirmed)"); + check(!g.dm_order_bad, "display-mode: each batch's events arrived before the next batch"); + check(g.hw_events == want, g.opt.test_display_deferred + ? "display-mode: events 2D, 3D, 2D (deferred 2D applied at bring-up)" + : "display-mode: events 2D,3D,2D,3D,2D (no stale 2D, triple, no-op)"); } LOGI("%s", pass ? "PASS" : "FAIL"); return pass ? 0 : 1;