diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshVertex.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshVertex.cs index 4db319f40..4bb6d1d35 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshVertex.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshVertex.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Rendering; @@ -8,7 +9,7 @@ namespace UniGLTF /// インターリーブされたメッシュの頂点情報を表す構造体 /// そのままGPUにアップロードされる /// - [StructLayout(LayoutKind.Sequential)] + [Serializable, StructLayout(LayoutKind.Sequential)] internal readonly struct MeshVertex { private readonly Vector3 _position; diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/SkinJoint.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/SkinJoint.cs index 6e38e6642..5c2ec357f 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/SkinJoint.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/SkinJoint.cs @@ -2,6 +2,7 @@ using System; namespace UniGLTF { + [Serializable] public struct SkinJoints : IEquatable { public ushort Joint0; diff --git a/Assets/VRM10/Runtime/IO/Model/CopyIndicesJob.cs b/Assets/VRM10/Runtime/IO/Model/CopyIndicesJob.cs new file mode 100644 index 000000000..757083812 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Model/CopyIndicesJob.cs @@ -0,0 +1,120 @@ +using Unity.Collections; +using Unity.Jobs; + +#if ENABLE_VRM10_BURST +using Unity.Burst; +#endif + +namespace UniVRM10 +{ + /// + /// インデックス配列を、オフセットを加えながら複製するJob郡 + /// MEMO: ushortを考慮することをやめればかなりシンプルに書ける + /// + internal struct CopyIndicesJobs + { + /// + /// unsigned int -> unsigned int + /// +#if ENABLE_VRM10_BURST + [BurstCompile] +#endif + public struct UInt2UInt : IJobParallelFor + { + private readonly uint _vertexOffset; + + [ReadOnly] private readonly NativeSlice _source; + [WriteOnly] private NativeSlice _destination; + + public UInt2UInt(uint vertexOffset, NativeSlice source, NativeSlice destination) + { + _vertexOffset = vertexOffset; + _source = source; + _destination = destination; + } + + public void Execute(int index) + { + _destination[index] = _source[index] + _vertexOffset; + } + } + + /// + /// unsigned short -> unsigned int + /// +#if ENABLE_VRM10_BURST + [BurstCompile] +#endif + public struct Ushort2Uint : IJobParallelFor + { + private readonly uint _vertexOffset; + + [ReadOnly] private readonly NativeSlice _source; + [WriteOnly] private NativeSlice _destination; + + public Ushort2Uint(uint vertexOffset, NativeSlice source, NativeSlice destination) + { + _vertexOffset = vertexOffset; + _source = source; + _destination = destination; + } + + public void Execute(int index) + { + _destination[index] = _source[index] + _vertexOffset; + } + } + + /// + /// unsigned short -> unsigned short + /// +#if ENABLE_VRM10_BURST + [BurstCompile] +#endif + public struct Ushort2Ushort : IJobParallelFor + { + private readonly ushort _vertexOffset; + + [ReadOnly] private readonly NativeSlice _source; + [WriteOnly] private NativeSlice _destination; + + public Ushort2Ushort(ushort vertexOffset, NativeSlice source, NativeSlice destination) + { + _vertexOffset = vertexOffset; + _source = source; + _destination = destination; + } + + public void Execute(int index) + { + _destination[index] = (ushort)(_source[index] + _vertexOffset); + } + } + + /// + /// unsigned int -> unsigned short + /// +#if ENABLE_VRM10_BURST + [BurstCompile] +#endif + public struct Uint2Ushort : IJobParallelFor + { + private readonly ushort _vertexOffset; + + [ReadOnly] private readonly NativeSlice _source; + [WriteOnly] private NativeSlice _destination; + + public Uint2Ushort(ushort vertexOffset, NativeSlice source, NativeSlice destination) + { + _vertexOffset = vertexOffset; + _source = source; + _destination = destination; + } + + public void Execute(int index) + { + _destination[index] = (ushort)(_source[index] + _vertexOffset); + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/Model/CopyIndicesJob.cs.meta b/Assets/VRM10/Runtime/IO/Model/CopyIndicesJob.cs.meta new file mode 100644 index 000000000..454e4dc5a --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Model/CopyIndicesJob.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3c6a7113ee13415e9b8c1063a683f3f8 +timeCreated: 1637033014 \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/Model/InterleaveMeshVerticesJob.cs b/Assets/VRM10/Runtime/IO/Model/InterleaveMeshVerticesJob.cs new file mode 100644 index 000000000..f16040cca --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Model/InterleaveMeshVerticesJob.cs @@ -0,0 +1,79 @@ +using UniGLTF; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using UnityEngine; + +#if ENABLE_VRM10_BURST +using Unity.Burst; +#endif + +namespace UniVRM10 +{ + /// + /// 渡されたバッファを一つのバッファにインターリーブするJob + /// +#if ENABLE_VRM10_BURST + [BurstCompile] +#endif + internal struct InterleaveMeshVerticesJob : IJobParallelFor + { + [WriteOnly] + private NativeSlice _vertices; + + [ReadOnly] + private readonly NativeSlice _positions; + + // default値を許容する + [ReadOnly, NativeDisableContainerSafetyRestriction] + private readonly NativeSlice _normals; + + [ReadOnly, NativeDisableContainerSafetyRestriction] + private readonly NativeSlice _texCoords; + + [ReadOnly, NativeDisableContainerSafetyRestriction] + private readonly NativeSlice _colors; + + [ReadOnly, NativeDisableContainerSafetyRestriction] + private readonly NativeSlice _weights; + + [ReadOnly, NativeDisableContainerSafetyRestriction] + private readonly NativeSlice _joints; + + public InterleaveMeshVerticesJob( + NativeSlice vertices, + NativeSlice positions, + NativeSlice normals = default, + NativeSlice texCoords = default, + NativeSlice colors = default, + NativeSlice weights = default, + NativeSlice joints = default) + { + _vertices = vertices; + _positions = positions; + _normals = normals; + _texCoords = texCoords; + _colors = colors; + _weights = weights; + _joints = joints; + } + + public void Execute(int index) + { + _vertices[index] = new MeshVertex( + _positions[index], + _normals.Length > 0 ? _normals[index] : Vector3.zero, + _texCoords.Length > 0 ? _texCoords[index] : Vector2.zero, + _colors.Length > 0 ? _colors[index] : Color.white, + _joints.Length > 0 ? _joints[index].Joint0 : (ushort)0, + _joints.Length > 0 ? _joints[index].Joint1 : (ushort)0, + _joints.Length > 0 ? _joints[index].Joint2 : (ushort)0, + _joints.Length > 0 ? _joints[index].Joint3 : (ushort)0, + _weights.Length > 0 ? _weights[index].x : 0, + _weights.Length > 0 ? _weights[index].y : 0, + _weights.Length > 0 ? _weights[index].z : 0, + _weights.Length > 0 ? _weights[index].w : 0 + ); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/Model/InterleaveMeshVerticesJob.cs.meta b/Assets/VRM10/Runtime/IO/Model/InterleaveMeshVerticesJob.cs.meta new file mode 100644 index 000000000..6a54c7ebc --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Model/InterleaveMeshVerticesJob.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 01d2fcab0a3941fa9cf2e4f1a641105b +timeCreated: 1636688648 \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/Model/MeshImporter.cs b/Assets/VRM10/Runtime/IO/Model/MeshImporter.cs index 3e0c382e2..113d54653 100644 --- a/Assets/VRM10/Runtime/IO/Model/MeshImporter.cs +++ b/Assets/VRM10/Runtime/IO/Model/MeshImporter.cs @@ -1,6 +1,12 @@ using System; using UniGLTF; +using Unity.Collections; +using Unity.Jobs; using UnityEngine; +using UnityEngine.Profiling; +using UnityEngine.Rendering; +using VrmLib; +using Mesh = UnityEngine.Mesh; namespace UniVRM10 { @@ -12,72 +18,100 @@ namespace UniVRM10 /// /// /// - public static UnityEngine.Mesh LoadSharedMesh(VrmLib.Mesh src, VrmLib.Skin skin = null) + public static Mesh LoadSharedMesh(VrmLib.Mesh src, Skin skin = null) { - // submesh 方式 - var mesh = new UnityEngine.Mesh(); - if (src.IndexBuffer.Count > UInt16.MaxValue) - { - mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; - } + Profiler.BeginSample("MeshImporter.LoadSharedMesh"); + var mesh = new Mesh(); - mesh.vertices = src.VertexBuffer.Positions.GetSpan().ToArray(); - mesh.normals = src.VertexBuffer.Normals?.GetSpan().ToArray(); - mesh.uv = src.VertexBuffer.TexCoords?.GetSpan().ToArray(); - mesh.colors = src.VertexBuffer.Colors?.GetSpan().ToArray(); - if (src.VertexBuffer.Weights != null && src.VertexBuffer.Joints != null) + var positions = src.VertexBuffer.Positions.AsNativeArray(Allocator.TempJob); + var normals = src.VertexBuffer.Normals?.AsNativeArray(Allocator.TempJob) ?? default; + var texCoords = src.VertexBuffer.TexCoords?.AsNativeArray(Allocator.TempJob) ?? default; + var colors = src.VertexBuffer.Colors?.AsNativeArray(Allocator.TempJob) ?? default; + var weights = src.VertexBuffer.Weights?.AsNativeArray(Allocator.TempJob) ?? default; + var joints = src.VertexBuffer.Joints?.AsNativeArray(Allocator.TempJob) ?? default; + + var vertices = new NativeArray(positions.Length, Allocator.TempJob); + + // JobとBindPoseの更新を並行して行う + var jobHandle = + new InterleaveMeshVerticesJob(vertices, positions, normals, texCoords, colors, weights, joints) + .Schedule(vertices.Length, 1); + JobHandle.ScheduleBatchedJobs(); + + // BindPoseを更新 + if (weights.IsCreated && joints.IsCreated) { - var boneWeights = new BoneWeight[mesh.vertexCount]; - if (src.VertexBuffer.Weights.Count != mesh.vertexCount || src.VertexBuffer.Joints.Count != mesh.vertexCount) + if (weights.Length != positions.Length || joints.Length != positions.Length) { throw new ArgumentException(); } - var weights = src.VertexBuffer.Weights.GetSpan(); - var joints = src.VertexBuffer.Joints.GetSpan(); if (skin != null) { mesh.bindposes = skin.InverseMatrices.GetSpan().ToArray(); } - - for (int i = 0; i < weights.Length; ++i) - { - var w = weights[i]; - boneWeights[i].weight0 = w.x; - boneWeights[i].weight1 = w.y; - boneWeights[i].weight2 = w.z; - boneWeights[i].weight3 = w.w; - } - for (int i = 0; i < joints.Length; ++i) - { - var j = joints[i]; - boneWeights[i].boneIndex0 = j.Joint0; - boneWeights[i].boneIndex1 = j.Joint1; - boneWeights[i].boneIndex2 = j.Joint2; - boneWeights[i].boneIndex3 = j.Joint3; - } - mesh.boneWeights = boneWeights; } - mesh.subMeshCount = src.Submeshes.Count; - var triangles = src.IndexBuffer.GetAsIntList(); - for (int i = 0; i < src.Submeshes.Count; ++i) + // Jobを完了 + jobHandle.Complete(); + + // 入力のNativeArrayを開放 + positions.Dispose(); + if (normals.IsCreated) normals.Dispose(); + if (texCoords.IsCreated) texCoords.Dispose(); + if (colors.IsCreated) colors.Dispose(); + if (weights.IsCreated) weights.Dispose(); + if (joints.IsCreated) joints.Dispose(); + + // 頂点を更新 + MeshVertex.SetVertexBufferParamsToMesh(mesh, vertices.Length); + mesh.SetVertexBufferData(vertices, 0, 0, vertices.Length); + + // 出力のNativeArrayを開放 + vertices.Dispose(); + + // Indexを更新 + switch (src.IndexBuffer.ComponentType) { - var submesh = src.Submeshes[i]; - mesh.SetTriangles(triangles.GetRange(submesh.Offset, submesh.DrawCount), i); + case AccessorValueType.UNSIGNED_SHORT: + var shortIndices = src.IndexBuffer.AsNativeArray(Allocator.Temp); + mesh.SetIndexBufferParams(shortIndices.Length, IndexFormat.UInt16); + mesh.SetIndexBufferData(shortIndices, 0, 0, shortIndices.Length); + shortIndices.Dispose(); + break; + case AccessorValueType.UNSIGNED_INT: + var intIndices = src.IndexBuffer.AsNativeArray(Allocator.Temp); + mesh.SetIndexBufferParams(intIndices.Length, IndexFormat.UInt32); + mesh.SetIndexBufferData(intIndices, 0, 0, intIndices.Length); + intIndices.Dispose(); + break; + default: + throw new NotImplementedException(); } + // SubMeshを更新 + mesh.subMeshCount = src.Submeshes.Count; + for (var i = 0; i < src.Submeshes.Count; ++i) + { + var subMesh = src.Submeshes[i]; + mesh.SetSubMesh(i, new SubMeshDescriptor(subMesh.Offset, subMesh.DrawCount)); + } + + // MorphTargetを更新 foreach (var morphTarget in src.MorphTargets) { - var positions = + var morphTargetPositions = morphTarget.VertexBuffer.Positions != null ? morphTarget.VertexBuffer.Positions.GetSpan().ToArray() : new Vector3[mesh.vertexCount] // dummy ; - mesh.AddBlendShapeFrame(morphTarget.Name, 100.0f, positions, null, null); + mesh.AddBlendShapeFrame(morphTarget.Name, 100.0f, morphTargetPositions, null, null); } + // 各種パラメーターを再計算 mesh.RecalculateBounds(); mesh.RecalculateTangents(); + + Profiler.EndSample(); return mesh; } diff --git a/Assets/VRM10/Runtime/IO/Model/MeshImporterDivided.cs b/Assets/VRM10/Runtime/IO/Model/MeshImporterDivided.cs index b2b108139..53577d6fe 100644 --- a/Assets/VRM10/Runtime/IO/Model/MeshImporterDivided.cs +++ b/Assets/VRM10/Runtime/IO/Model/MeshImporterDivided.cs @@ -1,117 +1,289 @@ +using System; using System.Collections.Generic; using System.Linq; using UniGLTF; +using Unity.Collections; +using Unity.Jobs; using UnityEngine; +using UnityEngine.Profiling; +using UnityEngine.Rendering; +using VrmLib; +using Mesh = UnityEngine.Mesh; namespace UniVRM10 { public static class MeshImporterDivided { - public static UnityEngine.Mesh LoadDivided(VrmLib.MeshGroup src) + public static Mesh LoadDivided(MeshGroup meshGroup) { - var dst = new UnityEngine.Mesh(); - if (src.Meshes.Sum(x => x.IndexBuffer.Count) > ushort.MaxValue) + Profiler.BeginSample("MeshImporterDivided.LoadDivided"); + + var vertexCount = meshGroup.Meshes.Sum(mesh => mesh.VertexBuffer.Count); + var indexCount = meshGroup.Meshes.Sum(mesh => mesh.IndexBuffer.Count); + + var resultMesh = new Mesh(); + + // 頂点バッファ・BindPoseを構築して更新 + UpdateVerticesAndBindPose(meshGroup, vertexCount, resultMesh); + + // インデックスバッファを構築して更新 + UpdateIndices(meshGroup, vertexCount, indexCount, resultMesh); + + // SubMeshを更新 + resultMesh.subMeshCount = meshGroup.Meshes.Count; + var indexOffset = 0; + for (var i = 0; i < meshGroup.Meshes.Count; ++i) { - dst.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; + var mesh = meshGroup.Meshes[i]; + resultMesh.SetSubMesh(i, new SubMeshDescriptor(indexOffset, mesh.IndexBuffer.Count)); + indexOffset += mesh.IndexBuffer.Count; } - // - // vertices - // - var vertexCount = src.Meshes.Sum(x => x.VertexBuffer.Count); - var positions = new List(vertexCount); - var normals = new List(vertexCount); - var uv = new List(vertexCount); - var boneWeights = new List(vertexCount); - for (int meshIndex = 0; meshIndex < src.Meshes.Count; ++meshIndex) + // 各種データを再構築 + resultMesh.RecalculateBounds(); + resultMesh.RecalculateTangents(); + + // BlendShapeを更新 + var blendShapeCount = meshGroup.Meshes[0].MorphTargets.Count; + + for (var i = 0; i < blendShapeCount; ++i) { - var mesh = src.Meshes[meshIndex]; - positions.AddRange(mesh.VertexBuffer.Positions.GetSpan()); - normals.AddRange(mesh.VertexBuffer.Normals.GetSpan()); - uv.AddRange(mesh.VertexBuffer.TexCoords.GetSpan()); - if (src.Skin != null) + var positionsCount = 0; + var normalsCount = 0; + foreach (var mesh in meshGroup.Meshes) { - var j = mesh.VertexBuffer.Joints.GetSpan(); - var w = mesh.VertexBuffer.Weights.GetSpan(); - for (int i = 0; i < mesh.VertexBuffer.Count; ++i) + var morphTarget = mesh.MorphTargets[i]; + positionsCount += morphTarget.VertexBuffer.Positions.Count; + normalsCount += morphTarget.VertexBuffer.Normals?.Count ?? morphTarget.VertexBuffer.Count; + } + + var blendShapePositions = new NativeArray(positionsCount, Allocator.Temp); + var blendShapeNormals = new NativeArray(normalsCount, Allocator.Temp); + + var blendShapePositionOffset = 0; + var blendShapeNormalOffset = 0; + foreach (var mesh in meshGroup.Meshes) + { + var morphTarget = mesh.MorphTargets[i]; + morphTarget.VertexBuffer.Positions.CopyToNativeSlice( + new NativeSlice( + blendShapePositions, + blendShapePositionOffset, + morphTarget.VertexBuffer.Positions.Count + ) + ); + + // nullならdefault(0)のまま + morphTarget.VertexBuffer.Normals?.CopyToNativeSlice( + new NativeSlice( + blendShapeNormals, + blendShapeNormalOffset, + morphTarget.VertexBuffer.Normals.Count + ) + ); + + blendShapePositionOffset += morphTarget.VertexBuffer.Positions.Count; + blendShapeNormalOffset += morphTarget.VertexBuffer.Normals?.Count ?? morphTarget.VertexBuffer.Count; + } + + resultMesh.AddBlendShapeFrame(meshGroup.Meshes[0].MorphTargets[i].Name, + 100.0f, + blendShapePositions.ToArray(), + blendShapeNormals.ToArray(), + null); + } + + Profiler.EndSample(); + + return resultMesh; + } + + /// + /// インデックスバッファを更新する + /// MEMO: 出力に対するushortを考慮することをやめればかなりシンプルに書ける + /// + private static void UpdateIndices(MeshGroup meshGroup, int vertexCount, int indexCount, Mesh resultMesh) + { + Profiler.BeginSample("MeshImporterDivided.UpdateIndices"); + + JobHandle jobHandle = default; + + var disposables = new List(); + + // 出力をushortにするべきかどうかを判別 + if (vertexCount < ushort.MaxValue) + { + var indices = new NativeArray(indexCount, Allocator.TempJob); + disposables.Add(indices); + var indexOffset = 0; + var vertexOffset = 0; + foreach (var mesh in meshGroup.Meshes) + { + switch (mesh.IndexBuffer.ComponentType) { - var jj = j[i]; - var ww = w[i]; - boneWeights.Add(new BoneWeight + case AccessorValueType.SHORT: { - boneIndex0 = jj.Joint0, - boneIndex1 = jj.Joint1, - boneIndex2 = jj.Joint2, - boneIndex3 = jj.Joint3, - weight0 = ww.x, - weight1 = ww.y, - weight2 = ww.z, - weight3 = ww.w, - }); + // unsigned short -> unsigned short + var source = mesh.IndexBuffer.AsNativeArray(Allocator.TempJob); + disposables.Add(source); + jobHandle = new CopyIndicesJobs.Ushort2Ushort( + (ushort)vertexOffset, + new NativeSlice(source), + new NativeSlice(indices, indexOffset, mesh.IndexBuffer.Count)) + .Schedule(mesh.IndexBuffer.Count, 1, jobHandle); + break; + } + case AccessorValueType.UNSIGNED_INT: + { + // unsigned int -> unsigned short + var source = mesh.IndexBuffer.AsNativeArray(Allocator.TempJob); + disposables.Add(source); + jobHandle = new CopyIndicesJobs.Uint2Ushort( + (ushort)vertexOffset, + source, + new NativeSlice(indices, indexOffset, mesh.IndexBuffer.Count)) + .Schedule(mesh.IndexBuffer.Count, 1, jobHandle); + break; + } + default: + throw new ArgumentOutOfRangeException(); } + + vertexOffset += mesh.VertexBuffer.Count; + indexOffset += mesh.IndexBuffer.Count; } + + jobHandle.Complete(); + + resultMesh.SetIndexBufferParams(indexCount, IndexFormat.UInt16); + resultMesh.SetIndexBufferData(indices, 0, 0, indexCount); } - - dst.name = src.Name; - dst.vertices = positions.ToArray(); - dst.normals = normals.ToArray(); - dst.uv = uv.ToArray(); - if (src.Skin != null) + else { - dst.boneWeights = boneWeights.ToArray(); - } - - // - // skin - // - if (src.Skin != null) - { - dst.bindposes = src.Skin.InverseMatrices.GetSpan().ToArray(); - } - - // - // triangles - // - dst.subMeshCount = src.Meshes.Count; - var offset = 0; - for (int meshIndex = 0; meshIndex < src.Meshes.Count; ++meshIndex) - { - var mesh = src.Meshes[meshIndex]; - var indices = mesh.IndexBuffer.GetAsIntArray().Select(x => offset + x).ToArray(); - dst.SetTriangles(indices, meshIndex); - offset += mesh.VertexBuffer.Count; - } - - dst.RecalculateBounds(); - dst.RecalculateTangents(); - - // - // blendshape - // - var blendShapeCount = src.Meshes[0].MorphTargets.Count; - for (int i = 0; i < blendShapeCount; ++i) - { - positions.Clear(); - normals.Clear(); - var name = src.Meshes[0].MorphTargets[i].Name; - for (int meshIndex = 0; meshIndex < src.Meshes.Count; ++meshIndex) + var indices = new NativeArray(indexCount, Allocator.TempJob); + disposables.Add(indices); + var indexOffset = 0; + var vertexOffset = 0; + foreach (var mesh in meshGroup.Meshes) { - var morphTarget = src.Meshes[meshIndex].MorphTargets[i]; - positions.AddRange(morphTarget.VertexBuffer.Positions.GetSpan()); - if (morphTarget.VertexBuffer.Normals != null) + switch (mesh.IndexBuffer.ComponentType) { - normals.AddRange(morphTarget.VertexBuffer.Normals.GetSpan()); - } - else - { - // fill zero - normals.AddRange(Enumerable.Range(0, morphTarget.VertexBuffer.Count).Select(x => Vector3.zero)); + case AccessorValueType.SHORT: + { + // unsigned short -> unsigned int + var source = mesh.IndexBuffer.AsNativeArray(Allocator.TempJob); + disposables.Add(source); + jobHandle = new CopyIndicesJobs.Ushort2Uint( + (uint)vertexOffset, + source, + new NativeSlice(indices, indexOffset, mesh.IndexBuffer.Count)) + .Schedule(mesh.IndexBuffer.Count, 1, jobHandle); + break; + } + case AccessorValueType.UNSIGNED_INT: + { + // unsigned int -> unsigned int + var source = mesh.IndexBuffer.AsNativeArray(Allocator.TempJob); + disposables.Add(source); + jobHandle = new CopyIndicesJobs.UInt2UInt( + (uint)vertexOffset, + source, + new NativeSlice(indices, indexOffset, mesh.IndexBuffer.Count)) + .Schedule(mesh.IndexBuffer.Count, 1, jobHandle); + break; + } + default: + throw new ArgumentOutOfRangeException(); } + + vertexOffset += mesh.VertexBuffer.Count; + indexOffset += mesh.IndexBuffer.Count; } - dst.AddBlendShapeFrame(name, 100.0f, positions.ToArray(), normals.ToArray(), null); + + jobHandle.Complete(); + + resultMesh.SetIndexBufferParams(indexCount, IndexFormat.UInt32); + resultMesh.SetIndexBufferData(indices, 0, 0, indexCount); } - return dst; + foreach (var disposable in disposables) + { + disposable.Dispose(); + } + + Profiler.EndSample(); + } + + /// + /// メッシュの頂点情報の更新を行う際、MainThreadが空くため、その間にBindPoseの更新も行う + /// + private static void UpdateVerticesAndBindPose( + MeshGroup meshGroup, + int vertexCount, + Mesh resultMesh) + { + Profiler.BeginSample("MeshImporterDivided.UpdateVerticesAndBindPose"); + + var disposables = new List(); + + // JobのSchedule + var vertices = new NativeArray(vertexCount, Allocator.TempJob); + disposables.Add(vertices); + + var indexOffset = 0; + JobHandle interleaveVertexJob = default; + + foreach (var mesh in meshGroup.Meshes) + { + var positions = mesh.VertexBuffer.Positions.AsNativeArray(Allocator.TempJob); + var normals = mesh.VertexBuffer.Normals.AsNativeArray(Allocator.TempJob); + var texCoords = mesh.VertexBuffer.TexCoords.AsNativeArray(Allocator.TempJob); + var weights = meshGroup.Skin != null + ? mesh.VertexBuffer.Weights.AsNativeArray(Allocator.TempJob) + : default; + var joints = meshGroup.Skin != null + ? mesh.VertexBuffer.Joints.AsNativeArray(Allocator.TempJob) + : default; + if (positions.IsCreated) disposables.Add(positions); + if (normals.IsCreated) disposables.Add(normals); + if (texCoords.IsCreated) disposables.Add(texCoords); + if (weights.IsCreated) disposables.Add(weights); + if (joints.IsCreated) disposables.Add(joints); + + interleaveVertexJob = new InterleaveMeshVerticesJob( + new NativeSlice(vertices, indexOffset, mesh.VertexBuffer.Count), + positions, + normals, + texCoords, + default, + weights, + joints) + .Schedule(mesh.VertexBuffer.Count, 1, interleaveVertexJob); + indexOffset += mesh.VertexBuffer.Count; + } + + JobHandle.ScheduleBatchedJobs(); + + // 並行してBindposeの更新を行う + if (meshGroup.Skin != null) + { + resultMesh.bindposes = meshGroup.Skin.InverseMatrices.GetSpan().ToArray(); + } + + // Jobを完了 + interleaveVertexJob.Complete(); + + // VertexBufferを設定 + MeshVertex.SetVertexBufferParamsToMesh(resultMesh, vertices.Length); + resultMesh.SetVertexBufferData(vertices, 0, 0, vertices.Length); + + // 各種バッファを破棄 + foreach (var disposable in disposables) + { + disposable.Dispose(); + } + + Profiler.EndSample(); } } -} +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/Model/MeshVertex.cs b/Assets/VRM10/Runtime/IO/Model/MeshVertex.cs new file mode 100644 index 000000000..d5926b1b3 --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Model/MeshVertex.cs @@ -0,0 +1,71 @@ +using System; +using System.Runtime.InteropServices; +using UnityEditor.UI; +using UnityEngine; +using UnityEngine.Rendering; + +namespace UniVRM10 +{ + /// + /// インターリーブされたメッシュの頂点情報を表す構造体 + /// そのままGPUにアップロードされる + /// + [Serializable, StructLayout(LayoutKind.Sequential)] + internal readonly struct MeshVertex + { + private readonly Vector3 _position; + private readonly Vector3 _normal; + private readonly Color _color; + private readonly Vector2 _texCoord; + private readonly float _boneWeight0; + private readonly float _boneWeight1; + private readonly float _boneWeight2; + private readonly float _boneWeight3; + private readonly ushort _boneIndex0; + private readonly ushort _boneIndex1; + private readonly ushort _boneIndex2; + private readonly ushort _boneIndex3; + + public MeshVertex( + Vector3 position, + Vector3 normal, + Vector2 texCoord, + Color color, + ushort boneIndex0, + ushort boneIndex1, + ushort boneIndex2, + ushort boneIndex3, + float boneWeight0, + float boneWeight1, + float boneWeight2, + float boneWeight3) + { + _position = position; + _normal = normal; + _texCoord = texCoord; + _color = color; + _boneIndex0 = boneIndex0; + _boneIndex1 = boneIndex1; + _boneIndex2 = boneIndex2; + _boneIndex3 = boneIndex3; + _boneWeight0 = boneWeight0; + _boneWeight1 = boneWeight1; + _boneWeight2 = boneWeight2; + _boneWeight3 = boneWeight3; + } + + private static readonly VertexAttributeDescriptor[] vertexAttributeDescriptor = { + new VertexAttributeDescriptor(VertexAttribute.Position), + new VertexAttributeDescriptor(VertexAttribute.Normal), + new VertexAttributeDescriptor(VertexAttribute.Color, dimension: 4), + new VertexAttributeDescriptor(VertexAttribute.TexCoord0, dimension: 2), + new VertexAttributeDescriptor(VertexAttribute.BlendWeight, dimension: 4), + new VertexAttributeDescriptor(VertexAttribute.BlendIndices, VertexAttributeFormat.UInt16, 4), + }; + + public static void SetVertexBufferParamsToMesh(Mesh mesh, int length) + { + mesh.SetVertexBufferParams(length, vertexAttributeDescriptor); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/IO/Model/MeshVertex.cs.meta b/Assets/VRM10/Runtime/IO/Model/MeshVertex.cs.meta new file mode 100644 index 000000000..0e80ae0bd --- /dev/null +++ b/Assets/VRM10/Runtime/IO/Model/MeshVertex.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 78c70746c10e46bc97de0a7afedb23c7 +timeCreated: 1636625951 \ No newline at end of file diff --git a/Assets/VRM10/Runtime/VRM10.asmdef b/Assets/VRM10/Runtime/VRM10.asmdef index b65ef4c5a..e8a38f04c 100644 --- a/Assets/VRM10/Runtime/VRM10.asmdef +++ b/Assets/VRM10/Runtime/VRM10.asmdef @@ -8,7 +8,8 @@ "GUID:bce005214fa49654d93927908c15b1f2", "GUID:0aaf403bd13871a44b7127aef2695ff8", "GUID:b7aa47b240b57de44a4b2021c143c9bf", - "GUID:f2ca1407928ebdc4bbe7765cc278be44" + "GUID:f2ca1407928ebdc4bbe7765cc278be44", + "GUID:2665a8d13d1b3f18800f46e256720795" ], "includePlatforms": [], "excludePlatforms": [], @@ -17,6 +18,12 @@ "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], - "versionDefines": [], + "versionDefines": [ + { + "name": "com.unity.burst", + "expression": "0.0.1", + "define": "ENABLE_VRM10_BURST" + } + ], "noEngineReferences": false } \ No newline at end of file diff --git a/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs b/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs index 0d6336e7e..b37fd8ac3 100644 --- a/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs +++ b/Assets/VRM10/vrmlib/Runtime/BufferAccessor.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Numerics; using System.Runtime.InteropServices; using UniGLTF; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; namespace VrmLib { @@ -103,6 +105,31 @@ namespace VrmLib return SpanLike.Wrap(Bytes); } + /// + /// バッファをNativeArrayに変換して返す + /// 開放の責務は使い手側にある点に注意 + /// + public unsafe NativeArray AsNativeArray(Allocator allocator) where T : struct + { + fixed (byte* byteArray = Bytes.Array) + { + var nativeArray = new NativeArray(Bytes.Count / Marshal.SizeOf(), allocator); + UnsafeUtility.MemCpy(nativeArray.GetUnsafePtr(), byteArray + Bytes.Offset, Bytes.Count); + return nativeArray; + } + } + + /// + /// バッファをNativeSliceへと書き込む + /// + public unsafe void CopyToNativeSlice(NativeSlice destArray) where T : unmanaged + { + fixed (byte* byteArray = Bytes.Array) + { + UnsafeUtility.MemCpy((T*)destArray.GetUnsafePtr(), byteArray + Bytes.Offset, Bytes.Count); + } + } + public void Assign(T[] values) where T : struct { if (Marshal.SizeOf(typeof(T)) != Stride) diff --git a/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef b/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef index d3ffc1a7f..0e41a1f25 100644 --- a/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef +++ b/Assets/VRM10/vrmlib/Runtime/VrmLib.asmdef @@ -5,7 +5,7 @@ ], "includePlatforms": [], "excludePlatforms": [], - "allowUnsafeCode": false, + "allowUnsafeCode": true, "overrideReferences": false, "precompiledReferences": [], "autoReferenced": false,