version 2.5.1

This commit is contained in:
Sean Lu
2025-01-10 17:31:06 +08:00
parent 2554daa86e
commit 25a5fd2124
91 changed files with 16226 additions and 11225 deletions

View File

@@ -7,8 +7,17 @@ namespace VIVE.OpenXR.Feature
{
public interface IViveFeatureWrapper
{
/// <summary>
/// OnInstanceCreate might be called multiple times. Because many features might be using the same instance.
/// </summary>
/// <param name="xrInstance"></param>
/// <param name="xrGetInstanceProcAddr"></param>
/// <returns></returns>
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddr);
/// <summary>
/// OnInstanceDestroy might be called multiple times. Because many features might be using the same instance.
/// </summary>
public void OnInstanceDestroy();
}
@@ -21,6 +30,11 @@ namespace VIVE.OpenXR.Feature
// Set true in yourfeature's OnInstanceCreate
public bool IsInited { get; protected set; } = false;
/// <summary>
/// If the feature is inited not successfully, Set this true. Use to avoid multiple inits.
/// </summary>
public bool TryInited { get; protected set; } = false;
public OpenXRHelper.xrGetInstanceProcAddrDelegate xrGetInstanceProcAddr;
/// <summary>

View File

@@ -1,26 +1,114 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine.Profiling;
namespace VIVE.OpenXR
{
public static class MemoryTools
{
internal static class MemoryTools
{
/// <summary>
/// Make sure the input ptr is a OpenXR XrBaseStructure derived struct.
/// </summary>
/// <param name="ptr">the struct to get its next.</param>
/// <returns>the next's value</returns>
public static unsafe IntPtr GetNext(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
return IntPtr.Zero;
//Profiler.BeginSample("GetNext");
XrBaseStructure* ptrToStruct = (XrBaseStructure*)ptr.ToPointer();
//Profiler.EndSample();
return ptrToStruct->next;
}
/// <summary>
/// Make sure the input ptr is a OpenXR XrBaseStructure derived struct.
/// </summary>
/// <param name="ptr">the struct to get its type</param>
/// <returns>the struct's type</returns>
public static unsafe XrStructureType GetType(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
throw new Exception("The input pointer is null.");
//Profiler.BeginSample("GetType");
XrBaseStructure* ptrToStruct = (XrBaseStructure*)ptr.ToPointer();
//Profiler.EndSample();
return ptrToStruct->type;
}
public static unsafe XrBaseStructure ToBaseStructure(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
throw new Exception("The input pointer is null.");
//Profiler.BeginSample("ToBaseStructure");
XrBaseStructure* ptrToStruct = (XrBaseStructure*)ptr.ToPointer();
//Profiler.EndSample();
return *ptrToStruct;
}
public static unsafe T PtrToStructure<T>(IntPtr ptr) where T : unmanaged
{
//Profiler.BeginSample("PtrToStructure");
// Not to use Marshal.PtrToStructure<T> because it is slow.
T t = default; // Use new T() will cause GC alloc.
Buffer.MemoryCopy((void*)ptr, &t, sizeof(T), sizeof(T));
//Profiler.EndSample();
return t;
}
public static unsafe void PtrToStructure<T>(IntPtr ptr, ref T t) where T : unmanaged
{
//Profiler.BeginSample("PtrToStructure");
fixed (T* destinationPtr = &t)
{
Buffer.MemoryCopy((void*)ptr, destinationPtr, sizeof(T), sizeof(T));
}
//Profiler.EndSample();
}
public static unsafe void StructureToPtr<T>(T t, IntPtr ptr) where T : unmanaged
{
//Profiler.BeginSample("StructureToPtr");
// Not to use Marshal.StructureToPtr<T> because it is slow.
Buffer.MemoryCopy(&t, (void*)ptr, sizeof(T), sizeof(T));
//Profiler.EndSample();
}
/// <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;
}
public static unsafe IntPtr ToIntPtr<T>(T[] array) where T : Enum
{
int size = sizeof(int) * array.Length;
IntPtr ptr = Marshal.AllocHGlobal(size);
int* intPtr = (int*)ptr.ToPointer();
for (int i = 0; i < array.Length; i++)
{
// Convert enum to int. This has better performance than Convert.ToInt32.
intPtr[i] = (int)(object)array[i];
}
return ptr;
}
/// <summary>
/// Convert the struct to IntPtr. Should call <see cref="ReleaseRawMemory(IntPtr)"/> after use.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="structure"></param>
/// <returns></returns>
public static IntPtr ToIntPtr<T>(T structure) where T : struct
{
int size = Marshal.SizeOf(structure);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(structure, ptr, true);
return ptr;
}
/// <summary>
/// Make the same size raw buffer from input array.
@@ -28,30 +116,68 @@ namespace VIVE.OpenXR
/// <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);
}
public static unsafe IntPtr MakeRawMemory<T>(T[] refArray) where T : unmanaged
{
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>
/// 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 unsafe void CopyFromRawMemory<T>(T[] array, IntPtr raw, int count = 0) where T : unmanaged
{
//Profiler.BeginSample("CopyFromRawMemory");
int N = array.Length;
if (count > 0 && count < array.Length)
N = count;
int step = sizeof(T);
int bufferSize = step * N;
// Pin array's address. Prevent GC move it.
fixed (T* destPtr = array)
{
T* sourcePtr = (T*)raw.ToPointer();
Buffer.MemoryCopy(sourcePtr, destPtr, bufferSize, bufferSize);
}
//Profiler.EndSample();
}
/// <summary>
/// Copy all raw memory to the array. This has higher performance than <see cref="CopyFromRawMemory"/>.
/// Use this method if you have frequent update requirements.
/// You need prepare a byte buffer to store the raw memory. The byte buffer size should be tSize * array.Length.
/// tSize is used for checking the byte buffer size. If tSize is 0, it will use Marshal.SizeOf(typeof(T)).
/// You can save the size at your size to avoid the Marshal.Sizeof(typeof(T)) call repeatedly.
/// </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>
public static unsafe void CopyAllFromRawMemory<T>(T[] array, IntPtr raw) where T : unmanaged
{
#if DEBUG
if (array == null)
throw new ArgumentNullException(nameof(array), "Output array cannot be null.");
if (raw == IntPtr.Zero)
throw new ArgumentNullException(nameof(raw), "Raw memory pointer cannot be null.");
#endif
//Profiler.BeginSample("CopyAllFromRawMemory");
int elementSize = sizeof(T);
int requiredBufferSize = elementSize * array.Length;
// Pin array's address. Prevent GC move it.
fixed (T* destPtr = array)
{
T* sourcePtr = (T*)raw.ToPointer();
Buffer.MemoryCopy(sourcePtr, destPtr, requiredBufferSize, requiredBufferSize);
}
//Profiler.EndSample();
}
/// <summary>
/// Make the same size raw buffer from input array. Make sure the raw has enough size.
@@ -59,22 +185,44 @@ namespace VIVE.OpenXR
/// <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);
}
}
public static unsafe void CopyToRawMemory<T>(IntPtr raw, T[] array) where T : unmanaged
{
//Profiler.BeginSample("CopyToRawMemory");
int step = sizeof(T);
int bufferSize = step * array.Length;
// Pin array's address. Prevent GC move it.
fixed (T* destPtr = array)
{
void* ptr = raw.ToPointer();
Buffer.MemoryCopy(destPtr, ptr, bufferSize, bufferSize);
}
//Profiler.EndSample();
}
/// <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);
}
}
}
{
Marshal.FreeHGlobal(ptr);
}
/// <summary>
/// Find a pointer in the next chain. Make sure the input next pointer is a OpenXR XrBaseStructure derived struct.
/// </summary>
/// <param name="target"></param>
/// <param name="next"></param>
/// <returns>true if exist</returns>
public static bool HasPtrInNextChain(IntPtr target, IntPtr next)
{
while (next != IntPtr.Zero)
{
if (next == target)
return true;
next = GetNext(next);
}
return false;
}
}
}

