refactor: optimize memory for mega-meshes and fix collection usage

- Switch large/dynamic collections from StackScope to Allocator.Temp/Persistent to prevent stack overflow
- Remove redundant Resize calls; use capacity in constructor + Add or AsSpan().Fill for initialization
- Correctly propagate Allocator parameter for returned collections
- Ensure all temporary collections are properly disposed before returning
- Refine ClodBuilder, ClodPartition, ClodBoundsHelper, and ClodSimplify for high-scale mesh processing
This commit is contained in:
2026-03-17 02:34:42 +00:00
parent 22fdae1061
commit 0a3502b858
4 changed files with 45 additions and 36 deletions

View File

@@ -20,8 +20,8 @@ internal static class ClodBoundsHelper
public static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group) public static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
{ {
using var scope = AllocationManager.CreateStackScope(); // Use Temp for the bounds list to support mega-meshes without stack overflow
var boundsList = new UnsafeList<ClodBounds>((nuint)group.Length, scope.AllocationHandle); var boundsList = new UnsafeList<ClodBounds>((nuint)group.Length, Allocator.Temp);
for (int j = 0; j < (int)group.Length; j++) for (int j = 0; j < (int)group.Length; j++)
{ {
@@ -46,6 +46,7 @@ internal static class ClodBoundsHelper
result.error = Math.Max(result.error, clusters[group[j]].bounds.error); result.error = Math.Max(result.error, clusters[group[j]].bounds.error);
} }
boundsList.Dispose();
return result; return result;
} }
} }

View File

@@ -21,16 +21,12 @@ public unsafe static class ClodBuilder
{ {
Debug.Assert(mesh.vertexAttributesStride % (nuint)sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)"); Debug.Assert(mesh.vertexAttributesStride % (nuint)sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
using var scope = AllocationManager.CreateStackScope(); // Use Persistent or Temp for large mesh data to avoid stack overflow
var locks = new UnsafeList<byte>(mesh.vertexCount, Allocator.Temp);
var locks = new UnsafeList<byte>(mesh.vertexCount, scope.AllocationHandle); locks.AsSpan().Fill(0);
locks.Resize(mesh.vertexCount);
for (int i = 0; i < (int)mesh.vertexCount; i++)
locks[i] = 0;
// Generate position-only remap // Generate position-only remap
var remap = new UnsafeList<uint>(mesh.vertexCount, scope.AllocationHandle); var remap = new UnsafeList<uint>(mesh.vertexCount, Allocator.Temp);
remap.Resize(mesh.vertexCount);
MeshOptApi.GeneratePositionRemap(remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); MeshOptApi.GeneratePositionRemap(remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
// Set up protect bits on UV seams // Set up protect bits on UV seams
@@ -62,16 +58,16 @@ public unsafe static class ClodBuilder
clusters[i].bounds = ClodBoundsHelper.ComputeBounds(mesh, clusters[i].indices, 0.0f); clusters[i].bounds = ClodBoundsHelper.ComputeBounds(mesh, clusters[i].indices, 0.0f);
} }
var pending = new UnsafeList<int>(clusters.Length, scope.AllocationHandle); var pending = new UnsafeList<int>(clusters.Length, Allocator.Temp);
pending.Resize(clusters.Length);
for (int i = 0; i < (int)clusters.Length; i++) for (int i = 0; i < (int)clusters.Length; i++)
pending[i] = i; pending.Add(i);
int depth = 0; int depth = 0;
while (pending.Length > 1) while (pending.Length > 1)
{ {
var groups = ClodPartition.Partition(config, mesh, clusters, pending, remap, scope.AllocationHandle); // Partition results are temporary but returned, using Temp allocator
var groups = ClodPartition.Partition(config, mesh, clusters, pending, remap, Allocator.Temp);
pending.Clear(); pending.Clear();
@@ -80,7 +76,8 @@ public unsafe static class ClodBuilder
for (int i = 0; i < (int)groups.Length; i++) for (int i = 0; i < (int)groups.Length; i++)
{ {
var merged = new UnsafeList<uint>((nuint)groups[i].Length * config.maxTriangles * 3, scope.AllocationHandle); // Merged indices for a group of clusters
var merged = new UnsafeList<uint>((nuint)groups[i].Length * config.maxTriangles * 3, Allocator.Temp);
for (int j = 0; j < (int)groups[i].Length; j++) for (int j = 0; j < (int)groups[i].Length; j++)
{ {
var clusterIndices = clusters[groups[i][j]].indices; var clusterIndices = clusters[groups[i][j]].indices;
@@ -99,6 +96,7 @@ public unsafe static class ClodBuilder
{ {
bounds.error = float.MaxValue; bounds.error = float.MaxValue;
OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
merged.Dispose();
continue; continue;
} }
@@ -123,6 +121,7 @@ public unsafe static class ClodBuilder
} }
split.Dispose(); split.Dispose();
merged.Dispose();
} }
// Cleanup groups // Cleanup groups
@@ -147,6 +146,10 @@ public unsafe static class ClodBuilder
for (int i = 0; i < (int)clusters.Length; i++) for (int i = 0; i < (int)clusters.Length; i++)
clusters[i].indices.Dispose(); clusters[i].indices.Dispose();
clusters.Dispose(); clusters.Dispose();
locks.Dispose();
remap.Dispose();
pending.Dispose();
return finalClusterCount; return finalClusterCount;
} }
@@ -162,22 +165,23 @@ public unsafe static class ClodBuilder
ClodOutputDelegate outputCallback ClodOutputDelegate outputCallback
) )
{ {
using var scope = AllocationManager.CreateStackScope(); // Use Temp for the output array to avoid stack pressure
var groupClusters = new UnsafeList<ClodCluster>(group.Length, scope.AllocationHandle); var groupClusters = new UnsafeList<ClodCluster>(group.Length, Allocator.Temp);
groupClusters.Resize((nuint)group.Length);
for (int i = 0; i < (int)group.Length; i++) for (int i = 0; i < (int)group.Length; i++)
{ {
ref var srcCluster = ref clusters[group[i]]; ref var srcCluster = ref clusters[group[i]];
ref var dstCluster = ref groupClusters[i]; var dstCluster = new ClodCluster
{
dstCluster.refined = srcCluster.refined; refined = srcCluster.refined,
dstCluster.bounds = (config.optimizeBounds && srcCluster.refined != -1) bounds = (config.optimizeBounds && srcCluster.refined != -1)
? ClodBoundsHelper.ComputeBounds(mesh, srcCluster.indices, srcCluster.bounds.error) ? ClodBoundsHelper.ComputeBounds(mesh, srcCluster.indices, srcCluster.bounds.error)
: srcCluster.bounds; : srcCluster.bounds,
dstCluster.indices = srcCluster.indices.GetUnsafePtr(); indices = srcCluster.indices.GetUnsafePtr(),
dstCluster.indexCount = (nuint)srcCluster.indices.Length; indexCount = (nuint)srcCluster.indices.Length,
dstCluster.vertexCount = srcCluster.vertices; vertexCount = srcCluster.vertices
};
groupClusters.Add(dstCluster);
} }
var clodGroup = new ClodGroup { depth = depth, simplified = simplified }; var clodGroup = new ClodGroup { depth = depth, simplified = simplified };
@@ -185,6 +189,7 @@ public unsafe static class ClodBuilder
? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Length) ? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Length)
: -1; : -1;
groupClusters.Dispose();
return result; return result;
} }
} }

