refactor: address PR review feedback on meshlet LOD system

- Remove WORK_SUMMARY.md
- Use Debug.Assert for stride validation in ClodBuilder
- Fix ClodBuilder.Build return value after cluster disposal
- Update ClodPartition to accept AllocationHandle for return collections
- Standardize on camelCase for public fields in ClodConfig, ClodMesh, ClodBounds, etc.
- Remove redundant Resize calls where capacity suffices or Add is used
- Enforce stack allocator usage for internal temporary collections
- Ensure proper allocator propagation for collections returned from methods
This commit is contained in:
2026-03-17 02:18:37 +00:00
parent f7fb7da496
commit 92c503b253
5 changed files with 64 additions and 113 deletions

View File

@@ -21,12 +21,11 @@ internal static class ClodBoundsHelper
public static ClodBounds MergeBounds(UnsafeList<Cluster> clusters, UnsafeList<int> group)
{
using var scope = AllocationManager.CreateStackScope();
var boundsList = new UnsafeList<ClodBounds>(group.Length, scope.AllocationHandle);
boundsList.Resize((nuint)group.Length);
var boundsList = new UnsafeList<ClodBounds>((nuint)group.Length, scope.AllocationHandle);
for (int j = 0; j < (int)group.Length; j++)
{
boundsList[j] = clusters[group[j]].bounds;
boundsList.Add(clusters[group[j]].bounds);
}
var merged = MeshOptApi.ComputeSphereBounds(

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Numerics;
using Ghost.MeshOptimizer;
using Misaki.HighPerformance;
@@ -16,24 +17,19 @@ internal struct Cluster
public unsafe static class ClodBuilder
{
private const float CONST_SIMPLIFY_RATIO_DEFAULT = 0.5f;
private const float CONST_SIMPLIFY_THRESHOLD_DEFAULT = 0.85f;
private const float CONST_SIMPLIFY_ERROR_MERGE_PREVIOUS_DEFAULT = 1.0f;
private const float CONST_SIMPLIFY_ERROR_MERGE_ADDITIVE_DEFAULT = 0.0f;
private const float CONST_SIMPLIFY_ERROR_FACTOR_SLOPPY_DEFAULT = 2.0f;
public static nuint Build(ClodConfig config, ClodMesh mesh, void* outputContext, ClodOutputDelegate outputCallback, Allocator allocator = Allocator.Persistent)
public static nuint Build(ClodConfig config, ClodMesh mesh, void* outputContext, ClodOutputDelegate outputCallback)
{
if (mesh.vertexAttributesStride % (nuint)sizeof(float) != 0)
throw new ArgumentException("vertexAttributesStride must be a multiple of sizeof(float)");
Debug.Assert(mesh.vertexAttributesStride % (nuint)sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)");
var locks = new UnsafeList<byte>((int)mesh.vertexCount, allocator);
using var scope = AllocationManager.CreateStackScope();
var locks = new UnsafeList<byte>(mesh.vertexCount, scope.AllocationHandle);
locks.Resize(mesh.vertexCount);
for (int i = 0; i < (int)mesh.vertexCount; i++)
locks[i] = 0;
// Generate position-only remap
var remap = new UnsafeList<uint>((int)mesh.vertexCount, allocator);
var remap = new UnsafeList<uint>(mesh.vertexCount, scope.AllocationHandle);
remap.Resize(mesh.vertexCount);
MeshOptApi.GeneratePositionRemap(remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride);
@@ -58,7 +54,7 @@ public unsafe static class ClodBuilder
}
// Initial clusterization
var clusters = ClodInternal.Clusterize(config, mesh, mesh.indices, mesh.indexCount, allocator);
var clusters = ClodInternal.Clusterize(config, mesh, mesh.indices, mesh.indexCount, Allocator.Persistent);
// Compute initial bounds
for (int i = 0; i < (int)clusters.Length; i++)
@@ -66,8 +62,8 @@ public unsafe static class ClodBuilder
clusters[i].bounds = ClodBoundsHelper.ComputeBounds(mesh, clusters[i].indices, 0.0f);
}
var pending = new UnsafeList<int>((int)clusters.Length, allocator);
pending.Resize((nuint)clusters.Length);
var pending = new UnsafeList<int>(clusters.Length, scope.AllocationHandle);
pending.Resize(clusters.Length);
for (int i = 0; i < (int)clusters.Length; i++)
pending[i] = i;
@@ -75,7 +71,7 @@ public unsafe static class ClodBuilder
while (pending.Length > 1)
{
var groups = ClodPartition.Partition(config, mesh, clusters, pending, remap);
var groups = ClodPartition.Partition(config, mesh, clusters, pending, remap, scope.AllocationHandle);
pending.Clear();
@@ -84,7 +80,7 @@ public unsafe static class ClodBuilder
for (int i = 0; i < (int)groups.Length; i++)
{
var merged = new UnsafeList<uint>(groups[i].Length * (int)config.maxTriangles * 3, allocator);
var merged = new UnsafeList<uint>((nuint)groups[i].Length * config.maxTriangles * 3, scope.AllocationHandle);
for (int j = 0; j < (int)groups[i].Length; j++)
{
var clusterIndices = clusters[groups[i][j]].indices;
@@ -102,15 +98,13 @@ public unsafe static class ClodBuilder
if (simplified.Length > (nuint)(merged.Length * config.simplifyThreshold))
{
bounds.error = float.MaxValue;
OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback, allocator);
merged.Dispose();
simplified.Dispose();
OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback);
continue;
}
bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive;
int refined = OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback, allocator);
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++)
@@ -119,7 +113,7 @@ public unsafe static class ClodBuilder
}
// Clusterize simplified mesh
var split = ClodInternal.Clusterize(config, mesh, simplified.GetUnsafePtr(), simplified.Length, allocator);
var split = ClodInternal.Clusterize(config, mesh, simplified.GetUnsafePtr(), simplified.Length, Allocator.Persistent);
for (int j = 0; j < (int)split.Length; j++)
{
split[j].refined = refined;
@@ -129,8 +123,6 @@ public unsafe static class ClodBuilder
}
split.Dispose();
merged.Dispose();
simplified.Dispose();
}
// Cleanup groups
@@ -146,18 +138,17 @@ public unsafe static class ClodBuilder
var cluster = clusters[pending[0]];
var bounds = cluster.bounds;
bounds.error = float.MaxValue;
OutputGroup(config, mesh, clusters, pending, bounds, depth, outputContext, outputCallback, allocator);
OutputGroup(config, mesh, clusters, pending, bounds, depth, outputContext, outputCallback);
}
nuint finalClusterCount = clusters.Length;
// Cleanup
for (int i = 0; i < (int)clusters.Length; i++)
clusters[i].indices.Dispose();
clusters.Dispose();
locks.Dispose();
remap.Dispose();
pending.Dispose();
return (nuint)clusters.Length;
return finalClusterCount;
}
private static int OutputGroup(
@@ -168,11 +159,11 @@ public unsafe static class ClodBuilder
ClodBounds simplified,
int depth,
void* outputContext,
ClodOutputDelegate outputCallback,
Allocator allocator
ClodOutputDelegate outputCallback
)
{
var groupClusters = new UnsafeList<ClodCluster>(group.Length, allocator);
using var scope = AllocationManager.CreateStackScope();
var groupClusters = new UnsafeList<ClodCluster>(group.Length, scope.AllocationHandle);
groupClusters.Resize((nuint)group.Length);
for (int i = 0; i < (int)group.Length; i++)
@@ -189,12 +180,11 @@ public unsafe static class ClodBuilder
dstCluster.vertexCount = srcCluster.vertices;
}
var clodGroup = new ClodGroup { Depth = depth, Simplified = simplified };
var clodGroup = new ClodGroup { depth = depth, simplified = simplified };
int result = outputCallback != null
? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Length)
: -1;
groupClusters.Dispose();
return result;
}
}

