Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Runtime/DisplayXRNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,23 @@ public static extern void displayxr_window_space_ui_set_layer(
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern void displayxr_window_space_ui_clear();

// Per-slot window-space UI (#336 follow-up). Several DisplayXRWindowSpaceUI
// components can be live at once; each takes a slot so they no longer overwrite
// each other. The slot-less calls above address slot 0.
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern int displayxr_window_space_ui_acquire_slot();

[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern void displayxr_window_space_ui_release_slot(int slot);

[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern void displayxr_window_space_ui_set_texture_slot(
int slot, IntPtr nativeTex, int width, int height);

[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern void displayxr_window_space_ui_set_layer_slot(
int slot, float x, float y, float width, float height, float disparity);

// ====================================================================
// Local2D overlay (#439/#491) — modern mask-based 2D-over-3D layer
// (XrCompositionLayerLocal2DDXR, "glass over 3D"). Replaces the legacy
Expand Down
107 changes: 93 additions & 14 deletions Runtime/DisplayXRWindowSpaceUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ public class DisplayXRWindowSpaceUI : MonoBehaviour
// (#336) Vulkan has no wrappable bridge: the getter returns a session token in
// place of m_BridgePtr, and the copy is a plugin event on the render thread.
private bool m_VulkanBridge;
// This component's wsui slot (#336 follow-up): -1 = none (every slot taken, edit
// mode, or not acquired yet).
private int m_Slot = -1;
private bool m_SlotApiMissing, m_WarnedNoSlot, m_VulkanCopyBroken;
private float m_NextSlotRetry;
private System.IntPtr m_RenderEventFunc = System.IntPtr.Zero;

// Saved state, restored in OnDisable.
Expand Down Expand Up @@ -245,9 +250,10 @@ void OnEnable()
m_Canvas.worldCamera = m_OverlayCamera;

// ---- Tell native about our texture + initial layer descriptor ----
DisplayXRNative.displayxr_window_space_ui_set_layer(
AcquireSlot();
NativeSetLayer(
positionX, positionY, width, height, disparity);
DisplayXRNative.displayxr_window_space_ui_set_texture(
NativeSetTexture(
OverlayTexture.GetNativeTexturePtr(), m_RtSize.x, m_RtSize.y);

// ---- Windows-only: query the cross-device bridge ----
Expand Down Expand Up @@ -286,8 +292,9 @@ private void TryAcquireBridge()
{
// Custom display-provider mode: the provider owns a SEPARATE
// D3D12 device, so it exposes its own cross-device wsui bridge. (#166)
DisplayXRProviderNative.dxr_prov_get_wsui_bridge(
(uint)m_RtSize.x, (uint)m_RtSize.y,
if (m_Slot < 0) return; // no slot: this layer isn't shown
DisplayXRProviderNative.dxr_prov_get_wsui_bridge_slot(
m_Slot, (uint)m_RtSize.x, (uint)m_RtSize.y,
out bridgePtr, out bw, out bh);
}
if (bridgePtr == System.IntPtr.Zero || bw == 0 || bh == 0)
Expand All @@ -298,7 +305,8 @@ private void TryAcquireBridge()
m_BridgePtr = System.IntPtr.Zero;
return;
}
m_VulkanBridge = SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan;
m_VulkanBridge = !m_VulkanCopyBroken &&
SystemInfo.graphicsDeviceType == GraphicsDeviceType.Vulkan;
if (m_VulkanBridge)
{
// Vulkan (#336): bridgePtr is a session token, never a texture.
Expand All @@ -320,10 +328,10 @@ private void TryAcquireBridge()
// file static and survives, but re-push it anyway so a restart can never
// leave the runtime with a stale/never-seen layer, and invalidate the
// change-detection cache below so LateUpdate re-pushes too.
DisplayXRNative.displayxr_window_space_ui_set_texture(
NativeSetTexture(
OverlayTexture != null ? OverlayTexture.GetNativeTexturePtr() : System.IntPtr.Zero,
m_RtSize.x, m_RtSize.y);
DisplayXRNative.displayxr_window_space_ui_set_layer(
NativeSetLayer(
positionX, positionY, width, height, disparity);
m_LastX = positionX; m_LastY = positionY;
m_LastW = width; m_LastH = height;
Expand All @@ -344,24 +352,94 @@ private void IssueVulkanCopy()
if (m_BridgePtr == System.IntPtr.Zero) return;
try
{
DisplayXRProviderNative.dxr_prov_wsui_request_copy();
if (DisplayXRProviderNative.dxr_prov_wsui_needs_copy() == 0) return;
if (m_Slot < 0) return;
DisplayXRProviderNative.dxr_prov_wsui_request_copy_slot(m_Slot);
if (DisplayXRProviderNative.dxr_prov_wsui_needs_copy_slot(m_Slot) == 0) return;
if (m_RenderEventFunc == System.IntPtr.Zero)
m_RenderEventFunc = DisplayXRProviderNative.dxr_prov_get_render_event_func();
if (m_RenderEventFunc != System.IntPtr.Zero)
GL.IssuePluginEvent(m_RenderEventFunc, DisplayXRProviderNative.kVkOverlayCopyWsuiEvent);
GL.IssuePluginEvent(m_RenderEventFunc,
DisplayXRProviderNative.kVkOverlayCopyWsuiEvent0 + m_Slot);
}
catch (System.EntryPointNotFoundException)
{
// Native plugin older than the managed side: stay inert instead of
// throwing every frame.
Debug.LogWarning("[DisplayXR] wsui: native plugin lacks the Vulkan overlay " +
"exports (#336) — window-space UI disabled on Vulkan.");
m_VulkanCopyBroken = true; // latch: TryAcquireBridge must not re-arm it
m_VulkanBridge = false;
m_BridgePtr = System.IntPtr.Zero;
}
}

// Take a wsui slot for this component. Idempotent. Play mode only: there is no
// session in edit mode (Play Mode is the preview), and an [ExecuteAlways] edit-mode
// or prefab-stage instance holding a slot would starve the play-mode ones.
private bool AcquireSlot()
{
if (m_Slot >= 0) return true;
if (!Application.isPlaying || m_SlotApiMissing) return false;
try
{
m_Slot = DisplayXRNative.displayxr_window_space_ui_acquire_slot();
}
catch (System.EntryPointNotFoundException)
{
// Native plugin older than the managed side (they ship together, so this is
// a stale local build): stay inert rather than half-work.
m_SlotApiMissing = true;
Debug.LogWarning("[DisplayXR] wsui: native plugin lacks the slot API — " +
"window-space UI disabled. Rebuild/refresh the native plugin.");
return false;
}
if (m_Slot < 0 && !m_WarnedNoSlot)
{
m_WarnedNoSlot = true;
Debug.LogWarning($"[DisplayXR] wsui: '{name}' has no free window-space slot — " +
"too many DisplayXRWindowSpaceUI components are enabled at once. " +
"It will appear when another one is disabled.");
}
return m_Slot >= 0;
}

// A component that found every slot taken retries (about once a second), so it
// appears as soon as another HUD frees one instead of staying hidden until it is
// re-enabled.
private void RetryAcquireSlot()
{
if (m_Slot >= 0 || m_SlotApiMissing || !Application.isPlaying) return;
if (Time.unscaledTime < m_NextSlotRetry) return;
m_NextSlotRetry = Time.unscaledTime + 1f;
if (!AcquireSlot()) return;
m_WarnedNoSlot = false;
NativeSetLayer(positionX, positionY, width, height, disparity);
if (OverlayTexture != null)
NativeSetTexture(OverlayTexture.GetNativeTexturePtr(), m_RtSize.x, m_RtSize.y);
m_LastX = positionX; m_LastY = positionY;
m_LastW = width; m_LastH = height;
m_LastDisparity = disparity;
Debug.Log($"[DisplayXR] wsui: '{name}' got slot {m_Slot}");
}

private void ReleaseSlot()
{
if (m_Slot < 0) return;
DisplayXRNative.displayxr_window_space_ui_release_slot(m_Slot);
m_Slot = -1;
}

private void NativeSetTexture(System.IntPtr tex, int w, int h)
{
if (m_Slot < 0) return;
DisplayXRNative.displayxr_window_space_ui_set_texture_slot(m_Slot, tex, w, h);
}

private void NativeSetLayer(float x, float y, float w, float h, float disparity)
{
if (m_Slot < 0) return;
DisplayXRNative.displayxr_window_space_ui_set_layer_slot(m_Slot, x, y, w, h, disparity);
}

private void ReleaseBridgeTex()
{
if (m_BridgeTex != null)
Expand All @@ -374,7 +452,7 @@ private void ReleaseBridgeTex()

void OnDisable()
{
DisplayXRNative.displayxr_window_space_ui_clear();
ReleaseSlot();

// Restore the canvas's original mode + transform + layer.
if (m_StateSaved && m_Canvas != null)
Expand Down Expand Up @@ -410,6 +488,7 @@ void OnDisable()

void LateUpdate()
{
RetryAcquireSlot();
// Keep EVERYTHING under the canvas on the private layer, every frame (#289).
// OnEnable parks the hierarchy on kPrivateLayer once, but the overlay
// camera culls to that layer alone — so anything Instantiated under the
Expand Down Expand Up @@ -516,7 +595,7 @@ void LateUpdate()
width != m_LastW || height != m_LastH ||
disparity != m_LastDisparity)
{
DisplayXRNative.displayxr_window_space_ui_set_layer(
NativeSetLayer(
positionX, positionY, width, height, disparity);
m_LastX = positionX; m_LastY = positionY;
m_LastW = width; m_LastH = height;
Expand Down Expand Up @@ -687,7 +766,7 @@ private void ResizeOverlayTexture(Vector2Int size)
m_OverlayCamera.targetTexture = OverlayTexture;
if (m_CanvasRect != null)
m_CanvasRect.sizeDelta = size;
DisplayXRNative.displayxr_window_space_ui_set_texture(
NativeSetTexture(
OverlayTexture.GetNativeTexturePtr(), size.x, size.y);
ReleaseBridgeTex();
TryAcquireBridge();
Expand Down
20 changes: 13 additions & 7 deletions Runtime/Provider/DisplayXRProviderNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,17 +341,23 @@ public static extern void dxr_prov_get_local2d_bridge(
/// Keep in sync with DXR_EVENT_VK_OVERLAY_COPY_LOCAL2D (displayxr_unity_plugin.cpp).</summary>
public const int kVkOverlayCopyLocal2DEvent = 0x44585201;

/// <summary>(#336) wsui twin of <see cref="dxr_prov_local2d_needs_copy"/>.</summary>
/// <summary>(#336) wsui twin of <see cref="dxr_prov_local2d_needs_copy"/>, per slot.</summary>
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern int dxr_prov_wsui_needs_copy();
public static extern int dxr_prov_wsui_needs_copy_slot(int slot);

/// <summary>(#336) wsui twin of <see cref="dxr_prov_local2d_request_copy"/>.</summary>
/// <summary>(#336) wsui twin of <see cref="dxr_prov_local2d_request_copy"/>, per slot.</summary>
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern void dxr_prov_wsui_request_copy();
public static extern void dxr_prov_wsui_request_copy_slot(int slot);

/// <summary>Event id: copy the wsui canvas RT into its Vulkan overlay bridge.
/// Keep in sync with DXR_EVENT_VK_OVERLAY_COPY_WSUI (displayxr_unity_plugin.cpp).</summary>
public const int kVkOverlayCopyWsuiEvent = 0x44585202;
/// <summary>Event id for wsui slot 0; slot N is this + N. Keep in sync with
/// DXR_EVENT_VK_OVERLAY_COPY_WSUI0 (displayxr_unity_plugin.cpp).</summary>
public const int kVkOverlayCopyWsuiEvent0 = 0x44585210;

/// <summary>Per-slot form of <see cref="dxr_prov_get_wsui_bridge"/>.</summary>
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern void dxr_prov_get_wsui_bridge_slot(
int slot, uint width, uint height,
out System.IntPtr nativePtr, out uint outWidth, out uint outHeight);

/// <summary>
/// Per-eye foreground-clip data (#166 Phase B): the eye's foreground far
Expand Down
23 changes: 13 additions & 10 deletions native~/displayxr_unity_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// Unity's own device.

#include "displayxr_unity_plugin.h"
#include "displayxr_window_space_ui.h" // DXR_WSUI_MAX_SLOTS, get_pending_slot

#include <stdio.h>

Expand Down Expand Up @@ -59,15 +60,14 @@ static bool s_vk_captured = false;
//
// Keep the ids in sync with DisplayXRProviderNative.kVkOverlayCopy*Event.
#define DXR_EVENT_VK_OVERLAY_COPY_LOCAL2D 0x44585201
#define DXR_EVENT_VK_OVERLAY_COPY_WSUI 0x44585202
// wsui slot N is DXR_EVENT_VK_OVERLAY_COPY_WSUI0 + N (N < DXR_WSUI_MAX_SLOTS).
#define DXR_EVENT_VK_OVERLAY_COPY_WSUI0 0x44585210

extern "C" int displayxr_local2d_get_pending(void **out_tex, int *out_w, int *out_h);
extern "C" int displayxr_window_space_ui_get_pending(void **out_tex, int *out_tex_w, int *out_tex_h,
float *out_x, float *out_y,
float *out_lw, float *out_lh, float *out_disp);
extern "C" int dxr_pvk_overlay_ready(int kind);
extern "C" int dxr_pvk_overlay_record_unity_copy(int kind, void *cmd_buf, void *src_image,
int64_t src_format, uint32_t src_w, uint32_t src_h);
int64_t src_format, uint32_t src_w, uint32_t src_h,
void *src_id);

static void configure_vulkan_events(void)
{
Expand All @@ -79,7 +79,8 @@ static void configure_vulkan_events(void)
cfg.graphicsQueueAccess = kUnityVulkanGraphicsQueueAccess_DontCare;
cfg.flags = kUnityVulkanEventConfigFlag_EnsurePreviousFrameSubmission;
s_unity_vk->ConfigureEvent(DXR_EVENT_VK_OVERLAY_COPY_LOCAL2D, &cfg);
s_unity_vk->ConfigureEvent(DXR_EVENT_VK_OVERLAY_COPY_WSUI, &cfg);
for (int i = 0; i < DXR_WSUI_MAX_SLOTS; i++)
s_unity_vk->ConfigureEvent(DXR_EVENT_VK_OVERLAY_COPY_WSUI0 + i, &cfg);
}

static void vk_overlay_copy(int kind, void *tex, int w, int h)
Expand All @@ -106,7 +107,7 @@ static void vk_overlay_copy(int kind, void *tex, int w, int h)
uint32_t sw = img.extent.width ? img.extent.width : (uint32_t)w;
uint32_t sh = img.extent.height ? img.extent.height : (uint32_t)h;
dxr_pvk_overlay_record_unity_copy(kind, (void *)st.commandBuffer, (void *)img.image,
(int64_t)img.format, sw, sh);
(int64_t)img.format, sw, sh, tex);
}
#endif

Expand All @@ -117,11 +118,13 @@ static void UNITY_INTERFACE_API on_render_event(int event_id)
if (event_id == DXR_EVENT_VK_OVERLAY_COPY_LOCAL2D) {
void *tex = NULL; int w = 0, h = 0;
if (displayxr_local2d_get_pending(&tex, &w, &h)) vk_overlay_copy(0 /* LOCAL2D */, tex, w, h);
} else if (event_id == DXR_EVENT_VK_OVERLAY_COPY_WSUI) {
} else if (event_id >= DXR_EVENT_VK_OVERLAY_COPY_WSUI0 &&
event_id < DXR_EVENT_VK_OVERLAY_COPY_WSUI0 + DXR_WSUI_MAX_SLOTS) {
int slot = event_id - DXR_EVENT_VK_OVERLAY_COPY_WSUI0;
void *tex = NULL; int w = 0, h = 0;
float x, y, lw, lh, disp;
if (displayxr_window_space_ui_get_pending(&tex, &w, &h, &x, &y, &lw, &lh, &disp))
vk_overlay_copy(1 /* WSUI */, tex, w, h);
if (displayxr_window_space_ui_get_pending_slot(slot, &tex, &w, &h, &x, &y, &lw, &lh, &disp))
vk_overlay_copy(1 + slot /* DXR_PVK_OVERLAY_WSUI0 + slot */, tex, w, h);
}
#else
(void)event_id;
Expand Down
Loading
Loading