View File

@@ -6,7 +6,7 @@ namespace Ghost.Graphics.Meshlet;
internal static class ClodPartition internal static class ClodPartition
{ {
public static UnsafeList<UnsafeList<int>> Partition(ClodConfig config, ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeList<uint> remap, AllocationHandle allocator) public static UnsafeList<UnsafeList<int>> Partition(ClodConfig config, ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeList<uint> remap, Allocator allocator)
{ {
if (pending.Length <= (int)config.partitionSize) if (pending.Length <= (int)config.partitionSize)
{ {
@@ -15,9 +15,9 @@ internal static class ClodPartition
return partitions; return partitions;
} }
using var stackScope = AllocationManager.CreateStackScope(); // Internal counters/indices can stay on Temp to avoid stack overflow for huge meshes
var clusterIndices = new UnsafeList<uint>(1024, stackScope.AllocationHandle); var clusterIndices = new UnsafeList<uint>(1024, Allocator.Temp);
var clusterCounts = new UnsafeList<uint>((nuint)pending.Length, stackScope.AllocationHandle); var clusterCounts = new UnsafeList<uint>((nuint)pending.Length, Allocator.Temp);
nuint totalIndexCount = 0; nuint totalIndexCount = 0;
for (int i = 0; i < pending.Length; i++) for (int i = 0; i < pending.Length; i++)
@@ -41,7 +41,7 @@ internal static class ClodPartition
offset += (nuint)cluster.indices.Length; offset += (nuint)cluster.indices.Length;
} }
var clusterPart = new UnsafeList<uint>((nuint)pending.Length, stackScope.AllocationHandle); var clusterPart = new UnsafeList<uint>((nuint)pending.Length, Allocator.Temp);
clusterPart.Resize((nuint)pending.Length); clusterPart.Resize((nuint)pending.Length);
nuint partitionCount = MeshOptApi.PartitionClusters( nuint partitionCount = MeshOptApi.PartitionClusters(
@@ -67,6 +67,10 @@ internal static class ClodPartition
partitions[(int)clusterPart[i]].Add(pending[i]); partitions[(int)clusterPart[i]].Add(pending[i]);
} }
clusterIndices.Dispose();
clusterCounts.Dispose();
clusterPart.Dispose();
return partitions; return partitions;
} }
} }

View File

@@ -20,8 +20,8 @@ internal static class ClodSimplify
return indices; return indices;
} }
using var scope = AllocationManager.CreateStackScope(); // Use Allocator.Temp for LOD results to avoid stack overflow on mega-meshes
var lod = new UnsafeList<uint>(indices.Length, scope.AllocationHandle); var lod = new UnsafeList<uint>((nuint)indices.Length, Allocator.Temp);
lod.Resize((nuint)indices.Length); lod.Resize((nuint)indices.Length);
uint options = MeshOptApi.SimplifySparse | MeshOptApi.SimplifyErrorAbsolute; uint options = MeshOptApi.SimplifySparse | MeshOptApi.SimplifyErrorAbsolute;
@@ -124,7 +124,6 @@ internal static class ClodSimplify
float* error float* error
) )
{ {
// Simplified version - deindex and use sloppy simplification // Placeholder for sloppy simplification fallback logic
// Implementation details would involve creating a subset for sparse simplification
} }
} }