using System; using System.Runtime.InteropServices; using UnityEngine.Profiling; namespace VIVE.OpenXR { internal static class MemoryTools { /// /// Make sure the input ptr is a OpenXR XrBaseStructure derived struct. /// /// the struct to get its next. /// the next's value 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; } /// /// Make sure the input ptr is a OpenXR XrBaseStructure derived struct. /// /// the struct to get its type /// the struct's type 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(IntPtr ptr) where T : unmanaged { //Profiler.BeginSample("PtrToStructure"); // Not to use Marshal.PtrToStructure 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(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, IntPtr ptr) where T : unmanaged { //Profiler.BeginSample("StructureToPtr"); // Not to use Marshal.StructureToPtr because it is slow. Buffer.MemoryCopy(&t, (void*)ptr, sizeof(T), sizeof(T)); //Profiler.EndSample(); } /// /// Convert the enum array to IntPtr. Should call after use. /// /// /// public static unsafe IntPtr ToIntPtr(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; } /// /// Convert the struct to IntPtr. Should call after use. /// /// /// /// public static IntPtr ToIntPtr(T structure) where T : struct { int size = Marshal.SizeOf(structure); IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.StructureToPtr(structure, ptr, true); return ptr; } /// /// Make the same size raw buffer from input array. /// /// Data type could be primitive type or struct. Should call after use. /// The data array /// The memory handle. Should release by public static unsafe IntPtr MakeRawMemory(T[] refArray) where T : unmanaged { int size = Marshal.SizeOf(typeof(T)) * refArray.Length; return Marshal.AllocHGlobal(size); } /// /// Copy the raw memory to the array. You should make sure the array has the same size as the raw memory. /// /// Convert the memory to this type array. /// The output array. /// The data source in raw memory form. /// Specify the copy count. Count should be less than array length. public static unsafe void CopyFromRawMemory(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(); } /// /// Copy all raw memory to the array. This has higher performance than . /// 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. /// /// Convert the memory to this type array. /// The output array. /// The data source in raw memory form. public static unsafe void CopyAllFromRawMemory(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(); } /// /// Make the same size raw buffer from input array. Make sure the raw has enough size. /// /// Convert this type array to raw memory. /// The output data in raw memory form /// The data source public static unsafe void CopyToRawMemory(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(); } /// /// Release the raw memory handle which is created by /// /// public static void ReleaseRawMemory(IntPtr ptr) { Marshal.FreeHGlobal(ptr); } /// /// Find a pointer in the next chain. Make sure the input next pointer is a OpenXR XrBaseStructure derived struct. /// /// /// /// true if exist public static bool HasPtrInNextChain(IntPtr target, IntPtr next) { while (next != IntPtr.Zero) { if (next == target) return true; next = GetNext(next); } return false; } } }