diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 454a227a..9a50da0f 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog]. ### Added ### Changed +- Improved performance in InternalAutoFreezeMeaninglessBlendShapeProcessor [`#1325`](https://github.com/anatawa12/AvatarOptimizer/pull/1325) ### Deprecated diff --git a/CHANGELOG.md b/CHANGELOG.md index 3192a6df..728d85f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,7 +89,7 @@ The format is based on [Keep a Changelog]. - Renamed debug options internally `#1228` - This will lose previously configured debug options. - However, debug options are not considered as Public API as stated in documents so this is not backward incompatible changes in semver 2.0.0 section 8. -- Performance Improvements `#1234` `#1243` `#1240` `#1288` `#1304` `#1307` `#1314` +- Performance Improvements `#1234` `#1243` `#1240` `#1288` `#1304` `#1307` `#1314` `#1325` - Transform gizmo are now hidden while you're editing box of Remove Mesh in Box `#1259` - This prevents mistakenly moving the Skinned Mesh Renderer while editing the box. - Make MergePhysBone implement `INetworkID` `#1260` diff --git a/Editor/Processors/SkinnedMeshes/InternalAutoFreezeMeaninglessBlendShapeProcessor.cs b/Editor/Processors/SkinnedMeshes/InternalAutoFreezeMeaninglessBlendShapeProcessor.cs index b386732d..93954cce 100644 --- a/Editor/Processors/SkinnedMeshes/InternalAutoFreezeMeaninglessBlendShapeProcessor.cs +++ b/Editor/Processors/SkinnedMeshes/InternalAutoFreezeMeaninglessBlendShapeProcessor.cs @@ -1,9 +1,14 @@ +using System; using System.Collections.Generic; using System.Linq; using nadena.dev.ndmf; +using Unity.Burst; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; using UnityEditor; using UnityEngine; +using UnityEngine.Profiling; namespace Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes { @@ -35,13 +40,23 @@ public override void Process(BuildContext context, MeshInfo2 target) value[vertex.BlendShapeBufferVertexIndex] = true; } + Profiler.BeginSample("buffers loop"); + + var shapeCount = buffers.Sum(x => x.Key.Shapes.Count); + + using var isMeaningfulArray = new NativeArray(shapeCount, Allocator.TempJob); + List<(string, JobHandle, int)> meaningfulIndex = new List<(string, JobHandle, int)>(shapeCount); + + int bufIndexNext = 0; foreach (var (shapeBuffer, useIndices) in buffers) { foreach (var (shapeName, shapeShape) in shapeBuffer.Shapes) { + var bufIndex = bufIndexNext++; + if (!meaninglessBlendShapes.Contains(shapeName)) continue; - - var meaningfull = false; + + JobHandle completionHandle = default; foreach (var bufferIndex in shapeShape.FramesBufferIndices) { @@ -49,21 +64,39 @@ public override void Process(BuildContext context, MeshInfo2 target) var deltaNormals = shapeBuffer.DeltaNormals[bufferIndex]; var deltaTangents = shapeBuffer.DeltaTangents[bufferIndex]; - for (var i = 0; i < deltaVertices.Length; i++) + if (bufIndex >= isMeaningfulArray.Length) { - if (!useIndices[i]) continue; - if (deltaVertices[i] != Vector3.zero || deltaNormals[i] != Vector3.zero || deltaTangents[i] != Vector3.zero) - { - meaningfull = true; - break; - } + throw new IndexOutOfRangeException("bufIndex >= isMeaningfulArray.Length"); } + + var job = new ShapeAnalysisJob + { + isMeaningfulArray = isMeaningfulArray, + resultIndex = bufIndex, + deltaVertices = deltaVertices, + deltaNormals = deltaNormals, + deltaTangents = deltaTangents, + useIndices = useIndices, + }.Schedule(deltaVertices.Length, 64); + completionHandle = JobHandle.CombineDependencies(job, completionHandle); } - - if (meaningfull) - meaninglessBlendShapes.Remove(shapeName); + + meaningfulIndex.Add((shapeName, completionHandle, bufIndex)); } } + Profiler.BeginSample("Await completion"); + foreach (var (_, handle, _) in meaningfulIndex) + { + handle.Complete(); + } + foreach (var (shapeName, _, bufIndex) in meaningfulIndex) + { + if (isMeaningfulArray[bufIndex]) + meaninglessBlendShapes.Remove(shapeName); + } + Profiler.EndSample(); + + Profiler.EndSample(); foreach (var meaninglessBlendShape in meaninglessBlendShapes) context.RecordRemoveProperty(Target, $"blendShape.{meaninglessBlendShape}"); @@ -73,6 +106,35 @@ public override void Process(BuildContext context, MeshInfo2 target) set.UnionWith(meaninglessBlendShapes); freezeBlendShape.shapeKeysSet.SetValueNonPrefab(set); } + + [BurstCompile] + struct ShapeAnalysisJob : IJobParallelFor + { + // We will issue a separate job for each blend shape frame, and want them to run in parallel. However, + // normally unity will prevent us from using the same NativeArray in multiple such jobs. Since we're + // writing only, and the order in which the writes occurs doesn't matter, _and_ this is an atomic primitive + // type, we can get away with disabling this safety check. + [NativeDisableContainerSafetyRestriction] + [WriteOnly] + public NativeArray isMeaningfulArray; + + public int resultIndex; + + [ReadOnly] public NativeArray deltaVertices; + [ReadOnly] public NativeArray deltaNormals; + [ReadOnly] public NativeArray deltaTangents; + [ReadOnly] public NativeArray useIndices; + + public void Execute(int vertIndex) + { + if (!useIndices[vertIndex]) return; + + if (deltaVertices[vertIndex] != Vector3.zero || deltaNormals[vertIndex] != Vector3.zero || deltaTangents[vertIndex] != Vector3.zero) + { + isMeaningfulArray[resultIndex] = true; + } + } + } // nothing to do public override IMeshInfoComputer GetComputer(IMeshInfoComputer upstream) => upstream;