diff --git a/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneInstanceBuffers.cs b/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneInstanceBuffers.cs new file mode 100644 index 000000000..02ad168de --- /dev/null +++ b/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneInstanceBuffers.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.Collections; +using UnityEngine; +using UnityEngine.Profiling; +using UniVRM10.FastSpringBones.Blittables; + +namespace UniVRM10.FastSpringBones.System +{ + public class FastSpringBoneBuffer : IDisposable + { + public NativeArray Springs { get; } + public NativeArray Joints { get; } + public NativeArray Colliders { get; } + public NativeArray Logics { get; } + public NativeArray BlittableTransforms { get; } + public Transform[] Transforms { get; } + + public FastSpringBoneBuffer(IReadOnlyList springs) + { + Profiler.BeginSample("FastSpringBone.ConstructBuffers"); + + // Transformの列挙 + Profiler.BeginSample("FastSpringBone.ConstructBuffers.ConstructTransformBuffer"); + var transformHashSet = new HashSet(); + foreach (var spring in springs) + { + foreach (var joint in spring.joints) + { + transformHashSet.Add(joint.Transform); + if (joint.Transform.parent != null) transformHashSet.Add(joint.Transform.parent); + } + foreach (var collider in spring.colliders) + { + transformHashSet.Add(collider.Transform); + } + + if (spring.center != null) transformHashSet.Add(spring.center); + } + var transforms = transformHashSet.ToArray(); + var transformIndexDictionary = transforms.Select((trs, index) => (trs, index)) + .ToDictionary(tuple => tuple.trs, tuple => tuple.index); + Profiler.EndSample(); + + // 各種bufferの構築 + Profiler.BeginSample("FastSpringBone.ConstructBuffers.ConstructBuffers"); + var blittableColliders = new List(); + var blittableJoints = new List(); + var blittableSprings = new List(); + var blittableLogics = new List(); + + foreach (var spring in springs) + { + var blittableSpring = new BlittableSpring + { + colliderSpan = new BlittableSpan + { + startIndex = blittableColliders.Count, + count = spring.colliders.Length, + }, + logicSpan = new BlittableSpan + { + startIndex = blittableJoints.Count, + count = spring.joints.Length - 1, + }, + centerTransformIndex = spring.center ? transformIndexDictionary[spring.center] : -1 + }; + blittableSprings.Add(blittableSpring); + + blittableColliders.AddRange(spring.colliders.Select(collider => + { + var blittable = collider.Collider; + blittable.transformIndex = transformIndexDictionary[collider.Transform]; + return blittable; + })); + blittableJoints.AddRange(spring.joints.Take(spring.joints.Length - 1).Select(joint => + { + var blittable = joint.Joint; + return blittable; + })); + + for (var i = 0; i < spring.joints.Length - 1; ++i) + { + var joint = spring.joints[i]; + var tailJoint = spring.joints[i + 1]; + var localPosition = tailJoint.Transform.localPosition; + var scale = tailJoint.Transform.lossyScale; + var localChildPosition = + new Vector3( + localPosition.x * scale.x, + localPosition.y * scale.y, + localPosition.z * scale.z + ); + + var worldChildPosition = joint.Transform.TransformPoint(localChildPosition); + var currentTail = spring.center != null + ? spring.center.InverseTransformPoint(worldChildPosition) + : worldChildPosition; + var parent = joint.Transform.parent; + blittableLogics.Add(new BlittableLogic + { + headTransformIndex = transformIndexDictionary[joint.Transform], + parentTransformIndex = parent != null ? transformIndexDictionary[parent] : -1, + currentTail = currentTail, + prevTail = currentTail, + localRotation = joint.Transform.localRotation, + boneAxis = localChildPosition.normalized, + length = localChildPosition.magnitude + }); + } + } + Profiler.EndSample(); + + // 各種bufferの初期化 + Profiler.BeginSample("FastSpringBone.ConstructBuffers.ConstructNativeArrays"); + Springs = new NativeArray(blittableSprings.ToArray(), Allocator.Persistent); + + Joints = new NativeArray(blittableJoints.ToArray(), Allocator.Persistent); + Colliders = new NativeArray(blittableColliders.ToArray(), Allocator.Persistent); + Logics = new NativeArray(blittableLogics.ToArray(), Allocator.Persistent); + + BlittableTransforms = new NativeArray(transforms.Length, Allocator.Persistent); + Transforms = transforms.ToArray(); + Profiler.EndSample(); + + Profiler.EndSample(); + } + + public void Dispose() + { + Springs.Dispose(); + Joints.Dispose(); + BlittableTransforms.Dispose(); + Colliders.Dispose(); + Logics.Dispose(); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneInstanceBuffers.cs.meta b/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneInstanceBuffers.cs.meta new file mode 100644 index 000000000..6dcc7fa2e --- /dev/null +++ b/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneInstanceBuffers.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 44fb3ab8e7b24e92ade483775da17002 +timeCreated: 1633487557 \ No newline at end of file diff --git a/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneScope.cs b/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneScope.cs index 66fd48274..7a040506d 100644 --- a/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneScope.cs +++ b/Assets/VRM10/Runtime/FastSpringBone/InputPorts/FastSpringBoneScope.cs @@ -9,21 +9,24 @@ namespace UniVRM10.FastSpringBones.System /// public class FastSpringBoneScope : IDisposable { - private readonly FastSpringBoneService _fastSpringBoneService; + private readonly FastSpringBoneService _service; + private readonly FastSpringBoneBuffer _buffer; public IReadOnlyList Springs { get; } public FastSpringBoneScope(IReadOnlyList springs) { Springs = springs; + _service = FastSpringBoneService.Instance; + _buffer = new FastSpringBoneBuffer(springs); - _fastSpringBoneService = FastSpringBoneService.Instance; - _fastSpringBoneService.BufferCombiner.Register(this); + _service.BufferCombiner.Register(_buffer); } public void Dispose() { - _fastSpringBoneService.BufferCombiner.Unregister(this); + _service.BufferCombiner.Unregister(_buffer); + _buffer.Dispose(); } } } \ No newline at end of file diff --git a/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs b/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs index 8af7f1988..63ac84af3 100644 --- a/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs +++ b/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; -using System.Linq; using Unity.Collections; +using Unity.Jobs; using UnityEngine; using UnityEngine.Jobs; using UnityEngine.Profiling; using UniVRM10.FastSpringBones.Blittables; +#if ENABLE_SPRINGBONE_BURST +using Unity.Burst; +#endif namespace UniVRM10.FastSpringBones.System { @@ -15,13 +18,13 @@ namespace UniVRM10.FastSpringBones.System public sealed class FastSpringBoneBufferCombiner : IDisposable { private NativeArray _springs; - private NativeArray _joints; private NativeArray _transforms; - private TransformAccessArray _transformAccessArray; private NativeArray _colliders; + private NativeArray _joints; private NativeArray _logics; + private TransformAccessArray _transformAccessArray; - private readonly LinkedList _fastSpringBoneScopes = new LinkedList(); + private readonly LinkedList _buffers = new LinkedList(); private bool _isDirty; @@ -32,139 +35,138 @@ namespace UniVRM10.FastSpringBones.System public NativeArray Colliders => _colliders; public NativeArray Logics => _logics; - public void Register(FastSpringBoneScope scope) + public void Register(FastSpringBoneBuffer buffer) { - _fastSpringBoneScopes.AddLast(scope); + _buffers.AddLast(buffer); _isDirty = true; } - public void Unregister(FastSpringBoneScope scope) + public void Unregister(FastSpringBoneBuffer buffer) { - _fastSpringBoneScopes.Remove(scope); + _buffers.Remove(buffer); _isDirty = true; } /// /// 変更があったならばバッファを再構築する /// - public void ReconstructIfDirty() + public JobHandle ReconstructIfDirty(JobHandle handle) { if (_isDirty) { - ReconstructBuffers(); + var result = ReconstructBuffers(handle); _isDirty = false; + return result; } + + return handle; } /// /// バッファを再構築する /// TODO: 最適化 /// - private void ReconstructBuffers() + private JobHandle ReconstructBuffers(JobHandle handle) { Profiler.BeginSample("FastSpringBone.ReconstructBuffers"); + + Profiler.BeginSample("FastSpringBone.ReconstructBuffers.DisposeBuffers"); DisposeAllBuffers(); + Profiler.EndSample(); - var springs = _fastSpringBoneScopes.SelectMany(scope => scope.Springs).ToArray(); + var springsCount = 0; + var collidersCount = 0; + var logicsCount = 0; + var transformsCount = 0; - // Transformの列挙 - var transformHashSet = new HashSet(); - foreach (var spring in springs) + Profiler.BeginSample("FastSpringBone.ReconstructBuffers.CountBufferSize"); + foreach (var buffer in _buffers) { - foreach (var joint in spring.joints) - { - transformHashSet.Add(joint.Transform); - if (joint.Transform.parent != null) transformHashSet.Add(joint.Transform.parent); - } - foreach (var collider in spring.colliders) - { - transformHashSet.Add(collider.Transform); - } - - if (spring.center != null) transformHashSet.Add(spring.center); + springsCount += buffer.Springs.Length; + collidersCount += buffer.Colliders.Length; + logicsCount += buffer.Logics.Length; + transformsCount += buffer.BlittableTransforms.Length; } - var transforms = transformHashSet.ToArray(); - var transformIndexDictionary = transforms.Select((trs, index) => (trs, index)) - .ToDictionary(tuple => tuple.trs, tuple => tuple.index); - - // 各種bufferの構築 - var blittableColliders = new List(); - var blittableJoints = new List(); - var blittableSprings = new List(); - var blittableLogics = new List(); - - foreach (var spring in springs) - { - var blittableSpring = new BlittableSpring - { - colliderSpan = new BlittableSpan - { - startIndex = blittableColliders.Count, - count = spring.colliders.Length, - }, - logicSpan = new BlittableSpan - { - startIndex = blittableJoints.Count, - count = spring.joints.Length - 1, - }, - centerTransformIndex = spring.center ? transformIndexDictionary[spring.center] : -1 - }; - blittableSprings.Add(blittableSpring); - - blittableColliders.AddRange(spring.colliders.Select(collider => - { - var blittable = collider.Collider; - blittable.transformIndex = transformIndexDictionary[collider.Transform]; - return blittable; - })); - blittableJoints.AddRange(spring.joints.Take(spring.joints.Length - 1).Select(joint => - { - var blittable = joint.Joint; - return blittable; - })); - - for (var i = 0; i < spring.joints.Length - 1; ++i) - { - var joint = spring.joints[i]; - var tailJoint = spring.joints[i + 1]; - var localPosition = tailJoint.Transform.localPosition; - var scale = tailJoint.Transform.lossyScale; - var localChildPosition = - new Vector3( - localPosition.x * scale.x, - localPosition.y * scale.y, - localPosition.z * scale.z - ); - - var worldChildPosition = joint.Transform.TransformPoint(localChildPosition); - var currentTail = spring.center != null - ? spring.center.InverseTransformPoint(worldChildPosition) - : worldChildPosition; - var parent = joint.Transform.parent; - blittableLogics.Add(new BlittableLogic - { - headTransformIndex = transformIndexDictionary[joint.Transform], - parentTransformIndex = parent != null ? transformIndexDictionary[parent] : -1, - currentTail = currentTail, - prevTail = currentTail, - localRotation = joint.Transform.localRotation, - boneAxis = localChildPosition.normalized, - length = localChildPosition.magnitude - }); - } - } - - // 各種bufferの初期化 - _springs = new NativeArray(blittableSprings.ToArray(), Allocator.Persistent); - - _joints = new NativeArray(blittableJoints.ToArray(), Allocator.Persistent); - _colliders = new NativeArray(blittableColliders.ToArray(), Allocator.Persistent); - _logics = new NativeArray(blittableLogics.ToArray(), Allocator.Persistent); - - _transforms = new NativeArray(transforms.Length, Allocator.Persistent); - _transformAccessArray = new TransformAccessArray(transforms.ToArray()); Profiler.EndSample(); + + // バッファの構築 + Profiler.BeginSample("FastSpringBone.ReconstructBuffers.CreateBuffers"); + _springs = new NativeArray(springsCount, Allocator.Persistent); + + _joints = new NativeArray(logicsCount, Allocator.Persistent); + _logics = new NativeArray(logicsCount, Allocator.Persistent); + _colliders = new NativeArray(collidersCount, Allocator.Persistent); + + _transforms = new NativeArray(transformsCount, Allocator.Persistent); + Profiler.EndSample(); + + var springsOffset = 0; + var collidersOffset = 0; + var logicsOffset = 0; + var transformOffset = 0; + + Profiler.BeginSample("FastSpringBone.ReconstructBuffers.ScheduleLoadBufferJobs"); + foreach (var buffer in _buffers) + { + // バッファの読み込みをスケジュール + handle = new LoadTransformsJob + { + SrcTransforms = buffer.BlittableTransforms, + DestTransforms = + new NativeSlice(_transforms, transformOffset, + buffer.BlittableTransforms.Length) + }.Schedule(buffer.BlittableTransforms.Length, 1, handle); + handle = new LoadSpringsJob + { + SrcSprings = buffer.Springs, + DestSprings = new NativeSlice(_springs, springsOffset, buffer.Springs.Length), + CollidersOffset = collidersOffset, + LogicsOffset = logicsOffset, + TransformOffset = transformOffset + }.Schedule(buffer.Springs.Length, 1, handle); + handle = new LoadCollidersJob() + { + SrcColliders = buffer.Colliders, + DestColliders = + new NativeSlice(_colliders, collidersOffset, buffer.Colliders.Length), + TransformOffset = transformOffset + }.Schedule(buffer.Colliders.Length, 1, handle); + handle = new OffsetLogicsJob() + { + SrcLogics = buffer.Logics, + SrcJoints = buffer.Joints, + DestLogics = new NativeSlice(_logics, logicsOffset, buffer.Logics.Length), + DestJoints = new NativeSlice(_joints, logicsOffset, buffer.Logics.Length), + TransformOffset = transformOffset + }.Schedule(buffer.Logics.Length, 1, handle); + + springsOffset += buffer.Springs.Length; + collidersOffset += buffer.Colliders.Length; + logicsOffset += buffer.Logics.Length; + transformOffset += buffer.BlittableTransforms.Length; + } + + // TransformAccessArrayの構築と並行してJobを行うため、この時点で走らせておく + JobHandle.ScheduleBatchedJobs(); + Profiler.EndSample(); + + // TransformAccessArrayの構築 + Profiler.BeginSample("FastSpringBone.ReconstructBuffers.LoadTransformAccessArray"); + var transforms = new Transform[transformsCount]; + var transformAccessArrayOffset = 0; + foreach (var buffer in _buffers) + { + Array.Copy(buffer.Transforms, 0, transforms, transformAccessArrayOffset, buffer.Transforms.Length); + transformAccessArrayOffset += buffer.BlittableTransforms.Length; + } + + _transformAccessArray = new TransformAccessArray(transforms); + Profiler.EndSample(); + + Profiler.EndSample(); + + return handle; } private void DisposeAllBuffers() @@ -181,5 +183,78 @@ namespace UniVRM10.FastSpringBones.System { DisposeAllBuffers(); } + +#if ENABLE_SPRINGBONE_BURST + [BurstCompile] +#endif + private struct LoadTransformsJob : IJobParallelFor + { + [ReadOnly] public NativeArray SrcTransforms; + [WriteOnly] public NativeSlice DestTransforms; + + public void Execute(int index) + { + DestTransforms[index] = SrcTransforms[index]; + } + } + +#if ENABLE_SPRINGBONE_BURST + [BurstCompile] +#endif + private struct LoadSpringsJob : IJobParallelFor + { + [ReadOnly] public NativeArray SrcSprings; + [WriteOnly] public NativeSlice DestSprings; + public int CollidersOffset; + public int LogicsOffset; + public int TransformOffset; + + public void Execute(int index) + { + var spring = SrcSprings[index]; + spring.colliderSpan.startIndex += CollidersOffset; + spring.logicSpan.startIndex += LogicsOffset; + if (spring.centerTransformIndex >= 0) spring.centerTransformIndex += TransformOffset; + DestSprings[index] = spring; + } + } + +#if ENABLE_SPRINGBONE_BURST + [BurstCompile] +#endif + private struct LoadCollidersJob : IJobParallelFor + { + [ReadOnly] public NativeArray SrcColliders; + [WriteOnly] public NativeSlice DestColliders; + public int TransformOffset; + + public void Execute(int index) + { + var collider = SrcColliders[index]; + collider.transformIndex += TransformOffset; + DestColliders[index] = collider; + } + } + +#if ENABLE_SPRINGBONE_BURST + [BurstCompile] +#endif + private struct OffsetLogicsJob : IJobParallelFor + { + [ReadOnly] public NativeSlice SrcLogics; + [ReadOnly] public NativeSlice SrcJoints; + [WriteOnly] public NativeSlice DestLogics; + [WriteOnly] public NativeSlice DestJoints; + public int TransformOffset; + + public void Execute(int index) + { + var logic = SrcLogics[index]; + logic.headTransformIndex += TransformOffset; + if (logic.parentTransformIndex >= 0) logic.parentTransformIndex += TransformOffset; + DestLogics[index] = logic; + DestJoints[index] = SrcJoints[index]; + } + } } } \ No newline at end of file diff --git a/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneScheduler.cs b/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneScheduler.cs index 3ca6541be..064d37360 100644 --- a/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneScheduler.cs +++ b/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneScheduler.cs @@ -16,12 +16,13 @@ namespace UniVRM10.FastSpringBones.System public JobHandle Schedule() { - _bufferCombiner.ReconstructIfDirty(); + var handle = default(JobHandle); + handle = _bufferCombiner.ReconstructIfDirty(handle); - var handle = new PullTransformJob + handle = new PullTransformJob { Transforms = _bufferCombiner.Transforms - }.Schedule(_bufferCombiner.TransformAccessArray); + }.Schedule(_bufferCombiner.TransformAccessArray, handle); handle = new UpdateFastSpringBoneJob {