MeshImporterDividedを高速化

This commit is contained in:
notargs
2021-11-16 18:38:37 +09:00
parent ed4aada948
commit 7b32f07c78
4 changed files with 255 additions and 106 deletions

View File

@@ -18,39 +18,36 @@ namespace UniVRM10
#endif
internal struct InterleaveMeshVerticesJob : IJobParallelFor
{
[WriteOnly, NativeDisableParallelForRestriction]
private NativeArray<MeshVertex> _vertices;
[WriteOnly]
private NativeSlice<MeshVertex> _vertices;
[ReadOnly]
private readonly NativeArray<Vector3> _positions;
private readonly NativeSlice<Vector3> _positions;
// default値を許容する
[ReadOnly, NativeDisableContainerSafetyRestriction]
private readonly NativeArray<Vector3> _normals;
private readonly NativeSlice<Vector3> _normals;
[ReadOnly, NativeDisableContainerSafetyRestriction]
private readonly NativeArray<Vector2> _texCoords;
private readonly NativeSlice<Vector2> _texCoords;
[ReadOnly, NativeDisableContainerSafetyRestriction]
private readonly NativeArray<Color> _colors;
private readonly NativeSlice<Color> _colors;
[ReadOnly, NativeDisableContainerSafetyRestriction]
private readonly NativeArray<Vector4> _weights;
private readonly NativeSlice<Vector4> _weights;
[ReadOnly, NativeDisableContainerSafetyRestriction]
private readonly NativeArray<SkinJoints> _joints;
private readonly int _verticesOffset;
private readonly NativeSlice<SkinJoints> _joints;
public InterleaveMeshVerticesJob(
NativeArray<MeshVertex> vertices,
NativeSlice<MeshVertex> vertices,
NativeArray<Vector3> positions,
NativeArray<Vector3> normals = default,
NativeArray<Vector2> texCoords = default,
NativeArray<Color> colors = default,
NativeArray<Vector4> weights = default,
NativeArray<SkinJoints> joints = default,
int verticesOffset = 0)
NativeArray<SkinJoints> joints = default)
{
_vertices = vertices;
_positions = positions;
@@ -59,24 +56,23 @@ namespace UniVRM10
_colors = colors;
_weights = weights;
_joints = joints;
_verticesOffset = verticesOffset;
}
public void Execute(int index)
{
_vertices[index + _verticesOffset] = new MeshVertex(
_vertices[index] = new MeshVertex(
_positions[index],
_normals.IsCreated ? _normals[index] : Vector3.zero,
_texCoords.IsCreated ? _texCoords[index] : Vector2.zero,
_colors.IsCreated ? _colors[index] : Color.white,
_joints.IsCreated ? _joints[index].Joint0 : (ushort)0,
_joints.IsCreated ? _joints[index].Joint1 : (ushort)0,
_joints.IsCreated ? _joints[index].Joint2 : (ushort)0,
_joints.IsCreated ? _joints[index].Joint3 : (ushort)0,
_weights.IsCreated ? _weights[index].x : 0,
_weights.IsCreated ? _weights[index].y : 0,
_weights.IsCreated ? _weights[index].z : 0,
_weights.IsCreated ? _weights[index].w : 0
_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
);
}
}

View File

