// 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
}
}