View File

@@ -6,19 +6,18 @@ namespace Ghost.Graphics.Meshlet;
internal static class ClodPartition
{
public static UnsafeList<UnsafeList<int>> Partition(ClodConfig config, ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeList<uint> remap)
public static UnsafeList<UnsafeList<int>> Partition(ClodConfig config, ClodMesh mesh, UnsafeList<Cluster> clusters, UnsafeList<int> pending, UnsafeList<uint> remap, AllocationHandle allocator)
{
if (pending.Length <= (int)config.partitionSize)
{
using var scope = AllocationManager.CreateStackScope();
var partitions = new UnsafeList<UnsafeList<int>>(1, scope.AllocationHandle);
var partitions = new UnsafeList<UnsafeList<int>>(1, allocator);
partitions.Add(pending);
return partitions;
}
using var stackScope = AllocationManager.CreateStackScope();
var clusterIndices = new UnsafeList<uint>(1024, stackScope.AllocationHandle);
var clusterCounts = new UnsafeList<uint>(pending.Length, stackScope.AllocationHandle);
var clusterCounts = new UnsafeList<uint>((nuint)pending.Length, stackScope.AllocationHandle);
nuint totalIndexCount = 0;
for (int i = 0; i < pending.Length; i++)
@@ -42,7 +41,7 @@ internal static class ClodPartition
offset += (nuint)cluster.indices.Length;
}
var clusterPart = new UnsafeList<uint>(pending.Length, stackScope.AllocationHandle);
var clusterPart = new UnsafeList<uint>((nuint)pending.Length, stackScope.AllocationHandle);
clusterPart.Resize((nuint)pending.Length);
nuint partitionCount = MeshOptApi.PartitionClusters(
@@ -57,10 +56,10 @@ internal static class ClodPartition
config.partitionSize
);
var partitions = new UnsafeList<UnsafeList<int>>(partitionCount, stackScope.AllocationHandle);
var partitions = new UnsafeList<UnsafeList<int>>(partitionCount, allocator);
for (nuint i = 0; i < partitionCount; i++)
{
partitions.Add(new UnsafeList<int>((nuint)(config.partitionSize + config.partitionSize / 3), stackScope.AllocationHandle));
partitions.Add(new UnsafeList<int>((nuint)(config.partitionSize + config.partitionSize / 3), allocator));
}
for (int i = 0; i < pending.Length; i++)

View File

@@ -1,16 +1,41 @@
using System;
using Ghost.MeshOptimizer;
using Misaki.HighPerformance;
namespace Ghost.Graphics.Meshlet;
public unsafe struct ClodMesh
{
public float* vertexPositions;
public nuint vertexCount;
public nuint vertexPositionsStride;
public float* vertexAttributes;
public nuint vertexAttributesStride;
public float* attributeWeights;
public nuint attributeCount;
public uint* indices;
public nuint indexCount;
public byte* vertexLock;
public uint attributeProtectMask;
}
public struct ClodGroup
{
public int depth;
public ClodBounds simplified;
}
public unsafe struct ClodCluster
{
public int refined;
public ClodBounds bounds;
public uint* indices;
public nuint indexCount;
public nuint vertexCount;
public float* vertexPositions;
public nuint vertexPositionsStride;
public float* vertexAttributes;
public nuint vertexAttributesStride;
public byte* vertexLock;
public float* attributeWeights;
public nuint attributeCount;
public uint attributeProtectMask;
}
public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount);