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..c0302a2df 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,31 @@ 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 (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). + */ +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..fc2775d7d 100644 --- a/src/xrt/compositor/multi/comp_multi_weave_linux.c +++ b/src/xrt/compositor/multi/comp_multi_weave_linux.c @@ -92,8 +92,11 @@ #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 "os/os_time.h" + #include "util/u_misc.h" #include "util/u_logging.h" #include "util/u_handles.h" @@ -981,6 +984,75 @@ 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. + * + * 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) in %.1f ms%s — panel state " + "unchanged, no event sent", + want_3d ? "3D" : "2D", why, dp_ms, slow); + 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), 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) 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", 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; + 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 +1157,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 +2803,33 @@ 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. + 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 " + "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..c25f18ff6 100644 --- a/test_apps/weave/weave_present_vk_linux/main.cpp +++ b/test_apps/weave/weave_present_vk_linux/main.cpp @@ -76,6 +76,20 @@ * code 0 = PASS. * --test-resize=WxH@N reallocates mid-run, as a window * resize or F11 does. + * --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 @@ -217,6 +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_deferred = false; //!< --test-display-mode=deferred }; static void @@ -247,6 +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[=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 " @@ -302,6 +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 || 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) { @@ -355,6 +378,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 +848,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 +2301,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 +2351,50 @@ run() if (!poll_xr_events()) { break; } + if (g.opt.test_display_mode && g.pfn_request_display_mode != nullptr) { + // 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; + } + 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; + } + } + } + } if (!g.headless) { g.window.pump_events( @@ -2468,6 +2548,15 @@ 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 = 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 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; }