@@ -23,7 +23,6 @@ namespace UniVRM10
Profiler.BeginSample("MeshImporter.LoadSharedMesh");
var mesh = new Mesh();
// これらのNativeArrayはJobによって開放される
var positions = src.VertexBuffer.Positions.AsNativeArray<Vector3>(Allocator.TempJob);
var normals = src.VertexBuffer.Normals?.AsNativeArray<Vector3>(Allocator.TempJob) ?? default;
var texCoords = src.VertexBuffer.TexCoords?.AsNativeArray<Vector2>(Allocator.TempJob) ?? default;
@@ -37,6 +36,7 @@ namespace UniVRM10
var jobHandle =
new InterleaveMeshVerticesJob(vertices, positions, normals, texCoords, colors, weights, joints)
.Schedule(vertices.Length, 1);
JobHandle.ScheduleBatchedJobs();
// BindPoseを更新
if (weights.IsCreated && joints.IsCreated)
@@ -73,13 +73,13 @@ namespace UniVRM10
switch (src.IndexBuffer.ComponentType)
{
case AccessorValueType.UNSIGNED_SHORT:
var shortIndices = src.IndexBuffer.AsNativeArray<short>(Allocator.Temp);
var shortIndices = src.IndexBuffer.AsNativeArray<ushort>(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<int>(Allocator.Temp);
var intIndices = src.IndexBuffer.AsNativeArray<uint>(Allocator.Temp);
mesh.SetIndexBufferParams(intIndices.Length, IndexFormat.UInt32);
mesh.SetIndexBufferData(intIndices, 0, 0, intIndices.Length);
intIndices.Dispose();

View File

@@ -6,92 +6,58 @@ 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 Mesh LoadDivided(VrmLib.MeshGroup src)
public static Mesh LoadDivided(MeshGroup meshGroup)
{
Profiler.BeginSample("MeshImporterDivided.LoadDivided");
var dst = new UnityEngine.Mesh();
if (src.Meshes.Sum(x => x.IndexBuffer.Count) > ushort.MaxValue)
{
dst.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
}
// 頂点バッファを構築
var vertexCount = src.Meshes.Sum(x => x.VertexBuffer.Count);
var vertices = new NativeArray<MeshVertex>(vertexCount, Allocator.TempJob);
var vertexCount = meshGroup.Meshes.Sum(mesh => mesh.VertexBuffer.Count);
var indexCount = meshGroup.Meshes.Sum(mesh => mesh.IndexBuffer.Count);
var jobDisposables = new List<IDisposable>();
// JobのSchedule
JobHandle jobHandle = default;
var resultMesh = new Mesh();
// 頂点バッファ・BindPoseを構築して更新
UpdateVerticesAndBindPose(meshGroup, vertexCount, resultMesh);
// インデックスバッファを構築して更新
UpdateIndices(meshGroup, vertexCount, indexCount, resultMesh);
// SubMeshを更新
resultMesh.subMeshCount = meshGroup.Meshes.Count;
var indexOffset = 0;
foreach (var mesh in src.Meshes)
for (var i = 0; i < meshGroup.Meshes.Count; ++i)
{
// これらのNativeArrayはJobによって開放される
var positions = mesh.VertexBuffer.Positions.AsNativeArray<Vector3>(Allocator.TempJob);
var normals = mesh.VertexBuffer.Normals.AsNativeArray<Vector3>(Allocator.TempJob);
var texCoords = mesh.VertexBuffer.TexCoords.AsNativeArray<Vector2>(Allocator.TempJob);
var weights = src.Skin != null ? mesh.VertexBuffer.Weights.AsNativeArray<Vector4>(Allocator.TempJob) : default;
var joints = src.Skin != null ? mesh.VertexBuffer.Joints.AsNativeArray<SkinJoints>(Allocator.TempJob) : default;
if (positions.IsCreated) jobDisposables.Add(positions);
if (normals.IsCreated) jobDisposables.Add(normals);
if (texCoords.IsCreated) jobDisposables.Add(texCoords);
if (weights.IsCreated) jobDisposables.Add(weights);
if (joints.IsCreated) jobDisposables.Add(joints);
jobHandle = new InterleaveMeshVerticesJob(vertices, positions, normals, texCoords, default, weights, joints, indexOffset)
.Schedule(mesh.VertexBuffer.Count, 1, jobHandle);
indexOffset += mesh.VertexBuffer.Count;
var mesh = meshGroup.Meshes[i];
resultMesh.SetSubMesh(i, new SubMeshDescriptor(indexOffset, mesh.IndexBuffer.Count));
indexOffset += mesh.IndexBuffer.Count;
}
// Jobと並行してBindposeの更新を行う
if (src.Skin != null)
{
dst.bindposes = src.Skin.InverseMatrices.GetSpan<Matrix4x4>().ToArray();
}
// Jobを完了
jobHandle.Complete();
foreach (var disposable in jobDisposables)
{
disposable.Dispose();
}
// 各種データを再構築
Profiler.BeginSample("Bounds");
resultMesh.RecalculateBounds();
Profiler.EndSample();
Profiler.BeginSample("Tangents");
resultMesh.RecalculateTangents();
Profiler.EndSample();
MeshVertex.SetVertexBufferParamsToMesh(dst, vertices.Length);
dst.SetVertexBufferData(vertices, 0, 0, vertices.Length);
vertices.Dispose();
// triangles
dst.subMeshCount = src.Meshes.Count;
var offset = 0;
for (var 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;
// BlendShapeを更新
Profiler.BeginSample("BlendShape");
var blendShapeCount = meshGroup.Meshes[0].MorphTargets.Count;
var blendShapePositions = new List<Vector3>();
var blendShapeNormals = new List<Vector3>();
for (var i = 0; i < blendShapeCount; ++i)
{
blendShapePositions.Clear();
blendShapeNormals.Clear();
var name = src.Meshes[0].MorphTargets[i].Name;
foreach (var mesh in src.Meshes)
var name = meshGroup.Meshes[0].MorphTargets[i].Name;
foreach (var mesh in meshGroup.Meshes)
{
var morphTarget = mesh.MorphTargets[i];
blendShapePositions.AddRange(morphTarget.VertexBuffer.Positions.GetSpan<Vector3>());
@@ -102,15 +68,207 @@ namespace UniVRM10
else
{
// fill zero
blendShapeNormals.AddRange(Enumerable.Range(0, morphTarget.VertexBuffer.Count).Select(x => Vector3.zero));
blendShapeNormals.AddRange(Enumerable.Range(0, morphTarget.VertexBuffer.Count)
.Select(x => Vector3.zero));
}
}
dst.AddBlendShapeFrame(name, 100.0f, blendShapePositions.ToArray(), blendShapeNormals.ToArray(), null);
resultMesh.AddBlendShapeFrame(name, 100.0f, blendShapePositions.ToArray(), blendShapeNormals.ToArray(),
null);
}
Profiler.EndSample();
Profiler.EndSample();
return resultMesh;
}
/// <summary>
/// インデックスバッファを更新する
/// MEMO: 出力に対するushortを考慮することをやめればかなりシンプルに書ける
/// </summary>
private static void UpdateIndices(MeshGroup meshGroup, int vertexCount, int indexCount, Mesh resultMesh)
{
Profiler.BeginSample("MeshImporterDivided.UpdateIndices");
JobHandle jobHandle = default;
var disposables = new List<IDisposable>();
// 出力をushortにするべきかどうかを判別
if (vertexCount < ushort.MaxValue)
{
var indices = new NativeArray<ushort>(indexCount, Allocator.TempJob);
disposables.Add(indices);
var indexOffset = 0;
var vertexOffset = 0;
foreach (var mesh in meshGroup.Meshes)
{
switch (mesh.IndexBuffer.ComponentType)
{
case AccessorValueType.SHORT:
{
// unsigned short -> unsigned short
var source = mesh.IndexBuffer.AsNativeArray<ushort>(Allocator.TempJob);
disposables.Add(source);
jobHandle = new CopyIndicesJobs.Ushort2Ushort(
(ushort)vertexOffset,
new NativeSlice<ushort>(source),
new NativeSlice<ushort>(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<uint>(Allocator.TempJob);
disposables.Add(source);
jobHandle = new CopyIndicesJobs.Uint2Ushort(
(ushort)vertexOffset,
source,
new NativeSlice<ushort>(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);
}
else
{
var indices = new NativeArray<uint>(indexCount, Allocator.TempJob);
disposables.Add(indices);
var indexOffset = 0;
var vertexOffset = 0;
foreach (var mesh in meshGroup.Meshes)
{
switch (mesh.IndexBuffer.ComponentType)
{
case AccessorValueType.SHORT:
{
// unsigned short -> unsigned int
var source = mesh.IndexBuffer.AsNativeArray<ushort>(Allocator.TempJob);
disposables.Add(source);
jobHandle = new CopyIndicesJobs.Ushort2Uint(
(uint)vertexOffset,
source,
new NativeSlice<uint>(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<uint>(Allocator.TempJob);
disposables.Add(source);
jobHandle = new CopyIndicesJobs.UInt2UInt(
(uint)vertexOffset,
source,
new NativeSlice<uint>(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.UInt32);
resultMesh.SetIndexBufferData(indices, 0, 0, indexCount);
}
foreach (var disposable in disposables)
{
disposable.Dispose();
}
Profiler.EndSample();
}
/// <summary>
/// メッシュの頂点情報の更新を行う際、MainThreadが空くため、その間にBindPoseの更新も行う
/// </summary>
private static void UpdateVerticesAndBindPose(
MeshGroup meshGroup,
int vertexCount,
Mesh resultMesh)
{
Profiler.BeginSample("MeshImporterDivided.UpdateVerticesAndBindPose");
var disposables = new List<IDisposable>();
// JobのSchedule
var vertices = new NativeArray<MeshVertex>(vertexCount, Allocator.TempJob);
disposables.Add(vertices);
var indexOffset = 0;
JobHandle interleaveVertexJob = default;
foreach (var mesh in meshGroup.Meshes)
{
var positions = mesh.VertexBuffer.Positions.AsNativeArray<Vector3>(Allocator.TempJob);
var normals = mesh.VertexBuffer.Normals.AsNativeArray<Vector3>(Allocator.TempJob);
var texCoords = mesh.VertexBuffer.TexCoords.AsNativeArray<Vector2>(Allocator.TempJob);
var weights = meshGroup.Skin != null
? mesh.VertexBuffer.Weights.AsNativeArray<Vector4>(Allocator.TempJob)
: default;
var joints = meshGroup.Skin != null
? mesh.VertexBuffer.Joints.AsNativeArray<SkinJoints>(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<MeshVertex>(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<Matrix4x4>().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();
return dst;
}
}
}
}

View File

@@ -109,13 +109,8 @@ namespace VrmLib
/// バッファをNativeArrayに変換して返す
/// 開放の責務は使い手側にある点に注意
/// </summary>
public unsafe NativeArray<T> AsNativeArray<T>(Allocator allocator, bool checkStride = true) where T : struct
public unsafe NativeArray<T> AsNativeArray<T>(Allocator allocator) where T : struct
{
if (checkStride && Marshal.SizeOf(typeof(T)) != Stride)
{
throw new Exception("different sizeof(T) with stride");
}
fixed (byte* byteArray = Bytes.Array)
{
var nativeArray = new NativeArray<T>(Bytes.Count / Marshal.SizeOf<T>(), allocator);