From 2376fc9414869b0b321f1b1b7874ca84110c2d2f Mon Sep 17 00:00:00 2001 From: Julian Date: Tue, 17 Mar 2026 03:14:09 +0000 Subject: [PATCH] fix: resolve all build errors in Meshlet LOD pipeline - Use correct UnsafeList constructor (int capacity, Allocator) - Use .Count instead of .Length for UnsafeList - Cast GetUnsafePtr() to typed pointers explicitly - Use Api.meshopt_* constants (not MeshOptApi) for simplify flags - Use meshopt_Meshlet instance methods BuildsFlex/BuildsSpatial - Use correct meshopt_Meshlet field names (vertex_offset, triangle_offset, etc.) - Fix byte constant overflow with unchecked cast in LockBoundary - Add Ghost.MeshOptimizer project reference to Ghost.Graphics.csproj --- .../Ghost.Graphics/Ghost.Graphics.csproj | 1 + .../Meshlet/ClodBoundsHelper.cs | 52 +++++------ .../Ghost.Graphics/Meshlet/ClodBuilder.cs | 87 ++++++++----------- .../Ghost.Graphics/Meshlet/ClodInternal.cs | 83 ++++++++---------- .../Meshlet/ClodInternal_Boundary.cs | 42 ++++----- .../Meshlet/ClodInternal_Partition.cs | 66 ++++++-------- .../Ghost.Graphics/Meshlet/ClodMesh.cs | 4 +- .../Ghost.Graphics/Meshlet/ClodSimplify.cs | 86 +++++++----------- 8 files changed, 178 insertions(+), 243 deletions(-) diff --git a/src/Runtime/Ghost.Graphics/Ghost.Graphics.csproj b/src/Runtime/Ghost.Graphics/Ghost.Graphics.csproj index fe61d22..e9b6b34 100644 --- a/src/Runtime/Ghost.Graphics/Ghost.Graphics.csproj +++ b/src/Runtime/Ghost.Graphics/Ghost.Graphics.csproj @@ -30,6 +30,7 @@ + diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs index 6204dd1..f1edd8e 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs @@ -1,52 +1,48 @@ using System; using System.Numerics; using Ghost.MeshOptimizer; -using Misaki.HighPerformance; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Graphics.Meshlet; internal static class ClodBoundsHelper { - public static ClodBounds ComputeBounds(ClodMesh mesh, UnsafeList indices, float error) + public static unsafe ClodBounds ComputeBounds(ClodMesh mesh, UnsafeList indices, float error) { - var bounds = MeshOptApi.ComputeClusterBounds(indices.GetUnsafePtr(), (nuint)indices.Length, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); - - var result = new ClodBounds(); - result.center = new Vector3(bounds.center[0], bounds.center[1], bounds.center[2]); - result.radius = bounds.radius; - result.error = error; - return result; + var bounds = MeshOptApi.ComputeClusterBounds((uint*)indices.GetUnsafePtr(), (nuint)indices.Count, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); + return new ClodBounds + { + center = new Vector3(bounds.center[0], bounds.center[1], bounds.center[2]), + radius = bounds.radius, + error = error + }; } - public static ClodBounds MergeBounds(UnsafeList clusters, UnsafeList group) + public static unsafe ClodBounds MergeBounds(UnsafeList clusters, UnsafeList group) { - // Use Temp for the bounds list to support mega-meshes without stack overflow - var boundsList = new UnsafeList((nuint)group.Length, Allocator.Temp); - - for (int j = 0; j < (int)group.Length; j++) - { + var boundsList = new UnsafeList(group.Count, Allocator.Temp); + for (int j = 0; j < group.Count; j++) boundsList.Add(clusters[group[j]].bounds); - } var merged = MeshOptApi.ComputeSphereBounds( (float*)boundsList.GetUnsafePtr(), - (nuint)group.Length, + (nuint)group.Count, (nuint)sizeof(ClodBounds), - (float*)boundsList.GetUnsafePtr() + 3, // offset to radius field + (float*)boundsList.GetUnsafePtr() + 3, (nuint)sizeof(ClodBounds) ); - var result = new ClodBounds(); - result.center = new Vector3(merged.center[0], merged.center[1], merged.center[2]); - result.radius = merged.radius; - - result.error = 0.0f; - for (int j = 0; j < (int)group.Length; j++) - { - result.error = Math.Max(result.error, clusters[group[j]].bounds.error); - } + float maxError = 0.0f; + for (int j = 0; j < group.Count; j++) + maxError = Math.Max(maxError, clusters[group[j]].bounds.error); boundsList.Dispose(); - return result; + return new ClodBounds + { + center = new Vector3(merged.center[0], merged.center[1], merged.center[2]), + radius = merged.radius, + error = maxError + }; } } diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodBuilder.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodBuilder.cs index 0bf5bce..f082234 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodBuilder.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodBuilder.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics; -using System.Numerics; using Ghost.MeshOptimizer; -using Misaki.HighPerformance; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Graphics.Meshlet; @@ -21,78 +21,69 @@ public unsafe static class ClodBuilder { Debug.Assert(mesh.vertexAttributesStride % (nuint)sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)"); - // Use Persistent or Temp for large mesh data to avoid stack overflow - var locks = new UnsafeList(mesh.vertexCount, Allocator.Temp); + var locks = new UnsafeList((int)mesh.vertexCount, Allocator.Temp); locks.AsSpan().Fill(0); - // Generate position-only remap - var remap = new UnsafeList(mesh.vertexCount, Allocator.Temp); - MeshOptApi.GeneratePositionRemap(remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); + var remap = new UnsafeList((int)mesh.vertexCount, Allocator.Temp); + remap.Resize((int)mesh.vertexCount); + MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); - // Set up protect bits on UV seams if (mesh.attributeProtectMask != 0) { nuint maxAttributes = mesh.vertexAttributesStride / sizeof(float); for (nuint i = 0; i < mesh.vertexCount; i++) { - uint r = remap[(int)i]; + uint r = ((uint*)remap.GetUnsafePtr())[(int)i]; for (nuint j = 0; j < maxAttributes; j++) { if ((r != i) && ((mesh.attributeProtectMask & (1u << (int)j)) != 0)) { if (mesh.vertexAttributes[i * maxAttributes + j] != mesh.vertexAttributes[r * maxAttributes + j]) { - locks[(int)i] |= (byte)MeshOptApi.SimplifyVertex_Protect; + ((byte*)locks.GetUnsafePtr())[(int)i] |= (byte)(Api.meshopt_SimplifyVertex_Protect & 0xFF); } } } } } - // Initial clusterization var clusters = ClodInternal.Clusterize(config, mesh, mesh.indices, mesh.indexCount, Allocator.Persistent); - // Compute initial bounds - for (int i = 0; i < (int)clusters.Length; i++) + for (int i = 0; i < clusters.Count; i++) { clusters[i].bounds = ClodBoundsHelper.ComputeBounds(mesh, clusters[i].indices, 0.0f); } - var pending = new UnsafeList(clusters.Length, Allocator.Temp); - for (int i = 0; i < (int)clusters.Length; i++) + var pending = new UnsafeList(clusters.Count, Allocator.Temp); + for (int i = 0; i < clusters.Count; i++) pending.Add(i); int depth = 0; - while (pending.Length > 1) + while (pending.Count > 1) { - // Partition results are temporary but returned, using Temp allocator var groups = ClodPartition.Partition(config, mesh, clusters, pending, remap, Allocator.Temp); - pending.Clear(); - // Lock boundaries ClodBoundary.LockBoundary(locks, groups, clusters, remap, mesh.vertexLock); - for (int i = 0; i < (int)groups.Length; i++) + for (int i = 0; i < groups.Count; i++) { - // Merged indices for a group of clusters - var merged = new UnsafeList((nuint)groups[i].Length * config.maxTriangles * 3, Allocator.Temp); - for (int j = 0; j < (int)groups[i].Length; j++) + var merged = new UnsafeList(groups[i].Count * (int)config.maxTriangles * 3, Allocator.Temp); + for (int j = 0; j < groups[i].Count; j++) { var clusterIndices = clusters[groups[i][j]].indices; - for (int k = 0; k < (int)clusterIndices.Length; k++) + for (int k = 0; k < clusterIndices.Count; k++) merged.Add(clusterIndices[k]); } - nuint targetSize = ((nuint)merged.Length / 3) * (nuint)config.simplifyRatio * 3; - + nuint targetSize = ((nuint)merged.Count / 3) * (nuint)config.simplifyRatio * 3; var bounds = ClodBoundsHelper.MergeBounds(clusters, groups[i]); float error = 0.0f; var simplified = ClodSimplify.Simplify(config, mesh, merged, locks, targetSize, &error); - if (simplified.Length > (nuint)(merged.Length * config.simplifyThreshold)) + if ((nuint)simplified.Count > (nuint)(merged.Count * config.simplifyThreshold)) { bounds.error = float.MaxValue; OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); @@ -104,49 +95,41 @@ public unsafe static class ClodBuilder int refined = OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); - // Discard old clusters - for (int j = 0; j < (int)groups[i].Length; j++) - { + for (int j = 0; j < groups[i].Count; j++) clusters[groups[i][j]].indices.Dispose(); - } - // Clusterize simplified mesh - var split = ClodInternal.Clusterize(config, mesh, simplified.GetUnsafePtr(), simplified.Length, Allocator.Persistent); - for (int j = 0; j < (int)split.Length; j++) + var split = ClodInternal.Clusterize(config, mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Count, Allocator.Persistent); + for (int j = 0; j < split.Count; j++) { split[j].refined = refined; split[j].bounds = bounds; clusters.Add(split[j]); - pending.Add((int)clusters.Length - 1); + pending.Add(clusters.Count - 1); } split.Dispose(); merged.Dispose(); } - // Cleanup groups - for (int i = 0; i < (int)groups.Length; i++) + for (int i = 0; i < groups.Count; i++) groups[i].Dispose(); groups.Dispose(); depth++; } - if (pending.Length > 0) + if (pending.Count > 0) { - var cluster = clusters[pending[0]]; - var bounds = cluster.bounds; + var bounds = clusters[pending[0]].bounds; bounds.error = float.MaxValue; OutputGroup(config, mesh, clusters, pending, bounds, depth, outputContext, outputCallback); } - nuint finalClusterCount = clusters.Length; + nuint finalClusterCount = (nuint)clusters.Count; - // Cleanup - for (int i = 0; i < (int)clusters.Length; i++) + for (int i = 0; i < clusters.Count; i++) clusters[i].indices.Dispose(); clusters.Dispose(); - locks.Dispose(); remap.Dispose(); pending.Dispose(); @@ -165,28 +148,26 @@ public unsafe static class ClodBuilder ClodOutputDelegate outputCallback ) { - // Use Temp for the output array to avoid stack pressure - var groupClusters = new UnsafeList(group.Length, Allocator.Temp); + var groupClusters = new UnsafeList(group.Count, Allocator.Temp); - for (int i = 0; i < (int)group.Length; i++) + for (int i = 0; i < group.Count; i++) { ref var srcCluster = ref clusters[group[i]]; - var dstCluster = new ClodCluster + groupClusters.Add(new ClodCluster { refined = srcCluster.refined, bounds = (config.optimizeBounds && srcCluster.refined != -1) ? ClodBoundsHelper.ComputeBounds(mesh, srcCluster.indices, srcCluster.bounds.error) : srcCluster.bounds, - indices = srcCluster.indices.GetUnsafePtr(), - indexCount = (nuint)srcCluster.indices.Length, + indices = (uint*)srcCluster.indices.GetUnsafePtr(), + indexCount = (nuint)srcCluster.indices.Count, vertexCount = srcCluster.vertices - }; - groupClusters.Add(dstCluster); + }); } var clodGroup = new ClodGroup { depth = depth, simplified = simplified }; int result = outputCallback != null - ? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Length) + ? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Count) : -1; groupClusters.Dispose(); diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs index 88d8035..7184073 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs @@ -1,92 +1,79 @@ using System; using Ghost.MeshOptimizer; -using Misaki.HighPerformance; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Graphics.Meshlet; internal static class ClodInternal { - public static UnsafeList Clusterize(ClodConfig config, ClodMesh mesh, uint* indices, nuint indexCount, Allocator allocator) + public static unsafe UnsafeList Clusterize(ClodConfig config, ClodMesh mesh, uint* indices, nuint indexCount, Allocator allocator) { nuint maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles); - var meshlets = new UnsafeList(maxMeshlets, allocator); - var meshletVertices = new UnsafeList(indexCount, allocator); - var meshletTriangles = new UnsafeList(indexCount, allocator); + var meshlets = new UnsafeList((int)maxMeshlets, Allocator.Temp); + meshlets.Resize((int)maxMeshlets); + var meshletVertices = new UnsafeList((int)indexCount, Allocator.Temp); + meshletVertices.Resize((int)indexCount); + var meshletTriangles = new UnsafeList((int)indexCount, Allocator.Temp); + meshletTriangles.Resize((int)indexCount); + + meshopt_Meshlet* pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr(); + uint* pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr(); + byte* pMeshletTriangles = (byte*)meshletTriangles.GetUnsafePtr(); - meshlets.Resize(maxMeshlets); - nuint meshletCount; if (config.clusterSpatial) { - meshletCount = MeshOptApi.BuildMeshletsSpatial( - meshlets.GetUnsafePtr(), - meshletVertices.GetUnsafePtr(), - meshletTriangles.GetUnsafePtr(), - indices, - indexCount, - mesh.vertexPositions, - mesh.vertexCount, - mesh.vertexPositionsStride, - config.maxVertices, - config.minTriangles, - config.maxTriangles, + meshletCount = pMeshlets[0].BuildsSpatial( + pMeshletVertices, pMeshletTriangles, + indices, indexCount, + mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride, + config.maxVertices, config.minTriangles, config.maxTriangles, config.clusterFillWeight ); } else { - meshletCount = MeshOptApi.BuildMeshletsFlex( - meshlets.GetUnsafePtr(), - meshletVertices.GetUnsafePtr(), - meshletTriangles.GetUnsafePtr(), - indices, - indexCount, - mesh.vertexPositions, - mesh.vertexCount, - mesh.vertexPositionsStride, - config.maxVertices, - config.minTriangles, - config.maxTriangles, - 0.0f, - config.clusterSplitFactor + meshletCount = pMeshlets[0].BuildsFlex( + pMeshletVertices, pMeshletTriangles, + indices, indexCount, + mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride, + config.maxVertices, config.minTriangles, config.maxTriangles, + 0.0f, config.clusterSplitFactor ); } - meshlets.Resize(meshletCount); - var clusters = new UnsafeList(meshletCount, allocator); - + var clusters = new UnsafeList((int)meshletCount, allocator); + for (nuint i = 0; i < meshletCount; i++) { - ref var meshlet = ref meshlets[i]; + ref var meshlet = ref pMeshlets[i]; if (config.optimizeClusters) { MeshOptApi.OptimizeMeshlet( - meshletVertices.GetUnsafePtr() + meshlet.vertexOffset, - meshletTriangles.GetUnsafePtr() + meshlet.triangleOffset, - meshlet.triangleCount, - meshlet.vertexCount + pMeshletVertices + meshlet.vertex_offset, + pMeshletTriangles + meshlet.triangle_offset, + meshlet.triangle_count, + meshlet.vertex_count ); } var cluster = new Cluster { - vertices = meshlet.vertexCount, - indices = new UnsafeList(meshlet.triangleCount * 3, allocator), + vertices = meshlet.vertex_count, + indices = new UnsafeList((int)(meshlet.triangle_count * 3), allocator), group = -1, refined = -1 }; - for (nuint j = 0; j < meshlet.triangleCount * 3; j++) - { - cluster.indices.Add(meshletVertices[(int)(meshlet.vertexOffset + meshletTriangles[(int)(meshlet.triangleOffset + j)])]); - } + for (nuint j = 0; j < meshlet.triangle_count * 3; j++) + cluster.indices.Add(pMeshletVertices[meshlet.vertex_offset + pMeshletTriangles[meshlet.triangle_offset + j]]); clusters.Add(cluster); } - // Cleanup meshlets.Dispose(); meshletVertices.Dispose(); meshletTriangles.Dispose(); diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Boundary.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Boundary.cs index 70b64d1..2715395 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Boundary.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Boundary.cs @@ -1,48 +1,48 @@ using Ghost.MeshOptimizer; -using Misaki.HighPerformance; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Graphics.Meshlet; internal static class ClodBoundary { - public static void LockBoundary(UnsafeList locks, UnsafeList> groups, UnsafeList clusters, UnsafeList remap, byte* vertexLock) + public static unsafe void LockBoundary(UnsafeList locks, UnsafeList> groups, UnsafeList clusters, UnsafeList remap, byte* vertexLock) { - for (int i = 0; i < (int)locks.Length; i++) - { - locks[i] &= ~((byte)((1 << 0) | (1 << 7))); - } + byte* pLocks = (byte*)locks.GetUnsafePtr(); + uint* pRemap = (uint*)remap.GetUnsafePtr(); - for (int i = 0; i < (int)groups.Length; i++) + for (int i = 0; i < locks.Count; i++) + pLocks[i] = unchecked((byte)(pLocks[i] & ~((1 << 0) | (1 << 7)))); + + for (int i = 0; i < groups.Count; i++) { - for (int j = 0; j < (int)groups[i].Length; j++) + for (int j = 0; j < groups[i].Count; j++) { var cluster = clusters[groups[i][j]]; - for (int k = 0; k < (int)cluster.indices.Length; k++) + for (int k = 0; k < cluster.indices.Count; k++) { - uint v = cluster.indices[k]; - uint r = remap[(int)v]; - locks[(int)r] |= (byte)(locks[(int)r] >> 7); + uint r = pRemap[(int)cluster.indices[k]]; + pLocks[r] |= (byte)(pLocks[r] >> 7); } } - for (int j = 0; j < (int)groups[i].Length; j++) + for (int j = 0; j < groups[i].Count; j++) { var cluster = clusters[groups[i][j]]; - for (int k = 0; k < (int)cluster.indices.Length; k++) + for (int k = 0; k < cluster.indices.Count; k++) { - uint v = cluster.indices[k]; - uint r = remap[(int)v]; - locks[(int)r] |= (byte)(1 << 7); + uint r = pRemap[(int)cluster.indices[k]]; + pLocks[r] |= (byte)(1 << 7); } } } - for (int i = 0; i < (int)locks.Length; i++) + for (int i = 0; i < locks.Count; i++) { - uint r = remap[i]; - locks[i] = (byte)((locks[(int)r] & 1) | (locks[i] & (byte)MeshOptApi.meshopt_SimplifyVertex_Protect)); + uint r = pRemap[i]; + pLocks[i] = (byte)((pLocks[r] & 1) | (pLocks[i] & (byte)(Api.meshopt_SimplifyVertex_Protect & 0xFF))); if (vertexLock != null) - locks[i] |= vertexLock[i]; + pLocks[i] |= vertexLock[i]; } } } diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs index 55bf8b3..b0423fe 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs @@ -1,71 +1,59 @@ using System; using Ghost.MeshOptimizer; -using Misaki.HighPerformance; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Graphics.Meshlet; internal static class ClodPartition { - public static UnsafeList> Partition(ClodConfig config, ClodMesh mesh, UnsafeList clusters, UnsafeList pending, UnsafeList remap, Allocator allocator) + public static unsafe UnsafeList> Partition(ClodConfig config, ClodMesh mesh, UnsafeList clusters, UnsafeList pending, UnsafeList remap, Allocator allocator) { - if (pending.Length <= (int)config.partitionSize) + if (pending.Count <= (int)config.partitionSize) { - var partitions = new UnsafeList>(1, allocator); - partitions.Add(pending); - return partitions; + var single = new UnsafeList>(1, allocator); + single.Add(pending); + return single; } - // Internal counters/indices can stay on Temp to avoid stack overflow for huge meshes - var clusterIndices = new UnsafeList(1024, Allocator.Temp); - var clusterCounts = new UnsafeList((nuint)pending.Length, Allocator.Temp); - nuint totalIndexCount = 0; - for (int i = 0; i < pending.Length; i++) - { - var cluster = clusters[pending[i]]; - totalIndexCount += cluster.indices.Length; - } + for (int i = 0; i < pending.Count; i++) + totalIndexCount += (nuint)clusters[pending[i]].indices.Count; - clusterIndices.Resize(totalIndexCount); + var clusterIndices = new UnsafeList((int)totalIndexCount, Allocator.Temp); + var clusterCounts = new UnsafeList(pending.Count, Allocator.Temp); nuint offset = 0; - for (int i = 0; i < pending.Length; i++) + for (int i = 0; i < pending.Count; i++) { var cluster = clusters[pending[i]]; - clusterCounts.Add((uint)cluster.indices.Length); - - for (int j = 0; j < (int)cluster.indices.Length; j++) - { - clusterIndices[(int)offset + j] = remap[(int)cluster.indices[j]]; - } - offset += (nuint)cluster.indices.Length; + clusterCounts.Add((uint)cluster.indices.Count); + for (int j = 0; j < cluster.indices.Count; j++) + clusterIndices.Add(((uint*)remap.GetUnsafePtr())[(int)cluster.indices[j]]); + offset += (nuint)cluster.indices.Count; } - var clusterPart = new UnsafeList((nuint)pending.Length, Allocator.Temp); - clusterPart.Resize((nuint)pending.Length); + var clusterPart = new UnsafeList(pending.Count, Allocator.Temp); + clusterPart.Resize(pending.Count); nuint partitionCount = MeshOptApi.PartitionClusters( - clusterPart.GetUnsafePtr(), - clusterIndices.GetUnsafePtr(), + (uint*)clusterPart.GetUnsafePtr(), + (uint*)clusterIndices.GetUnsafePtr(), totalIndexCount, - clusterCounts.GetUnsafePtr(), - (nuint)pending.Length, + (uint*)clusterCounts.GetUnsafePtr(), + (nuint)pending.Count, config.partitionSpatial ? mesh.vertexPositions : null, - remap.Length, + (nuint)remap.Count, mesh.vertexPositionsStride, config.partitionSize ); - var partitions = new UnsafeList>(partitionCount, allocator); + var partitions = new UnsafeList>((int)partitionCount, allocator); for (nuint i = 0; i < partitionCount; i++) - { - partitions.Add(new UnsafeList((nuint)(config.partitionSize + config.partitionSize / 3), allocator)); - } + partitions.Add(new UnsafeList((int)(config.partitionSize + config.partitionSize / 3), allocator)); - for (int i = 0; i < pending.Length; i++) - { - partitions[(int)clusterPart[i]].Add(pending[i]); - } + for (int i = 0; i < pending.Count; i++) + partitions[(int)((uint*)clusterPart.GetUnsafePtr())[i]].Add(pending[i]); clusterIndices.Dispose(); clusterCounts.Dispose(); diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodMesh.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodMesh.cs index e552d89..3d6869b 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodMesh.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodMesh.cs @@ -1,6 +1,8 @@ using System; using Ghost.MeshOptimizer; -using Misaki.HighPerformance; +using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.LowLevel; +using Misaki.HighPerformance.LowLevel.Buffer; namespace Ghost.Graphics.Meshlet; diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs index 09e9f86..600d410 100644 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs +++ b/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs @@ -1,12 +1,13 @@ using System; using Ghost.MeshOptimizer; -using Misaki.HighPerformance; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; namespace Ghost.Graphics.Meshlet; internal static class ClodSimplify { - public static UnsafeList Simplify( + public static unsafe UnsafeList Simplify( ClodConfig config, ClodMesh mesh, UnsafeList indices, @@ -15,25 +16,22 @@ internal static class ClodSimplify float* error ) { - if (targetCount > (nuint)indices.Length) - { + if (targetCount >= (nuint)indices.Count) return indices; - } - // Use Allocator.Temp for LOD results to avoid stack overflow on mega-meshes - var lod = new UnsafeList((nuint)indices.Length, Allocator.Temp); - lod.Resize((nuint)indices.Length); + var lod = new UnsafeList(indices.Count, Allocator.Temp); + lod.Resize(indices.Count); - uint options = MeshOptApi.SimplifySparse | MeshOptApi.SimplifyErrorAbsolute; + uint options = (uint)(Api.meshopt_SimplifySparse | Api.meshopt_SimplifyErrorAbsolute); if (config.simplifyPermissive) - options |= MeshOptApi.SimplifyPermissive; + options |= (uint)Api.meshopt_SimplifyPermissive; if (config.simplifyRegularize) - options |= MeshOptApi.SimplifyRegularize; + options |= (uint)Api.meshopt_SimplifyRegularize; nuint resultSize = MeshOptApi.SimplifyWithAttributes( - lod.GetUnsafePtr(), - indices.GetUnsafePtr(), - (nuint)indices.Length, + (uint*)lod.GetUnsafePtr(), + (uint*)indices.GetUnsafePtr(), + (nuint)indices.Count, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride, @@ -41,23 +39,21 @@ internal static class ClodSimplify mesh.vertexAttributesStride, mesh.attributeWeights, mesh.attributeCount, - locks.GetUnsafePtr(), + (byte*)locks.GetUnsafePtr(), targetCount, float.MaxValue, options, error ); + lod.Resize((int)resultSize); - lod.Resize(resultSize); - - // Fallback to permissive if needed - if (lod.Length > targetCount && config.simplifyFallbackPermissive && !config.simplifyPermissive) + if ((nuint)lod.Count > targetCount && config.simplifyFallbackPermissive && !config.simplifyPermissive) { - options |= MeshOptApi.SimplifyPermissive; + options |= (uint)Api.meshopt_SimplifyPermissive; resultSize = MeshOptApi.SimplifyWithAttributes( - lod.GetUnsafePtr(), - indices.GetUnsafePtr(), - (nuint)indices.Length, + (uint*)lod.GetUnsafePtr(), + (uint*)indices.GetUnsafePtr(), + (nuint)indices.Count, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride, @@ -65,47 +61,43 @@ internal static class ClodSimplify mesh.vertexAttributesStride, mesh.attributeWeights, mesh.attributeCount, - locks.GetUnsafePtr(), + (byte*)locks.GetUnsafePtr(), targetCount, float.MaxValue, options, error ); - lod.Resize(resultSize); + lod.Resize((int)resultSize); } - // Sloppy fallback - if (lod.Length > targetCount && config.simplifyFallbackSloppy) + if ((nuint)lod.Count > targetCount && config.simplifyFallbackSloppy) { - SimplifyFallback(lod, mesh, indices, locks, targetCount, error); *error *= config.simplifyErrorFactorSloppy; } - // Edge limit check if (config.simplifyErrorEdgeLimit > 0) { float maxEdgeSq = 0; - for (int i = 0; i < (int)indices.Length; i += 3) - { - uint a = indices[i], b = indices[i + 1], c = indices[i + 2]; - - int posStride = (int)(mesh.vertexPositionsStride / sizeof(float)); - float* va = mesh.vertexPositions + (a * posStride); - float* vb = mesh.vertexPositions + (b * posStride); - float* vc = mesh.vertexPositions + (c * posStride); + uint* pIdx = (uint*)indices.GetUnsafePtr(); + int posStride = (int)(mesh.vertexPositionsStride / sizeof(float)); - float dx = va[0] - vb[0], dy = va[1] - vb[1], dz = va[2] - vb[2]; + for (int i = 0; i < indices.Count; i += 3) + { + uint a = pIdx[i], b = pIdx[i + 1], c = pIdx[i + 2]; + float* va = mesh.vertexPositions + (a * (uint)posStride); + float* vb = mesh.vertexPositions + (b * (uint)posStride); + float* vc = mesh.vertexPositions + (c * (uint)posStride); + + float dx, dy, dz; + dx = va[0] - vb[0]; dy = va[1] - vb[1]; dz = va[2] - vb[2]; float eab = dx * dx + dy * dy + dz * dz; - dx = va[0] - vc[0]; dy = va[1] - vc[1]; dz = va[2] - vc[2]; float eac = dx * dx + dy * dy + dz * dz; - dx = vb[0] - vc[0]; dy = vb[1] - vc[1]; dz = vb[2] - vc[2]; float ebc = dx * dx + dy * dy + dz * dz; float emax = Math.Max(Math.Max(eab, eac), ebc); float emin = Math.Min(Math.Min(eab, eac), ebc); - maxEdgeSq = Math.Max(maxEdgeSq, Math.Max(emin, emax / 4)); } @@ -114,16 +106,4 @@ internal static class ClodSimplify return lod; } - - private static void SimplifyFallback( - UnsafeList lod, - ClodMesh mesh, - UnsafeList indices, - UnsafeList locks, - nuint targetCount, - float* error - ) - { - // Placeholder for sloppy simplification fallback logic - } }