Files
2024-12-06 15:44:37 +08:00

898 lines
36 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
{
/// <summary>
/// Name: SecondaryViewConfiguration.cs
/// Role: OpenXR SecondaryViewConfiguration Extension Class
/// Responsibility: The OpenXR extension implementation and its lifecycles logic in OpenXR
/// </summary>
#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;
/// <summary>
/// ViveSecondaryViewConfiguration static instance (Singleton).
/// </summary>
public static ViveSecondaryViewConfiguration Instance
{
get
{
if (_instance == null)
{
_instance =
OpenXRSettings.Instance.GetFeature<ViveSecondaryViewConfiguration>();
}
return _instance;
}
}
#region OpenXR variables related to definition
/// <summary>
/// The log identification.
/// </summary>
private const string LogTag = "VIVE.OpenXR.SecondaryViewConfiguration";
/// <summary>
/// The feature id string. This is used to give the feature a well known id for reference.
/// </summary>
public const string FeatureId = "vive.openxr.feature.secondaryviewconfiguration";
/// <summary>
/// OpenXR specification <a href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_MSFT_secondary_view_configuration">12.122. XR_MSFT_secondary_view_configuration</a>.
/// </summary>
public const string OPEN_XR_EXTENSION_STRING = "XR_MSFT_secondary_view_configuration";
/// <summary>
/// The extension library name.
/// </summary>
private const string ExtLib = "libviveopenxr";
#endregion
#region OpenXR variables related to its life-cycle
/// <summary>
/// The flag represents whether the OpenXR loader created an instance or not.
/// </summary>
private bool XrInstanceCreated { get; set; } = false;
/// <summary>
/// The flag represents whether the OpenXR loader created a session or not.
/// </summary>
private bool XrSessionCreated { get; set; } = false;
/// <summary>
/// The flag represents whether the OpenXR loader started a session or not.
/// </summary>
private bool XrSessionStarted { get; set; } = false;
/// <summary>
/// The instance created through xrCreateInstance.
/// </summary>
private XrInstance XrInstance { get; set; } = 0;
/// <summary>
/// An XrSystemId is an opaque atom used by the runtime to identify a system.
/// </summary>
private XrSystemId XrSystemId { get; set; } = 0;
/// <summary>
/// A session represents an applications intention to display XR content to the user.
/// </summary>
private XrSession XrSession { get; set; } = 0;
/// <summary>
/// New possible session lifecycle states.
/// </summary>
private XrSessionState XrSessionNewState { get; set; } = XrSessionState.XR_SESSION_STATE_UNKNOWN;
/// <summary>
/// The previous state possible session lifecycle states.
/// </summary>
private XrSessionState XrSessionOldState { get; set; } = XrSessionState.XR_SESSION_STATE_UNKNOWN;
/// <summary>
/// The function delegate declaration of xrGetInstanceProcAddr.
/// </summary>
private OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr { get; set; }
#endregion
#region Variables related to handle agent functions
/// <summary>
/// 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".
/// </summary>
private delegate void SetSecondaryViewConfigurationStateDelegate(bool isEnabled);
/// <summary>
/// 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".
/// </summary>
private delegate void StopEnableSecondaryViewConfigurationDelegate(bool isStopped);
/// <summary>
/// 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".
/// </summary>
private delegate void SetTextureSizeDelegate(UInt32 width, UInt32 height);
/// <summary>
/// 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".
/// </summary>
private delegate void SetFovDelegate(XrFovf fov);
#endregion
#region Variables related to callback functions instantiation
/// <summary>
/// A delegate declaration can encapsulate the method that takes a Vector2 argument and returns void.
/// </summary>
public delegate void OnTextureSizeUpdatedDelegate(Vector2 size);
/// <summary>
/// The instantiation of the delegate OnTextureSizeUpdatedDelegate. This will be called when the texture size coming from the native plugin is updated.
/// </summary>
public OnTextureSizeUpdatedDelegate onTextureSizeUpdated;
/// <summary>
/// A delegate declaration can encapsulate the method that takes no argument and returns void.
/// </summary>
public delegate void OnTextureUpdatedDelegate();
/// <summary>
/// The instantiation of the delegate OnTextureUpdatedDelegate. This will be called when the texture coming from the native plugin is updated.
/// </summary>
public OnTextureUpdatedDelegate onTextureUpdated;
/// <summary>
/// A delegate declaration can encapsulate the method that takes four floating-point arguments, left, right, up, and down, respectively, and returns void.
/// </summary>
public delegate void OnFovUpdatedDelegate(float left, float right, float up, float down);
/// <summary>
/// The instantiation of the delegate OnFovUpdatedDelegate. This will be called when the fov setting coming from the native plugin is updated.
/// </summary>
public OnFovUpdatedDelegate onFovUpdated;
#endregion
#region Rendering and texture varibles
/// <summary>
/// The graphics backend that the current application is used.
/// </summary>
private static GraphicsAPI MyGraphicsAPI
{
get
{
return SystemInfo.graphicsDeviceType switch
{
GraphicsDeviceType.OpenGLES3 => GraphicsAPI.GLES3,
GraphicsDeviceType.Vulkan => GraphicsAPI.Vulkan,
_ => GraphicsAPI.Unknown
};
}
}
private Vector2 _textureSize;
/// <summary>
/// The value of texture size coming from the native plugin.
/// </summary>
public Vector2 TextureSize
{
get => _textureSize;
private set
{
_textureSize = value;
onTextureSizeUpdated?.Invoke(value);
}
}
private Texture _myTexture;
/// <summary>
/// The texture handler adopts the native 2D texture object coming from the native plugin.
/// </summary>
public Texture MyTexture
{
get => _myTexture;
private set
{
_myTexture = value;
onTextureUpdated?.Invoke();
}
}
#endregion
#region The variables (flag) represent the state related to this OpenXR extension
/// <summary>
/// The state of the second view configuration comes from runtime.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// The flag represents to whether the second view configuration is disabled or not.
/// </summary>
public bool IsStopped { get; set; }
/// <summary>
/// The flag represents to whether the "SpectatorCameraBased" script exists in the Unity scene.
/// </summary>
private bool IsExistSpectatorCameraBased { get; set; }
#endregion
#endregion
#region Function
#region OpenXR life-cycle functions
/// <summary>
/// Called after xrCreateInstance.
/// </summary>
/// <param name="xrInstance">Handle of the xrInstance.</param>
/// <returns>Returns true if successful. Returns false otherwise.</returns>
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);
}
/// <summary>
/// Called after xrGetSystem
/// </summary>
/// <param name="xrSystem">Handle of the xrSystemId</param>
protected override void OnSystemChange(ulong xrSystem)
{
XrSystemId = xrSystem;
Debug("OnSystemChange() " + XrSystemId);
base.OnSystemChange(xrSystem);
}
/// <summary>
/// Called after xrCreateSession.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionCreate(ulong xrSession)
{
XrSessionCreated = true;
XrSession = xrSession;
Debug("OnSessionCreate() " + XrSession);
base.OnSessionCreate(xrSession);
}
/// <summary>
/// Called after xrSessionBegin.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionBegin(ulong xrSession)
{
XrSessionStarted = true;
Debug("OnSessionBegin() " + XrSessionStarted);
base.OnSessionBegin(xrSession);
}
/// <summary>
/// Called when the OpenXR loader receives the XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED event from the runtime signaling that the XrSessionState has changed.
/// </summary>
/// <param name="oldState">Previous state.</param>
/// <param name="newState">New state.</param>
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);
}
/// <summary>
/// Called to hook xrGetInstanceProcAddr. Returning a different function pointer allows intercepting any OpenXR method.
/// </summary>
/// <param name="func">xrGetInstanceProcAddr native function pointer.</param>
/// <returns>Function pointer that Unity will use to look up OpenXR native functions.</returns>
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);
}
/// <summary>
/// Called before xrEndSession.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionEnd(ulong xrSession)
{
XrSessionStarted = false;
Debug("OnSessionEnd() " + XrSession);
base.OnSessionEnd(xrSession);
}
/// <summary>
/// Called before xrDestroySession.
/// </summary>
/// <param name="xrSession">Handle of the xrSession.</param>
protected override void OnSessionDestroy(ulong xrSession)
{
XrSessionCreated = false;
Debug("OnSessionDestroy() " + xrSession);
base.OnSessionDestroy(xrSession);
}
/// <summary>
/// Called before xrDestroyInstance.
/// </summary>
/// <param name="xrInstance">Handle of the xrInstance.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
XrInstanceCreated = false;
XrInstance = 0;
Debug("OnInstanceDestroy() " + xrInstance);
base.OnInstanceDestroy(xrInstance);
}
#endregion
#region Handle agent functions
/// <summary>
/// This function is defined as the "SetSecondaryViewConfigurationStateDelegate" delegate function.
/// <b>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.</b>
/// </summary>
/// <param name="isEnabled">The state of the second view configuration comes from runtime. True if enabled. False otherwise.</param>
[MonoPInvokeCallback(typeof(SetSecondaryViewConfigurationStateDelegate))]
private static void SetSecondaryViewConfigurationState(bool isEnabled)
{
Instance.IsEnabled = isEnabled;
if (Instance.IsEnableDebugLog)
{
Debug($"SetSecondaryViewConfigurationState: Instance.IsEnabled set as {Instance.IsEnabled}");
}
}
/// <summary>
/// This function is defined as the "StopEnableSecondaryViewConfigurationDelegate" delegate function.
/// <b>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.</b>
/// </summary>
/// <param name="isStopped">The flag refers to whether the second view configuration is disabled or not. True if the second view configuration is disabled. False otherwise.</param>
[MonoPInvokeCallback(typeof(StopEnableSecondaryViewConfigurationDelegate))]
private static void StopEnableSecondaryViewConfiguration(bool isStopped)
{
Instance.IsStopped = isStopped;
if (Instance.IsEnableDebugLog)
{
Debug($"StopEnableSecondaryViewConfiguration: Instance.IsStopped set as {Instance.IsStopped}");
}
}
/// <summary>
/// This function is defined as the "SetTextureSizeDelegate" delegate function.
/// <b>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.</b>
/// </summary>
/// <param name="width">The texture width comes from runtime.</param>
/// <param name="height">The texture height comes from runtime.</param>
[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
}
/// <summary>
/// This function is defined as the "SetFovDelegate" delegate function.
/// <b>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.</b>
/// </summary>
/// <param name="fov">The fov value comes from runtime.</param>
[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
/// <summary>
/// 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.
/// </summary>
/// <param name="xrInstance">The XrInstance is provided by the Unity OpenXR Plugin.</param>
/// <param name="xrGetInstanceProcAddrFuncPtr">Accessor for xrGetInstanceProcAddr function pointer.</param>
/// <returns>Return true if get successfully. False otherwise.</returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "secondary_view_configuration_get_function_address")]
private static extern bool SecondaryViewConfigurationGetFunctionAddress
(
XrInstance xrInstance,
IntPtr xrGetInstanceProcAddrFuncPtr
);
/// <summary>
/// Call this function to dispatch/hook all OpenXR functions to native plug-ins.
/// </summary>
/// <param name="graphicsAPI">The graphics backend adopted in the Unity engine.</param>
/// <param name="graphicsUVStartsAtTop">The bool value represents whether the texture UV coordinate convention for this platform has Y starting at the top of the image.</param>
/// <param name="func">xrGetInstanceProcAddr native function pointer.</param>
/// <param name="setSecondaryViewConfigurationStateDelegate">The delegate function pointer that functions types as "SetSecondaryViewConfigurationStateDelegate".</param>
/// <param name="stopEnableSecondaryViewConfigurationDelegate">The delegate function pointer that functions types as "StopEnableSecondaryViewConfigurationDelegate".</param>
/// <param name="setTextureSizeDelegate">The delegate function pointer that functions types as "SetTextureSizeDelegate".</param>
/// <param name="setFovDelegate">The delegate function pointer that functions types as "SetFovDelegate".</param>
/// <returns></returns>
[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
);
/// <summary>
/// Call this function to get the current swapchain image handler (its ID and memory address).
/// </summary>
/// <param name="imageIndex">The current handler index.</param>
/// <returns>The current handler memory address.</returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "get_secondary_view_texture_id")]
private static extern IntPtr GetSecondaryViewTextureId
(
out UInt32 imageIndex
);
/// <summary>
/// Call this function to tell native plug-in submit the swapchain image immediately.
/// </summary>
/// <returns>Return true if submit the swapchain image successfully. False otherwise.</returns>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "release_secondary_view_texture")]
public static extern bool ReleaseSecondaryViewTexture();
/// <summary>
/// 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".
/// </summary>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "release_all_resources")]
public static extern void ReleaseAllResources();
/// <summary>
/// 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.
/// </summary>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "require_reinit_swapchain")]
public static extern void RequireReinitSwapchain();
/// <summary>
/// Call this function to tell the native plug-in where the current spectator camera source comes from.
/// </summary>
/// <param name="isViewFromHmd">Please set true if the source comes from hmd. Otherwise, please set false.</param>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "set_view_from_hmd")]
public static extern void SetViewFromHmd(bool isViewFromHmd);
/// <summary>
/// Call this function to tell the non-hmd pose to the native plug-in.
/// </summary>
/// <param name="pose">The current non-hmd pose</param>
[DllImport(
dllName: ExtLib,
CallingConvention = CallingConvention.Cdecl,
EntryPoint = "set_non_hmd_view_pose")]
public static extern void SetNonHmdViewPose(XrPosef pose);
/// <summary>
/// Call this function to tell the native plug-in whether the texture data is ready or not.
/// </summary>
/// <param name="isReady">The texture data written by Unity Engine is ready or not.</param>
[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
/// <summary>
/// Check ViveSecondaryViewConfiguration extension is enabled or not.
/// </summary>
/// <returns>Return true if enabled. False otherwise.</returns>
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
}
/// <summary>
/// Get the OpenXR function via XrInstance.
/// </summary>
/// <param name="xrInstance">The XrInstance is provided by the Unity OpenXR Plugin.</param>
/// <returns>Return true if get successfully. False otherwise.</returns>
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
}
/// <summary>
/// Get the specific OpenXR function.
/// </summary>
/// <param name="openXRFunctionPointerAccessor">The function pointer accessor provide by OpenXR.</param>
/// <param name="openXRInstance">The XrInstance is provided by the Unity OpenXR Plugin.</param>
/// <param name="functionName">The specific OpenXR function.</param>
/// <param name="delegateFunction">Override value. The specific OpenXR function.</param>
/// <typeparam name="T">The class of the delegate function.</typeparam>
/// <returns>Return true if get successfully. False otherwise.</returns>
private static bool GetOpenXRDelegateFunction<T>
(
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;
}
/// <summary>
/// Print log with tag "VIVE.OpenXR.SecondaryViewConfiguration".
/// </summary>
/// <param name="msg">The log you want to print.</param>
private static void Debug(string msg)
{
UnityEngine.Debug.Log(LogTag + " " + msg);
}
/// <summary>
/// Print warning message with tag "VIVE.OpenXR.SecondaryViewConfiguration".
/// </summary>
/// <param name="msg">The warning message you want to print.</param>
private static void Warning(string msg)
{
UnityEngine.Debug.LogWarning(LogTag + " " + msg);
}
/// <summary>
/// Print an error message with the tag "VIVE.OpenXR.SecondaryViewConfiguration."
/// </summary>
/// <param name="msg">The error message you want to print.</param>
private static void Error(string msg)
{
UnityEngine.Debug.LogError(LogTag + " " + msg);
}
/// <summary>
/// Get the SpectatorCameraBased component in the current Unity scene.
/// </summary>
/// <returns>SpectatorCameraBased array if there are any SpectatorCameraBased components. Otherwise, return null.</returns>
private static SpectatorCameraBased[] GetSpectatorCameraBased()
{
var spectatorCameraBasedArray = (SpectatorCameraBased[])FindObjectsOfType(typeof(SpectatorCameraBased));
return (spectatorCameraBasedArray != null && spectatorCameraBasedArray.Length > 0)
? spectatorCameraBasedArray
: null;
}
/// <summary>
/// Create a GameObject that includes SpectatorCameraBased script in Unity scene for cooperation with extension native plugins.
/// </summary>
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<SpectatorCameraBased>() != null}");
}
else
{
Debug("Create Spectator Camera Base GameObject failed because the related extensions are not enabled.");
}
}
#endregion
#endregion
#region Enum definition
/// <summary>
/// The enum definition of supporting rendering backend.
/// </summary>
private enum GraphicsAPI
{
Unknown = 0,
GLES3 = 1,
Vulkan = 2
}
#endregion
}
}