View File

@@ -0,0 +1,278 @@
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace VIVE.OpenXR.Enterprise
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = "VIVE XR Enterprise Command",
Desc = "Support Enterprise request with special command",
Company = "HTC",
OpenxrExtensionStrings = kOpenxrExtensionString,
Version = "0.1",
BuildTargetGroups = new[] { BuildTargetGroup.Android },
FeatureId = featureId,
Hidden = true
)]
#endif
public class ViveEnterpriseCommand : OpenXRFeature
{
#region Log
const string LOG_TAG = "VIVE.OpenXR.Enterprise.Command ";
private static void DEBUG(String msg) { Debug.Log(LOG_TAG + msg); }
private static void ERROR(String msg) { Debug.LogError(LOG_TAG + msg); }
#endregion
/// <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.enterprise.command";
/// <summary>
/// The extension string.
/// </summary>
public const string kOpenxrExtensionString = "XR_HTC_enterprise_command";
#region OpenXR Life Cycle
private static bool m_XrInstanceCreated = false;
private static bool m_XrSessionCreated = false;
private static XrInstance m_XrInstance = 0;
private static XrSession m_XrSession = 0;
private static XrSystemId m_XrSystemId = 0;
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateInstance">xrCreateInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The created instance.</param>
/// <returns>True for valid <see cref="XrInstance">XrInstance</see></returns>
protected override bool OnInstanceCreate(ulong xrInstance)
{
if (!OpenXRRuntime.IsExtensionEnabled(kOpenxrExtensionString))
{
ERROR($"OnInstanceCreate() {kOpenxrExtensionString} is NOT enabled.");
return false;
}
m_XrInstanceCreated = true;
m_XrInstance = xrInstance;
DEBUG($"OnInstanceCreate() {m_XrInstance}");
return GetXrFunctionDelegates(m_XrInstance);
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroyInstance">xrDestroyInstance</see> is done.
/// </summary>
/// <param name="xrInstance">The instance to destroy.</param>
protected override void OnInstanceDestroy(ulong xrInstance)
{
if (m_XrInstance == xrInstance)
{
m_XrInstanceCreated = false;
m_XrInstance = 0;
}
DEBUG($"OnInstanceDestroy() {xrInstance}");
}
/// <summary>
/// Called when the <see cref="XrSystemId">XrSystemId</see> retrieved by <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrGetSystem">xrGetSystem</see> is changed.
/// </summary>
/// <param name="xrSystem">The system id.</param>
protected override void OnSystemChange(ulong xrSystem)
{
m_XrSystemId = xrSystem;
DEBUG($"OnSystemChange() {m_XrSystemId}");
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrCreateSession">xrCreateSession</see> is done.
/// </summary>
/// <param name="xrSession">The created session ID.</param>
protected override void OnSessionCreate(ulong xrSession)
{
m_XrSession = xrSession;
m_XrSessionCreated = true;
DEBUG($"OnSessionCreate() {m_XrSession}");
}
/// <summary>
/// Called when <see href="https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#xrDestroySession">xrDestroySession</see> is done.
/// </summary>
/// <param name="xrSession">The session ID to destroy.</param>
protected override void OnSessionDestroy(ulong xrSession)
{
DEBUG($"OnSessionDestroy() {xrSession}");
if (m_XrSession == xrSession)
{
m_XrSession = 0;
m_XrSessionCreated = false;
}
}
#endregion
#region OpenXR function delegates
/// xrEnterpriseCommandHTC
private static ViveEnterpriseCommandHelper.xrEnterpriseCommandHTCDelegate xrEnterpriseCommandHTC;
/// xrGetInstanceProcAddr
private static OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddr;
/// <summary>
/// Enterprise command request for special functionality.
/// </summary>
/// <param name="request">The request of enterprise command</param>
/// <param name="result">The result of enterprise command</param>
/// <returns>Return XR_SUCCESS if request successfully. False otherwise.</returns>
private static XrResult EnterpriseCommandHTC(XrEnterpriseCommandBufferHTC request, ref XrEnterpriseCommandBufferHTC result)
{
if (!m_XrSessionCreated)
{
ERROR("EnterpriseCommandHTC() XR_ERROR_SESSION_LOST.");
return XrResult.XR_ERROR_SESSION_LOST;
}
if (!m_XrInstanceCreated)
{
ERROR("EnterpriseCommandHTC() XR_ERROR_INSTANCE_LOST.");
return XrResult.XR_ERROR_INSTANCE_LOST;
}
DEBUG($"EnterpriseCommandHTC() code: {request.code}, data: {CharArrayToString(request.data)}");
return xrEnterpriseCommandHTC(m_XrSession, request, ref result);
}
/// <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 request successfully. False otherwise.</returns>
private bool GetXrFunctionDelegates(XrInstance xrInstance)
{
/// xrGetInstanceProcAddr
if (xrGetInstanceProcAddr != null && xrGetInstanceProcAddr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrGetInstanceProcAddr.");
XrGetInstanceProcAddr = Marshal.GetDelegateForFunctionPointer(
xrGetInstanceProcAddr,
typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate)) as OpenXRHelper.xrGetInstanceProcAddrDelegate;
}
else
{
ERROR("xrGetInstanceProcAddr");
return false;
}
/// xrEnterpriseCommandHTC
if (XrGetInstanceProcAddr(xrInstance, "xrEnterpriseCommandHTC", out IntPtr funcPtr) == XrResult.XR_SUCCESS)
{
if (funcPtr != IntPtr.Zero)
{
DEBUG("Get function pointer of xrEnterpriseCommandHTC.");
xrEnterpriseCommandHTC = Marshal.GetDelegateForFunctionPointer(
funcPtr,
typeof(ViveEnterpriseCommandHelper.xrEnterpriseCommandHTCDelegate)) as ViveEnterpriseCommandHelper.xrEnterpriseCommandHTCDelegate;
}
}
else
{
ERROR("xrEnterpriseCommandHTC");
return false;
}
return true;
}
#endregion
#region Public API
private const int kCharLength = 256;
private const char kEndChar = '\0';
private static char[] charArray = new char[kCharLength];
/// <summary>
/// Request special feature with command, it should take code and command string.
/// </summary>
/// <param name="requestCode">The type of request code is integer.</param>
/// <param name="requestCommand">The maximum length of request command is 256.</param>
/// <param name="resultCode">The output of result code.</param>
/// <param name="resultCommand">The output of result command.</param>
/// <returns>Return true if request successfully. False otherwise.</returns>
public static bool CommandRequest(int requestCode, string requestCommand, out int resultCode, out string resultCommand)
{
resultCode = 0;
resultCommand = string.Empty;
XrEnterpriseCommandBufferHTC request = new XrEnterpriseCommandBufferHTC(requestCode, StringToCharArray(requestCommand));
XrEnterpriseCommandBufferHTC result = new XrEnterpriseCommandBufferHTC(resultCode, StringToCharArray(resultCommand));
if (EnterpriseCommandHTC(request, ref result) == XrResult.XR_SUCCESS)
{
resultCode = result.code;
resultCommand = CharArrayToString(result.data);
DEBUG($"CommandRequest Result code: {resultCode}, data: {resultCommand}");
return true;
}
return false;
}
#endregion
private static char[] StringToCharArray(string str)
{
Array.Clear(charArray, 0, kCharLength);
if (!string.IsNullOrEmpty(str))
{
int arrayLength = Math.Min(str.Length, kCharLength);
for (int i = 0; i < arrayLength; i++)
{
charArray[i] = str[i];
}
charArray[kCharLength - 1] = kEndChar;
}
return charArray;
}
private static string CharArrayToString(char[] charArray)
{
int actualLength = Array.FindIndex(charArray, c => c == kEndChar);
if (actualLength == -1)
{
actualLength = charArray.Length;
}
return new string(charArray, 0, actualLength);
}
}
#region Helper
public struct XrEnterpriseCommandBufferHTC
{
public Int32 code;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public char[] data;
public XrEnterpriseCommandBufferHTC(int in_code, char[] in_data)
{
code = (Int32)in_code;
data = new char[in_data.Length];
Array.Copy(in_data, data, in_data.Length);
}
}
public class ViveEnterpriseCommandHelper
{
/// <summary>
/// The function delegate of xrEnterpriseCommandHTC.
/// </summary>
/// <param name="session">An <see cref="XrSession">XrSession</see> in which the enterprise command will be active.</param>
/// <param name="request">The request of enterprise command</param>
/// <param name="result">The result of enterprise command</param>
/// <returns>Return XR_SUCCESS if request successfully. False otherwise.</returns>
public delegate XrResult xrEnterpriseCommandHTCDelegate(
XrSession session,
XrEnterpriseCommandBufferHTC request,
ref XrEnterpriseCommandBufferHTC result);
}
#endregion
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5b2125d5dc73694eaed34e97a0962e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -10,9 +10,14 @@ namespace VIVE.OpenXR.Feature
/// <summary>
/// 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().
///
/// Note:
/// In Standardalone's OpenXR MockRuntime, the CreateSwapchain and EnumerateSwapchainImages will work and return success,
/// but the images's native pointer will be null.
/// </summary>
public class CommonWrapper : ViveFeatureWrapperBase<CommonWrapper>, IViveFeatureWrapper
internal class CommonWrapper : ViveFeatureWrapperBase<CommonWrapper>, IViveFeatureWrapper
{
const string TAG = "CommonWrapper";
OpenXRHelper.xrGetSystemPropertiesDelegate XrGetSystemProperties;
OpenXRHelper.xrCreateSwapchainDelegate XrCreateSwapchain;
OpenXRHelper.xrDestroySwapchainDelegate XrDestroySwapchain;
@@ -32,11 +37,13 @@ namespace VIVE.OpenXR.Feature
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrPtr)
{
if (IsInited) return true;
if (TryInited) return false;
TryInited = true;
if (xrInstance == 0)
throw new Exception("CommonWrapper: xrInstance is null");
Debug.Log("CommonWrapper: OnInstanceCreate()");
Log.D(TAG, "OnInstanceCreate()");
SetGetInstanceProcAddrPtr(xrGetInstanceProcAddrPtr);
bool ret = true;
@@ -64,9 +71,11 @@ namespace VIVE.OpenXR.Feature
/// <returns></returns>
public void OnInstanceDestroy()
{
// Do not destroy twice
if (IsInited == false) return;
IsInited = false;
XrGetSystemProperties = null;
Debug.Log("CommonWrapper: OnInstanceDestroy()");
Log.D(TAG, "OnInstanceDestroy()");
}
public XrResult GetInstanceProcAddr(XrInstance instance, string name, out IntPtr function)
@@ -164,12 +173,12 @@ namespace VIVE.OpenXR.Feature
if (formatCapacityInput == 0)
{
Debug.Log("CommonWrapper: EnumerateSwapchainFormats(ci=" + formatCapacityInput + ")");
Log.D(TAG, "EnumerateSwapchainFormats(ci=" + formatCapacityInput + ")");
return XrEnumerateSwapchainFormats(session, 0, ref formatCountOutput, IntPtr.Zero);
}
else
{
Debug.Log("CommonWrapper: EnumerateSwapchainFormats(ci=" + formatCapacityInput + ", formats=long[" + formats.Length + "])");
Log.D(TAG, "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)
@@ -201,7 +210,7 @@ namespace VIVE.OpenXR.Feature
return XrResult.XR_ERROR_HANDLE_INVALID;
}
Profiler.BeginSample("ASW: xrAcqScImg");
Profiler.BeginSample("ASW:xrAcqScImg");
var res = XrAcquireSwapchainImage(swapchain, ref acquireInfo, out index);
Profiler.EndSample();
return res;
@@ -217,7 +226,7 @@ namespace VIVE.OpenXR.Feature
return XrResult.XR_ERROR_HANDLE_INVALID;
}
Profiler.BeginSample("ASW: xrWaitScImg");
Profiler.BeginSample("ASW:xrWaitScImg");
var res = XrWaitSwapchainImage(swapchain, ref waitInfo);
Profiler.EndSample();
return res;
@@ -234,7 +243,7 @@ namespace VIVE.OpenXR.Feature
}
// Add Profiler
Profiler.BeginSample("ASW: xrRelScImg");
Profiler.BeginSample("ASW:xrRelScImg");
var res = XrReleaseSwapchainImage(swapchain, ref releaseInfo);
Profiler.EndSample();
return res;

View File

@@ -17,6 +17,8 @@ namespace VIVE.OpenXR.Feature
/// </summary>
public class FutureWrapper : ViveFeatureWrapperBase<FutureWrapper>, IViveFeatureWrapper
{
const string TAG = "ViveFuture";
public enum XrFutureStateEXT
{
None = 0, // Not defined in extension. A default value.
@@ -76,6 +78,8 @@ namespace VIVE.OpenXR.Feature
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr xrGetInstanceProcAddrPtr)
{
if (IsInited) return true;
if (TryInited) return false;
TryInited = true;
if (xrInstance == null)
throw new Exception("FutureWrapper: xrInstance is null");
@@ -85,12 +89,12 @@ namespace VIVE.OpenXR.Feature
throw new Exception("FutureWrapper: xrGetInstanceProcAddr is null");
SetGetInstanceProcAddrPtr(xrGetInstanceProcAddrPtr);
Debug.Log("FutureWrapper: OnInstanceCreate()");
Log.D(TAG, "OnInstanceCreate()");
bool hasFuture = OpenXRRuntime.IsExtensionEnabled("XR_EXT_future");
if (!hasFuture)
{
Debug.LogError("FutureWrapper: XR_EXT_future is not enabled. Check your feature's kOpenxrExtensionString.");
Log.E(TAG, "FutureWrapper: XR_EXT_future is not enabled. Check your feature's kOpenxrExtensionString.");
return false;
}
@@ -102,7 +106,7 @@ namespace VIVE.OpenXR.Feature
if (!ret)
{
Debug.LogError("FutureWrapper: Failed to get function pointer.");
Log.E(TAG,"FutureWrapper: Failed to get function pointer.");
return false;
}
@@ -112,7 +116,7 @@ namespace VIVE.OpenXR.Feature
public void OnInstanceDestroy()
{
Debug.Log("FutureWrapper: OnInstanceDestroy()");
Log.D(TAG, "OnInstanceDestroy()");
IsInited = false;
XrPollFutureEXT = null;
XrCancelFutureEXT = null;

View File

@@ -1,6 +1,7 @@
// Copyright HTC Corporation All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using UnityEngine;
namespace VIVE.OpenXR.Feature
@@ -12,9 +13,13 @@ namespace VIVE.OpenXR.Feature
/// </summary>
public class SpaceWrapper : ViveFeatureWrapperBase<SpaceWrapper>, IViveFeatureWrapper
{
const string TAG = "ViveSpaceWrapper";
public delegate XrResult DelegateXrEnumerateReferenceSpaces(XrSession session, uint spaceCapacityInput, out uint spaceCountOutput, [Out] XrReferenceSpaceType[] spaces);
delegate XrResult DelegateXrLocateSpace(XrSpace space, XrSpace baseSpace, XrTime time, ref XrSpaceLocation location);
delegate XrResult DelegateXrDestroySpace(XrSpace space);
DelegateXrEnumerateReferenceSpaces XrEnumerateReferenceSpaces;
OpenXRHelper.xrCreateReferenceSpaceDelegate XrCreateReferenceSpace;
DelegateXrLocateSpace XrLocateSpace;
DelegateXrDestroySpace XrDestroySpace;
@@ -29,17 +34,20 @@ namespace VIVE.OpenXR.Feature
public bool OnInstanceCreate(XrInstance xrInstance, IntPtr GetAddr)
{
if (IsInited) return true;
if (TryInited) return false;
TryInited = true;
if (xrInstance == null)
throw new Exception("ViveSpace: xrInstance is null");
throw new Exception("ViveSpaceWrapper: xrInstance is null");
SetGetInstanceProcAddrPtr(GetAddr);
Debug.Log("ViveSpace: OnInstanceCreate()");
Log.D(TAG, "OnInstanceCreate()");
bool ret = true;
IntPtr funcPtr = IntPtr.Zero;
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrEnumerateReferenceSpaces", out XrEnumerateReferenceSpaces);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrCreateReferenceSpace", out XrCreateReferenceSpace);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrLocateSpace", out XrLocateSpace);
ret &= OpenXRHelper.GetXrFunctionDelegate(xrGetInstanceProcAddr, xrInstance, "xrDestroySpace", out XrDestroySpace);
@@ -49,12 +57,35 @@ namespace VIVE.OpenXR.Feature
public void OnInstanceDestroy()
{
// Do not destroy twice
if (IsInited == false) return;
IsInited = false;
XrEnumerateReferenceSpaces = null;
XrCreateReferenceSpace = null;
XrLocateSpace = null;
XrDestroySpace = null;
}
/// <summary>
///
/// </summary>
/// <param name="session"></param>
/// <param name="spaceCapacityInput"></param>
/// <param name="spaceCountOutput"></param>
/// <param name="spaces"></param>
/// <returns></returns>
public XrResult EnumerateReferenceSpaces(XrSession session, int spaceCapacityInput, ref int spaceCountOutput, ref XrReferenceSpaceType[] spaces)
{
spaceCountOutput = 0;
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
if (spaceCapacityInput != 0 && spaces != null && spaces.Length < spaceCapacityInput)
return XrResult.XR_ERROR_SIZE_INSUFFICIENT;
var ret = XrEnumerateReferenceSpaces(session, (uint)spaceCapacityInput, out uint spaceCountOutputXR, spaces);
spaceCountOutput = (int)spaceCountOutputXR;
return ret;
}
/// <summary>
/// Create a reference space without create info.
/// Example:
@@ -108,7 +139,7 @@ namespace VIVE.OpenXR.Feature
{
if (!IsInited)
return XrResult.XR_ERROR_HANDLE_INVALID;
Debug.Log($"DestroySpace({space})");
Log.D(TAG, $"DestroySpace({space})");
return XrDestroySpace(space);
}
}
@@ -124,7 +155,7 @@ namespace VIVE.OpenXR.Feature
public Space(XrSpace space)
{
Debug.Log($"Space({space})");
Log.D($"Space({space})");
this.space = space;
}

View File

@@ -5,9 +5,25 @@ using UnityEngine;
using AOT;
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace VIVE.OpenXR
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal class HookHandlerAttribute : Attribute
{
public string xrFuncName { get; }
/// <summary>
/// Set this function to handle the hook process in <see cref="ViveInterceptors.XrGetInstanceProcAddrInterceptor" />
/// </summary>
/// <param name="xrFuncName">The hooked openxr function name</param>
public HookHandlerAttribute(string xrFuncName)
{
this.xrFuncName = xrFuncName;
}
}
/// <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.
@@ -22,7 +38,16 @@ namespace VIVE.OpenXR
/// return ViveInterceptors.Instance.HookGetInstanceProcAddr(func);
/// }
/// </summary>
partial class ViveInterceptors
// For extending the ViveInterceptors class, create a new partial class and implement the required functions.
// For example:
// public partial class ViveInterceptors
// {
// [HookHandler("xrYourFunction")]
// private static XrResult OnHookXrYourFunction(XrInstance instance, string name, out IntPtr function)
// { ... }
// }
partial class ViveInterceptors
{
public const string TAG = "VIVE.OpenXR.ViveInterceptors";
static StringBuilder m_sb = null;
@@ -32,8 +57,6 @@ namespace VIVE.OpenXR
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 static ViveInterceptors instance = null;
public static ViveInterceptors Instance
@@ -48,15 +71,32 @@ namespace VIVE.OpenXR
public ViveInterceptors()
{
Debug.Log("ViveInterceptors");
Log.D("ViveInterceptors");
RegisterFunctions();
}
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;
delegate XrResult HookHandler(XrInstance instance, string name, out IntPtr function);
static readonly Dictionary<string, HookHandler> interceptors = new Dictionary<string, HookHandler>();
[MonoPInvokeCallback(typeof(DelegateXrGetInstanceProcAddr))]
private static void RegisterFunctions()
{
var methods = typeof(ViveInterceptors).GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
foreach (var method in methods)
{
var attribute = method.GetCustomAttributes(typeof(HookHandlerAttribute), false).FirstOrDefault() as HookHandlerAttribute;
if (attribute != null)
{
Log.I(TAG, $"Registering hook handler {attribute.xrFuncName}");
interceptors.Add(attribute.xrFuncName, (HookHandler)method.CreateDelegate(typeof(HookHandler)));
}
}
}
private static readonly OpenXRHelper.xrGetInstanceProcAddrDelegate hookXrGetInstanceProcAddrHandle = new OpenXRHelper.xrGetInstanceProcAddrDelegate(XrGetInstanceProcAddrInterceptor);
private static readonly IntPtr hookGetInstanceProcAddrHandlePtr = Marshal.GetFunctionPointerForDelegate(hookXrGetInstanceProcAddrHandle);
static OpenXRHelper.xrGetInstanceProcAddrDelegate XrGetInstanceProcAddrOriginal = null;
[MonoPInvokeCallback(typeof(OpenXRHelper.xrGetInstanceProcAddrDelegate))]
private static XrResult XrGetInstanceProcAddrInterceptor(XrInstance instance, string name, out IntPtr function)
{
// Used to check if the original function is already hooked.
@@ -66,64 +106,16 @@ namespace VIVE.OpenXR
return XrResult.XR_SUCCESS;
}
// Custom interceptors
if (name == "xrWaitFrame" && requiredFunctions.Contains(name))
// Check if the function is intercepted by other features
if (interceptors.ContainsKey(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 no request for this function, call the original function directly.
if (!requiredFunctions.Contains(name))
return XrGetInstanceProcAddrOriginal(instance, name, out function);
if (name == "xrEndFrame" && requiredFunctions.Contains(name))
{
Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted.");
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
var ret = interceptors[name](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;
}
Log.I(TAG, name + " is intercepted");
return ret;
}
@@ -132,23 +124,23 @@ namespace VIVE.OpenXR
public IntPtr HookGetInstanceProcAddr(IntPtr func)
{
Debug.Log($"{TAG}: HookGetInstanceProcAddr");
Log.D(TAG, "HookGetInstanceProcAddr");
if (XrGetInstanceProcAddrOriginal == null)
{
Debug.Log($"{TAG}: registering our own xrGetInstanceProcAddr");
XrGetInstanceProcAddrOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrGetInstanceProcAddr>(func);
Log.D(TAG, "registering our own xrGetInstanceProcAddr");
XrGetInstanceProcAddrOriginal = Marshal.GetDelegateForFunctionPointer<OpenXRHelper.xrGetInstanceProcAddrDelegate>(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.");
Log.D(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");
Log.D(TAG, "Already hooked");
return func;
}
}
@@ -173,9 +165,34 @@ namespace VIVE.OpenXR
/// <param name="name"></param>
public void AddRequiredFunction(string name)
{
if (requiredFunctions.Contains(name)) return;
Debug.Log($"{TAG}: AddRequiredFunction({name})");
requiredFunctions.Add(name);
Log.D(TAG, $"AddRequiredFunction({name})");
if (!interceptors.ContainsKey(name))
{
Log.E(TAG, $"AddRequiredFunction({name}) failed. No such function.");
return;
}
if (!requiredFunctions.Contains(name))
requiredFunctions.Add(name);
// If your function support unregister, you can add the reference count here.
if (name == "xrLocateViews")
xrLocateViewsReferenceCount++;
}
/// <summary>
/// If no need to use this hooked function, call this will remove your requirement.
/// If all requirements are removed, the original function will be called directly.
/// </summary>
/// <param name="name"></param>
public void RemoveRequiredFunction(string name)
{
// If your function support unregister, you can add the reference count here.
if (requiredFunctions.Contains(name))
{
if (name == "xrLocateViews")
xrLocateViewsReferenceCount = Mathf.Max(xrLocateViewsReferenceCount--, 0);
}
}
}
}

View File

@@ -12,6 +12,21 @@ namespace VIVE.OpenXR
{
partial class ViveInterceptors
{
[HookHandler("xrBeginSession")]
private static XrResult OnHookXrBeginSession(XrInstance instance, string name, out IntPtr function)
{
if (xrBeginSessionOrigin == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
xrBeginSessionOrigin = Marshal.GetDelegateForFunctionPointer<xrBeginSessionDelegate>(function);
}
function = xrBeginSessionPtr;
return XrResult.XR_SUCCESS;
}
#region xrBeginSession
public delegate XrResult xrBeginSessionDelegate(XrSession session, ref XrSessionBeginInfo beginInfo);
private static xrBeginSessionDelegate xrBeginSessionOrigin = null;
@@ -19,7 +34,7 @@ namespace VIVE.OpenXR
[MonoPInvokeCallback(typeof(xrBeginSessionDelegate))]
private static XrResult xrBeginSessionInterceptor(XrSession session, ref XrSessionBeginInfo beginInfo)
{
Profiler.BeginSample("ViveInterceptors:BeginSession");
Profiler.BeginSample("VI:BeginSession");
XrResult result = XrResult.XR_ERROR_FUNCTION_UNSUPPORTED;
if (xrBeginSessionOrigin != null)
@@ -48,7 +63,8 @@ namespace VIVE.OpenXR
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);
sb.Clear().Append("xrBeginSessionInterceptor() beginInfo.next = (").Append(fsBeginInfo.type).Append(", ").Append(fsBeginInfo.mode).Append(")");
Log.D(sb);
#endif
}
@@ -56,7 +72,7 @@ namespace VIVE.OpenXR
}
else
{
sb.Clear().Append("xrBeginSessionInterceptor() Not assign xrBeginSession!"); ERROR(sb);
Log.E("xrBeginSessionInterceptor() Not assign xrBeginSession!");
}
Profiler.EndSample();
@@ -79,7 +95,8 @@ namespace VIVE.OpenXR
{
m_EnableFrameSynchronization = active;
m_FrameSynchronizationMode = mode;
sb.Clear().Append("ActivateFrameSynchronization() ").Append(active ? "enable " : "disable ").Append(mode); DEBUG(sb);
sb.Clear().Append("ActivateFrameSynchronization() ").Append(active ? "enable " : "disable ").Append(mode);
Log.D(sb);
}
}
}
}

View File

@@ -0,0 +1,98 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Runtime.InteropServices;
using System;
using AOT;
using UnityEngine.Profiling;
namespace VIVE.OpenXR
{
public partial class ViveInterceptors
{
[HookHandler("xrLocateViews")]
private static XrResult OnHookXrLocateViews(XrInstance instance, string name, out IntPtr function)
{
if (xrLocateViewsOriginal == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
xrLocateViewsOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrLocateViews>(function);
}
function = xrLocateViewsInterceptorPtr;
return XrResult.XR_SUCCESS;
}
public struct XrViewLocateInfo
{
public XrStructureType type;
public IntPtr next;
public XrViewConfigurationType viewConfigurationType;
public XrTime displayTime;
public XrSpace space;
}
public struct XrView
{
public XrStructureType type;
public IntPtr next;
public XrPosef pose;
public XrFovf fov;
}
public enum XrViewStateFlags {
ORIENTATION_VALID_BIT = 0x00000001,
POSITION_VALID_BIT = 0x00000002,
ORIENTATION_TRACKED_BIT = 0x00000004,
POSITION_TRACKED_BIT = 0x00000008,
}
public struct XrViewState
{
public XrStructureType type;
public IntPtr next;
public XrViewStateFlags viewStateFlags;
}
public delegate XrResult DelegateXrLocateViews(XrSession session, IntPtr /*XrViewLocateInfo*/ viewLocateInfo, IntPtr /*XrViewState*/ viewState, uint viewCapacityInput, ref uint viewCountOutput, IntPtr /*XrView*/ views);
private static readonly DelegateXrLocateViews xrLocateViewsInterceptorHandle = new DelegateXrLocateViews(XrLocateViewsInterceptor);
private static readonly IntPtr xrLocateViewsInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrLocateViewsInterceptorHandle);
static DelegateXrLocateViews xrLocateViewsOriginal = null;
static int xrLocateViewsReferenceCount = 0;
[MonoPInvokeCallback(typeof(DelegateXrLocateViews))]
private static XrResult XrLocateViewsInterceptor(XrSession session, IntPtr viewLocateInfo, IntPtr viewState, uint viewCapacityInput, ref uint viewCountOutput, IntPtr views)
{
// Call the original function if the reference count is less than or equal to 0
if (xrLocateViewsReferenceCount <= 0)
return xrLocateViewsOriginal(session, viewLocateInfo, viewState, viewCapacityInput, ref viewCountOutput, views);
Profiler.BeginSample("VI:LocateViewsA");
XrResult result = XrResult.XR_SUCCESS;
if (instance.BeforeOriginalLocateViews != null)
instance.BeforeOriginalLocateViews(session, viewLocateInfo, viewState, viewCapacityInput, ref viewCountOutput, views);
Profiler.EndSample();
result = xrLocateViewsOriginal(session, viewLocateInfo, viewState, viewCapacityInput, ref viewCountOutput, views);
Profiler.BeginSample("VI:LocateViewsB");
instance.AfterOriginalLocateViews?.Invoke(session, viewLocateInfo, viewState, viewCapacityInput, ref viewCountOutput, views);
Profiler.EndSample();
return result;
}
/// <summary>
/// If you return false, the original function will not be called.
/// </summary>
/// <returns></returns>
public delegate bool DelegateXrLocateViewsInterceptor(XrSession session, IntPtr viewLocateInfo, IntPtr viewState, uint viewCapacityInput, ref uint viewCountOutput, IntPtr views);
/// <summary>
/// Use this to intercept the original function. This will be called before the original function.
/// </summary>
public DelegateXrLocateViewsInterceptor BeforeOriginalLocateViews;
/// <summary>
/// Use this to intercept the original function. This will be called after the original function.
/// </summary>
public DelegateXrLocateViewsInterceptor AfterOriginalLocateViews;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5dfd24c69475c3740975bf5538de3869
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -12,6 +12,20 @@ namespace VIVE.OpenXR
{
partial class ViveInterceptors
{
[HookHandler("xrPollEvent")]
private static XrResult OnHookXrPollEvent(XrInstance instance, string name, out IntPtr function)
{
if (xrPollEventOrigin == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
xrPollEventOrigin = Marshal.GetDelegateForFunctionPointer<xrPollEventDelegate>(function);
}
function = xrPollEventPtr;
return XrResult.XR_SUCCESS;
}
#region xrPollEvent
public delegate XrResult xrPollEventDelegate(XrInstance instance, ref XrEventDataBuffer eventData);
private static xrPollEventDelegate xrPollEventOrigin = null;
@@ -19,7 +33,7 @@ namespace VIVE.OpenXR
[MonoPInvokeCallback(typeof(xrPollEventDelegate))]
private static XrResult xrPollEventInterceptor(XrInstance instance, ref XrEventDataBuffer eventData)
{
Profiler.BeginSample("ViveInterceptors:WaitFrame");
Profiler.BeginSample("VI:PollEvent");
XrResult result = XrResult.XR_SUCCESS;
if (xrPollEventOrigin != null)
@@ -28,7 +42,7 @@ namespace VIVE.OpenXR
if (result == XrResult.XR_SUCCESS)
{
sb.Clear().Append("xrPollEventInterceptor() xrPollEvent ").Append(eventData.type); DEBUG(sb);
sb.Clear().Append("xrPollEventInterceptor() xrPollEvent ").Append(eventData.type); Log.D("PollEvent", sb);
switch(eventData.type)
{
case XrStructureType.XR_TYPE_EVENT_DATA_PASSTHROUGH_CONFIGURATION_IMAGE_RATE_CHANGED_HTC:
@@ -41,7 +55,7 @@ namespace VIVE.OpenXR
.Append(", fromImageRatesrc.dstImageRate: ").Append(fromImageRate.dstImageRate)
.Append(", toImageRate.srcImageRate: ").Append(toImageRate.srcImageRate)
.Append(", toImageRate.dstImageRate: ").Append(toImageRate.dstImageRate);
DEBUG(sb);
Log.D("PollEvent", sb.ToString());
VivePassthroughImageRateChanged.Send(fromImageRate.srcImageRate, fromImageRate.dstImageRate, toImageRate.srcImageRate, toImageRate.dstImageRate);
}
break;
@@ -53,7 +67,7 @@ namespace VIVE.OpenXR
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);
Log.D("PollEvent", sb);
VivePassthroughImageQualityChanged.Send(fromImageQuality.scale, toImageQuality.scale);
}
break;
@@ -65,7 +79,7 @@ namespace VIVE.OpenXR
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB")
.Append(", fromDisplayRefreshRate: ").Append(fromDisplayRefreshRate)
.Append(", toDisplayRefreshRate: ").Append(toDisplayRefreshRate);
DEBUG(sb);
Log.D("PollEvent", sb);
ViveDisplayRefreshRateChanged.Send(fromDisplayRefreshRate, toDisplayRefreshRate);
}
break;
@@ -87,7 +101,7 @@ namespace VIVE.OpenXR
.Append(", session: ").Append(eventDataSession.session)
.Append(", state: ").Append(eventDataSession.state)
.Append(", isUserPresent: ").Append(isUserPresent);
DEBUG(sb);
Log.D("PollEvent", sb);
}
break;
case XrStructureType.XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT:
@@ -97,7 +111,7 @@ namespace VIVE.OpenXR
sb.Clear().Append("xrPollEventInterceptor() XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT")
.Append(", session: ").Append(eventDataUserPresence.session)
.Append(", isUserPresent: ").Append(isUserPresent);
DEBUG(sb);
Log.D("PollEvent", sb);
}
break;
default:
@@ -105,7 +119,7 @@ namespace VIVE.OpenXR
}
}
//sb.Clear().Append("xrPollEventInterceptor() xrPollEvent result: ").Append(result).Append(", isUserPresent: ").Append(isUserPresent); DEBUG(sb);
//sb.Clear().Append("xrPollEventInterceptor() xrPollEvent result: ").Append(result).Append(", isUserPresent: ").Append(isUserPresent); Log.d("PollEvent", sb);
}
Profiler.EndSample();

View File

@@ -8,6 +8,20 @@ namespace VIVE.OpenXR
{
partial class ViveInterceptors
{
[HookHandler("xrEndFrame")]
private static XrResult OnHookXrEndFrame(XrInstance instance, string name, out IntPtr function)
{
if (XrEndFrameOriginal == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
XrEndFrameOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrEndFrame>(function);
}
function = xrEndFrameInterceptorPtr;
return XrResult.XR_SUCCESS;
}
public struct XrCompositionLayerBaseHeader
{
public XrStructureType type; // This base structure itself has no associated XrStructureType value.
@@ -37,15 +51,16 @@ namespace VIVE.OpenXR
// instance must not null
//if (instance == null)
// return XrEndFrameOriginal(session, ref frameEndInfo);
Profiler.BeginSample("VI:EndFrame");
Profiler.BeginSample("VI:EndFrameB");
XrResult result = XrResult.XR_SUCCESS;
if (instance.BeforeOriginalEndFrame != null &&
!instance.BeforeOriginalEndFrame(session, ref frameEndInfo, ref result))
{
Profiler.EndSample();
bool ret = true;
if (instance.BeforeOriginalEndFrame != null)
ret = instance.BeforeOriginalEndFrame(session, ref frameEndInfo, ref result);
Profiler.EndSample();
if (!ret)
return result;
}
result = XrEndFrameOriginal(session, ref frameEndInfo);
Profiler.BeginSample("VI:EndFrameA");
instance.AfterOriginalEndFrame?.Invoke(session, ref frameEndInfo, ref result);
Profiler.EndSample();
return result;
@@ -86,4 +101,4 @@ namespace VIVE.OpenXR
}
#endif
}
}
}

