// Copyright HTC Corporation All Rights Reserved. using AOT; using System; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; #if UNITY_EDITOR using UnityEditor; using UnityEditor.XR.OpenXR.Features; #endif namespace VIVE.OpenXR.SecondaryViewConfiguration { /// /// Name: SecondaryViewConfiguration.cs /// Role: OpenXR SecondaryViewConfiguration Extension Class /// Responsibility: The OpenXR extension implementation and its lifecycles logic in OpenXR /// #if UNITY_EDITOR [OpenXRFeature(UiName = "VIVE XR Spectator Camera (Beta)", BuildTargetGroups = new[] { BuildTargetGroup.Android }, Company = "HTC", Desc = "Allows an application to enable support for one or more secondary view configurations.", DocumentationLink = "..\\Documentation", OpenxrExtensionStrings = OPEN_XR_EXTENSION_STRING, Version = "1.0.0", FeatureId = FeatureId)] #endif public partial class ViveSecondaryViewConfiguration : OpenXRFeature { #region Varibles private static ViveSecondaryViewConfiguration _instance; /// /// ViveSecondaryViewConfiguration static instance (Singleton). /// public static ViveSecondaryViewConfiguration Instance { get { if (_instance == null) { _instance = OpenXRSettings.Instance.GetFeature(); } return _instance; } } #region OpenXR variables related to definition /// /// The log identification. /// private const string LogTag = "VIVE.OpenXR.SecondaryViewConfiguration"; /// /// The feature id string. This is used to give the feature a well known id for reference. /// public const string FeatureId = "vive.openxr.feature.secondaryviewconfiguration"; /// /// OpenXR specification 12.122. XR_MSFT_secondary_view_configuration. /// public const string OPEN_XR_EXTENSION_STRING = "XR_MSFT_secondary_view_configuration"; /// /// The extension library name. /// private const string ExtLib = "libviveopenxr"; #endregion #region OpenXR variables related to its life-cycle /// /// The flag represents whether the OpenXR loader created an instance or not. /// private bool XrInstanceCreated { get; set; } = false; /// /// The flag represents whether the OpenXR loader created a session or not. /// private bool XrSessionCreated { get; set; } = false; /// /// The flag represents whether the OpenXR loader started a session or not. /// private bool XrSessionStarted { get; set; } = false; /// /// The instance created through xrCreateInstance. /// private XrInstance XrInstance { get; set; } = 0; /// /// An XrSystemId is an opaque atom used by the runtime to identify a system. /// private XrSystemId XrSystemId { get; set; } = 0; /// /// A session represents an application’s intention to display XR content to the user. /// private XrSession XrSession { get; set; } = 0; /// /// New possible session lifecycle states. /// private XrSessionState XrSessionNewState { get; set; } = XrSessionState.XR_SESSION_STATE_UNKNOWN; /// /// The previous state possible session lifecycle states. /// private XrSessionState XrSessionOldState { get; set; } = XrSessionState.XR_SESSION_STATE_UNKNOWN; /// /// The function delegate declaration of xrGetInstanceProcAddr. /// private OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr { get; set; } #endregion #region Variables related to handle agent functions /// /// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod". /// private delegate void SetSecondaryViewConfigurationStateDelegate(bool isEnabled); /// /// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod". /// private delegate void StopEnableSecondaryViewConfigurationDelegate(bool isStopped); /// /// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod". /// private delegate void SetTextureSizeDelegate(UInt32 width, UInt32 height); /// /// A delegate declaration can encapsulate the method that takes a boolean argument and returns void. This declaration should only be used in the function "SecondaryViewConfigurationInterceptOpenXRMethod". /// private delegate void SetFovDelegate(XrFovf fov); #endregion #region Variables related to callback functions instantiation /// /// A delegate declaration can encapsulate the method that takes a Vector2 argument and returns void. /// public delegate void OnTextureSizeUpdatedDelegate(Vector2 size); /// /// The instantiation of the delegate OnTextureSizeUpdatedDelegate. This will be called when the texture size coming from the native plugin is updated. /// public OnTextureSizeUpdatedDelegate onTextureSizeUpdated; /// /// A delegate declaration can encapsulate the method that takes no argument and returns void. /// public delegate void OnTextureUpdatedDelegate(); /// /// The instantiation of the delegate OnTextureUpdatedDelegate. This will be called when the texture coming from the native plugin is updated. /// public OnTextureUpdatedDelegate onTextureUpdated; /// /// A delegate declaration can encapsulate the method that takes four floating-point arguments, left, right, up, and down, respectively, and returns void. /// public delegate void OnFovUpdatedDelegate(float left, float right, float up, float down); /// /// The instantiation of the delegate OnFovUpdatedDelegate. This will be called when the fov setting coming from the native plugin is updated. /// public OnFovUpdatedDelegate onFovUpdated; #endregion #region Rendering and texture varibles /// /// The graphics backend that the current application is used. /// private static GraphicsAPI MyGraphicsAPI { get { return SystemInfo.graphicsDeviceType switch { GraphicsDeviceType.OpenGLES3 => GraphicsAPI.GLES3, GraphicsDeviceType.Vulkan => GraphicsAPI.Vulkan, _ => GraphicsAPI.Unknown }; } } private Vector2 _textureSize; /// /// The value of texture size coming from the native plugin. /// public Vector2 TextureSize { get => _textureSize; private set { _textureSize = value; onTextureSizeUpdated?.Invoke(value); } } private Texture _myTexture; /// /// The texture handler adopts the native 2D texture object coming from the native plugin. /// public Texture MyTexture { get => _myTexture; private set { _myTexture = value; onTextureUpdated?.Invoke(); } } #endregion #region The variables (flag) represent the state related to this OpenXR extension /// /// The state of the second view configuration comes from runtime. /// public bool IsEnabled { get; set; } /// /// The flag represents to whether the second view configuration is disabled or not. /// public bool IsStopped { get; set; } /// /// The flag represents to whether the "SpectatorCameraBased" script exists in the Unity scene. /// private bool IsExistSpectatorCameraBased { get; set; } #endregion #endregion #region Function #region OpenXR life-cycle functions /// /// Called after xrCreateInstance. /// /// Handle of the xrInstance. /// Returns true if successful. Returns false otherwise. protected override bool OnInstanceCreate(ulong xrInstance) { if (!IsExtensionEnabled()) { Warning("OnInstanceCreate() " + OPEN_XR_EXTENSION_STRING + " is NOT enabled."); return false; } var mySpectatorCameraBasedGameObject = GetSpectatorCameraBased(); IsExistSpectatorCameraBased = mySpectatorCameraBasedGameObject != null; XrInstanceCreated = true; XrInstance = xrInstance; Debug("OnInstanceCreate() " + XrInstance); if (!GetXrFunctionDelegates(XrInstance)) { Error("Get function pointer of OpenXRFunctionPointerAccessor failed."); return false; } Debug("Get function pointer of OpenXRFunctionPointerAccessor succeed."); return base.OnInstanceCreate(xrInstance); } /// /// Called after xrGetSystem /// /// Handle of the xrSystemId protected override void OnSystemChange(ulong xrSystem) { XrSystemId = xrSystem; Debug("OnSystemChange() " + XrSystemId); base.OnSystemChange(xrSystem); } /// /// Called after xrCreateSession. /// /// Handle of the xrSession. protected override void OnSessionCreate(ulong xrSession) { XrSessionCreated = true; XrSession = xrSession; Debug("OnSessionCreate() " + XrSession); base.OnSessionCreate(xrSession); } /// /// Called after xrSessionBegin. /// /// Handle of the xrSession. protected override void OnSessionBegin(ulong xrSession) { XrSessionStarted = true; Debug("OnSessionBegin() " + XrSessionStarted); base.OnSessionBegin(xrSession); } /// /// Called when the OpenXR loader receives the XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED event from the runtime signaling that the XrSessionState has changed. /// /// Previous state. /// New state. protected override void OnSessionStateChange(int oldState, int newState) { Debug("OnSessionStateChange() oldState: " + oldState + " newState:" + newState); if (Enum.IsDefined(typeof(XrSessionState), oldState)) { XrSessionOldState = (XrSessionState)oldState; } else { Warning("OnSessionStateChange() oldState undefined"); } if (Enum.IsDefined(typeof(XrSessionState), newState)) { XrSessionNewState = (XrSessionState)newState; } else { Warning("OnSessionStateChange() newState undefined"); } base.OnSessionStateChange(oldState, newState); } /// /// Called to hook xrGetInstanceProcAddr. Returning a different function pointer allows intercepting any OpenXR method. /// /// xrGetInstanceProcAddr native function pointer. /// Function pointer that Unity will use to look up OpenXR native functions. protected override IntPtr HookGetInstanceProcAddr(IntPtr func) { Debug("HookGetInstanceProcAddr Start"); if (MyGraphicsAPI is GraphicsAPI.GLES3 or GraphicsAPI.Vulkan) { Debug($"The app graphics API is {MyGraphicsAPI}"); return SecondaryViewConfigurationInterceptOpenXRMethod ( MyGraphicsAPI, SystemInfo.graphicsUVStartsAtTop, func, SetSecondaryViewConfigurationState, StopEnableSecondaryViewConfiguration, SetTextureSize, SetFov ); } Error( "The render backend is not supported. Requires OpenGL or Vulkan backend for secondary view configuration feature."); return base.HookGetInstanceProcAddr(func); } /// /// Called before xrEndSession. /// /// Handle of the xrSession. protected override void OnSessionEnd(ulong xrSession) { XrSessionStarted = false; Debug("OnSessionEnd() " + XrSession); base.OnSessionEnd(xrSession); } /// /// Called before xrDestroySession. /// /// Handle of the xrSession. protected override void OnSessionDestroy(ulong xrSession) { XrSessionCreated = false; Debug("OnSessionDestroy() " + xrSession); base.OnSessionDestroy(xrSession); } /// /// Called before xrDestroyInstance. /// /// Handle of the xrInstance. protected override void OnInstanceDestroy(ulong xrInstance) { XrInstanceCreated = false; XrInstance = 0; Debug("OnInstanceDestroy() " + xrInstance); base.OnInstanceDestroy(xrInstance); } #endregion #region Handle agent functions /// /// This function is defined as the "SetSecondaryViewConfigurationStateDelegate" delegate function. /// Please be careful that this function should ONLY be called by native plug-ins. /// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE. /// /// The state of the second view configuration comes from runtime. True if enabled. False otherwise. [MonoPInvokeCallback(typeof(SetSecondaryViewConfigurationStateDelegate))] private static void SetSecondaryViewConfigurationState(bool isEnabled) { Instance.IsEnabled = isEnabled; if (Instance.IsEnableDebugLog) { Debug($"SetSecondaryViewConfigurationState: Instance.IsEnabled set as {Instance.IsEnabled}"); } } /// /// This function is defined as the "StopEnableSecondaryViewConfigurationDelegate" delegate function. /// Please be careful that this function should ONLY be called by native plug-ins. /// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE. /// /// The flag refers to whether the second view configuration is disabled or not. True if the second view configuration is disabled. False otherwise. [MonoPInvokeCallback(typeof(StopEnableSecondaryViewConfigurationDelegate))] private static void StopEnableSecondaryViewConfiguration(bool isStopped) { Instance.IsStopped = isStopped; if (Instance.IsEnableDebugLog) { Debug($"StopEnableSecondaryViewConfiguration: Instance.IsStopped set as {Instance.IsStopped}"); } } /// /// This function is defined as the "SetTextureSizeDelegate" delegate function. /// Please be careful that this function should ONLY be called by native plug-ins. /// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE. /// /// The texture width comes from runtime. /// The texture height comes from runtime. [MonoPInvokeCallback(typeof(SetTextureSizeDelegate))] private static void SetTextureSize(uint width, uint height) { if (!Instance.IsExistSpectatorCameraBased) { CreateSpectatorCameraBased(); } Instance.TextureSize = new Vector2(width, height); if (Instance.IsEnableDebugLog) { Debug($"SetTextureSize width: {Instance.TextureSize.x}, height: {Instance.TextureSize.y}"); } IntPtr texPtr = GetSecondaryViewTextureId(out uint imageIndex); if (Instance.IsEnableDebugLog) { Debug($"SetTextureSize texPtr: {texPtr}, imageIndex: {imageIndex}"); } if (texPtr == IntPtr.Zero) { Error($"SetTextureSize texPtr is invalid: {texPtr}"); return; } if (Instance.IsEnableDebugLog) { Debug("Get ptr successfully"); } Instance.MyTexture = Texture2D.CreateExternalTexture( (int)Instance.TextureSize.x, (int)Instance.TextureSize.y, TextureFormat.RGBA32, false, false, texPtr); #region For development usage (Just for reference) /* if (Instance.IsEnableDebugLog) { Debug("Create texture successfully"); Debug($"Instance.MyTexture.height: {Instance.MyTexture.height}"); Debug($"Instance.MyTexture.width: {Instance.MyTexture.width}"); Debug($"Instance.MyTexture.dimension: {Instance.MyTexture.dimension}"); Debug($"Instance.MyTexture.anisoLevel: {Instance.MyTexture.anisoLevel}"); Debug($"Instance.MyTexture.filterMode: {Instance.MyTexture.filterMode}"); Debug($"Instance.MyTexture.wrapMode: {Instance.MyTexture.wrapMode}"); Debug($"Instance.MyTexture.graphicsFormat: {Instance.MyTexture.graphicsFormat}"); Debug($"Instance.MyTexture.isReadable: {Instance.MyTexture.isReadable}"); Debug($"Instance.MyTexture.texelSize: {Instance.MyTexture.texelSize}"); Debug($"Instance.MyTexture.mipmapCount: {Instance.MyTexture.mipmapCount}"); Debug($"Instance.MyTexture.updateCount: {Instance.MyTexture.updateCount}"); Debug($"Instance.MyTexture.mipMapBias: {Instance.MyTexture.mipMapBias}"); Debug($"Instance.MyTexture.wrapModeU: {Instance.MyTexture.wrapModeU}"); Debug($"Instance.MyTexture.wrapModeV: {Instance.MyTexture.wrapModeV}"); Debug($"Instance.MyTexture.wrapModeW: {Instance.MyTexture.wrapModeW}"); Debug($"Instance.MyTexture.filterMode: {Instance.MyTexture.name}"); Debug($"Instance.MyTexture.hideFlags: {Instance.MyTexture.hideFlags}"); Debug($"Instance.MyTexture.GetInstanceID(): {Instance.MyTexture.GetInstanceID()}"); Debug($"Instance.MyTexture.GetType(): {Instance.MyTexture.GetType()}"); Debug($"Instance.MyTexture.GetNativeTexturePtr(): {Instance.MyTexture.GetNativeTexturePtr()}"); // Print imageContentsHash will cause an error // Debug($"Instance.MyTexture.imageContentsHash: {Instance.MyTexture.imageContentsHash}"); } */ #endregion } /// /// This function is defined as the "SetFovDelegate" delegate function. /// Please be careful that this function should ONLY be called by native plug-ins. /// THIS FUNCTION IS NOT DESIGNED FOR CALLING FROM THE UNITY ENGINE SIDE. /// /// The fov value comes from runtime. [MonoPInvokeCallback(typeof(SetFovDelegate))] private static void SetFov(XrFovf fov) { if (Instance.IsEnableDebugLog) { Debug($"fov.AngleDown {fov.angleDown}"); Debug($"fov.AngleLeft {fov.angleLeft}"); Debug($"fov.AngleRight {fov.angleRight}"); Debug($"fov.AngleUp {fov.angleUp}"); } Instance.onFovUpdated?.Invoke(fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown); } #endregion #region C++ interop functions /// /// Call this function to trigger the native plug-in that gets a specific OpenXR function for services to the /// Unity engine, such as dispatching the Unity data to runtime and returning the data from runtime to the /// Unity engine. /// /// The XrInstance is provided by the Unity OpenXR Plugin. /// Accessor for xrGetInstanceProcAddr function pointer. /// Return true if get successfully. False otherwise. [DllImport( dllName: ExtLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "secondary_view_configuration_get_function_address")] private static extern bool SecondaryViewConfigurationGetFunctionAddress ( XrInstance xrInstance, IntPtr xrGetInstanceProcAddrFuncPtr ); /// /// Call this function to dispatch/hook all OpenXR functions to native plug-ins. /// /// The graphics backend adopted in the Unity engine. /// The bool value represents whether the texture UV coordinate convention for this platform has Y starting at the top of the image. /// xrGetInstanceProcAddr native function pointer. /// The delegate function pointer that functions types as "SetSecondaryViewConfigurationStateDelegate". /// The delegate function pointer that functions types as "StopEnableSecondaryViewConfigurationDelegate". /// The delegate function pointer that functions types as "SetTextureSizeDelegate". /// The delegate function pointer that functions types as "SetFovDelegate". /// [DllImport( dllName: ExtLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "secondary_view_configuration_intercept_openxr_method")] private static extern IntPtr SecondaryViewConfigurationInterceptOpenXRMethod ( GraphicsAPI graphicsAPI, bool graphicsUVStartsAtTop, IntPtr func, [MarshalAs(UnmanagedType.FunctionPtr)] SetSecondaryViewConfigurationStateDelegate setSecondaryViewConfigurationStateDelegate, [MarshalAs(UnmanagedType.FunctionPtr)] StopEnableSecondaryViewConfigurationDelegate stopEnableSecondaryViewConfigurationDelegate, [MarshalAs(UnmanagedType.FunctionPtr)] SetTextureSizeDelegate setTextureSizeDelegate, [MarshalAs(UnmanagedType.FunctionPtr)] SetFovDelegate setFovDelegate ); /// /// Call this function to get the current swapchain image handler (its ID and memory address). /// /// The current handler index. /// The current handler memory address. [DllImport( dllName: ExtLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "get_secondary_view_texture_id")] private static extern IntPtr GetSecondaryViewTextureId ( out UInt32 imageIndex ); /// /// Call this function to tell native plug-in submit the swapchain image immediately. /// /// Return true if submit the swapchain image successfully. False otherwise. [DllImport( dllName: ExtLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "release_secondary_view_texture")] public static extern bool ReleaseSecondaryViewTexture(); /// /// Call this function to release all resources in native plug-in. Please be careful that this function should /// ONLY call in the Unity "OnDestroy" lifecycle event in the class "SpectatorCameraBased". /// [DllImport( dllName: ExtLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "release_all_resources")] public static extern void ReleaseAllResources(); /// /// Call this function if requiring swapchain re-initialization. The native plug-in will set a re-initialization /// flag. Once the secondary view is enabled after that, the swapchain will re-init immediately. /// [DllImport( dllName: ExtLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "require_reinit_swapchain")] public static extern void RequireReinitSwapchain(); /// /// Call this function to tell the native plug-in where the current spectator camera source comes from. /// /// Please set true if the source comes from hmd. Otherwise, please set false. [DllImport( dllName: ExtLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "set_view_from_hmd")] public static extern void SetViewFromHmd(bool isViewFromHmd); /// /// Call this function to tell the non-hmd pose to the native plug-in. /// /// The current non-hmd pose [DllImport( dllName: ExtLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "set_non_hmd_view_pose")] public static extern void SetNonHmdViewPose(XrPosef pose); /// /// Call this function to tell the native plug-in whether the texture data is ready or not. /// /// The texture data written by Unity Engine is ready or not. [DllImport( dllName: ExtLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "set_state_secondary_view_image_data_ready")] public static extern void SetStateSecondaryViewImageDataReady(bool isReady); #endregion #region Utilities functions /// /// Check ViveSecondaryViewConfiguration extension is enabled or not. /// /// Return true if enabled. False otherwise. public static bool IsExtensionEnabled() { #if UNITY_2022_1_OR_NEWER return OpenXRRuntime.IsExtensionEnabled(OPEN_XR_EXTENSION_STRING); #else // Does not support 2021 or lower return false; #endif } /// /// Get the OpenXR function via XrInstance. /// /// The XrInstance is provided by the Unity OpenXR Plugin. /// Return true if get successfully. False otherwise. private bool GetXrFunctionDelegates(XrInstance xrInstance) { if (xrGetInstanceProcAddr != IntPtr.Zero) { Debug("Get function pointer of openXRFunctionPointerAccessor."); XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(xrGetInstanceProcAddr, typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate; if (XrGetInstanceProcAddr == null) { Error( "Get function pointer of openXRFunctionPointerAccessor failed due to the XrGetInstanceProcAddr is null."); return false; } } else { Error( "Get function pointer of openXRFunctionPointerAccessor failed due to the xrGetInstanceProcAddr is null."); return false; } Debug("Try to get the function pointer for XR_MSFT_secondary_view_configuration."); return SecondaryViewConfigurationGetFunctionAddress(xrInstance, xrGetInstanceProcAddr); #region Get function in C# (Just for reference) /* if (GetOpenXRDelegateFunction( XrGetInstanceProcAddr, xrInstance, "xrEnumerateViewConfigurations", out _xrEnumerateViewConfigurations) is false) { Error("Get delegate function of XrEnumerateViewConfigurations failed."); return false; } */ #endregion } /// /// Get the specific OpenXR function. /// /// The function pointer accessor provide by OpenXR. /// The XrInstance is provided by the Unity OpenXR Plugin. /// The specific OpenXR function. /// Override value. The specific OpenXR function. /// The class of the delegate function. /// Return true if get successfully. False otherwise. private static bool GetOpenXRDelegateFunction ( in OpenXRHelper.xrGetInstanceProcAddrDelegate openXRFunctionPointerAccessor, in XrInstance openXRInstance, in string functionName, out T delegateFunction ) where T : class { delegateFunction = default(T); if (openXRFunctionPointerAccessor == null || openXRInstance == 0 || string.IsNullOrEmpty(functionName)) { Error($"Get OpenXR delegate function, {functionName}, failed due to the invalid parameter(s)."); return false; } XrResult getFunctionState = openXRFunctionPointerAccessor(openXRInstance, functionName, out IntPtr funcPtr); bool funcPtrIsNull = funcPtr == IntPtr.Zero; Debug("Get OpenXR delegate function, " + functionName + ", state: " + getFunctionState); Debug("Get OpenXR delegate function, " + functionName + ", funcPtrIsNull: " + funcPtrIsNull); if (getFunctionState != XrResult.XR_SUCCESS || funcPtrIsNull) { Error( $"Get OpenXR delegate function, {functionName}, failed due to the native error or invalid return."); return false; } try { delegateFunction = Marshal.GetDelegateForFunctionPointer(funcPtr, typeof(T)) as T; } catch (Exception e) { Error($"Get OpenXR delegate function, {functionName}, failed due to the exception: {e.Message}"); return false; } Debug($"Get OpenXR delegate function, {functionName}, succeed."); return true; } /// /// Print log with tag "VIVE.OpenXR.SecondaryViewConfiguration". /// /// The log you want to print. private static void Debug(string msg) { UnityEngine.Debug.Log(LogTag + " " + msg); } /// /// Print warning message with tag "VIVE.OpenXR.SecondaryViewConfiguration". /// /// The warning message you want to print. private static void Warning(string msg) { UnityEngine.Debug.LogWarning(LogTag + " " + msg); } /// /// Print an error message with the tag "VIVE.OpenXR.SecondaryViewConfiguration." /// /// The error message you want to print. private static void Error(string msg) { UnityEngine.Debug.LogError(LogTag + " " + msg); } /// /// Get the SpectatorCameraBased component in the current Unity scene. /// /// SpectatorCameraBased array if there are any SpectatorCameraBased components. Otherwise, return null. private static SpectatorCameraBased[] GetSpectatorCameraBased() { var spectatorCameraBasedArray = (SpectatorCameraBased[])FindObjectsOfType(typeof(SpectatorCameraBased)); return (spectatorCameraBasedArray != null && spectatorCameraBasedArray.Length > 0) ? spectatorCameraBasedArray : null; } /// /// Create a GameObject that includes SpectatorCameraBased script in Unity scene for cooperation with extension native plugins. /// private static void CreateSpectatorCameraBased() { if (IsExtensionEnabled()) { Debug($"Instance.IsExistSpectatorCameraBased = {Instance.IsExistSpectatorCameraBased}"); Instance.IsExistSpectatorCameraBased = true; if (GetSpectatorCameraBased() != null) { Debug("No need to add SpectatorCameraBased because the scene already exist."); return; } Debug("Start to add SpectatorCameraBased."); var spectatorCameraBase = new GameObject("Spectator Camera Base", typeof(SpectatorCameraBased)) { transform = { position = Vector3.zero, rotation = Quaternion.identity } }; Debug($"Create Spectator Camera Base GameObject successfully: {spectatorCameraBase != null}"); Debug( $"Included SpectatorCameraBased component: {spectatorCameraBase.GetComponent() != null}"); } else { Debug("Create Spectator Camera Base GameObject failed because the related extensions are not enabled."); } } #endregion #endregion #region Enum definition /// /// The enum definition of supporting rendering backend. /// private enum GraphicsAPI { Unknown = 0, GLES3 = 1, Vulkan = 2 } #endregion } }