diff --git a/Runtime/DisplayXRWindowSpaceUI.cs b/Runtime/DisplayXRWindowSpaceUI.cs index 828fb02..4df30ef 100644 --- a/Runtime/DisplayXRWindowSpaceUI.cs +++ b/Runtime/DisplayXRWindowSpaceUI.cs @@ -112,8 +112,9 @@ public class DisplayXRWindowSpaceUI : MonoBehaviour private const int kPanelSizeNone = 0; private const int kPanelSizeTile = 1; private const int kPanelSizeScreen = 2; + private const int kPanelSizeComposited = 3; private int m_PanelSizeSource = kPanelSizeNone; - private int m_ShellMode = -1; // -1 = not yet queried + private static int s_ShellMode = -1; // -1 = not yet queried (fixed for the process) // This component's private stage (see DisplayXROverlayStage): its canvas and // camera sit there, far from any scene content and from every other overlay's @@ -656,9 +657,23 @@ void LateUpdate() } } - private bool TryGetPanelPixelSize(out float pw, out float ph) + /// + /// The size in pixels of the window this app's window-space UI is composited into — + /// the frame / etc. are fractions of, and the + /// size the HUD RT aspect is derived from. Lay out HUD rects against THIS, not + /// Screen.*: in a transparent-overlay app on Windows Screen.* is Unity's + /// own cloaked window, which can have any size (#291). Source order: workspace tile + /// (shell) → the provider's composited window → Screen.*. Only the aspect is + /// meaningful across sources (native-DPI pixels). Returns false when nothing is known. + /// + public static bool TryGetWindowPixelSize(out float width, out float height) { - float cw = 0f, ch = 0f; + return GetWindowPixelSize(out width, out height) != kPanelSizeNone; + } + + private static int GetWindowPixelSize(out float cw, out float ch) + { + cw = 0f; ch = 0f; int source = kPanelSizeNone; // Workspace tile (#323): under the shell Unity's window is launched @@ -675,14 +690,33 @@ private bool TryGetPanelPixelSize(out float pw, out float ph) cw = tw; ch = th; source = kPanelSizeTile; } - // Built-app / Play Mode: the runtime composites into Unity's main - // window, so Screen.* is meaningful. + // The window the runtime actually composites into, as the provider last + // measured it (the eye swapchain is sized from the same number). In a + // transparent-overlay app that is the visible overlay window, while Unity's own + // window is cloaked/parked at whatever size Unity persisted (observed 512x728 and + // 3872x2248 next to a 3840x2160 overlay; it grows by the frame size each run) — + // so Screen.* gave a wrong aspect and squeezed the HUD. Only the ASPECT is used + // (see ComputeRtSize), so the native-DPI pixels need no conversion. + else if (TryGetCompositedSize(out float qw, out float qh)) + { + cw = qw; ch = qh; + source = kPanelSizeComposited; + } + // No provider session (or an older native plugin): Screen.*, which is right + // whenever the runtime composites into Unity's main window. else if (Screen.width > 0 && Screen.height > 0) { cw = Screen.width; ch = Screen.height; source = kPanelSizeScreen; } + return source; + } + + private bool TryGetPanelPixelSize(out float pw, out float ph) + { + int source = GetWindowPixelSize(out float cw, out float ch); + if (source == kPanelSizeNone) { pw = ph = 0f; @@ -695,7 +729,7 @@ private bool TryGetPanelPixelSize(out float pw, out float ph) { m_PanelSizeSource = source; Debug.Log($"[DisplayXR] wsui: panel size source = " + - $"{(source == kPanelSizeTile ? "workspace tile" : "Screen.*")} " + + $"{(source == kPanelSizeTile ? "workspace tile" : source == kPanelSizeComposited ? "composited window" : "Screen.*")} " + $"({cw}x{ch})"); } @@ -704,10 +738,33 @@ private bool TryGetPanelPixelSize(out float pw, out float ph) return true; } + // The provider's last measured composited window size; false with no provider + // session, before the first measurement, or against a native plugin without the + // export (then never asked again). + private static bool s_CompositedSizeMissing; + private static bool TryGetCompositedSize(out float w, out float h) + { + w = h = 0f; + if (s_CompositedSizeMissing || !DisplayXRProviderDriver.IsActive) return false; + try + { + if (DisplayXRProviderNative.dxr_prov_get_composited_size(out uint cw, out uint ch) == 0 || + cw == 0 || ch == 0) + return false; + w = cw; h = ch; + return true; + } + catch (System.EntryPointNotFoundException) + { + s_CompositedSizeMissing = true; + return false; + } + } + // The live workspace-tile canvas in px, or false when there is no tile size // yet (slot not bound, older runtime, or no native binary at all in the // editor before a Play session). - private bool TryGetTileCanvasSize(out float w, out float h) + private static bool TryGetTileCanvasSize(out float w, out float h) { w = h = 0f; try @@ -725,20 +782,20 @@ private bool TryGetTileCanvasSize(out float w, out float h) // Shell mode is fixed for the process (an env var read at load), so cache it // rather than P/Invoking twice a frame. An older native binary without the // export would throw per call, so a missing entry point also latches here. - private bool IsShellMode + private static bool IsShellMode { get { - if (m_ShellMode < 0) + if (s_ShellMode < 0) { try { - m_ShellMode = DisplayXRNative.displayxr_is_shell_mode() != 0 ? 1 : 0; + s_ShellMode = DisplayXRNative.displayxr_is_shell_mode() != 0 ? 1 : 0; } - catch (System.EntryPointNotFoundException) { m_ShellMode = 0; } - catch (System.DllNotFoundException) { m_ShellMode = 0; } + catch (System.EntryPointNotFoundException) { s_ShellMode = 0; } + catch (System.DllNotFoundException) { s_ShellMode = 0; } } - return m_ShellMode > 0; + return s_ShellMode > 0; } } diff --git a/Runtime/Provider/DisplayXRProviderNative.cs b/Runtime/Provider/DisplayXRProviderNative.cs index 83551da..44b15cc 100644 --- a/Runtime/Provider/DisplayXRProviderNative.cs +++ b/Runtime/Provider/DisplayXRProviderNative.cs @@ -313,6 +313,15 @@ public static extern void dxr_prov_get_local2d_bridge( uint width, uint height, out System.IntPtr nativePtr, out uint outWidth, out uint outHeight); + /// + /// Live size in px of the window (or workspace tile) the runtime composites this app + /// into, as last measured by the provider; 0 when no session runs or no size is known. + /// In a transparent-overlay app Unity's own window is cloaked, so Screen.* does + /// not describe it. Use the aspect; the absolute values are native-DPI pixels. + /// + [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] + public static extern int dxr_prov_get_composited_size(out uint width, out uint height); + /// Set the Local2D dest rect in client-window pixels. w<=0||h<=0 clears. [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] public static extern void dxr_prov_set_local2d_rect(int x, int y, int w, int h); diff --git a/native~/displayxr_xrprovider/displayxr_provider_session.cpp b/native~/displayxr_xrprovider/displayxr_provider_session.cpp index dcfe636..1e1e4f3 100644 --- a/native~/displayxr_xrprovider/displayxr_provider_session.cpp +++ b/native~/displayxr_xrprovider/displayxr_provider_session.cpp @@ -1094,6 +1094,25 @@ extern "C" int dxr_prov_workspace_tile_size(uint32_t *w, uint32_t *h) return 0; } +// The last LIVE size ps_window_size measured (tile canvas or the bound window's client), +// packed w<<32|h; 0 = none yet / only the display-dims fallback. Published for C# +// (dxr_prov_get_composited_size): DisplayXRWindowSpaceUI derives its panel aspect from it, +// because in a transparent-overlay app Screen.* is the size of Unity's own CLOAKED window, +// which has nothing to do with the window the runtime composites into. A cached value, not a +// live query, on purpose: on Linux the measurement goes through the X connection the runtime +// borrows (see displayxr_linux.c), which must not be driven from Unity's main thread. +static std::atomic s_live_window_px{0}; + +int dxr_prov_get_composited_size(uint32_t *w, uint32_t *h) +{ + uint64_t v = s_live_window_px.load(); + uint32_t ww = (uint32_t)(v >> 32), hh = (uint32_t)(v & 0xffffffffu); + if (!s_ps.running || ww == 0 || hh == 0) return 0; + if (w) *w = ww; + if (h) *h = hh; + return 1; +} + // The size the app + provider render/author for. In workspace-tile mode (#225) // this is the shell-driven tile canvas (so the render follows 3D-window resize); // otherwise the bound overlay's live client size (= Unity's window client area). @@ -1108,6 +1127,7 @@ static void ps_window_size(uint32_t *w, uint32_t *h) ps_query_tile_size(); // lazy first fetch until the slot binds if (s_ps.tile_px_w > 0 && s_ps.tile_px_h > 0) { *w = s_ps.tile_px_w; *h = s_ps.tile_px_h; + s_live_window_px.store(((uint64_t)s_ps.tile_px_w << 32) | s_ps.tile_px_h); return; } } @@ -1130,6 +1150,7 @@ static void ps_window_size(uint32_t *w, uint32_t *h) // self-hosting), which falls through to the display-info default below. displayxr_linux_window_size(&ww, &hh); #endif + if (ww > 0 && hh > 0) s_live_window_px.store(((uint64_t)ww << 32) | hh); // a real measurement if (ww == 0 || hh == 0) { ww = s_ps.display_info.is_valid ? s_ps.display_info.pixel_width : 1920; hh = s_ps.display_info.is_valid ? s_ps.display_info.pixel_height : 1080; @@ -5023,6 +5044,7 @@ int dxr_prov_session_start(const char *runtime_json_path, void dxr_prov_session_stop(void) { + s_live_window_px.store(0); // the next session measures its own window for (int i = 0; i < PS_MAX_WSUI; i++) { ProviderWsui *ws = &s_ps.wsui[i]; if (ws->swapchain && s_ps.pfn_destroy_swapchain) s_ps.pfn_destroy_swapchain(ws->swapchain); diff --git a/native~/displayxr_xrprovider/displayxr_provider_session.h b/native~/displayxr_xrprovider/displayxr_provider_session.h index 666b353..67022e0 100644 --- a/native~/displayxr_xrprovider/displayxr_provider_session.h +++ b/native~/displayxr_xrprovider/displayxr_provider_session.h @@ -308,6 +308,14 @@ DISPLAYXR_EXPORT int dxr_prov_get_zone_stereo_matrices(uint32_t zone, float *lv, /// Fill `zone`'s window-client pixel rect (top-left origin). Returns 1 on success. DISPLAYXR_EXPORT int dxr_prov_get_zone_rect_px(uint32_t zone, int *x, int *y, int *w, int *h); +/// The live size in pixels of the window (or workspace tile) the runtime composites this +/// app into — the size the eye swapchain is derived from — as last measured by the provider. +/// Returns 1 and fills *w/*h while a session runs and a real size is known; 0 otherwise. +/// In a transparent-overlay app Unity's own window is cloaked and Screen.* does not describe +/// it, so a window-space UI derives its panel aspect from this instead. Pixels of one window, +/// so the ASPECT is DPI-independent; don't mix the absolute values with managed geometry. +DISPLAYXR_EXPORT int dxr_prov_get_composited_size(uint32_t *w, uint32_t *h); + /// Local2D layer (#166 Phase B): lazily create the provider's Local2D overlay /// swapchain + cross-device bridge sized to w×h and return the Unity-device handle /// of the bridge. C# (DisplayXRLocal2D) Graphics.CopyTexture's its canvas RT into it