From 0c8fd026f612533de1d55038dbc4d68df45d8a87 Mon Sep 17 00:00:00 2001 From: Andreas Atteneder Date: Wed, 15 Sep 2021 20:43:05 +0200 Subject: [PATCH 1/7] feat: WIP support for EXT_meshopt_compression --- Runtime/Scripts/Extensions.cs | 1 + Runtime/Scripts/GltfImport.cs | 245 ++++++++++++++------ Runtime/Scripts/Schema/BufferView.cs | 71 +++++- Runtime/Scripts/Schema/glTFastSchema.asmdef | 8 +- Runtime/Scripts/glTFast.asmdef | 10 +- 5 files changed, 248 insertions(+), 87 deletions(-) diff --git a/Runtime/Scripts/Extensions.cs b/Runtime/Scripts/Extensions.cs index 07849384..16cb1cbe 100644 --- a/Runtime/Scripts/Extensions.cs +++ b/Runtime/Scripts/Extensions.cs @@ -35,6 +35,7 @@ public static class Extensions { public const string MaterialsTransmission = "KHR_materials_transmission"; public const string MaterialsUnlit = "KHR_materials_unlit"; public const string MeshGPUInstancing = "EXT_mesh_gpu_instancing"; + public const string MeshoptCompression = "EXT_meshopt_compression"; public const string MeshQuantization = "KHR_mesh_quantization"; public const string TextureBasisUniversal = "KHR_texture_basisu"; public const string TextureTransform = "KHR_texture_transform"; diff --git a/Runtime/Scripts/GltfImport.cs b/Runtime/Scripts/GltfImport.cs index bfff49ba..9b614043 100644 --- a/Runtime/Scripts/GltfImport.cs +++ b/Runtime/Scripts/GltfImport.cs @@ -35,6 +35,9 @@ using Unity.Collections.LowLevel.Unsafe; using Unity.Mathematics; using Debug = UnityEngine.Debug; +#if MESHOPT +using Meshoptimizer; +#endif #if MEASURE_TIMINGS using GLTFast.Tests; #endif @@ -83,6 +86,9 @@ public class GltfImport : IGltfReadable, IGltfBuffers { #if KTX_UNITY Extensions.TextureBasisUniversal, #endif // KTX_UNITY +#if MESHOPT + Extensions.MeshoptCompression, +#endif Extensions.MaterialsPbrSpecularGlossiness, Extensions.MaterialsUnlit, Extensions.TextureTransform, @@ -164,6 +170,10 @@ public class GltfImport : IGltfReadable, IGltfBuffers { /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#binary-buffer GlbBinChunk? glbBinChunk; +#if MESHOPT + Dictionary> meshoptBufferViews; +#endif + /// /// Unity's animation system addresses target GameObjects by hierarchical name. /// To make sure names are consistent and have no conflicts they are precalculated @@ -825,7 +835,9 @@ async Task WaitForBufferDownloads() { continue; } var b = buffers[i]; - binChunks[i] = new GlbBinChunk(0,(uint) b.Length); + if (b != null) { + binChunks[i] = new GlbBinChunk(0,(uint) b.Length); + } } Profiler.EndSample(); } @@ -1027,8 +1039,38 @@ byte[] GetBuffer(int index) { return buffers[index]; } - unsafe NativeSlice GetBufferView(BufferView bufferView) { - int bufferIndex = bufferView.buffer; + NativeSlice GetBufferView(int bufferViewIndex,int offset = 0, int length = 0) { + var bufferView = gltfRoot.bufferViews[bufferViewIndex]; +#if MESHOPT + if (bufferView.extensions?.EXT_meshopt_compression != null) { + var fullSlice = meshoptBufferViews[bufferViewIndex]; + if (offset == 0 && length <= 0) { + return fullSlice; + } + Assert.IsTrue(offset >= 0); + if (length <= 0) { + length = fullSlice.Length - offset; + } + Assert.IsTrue(offset+length <= fullSlice.Length); + return new NativeSlice(fullSlice,offset,length); + } +#endif + return GetBufferViewSlice(bufferView,offset,length); + } + + unsafe NativeSlice GetBufferViewSlice( + BufferViewBase bufferView, + int offset = 0, + int length = 0 + ) + { + Assert.IsTrue(offset >= 0); + if (length <= 0) { + length = bufferView.byteLength - offset; + } + Assert.IsTrue( offset+length <= bufferView.byteLength); + + var bufferIndex = bufferView.buffer; if(!nativeBuffers[bufferIndex].IsCreated) { Profiler.BeginSample("ConvertToNativeArray"); var buffer = GetBuffer(bufferIndex); @@ -1043,9 +1085,43 @@ unsafe NativeSlice GetBufferView(BufferView bufferView) { Profiler.EndSample(); } var chunk = binChunks[bufferIndex]; - return new NativeSlice(nativeBuffers[bufferIndex],chunk.start+bufferView.byteOffset,bufferView.byteLength); + return new NativeSlice( + nativeBuffers[bufferIndex], + chunk.start + bufferView.byteOffset + offset, + length + ); } +#if MESHOPT + void MeshoptDecode() { + if(gltfRoot.bufferViews!=null) { + for (var i = 0; i < gltfRoot.bufferViews.Length; i++) { + var bufferView = gltfRoot.bufferViews[i]; + if (bufferView.extensions?.EXT_meshopt_compression != null) { + var meshopt = bufferView.extensions?.EXT_meshopt_compression; + if (meshoptBufferViews == null) { + meshoptBufferViews = new Dictionary>(); + } + + var arr = new NativeArray(meshopt.count * meshopt.byteStride, Allocator.Persistent); + + var origBufferView = GetBufferViewSlice(meshopt); + + Decode.DecodeGltfBuffer( + arr, + meshopt.count, + meshopt.byteStride, + origBufferView, + meshopt.modeEnum, + meshopt.filterEnum + ); + meshoptBufferViews[i] = arr; + } + } + } + } +#endif // MESHOPT + async Task Prepare() { if(gltfRoot.meshes!=null) { meshPrimitiveIndex = new int[gltfRoot.meshes.Length+1]; @@ -1066,7 +1142,12 @@ async Task Prepare() { CreateTexturesFromBuffers(gltfRoot.images,gltfRoot.bufferViews,imageCreateContexts); } await deferAgent.BreakPoint(); - + +#if MESHOPT + // TODO: Make async and jobify + MeshoptDecode(); +#endif + var success = true; if(gltfRoot.accessors!=null) { @@ -1446,6 +1527,15 @@ void DisposeVolatileData() { imageReadable = null; imageGamma = null; glbBinChunk = null; + +#if MESHOPT + if(meshoptBufferViews!=null) { + foreach (var nativeBuffer in meshoptBufferViews.Values) { + nativeBuffer.Dispose(); + } + meshoptBufferViews = null; + } +#endif } void InstantiateSceneInternal( Root gltf, IInstantiator instantiator, int sceneId ) { @@ -1654,7 +1744,6 @@ async Task if (imgFormat!=ImageFormat.Unknown) { if (img.bufferView >= 0) { - var bufferView = bufferViews[img.bufferView]; if(imgFormat == ImageFormat.KTX) { #if KTX_UNITY @@ -1662,7 +1751,7 @@ async Task if(ktxLoadContextsBuffer==null) { ktxLoadContextsBuffer = new List(); } - var ktxContext = new KtxLoadNativeContext(i,GetBufferView(bufferView)); + var ktxContext = new KtxLoadNativeContext(i,GetBufferView(img.bufferView)); ktxLoadContextsBuffer.Add(ktxContext); Profiler.EndSample(); await deferAgent.BreakPoint(); @@ -1671,6 +1760,7 @@ async Task #endif // KTX_UNITY } else { Profiler.BeginSample("CreateTexturesFromBuffers.ExtractBuffer"); + var bufferView = bufferViews[img.bufferView]; var buffer = GetBuffer(bufferView.buffer); var chunk = binChunks[bufferView.buffer]; @@ -2253,7 +2343,7 @@ void PreparePrimitiveDraco( Root gltf, Mesh mesh, MeshPrimitive primitive, ref P var draco_ext = primitive.extensions.KHR_draco_mesh_compression; var bufferView = gltf.bufferViews[draco_ext.bufferView]; - var buffer = GetBufferView(bufferView); + var buffer = GetBufferViewSlice(bufferView); c.StartDecode(buffer, draco_ext.attributes.WEIGHTS_0, draco_ext.attributes.JOINTS_0); } @@ -2290,9 +2380,7 @@ unsafe void GetIndicesJob(Root gltf, int accessorIndex, out int[] indices, out J Profiler.BeginSample("PrepareGetIndicesJob"); // index var accessor = gltf.accessors[accessorIndex]; - var bufferView = gltf.bufferViews[accessor.bufferView]; - int bufferIndex = bufferView.buffer; - var buffer = GetBuffer(bufferIndex); + var bufferView = GetBufferView(accessor.bufferView,accessor.byteOffset); Profiler.BeginSample("Alloc"); indices = new int[accessor.count]; @@ -2301,28 +2389,26 @@ unsafe void GetIndicesJob(Root gltf, int accessorIndex, out int[] indices, out J resultHandle = GCHandle.Alloc(indices, GCHandleType.Pinned); Profiler.EndSample(); - var chunk = binChunks[bufferIndex]; Assert.AreEqual(accessor.typeEnum, GLTFAccessorAttributeType.SCALAR); //Assert.AreEqual(accessor.count * GetLength(accessor.typeEnum) * 4 , (int) chunk.length); if (accessor.isSparse) { logger.Error(LogCode.SparseAccessor,"indices"); } - var start = accessor.byteOffset + bufferView.byteOffset + chunk.start; Profiler.BeginSample("CreateJob"); switch( accessor.componentType ) { case GLTFComponentType.UnsignedByte: if(flip) { var job8 = new Jobs.ConvertIndicesUInt8ToInt32FlippedJob(); - fixed( void* src = &(buffer[start]), dst = &(indices[0]) ) { - job8.input = (byte*)src; + fixed( void* dst = &(indices[0]) ) { + job8.input = (byte*)bufferView.GetUnsafeReadOnlyPtr(); job8.result = (int3*)dst; } jobHandle = job8.Schedule(accessor.count/3,DefaultBatchCount); } else { var job8 = new Jobs.ConvertIndicesUInt8ToInt32Job(); - fixed( void* src = &(buffer[start]), dst = &(indices[0]) ) { - job8.input = (byte*)src; + fixed( void* dst = &(indices[0]) ) { + job8.input = (byte*)bufferView.GetUnsafeReadOnlyPtr(); job8.result = (int*)dst; } jobHandle = job8.Schedule(accessor.count,DefaultBatchCount); @@ -2331,15 +2417,15 @@ unsafe void GetIndicesJob(Root gltf, int accessorIndex, out int[] indices, out J case GLTFComponentType.UnsignedShort: if(flip) { var job16 = new Jobs.ConvertIndicesUInt16ToInt32FlippedJob(); - fixed( void* src = &(buffer[start]), dst = &(indices[0]) ) { - job16.input = (ushort*) src; + fixed( void* dst = &(indices[0]) ) { + job16.input = (ushort*) bufferView.GetUnsafeReadOnlyPtr(); job16.result = (int3*) dst; } jobHandle = job16.Schedule(accessor.count/3,DefaultBatchCount); } else { var job16 = new Jobs.ConvertIndicesUInt16ToInt32Job(); - fixed( void* src = &(buffer[start]), dst = &(indices[0]) ) { - job16.input = (ushort*) src; + fixed( void* dst = &(indices[0]) ) { + job16.input = (ushort*) bufferView.GetUnsafeReadOnlyPtr(); job16.result = (int*) dst; } jobHandle = job16.Schedule(accessor.count,DefaultBatchCount); @@ -2348,15 +2434,15 @@ unsafe void GetIndicesJob(Root gltf, int accessorIndex, out int[] indices, out J case GLTFComponentType.UnsignedInt: if(flip) { var job32 = new Jobs.ConvertIndicesUInt32ToInt32FlippedJob(); - fixed( void* src = &(buffer[start]), dst = &(indices[0]) ) { - job32.input = (uint*) src; + fixed( void* dst = &(indices[0]) ) { + job32.input = (uint*) bufferView.GetUnsafeReadOnlyPtr(); job32.result = (int3*) dst; } jobHandle = job32.Schedule(accessor.count/3,DefaultBatchCount); } else { var job32 = new Jobs.ConvertIndicesUInt32ToInt32Job(); - fixed( void* src = &(buffer[start]), dst = &(indices[0]) ) { - job32.input = (uint*) src; + fixed( void* dst = &(indices[0]) ) { + job32.input = (uint*) bufferView.GetUnsafeReadOnlyPtr(); job32.result = (int*) dst; } jobHandle = job32.Schedule(accessor.count,DefaultBatchCount); @@ -2375,30 +2461,25 @@ unsafe void GetMatricesJob(Root gltf, int accessorIndex, out NativeArray(accessor.count,Allocator.Persistent); Profiler.EndSample(); - var chunk = binChunks[bufferIndex]; Assert.AreEqual(accessor.typeEnum, GLTFAccessorAttributeType.MAT4); //Assert.AreEqual(accessor.count * GetLength(accessor.typeEnum) * 4 , (int) chunk.length); if (accessor.isSparse) { logger.Error(LogCode.SparseAccessor,"Matrix"); } - var start = accessor.byteOffset + bufferView.byteOffset + chunk.start; Profiler.BeginSample("CreateJob"); switch( accessor.componentType ) { case GLTFComponentType.Float: - var job32 = new Jobs.ConvertMatricesJob(); - job32.result = (float4x4*)matrices.GetUnsafePtr(); - fixed( void* src = &(buffer[start]) ) { - job32.input = (float4x4*) src; - } + var job32 = new Jobs.ConvertMatricesJob { + input = (float4x4*)bufferView.GetUnsafeReadOnlyPtr(), + result = (float4x4*)matrices.GetUnsafePtr() + }; jobHandle = job32.Schedule(accessor.count,DefaultBatchCount); break; default: @@ -2413,39 +2494,33 @@ unsafe void GetMatricesJob(Root gltf, int accessorIndex, out NativeArray vectors, out JobHandle? jobHandle, bool flip) { Profiler.BeginSample("GetVector3Job"); var accessor = gltf.accessors[accessorIndex]; - var bufferView = gltf.bufferViews[accessor.bufferView]; - var bufferIndex = bufferView.buffer; - var buffer = GetBuffer(bufferIndex); + var bufferView = GetBufferView(accessor.bufferView,accessor.byteOffset); Profiler.BeginSample("Alloc"); vectors = new NativeArray(accessor.count,Allocator.Persistent); Profiler.EndSample(); - var chunk = binChunks[bufferIndex]; Assert.AreEqual(accessor.typeEnum, GLTFAccessorAttributeType.VEC3); if (accessor.isSparse) { logger.Error(LogCode.SparseAccessor,"Vector3"); } - var start = accessor.byteOffset + bufferView.byteOffset + chunk.start; Profiler.BeginSample("CreateJob"); switch( accessor.componentType ) { case GLTFComponentType.Float when flip: { - var job = new ConvertVector3FloatToFloatJob { result = (float3*)vectors.GetUnsafePtr() }; - fixed( void* src = &(buffer[start]) ) { - job.input = (float3*) src; - } + var job = new ConvertVector3FloatToFloatJob { + input = (float3*)bufferView.GetUnsafeReadOnlyPtr(), + result = (float3*)vectors.GetUnsafePtr() + }; jobHandle = job.Schedule(accessor.count,DefaultBatchCount); break; } case GLTFComponentType.Float when !flip: { var job = new MemCopyJob { + input = (float*)bufferView.GetUnsafeReadOnlyPtr(), bufferSize = accessor.count * 12, result = (float*)vectors.GetUnsafePtr() }; - fixed( void* src = &(buffer[start]) ) { - job.input = (float*) src; - } jobHandle = job.Schedule(); break; } @@ -2462,50 +2537,40 @@ unsafe void GetVector4Job(Root gltf, int accessorIndex, out NativeArray(accessor.count,Allocator.Persistent); Profiler.EndSample(); - var chunk = binChunks[bufferIndex]; Assert.AreEqual(accessor.typeEnum, GLTFAccessorAttributeType.VEC4); if (accessor.isSparse) { logger.Error(LogCode.SparseAccessor,"Vector4"); } - var start = accessor.byteOffset + bufferView.byteOffset + chunk.start; Profiler.BeginSample("CreateJob"); switch( accessor.componentType ) { case GLTFComponentType.Float: { var job = new ConvertRotationsFloatToFloatJob { + input = (float4*)bufferView.GetUnsafeReadOnlyPtr(), result = (float4*)vectors.GetUnsafePtr() }; - fixed( void* src = &(buffer[start]) ) { - job.input = (float4*) src; - } jobHandle = job.Schedule(accessor.count,DefaultBatchCount); break; } case GLTFComponentType.Short: { var job = new ConvertRotationsInt16ToFloatJob { + input = (short*)bufferView.GetUnsafeReadOnlyPtr(), result = (float*)vectors.GetUnsafePtr() }; - fixed( void* src = &(buffer[start]) ) { - job.input = (short*) src; - } jobHandle = job.Schedule(accessor.count,DefaultBatchCount); break; } case GLTFComponentType.Byte: { var job = new ConvertRotationsInt8ToFloatJob { + input = (sbyte*)bufferView.GetUnsafeReadOnlyPtr(), result = (float*)vectors.GetUnsafePtr() }; - fixed( void* src = &(buffer[start]) ) { - job.input = (sbyte*) src; - } jobHandle = job.Schedule(accessor.count,DefaultBatchCount); break; } @@ -2524,8 +2589,7 @@ unsafe void GetScalarJob(Root gltf, int accessorIndex, out NativeArray? s scalars = null; jobHandle = null; var accessor = gltf.accessors[accessorIndex]; - var bufferView = gltf.bufferViews[accessor.bufferView]; - var buffer = GetBufferView(bufferView); + var buffer = GetBufferView(accessor.bufferView,accessor.byteOffset); Assert.AreEqual(accessor.typeEnum, GLTFAccessorAttributeType.SCALAR); if (accessor.isSparse) { @@ -2601,11 +2665,20 @@ public unsafe void GetAccessor(int index, out Accessor accessor, out void* data, return; } var bufferView = gltfRoot.bufferViews[accessor.bufferView]; - byteStride = bufferView.byteStride; - var bufferIndex = bufferView.buffer; - var buffer = GetBuffer(bufferIndex); - fixed(void* src = &(buffer[accessor.byteOffset + bufferView.byteOffset + binChunks[bufferIndex].start])) { - data = src; +#if MESHOPT + var meshopt = bufferView.extensions?.EXT_meshopt_compression; + if (meshopt != null) { + byteStride = meshopt.byteStride; + data = (byte*)meshoptBufferViews[accessor.bufferView].GetUnsafeReadOnlyPtr() + accessor.byteOffset; + } else +#endif + { + byteStride = bufferView.byteStride; + var bufferIndex = bufferView.buffer; + var buffer = GetBuffer(bufferIndex); + fixed(void* src = &(buffer[accessor.byteOffset + bufferView.byteOffset + binChunks[bufferIndex].start])) { + data = src; + } } // // Alternative that uses NativeArray/Slice @@ -2615,19 +2688,37 @@ public unsafe void GetAccessor(int index, out Accessor accessor, out void* data, public unsafe void GetAccessorSparseIndices(AccessorSparseIndices sparseIndices, out void* data) { var bufferView = gltfRoot.bufferViews[sparseIndices.bufferView]; - var bufferIndex = bufferView.buffer; - var buffer = GetBuffer(bufferIndex); - fixed (void* src = &(buffer[sparseIndices.byteOffset + bufferView.byteOffset + binChunks[bufferIndex].start])) { - data = src; +#if MESHOPT + var meshopt = bufferView.extensions?.EXT_meshopt_compression; + if (meshopt != null) { + data = (byte*)meshoptBufferViews[(int)sparseIndices.bufferView].GetUnsafeReadOnlyPtr() + sparseIndices.byteOffset; + } + else +#endif + { + var bufferIndex = bufferView.buffer; + var buffer = GetBuffer(bufferIndex); + fixed (void* src = &(buffer[sparseIndices.byteOffset + bufferView.byteOffset + binChunks[bufferIndex].start])) { + data = src; + } } } public unsafe void GetAccessorSparseValues(AccessorSparseValues sparseValues, out void* data) { var bufferView = gltfRoot.bufferViews[sparseValues.bufferView]; - var bufferIndex = bufferView.buffer; - var buffer = GetBuffer(bufferIndex); - fixed (void* src = &(buffer[sparseValues.byteOffset + bufferView.byteOffset + binChunks[bufferIndex].start])) { - data = src; +#if MESHOPT + var meshopt = bufferView.extensions?.EXT_meshopt_compression; + if (meshopt != null) { + data = (byte*)meshoptBufferViews[(int)sparseValues.bufferView].GetUnsafeReadOnlyPtr() + sparseValues.byteOffset; + } + else +#endif + { + var bufferIndex = bufferView.buffer; + var buffer = GetBuffer(bufferIndex); + fixed (void* src = &(buffer[sparseValues.byteOffset + bufferView.byteOffset + binChunks[bufferIndex].start])) { + data = src; + } } } #endregion IGltfBuffers diff --git a/Runtime/Scripts/Schema/BufferView.cs b/Runtime/Scripts/Schema/BufferView.cs index 6531d7c3..e51eb1e9 100644 --- a/Runtime/Scripts/Schema/BufferView.cs +++ b/Runtime/Scripts/Schema/BufferView.cs @@ -13,14 +13,18 @@ // limitations under the License. // +#if MESHOPT +using static Meshoptimizer.Decode; +#endif + namespace GLTFast.Schema { - public enum BufferViewTarget - { - None = 0, - ArrayBuffer = 34962, - ElementArrayBuffer = 34963, - } + // public enum BufferViewTarget + // { + // None = 0, + // ArrayBuffer = 34962, + // ElementArrayBuffer = 34963, + // } [System.Serializable] public class BufferSlice { @@ -38,7 +42,7 @@ public class BufferSlice { } [System.Serializable] - public class BufferView : BufferSlice { + public class BufferViewBase : BufferSlice { /// /// The index of the buffer. /// @@ -51,7 +55,11 @@ public class BufferView : BufferSlice { /// 255 /// public int byteStride = -1; + } + + [System.Serializable] + public class BufferView : BufferViewBase { /// /// The target that the WebGL buffer should be bound to. /// All valid values correspond to WebGL enums. @@ -74,5 +82,54 @@ public void GltfSerialize(JsonWriter writer) { } writer.Close(); } + +#if MESHOPT + public BufferViewExtensions extensions; +#endif + } + +#if MESHOPT + [System.Serializable] + public class BufferViewExtensions { + public BufferViewMeshoptExtension EXT_meshopt_compression; + } + + [System.Serializable] + public class BufferViewMeshoptExtension : BufferViewBase { + + public int count; + public string mode; + public string filter; + + Mode _modeEnum = Mode.Undefined; + public Mode modeEnum { + get { + if (_modeEnum != Mode.Undefined) { + return _modeEnum; + } + if (!string.IsNullOrEmpty (mode)) { + _modeEnum = (Mode)System.Enum.Parse (typeof(Mode), mode, true); + mode = null; + return _modeEnum; + } + return Mode.Undefined; + } + } + + Filter _filterEnum = Filter.Undefined; + public Filter filterEnum { + get { + if (_filterEnum != Filter.Undefined) { + return _filterEnum; + } + if (!string.IsNullOrEmpty (filter)) { + _filterEnum = (Filter)System.Enum.Parse (typeof(Filter), filter, true); + filter = null; + return _filterEnum; + } + return Filter.None; + } + } } +#endif } \ No newline at end of file diff --git a/Runtime/Scripts/Schema/glTFastSchema.asmdef b/Runtime/Scripts/Schema/glTFastSchema.asmdef index ce3e2cfa..7ae7bc90 100644 --- a/Runtime/Scripts/Schema/glTFastSchema.asmdef +++ b/Runtime/Scripts/Schema/glTFastSchema.asmdef @@ -2,7 +2,8 @@ "name": "glTFastSchema", "rootNamespace": "", "references": [ - "Unity.Mathematics" + "Unity.Mathematics", + "meshoptimizer" ], "includePlatforms": [], "excludePlatforms": [], @@ -22,6 +23,11 @@ "expression": "1.1.0", "define": "KTX_UNITY" }, + { + "name": "com.unity.meshoptimizer", + "expression": "", + "define": "MESHOPT" + }, { "name": "com.unity.modules.animation", "expression": "1.0.0", diff --git a/Runtime/Scripts/glTFast.asmdef b/Runtime/Scripts/glTFast.asmdef index d2dd6a48..cc2a1859 100644 --- a/Runtime/Scripts/glTFast.asmdef +++ b/Runtime/Scripts/glTFast.asmdef @@ -1,14 +1,15 @@ { "name": "glTFast", "references": [ - "Draco", "glTFastSchema", "glTFastFakeSchema", - "Ktx", "Unity.Mathematics", "Unity.Burst", "Unity.Collections", "Unity.Jobs", + "Draco", + "Ktx", + "meshoptimizer", "Unity.RenderPipelines.Universal.Runtime", "Unity.RenderPipelines.HighDefinition.Runtime" ], @@ -54,6 +55,11 @@ "name": "com.unity.jobs", "expression": "0.2.0", "define": "UNITY_JOBS" + }, + { + "name": "com.unity.meshoptimizer", + "expression": "", + "define": "MESHOPT" } ], "noEngineReferences": false From dfa33c33d535de97538a5e2fe53862eba0474722 Mon Sep 17 00:00:00 2001 From: Andreas Atteneder Date: Thu, 16 Sep 2021 13:15:47 +0200 Subject: [PATCH 2/7] feat: MeshOpt is now jobified and async --- Runtime/Scripts/GltfImport.cs | 53 ++++++++++++++++++++++++---- Runtime/Scripts/Schema/BufferView.cs | 2 +- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/Runtime/Scripts/GltfImport.cs b/Runtime/Scripts/GltfImport.cs index 9b614043..6c934ea8 100644 --- a/Runtime/Scripts/GltfImport.cs +++ b/Runtime/Scripts/GltfImport.cs @@ -172,6 +172,8 @@ public class GltfImport : IGltfReadable, IGltfBuffers { #if MESHOPT Dictionary> meshoptBufferViews; + NativeArray meshoptReturnValues; + JobHandle meshoptJobHandle; #endif /// @@ -547,6 +549,12 @@ async Task LoadContent() { var success = await WaitForBufferDownloads(); downloadTasks?.Clear(); +#if MESHOPT + if (success) { + MeshoptDecode(); + } +#endif + if (textureDownloadTasks != null) { success = success && await WaitForTextureDownloads(); textureDownloadTasks.Clear(); @@ -1095,19 +1103,23 @@ unsafe NativeSlice GetBufferViewSlice( #if MESHOPT void MeshoptDecode() { if(gltfRoot.bufferViews!=null) { + List jobHandlesList = null; for (var i = 0; i < gltfRoot.bufferViews.Length; i++) { var bufferView = gltfRoot.bufferViews[i]; if (bufferView.extensions?.EXT_meshopt_compression != null) { var meshopt = bufferView.extensions?.EXT_meshopt_compression; - if (meshoptBufferViews == null) { + if (jobHandlesList == null) { meshoptBufferViews = new Dictionary>(); + jobHandlesList = new List(gltfRoot.bufferViews.Length); + meshoptReturnValues = new NativeArray(gltfRoot.bufferViews.Length, Allocator.TempJob); } var arr = new NativeArray(meshopt.count * meshopt.byteStride, Allocator.Persistent); var origBufferView = GetBufferViewSlice(meshopt); - - Decode.DecodeGltfBuffer( + + var jobHandle = Decode.DecodeGltfBuffer( + new NativeSlice(meshoptReturnValues,i,1), arr, meshopt.count, meshopt.byteStride, @@ -1115,11 +1127,35 @@ void MeshoptDecode() { meshopt.modeEnum, meshopt.filterEnum ); + jobHandlesList.Add(jobHandle); meshoptBufferViews[i] = arr; } } + + if (jobHandlesList != null) { + using (var jobHandles = new NativeArray(jobHandlesList.ToArray(), Allocator.Temp)) { + meshoptJobHandle = JobHandle.CombineDependencies(jobHandles); + } + } + } + } + + async Task WaitForMeshoptDecode() { + var success = true; + if (meshoptBufferViews != null) { + while (!meshoptJobHandle.IsCompleted) { + await Task.Yield(); + } + meshoptJobHandle.Complete(); + + foreach (var returnValue in meshoptReturnValues) { + success &= returnValue == 0; + } + meshoptReturnValues.Dispose(); } + return success; } + #endif // MESHOPT async Task Prepare() { @@ -1143,13 +1179,13 @@ async Task Prepare() { } await deferAgent.BreakPoint(); + var success = true; + #if MESHOPT - // TODO: Make async and jobify - MeshoptDecode(); + success = await WaitForMeshoptDecode(); + if (!success) return false; #endif - var success = true; - if(gltfRoot.accessors!=null) { success = await LoadAccessorData(gltfRoot); await deferAgent.BreakPoint(); @@ -1535,6 +1571,9 @@ void DisposeVolatileData() { } meshoptBufferViews = null; } + if (meshoptReturnValues.IsCreated) { + meshoptReturnValues.Dispose(); + } #endif } diff --git a/Runtime/Scripts/Schema/BufferView.cs b/Runtime/Scripts/Schema/BufferView.cs index e51eb1e9..db715a7c 100644 --- a/Runtime/Scripts/Schema/BufferView.cs +++ b/Runtime/Scripts/Schema/BufferView.cs @@ -14,7 +14,7 @@ // #if MESHOPT -using static Meshoptimizer.Decode; +using Meshoptimizer; #endif namespace GLTFast.Schema { From bc68e8cb69dc28a67bc3c34d458ed70fc2ed84cf Mon Sep 17 00:00:00 2001 From: Andreas Atteneder Date: Mon, 20 Sep 2021 17:07:12 +0200 Subject: [PATCH 3/7] fix: renamed meshopt package --- Runtime/Scripts/Schema/glTFastSchema.asmdef | 4 ++-- Runtime/Scripts/glTFast.asmdef | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Runtime/Scripts/Schema/glTFastSchema.asmdef b/Runtime/Scripts/Schema/glTFastSchema.asmdef index 7ae7bc90..3007f21a 100644 --- a/Runtime/Scripts/Schema/glTFastSchema.asmdef +++ b/Runtime/Scripts/Schema/glTFastSchema.asmdef @@ -3,7 +3,7 @@ "rootNamespace": "", "references": [ "Unity.Mathematics", - "meshoptimizer" + "Unity.Meshopt.Decompress" ], "includePlatforms": [], "excludePlatforms": [], @@ -24,7 +24,7 @@ "define": "KTX_UNITY" }, { - "name": "com.unity.meshoptimizer", + "name": "com.unity.meshopt.decompress", "expression": "", "define": "MESHOPT" }, diff --git a/Runtime/Scripts/glTFast.asmdef b/Runtime/Scripts/glTFast.asmdef index cc2a1859..24fb0e1a 100644 --- a/Runtime/Scripts/glTFast.asmdef +++ b/Runtime/Scripts/glTFast.asmdef @@ -9,7 +9,7 @@ "Unity.Jobs", "Draco", "Ktx", - "meshoptimizer", + "Unity.Meshopt.Decompress", "Unity.RenderPipelines.Universal.Runtime", "Unity.RenderPipelines.HighDefinition.Runtime" ], @@ -57,7 +57,7 @@ "define": "UNITY_JOBS" }, { - "name": "com.unity.meshoptimizer", + "name": "com.unity.meshopt.decompress", "expression": "", "define": "MESHOPT" } From 310290ef055191cc52c672ea7d719fb673dacf17 Mon Sep 17 00:00:00 2001 From: Andreas Atteneder Date: Wed, 29 Sep 2021 10:30:01 +0200 Subject: [PATCH 4/7] doc: Added change log (fixes #106) --- ChangeLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog.md b/ChangeLog.md index 5ac1d657..dea0d12a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unrelease] - ### Added - Experimental glTF Editor Export (under main menu `File > Export` and via API `GLTFast.Export.GameObjectExport`; #249) +- Support for meshopt compressed glTFs (EXT_meshopt_compression; #106) - *Generate Lightmap UVs* option in the glTF import inspector lets you create a secondary texture coordinate set (similar to the Model Import Settings from other formats; #238) - Generic `ICodeLogger` methods that don't require a `LogCode` ### Changed From ff989497cc1bc0c1f33b8fe56347163c92d11257 Mon Sep 17 00:00:00 2001 From: Andreas Atteneder Date: Tue, 26 Oct 2021 23:10:27 +0200 Subject: [PATCH 5/7] chore: sorted links --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ea1355ae..f622c691 100644 --- a/README.md +++ b/README.md @@ -149,13 +149,13 @@ limitations under the License. *Khronos®* is a registered trademark and *glTF™* is a trademark of [The Khronos Group Inc][khronos]. -[unity]: https://unity.com -[gltf]: https://www.khronos.org/gltf -[gltf-spec]: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html -[gltfast-web-demo]: https://gltf.pixel.engineer -[khronos]: https://www.khronos.org [embibe]: https://www.embibe.com +[gltf-spec]: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html +[gltf]: https://www.khronos.org/gltf [gltfasset_component]: ./Documentation~/img/gltfasset_component.png "Inspector showing a GltfAsset component added to a GameObject" +[gltfast-web-demo]: https://gltf.pixel.engineer [import-gif]: ./Documentation~/img/import.gif "Video showing glTF files being copied into the Assets folder and imported" +[khronos]: https://www.khronos.org +[unity]: https://unity.com [upm_install]: ./Documentation~/img/upm_install.png "Unity Package Manager add menu" [workflows]: ./Documentation~/glTFast.md#workflows \ No newline at end of file From 1988434c369d683cf54949da0748ce649a91367b Mon Sep 17 00:00:00 2001 From: Andreas Atteneder Date: Tue, 26 Oct 2021 23:40:01 +0200 Subject: [PATCH 6/7] doc: Added meshopt to docs and made minor improvements --- Documentation~/features.md | 10 ++++++---- README.md | 20 +++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Documentation~/features.md b/Documentation~/features.md index 6dbb1372..fb500646 100644 --- a/Documentation~/features.md +++ b/Documentation~/features.md @@ -49,10 +49,11 @@ The glTF 2.0 specification is fully supported, with only a few minor remarks. |glTF (.gltf) | ✅ | ✅ |glTF-Binary (.glb) | ✅ | ✅ | | | -| **Buffer type** +| **Buffer** | External URIs | ✅ | ✅ | GLB main buffer | ✅ | ✅ -| Embed buffers or textures (base-64 encoded within JSON) | ✅ | +| Embed buffers or textures (base-64 encoded within JSON) | ✅ | +| [meshoptimizer compression][MeshOpt] (via [package][MeshOptPkg])| ✅ | | | | | **Basics** | Scenes | ✅ | ✅ @@ -136,7 +137,7 @@ The glTF 2.0 specification is fully supported, with only a few minor remarks. | | | | **Vendor** | 1EXT_mesh_gpu_instancing | ✅ | -| EXT_meshopt_compression | [ℹ️][MeshOpt] | +| EXT_meshopt_compression | ✅ | | EXT_lights_image_based | [ℹ️][IBL] | 1: Without support for custom vertex attributes (e.g. `_ID`) @@ -262,7 +263,8 @@ Possibly incomplete list of things that are known to not work with Entities yet: [HDRP]: https://unity.com/srp/High-Definition-Render-Pipeline [IBL]: https://github.com/atteneder/glTFast/issues/108 [IOR]: https://github.com/atteneder/glTFast/issues/207 -[MeshOpt]: https://github.com/atteneder/glTFast/issues/106 +[MeshOpt]: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_meshopt_compression +[MeshOptPkg]: https://docs.unity3d.com/Packages/com.unity.meshopt.decompress@0.1/manual/index.html [newIssue]: https://github.com/atteneder/glTFast/issues/new [PointLights]: https://github.com/atteneder/glTFast/issues/17 [RuntimeExport]: https://github.com/atteneder/glTFast/issues/259 diff --git a/README.md b/README.md index f622c691..bf0d8f00 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,13 @@ It runs a script that installs *glTFast* via a [scoped registry](https://docs.un Afterwards *glTFast* and further, optional packages are listed in the *Package Manager* (under *My Registries*) and can be installed and updated from there. -### Optional dependencies +### Optional Packages -- [Draco 3D Data Compression Unity Package](https://github.com/atteneder/DracoUnity) (provides support for [KHR_draco_mesh_compression](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression)) -- [KTX/Basis Texture Unity Package](https://github.com/atteneder/KtxUnity) (in Beta; provides support for [KHR_texture_basisu](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu)) +There are some related package that improve *glTFast* by extending its feature set. + +- [Draco 3D Data Compression Unity Package][DracoUnity] (provides support for [KHR_draco_mesh_compression][ExtDraco]) +- [KTX/Basis Texture Unity Package][KtxUnity] (in Beta; provides support for [KHR_texture_basisu][ExtBasisU]) +- [*meshoptimizer decompression for Unity*][Meshopt] (provides support for [EXT_meshopt_compression][ExtMeshopt])
Alternative: Install via GIT URL @@ -63,9 +66,10 @@ Enter the following URL: `https://github.com/atteneder/glTFast.git` -To add support for Draco mesh compression, repeat the last step and also add the DracoUnity packages using this URL: +To add more functionality, repeat the last step and also add related packages using these URLs: -`https://gitlab.com/atteneder/DracoUnity.git` +- `https://gitlab.com/atteneder/DracoUnity.git` for Draco mesh compression +- `https://github.com/atteneder/KtxUnity.git` for KTX texture compression > Note: You have to have a GIT LFS client (large file support) installed on your system. Otherwise you will get an error that the native library file (dll on Windows) is corrupt! @@ -150,12 +154,18 @@ limitations under the License. *Khronos®* is a registered trademark and *glTF™* is a trademark of [The Khronos Group Inc][khronos]. [embibe]: https://www.embibe.com +[DracoUnity]: https://github.com/atteneder/DracoUnity +[ExtBasisU]: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu +[ExtDraco]: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression +[ExtMeshopt]: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/EXT_meshopt_compression [gltf-spec]: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html [gltf]: https://www.khronos.org/gltf [gltfasset_component]: ./Documentation~/img/gltfasset_component.png "Inspector showing a GltfAsset component added to a GameObject" [gltfast-web-demo]: https://gltf.pixel.engineer [import-gif]: ./Documentation~/img/import.gif "Video showing glTF files being copied into the Assets folder and imported" [khronos]: https://www.khronos.org +[KtxUnity]: https://github.com/atteneder/KtxUnity +[Meshopt]: https://docs.unity3d.com/Packages/com.unity.meshopt.decompress@0.1/manual/index.html [unity]: https://unity.com [upm_install]: ./Documentation~/img/upm_install.png "Unity Package Manager add menu" [workflows]: ./Documentation~/glTFast.md#workflows \ No newline at end of file From b5f9af86a702f0bbd2e815c394b61ace112c8ac6 Mon Sep 17 00:00:00 2001 From: Andreas Atteneder Date: Wed, 27 Oct 2021 00:38:21 +0200 Subject: [PATCH 7/7] doc: Updated package description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8ff4c26..adf28146 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "com.atteneder.gltfast", "version": "4.4.0", "displayName": "glTFast", - "description": "Load glTF 3D files fast at runtime or import them into the asset database in the Editor", + "description": "Use glTFast to import and export glTF 3D files efficiently at runtime or in the Editor", "unity": "2019.4", "keywords": [ "mesh",