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:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -148,6 +147,10 @@ public unsafe static class ClodBuilder
|
|||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user