diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/NativeFile.cs b/Packages/UniGLTF/Runtime/UniGLTF/IO/NativeFile.cs new file mode 100644 index 000000000..a5c327e06 --- /dev/null +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/NativeFile.cs @@ -0,0 +1,59 @@ +using System.IO; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.IO.LowLevel.Unsafe; + +namespace UniGLTF +{ + /// + /// Read a file directly into a NativeArray via AsyncReadManager without a managed byte[]. + /// Ported from Unio (https://github.com/hadashiA/Unio) MIT License. + /// + public static class NativeFile + { + public static unsafe NativeArray ReadAllBytes(string filePath) + { + FileInfoResult fileInfoResult; + var fileInfoHandle = AsyncReadManager.GetFileInfo(filePath, &fileInfoResult); + fileInfoHandle.JobHandle.Complete(); + + if (fileInfoResult.FileState == FileState.Absent) + { + throw new FileNotFoundException(filePath); + } + + var size = fileInfoResult.FileSize; + var buffer = (byte*)UnsafeUtility.Malloc(size, 16, Allocator.Persistent); + var readCommand = new ReadCommand + { + Offset = 0, + Size = size, + Buffer = buffer, + }; + + var readHandle = AsyncReadManager.Read(filePath, &readCommand, 1); + try + { + readHandle.JobHandle.Complete(); + if (readHandle.Status != ReadStatus.Complete) + { + UnsafeUtility.Free(buffer, Allocator.Persistent); + throw new IOException($"Read operation failed ({readHandle.Status}). {filePath}"); + } + } + finally + { + if (readHandle.IsValid()) + { + readHandle.Dispose(); + } + } + + var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(buffer, (int)size, Allocator.Persistent); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, AtomicSafetyHandle.Create()); +#endif + return array; + } + } +} \ No newline at end of file diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/NativeFile.cs.meta b/Packages/UniGLTF/Runtime/UniGLTF/IO/NativeFile.cs.meta new file mode 100644 index 000000000..958a58db4 --- /dev/null +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/NativeFile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5b957f0aa4fb4d05b57882560f2d778 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/IO/Vrm10.cs b/Packages/VRM10/Runtime/IO/Vrm10.cs index 1008602fa..6bc6c9052 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10.cs @@ -1,8 +1,8 @@ using System; -using System.IO; using System.Threading; using System.Threading.Tasks; using UniGLTF; +using Unity.Collections; using UnityEngine; namespace UniVRM10 @@ -53,23 +53,34 @@ namespace UniVRM10 ? new RuntimeOnlyAwaitCaller() : new ImmediateCaller(); - using var gltfData = await awaitCaller.Run(() => + NativeArray bytes = default; + try { - var bytes = File.ReadAllBytes(path); - return new GlbLowLevelParser(path, bytes).Parse(); - }); - return await LoadAsync( - gltfData, - canLoadVrm0X, - controlRigGenerationOption, - showMeshes, - awaitCaller, - textureDeserializer, - materialGenerator, - vrmMetaInformationCallback, - ct, - importerContextSettings, - springboneRuntime); + using var gltfData = await awaitCaller.Run(() => + { + bytes = NativeFile.ReadAllBytes(path); + return GlbLowLevelParser.Parse(path, bytes); + }); + return await LoadAsync( + gltfData, + canLoadVrm0X, + controlRigGenerationOption, + showMeshes, + awaitCaller, + textureDeserializer, + materialGenerator, + vrmMetaInformationCallback, + ct, + importerContextSettings, + springboneRuntime); + } + finally + { + if (bytes.IsCreated) + { + bytes.Dispose(); + } + } } /// @@ -89,7 +100,7 @@ namespace UniVRM10 /// CancellationToken /// Importer context settings. /// vrm-1.0 instance. Maybe return null if unexpected error was raised. - public static async Task LoadBytesAsync( + public static Task LoadBytesAsync( byte[] bytes, bool canLoadVrm0X = true, ControlRigGenerationOption controlRigGenerationOption = ControlRigGenerationOption.Generate, @@ -101,12 +112,80 @@ namespace UniVRM10 CancellationToken ct = default, ImporterContextSettings importerContextSettings = null, IVrm10SpringBoneRuntime springboneRuntime = null) + { + return LoadBytesAsync( + bytes.AsMemory(), + canLoadVrm0X, + controlRigGenerationOption, + showMeshes, + awaitCaller, + textureDeserializer, + materialGenerator, + vrmMetaInformationCallback, + ct, + importerContextSettings, + springboneRuntime); + } + + /// + /// Load the VRM file from the NativeArray. + /// + /// The caller is responsible for disposing `bytes`. Do not dispose it until this task completes. + /// You should call this on Unity main thread. + /// This will throw Exceptions (include OperationCanceledException). + /// + public static Task LoadBytesAsync( + NativeArray bytes, + bool canLoadVrm0X = true, + ControlRigGenerationOption controlRigGenerationOption = ControlRigGenerationOption.Generate, + bool showMeshes = true, + IAwaitCaller awaitCaller = null, + ITextureDeserializer textureDeserializer = null, + IMaterialDescriptorGenerator materialGenerator = null, + VrmMetaInformationCallback vrmMetaInformationCallback = null, + CancellationToken ct = default, + ImporterContextSettings importerContextSettings = null, + IVrm10SpringBoneRuntime springboneRuntime = null) + { + return LoadBytesAsync( + bytes.AsMemory(), + canLoadVrm0X, + controlRigGenerationOption, + showMeshes, + awaitCaller, + textureDeserializer, + materialGenerator, + vrmMetaInformationCallback, + ct, + importerContextSettings, + springboneRuntime); + } + + /// + /// Load the VRM file from the memory. + /// + /// The caller is responsible for keeping the underlying memory alive until this task completes. + /// You should call this on Unity main thread. + /// This will throw Exceptions (include OperationCanceledException). + /// + public static async Task LoadBytesAsync( + ReadOnlyMemory bytes, + bool canLoadVrm0X = true, + ControlRigGenerationOption controlRigGenerationOption = ControlRigGenerationOption.Generate, + bool showMeshes = true, + IAwaitCaller awaitCaller = null, + ITextureDeserializer textureDeserializer = null, + IMaterialDescriptorGenerator materialGenerator = null, + VrmMetaInformationCallback vrmMetaInformationCallback = null, + CancellationToken ct = default, + ImporterContextSettings importerContextSettings = null, + IVrm10SpringBoneRuntime springboneRuntime = null) { awaitCaller ??= Application.isPlaying ? new RuntimeOnlyAwaitCaller() : new ImmediateCaller(); - using var gltfData = await awaitCaller.Run(() => new GlbLowLevelParser(string.Empty, bytes).Parse()); + using var gltfData = await awaitCaller.Run(() => GlbLowLevelParser.Parse("", bytes)); return await LoadAsync( gltfData, canLoadVrm0X,