#if XR_COMPOSITION_LAYERS using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.InteropServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.XR.CompositionLayers.Extensions; using Unity.XR.CompositionLayers.Layers; using Unity.XR.CompositionLayers.Services; using UnityEngine.XR.OpenXR.NativeTypes; #if UNITY_VIDEO using UnityEngine.Video; #endif namespace UnityEngine.XR.OpenXR.CompositionLayers { /// /// Provides a base implementation for the interface. /// You can implement the required methods of this abstract class to create a concrete layer handler. /// /// /// The methods that this class implements handle adding /// and removing composition layers from native arrays, swap chain creation dispatching and other tasks /// required by the Unity side of the API. /// /// The abstract methods that you must implement handle the custom aspects of your layer. These methods include: /// /// * /// * /// * /// /// You are not required to implement a custom layer handler based on this abstract class, but doing so should be /// easier than implementing the interface in its entirety. /// /// You must register your concrete layer handler object with /// . /// /// The native OpenXR structure of the composition layer to handle. public abstract class OpenXRCustomLayerHandler : OpenXRLayerProvider.ILayerHandler, IDisposable where T : struct { /// /// Container for swapchain related information that may be needed during the creation of the native OpenXR composition layer struct. /// protected struct SwapchainCreateInfo { /// /// Native structure for the swapchain creation info. /// public XrSwapchainCreateInfo nativeStruct; /// /// Tells if swapchain is using an external surface. /// [MarshalAs(UnmanagedType.I1)] public bool isExternalSurface; /// /// Tells if swapchain should be stereo. /// public bool isStereo; /// /// Initializes and returns an instance of SwapchainCreateInfo with the provided parameters. /// /// Native structure for the swapchain creation info. /// Tells if swapchain is using an external surface. /// Tells if swapchain should be stereo. public SwapchainCreateInfo(XrSwapchainCreateInfo xrSwapchainCreateInfo, bool isExternalSurface = false, bool isStereo = false) { this.nativeStruct = xrSwapchainCreateInfo; this.isExternalSurface = isExternalSurface; this.isStereo = isStereo; } /// /// Implicit conversion with just a native XrSwapchainCreateInfo struct. /// /// The native struct to convert. public static implicit operator SwapchainCreateInfo(XrSwapchainCreateInfo createInfo) => new SwapchainCreateInfo(createInfo); } /// /// Container for swapchain related information that may be needed during the creation of the native OpenXR composition layer struct. /// protected struct SwapchainCreatedOutput { /// /// The handle of the created swapchain. /// Can be used to initialize the swapchain member of a native OpenXR composition layer struct. /// public ulong handle; /// /// The second handle of the created stereo swapchain. /// Can be used to initialize the swapchain member of a native OpenXR composition layer struct. /// public ulong secondStereoHandle; } /// /// Container for grouping render information for each compostion layer. /// class LayerRenderInfo { public RenderTexture RenderTexture; public Texture Texture; #if UNITY_VIDEO public VideoPlayer videoPlayer; #endif public MeshCollider meshCollider; } /// /// Initializes and returns an instance of this OpenXRCustomLayerHandler<T> while also setting the singleton instance member. /// protected OpenXRCustomLayerHandler() => Instance = this; /// /// Singleton instance of this specific handler. /// protected static OpenXRCustomLayerHandler Instance; /// /// Deinitializes this instance of c>OpenXRCustomLayerHandler<T>. /// ~OpenXRCustomLayerHandler() => Dispose(false); /// /// Override this method to create the struct that is passed to OpenXR /// to create a swapchain. /// /// /// To add extensions when constructing the struct, initialize /// the Next pointer with /// . /// /// /// Constructs an XrSwapchainCreateInfo struct with some members using component data. /// /// (); /// unsafe /// { /// swapchainCreateInfo = new XrSwapchainCreateInfo() /// { /// Type = (uint)XrStructureType.XR_TYPE_SWAPCHAIN_CREATE_INFO, /// Next = OpenXRLayerUtility.GetExtensionsChain(layerInfo, CompositionLayerExtension.ExtensionTarget.Swapchain), /// CreateFlags = 0, /// UsageFlags = (ulong)(XrSwapchainUsageFlags.XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XrSwapchainUsageFlags.XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT), /// Format = OpenXRLayerUtility.GetDefaultColorFormat(), /// SampleCount = 1, /// Width = (uint)texturesExtension.LeftTexture.width, /// Height = (uint)texturesExtension.LeftTexture.height, /// FaceCount = 1, /// ArraySize = 1, /// MipCount = (uint)texturesExtension.LeftTexture.mipmapCount, /// }; /// } /// return true; /// } /// ]]> /// /// /// Container for the instance id and CompositionLayer component of the composition layer /// that was just created. /// An XrSwapchainCreateInfo object created and initialized by the concrete implementation of this method. /// A bool indicating success or failure. protected abstract bool CreateSwapchain(CompositionLayerManager.LayerInfo layerInfo, out SwapchainCreateInfo swapchainCreateInfo); /// /// Override this method to create the native composition layer struct of type T that is passed to OpenXR. /// A swapchain info struct is provided so your layer handler has access to any needed swapchain information. /// /// /// To add extensions when constructing the struct, initialize /// the Next pointer with . /// /// If your struct needs any XrSpace relative info you can use /// to get the current app space. /// /// /// Constructs an XrCompositionLayerQuad struct with some members using component data. /// /// (); /// var transform = layerInfo.Layer.GetComponent(); /// /// unsafe /// { /// var data = layerInfo.Layer.LayerData as QuadLayerData; /// /// nativeLayer = new XrCompositionLayerQuad() /// { /// Type = 36, /// Next = OpenXRLayerUtility.GetExtensionsChain(layerInfo, CompositionLayerExtension.ExtensionTarget.Layer), /// LayerFlags = 0x00000002, /// Space = OpenXRLayerUtility.GetCurrentAppSpace(), /// EyeVisibility = 0, /// SubImage = new SwapchainSubImage() /// { /// Swapchain = swapchainOutput.handle, /// ImageRect = new XrRect2Di() /// { /// offset = new XrOffset2Di() { x = 0, y = 0 }, /// extent = new XrExtent2Di() /// { /// width = texturesExtension.LeftTexture.width, /// height = texturesExtension.LeftTexture.height /// } /// }, /// ImageArrayIndex = 0 /// }, /// Pose = new XrPosef(transform.position, transform.rotation), /// Size = new XrExtend2Df() /// { /// width = data.GetScaledSize(transform.lossyScale).x, /// height = data.GetScaledSize(transform.lossyScale).y /// } /// }; /// } /// return true; /// } /// ]]> /// /// /// Container for the instance id and CompositionLayer component of the composition layer /// that was just created. /// Information regarding the swapchain that was created for this layer, /// such as the associated swapchain handle. /// An object of type T that is created and initialized by the concrete implementation of this method. /// A bool indicating success or failure. protected abstract bool CreateNativeLayer(CompositionLayerManager.LayerInfo layerInfo, SwapchainCreatedOutput swapchainOutput, out T nativeLayer); /// /// Override this method to modify a native composition layer struct in response to changes on the associated /// object or any extension components on the /// GameObject. /// /// /// You must reinitialize the Next pointer with /// to get any potential updates from extension components. /// /// /// Modifies an XrCompositionLayerQuad struct with new transform and extension information. /// /// (); /// /// nativeLayer.Pose = new XrPosef(transform.position, transform.rotation); /// nativeLayer.Size = new XrExtend2Df() /// { /// width = data.GetScaledSize(transform.lossyScale).x, /// height = data.GetScaledSize(transform.lossyScale).y /// }; /// /// unsafe /// { /// nativeLayer.Next = OpenXRLayerUtility.GetExtensionsChain(layerInfo, CompositionLayerExtension.ExtensionTarget.Layer); /// } /// return true; /// } /// ]]> /// /// /// Container for the instance id and CompositionLayer component of the composition /// layer that was modified. /// A reference to the native OpenXR structure of the composition layer that was modified. /// The concrete implementation of this method should update the values of the structure as appropriate. /// A bool indicating success or failure. protected abstract bool ModifyNativeLayer(CompositionLayerManager.LayerInfo layerInfo, ref T nativeLayer); /// /// Mapping of instance ids and native layer structs to help determine what layers are currently set to be active. /// protected Dictionary m_nativeLayers = new Dictionary(); /// /// Thread safe queue used to dispatch callbacks that may come from other threads such as the swapchain creation /// on the graphics thread. /// protected ConcurrentQueue actionsForMainThread = new ConcurrentQueue(); Dictionary m_renderInfos = new Dictionary(); Dictionary m_layerInfos = new Dictionary(); NativeArray m_ActiveNativeLayers; NativeArray m_ActiveNativeLayerOrders; int m_ActiveNativeLayerCount; /// /// Implements the method that is called by the /// during the Unity update loop. /// /// /// This implementation carries out two tasks. It dequeues actions for the main thread like dispatch when /// the swapchain has been /// created and it adds all the active layers to the endFrameInfo struct in the native UnityOpenXR lib. /// public virtual void OnUpdate() { while (actionsForMainThread.Count > 0) { if (actionsForMainThread.TryDequeue(out Action action)) action(); } unsafe { if (m_ActiveNativeLayerCount > 0) OpenXRLayerUtility.AddActiveLayersToEndFrame(m_ActiveNativeLayers.GetUnsafePtr(), m_ActiveNativeLayerOrders.GetUnsafePtr(), m_ActiveNativeLayerCount, UnsafeUtility.SizeOf()); } m_ActiveNativeLayerCount = 0; } /// /// Implements the method that is called by the /// when a new layer has been created. /// This implementation triggers the creation of a swapchain before the actual native layer struct is created. /// /// Container for the instance id and CompositionLayer component of the composition layer /// being created. public void CreateLayer(CompositionLayerManager.LayerInfo layerInfo) { CreateSwapchainAsync(layerInfo); } /// /// Implements the method that is called by the /// when a layer or attached extension has been modified. /// This implementation asks the subclass for any changes that must be made to the layer via /// /// by sending a reference to the native layer struct. /// /// Container for the instance id and CompositionLayer component of the composition layer /// that was modified. public virtual void ModifyLayer(CompositionLayerManager.LayerInfo layerInfo) { var texturesExtension = layerInfo.Layer.GetComponent(); if (!m_nativeLayers.TryGetValue(layerInfo.Id, out var nativeLayer)) { if (texturesExtension != null && texturesExtension.TextureAdded) { texturesExtension.TextureAdded = false; CreateLayer(layerInfo); } return; } var success = ModifyNativeLayer(layerInfo, ref nativeLayer); if (success) m_nativeLayers[layerInfo.Id] = nativeLayer; } /// /// Implements the method that is called by the /// when a layer is destroyed or disabled. /// /// The instance id of the CompositionLayer component that was removed. public virtual void RemoveLayer(int removedLayerId) { OpenXRLayerUtility.ReleaseSwapchain(removedLayerId); m_nativeLayers.Remove(removedLayerId); m_layerInfos.Remove(removedLayerId); m_renderInfos.Remove(removedLayerId); } /// /// Implements the method that is called by the /// when a layer is considered to be currently active. /// /// Container for the instance id and CompositionLayer component of the composition layer /// being set to active. public virtual void SetActiveLayer(CompositionLayerManager.LayerInfo layerInfo) { if (!m_nativeLayers.TryGetValue(layerInfo.Id, out var nativeLayer)) return; var success = ActiveNativeLayer(layerInfo, ref nativeLayer); if (!success) return; m_nativeLayers[layerInfo.Id] = nativeLayer; ResizeNativeArrays(); m_ActiveNativeLayers[m_ActiveNativeLayerCount] = m_nativeLayers[layerInfo.Id]; m_ActiveNativeLayerOrders[m_ActiveNativeLayerCount] = layerInfo.Layer.Order; ++m_ActiveNativeLayerCount; } /// /// Implements method from that is called by the /// when this custom layer handler instance is disposed. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Clears all maps and disposes any created native arrays. /// /// Determines if this method was called from the Dispose() method or the finalizer. protected virtual void Dispose(bool disposing) { if (disposing) { m_layerInfos.Clear(); m_nativeLayers.Clear(); m_renderInfos.Clear(); } if (m_ActiveNativeLayers.IsCreated) m_ActiveNativeLayers.Dispose(); if (m_ActiveNativeLayerOrders.IsCreated) m_ActiveNativeLayerOrders.Dispose(); } /// /// Calls to create a /// struct that is then passed to the /// UnityOpenXR lib to actually create the swapchain on the graphics thread. /// The static method is passed as a callback and invoked when /// the swapchain has been created. /// /// Container for the instance id and CompositionLayer component of the composition layer /// that was just created. protected virtual void CreateSwapchainAsync(CompositionLayerManager.LayerInfo layerInfo) { m_layerInfos[layerInfo.Id] = layerInfo; var success = CreateSwapchain(layerInfo, out var swapChainInfo); if (!success) return; if (swapChainInfo.isStereo) OpenXRLayerUtility.CreateStereoSwapchain(layerInfo.Id, swapChainInfo.nativeStruct, OnCreatedStereoSwapchainCallback); else OpenXRLayerUtility.CreateSwapchain(layerInfo.Id, swapChainInfo.nativeStruct, swapChainInfo.isExternalSurface, OnCreatedSwapchainCallback); } /// /// This method is dispatched to the main thread inside /// and asks this subclass to create the native layer struct by invoking /// . /// /// Container for the instance id and CompositionLayer component of the composition layer /// that was just created. /// Information regarding the swapchain that was created for this layer, such as /// the associated swapchain handle. protected virtual void OnCreatedSwapchain(CompositionLayerManager.LayerInfo layerInfo, SwapchainCreatedOutput swapchainOutput) { var success = CreateNativeLayer(layerInfo, swapchainOutput, out var nativeLayer); if (success) m_nativeLayers[layerInfo.Id] = nativeLayer; } /// /// Ensures that the native arrays are of the same size as the m_nativeLayers map. /// protected virtual void ResizeNativeArrays() { if (!m_ActiveNativeLayers.IsCreated && !m_ActiveNativeLayerOrders.IsCreated) { m_ActiveNativeLayers = new NativeArray(m_nativeLayers.Count, Allocator.Persistent); m_ActiveNativeLayerOrders = new NativeArray(m_nativeLayers.Count, Allocator.Persistent); return; } Assertions.Assert.AreEqual(m_ActiveNativeLayers.Length, m_ActiveNativeLayerOrders.Length); if (m_ActiveNativeLayers.Length < m_nativeLayers.Count) { var newLayerArray = new NativeArray(m_nativeLayers.Count, Allocator.Persistent); NativeArray.Copy(m_ActiveNativeLayers, newLayerArray, m_ActiveNativeLayers.Length); m_ActiveNativeLayers.Dispose(); m_ActiveNativeLayers = newLayerArray; var newOrderArray = new NativeArray(m_nativeLayers.Count, Allocator.Persistent); NativeArray.Copy(m_ActiveNativeLayerOrders, newOrderArray, m_ActiveNativeLayerOrders.Length); m_ActiveNativeLayerOrders.Dispose(); m_ActiveNativeLayerOrders = newOrderArray; } } /// /// Override this method to modify a native composition layer struct in response to when it is active. /// An active compositon layer will invoke this every frame. /// /// Container for the instance id and CompositionLayer component of the composition /// layer that is active. /// A reference to the native OpenXR structure of the composition layer that is active. /// Bool indicating success or failure. A failure case will result in the native composition layer struct not being added into the final XrFrameEndInfo struct. protected virtual bool ActiveNativeLayer(CompositionLayerManager.LayerInfo layerInfo, ref T nativeLayer) { var texturesExtension = layerInfo.Layer.GetComponent(); if (texturesExtension == null || texturesExtension.LeftTexture == null || texturesExtension.sourceTexture == TexturesExtension.SourceTextureEnum.AndroidSurface) return true; if (m_renderInfos.TryGetValue(layerInfo.Id, out var container)) { bool isNewTexture = container.Texture != texturesExtension.LeftTexture; if (isNewTexture) { // If we have a new texture with different dimensions then we need to release the current swapchain and create another. // This is an async procedure that also creates a new native layer object. if (container.Texture.width != texturesExtension.LeftTexture.width || container.Texture.height != texturesExtension.LeftTexture.height) { RemoveLayer(layerInfo.Id); CreateSwapchainAsync(layerInfo); return false; } else container.Texture = texturesExtension.LeftTexture; #if UNITY_VIDEO container.videoPlayer = layerInfo.Layer.GetComponent(); #endif container.meshCollider = layerInfo.Layer.GetComponent(); } bool isVideo = false; #if UNITY_VIDEO isVideo = container.videoPlayer != null && container.videoPlayer.enabled; #endif bool isUI = container.meshCollider != null && container.meshCollider.enabled; #if UNITY_EDITOR // Layers with a video or ui component in editor may have multiple native render textures associated with the layer id so we must find them. if (isVideo || isUI) OpenXRLayerUtility.FindAndWriteToRenderTexture(layerInfo, container.Texture, out container.RenderTexture); else if (isNewTexture) OpenXRLayerUtility.WriteToRenderTexture(container.Texture, container.RenderTexture); #else // We only need to write continuously to the native render texture if our texture is changing. if (isVideo || isUI || isNewTexture) OpenXRLayerUtility.WriteToRenderTexture(container.Texture, container.RenderTexture); #endif } else { bool isRenderTextureWritten = OpenXRLayerUtility.FindAndWriteToRenderTexture(layerInfo, texturesExtension.LeftTexture, out RenderTexture renderTexture); if (isRenderTextureWritten) { var layerRenderInfo = new LayerRenderInfo() { Texture = texturesExtension.LeftTexture, RenderTexture = renderTexture, #if UNITY_VIDEO videoPlayer = layerInfo.Layer.GetComponent(), #endif meshCollider = layerInfo.Layer.GetComponent() }; m_renderInfos.Add(layerInfo.Id, layerRenderInfo); }; } return true; } [AOT.MonoPInvokeCallback(typeof(OpenXRLayerUtility.SwapchainCallbackDelegate))] static void OnCreatedSwapchainCallback(int layerId, ulong swapchainHandle) { if (Instance == null) return; Instance.actionsForMainThread.Enqueue(() => { Instance.OnCreatedSwapchain(Instance.m_layerInfos[layerId], new SwapchainCreatedOutput { handle = swapchainHandle });}); } [AOT.MonoPInvokeCallback(typeof(OpenXRLayerUtility.StereoSwapchainCallbackDelegate))] static void OnCreatedStereoSwapchainCallback(int layerId, ulong swapchainHandleLeft, ulong swapchainHandleRight) { if (Instance == null) return; Instance.actionsForMainThread.Enqueue(() => { Instance.OnCreatedSwapchain(Instance.m_layerInfos[layerId], new SwapchainCreatedOutput { handle = swapchainHandleLeft, secondStereoHandle = swapchainHandleRight}); }); } } } #endif