View File

@@ -0,0 +1,91 @@
// Copyright HTC Corporation All Rights Reserved.
using System.Runtime.InteropServices;
using System;
using AOT;
using UnityEngine.Profiling;
namespace VIVE.OpenXR
{
public partial class ViveInterceptors
{
[HookHandler("xrGetVisibilityMaskKHR")]
private static XrResult OnHookXrGetVisibilityMaskKHR(XrInstance instance, string name, out IntPtr function)
{
if (xrGetVisibilityMaskKHROriginal == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
xrGetVisibilityMaskKHROriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrGetVisibilityMaskKHR>(function);
}
function = xrGetVisibilityMaskKHRInterceptorPtr;
return XrResult.XR_SUCCESS;
}
public enum XrVisibilityMaskTypeKHR
{
HIDDEN_TRIANGLE_MESH_KHR = 1,
VISIBLE_TRIANGLE_MESH_KHR = 2,
LINE_LOOP_KHR = 3,
}
public struct XrVisibilityMaskKHR
{
public XrStructureType type;
public IntPtr next;
public uint vertexCapacityInput;
public uint vertexCountOutput;
public IntPtr vertices; // XrVector2f array
public uint indexCapacityInput;
public uint indexCountOutput;
public IntPtr indices; // uint array
}
// XrCompositionLayerSpaceWarpInfoFlagsFB bits
public delegate XrResult DelegateXrGetVisibilityMaskKHR(XrSession session, XrViewConfigurationType viewConfigurationType, uint viewIndex, XrVisibilityMaskTypeKHR visibilityMaskType, ref XrVisibilityMaskKHR visibilityMask);
private static readonly DelegateXrGetVisibilityMaskKHR xrGetVisibilityMaskKHRInterceptorHandle = new DelegateXrGetVisibilityMaskKHR(XrGetVisibilityMaskKHRInterceptor);
private static readonly IntPtr xrGetVisibilityMaskKHRInterceptorPtr = Marshal.GetFunctionPointerForDelegate(xrGetVisibilityMaskKHRInterceptorHandle);
static DelegateXrGetVisibilityMaskKHR xrGetVisibilityMaskKHROriginal = null;
[MonoPInvokeCallback(typeof(DelegateXrGetVisibilityMaskKHR))]
private static XrResult XrGetVisibilityMaskKHRInterceptor(XrSession session, XrViewConfigurationType viewConfigurationType, uint viewIndex, XrVisibilityMaskTypeKHR visibilityMaskType, ref XrVisibilityMaskKHR visibilityMask)
{
// instance must not null
//if (instance == null)
// return XrGetVisibilityMaskKHROriginal(session, ref frameEndInfo);
Profiler.BeginSample("VI:GetVMB");
XrResult result = XrResult.XR_SUCCESS;
bool ret = true;
if (instance.BeforeOriginalGetVisibilityMaskKHR != null)
ret = instance.BeforeOriginalGetVisibilityMaskKHR(session, viewConfigurationType, viewIndex, visibilityMaskType, ref visibilityMask, ref result);
Profiler.EndSample();
if (!ret)
return result;
result = xrGetVisibilityMaskKHROriginal(session, viewConfigurationType, viewIndex, visibilityMaskType, ref visibilityMask);
Profiler.BeginSample("VI:GetVMA");
instance.AfterOriginalGetVisibilityMaskKHR?.Invoke(session, viewConfigurationType, viewIndex, visibilityMaskType, ref visibilityMask, 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 DelegateXrGetVisibilityMaskKHRInterceptor(XrSession session, XrViewConfigurationType viewConfigurationType, uint viewIndex, XrVisibilityMaskTypeKHR visibilityMaskType, ref XrVisibilityMaskKHR visibilityMask, ref XrResult result);
/// <summary>
/// Use this to intercept the original function. This will be called before the original function.
/// </summary>
public DelegateXrGetVisibilityMaskKHRInterceptor BeforeOriginalGetVisibilityMaskKHR;
/// <summary>
/// Use this to intercept the original function. This will be called after the original function.
/// </summary>
public DelegateXrGetVisibilityMaskKHRInterceptor AfterOriginalGetVisibilityMaskKHR;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a4c00b0b7df78d34d89cd728c9de0672
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -9,7 +9,20 @@ namespace VIVE.OpenXR
{
partial class ViveInterceptors
{
#region XRWaitFrame
[HookHandler("xrWaitFrame")]
private static XrResult OnHookXrWaitFrame(XrInstance instance, string name, out IntPtr function)
{
if (XrWaitFrameOriginal == null)
{
var ret = XrGetInstanceProcAddrOriginal(instance, name, out function);
if (ret != XrResult.XR_SUCCESS)
return ret;
XrWaitFrameOriginal = Marshal.GetDelegateForFunctionPointer<DelegateXrWaitFrame>(function);
}
function = xrWaitFrameInterceptorPtr;
return XrResult.XR_SUCCESS;
}
public struct XrFrameWaitInfo
{
public XrStructureType type;
@@ -103,6 +116,5 @@ namespace VIVE.OpenXR
/// Use this to intercept the original function. This will be called after the original function.
/// </summary>
public DelegateXrWaitFrameInterceptor AfterOriginalWaitFrame;
#endregion XRWaitFrame
}
}

View File

@@ -0,0 +1,247 @@
// Copyright HTC Corporation All Rights Reserved.
#if UNITY_ANDROID && !UNITY_EDITOR
using System.Runtime.InteropServices;
#endif
using System.Text;
// Non android will need UnityEngine
using UnityEngine;
namespace VIVE.OpenXR
{
public static class Log
{
public const string TAG = "VIVE.OpenXR";
#if UNITY_ANDROID && !UNITY_EDITOR
[DllImport("liblog.so")]
private static extern int __android_log_print(int prio, string tag, string fmt, string msg);
#endif
// Use ("%s", message) instead of just (message) is because of the following reason:
// In case message contains special characters like %, \n, \r, etc. It will be treated as format string.
// This is a little waste of performance, but it's safer.
/// <summary>
/// Not show in Standalone
/// </summary>
public static void D(string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, TAG, "%s", message); // Android Debug
#endif
}
public static void I(string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, TAG, "%s", message); // Android Info
#else
Debug.Log(message);
#endif
}
public static void W(string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, TAG, "%s", message); // Android Warning
#else
Debug.LogWarning(message);
#endif
}
public static void E(string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, TAG, "%s", message); // Android Error
#else
Debug.LogError(message);
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void D(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, tag, "%s", message);
#endif
}
public static void I(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, tag, "%s", message);
#else
Debug.LogFormat("{0}: {1}", tag, message);
#endif
}
public static void W(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, tag, "%s", message); // Android Warning
#else
Debug.LogWarningFormat("{0}: {1}", tag, message);
#endif
}
public static void E(string tag, string message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, tag, "%s", message); // Android Error
#else
Debug.LogErrorFormat("{0}: {1}", tag, message);
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void D(StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, TAG, "%s", message.ToString());
#endif
}
public static void I(StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, TAG, "%s", message.ToString());
#else
Debug.Log(message.ToString());
#endif
}
public static void W(StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, TAG, "%s", message.ToString()); // Android Warning
#else
Debug.LogWarning(message.ToString());
#endif
}
public static void E(StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, TAG, "%s", message.ToString()); // Android Error
#else
Debug.LogError(message.ToString());
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void D(string tag, StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, tag, "%s", message.ToString());
#endif
}
public static void I(string tag, StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, tag, "%s", message.ToString());
#else
Debug.LogFormat("{0}: {1}", tag, message.ToString());
#endif
}
public static void W(string tag, StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, tag, "%s", message.ToString()); // Android Warning
#else
Debug.LogWarningFormat("{0}: {1}", tag, message.ToString());
#endif
}
public static void E(string tag, StringBuilder message)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, tag, "%s", message.ToString()); // Android Error
#else
Debug.LogErrorFormat("{0}: {1}", tag, message.ToString());
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void DFmt(string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, TAG, "%s", string.Format(fmt, args));
#endif
}
public static void IFmt(string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, TAG, "%s", string.Format(fmt, args));
#else
Debug.LogFormat(fmt, args);
#endif
}
public static void WFmt(string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, TAG, "%s", string.Format(fmt, args)); // Android Warning
#else
Debug.LogWarningFormat(fmt, args);
#endif
}
public static void EFmt(string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, TAG, "%s", string.Format(fmt, args)); // Android Error
#else
Debug.LogErrorFormat(fmt, args);
#endif
}
/// <summary>
/// Not show in Standalone
/// </summary>
public static void DFmt(string tag, string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(3, tag, "%s", string.Format(fmt, args));
#endif
}
public static void IFmt(string tag, string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(4, tag, "%s", string.Format(fmt, args));
#else
Debug.LogFormat("{0}: {1}", tag, string.Format(fmt, args));
#endif
}
public static void WFmt(string tag, string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(5, tag, "%s", string.Format(fmt, args)); // Android Warning
#else
Debug.LogWarningFormat("{0}: {1}", tag, fmt, string.Format(fmt, args));
#endif
}
public static void EFmt(string tag, string fmt, params object[] args)
{
#if UNITY_ANDROID && !UNITY_EDITOR
__android_log_print(6, tag, "%s", string.Format(fmt, args)); // Android Error
#else
Debug.LogErrorFormat("{0}: {1}", tag, fmt, string.Format(fmt, args));
#endif
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9750d8d4e8eb4994088534cb111510d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -58,7 +58,8 @@ namespace VIVE.OpenXR.Common.RenderThread
isFree = true
};
pool.Insert(index, newItem);
Debug.Log("RT.MessagePool.Obtain() pool count=" + pool.Count);
//Log.d("RT.MessagePool.Obtain<" + typeof(T) + ">() pool count=" + pool.Count); // Not to expose developer's type.
Log.D("RT.MessagePool.Obtain() pool count=" + pool.Count);
return newItem;
}
@@ -101,12 +102,13 @@ namespace VIVE.OpenXR.Common.RenderThread
/// 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++)
public PreAllocatedQueue(int queueSize = 2) : base()
{
for (int i = 0; i < queueSize; i++)
{
list.Add(null);
}
}
list.Add(null);
}
}
private int Next(int value)
{
@@ -154,7 +156,7 @@ namespace VIVE.OpenXR.Common.RenderThread
/// After use the Message, call Release() to the message.
/// </summary>
/// <returns></returns>
public Message Dequeue()
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.
@@ -163,12 +165,12 @@ namespace VIVE.OpenXR.Common.RenderThread
}
}
/// <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.
///
/// <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.
@@ -177,8 +179,8 @@ namespace VIVE.OpenXR.Common.RenderThread
///
/// 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
/// </summary>
public class RenderThreadTask
{
private static IntPtr GetFunctionPointerForDelegate(Delegate del)
{
@@ -208,20 +210,21 @@ namespace VIVE.OpenXR.Common.RenderThread
/// <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()
public RenderThreadTask(Receiver render, int queueSize = 2)
{
try { CommandList.RemoveAt(id); } finally { }
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()
{
// Remove could be in a random order, and will cause orderId change. DO not remove any of them.
//try { CommandList.Remove(this); } finally { }
}
void IssuePluginEvent(IntPtr callback, int eventID)
@@ -282,30 +285,36 @@ namespace VIVE.OpenXR.Common.RenderThread
// 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);
internal static readonly RenderThreadTask sampleRenderThreadTask2 = new RenderThreadTask(SampleReceiver2);
private static void SampleReceiver1(PreAllocatedQueue dataQueue)
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);
if (msg != null)
{
// 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);
}
private static void SampleReceiver2(PreAllocatedQueue dataQueue)
{
var msg = dataQueue.Dequeue() as SampleMessage;
if (msg != null)
{
// 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("Task2, the data passed to render thread: " + data);
}
}
// Send a message to the render thread every frame.
private void Update()
// 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>();
@@ -318,12 +327,12 @@ namespace VIVE.OpenXR.Common.RenderThread
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();
}
}
var msg = sampleRenderThreadTask2.Queue.Obtain<SampleMessage>();
msg.dataPassedToRenderThread = 234;
sampleRenderThreadTask2.Queue.Enqueue(msg);
sampleRenderThreadTask2.IssueEvent();
}
}
#endif
#endregion
}