version 2.5.0
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VIVE.OpenXR.Feature
|
||||
{
|
||||
public interface IViveFeatureWrapper
|
||||
{
|
||||
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddr);
|
||||
|
||||
public void OnInstanceDestroy();
|
||||
}
|
||||
|
||||
public class ViveFeatureWrapperBase<T> where T : ViveFeatureWrapperBase<T>, new()
|
||||
{
|
||||
private static readonly Lazy<T> lazyInstance = new Lazy<T>(() => new T());
|
||||
|
||||
public static T Instance => lazyInstance.Value;
|
||||
|
||||
// Set true in yourfeature's OnInstanceCreate
|
||||
public bool IsInited { get; protected set; } = false;
|
||||
|
||||
public OpenXRHelper.xrGetInstanceProcAddrDelegate xrGetInstanceProcAddr;
|
||||
|
||||
/// <summary>
|
||||
/// Complete the xrGetInstanceProcAddr by set the pointer received in OnInstanceCreate
|
||||
/// </summary>
|
||||
/// <param name="intPtr"></param>
|
||||
public void SetGetInstanceProcAddrPtr(IntPtr intPtr)
|
||||
{
|
||||
if (intPtr == null || intPtr == IntPtr.Zero)
|
||||
throw new Exception("xrGetInstanceProcAddr is null");
|
||||
|
||||
xrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrGetInstanceProcAddrDelegate>(intPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a27dc5505cdb29347aeda46676cedaa8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
80
com.htc.upm.vive.openxr/Runtime/Common/MemoryTools.cs
Normal file
80
com.htc.upm.vive.openxr/Runtime/Common/MemoryTools.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VIVE.OpenXR
|
||||
{
|
||||
public static class MemoryTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert the enum array to IntPtr. Should call <see cref="ReleaseRawMemory(IntPtr)"/> after use.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="array"></param>
|
||||
/// <returns></returns>
|
||||
public static IntPtr ToIntPtr<T>(T[] array) where T : Enum
|
||||
{
|
||||
int size = Marshal.SizeOf(typeof(T)) * array.Length;
|
||||
IntPtr ptr = Marshal.AllocHGlobal(size);
|
||||
int[] intArray = new int[array.Length];
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
intArray[i] = (int)(object)array[i];
|
||||
Marshal.Copy(intArray, 0, ptr, array.Length);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make the same size raw buffer from input array.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Data type could be primitive type or struct. Should call <see cref="ReleaseRawMemory(IntPtr)"/> after use.</typeparam>
|
||||
/// <param name="refArray">The data array</param>
|
||||
/// <returns>The memory handle. Should release by <see cref="ReleaseRawMemory(IntPtr)"/></returns>
|
||||
public static IntPtr MakeRawMemory<T>(T[] refArray)
|
||||
{
|
||||
int size = Marshal.SizeOf(typeof(T)) * refArray.Length;
|
||||
return Marshal.AllocHGlobal(size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the raw memory to the array. You should make sure the array has the same size as the raw memory.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Convert the memory to this type array.</typeparam>
|
||||
/// <param name="array">The output array.</param>
|
||||
/// <param name="raw">The data source in raw memory form.</param>
|
||||
/// <param name="count">Specify the copy count. Count should be less than array length.</param>
|
||||
public static void CopyFromRawMemory<T>(T[] array, IntPtr raw, int count = 0)
|
||||
{
|
||||
int N = array.Length;
|
||||
if (count > 0 && count < array.Length)
|
||||
N = count;
|
||||
int step = Marshal.SizeOf(typeof(T));
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
array[i] = Marshal.PtrToStructure<T>(IntPtr.Add(raw, i * step));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make the same size raw buffer from input array. Make sure the raw has enough size.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Convert this type array to raw memory.</typeparam>
|
||||
/// <param name="raw">The output data in raw memory form</param>
|
||||
/// <param name="array">The data source</param>
|
||||
public static void CopyToRawMemory<T>(IntPtr raw, T[] array)
|
||||
{
|
||||
int step = Marshal.SizeOf(typeof(T));
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
Marshal.StructureToPtr<T>(array[i], IntPtr.Add(raw, i * step), false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release the raw memory handle which is created by <see cref="MakeRawMemory{T}(T[])"/>
|
||||
/// </summary>
|
||||
/// <param name="ptr"></param>
|
||||
public static void ReleaseRawMemory(IntPtr ptr)
|
||||
{
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
com.htc.upm.vive.openxr/Runtime/Common/MemoryTools.cs.meta
Normal file
11
com.htc.upm.vive.openxr/Runtime/Common/MemoryTools.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a887cb158a37cf45b17458a4f27d7ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace VIVE.OpenXR.Feature
|
||||
{
|
||||
@@ -10,23 +11,16 @@ namespace VIVE.OpenXR.Feature
|
||||
/// To use this wrapper, you need to call CommonWrapper.Instance.OnInstanceCreate() in your feature's OnInstanceCreate(),
|
||||
/// and call CommonWrapper.Instance.OnInstanceDestroy() in your feature's OnInstanceDestroy().
|
||||
/// </summary>
|
||||
public class CommonWrapper
|
||||
public class CommonWrapper : ViveFeatureWrapperBase<CommonWrapper>, IViveFeatureWrapper
|
||||
{
|
||||
static CommonWrapper instance = null;
|
||||
public static CommonWrapper Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
instance = new CommonWrapper();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
bool isInited = false;
|
||||
|
||||
OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
|
||||
OpenXRHelper.xrGetSystemPropertiesDelegate XrGetSystemProperties;
|
||||
OpenXRHelper.xrCreateSwapchainDelegate XrCreateSwapchain;
|
||||
OpenXRHelper.xrDestroySwapchainDelegate XrDestroySwapchain;
|
||||
OpenXRHelper.xrEnumerateSwapchainFormatsDelegate XrEnumerateSwapchainFormats;
|
||||
OpenXRHelper.xrEnumerateSwapchainImagesDelegate XrEnumerateSwapchainImages;
|
||||
OpenXRHelper.xrWaitSwapchainImageDelegate XrWaitSwapchainImage;
|
||||
OpenXRHelper.xrAcquireSwapchainImageDelegate XrAcquireSwapchainImage;
|
||||
OpenXRHelper.xrReleaseSwapchainImageDelegate XrReleaseSwapchainImage;
|
||||
|
||||
/// <summary>
|
||||
/// In feature's OnInstanceCreate(), call CommonWrapper.Instance.OnInstanceCreate() for init common APIs.
|
||||
@@ -35,32 +29,32 @@ namespace VIVE.OpenXR.Feature
|
||||
/// <param name="xrGetInstanceProcAddr">Pass OpenXRFeature.xrGetInstanceProcAddr in.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception">If input data not valid.</exception>
|
||||
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddr)
|
||||
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrPtr)
|
||||
{
|
||||
if (isInited) return true;
|
||||
if (IsInited) return true;
|
||||
|
||||
if (xrInstance == 0)
|
||||
throw new Exception("CommonWrapper: xrInstance is null");
|
||||
|
||||
Debug.Log("CommonWrapper: OnInstanceCreate()");
|
||||
/// OpenXRFeature.xrGetInstanceProcAddr
|
||||
if (xrGetInstanceProcAddr == null || xrGetInstanceProcAddr == IntPtr.Zero)
|
||||
throw new Exception("CommonWrapper: xrGetInstanceProcAddr is null");
|
||||
|
||||
Debug.Log("CommonWrapper: Get function pointer of xrGetInstanceProcAddr.");
|
||||
XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(
|
||||
xrGetInstanceProcAddr,
|
||||
typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
|
||||
SetGetInstanceProcAddrPtr(xrGetInstanceProcAddrPtr);
|
||||
|
||||
bool ret = true;
|
||||
IntPtr funcPtr = IntPtr.Zero;
|
||||
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(XrGetInstanceProcAddr, xrInstance, "xrGetSystemProperties", out XrGetSystemProperties);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrGetSystemProperties", out XrGetSystemProperties);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrCreateSwapchain", out XrCreateSwapchain);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrDestroySwapchain", out XrDestroySwapchain);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrEnumerateSwapchainFormats", out XrEnumerateSwapchainFormats);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrEnumerateSwapchainImages", out XrEnumerateSwapchainImages);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrWaitSwapchainImage", out XrWaitSwapchainImage);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrAcquireSwapchainImage", out XrAcquireSwapchainImage);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrReleaseSwapchainImage", out XrReleaseSwapchainImage);
|
||||
|
||||
if (!ret)
|
||||
throw new Exception("CommonWrapper: Get function pointers failed.");
|
||||
|
||||
isInited = ret;
|
||||
IsInited = ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -70,21 +64,20 @@ namespace VIVE.OpenXR.Feature
|
||||
/// <returns></returns>
|
||||
public void OnInstanceDestroy()
|
||||
{
|
||||
isInited = false;
|
||||
XrGetInstanceProcAddr = null;
|
||||
IsInited = false;
|
||||
XrGetSystemProperties = null;
|
||||
Debug.Log("CommonWrapper: OnInstanceDestroy()");
|
||||
}
|
||||
|
||||
public XrResult GetInstanceProcAddr(XrInstance instance, string name, out IntPtr function)
|
||||
{
|
||||
if (isInited == false || XrGetInstanceProcAddr == null)
|
||||
if (IsInited == false || xrGetInstanceProcAddr == null)
|
||||
{
|
||||
function = IntPtr.Zero;
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
return XrGetInstanceProcAddr(instance, name, out function);
|
||||
return xrGetInstanceProcAddr(instance, name, out function);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -97,7 +90,7 @@ namespace VIVE.OpenXR.Feature
|
||||
/// <returns></returns>
|
||||
public XrResult GetSystemProperties(XrInstance instance, XrSystemId systemId, ref XrSystemProperties properties)
|
||||
{
|
||||
if (isInited == false || XrGetSystemProperties == null)
|
||||
if (IsInited == false || XrGetSystemProperties == null)
|
||||
{
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
}
|
||||
@@ -136,5 +129,115 @@ namespace VIVE.OpenXR.Feature
|
||||
Marshal.FreeHGlobal(systemProperties.next);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public XrResult CreateSwapchain(XrSession session, ref XrSwapchainCreateInfo createInfo, out XrSwapchain swapchain)
|
||||
{
|
||||
if (IsInited == false || XrCreateSwapchain == null)
|
||||
{
|
||||
swapchain = default;
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
return XrCreateSwapchain(session, ref createInfo, out swapchain);
|
||||
}
|
||||
|
||||
public XrResult DestroySwapchain(XrSwapchain swapchain)
|
||||
{
|
||||
if (IsInited == false || XrDestroySwapchain == null)
|
||||
{
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
return XrDestroySwapchain(swapchain);
|
||||
}
|
||||
|
||||
public XrResult EnumerateSwapchainFormats(XrSession session, uint formatCapacityInput, ref uint formatCountOutput, ref long[] formats)
|
||||
{
|
||||
if (IsInited == false || XrEnumerateSwapchainFormats == null)
|
||||
{
|
||||
formatCountOutput = 0;
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
if (formatCapacityInput != 0 && (formats == null || formats.Length < formatCapacityInput))
|
||||
return XrResult.XR_ERROR_SIZE_INSUFFICIENT;
|
||||
|
||||
if (formatCapacityInput == 0)
|
||||
{
|
||||
Debug.Log("CommonWrapper: EnumerateSwapchainFormats(ci=" + formatCapacityInput + ")");
|
||||
return XrEnumerateSwapchainFormats(session, 0, ref formatCountOutput, IntPtr.Zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("CommonWrapper: EnumerateSwapchainFormats(ci=" + formatCapacityInput + ", formats=long[" + formats.Length + "])");
|
||||
IntPtr formatsPtr = MemoryTools.MakeRawMemory(formats);
|
||||
var ret = XrEnumerateSwapchainFormats(session, formatCapacityInput, ref formatCountOutput, formatsPtr);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
MemoryTools.CopyFromRawMemory(formats, formatsPtr, (int)formatCountOutput);
|
||||
MemoryTools.ReleaseRawMemory(formatsPtr);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public XrResult EnumerateSwapchainImages(XrSwapchain swapchain, uint imageCapacityInput, ref uint imageCountOutput, IntPtr imagesPtr)
|
||||
{
|
||||
if (IsInited == false || XrEnumerateSwapchainImages == null)
|
||||
{
|
||||
imageCountOutput = 0;
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
return XrEnumerateSwapchainImages(swapchain, imageCapacityInput, ref imageCountOutput, imagesPtr);
|
||||
}
|
||||
|
||||
[DllImport("viveopenxr", EntryPoint = "CwAcquireSwapchainImage")]
|
||||
public static extern XrResult CwAcquireSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageAcquireInfo acquireInfo, out uint index);
|
||||
|
||||
public XrResult AcquireSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageAcquireInfo acquireInfo, out uint index)
|
||||
{
|
||||
if (IsInited == false || XrAcquireSwapchainImage == null)
|
||||
{
|
||||
index = 0;
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
Profiler.BeginSample("ASW: xrAcqScImg");
|
||||
var res = XrAcquireSwapchainImage(swapchain, ref acquireInfo, out index);
|
||||
Profiler.EndSample();
|
||||
return res;
|
||||
}
|
||||
|
||||
[DllImport("viveopenxr", EntryPoint = "CwWaitSwapchainImage")]
|
||||
public static extern XrResult CwWaitSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageWaitInfo waitInfo);
|
||||
|
||||
public XrResult WaitSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageWaitInfo waitInfo)
|
||||
{
|
||||
if (IsInited == false || XrWaitSwapchainImage == null)
|
||||
{
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
Profiler.BeginSample("ASW: xrWaitScImg");
|
||||
var res = XrWaitSwapchainImage(swapchain, ref waitInfo);
|
||||
Profiler.EndSample();
|
||||
return res;
|
||||
}
|
||||
|
||||
[DllImport("viveopenxr", EntryPoint = "CwReleaseSwapchainImage")]
|
||||
public static extern XrResult CwReleaseSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageReleaseInfo releaseInfo);
|
||||
|
||||
public XrResult ReleaseSwapchainImage(XrSwapchain swapchain, ref XrSwapchainImageReleaseInfo releaseInfo)
|
||||
{
|
||||
if (IsInited == false || XrReleaseSwapchainImage == null)
|
||||
{
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
}
|
||||
|
||||
// Add Profiler
|
||||
Profiler.BeginSample("ASW: xrRelScImg");
|
||||
var res = XrReleaseSwapchainImage(swapchain, ref releaseInfo);
|
||||
Profiler.EndSample();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
|
||||
namespace VIVE.OpenXR.Feature
|
||||
{
|
||||
using XrFutureEXT = System.IntPtr;
|
||||
|
||||
/// <summary>
|
||||
/// To use this wrapper,
|
||||
/// 1. Add the "XR_EXT_Future" extension to the instance's enabled extensions list.
|
||||
/// 2. Call FutureWrapper.Instance.OnInstanceCreate() in your feature's OnInstanceCreate().
|
||||
/// 3. Call FutureWrapper.Instance.OnInstanceDestroy() in your feature's OnInstanceDestroy().
|
||||
///
|
||||
/// <see cref="VIVE.OpenXR.Toolkits.FutureTask.Poll"/> function helps make async Task.
|
||||
/// </summary>
|
||||
public class FutureWrapper : ViveFeatureWrapperBase<FutureWrapper>, IViveFeatureWrapper
|
||||
{
|
||||
public enum XrFutureStateEXT
|
||||
{
|
||||
None = 0, // Not defined in extension. A default value.
|
||||
Pending = 1,
|
||||
Ready = 2,
|
||||
MAX = 0x7FFFFFFF
|
||||
}
|
||||
|
||||
public struct XrFuturePollInfoEXT {
|
||||
public XrStructureType type; // XR_TYPE_FUTURE_POLL_INFO_EXT
|
||||
public IntPtr next;
|
||||
public XrFutureEXT future;
|
||||
}
|
||||
|
||||
public struct XrFuturePollResultEXT {
|
||||
public XrStructureType type; // XR_TYPE_FUTURE_POLL_RESULT_EXT
|
||||
public IntPtr next;
|
||||
public XrFutureStateEXT state;
|
||||
}
|
||||
|
||||
public struct XrFutureCancelInfoEXT
|
||||
{
|
||||
public XrStructureType type; // XR_TYPE_FUTURE_CANCEL_INFO_EXT
|
||||
public IntPtr next;
|
||||
public XrFutureEXT future;
|
||||
}
|
||||
|
||||
public struct XrFutureCompletionBaseHeaderEXT
|
||||
{
|
||||
public XrStructureType type; // XR_TYPE_FUTURE_COMPLETION_EXT
|
||||
public IntPtr next;
|
||||
public XrResult futureResult;
|
||||
}
|
||||
|
||||
public struct XrFutureCompletionEXT
|
||||
{
|
||||
public XrStructureType type; // XR_TYPE_FUTURE_COMPLETION_EXT
|
||||
public IntPtr next;
|
||||
public XrResult futureResult;
|
||||
}
|
||||
|
||||
public delegate XrResult XrPollFutureEXTDelegate(XrInstance instance, ref XrFuturePollInfoEXT pollInfo, out XrFuturePollResultEXT pollResult);
|
||||
public delegate XrResult XrCancelFutureEXTDelegate(XrInstance instance, ref XrFutureCancelInfoEXT cancelInfo);
|
||||
|
||||
XrPollFutureEXTDelegate XrPollFutureEXT;
|
||||
XrCancelFutureEXTDelegate XrCancelFutureEXT;
|
||||
|
||||
XrInstance xrInstance;
|
||||
|
||||
/// <summary>
|
||||
/// Features should call FutureWrapper.Instance.OnInstanceCreate() in their OnInstanceCreate().
|
||||
/// </summary>
|
||||
/// <param name="xrInstance"></param>
|
||||
/// <param name="xrGetInstanceProcAddrPtr"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrPtr)
|
||||
{
|
||||
if (IsInited) return true;
|
||||
|
||||
if (xrInstance == null)
|
||||
throw new Exception("FutureWrapper: xrInstance is null");
|
||||
this.xrInstance = xrInstance;
|
||||
|
||||
if (xrGetInstanceProcAddrPtr == null)
|
||||
throw new Exception("FutureWrapper: xrGetInstanceProcAddr is null");
|
||||
SetGetInstanceProcAddrPtr(xrGetInstanceProcAddrPtr);
|
||||
|
||||
Debug.Log("FutureWrapper: OnInstanceCreate()");
|
||||
|
||||
bool hasFuture = OpenXRRuntime.IsExtensionEnabled("XR_EXT_future");
|
||||
if (!hasFuture)
|
||||
{
|
||||
Debug.LogError("FutureWrapper: XR_EXT_future is not enabled. Check your feature's kOpenxrExtensionString.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
IntPtr funcPtr = IntPtr.Zero;
|
||||
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrPollFutureEXT", out XrPollFutureEXT);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrCancelFutureEXT", out XrCancelFutureEXT);
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
Debug.LogError("FutureWrapper: Failed to get function pointer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
IsInited = ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void OnInstanceDestroy()
|
||||
{
|
||||
Debug.Log("FutureWrapper: OnInstanceDestroy()");
|
||||
IsInited = false;
|
||||
XrPollFutureEXT = null;
|
||||
XrCancelFutureEXT = null;
|
||||
xrInstance = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to get the state of a future. If Ready, Call complete functions to get the result.
|
||||
/// </summary>
|
||||
/// <param name="pollInfo"></param>
|
||||
/// <param name="pollResult"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public XrResult PollFuture(ref XrFuturePollInfoEXT pollInfo, out XrFuturePollResultEXT pollResult)
|
||||
{
|
||||
pollResult= new XrFuturePollResultEXT()
|
||||
{
|
||||
type = XrStructureType.XR_TYPE_FUTURE_POLL_RESULT_EXT,
|
||||
next = IntPtr.Zero,
|
||||
state = XrFutureStateEXT.None
|
||||
};
|
||||
if (!IsInited)
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
|
||||
return XrPollFutureEXT(xrInstance, ref pollInfo, out pollResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to get the state of a future. If Ready, Call complete functions to get the result.
|
||||
/// </summary>
|
||||
/// <param name="future"></param>
|
||||
/// <param name="pollResult"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public XrResult PollFuture(XrFutureEXT future, out XrFuturePollResultEXT pollResult)
|
||||
{
|
||||
pollResult = new XrFuturePollResultEXT()
|
||||
{
|
||||
type = XrStructureType.XR_TYPE_FUTURE_POLL_RESULT_EXT,
|
||||
next = IntPtr.Zero,
|
||||
state = XrFutureStateEXT.None
|
||||
};
|
||||
if (!IsInited)
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
|
||||
XrFuturePollInfoEXT pollInfo = new XrFuturePollInfoEXT()
|
||||
{
|
||||
type = XrStructureType.XR_TYPE_FUTURE_POLL_INFO_EXT,
|
||||
next = IntPtr.Zero,
|
||||
future = future
|
||||
};
|
||||
|
||||
return XrPollFutureEXT(xrInstance, ref pollInfo, out pollResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function cancels the future and signals that the async operation is not required.
|
||||
/// After a future has been cancelled any functions using this future must return XR_ERROR_FUTURE_INVALID_EXT.
|
||||
/// </summary>
|
||||
/// <param name="cancelInfo"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public XrResult CancelFuture(ref XrFutureCancelInfoEXT cancelInfo)
|
||||
{
|
||||
if (!IsInited)
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
|
||||
return XrCancelFutureEXT(xrInstance, ref cancelInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="CancelFuture(ref XrFutureCancelInfoEXT)"/>
|
||||
/// </summary>
|
||||
/// <param name="future"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public XrResult CancelFuture(XrFutureEXT future)
|
||||
{
|
||||
if (!IsInited)
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
|
||||
XrFutureCancelInfoEXT cancelInfo = new XrFutureCancelInfoEXT()
|
||||
{
|
||||
type = XrStructureType.XR_TYPE_FUTURE_CANCEL_INFO_EXT,
|
||||
next = IntPtr.Zero,
|
||||
future = future
|
||||
};
|
||||
|
||||
return XrCancelFutureEXT(xrInstance, ref cancelInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8522c7af0a4127409a8800e1ddd5985
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,10 +1,5 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
// Remove FAKE_DATA if editor or windows is supported.
|
||||
#if UNITY_EDITOR
|
||||
#define FAKE_DATA
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -15,21 +10,8 @@ namespace VIVE.OpenXR.Feature
|
||||
/// To use this wrapper, you need to call CommonWrapper.Instance.OnInstanceCreate() in your feature's OnInstanceCreate(),
|
||||
/// and call CommonWrapper.Instance.OnInstanceDestroy() in your feature's OnInstanceDestroy().
|
||||
/// </summary>
|
||||
public class SpaceWrapper
|
||||
public class SpaceWrapper : ViveFeatureWrapperBase<SpaceWrapper>, IViveFeatureWrapper
|
||||
{
|
||||
static SpaceWrapper instance = null;
|
||||
public static SpaceWrapper Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
instance = new SpaceWrapper();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
bool isInited = false;
|
||||
|
||||
delegate XrResult DelegateXrLocateSpace(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location);
|
||||
delegate XrResult DelegateXrDestroySpace(XrSpace space);
|
||||
|
||||
@@ -44,31 +26,30 @@ namespace VIVE.OpenXR.Feature
|
||||
/// <param name="GetAddr"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public bool OnInstanceCreate(XrInstance xrInstance, OpenXRHelper.xrGetInstanceProcAddrDelegate GetAddr)
|
||||
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr GetAddr)
|
||||
{
|
||||
if (isInited) return true;
|
||||
if (IsInited) return true;
|
||||
|
||||
if (xrInstance == null)
|
||||
throw new Exception("ViveSpace: xrInstance is null");
|
||||
|
||||
if (GetAddr == null)
|
||||
throw new Exception("ViveSpace: xrGetInstanceProcAddr is null");
|
||||
SetGetInstanceProcAddrPtr(GetAddr);
|
||||
|
||||
Debug.Log("ViveSpace: OnInstanceCreate()");
|
||||
|
||||
bool ret = true;
|
||||
IntPtr funcPtr = IntPtr.Zero;
|
||||
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrCreateReferenceSpace", out XrCreateReferenceSpace);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrLocateSpace", out XrLocateSpace);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(GetAddr, xrInstance, "xrDestroySpace", out XrDestroySpace);
|
||||
isInited = ret;
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrCreateReferenceSpace", out XrCreateReferenceSpace);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrLocateSpace", out XrLocateSpace);
|
||||
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrDestroySpace", out XrDestroySpace);
|
||||
IsInited = ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void OnInstanceDestroy()
|
||||
{
|
||||
isInited = false;
|
||||
IsInited = false;
|
||||
XrCreateReferenceSpace = null;
|
||||
XrLocateSpace = null;
|
||||
XrDestroySpace = null;
|
||||
@@ -77,8 +58,8 @@ namespace VIVE.OpenXR.Feature
|
||||
/// <summary>
|
||||
/// Create a reference space without create info.
|
||||
/// Example:
|
||||
/// CreateReferenceSpace(session, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_LOCAL, XrPosef.identity, out space);
|
||||
/// CreateReferenceSpace(session, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_STAGE, XrPosef.identity, out space);
|
||||
/// CreateReferenceSpace(session, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_LOCAL, XrPosef.Identity, out space);
|
||||
/// CreateReferenceSpace(session, XrReferenceSpaceType.XR_REFERENCE_SPACE_TYPE_STAGE, XrPosef.Identity, out space);
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="referenceSpaceType"></param>
|
||||
@@ -87,8 +68,9 @@ namespace VIVE.OpenXR.Feature
|
||||
/// <returns></returns>
|
||||
public XrResult CreateReferenceSpace(XrSession session, XrReferenceSpaceType referenceSpaceType, XrPosef pose, out XrSpace space)
|
||||
{
|
||||
if (!isInited)
|
||||
throw new Exception("ViveSpace: not initialized");
|
||||
space = 0;
|
||||
if (!IsInited)
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
|
||||
var createInfo = new XrReferenceSpaceCreateInfo();
|
||||
createInfo.type = XrStructureType.XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
|
||||
@@ -107,24 +89,25 @@ namespace VIVE.OpenXR.Feature
|
||||
/// <returns></returns>
|
||||
public XrResult CreateReferenceSpace(XrSession session, XrReferenceSpaceCreateInfo createInfo, out XrSpace space)
|
||||
{
|
||||
if (!isInited)
|
||||
throw new Exception("ViveSpace: not initialized");
|
||||
space = 0;
|
||||
if (!IsInited)
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
|
||||
return XrCreateReferenceSpace(session, ref createInfo, out space);
|
||||
}
|
||||
|
||||
public XrResult LocateSpace(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location)
|
||||
{
|
||||
if (!isInited)
|
||||
throw new Exception("ViveSpace: not initialized");
|
||||
Debug.Log($"LocateSpace(s={space}, bs={baseSpace}, t={time}");
|
||||
if (!IsInited)
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
//Debug.Log($"LocateSpace(s={space}, bs={baseSpace}, t={time}");
|
||||
return XrLocateSpace(space, baseSpace, time, ref location);
|
||||
}
|
||||
|
||||
public XrResult DestroySpace(XrSpace space)
|
||||
{
|
||||
if (!isInited)
|
||||
throw new Exception("ViveSpace: not initialized");
|
||||
if (!IsInited)
|
||||
return XrResult.XR_ERROR_HANDLE_INVALID;
|
||||
Debug.Log($"DestroySpace({space})");
|
||||
return XrDestroySpace(space);
|
||||
}
|
||||
@@ -157,19 +140,6 @@ namespace VIVE.OpenXR.Feature
|
||||
|
||||
public bool GetRelatedPose(XrSpace baseSpace, XrTime time, out UnityEngine.Pose pose)
|
||||
{
|
||||
#if FAKE_DATA
|
||||
if (Application.isEditor)
|
||||
{
|
||||
// make a random Pose
|
||||
//var pos = new Vector3(UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f));
|
||||
//var rot = new Quaternion(UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f), UnityEngine.Random.Range(-1f, 1f));
|
||||
var pos = Vector3.up;
|
||||
var rot = Quaternion.identity;
|
||||
rot.Normalize();
|
||||
pose = new Pose(pos, rot);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
// If the xrBaseSpace is changed, the pose will be updated.
|
||||
pose = default;
|
||||
XrSpaceLocation location = new XrSpaceLocation();
|
||||
@@ -179,14 +149,14 @@ namespace VIVE.OpenXR.Feature
|
||||
|
||||
if (ret != XrResult.XR_SUCCESS)
|
||||
{
|
||||
Debug.Log("Space: LocateSpace ret=" + ret);
|
||||
//Debug.Log("Space: LocateSpace ret=" + ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug.Log("Space: baseSpace=" + baseSpace + ", space=" + space + ", time=" + time + ", ret=" + ret);
|
||||
Debug.Log("Space: location.locationFlags=" + location.locationFlags);
|
||||
Debug.Log("Space: location.pose.position=" + location.pose.position.x + "," + location.pose.position.y + "," + location.pose.position.z);
|
||||
Debug.Log("Space: location.pose.orientation=" + location.pose.orientation.x + "," + location.pose.orientation.y + "," + location.pose.orientation.z + "," + location.pose.orientation.w);
|
||||
//Debug.Log("Space: baseSpace=" + baseSpace + ", space=" + space + ", time=" + time + ", ret=" + ret);
|
||||
//Debug.Log("Space: location.locationFlags=" + location.locationFlags);
|
||||
//Debug.Log("Space: location.pose.position=" + location.pose.position.x + "," + location.pose.position.y + "," + location.pose.position.z);
|
||||
//Debug.Log("Space: location.pose.orientation=" + location.pose.orientation.x + "," + location.pose.orientation.y + "," + location.pose.orientation.z + "," + location.pose.orientation.w);
|
||||
if ((location.locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_POSITION_VALID_BIT) > 0 &&
|
||||
(location.locationFlags & XrSpaceLocationFlags.XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) > 0)
|
||||
{
|
||||
@@ -211,7 +181,7 @@ namespace VIVE.OpenXR.Feature
|
||||
// Managered resource
|
||||
}
|
||||
// Non managered resource
|
||||
Debug.Log($"Space: DestroySpace({space})");
|
||||
//Debug.Log($"Space: DestroySpace({space})");
|
||||
SpaceWrapper.Instance.DestroySpace(space);
|
||||
space = 0;
|
||||
disposed = true;
|
||||
|
||||
@@ -3,81 +3,180 @@ using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using AOT;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace VIVE.OpenXR
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is made for all features that need to intercept OpenXR API calls.
|
||||
/// Some APIs will be called by Unity internally, and we need to intercept them in c# to get some information.
|
||||
/// Append more interceptable functions for this class by adding a new partial class.
|
||||
/// The partial class can help the delegate name be nice to read and search.
|
||||
/// Please create per function in one partial class.
|
||||
///
|
||||
/// For all features want to use this class, please call <see cref="HookGetInstanceProcAddr" /> in your feature class.
|
||||
/// For example:
|
||||
/// protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||
/// {
|
||||
/// return HtcInterceptors.Instance.HookGetInstanceProcAddr(func);
|
||||
/// }
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// This class is made for all features that need to intercept OpenXR API calls.
|
||||
/// Some APIs will be called by Unity internally, and we need to intercept them in c# to get some information.
|
||||
/// Append more interceptable functions for this class by adding a new partial class.
|
||||
/// The partial class can help the delegate name be nice to read and search.
|
||||
/// Please create per function in one partial class.
|
||||
///
|
||||
/// For all features want to use this class, please call <see cref="HookGetInstanceProcAddr" /> in your feature class.
|
||||
/// For example:
|
||||
/// protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||
/// {
|
||||
/// return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
|
||||
/// }
|
||||
/// </summary>
|
||||
partial class ViveInterceptors
|
||||
{
|
||||
public const string TAG = "Interceptors";
|
||||
|
||||
public static ViveInterceptors instance = null;
|
||||
public static ViveInterceptors Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
instance = new ViveInterceptors();
|
||||
return instance;
|
||||
public const string TAG = "VIVE.OpenXR.ViveInterceptors";
|
||||
static StringBuilder m_sb = null;
|
||||
static StringBuilder sb {
|
||||
get {
|
||||
if (m_sb == null) { m_sb = new StringBuilder(); }
|
||||
return m_sb;
|
||||
}
|
||||
}
|
||||
static void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", TAG, msg); }
|
||||
static void ERROR(StringBuilder msg) { Debug.LogErrorFormat("{0} {1}", TAG, msg); }
|
||||
|
||||
public ViveInterceptors()
|
||||
{
|
||||
Debug.Log("HtcInterceptors");
|
||||
}
|
||||
public static ViveInterceptors instance = null;
|
||||
public static ViveInterceptors Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
instance = new ViveInterceptors();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
bool isInited = false;
|
||||
public ViveInterceptors()
|
||||
{
|
||||
Debug.Log("ViveInterceptors");
|
||||
}
|
||||
|
||||
public delegate XrResult DelegateXrGetInstanceProcAddr(XrInstance instance, string name, out IntPtr function);
|
||||
private static readonly DelegateXrGetInstanceProcAddr hookXrGetInstanceProcAddrHandle = new DelegateXrGetInstanceProcAddr(XrGetInstanceProcAddrInterceptor);
|
||||
private static readonly IntPtr hookGetInstanceProcAddrHandlePtr = Marshal.GetFunctionPointerForDelegate(hookXrGetInstanceProcAddrHandle);
|
||||
static DelegateXrGetInstanceProcAddr XrGetInstanceProcAddrOriginal = null;
|
||||
public delegate XrResult DelegateXrGetInstanceProcAddr(XrInstance instance, string name, out IntPtr function);
|
||||
private static readonly DelegateXrGetInstanceProcAddr hookXrGetInstanceProcAddrHandle = new DelegateXrGetInstanceProcAddr(XrGetInstanceProcAddrInterceptor);
|
||||
private static readonly IntPtr hookGetInstanceProcAddrHandlePtr = Marshal.GetFunctionPointerForDelegate(hookXrGetInstanceProcAddrHandle);
|
||||
static DelegateXrGetInstanceProcAddr XrGetInstanceProcAddrOriginal = null;
|
||||
|
||||
[MonoPInvokeCallback(typeof(DelegateXrGetInstanceProcAddr))]
|
||||
private static XrResult XrGetInstanceProcAddrInterceptor(XrInstance instance, string name, out IntPtr function)
|
||||
{
|
||||
// Custom interceptors
|
||||
if (name == "xrWaitFrame")
|
||||
{
|
||||
Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted.");
|
||||
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
{
|
||||
XrWaitFrameOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrWaitFrame>(function);
|
||||
function = xrWaitFrameInterceptorPtr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||
}
|
||||
[MonoPInvokeCallback(typeof(DelegateXrGetInstanceProcAddr))]
|
||||
private static XrResult XrGetInstanceProcAddrInterceptor(XrInstance instance, string name, out IntPtr function)
|
||||
{
|
||||
// Used to check if the original function is already hooked.
|
||||
if (instance == 0 && name == "ViveInterceptorHooked")
|
||||
{
|
||||
function = IntPtr.Zero;
|
||||
return XrResult.XR_SUCCESS;
|
||||
}
|
||||
|
||||
// Custom interceptors
|
||||
if (name == "xrWaitFrame" && requiredFunctions.Contains(name))
|
||||
{
|
||||
Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted.");
|
||||
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
{
|
||||
XrWaitFrameOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrWaitFrame>(function);
|
||||
function = xrWaitFrameInterceptorPtr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (name == "xrEndFrame" && requiredFunctions.Contains(name))
|
||||
{
|
||||
Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted.");
|
||||
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
{
|
||||
XrEndFrameOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrEndFrame>(function);
|
||||
function = xrEndFrameInterceptorPtr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if PERFORMANCE_TEST
|
||||
if (name == "xrLocateSpace" && requiredFunctions.Contains(name))
|
||||
{
|
||||
Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted.");
|
||||
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
{
|
||||
XrLocateSpaceOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrLocateSpace>(function);
|
||||
function = xrLocateSpaceInterceptorPtr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
if (name == "xrPollEvent" && requiredFunctions.Contains(name))
|
||||
{
|
||||
Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted.");
|
||||
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
{
|
||||
xrPollEventOrigin = Marshal.GetDelegateForFunctionPointer < xrPollEventDelegate > (function);
|
||||
function = xrPollEventPtr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
if (name == "xrBeginSession" && requiredFunctions.Contains(name))
|
||||
{
|
||||
Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted.");
|
||||
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||
if (ret == XrResult.XR_SUCCESS)
|
||||
{
|
||||
xrBeginSessionOrigin = Marshal.GetDelegateForFunctionPointer<xrBeginSessionDelegate>(function);
|
||||
function = xrBeginSessionPtr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
return XrGetInstanceProcAddrOriginal(instance, name, out function);
|
||||
}
|
||||
|
||||
public IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||
{
|
||||
Debug.Log($"{TAG}: HookGetInstanceProcAddr");
|
||||
if (XrGetInstanceProcAddrOriginal == null)
|
||||
{
|
||||
Debug.Log($"{TAG}: registering our own xrGetInstanceProcAddr");
|
||||
XrGetInstanceProcAddrOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrGetInstanceProcAddr>(func);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor) {
|
||||
// This is a trick to check if the original function is already hooked by this class. Sometimes, the static XrGetInstanceProcAddrOriginal didn't work as expected.
|
||||
Debug.Log($"{TAG}: Check if duplicate hooked by this script with instance=0 and \"ViveInterceptorHooked\" name. If following a loader error, ignore it.");
|
||||
// E OpenXR-Loader: Error [SPEC | xrGetInstanceProcAddr | VUID-xrGetInstanceProcAddr-instance-parameter] : XR_NULL_HANDLE for instance but query for ViveInterceptorHooked requires a valid instance
|
||||
|
||||
// Call XrGetInstanceProcAddrOriginal to check if the original function is already hooked by this class
|
||||
if (XrGetInstanceProcAddrOriginal(0, "ViveInterceptorHooked", out IntPtr function) == XrResult.XR_SUCCESS)
|
||||
{
|
||||
// If it is called successfully, it means the original function is already hooked. So we should return the original function.
|
||||
Debug.Log($"{TAG}: Already hooked");
|
||||
return func;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return hookGetInstanceProcAddrHandlePtr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dont return hookGetInstanceProcAddrHandlePtr again.
|
||||
// If this hook function is called by multiple features, it should only work at the first time.
|
||||
// If called by other features, it should return the original function.
|
||||
return func;
|
||||
}
|
||||
}
|
||||
|
||||
static readonly List<string> requiredFunctions = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Call before <see cref="HookGetInstanceProcAddr" /> to add required functions."/>
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
public void AddRequiredFunction(string name)
|
||||
{
|
||||
if (requiredFunctions.Contains(name)) return;
|
||||
Debug.Log($"{TAG}: AddRequiredFunction({name})");
|
||||
requiredFunctions.Add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr HookGetInstanceProcAddr(IntPtr func)
|
||||
{
|
||||
Debug.Log($"{TAG}: registering our own xrGetInstanceProcAddr");
|
||||
if (XrGetInstanceProcAddrOriginal == null)
|
||||
{
|
||||
XrGetInstanceProcAddrOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrGetInstanceProcAddr>(func);
|
||||
isInited = true;
|
||||
return hookGetInstanceProcAddrHandlePtr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return func;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
#define DEBUG
|
||||
|
||||
using AOT;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine.Profiling;
|
||||
using VIVE.OpenXR.FrameSynchronization;
|
||||
|
||||
namespace VIVE.OpenXR
|
||||
{
|
||||
partial class ViveInterceptors
|
||||
{
|
||||
#region xrBeginSession
|
||||
public delegate XrResult xrBeginSessionDelegate(XrSession session, ref XrSessionBeginInfo beginInfo);
|
||||
private static xrBeginSessionDelegate xrBeginSessionOrigin = null;
|
||||
|
||||
[MonoPInvokeCallback(typeof(xrBeginSessionDelegate))]
|
||||
private static XrResult xrBeginSessionInterceptor(XrSession session, ref XrSessionBeginInfo beginInfo)
|
||||
{
|
||||
Profiler.BeginSample("ViveInterceptors:BeginSession");
|
||||
XrResult result = XrResult.XR_ERROR_FUNCTION_UNSUPPORTED;
|
||||
|
||||
if (xrBeginSessionOrigin != null)
|
||||
{
|
||||
if (m_EnableFrameSynchronization)
|
||||
{
|
||||
frameSynchronizationSessionBeginInfo.mode = m_FrameSynchronizationMode;
|
||||
frameSynchronizationSessionBeginInfo.next = beginInfo.next;
|
||||
beginInfo.next = Marshal.AllocHGlobal(Marshal.SizeOf(frameSynchronizationSessionBeginInfo));
|
||||
|
||||
long offset = 0;
|
||||
if (IntPtr.Size == 4)
|
||||
offset = beginInfo.next.ToInt32();
|
||||
else
|
||||
offset = beginInfo.next.ToInt64();
|
||||
|
||||
IntPtr frame_synchronization_session_begin_info_ptr = new IntPtr(offset);
|
||||
Marshal.StructureToPtr(frameSynchronizationSessionBeginInfo, frame_synchronization_session_begin_info_ptr, false);
|
||||
|
||||
#if DEBUG
|
||||
if (IntPtr.Size == 4)
|
||||
offset = beginInfo.next.ToInt32();
|
||||
else
|
||||
offset = beginInfo.next.ToInt64();
|
||||
|
||||
IntPtr fs_begin_info_ptr = new IntPtr(offset);
|
||||
XrFrameSynchronizationSessionBeginInfoHTC fsBeginInfo = (XrFrameSynchronizationSessionBeginInfoHTC)Marshal.PtrToStructure(fs_begin_info_ptr, typeof(XrFrameSynchronizationSessionBeginInfoHTC));
|
||||
|
||||
sb.Clear().Append("xrBeginSessionInterceptor() beginInfo.next = (").Append(fsBeginInfo.type).Append(", ").Append(fsBeginInfo.mode).Append(")"); DEBUG(sb);
|
||||
#endif
|
||||
}
|
||||
|
||||
result = xrBeginSessionOrigin(session, ref beginInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Clear().Append("xrBeginSessionInterceptor() Not assign xrBeginSession!"); ERROR(sb);
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static readonly xrBeginSessionDelegate xrBeginSession = new xrBeginSessionDelegate(xrBeginSessionInterceptor);
|
||||
private static readonly IntPtr xrBeginSessionPtr = Marshal.GetFunctionPointerForDelegate(xrBeginSession);
|
||||
#endregion
|
||||
|
||||
private static XrFrameSynchronizationSessionBeginInfoHTC frameSynchronizationSessionBeginInfo = XrFrameSynchronizationSessionBeginInfoHTC.identity;
|
||||
private static bool m_EnableFrameSynchronization = false;
|
||||
private static XrFrameSynchronizationModeHTC m_FrameSynchronizationMode = XrFrameSynchronizationModeHTC.XR_FRAME_SYNCHRONIZATION_MODE_STABILIZED_HTC;
|
||||
/// <summary>
|
||||
/// Activate or deactivate the Frame Synchronization feature.
|
||||
/// </summary>
|
||||
/// <param name="active">True for activate</param>
|
||||
/// <param name="mode">The <see cref="XrFrameSynchronizationModeHTC"/> used for Frame Synchronization.</param>
|
||||
public void ActivateFrameSynchronization(bool active, XrFrameSynchronizationModeHTC mode)
|
||||
{
|
||||
m_EnableFrameSynchronization = active;
|
||||
m_FrameSynchronizationMode = mode;
|
||||
sb.Clear().Append("ActivateFrameSynchronization() ").Append(active ? "enable " : "disable ").Append(mode); DEBUG(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c222b96d7eb4ca4bb6390e07b1967bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
|
||||
using AOT;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine.Profiling;
|
||||
using VIVE.OpenXR.DisplayRefreshRate;
|
||||
using VIVE.OpenXR.Passthrough;
|
||||
using VIVE.OpenXR.UserPresence;
|
||||
|
||||
namespace VIVE.OpenXR
|
||||
{
|
||||
partial class ViveInterceptors
|
||||
{
|
||||
#region xrPollEvent
|
||||
public delegate XrResult xrPollEventDelegate(XrInstance instance, ref XrEventDataBuffer eventData);
|
||||
private static xrPollEventDelegate xrPollEventOrigin = null;
|
||||
|
||||
[MonoPInvokeCallback(typeof(xrPollEventDelegate))]
|
||||
private static XrResult xrPollEventInterceptor(XrInstance instance, ref XrEventDataBuffer eventData)
|
||||
{
|
||||
Profiler.BeginSample("ViveInterceptors:WaitFrame");
|
||||
XrResult result = XrResult.XR_SUCCESS;
|
||||
|
||||
if (xrPollEventOrigin != null)
|
||||
{
|
||||
result = xrPollEventOrigin(instance, ref eventData);
|
||||
|
||||
if (result == XrResult.XR_SUCCESS)
|
||||
{
|
||||
sb.Clear().Append("xrPollEventInterceptor() xrPollEvent ").Append(eventData.type); DEBUG(sb);
|
||||
switch(eventData.type)
|
||||
{
|
||||
case XrStructureType.XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_RATE_CHANGED_HTC:
|
||||
if (XrEventDataPassthroughConfigurationImageRateChangedHTC.Get(eventData, out XrEventDataPassthroughConfigurationImageRateChangedHTC eventDataPassthroughConfigurationImageRate))
|
||||
{
|
||||
fromImageRate = eventDataPassthroughConfigurationImageRate.fromImageRate;
|
||||
toImageRate = eventDataPassthroughConfigurationImageRate.toImageRate;
|
||||
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_RATE_CHANGED_HTC")
|
||||
.Append(", fromImageRate.srcImageRate: ").Append(fromImageRate.srcImageRate)
|
||||
.Append(", fromImageRatesrc.dstImageRate: ").Append(fromImageRate.dstImageRate)
|
||||
.Append(", toImageRate.srcImageRate: ").Append(toImageRate.srcImageRate)
|
||||
.Append(", toImageRate.dstImageRate: ").Append(toImageRate.dstImageRate);
|
||||
DEBUG(sb);
|
||||
VivePassthroughImageRateChanged.Send(fromImageRate.srcImageRate, fromImageRate.dstImageRate, toImageRate.srcImageRate, toImageRate.dstImageRate);
|
||||
}
|
||||
break;
|
||||
case XrStructureType.XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_QUALITY_CHANGED_HTC:
|
||||
if (XrEventDataPassthroughConfigurationImageQualityChangedHTC.Get(eventData, out XrEventDataPassthroughConfigurationImageQualityChangedHTC eventDataPassthroughConfigurationImageQuality))
|
||||
{
|
||||
fromImageQuality = eventDataPassthroughConfigurationImageQuality.fromImageQuality;
|
||||
toImageQuality = eventDataPassthroughConfigurationImageQuality.toImageQuality;
|
||||
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_QUALITY_CHANGED_HTC")
|
||||
.Append(", fromImageQuality: ").Append(fromImageQuality.scale)
|
||||
.Append(", toImageQuality: ").Append(toImageQuality.scale);
|
||||
DEBUG(sb);
|
||||
VivePassthroughImageQualityChanged.Send(fromImageQuality.scale, toImageQuality.scale);
|
||||
}
|
||||
break;
|
||||
case XrStructureType.XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB:
|
||||
if(XrEventDataDisplayRefreshRateChangedFB.Get(eventData, out XrEventDataDisplayRefreshRateChangedFB eventDataDisplayRefreshRate))
|
||||
{
|
||||
fromDisplayRefreshRate = eventDataDisplayRefreshRate.fromDisplayRefreshRate;
|
||||
toDisplayRefreshRate = eventDataDisplayRefreshRate.toDisplayRefreshRate;
|
||||
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB")
|
||||
.Append(", fromDisplayRefreshRate: ").Append(fromDisplayRefreshRate)
|
||||
.Append(", toDisplayRefreshRate: ").Append(toDisplayRefreshRate);
|
||||
DEBUG(sb);
|
||||
ViveDisplayRefreshRateChanged.Send(fromDisplayRefreshRate, toDisplayRefreshRate);
|
||||
}
|
||||
break;
|
||||
case XrStructureType.XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
|
||||
if (XrEventDataSessionStateChanged.Get(eventData, out XrEventDataSessionStateChanged eventDataSession))
|
||||
{
|
||||
switch(eventDataSession.state)
|
||||
{
|
||||
case XrSessionState.XR_SESSION_STATE_READY:
|
||||
isUserPresent = true;
|
||||
break;
|
||||
case XrSessionState.XR_SESSION_STATE_STOPPING:
|
||||
isUserPresent = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED")
|
||||
.Append(", session: ").Append(eventDataSession.session)
|
||||
.Append(", state: ").Append(eventDataSession.state)
|
||||
.Append(", isUserPresent: ").Append(isUserPresent);
|
||||
DEBUG(sb);
|
||||
}
|
||||
break;
|
||||
case XrStructureType.XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT:
|
||||
if (XrEventDataUserPresenceChangedEXT.Get(eventData, out XrEventDataUserPresenceChangedEXT eventDataUserPresence))
|
||||
{
|
||||
isUserPresent = eventDataUserPresence.isUserPresent;
|
||||
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT")
|
||||
.Append(", session: ").Append(eventDataUserPresence.session)
|
||||
.Append(", isUserPresent: ").Append(isUserPresent);
|
||||
DEBUG(sb);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//sb.Clear().Append("xrPollEventInterceptor() xrPollEvent result: ").Append(result).Append(", isUserPresent: ").Append(isUserPresent); DEBUG(sb);
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static readonly xrPollEventDelegate xrPollEvent = new xrPollEventDelegate(xrPollEventInterceptor);
|
||||
private static readonly IntPtr xrPollEventPtr = Marshal.GetFunctionPointerForDelegate(xrPollEvent);
|
||||
#endregion
|
||||
|
||||
private static bool isUserPresent = true;
|
||||
public bool IsUserPresent() { return isUserPresent; }
|
||||
|
||||
private static float fromDisplayRefreshRate, toDisplayRefreshRate;
|
||||
public float FromDisplayRefreshRate() { return fromDisplayRefreshRate; }
|
||||
public float ToDisplayRefreshRate() { return toDisplayRefreshRate; }
|
||||
|
||||
private static XrPassthroughConfigurationImageRateHTC fromImageRate, toImageRate;
|
||||
private static XrPassthroughConfigurationImageQualityHTC fromImageQuality, toImageQuality;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2cc5716d3f563f49a47da6c1bd8ccbe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright HTC Corporation All Rights Reserved.
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using AOT;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace VIVE.OpenXR
|
||||
{
|
||||
partial class ViveInterceptors
|
||||
{
|
||||
public struct XrCompositionLayerBaseHeader
|
||||
{
|
||||
public XrStructureType type; // This base structure itself has no associated XrStructureType value.
|
||||
public System.IntPtr next;
|
||||
public XrCompositionLayerFlags layerFlags;
|
||||
public XrSpace space;
|
||||
}
|
||||
|
||||
public struct XrFrameEndInfo
|
||||
{
|
||||
public XrStructureType type;
|
||||
public System.IntPtr next;
|
||||
public XrTime displayTime;
|
||||
public XrEnvironmentBlendMode environmentBlendMode;
|
||||
public uint layerCount;
|
||||
public IntPtr layers; // XrCompositionLayerBaseHeader IntPtr array
|
||||
}
|
||||
|
||||
public delegate XrResult DelegateXrEndFrame(XrSession session, ref XrFrameEndInfo frameEndInfo);
|
||||
private static readonly DelegateXrEndFrame xrEndFrameInterceptorHandle = new DelegateXrEndFrame(XrEndFrameInterceptor);
|
||||
private static readonly IntPtr xrEndFrameInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrEndFrameInterceptorHandle);
|
||||
static DelegateXrEndFrame XrEndFrameOriginal = null;
|
||||
|
||||
[MonoPInvokeCallback(typeof(DelegateXrEndFrame))]
|
||||
private static XrResult XrEndFrameInterceptor(XrSession session, ref XrFrameEndInfo frameEndInfo)
|
||||
{
|
||||
// instance must not null
|
||||
//if (instance == null)
|
||||
// return XrEndFrameOriginal(session, ref frameEndInfo);
|
||||
Profiler.BeginSample("VI:EndFrame");
|
||||
XrResult result = XrResult.XR_SUCCESS;
|
||||
if (instance.BeforeOriginalEndFrame != null &&
|
||||
!instance.BeforeOriginalEndFrame(session, ref frameEndInfo, ref result))
|
||||
{
|
||||
Profiler.EndSample();
|
||||
return result;
|
||||
}
|
||||
result = XrEndFrameOriginal(session, ref frameEndInfo);
|
||||
instance.AfterOriginalEndFrame?.Invoke(session, ref frameEndInfo, ref result);
|
||||
Profiler.EndSample();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If you return false, the original function will not be called.
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="frameEndInfo"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public delegate bool DelegateXrEndFrameInterceptor(XrSession session, ref XrFrameEndInfo frameEndInfo, ref XrResult result);
|
||||
|
||||
/// <summary>
|
||||
/// Use this to intercept the original function. This will be called before the original function.
|
||||
/// </summary>
|
||||
public DelegateXrEndFrameInterceptor BeforeOriginalEndFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Use this to intercept the original function. This will be called after the original function.
|
||||
/// </summary>
|
||||
public DelegateXrEndFrameInterceptor AfterOriginalEndFrame;
|
||||
|
||||
#if PERFORMANCE_TEST
|
||||
public delegate XrResult DelegateXrLocateSpace(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location);
|
||||
private static readonly DelegateXrLocateSpace xrLocateSpaceInterceptorHandle = new DelegateXrLocateSpace(XrLocateSpaceInterceptor);
|
||||
private static readonly IntPtr xrLocateSpaceInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrLocateSpaceInterceptorHandle);
|
||||
static DelegateXrLocateSpace XrLocateSpaceOriginal = null;
|
||||
|
||||
[MonoPInvokeCallback(typeof(DelegateXrLocateSpace))]
|
||||
public static XrResult XrLocateSpaceInterceptor(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location)
|
||||
{
|
||||
Profiler.BeginSample("VI:LocateSpace");
|
||||
var ret = XrLocateSpaceOriginal(space, baseSpace, time, ref location);
|
||||
Profiler.EndSample();
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6bf7cf55d82ac6343b4eda92d1197a66
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -3,59 +3,106 @@ using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using AOT;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace VIVE.OpenXR
|
||||
{
|
||||
partial class ViveInterceptors
|
||||
{
|
||||
#region XRWaitFrame
|
||||
public struct XrFrameWaitInfo
|
||||
{
|
||||
public XrStructureType type;
|
||||
public IntPtr next;
|
||||
}
|
||||
partial class ViveInterceptors
|
||||
{
|
||||
#region XRWaitFrame
|
||||
public struct XrFrameWaitInfo
|
||||
{
|
||||
public XrStructureType type;
|
||||
public IntPtr next;
|
||||
}
|
||||
|
||||
public struct XrFrameState
|
||||
{
|
||||
public XrStructureType type;
|
||||
public IntPtr next;
|
||||
public XrTime predictedDisplayTime;
|
||||
public XrDuration predictedDisplayPeriod;
|
||||
public XrBool32 shouldRender;
|
||||
}
|
||||
public struct XrFrameState
|
||||
{
|
||||
public XrStructureType type;
|
||||
public IntPtr next;
|
||||
public XrTime predictedDisplayTime;
|
||||
public XrDuration predictedDisplayPeriod;
|
||||
public XrBool32 shouldRender;
|
||||
}
|
||||
|
||||
public delegate XrResult DelegateXrWaitFrame(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState);
|
||||
private static readonly DelegateXrWaitFrame xrWaitFrameInterceptorHandle = new DelegateXrWaitFrame(XrWaitFrameInterceptor);
|
||||
private static readonly IntPtr xrWaitFrameInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrWaitFrameInterceptorHandle);
|
||||
static DelegateXrWaitFrame XrWaitFrameOriginal = null;
|
||||
bool isWaitFrameIntercepted = false;
|
||||
|
||||
[MonoPInvokeCallback(typeof(DelegateXrWaitFrame))]
|
||||
private static XrResult XrWaitFrameInterceptor(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState)
|
||||
{
|
||||
var ret = XrWaitFrameOriginal(session, ref frameWaitInfo, ref frameState);
|
||||
currentFrameState = frameState;
|
||||
return ret;
|
||||
}
|
||||
public delegate XrResult DelegateXrWaitFrame(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState);
|
||||
private static readonly DelegateXrWaitFrame xrWaitFrameInterceptorHandle = new DelegateXrWaitFrame(XrWaitFrameInterceptor);
|
||||
private static readonly IntPtr xrWaitFrameInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrWaitFrameInterceptorHandle);
|
||||
static DelegateXrWaitFrame XrWaitFrameOriginal = null;
|
||||
|
||||
static XrFrameState currentFrameState = new XrFrameState() { predictedDisplayTime = 0 };
|
||||
[MonoPInvokeCallback(typeof(DelegateXrWaitFrame))]
|
||||
private static XrResult XrWaitFrameInterceptor(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState)
|
||||
{
|
||||
// instance must not null
|
||||
//if (instance == null)
|
||||
// return XrWaitFrameOriginal(session, ref frameWaitInfo, ref frameState);
|
||||
Profiler.BeginSample("VI:WaitFrame");
|
||||
instance.isWaitFrameIntercepted = true;
|
||||
XrResult result = XrResult.XR_SUCCESS;
|
||||
if (instance.BeforeOriginalWaitFrame != null &&
|
||||
!instance.BeforeOriginalWaitFrame(session, ref frameWaitInfo, ref frameState, ref result))
|
||||
{
|
||||
Profiler.EndSample();
|
||||
return result;
|
||||
}
|
||||
var ret = XrWaitFrameOriginal(session, ref frameWaitInfo, ref frameState);
|
||||
instance.AfterOriginalWaitFrame?.Invoke(session, ref frameWaitInfo, ref frameState, ref result);
|
||||
currentFrameState = frameState;
|
||||
Profiler.EndSample();
|
||||
return result;
|
||||
}
|
||||
|
||||
public XrFrameState GetCurrentFrameState()
|
||||
{
|
||||
if (!isInited) throw new Exception("ViveInterceptors is not inited");
|
||||
static XrFrameState currentFrameState = new XrFrameState() { predictedDisplayTime = 0 };
|
||||
|
||||
return currentFrameState;
|
||||
}
|
||||
/// <summary>
|
||||
/// Get the waitframe's result: XrFrameState. This result used in update is not matching the current frame. Use it after onBeforeRender.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public XrFrameState GetCurrentFrameState()
|
||||
{
|
||||
if (!isWaitFrameIntercepted) throw new Exception("ViveInterceptors is not intercepted");
|
||||
|
||||
public XrTime GetPredictTime()
|
||||
{
|
||||
if (!isInited) throw new Exception("ViveInterceptors is not inited");
|
||||
return currentFrameState;
|
||||
}
|
||||
|
||||
Debug.Log($"{TAG}: XrWaitFrameInterceptor(predictedDisplayTime={currentFrameState.predictedDisplayTime}");
|
||||
if (currentFrameState.predictedDisplayTime == 0)
|
||||
return new XrTime((long)(1000000L * (Time.unscaledTimeAsDouble + 0.011f)));
|
||||
else
|
||||
return currentFrameState.predictedDisplayTime;
|
||||
}
|
||||
#endregion XRWaitFrame
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Must request xrWaitFrame before calling this function. This result used in update is not matching the current frame. Use it after onBeforeRender.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public XrTime GetPredictTime()
|
||||
{
|
||||
if (!isWaitFrameIntercepted) throw new Exception("ViveInterceptors is not intercepted");
|
||||
|
||||
//Debug.Log($"{TAG}: XrWaitFrameInterceptor(predictedDisplayTime={currentFrameState.predictedDisplayTime}");
|
||||
if (currentFrameState.predictedDisplayTime == 0)
|
||||
return new XrTime((long)(1000000L * (Time.unscaledTimeAsDouble + 0.011f)));
|
||||
else
|
||||
return currentFrameState.predictedDisplayTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register WaitFrame event
|
||||
/// </summary>
|
||||
/// <param name="session"></param>
|
||||
/// <param name="frameWaitInfo"></param>
|
||||
/// <param name="frameState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
public delegate bool DelegateXrWaitFrameInterceptor(XrSession session, ref XrFrameWaitInfo frameWaitInfo, ref XrFrameState frameState, ref XrResult result);
|
||||
|
||||
/// <summary>
|
||||
/// Use this to intercept the original function. This will be called before the original function.
|
||||
/// </summary>
|
||||
public DelegateXrWaitFrameInterceptor BeforeOriginalWaitFrame;
|
||||
|
||||
/// <summary>
|
||||
/// Use this to intercept the original function. This will be called after the original function.
|
||||
/// </summary>
|
||||
public DelegateXrWaitFrameInterceptor AfterOriginalWaitFrame;
|
||||
#endregion XRWaitFrame
|
||||
}
|
||||
}
|
||||
|
||||
329
com.htc.upm.vive.openxr/Runtime/Common/ViveRenderThreadTask.cs
Normal file
329
com.htc.upm.vive.openxr/Runtime/Common/ViveRenderThreadTask.cs
Normal file
@@ -0,0 +1,329 @@
|
||||
using AOT;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
|
||||
namespace VIVE.OpenXR.Common.RenderThread
|
||||
{
|
||||
#region syncObject
|
||||
public class Message
|
||||
{
|
||||
public bool isFree = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MessagePool class manages a pool of message objects for reuse. You can enter any kind of message object.
|
||||
/// However when obtain, the message object will not able to cast to the type you want.
|
||||
/// You should only use one kind of message. Not mix different kind of message.
|
||||
/// </summary>
|
||||
public class MessagePool
|
||||
{
|
||||
// pool member is used to store message objects in a list.
|
||||
// Note that the size of this list will dynamically adjust as needed but will not automatically shrink.
|
||||
private readonly List<Message> pool = new List<Message>(2) { };
|
||||
private int index = 0;
|
||||
|
||||
public MessagePool() { }
|
||||
|
||||
// Next method calculates the next index value for cycling through message objects in the pool.
|
||||
private int Next(int value)
|
||||
{
|
||||
if (++value >= pool.Count)
|
||||
value = 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
// Obtain method retrieves a message object from the pool.
|
||||
// Ensure proper state setup for the message after retrieval and call Release() to the message after use.
|
||||
public T Obtain<T>() where T : Message, new()
|
||||
{
|
||||
int c = pool.Count;
|
||||
int i = index;
|
||||
for (int j = 0; j < c; i++, j++)
|
||||
{
|
||||
if (i >= c)
|
||||
i = 0;
|
||||
if (pool[i].isFree)
|
||||
{
|
||||
//Debug.LogError("Obtain idx=" + i);
|
||||
index = i;
|
||||
return (T)pool[i];
|
||||
}
|
||||
}
|
||||
index = Next(i);
|
||||
var newItem = new T()
|
||||
{
|
||||
isFree = true
|
||||
};
|
||||
pool.Insert(index, newItem);
|
||||
Debug.Log("RT.MessagePool.Obtain() pool count=" + pool.Count);
|
||||
return newItem;
|
||||
}
|
||||
|
||||
// Lock method marks a message as "in use" to prevent other code from reusing it.
|
||||
// This is already called to the message obtained from the pool.
|
||||
public static void Lock(Message msg)
|
||||
{
|
||||
msg.isFree = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release method marks a message as "free" so that other code can reuse it.
|
||||
/// You can use it in RenderThread. It will not trigger the GC event.
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public static void Release(Message msg)
|
||||
{
|
||||
msg.isFree = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PreAllocatedQueue class is a message queue based on MessagePool for preallocating message objects.
|
||||
/// Its main functionality is to add message objects to the queue and retrieve them from the queue.
|
||||
/// Messages should be enqueued in GameThread and dequeued in RenderThread.
|
||||
/// In render thread, dequeue will not trigger the GC event. Because the queue is preallocated.
|
||||
/// The 'lock' expression is not used for list's size change. Because lock should be avoid used in RenderThread.
|
||||
/// Set the queueSize as the double count of message you want to pass to render thread in one frame, and the
|
||||
/// list will never change size during runtime. Therefore we don't need to use 'lock' to protect the list.
|
||||
/// </summary>
|
||||
public class PreAllocatedQueue : MessagePool
|
||||
{
|
||||
// list member is used to store preallocated message objects in a list.
|
||||
// Note that the size of this list is set during initialization and does not dynamically adjust.
|
||||
private List<Message> list = new List<Message>();
|
||||
private int queueBegin = 0;
|
||||
private int queueEnd = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The queueSize should be the double count of message you want to pass to render thread in one frame.
|
||||
/// </summary>
|
||||
/// <param name="queueSize"></param>
|
||||
public PreAllocatedQueue(int queueSize = 2) : base() {
|
||||
for (int i = 0; i < queueSize; i++)
|
||||
{
|
||||
list.Add(null);
|
||||
}
|
||||
}
|
||||
|
||||
private int Next(int value)
|
||||
{
|
||||
if (++value >= list.Count)
|
||||
value = 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueue method adds a message object to the queue.
|
||||
/// If the queue is full, the new message is added to the end of the list.
|
||||
///
|
||||
/// This function is designed to use the message object obtained from the MessagePool.
|
||||
/// Ensure only one type of message object is used in the queue.
|
||||
///
|
||||
/// Enqueue will increase the queue size if the queue is full. This may trigger GC.Alloc.
|
||||
/// This function should be used in GameThread.
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
public void Enqueue(Message msg)
|
||||
{
|
||||
Lock(msg);
|
||||
queueEnd = Next(queueEnd);
|
||||
|
||||
// If the queue is full, add the message to the end of the list. Should not let it happen.
|
||||
// Use larger queue size to avoid this issue.
|
||||
// If you see the error log here, you should increase the queue size in your design.
|
||||
if (queueEnd == queueBegin)
|
||||
{
|
||||
// Should let Insert and queueBegin be atomic. No lock protection here.
|
||||
list.Insert(queueEnd, msg);
|
||||
queueBegin++;
|
||||
Debug.LogError("RT.MessagePool.Enqueue() list count=" + list.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
list[queueEnd] = msg;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dequeue method retrieves a message object from the queue.
|
||||
/// This method returns the first message object in the queue and removes it from the queue.
|
||||
/// This function will not trigger the GC event. Free to use in RenderThread.
|
||||
/// After use the Message, call Release() to the message.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Message Dequeue()
|
||||
{
|
||||
// No lock protection here. If list is not change size, it is safe.
|
||||
// However if list changed size, it is safe in most case.
|
||||
queueBegin = Next(queueBegin);
|
||||
return list[queueBegin];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RenderThreadTask class is used to execute specified tasks on the rendering thread.
|
||||
/// You don't need to develop a native function to run your task on the rendering thread.
|
||||
/// And you don't need to design how to pass data to render thread.
|
||||
/// This class can be run in Unity Editor since Unity 2021. Test your code in Unity Editor can save your time.
|
||||
///
|
||||
/// You should only create RenderThreadTask as static readonly. Do not create RenderThreadTask in dynamic.
|
||||
///
|
||||
/// You should not run Unity.Engine code in RenderThread. It will cause the Unity.Engine to hang.
|
||||
/// Any exception will not be caught and shown in RenderThread.
|
||||
/// You should print your error message out to clearify your issue.
|
||||
///
|
||||
/// The 'lock' expression is not used here. Because I believe the lock is not necessary in this case.
|
||||
/// And the lock will cause the performance issue. All the design here help you not to use 'lock'.
|
||||
/// </summary>
|
||||
public class RenderThreadTask
|
||||
{
|
||||
private static IntPtr GetFunctionPointerForDelegate(Delegate del)
|
||||
{
|
||||
return System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(del);
|
||||
}
|
||||
|
||||
public delegate void RenderEventDelegate(int e);
|
||||
private static readonly RenderEventDelegate handle = new RenderEventDelegate(RunSyncObjectInRenderThread);
|
||||
private static readonly IntPtr handlePtr = GetFunctionPointerForDelegate(handle);
|
||||
|
||||
public delegate void Receiver(PreAllocatedQueue dataQueue);
|
||||
|
||||
// CommandList is used to store all RenderThreadTask objects.
|
||||
// Do not create RenderThreadTask object in dynamic. It will cause the CommandList to increase infinitly.
|
||||
private static List<RenderThreadTask> CommandList = new List<RenderThreadTask>();
|
||||
|
||||
private PreAllocatedQueue queue;
|
||||
public PreAllocatedQueue Queue { get { return queue; } }
|
||||
|
||||
private readonly Receiver receiver;
|
||||
private readonly int id;
|
||||
|
||||
/// <summary>
|
||||
/// Input the receiver as render thread callback. The receiver will be executed in render thread.
|
||||
/// queueSize should be the double count of message you want to pass to render thread in one frame.
|
||||
/// </summary>
|
||||
/// <param name="render">The callback in render thread.</param>
|
||||
/// <param name="queueSize">If issue this event once in a frame, set queueSize as 2.</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public RenderThreadTask(Receiver render, int queueSize = 2)
|
||||
{
|
||||
queue = new PreAllocatedQueue(queueSize);
|
||||
receiver = render;
|
||||
if (receiver == null)
|
||||
throw new ArgumentNullException("receiver should not be null");
|
||||
|
||||
CommandList.Add(this);
|
||||
id = CommandList.IndexOf(this);
|
||||
}
|
||||
|
||||
~RenderThreadTask()
|
||||
{
|
||||
try { CommandList.RemoveAt(id); } finally { }
|
||||
}
|
||||
|
||||
void IssuePluginEvent(IntPtr callback, int eventID)
|
||||
{
|
||||
// Older version will hang after run script in render thread.
|
||||
GL.IssuePluginEvent(callback, eventID);
|
||||
return;
|
||||
}
|
||||
|
||||
void IssuePluginEvent(CommandBuffer cmdBuf, IntPtr callback, int eventID)
|
||||
{
|
||||
cmdBuf.IssuePluginEvent(callback, eventID);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IssueEvent method submits this task's receiver, which is set in constructor, to be executed on the rendering thread.
|
||||
/// </summary>
|
||||
public void IssueEvent()
|
||||
{
|
||||
// Let the render thread run the RunSyncObjectInRenderThread(id)
|
||||
IssuePluginEvent(handlePtr, id);
|
||||
}
|
||||
|
||||
public void IssueInCommandBuffer(CommandBuffer cmdBuf)
|
||||
{
|
||||
// Let the render thread run the RunSyncObjectInRenderThread(id)
|
||||
IssuePluginEvent(cmdBuf, handlePtr, id);
|
||||
}
|
||||
|
||||
// Called by RunSyncObjectInRenderThread()
|
||||
private void Receive()
|
||||
{
|
||||
receiver(queue);
|
||||
}
|
||||
|
||||
// RunSyncObjectInRenderThread method is a static method used to execute a specified task on the rendering thread.
|
||||
// This method is invoked by Unity's rendering event mechanism and does not need to be called directly by developers.
|
||||
[MonoPInvokeCallback(typeof(RenderEventDelegate))]
|
||||
private static void RunSyncObjectInRenderThread(int id)
|
||||
{
|
||||
CommandList[id].Receive();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region sample
|
||||
// Not to compile this sample into your application. Just for reference. You can run this sample in Unity Editor and it will work.
|
||||
#if UNITY_EDITOR
|
||||
public class ViveRenderThreadTaskSample : MonoBehaviour
|
||||
{
|
||||
// Create your own message class.
|
||||
internal class SampleMessage : Message
|
||||
{
|
||||
public int dataPassedToRenderThread;
|
||||
}
|
||||
|
||||
// Use static readonly to create RenderThreadTask. Keep internal to avoid miss use by other developers.
|
||||
internal static readonly RenderThreadTask sampleRenderThreadTask1 = new RenderThreadTask(SampleReceiver1);
|
||||
// Different task use different RenderThreadTask and different recevier.
|
||||
internal static readonly RenderThreadTask sampleRenderThreadTask2 = new RenderThreadTask(SampleReceiver2);
|
||||
|
||||
private static void SampleReceiver1(PreAllocatedQueue dataQueue)
|
||||
{
|
||||
var msg = dataQueue.Dequeue() as SampleMessage;
|
||||
// no need to check msg if it is null because your design should avoid it.
|
||||
// Keep data before release. Use local variable to keep data and release msg early. Should not keep the msg instance itself.
|
||||
var data = msg.dataPassedToRenderThread;
|
||||
// Make sure release the msg if finished. Other wise the memory will keep increasing when Obtain.
|
||||
MessagePool.Release(msg);
|
||||
|
||||
Debug.Log("Task1, the data passed to render thread: " + data);
|
||||
}
|
||||
|
||||
private static void SampleReceiver2(PreAllocatedQueue dataQueue)
|
||||
{
|
||||
var msg = dataQueue.Dequeue() as SampleMessage;
|
||||
var data = msg.dataPassedToRenderThread;
|
||||
MessagePool.Release(msg);
|
||||
Debug.Log("Task2, the data passed to render thread: " + data);
|
||||
}
|
||||
|
||||
// Send a message to the render thread every frame.
|
||||
private void Update()
|
||||
{
|
||||
// Make sure only one kind of message object is used in the queue.
|
||||
var msg = sampleRenderThreadTask1.Queue.Obtain<SampleMessage>();
|
||||
msg.dataPassedToRenderThread = 123;
|
||||
sampleRenderThreadTask1.Queue.Enqueue(msg);
|
||||
sampleRenderThreadTask1.IssueEvent();
|
||||
}
|
||||
|
||||
// Send a message to render thread when something clicked. Make sure only one click in one frame because the queue size is only two.
|
||||
public void OnClicked()
|
||||
{
|
||||
// Reuse the same message type is ok.
|
||||
var msg = sampleRenderThreadTask2.Queue.Obtain<SampleMessage>();
|
||||
msg.dataPassedToRenderThread = 234;
|
||||
sampleRenderThreadTask2.Queue.Enqueue(msg);
|
||||
sampleRenderThreadTask2.IssueEvent();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 251b4bedf6420fc4e84be778e501343f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user