diff --git a/src/Runtime/Ghost.Core/Ghost.Core.csproj b/src/Runtime/Ghost.Core/Ghost.Core.csproj index 5111c8c..7ec9104 100644 --- a/src/Runtime/Ghost.Core/Ghost.Core.csproj +++ b/src/Runtime/Ghost.Core/Ghost.Core.csproj @@ -21,8 +21,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodBounds.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodBounds.cs deleted file mode 100644 index 47e71c2..0000000 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodBounds.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Numerics; - -namespace Ghost.Graphics.Meshlet; - -/// -/// Represents the bounding sphere and simplification error for a LOD cluster. -/// -public struct ClodBounds -{ - /// The center of the bounding sphere. - public Vector3 center; - /// The radius of the bounding sphere. - public float radius; - /// The simplification error associated with this LOD level. - public float error; -} diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs deleted file mode 100644 index f1edd8e..0000000 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodBoundsHelper.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Numerics; -using Ghost.MeshOptimizer; -using Misaki.HighPerformance.LowLevel.Buffer; -using Misaki.HighPerformance.LowLevel.Collections; - -namespace Ghost.Graphics.Meshlet; - -internal static class ClodBoundsHelper -{ - public static unsafe ClodBounds ComputeBounds(ClodMesh mesh, UnsafeList indices, float error) - { - 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 unsafe ClodBounds MergeBounds(UnsafeList clusters, UnsafeList group) - { - 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.Count, - (nuint)sizeof(ClodBounds), - (float*)boundsList.GetUnsafePtr() + 3, - (nuint)sizeof(ClodBounds) - ); - - float maxError = 0.0f; - for (int j = 0; j < group.Count; j++) - maxError = Math.Max(maxError, clusters[group[j]].bounds.error); - - boundsList.Dispose(); - 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 deleted file mode 100644 index 7962f63..0000000 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodBuilder.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System; -using System.Diagnostics; -using Ghost.MeshOptimizer; -using Misaki.HighPerformance.LowLevel.Buffer; -using Misaki.HighPerformance.LowLevel.Collections; - -namespace Ghost.Graphics.Meshlet; - -internal struct Cluster -{ - public nuint vertices; - public UnsafeList indices; - public int group; - public int refined; - public ClodBounds bounds; -} - -/// -/// Provides methods to build a hierarchical Cluster LOD mesh. -/// -public unsafe static class ClodBuilder -{ - /// - /// Builds a cluster LOD hierarchy from the input mesh. - /// - /// The configuration parameters for the LOD building process. - /// The input mesh data. - /// Optional context pointer passed to the output callback. - /// Delegate invoked for each generated LOD group. - /// The total count of generated clusters. - public static nuint Build(ClodConfig config, ClodMesh mesh, void* outputContext, ClodOutputDelegate outputCallback) - { - Debug.Assert(mesh.vertexAttributesStride % (nuint)sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)"); - - var locks = new UnsafeList((int)mesh.vertexCount, Allocator.Temp); - locks.AsSpan().Fill(0); - - 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); - - if (mesh.attributeProtectMask != 0) - { - nuint maxAttributes = mesh.vertexAttributesStride / sizeof(float); - for (nuint i = 0; i < mesh.vertexCount; 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]) - { - ((byte*)locks.GetUnsafePtr())[(int)i] |= (byte)(Api.meshopt_SimplifyVertex_Protect & 0xFF); - } - } - } - } - } - - var clusters = ClodInternal.Clusterize(config, mesh, mesh.indices, mesh.indexCount, Allocator.Persistent); - - for (int i = 0; i < clusters.Count; i++) - { - clusters[i].bounds = ClodBoundsHelper.ComputeBounds(mesh, clusters[i].indices, 0.0f); - } - - var pending = new UnsafeList(clusters.Count, Allocator.Temp); - for (int i = 0; i < clusters.Count; i++) - pending.Add(i); - - int depth = 0; - - while (pending.Count > 1) - { - var groups = ClodPartition.Partition(config, mesh, clusters, pending, remap, Allocator.Temp); - pending.Clear(); - - ClodBoundary.LockBoundary(locks, groups, clusters, remap, mesh.vertexLock); - - for (int i = 0; i < groups.Count; i++) - { - 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 < clusterIndices.Count; k++) - merged.Add(clusterIndices[k]); - } - - 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 ((nuint)simplified.Count > (nuint)(merged.Count * config.simplifyThreshold)) - { - bounds.error = float.MaxValue; - OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); - merged.Dispose(); - 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); - - for (int j = 0; j < groups[i].Count; j++) - clusters[groups[i][j]].indices.Dispose(); - - 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(clusters.Count - 1); - } - - split.Dispose(); - merged.Dispose(); - } - - for (int i = 0; i < groups.Count; i++) - groups[i].Dispose(); - groups.Dispose(); - - depth++; - } - - if (pending.Count > 0) - { - var bounds = clusters[pending[0]].bounds; - bounds.error = float.MaxValue; - OutputGroup(config, mesh, clusters, pending, bounds, depth, outputContext, outputCallback); - } - - nuint finalClusterCount = (nuint)clusters.Count; - - for (int i = 0; i < clusters.Count; i++) - clusters[i].indices.Dispose(); - clusters.Dispose(); - locks.Dispose(); - remap.Dispose(); - pending.Dispose(); - - return finalClusterCount; - } - - private static int OutputGroup( - ClodConfig config, - ClodMesh mesh, - UnsafeList clusters, - UnsafeList group, - ClodBounds simplified, - int depth, - void* outputContext, - ClodOutputDelegate outputCallback - ) - { - var groupClusters = new UnsafeList(group.Count, Allocator.Temp); - - for (int i = 0; i < group.Count; i++) - { - ref var srcCluster = ref clusters[group[i]]; - groupClusters.Add(new ClodCluster - { - refined = srcCluster.refined, - bounds = (config.optimizeBounds && srcCluster.refined != -1) - ? ClodBoundsHelper.ComputeBounds(mesh, srcCluster.indices, srcCluster.bounds.error) - : srcCluster.bounds, - indices = (uint*)srcCluster.indices.GetUnsafePtr(), - indexCount = (nuint)srcCluster.indices.Count, - vertexCount = srcCluster.vertices - }); - } - - var clodGroup = new ClodGroup { depth = depth, simplified = simplified }; - int result = outputCallback != null - ? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Count) - : -1; - - groupClusters.Dispose(); - return result; - } -} diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodConfig.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodConfig.cs deleted file mode 100644 index cfd0d97..0000000 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodConfig.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; - -namespace Ghost.Graphics.Meshlet; - -/// -/// Configuration parameters for the cluster LOD generation pipeline. -/// -public struct ClodConfig -{ - /// The maximum number of vertices per meshlet. - public nuint maxVertices; - /// The minimum number of triangles per meshlet. - public nuint minTriangles; - /// The maximum number of triangles per meshlet. - public nuint maxTriangles; - /// Whether to use spatial partitioning during meshlet building. - public bool partitionSpatial; - /// Whether to sort clusters after partitioning. - public bool partitionSort; - /// The target size for partitions. - public nuint partitionSize; - /// Whether to cluster meshlets using spatial clustering. - public bool clusterSpatial; - /// Weight factor for cluster fill calculation. - public float clusterFillWeight; - /// Split factor for flexible clustering. - public float clusterSplitFactor; - /// The simplification ratio to achieve per LOD level. - public float simplifyRatio; - /// Threshold for stopping simplification. - public float simplifyThreshold; - /// Error factor used when merging previous LOD level errors. - public float simplifyErrorMergePrevious; - /// Additive error factor when merging LOD levels. - public float simplifyErrorMergeAdditive; - /// Error factor for sloppy simplification. - public float simplifyErrorFactorSloppy; - /// Edge length limit error factor. - public float simplifyErrorEdgeLimit; - /// Whether to allow permissive simplification. - public bool simplifyPermissive; - /// Whether to fallback to permissive simplification. - public bool simplifyFallbackPermissive; - /// Whether to fallback to sloppy simplification. - public bool simplifyFallbackSloppy; - /// Whether to regularize the mesh during simplification. - public bool simplifyRegularize; - /// Whether to optimize cluster bounds. - public bool optimizeBounds; - /// Whether to optimize clusters post-build. - public bool optimizeClusters; -} diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs deleted file mode 100644 index 7184073..0000000 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using Ghost.MeshOptimizer; -using Misaki.HighPerformance.LowLevel.Buffer; -using Misaki.HighPerformance.LowLevel.Collections; - -namespace Ghost.Graphics.Meshlet; - -internal static class ClodInternal -{ - 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((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(); - - nuint meshletCount; - if (config.clusterSpatial) - { - meshletCount = pMeshlets[0].BuildsSpatial( - pMeshletVertices, pMeshletTriangles, - indices, indexCount, - mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride, - config.maxVertices, config.minTriangles, config.maxTriangles, - config.clusterFillWeight - ); - } - else - { - meshletCount = pMeshlets[0].BuildsFlex( - pMeshletVertices, pMeshletTriangles, - indices, indexCount, - mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride, - config.maxVertices, config.minTriangles, config.maxTriangles, - 0.0f, config.clusterSplitFactor - ); - } - - var clusters = new UnsafeList((int)meshletCount, allocator); - - for (nuint i = 0; i < meshletCount; i++) - { - ref var meshlet = ref pMeshlets[i]; - - if (config.optimizeClusters) - { - MeshOptApi.OptimizeMeshlet( - pMeshletVertices + meshlet.vertex_offset, - pMeshletTriangles + meshlet.triangle_offset, - meshlet.triangle_count, - meshlet.vertex_count - ); - } - - var cluster = new Cluster - { - vertices = meshlet.vertex_count, - indices = new UnsafeList((int)(meshlet.triangle_count * 3), allocator), - group = -1, - refined = -1 - }; - - 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); - } - - meshlets.Dispose(); - meshletVertices.Dispose(); - meshletTriangles.Dispose(); - - return clusters; - } -} diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Boundary.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Boundary.cs deleted file mode 100644 index 2715395..0000000 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Boundary.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Ghost.MeshOptimizer; -using Misaki.HighPerformance.LowLevel.Buffer; -using Misaki.HighPerformance.LowLevel.Collections; - -namespace Ghost.Graphics.Meshlet; - -internal static class ClodBoundary -{ - public static unsafe void LockBoundary(UnsafeList locks, UnsafeList> groups, UnsafeList clusters, UnsafeList remap, byte* vertexLock) - { - byte* pLocks = (byte*)locks.GetUnsafePtr(); - uint* pRemap = (uint*)remap.GetUnsafePtr(); - - 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 < groups[i].Count; j++) - { - var cluster = clusters[groups[i][j]]; - for (int k = 0; k < cluster.indices.Count; k++) - { - uint r = pRemap[(int)cluster.indices[k]]; - pLocks[r] |= (byte)(pLocks[r] >> 7); - } - } - - for (int j = 0; j < groups[i].Count; j++) - { - var cluster = clusters[groups[i][j]]; - for (int k = 0; k < cluster.indices.Count; k++) - { - uint r = pRemap[(int)cluster.indices[k]]; - pLocks[r] |= (byte)(1 << 7); - } - } - } - - for (int i = 0; i < locks.Count; i++) - { - uint r = pRemap[i]; - pLocks[i] = (byte)((pLocks[r] & 1) | (pLocks[i] & (byte)(Api.meshopt_SimplifyVertex_Protect & 0xFF))); - if (vertexLock != null) - pLocks[i] |= vertexLock[i]; - } - } -} diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs deleted file mode 100644 index b0423fe..0000000 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodInternal_Partition.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using Ghost.MeshOptimizer; -using Misaki.HighPerformance.LowLevel.Buffer; -using Misaki.HighPerformance.LowLevel.Collections; - -namespace Ghost.Graphics.Meshlet; - -internal static class ClodPartition -{ - public static unsafe UnsafeList> Partition(ClodConfig config, ClodMesh mesh, UnsafeList clusters, UnsafeList pending, UnsafeList remap, Allocator allocator) - { - if (pending.Count <= (int)config.partitionSize) - { - var single = new UnsafeList>(1, allocator); - single.Add(pending); - return single; - } - - nuint totalIndexCount = 0; - for (int i = 0; i < pending.Count; i++) - totalIndexCount += (nuint)clusters[pending[i]].indices.Count; - - 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.Count; i++) - { - var cluster = clusters[pending[i]]; - 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(pending.Count, Allocator.Temp); - clusterPart.Resize(pending.Count); - - nuint partitionCount = MeshOptApi.PartitionClusters( - (uint*)clusterPart.GetUnsafePtr(), - (uint*)clusterIndices.GetUnsafePtr(), - totalIndexCount, - (uint*)clusterCounts.GetUnsafePtr(), - (nuint)pending.Count, - config.partitionSpatial ? mesh.vertexPositions : null, - (nuint)remap.Count, - mesh.vertexPositionsStride, - config.partitionSize - ); - - var partitions = new UnsafeList>((int)partitionCount, allocator); - for (nuint i = 0; i < partitionCount; i++) - partitions.Add(new UnsafeList((int)(config.partitionSize + config.partitionSize / 3), allocator)); - - for (int i = 0; i < pending.Count; i++) - partitions[(int)((uint*)clusterPart.GetUnsafePtr())[i]].Add(pending[i]); - - clusterIndices.Dispose(); - clusterCounts.Dispose(); - clusterPart.Dispose(); - - return partitions; - } -} diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodMesh.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodMesh.cs deleted file mode 100644 index 7da62e5..0000000 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodMesh.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace Ghost.Graphics.Meshlet; - -/// -/// Contains input data for the Cluster LOD generation pipeline. -/// -public unsafe struct ClodMesh -{ - /// Pointer to vertex position data (float array). - public float* vertexPositions; - /// Number of vertices in the mesh. - public nuint vertexCount; - /// Stride in bytes for vertex position data. - public nuint vertexPositionsStride; - /// Pointer to vertex attribute data (float array). - public float* vertexAttributes; - /// Stride in bytes for vertex attribute data. - public nuint vertexAttributesStride; - /// Pointer to attribute weights for simplification. - public float* attributeWeights; - /// Number of vertex attributes. - public nuint attributeCount; - /// Pointer to index data. - public uint* indices; - /// Number of indices in the mesh. - public nuint indexCount; - /// Pointer to per-vertex lock flags (1 byte per vertex). - public byte* vertexLock; - /// Mask indicating which attributes are protected during simplification. - public uint attributeProtectMask; -} - -/// -/// Defines a group of clusters in the LOD hierarchy. -/// -public struct ClodGroup -{ - /// LOD hierarchy depth of this group. - public int depth; - /// Bounding information for the simplified group. - public ClodBounds simplified; -} - -/// -/// Represents a cluster of meshlets in the LOD hierarchy. -/// -public unsafe struct ClodCluster -{ - /// Refinement level of the cluster. - public int refined; - /// Bounding info for the cluster. - public ClodBounds bounds; - /// Pointer to indices for this cluster. - public uint* indices; - /// Number of indices. - public nuint indexCount; - /// Number of vertices in the cluster. - public nuint vertexCount; -} - -/// -/// Delegate type for processing generated LOD groups. -/// -public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount); diff --git a/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs b/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs deleted file mode 100644 index 600d410..0000000 --- a/src/Runtime/Ghost.Graphics/Meshlet/ClodSimplify.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using Ghost.MeshOptimizer; -using Misaki.HighPerformance.LowLevel.Buffer; -using Misaki.HighPerformance.LowLevel.Collections; - -namespace Ghost.Graphics.Meshlet; - -internal static class ClodSimplify -{ - public static unsafe UnsafeList Simplify( - ClodConfig config, - ClodMesh mesh, - UnsafeList indices, - UnsafeList locks, - nuint targetCount, - float* error - ) - { - if (targetCount >= (nuint)indices.Count) - return indices; - - var lod = new UnsafeList(indices.Count, Allocator.Temp); - lod.Resize(indices.Count); - - uint options = (uint)(Api.meshopt_SimplifySparse | Api.meshopt_SimplifyErrorAbsolute); - if (config.simplifyPermissive) - options |= (uint)Api.meshopt_SimplifyPermissive; - if (config.simplifyRegularize) - options |= (uint)Api.meshopt_SimplifyRegularize; - - nuint resultSize = MeshOptApi.SimplifyWithAttributes( - (uint*)lod.GetUnsafePtr(), - (uint*)indices.GetUnsafePtr(), - (nuint)indices.Count, - mesh.vertexPositions, - mesh.vertexCount, - mesh.vertexPositionsStride, - mesh.vertexAttributes, - mesh.vertexAttributesStride, - mesh.attributeWeights, - mesh.attributeCount, - (byte*)locks.GetUnsafePtr(), - targetCount, - float.MaxValue, - options, - error - ); - lod.Resize((int)resultSize); - - if ((nuint)lod.Count > targetCount && config.simplifyFallbackPermissive && !config.simplifyPermissive) - { - options |= (uint)Api.meshopt_SimplifyPermissive; - resultSize = MeshOptApi.SimplifyWithAttributes( - (uint*)lod.GetUnsafePtr(), - (uint*)indices.GetUnsafePtr(), - (nuint)indices.Count, - mesh.vertexPositions, - mesh.vertexCount, - mesh.vertexPositionsStride, - mesh.vertexAttributes, - mesh.vertexAttributesStride, - mesh.attributeWeights, - mesh.attributeCount, - (byte*)locks.GetUnsafePtr(), - targetCount, - float.MaxValue, - options, - error - ); - lod.Resize((int)resultSize); - } - - if ((nuint)lod.Count > targetCount && config.simplifyFallbackSloppy) - { - *error *= config.simplifyErrorFactorSloppy; - } - - if (config.simplifyErrorEdgeLimit > 0) - { - float maxEdgeSq = 0; - uint* pIdx = (uint*)indices.GetUnsafePtr(); - int posStride = (int)(mesh.vertexPositionsStride / sizeof(float)); - - 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)); - } - - *error = Math.Min(*error, (float)Math.Sqrt(maxEdgeSq) * config.simplifyErrorEdgeLimit); - } - - return lod; - } -} diff --git a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs index eea254a..4fc63b0 100644 --- a/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs +++ b/src/Runtime/Ghost.Graphics/RenderGraphModule/RenderGraphResourcePool.cs @@ -129,7 +129,9 @@ internal sealed class RenderGraphResourceRegistry for (var i = 0; i < _resources.Count; i++) { if (_resources[i].type == RenderGraphResourceType.Texture) + { count++; + } } return count; } @@ -142,7 +144,9 @@ internal sealed class RenderGraphResourceRegistry for (var i = 0; i < _resources.Count; i++) { if (_resources[i].type == RenderGraphResourceType.Buffer) + { count++; + } } return count; } @@ -290,7 +294,9 @@ internal sealed class RenderGraphResourceRegistry { var res = _resources[i]; if (res.type != RenderGraphResourceType.Texture || res.isImported) + { continue; + } var desc = res.rgTextureDesc; if (desc.sizeMode == RGTextureSizeMode.Absolute) diff --git a/src/Runtime/Ghost.Graphics/Utilities/MeshletUtility.cs b/src/Runtime/Ghost.Graphics/Utilities/MeshletUtility.cs new file mode 100644 index 0000000..d39bb2b --- /dev/null +++ b/src/Runtime/Ghost.Graphics/Utilities/MeshletUtility.cs @@ -0,0 +1,623 @@ +using Ghost.MeshOptimizer; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.LowLevel.Utilities; +using Misaki.HighPerformance.Mathematics; +using System.Diagnostics; + +namespace Ghost.Graphics.Utilities; + +internal struct Cluster : IDisposable +{ + public UnsafeList indices; + public ClodBounds bounds; + public nuint vertices; + public int group; + public int refined; + + public void Dispose() + { + indices.Dispose(); + } +} + +/// +/// Represents the bounding sphere and simplification error for a LOD cluster. +/// +public struct ClodBounds +{ + /// The center of the bounding sphere. + public float3 center; + /// The radius of the bounding sphere. + public float radius; + /// The simplification error associated with this LOD level. + public float error; +} + +/// +/// Configuration parameters for the cluster LOD generation pipeline. +/// +public struct ClodConfig +{ + /// The maximum number of vertices per meshlet. + public nuint maxVertices; + /// The minimum number of triangles per meshlet. + public nuint minTriangles; + /// The maximum number of triangles per meshlet. + public nuint maxTriangles; + /// Whether to use spatial partitioning during meshlet building. + public bool partitionSpatial; + /// Whether to sort clusters after partitioning. + public bool partitionSort; + /// The target size for partitions. + public nuint partitionSize; + /// Whether to cluster meshlets using spatial clustering. + public bool clusterSpatial; + /// Weight factor for cluster fill calculation. + public float clusterFillWeight; + /// Split factor for flexible clustering. + public float clusterSplitFactor; + /// The simplification ratio to achieve per LOD level. + public float simplifyRatio; + /// Threshold for stopping simplification. + public float simplifyThreshold; + /// Error factor used when merging previous LOD level errors. + public float simplifyErrorMergePrevious; + /// Additive error factor when merging LOD levels. + public float simplifyErrorMergeAdditive; + /// Error factor for sloppy simplification. + public float simplifyErrorFactorSloppy; + /// Edge length limit error factor. + public float simplifyErrorEdgeLimit; + /// Whether to allow permissive simplification. + public bool simplifyPermissive; + /// Whether to fallback to permissive simplification. + public bool simplifyFallbackPermissive; + /// Whether to fallback to sloppy simplification. + public bool simplifyFallbackSloppy; + /// Whether to regularize the mesh during simplification. + public bool simplifyRegularize; + /// Whether to optimize cluster bounds. + public bool optimizeBounds; + /// Whether to optimize clusters post-build. + public bool optimizeClusters; +} + +/// +/// Contains input data for the Cluster LOD generation pipeline. +/// +public unsafe struct ClodMesh +{ + /// Pointer to vertex position data (float array). + public float* vertexPositions; + /// Number of vertices in the mesh. + public nuint vertexCount; + /// Stride in bytes for vertex position data. + public nuint vertexPositionsStride; + /// Pointer to vertex attribute data (float array). + public float* vertexAttributes; + /// Stride in bytes for vertex attribute data. + public nuint vertexAttributesStride; + /// Pointer to attribute weights for simplification. + public float* attributeWeights; + /// Number of vertex attributes. + public nuint attributeCount; + /// Pointer to index data. + public uint* indices; + /// Number of indices in the mesh. + public nuint indexCount; + /// Pointer to per-vertex lock flags (1 byte per vertex). + public byte* vertexLock; + /// Mask indicating which attributes are protected during simplification. + public uint attributeProtectMask; +} + +/// +/// Defines a group of clusters in the LOD hierarchy. +/// +public struct ClodGroup +{ + /// LOD hierarchy depth of this group. + public int depth; + /// Bounding information for the simplified group. + public ClodBounds simplified; +} + +/// +/// Represents a cluster of meshlets in the LOD hierarchy. +/// +public unsafe struct ClodCluster +{ + /// Refinement level of the cluster. + public int refined; + /// Bounding info for the cluster. + public ClodBounds bounds; + /// Pointer to indices for this cluster. + public uint* indices; + /// Number of indices. + public nuint indexCount; + /// Number of vertices in the cluster. + public nuint vertexCount; +} + +/// +/// Delegate type for processing generated LOD groups. +/// +public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ClodCluster* clusters, nuint clusterCount); + +// FIX: UnsafeList and UnsafeArray are not same as std::vector. + +public static unsafe class MeshletUtility +{ + private static ClodBounds ComputeBounds(ClodMesh mesh, UnsafeList indices, float error) + { + var bounds = MeshOptApi.ComputeClusterBounds((uint*)indices.GetUnsafePtr(), (nuint)indices.Count, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); + return new ClodBounds + { + center = new float3(bounds.center[0], bounds.center[1], bounds.center[2]), + radius = bounds.radius, + error = error + }; + } + + private static ClodBounds MergeBounds(UnsafeList clusters, UnsafeList group) + { + using var boundsList = new UnsafeArray(group.Count, Allocator.FreeList); + for (var j = 0; j < group.Count; j++) + { + boundsList[j] = (clusters[group[j]].bounds); + } + + var merged = MeshOptApi.ComputeSphereBounds( + (float*)boundsList.GetUnsafePtr(), + (nuint)group.Count, + (nuint)sizeof(ClodBounds), + (float*)boundsList.GetUnsafePtr() + 3, + (nuint)sizeof(ClodBounds) + ); + + var maxError = 0.0f; + for (var j = 0; j < group.Count; j++) + { + maxError = Math.Max(maxError, clusters[group[j]].bounds.error); + } + + return new ClodBounds + { + center = new float3(merged.center[0], merged.center[1], merged.center[2]), + radius = merged.radius, + error = maxError + }; + } + + private static UnsafeList Clusterize(ClodConfig config, ClodMesh mesh, uint* indices, nuint indexCount, Allocator allocator) + { + var maxMeshlets = MeshOptApi.BuildMeshletsBound(indexCount, config.maxVertices, config.minTriangles); + + using var meshlets = new UnsafeArray((int)maxMeshlets, Allocator.FreeList); + using var meshletVertices = new UnsafeArray((int)indexCount, Allocator.FreeList); + using var meshletTriangles = new UnsafeArray((int)indexCount, Allocator.FreeList); + + var pMeshlets = (meshopt_Meshlet*)meshlets.GetUnsafePtr(); + var pMeshletVertices = (uint*)meshletVertices.GetUnsafePtr(); + var pMeshletTriangles = (byte*)meshletTriangles.GetUnsafePtr(); + + nuint meshletCount; + if (config.clusterSpatial) + { + meshletCount = pMeshlets[0].BuildsSpatial( + pMeshletVertices, pMeshletTriangles, + indices, indexCount, + mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride, + config.maxVertices, config.minTriangles, config.maxTriangles, + config.clusterFillWeight + ); + } + else + { + meshletCount = pMeshlets[0].BuildsFlex( + pMeshletVertices, pMeshletTriangles, + indices, indexCount, + mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride, + config.maxVertices, config.minTriangles, config.maxTriangles, + 0.0f, config.clusterSplitFactor + ); + } + + var clusters = new UnsafeList((int)meshletCount, allocator); + + for (nuint i = 0; i < meshletCount; i++) + { + ref var meshlet = ref pMeshlets[i]; + + if (config.optimizeClusters) + { + MeshOptApi.OptimizeMeshlet( + pMeshletVertices + meshlet.vertex_offset, + pMeshletTriangles + meshlet.triangle_offset, + meshlet.triangle_count, + meshlet.vertex_count + ); + } + + var cluster = new Cluster + { + vertices = meshlet.vertex_count, + indices = new UnsafeList((int)(meshlet.triangle_count * 3), Allocator.Persistent), + group = -1, + refined = -1 + }; + + 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); + } + + return clusters; + } + + // CHANGED parameters: UnsafeList -> UnsafeArray (because UnsafeList with 0 count skips logic loops) + internal static void LockBoundary(UnsafeArray locks, UnsafeList> groups, UnsafeList clusters, UnsafeArray remap, byte* vertexLock) + { + var pLocks = (byte*)locks.GetUnsafePtr(); + var pRemap = (uint*)remap.GetUnsafePtr(); + + // CHANGED: locks.Count -> locks.Length + for (var i = 0; i < locks.Length; i++) + { + pLocks[i] = unchecked((byte)(pLocks[i] & ~((1 << 0) | (1 << 7)))); + } + + for (var i = 0; i < groups.Count; i++) + { + for (var j = 0; j < groups[i].Count; j++) + { + var cluster = clusters[groups[i][j]]; + for (var k = 0; k < cluster.indices.Count; k++) + { + var r = pRemap[(int)cluster.indices[k]]; + pLocks[r] |= (byte)(pLocks[r] >> 7); + } + } + + for (var j = 0; j < groups[i].Count; j++) + { + var cluster = clusters[groups[i][j]]; + for (var k = 0; k < cluster.indices.Count; k++) + { + var r = pRemap[(int)cluster.indices[k]]; + pLocks[r] |= 1 << 7; + } + } + } + + // CHANGED: locks.Count -> locks.Length + for (var i = 0; i < locks.Length; i++) + { + var r = pRemap[i]; + pLocks[i] = (byte)((pLocks[r] & 1) | (pLocks[i] & Api.meshopt_SimplifyVertex_Protect & 0xFF)); + if (vertexLock != null) + { + pLocks[i] |= vertexLock[i]; + } + } + } + + private static UnsafeList> Partition(ClodConfig config, ClodMesh mesh, UnsafeList clusters, UnsafeList pending, UnsafeArray remap, Allocator allocator) + { + if (pending.Count <= (int)config.partitionSize) + { + var single = new UnsafeList>(1, allocator); + single.Add(pending); + return single; + } + + nuint totalIndexCount = 0; + for (var i = 0; i < pending.Count; i++) + { + totalIndexCount += (nuint)clusters[pending[i]].indices.Count; + } + + using var clusterIndices = new UnsafeList((int)totalIndexCount, Allocator.FreeList); + using var clusterCounts = new UnsafeList(pending.Count, Allocator.FreeList); + + nuint offset = 0; + for (var i = 0; i < pending.Count; i++) + { + var cluster = clusters[pending[i]]; + clusterCounts.Add((uint)cluster.indices.Count); + for (var j = 0; j < cluster.indices.Count; j++) + { + clusterIndices.Add(((uint*)remap.GetUnsafePtr())[(int)cluster.indices[j]]); + } + + offset += (nuint)cluster.indices.Count; + } + + using var clusterPart = new UnsafeArray(pending.Count, Allocator.FreeList); + + var partitionCount = MeshOptApi.PartitionClusters( + (uint*)clusterPart.GetUnsafePtr(), + (uint*)clusterIndices.GetUnsafePtr(), + totalIndexCount, + (uint*)clusterCounts.GetUnsafePtr(), + (nuint)pending.Count, + config.partitionSpatial ? mesh.vertexPositions : null, + (nuint)remap.Length, + mesh.vertexPositionsStride, + config.partitionSize + ); + + var partitions = new UnsafeList>((int)partitionCount, allocator); + for (nuint i = 0; i < partitionCount; i++) + { + partitions.Add(new UnsafeList((int)(config.partitionSize + config.partitionSize / 3), allocator)); + } + + for (var i = 0; i < pending.Count; i++) + { + partitions[(int)((uint*)clusterPart.GetUnsafePtr())[i]].Add(pending[i]); + } + + return partitions; + } + + private static int OutputGroup(ClodConfig config, ClodMesh mesh, UnsafeList clusters, UnsafeList group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate outputCallback) + { + using var groupClusters = new UnsafeList(group.Count, Allocator.FreeList); + + for (var i = 0; i < group.Count; i++) + { + ref var srcCluster = ref clusters[group[i]]; + groupClusters.Add(new ClodCluster + { + refined = srcCluster.refined, + bounds = (config.optimizeBounds && srcCluster.refined != -1) + ? ComputeBounds(mesh, srcCluster.indices, srcCluster.bounds.error) + : srcCluster.bounds, + indices = (uint*)srcCluster.indices.GetUnsafePtr(), + indexCount = (nuint)srcCluster.indices.Count, + vertexCount = srcCluster.vertices + }); + } + + var clodGroup = new ClodGroup { depth = depth, simplified = simplified }; + var result = outputCallback != null + ? outputCallback(outputContext, clodGroup, (ClodCluster*)groupClusters.GetUnsafePtr(), (nuint)groupClusters.Count) + : -1; + + return result; + } + + public static UnsafeArray Simplify(ClodConfig config, ClodMesh mesh, ReadOnlyUnsafeCollection indices, ReadOnlyUnsafeCollection locks, nuint targetCount, float* error, Allocator allocator) + { + var lod = new UnsafeArray(indices.Count, allocator); + + if (targetCount >= (nuint)indices.Count) + { + lod.CopyFrom(indices.AsSpan()); + return lod; + } + + var options = (uint)(Api.meshopt_SimplifySparse | Api.meshopt_SimplifyErrorAbsolute); + if (config.simplifyPermissive) + { + options |= Api.meshopt_SimplifyPermissive; + } + + if (config.simplifyRegularize) + { + options |= Api.meshopt_SimplifyRegularize; + } + + var resultSize = MeshOptApi.SimplifyWithAttributes( + (uint*)lod.GetUnsafePtr(), + (uint*)indices.GetUnsafePtr(), + (nuint)indices.Count, + mesh.vertexPositions, + mesh.vertexCount, + mesh.vertexPositionsStride, + mesh.vertexAttributes, + mesh.vertexAttributesStride, + mesh.attributeWeights, + mesh.attributeCount, + (byte*)locks.GetUnsafePtr(), + targetCount, + float.MaxValue, + options, + error + ); + + lod.Resize((int)resultSize); + + if ((nuint)lod.Length > targetCount && config.simplifyFallbackPermissive && !config.simplifyPermissive) + { + options |= Api.meshopt_SimplifyPermissive; + resultSize = MeshOptApi.SimplifyWithAttributes( + (uint*)lod.GetUnsafePtr(), + (uint*)indices.GetUnsafePtr(), + (nuint)indices.Count, + mesh.vertexPositions, + mesh.vertexCount, + mesh.vertexPositionsStride, + mesh.vertexAttributes, + mesh.vertexAttributesStride, + mesh.attributeWeights, + mesh.attributeCount, + (byte*)locks.GetUnsafePtr(), + targetCount, + float.MaxValue, + options, + error + ); + + lod.Resize((int)resultSize); + } + + if ((nuint)lod.Length > targetCount && config.simplifyFallbackSloppy) + { + *error *= config.simplifyErrorFactorSloppy; + } + + if (config.simplifyErrorEdgeLimit > 0) + { + float maxEdgeSq = 0; + var pIdx = (uint*)indices.GetUnsafePtr(); + var posStride = (int)(mesh.vertexPositionsStride / sizeof(float)); + + for (var i = 0; i < indices.Count; i += 3) + { + uint a = pIdx[i], b = pIdx[i + 1], c = pIdx[i + 2]; + var va = mesh.vertexPositions + (a * (uint)posStride); + var vb = mesh.vertexPositions + (b * (uint)posStride); + var 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]; + var eab = dx * dx + dy * dy + dz * dz; + dx = va[0] - vc[0]; dy = va[1] - vc[1]; dz = va[2] - vc[2]; + var eac = dx * dx + dy * dy + dz * dz; + dx = vb[0] - vc[0]; dy = vb[1] - vc[1]; dz = vb[2] - vc[2]; + var ebc = dx * dx + dy * dy + dz * dz; + + var emax = Math.Max(Math.Max(eab, eac), ebc); + var emin = Math.Min(Math.Min(eab, eac), ebc); + maxEdgeSq = Math.Max(maxEdgeSq, Math.Max(emin, emax / 4)); + } + + *error = Math.Min(*error, (float)Math.Sqrt(maxEdgeSq) * config.simplifyErrorEdgeLimit); + } + + return lod; + } + + /// + /// Builds a cluster LOD hierarchy from the input mesh. + /// + /// The configuration parameters for the LOD building process. + /// The input mesh data. + /// Optional context pointer passed to the output callback. + /// Delegate invoked for each generated LOD group. + /// The total count of generated clusters. + public static nuint Build(ClodConfig config, ClodMesh mesh, void* outputContext, ClodOutputDelegate outputCallback) + { + Debug.Assert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)"); + + using var locks = new UnsafeArray((int)mesh.vertexCount, Allocator.FreeList, AllocationOption.Clear); + using var remap = new UnsafeArray((int)mesh.vertexCount, Allocator.FreeList); + + MeshOptApi.GeneratePositionRemap((uint*)remap.GetUnsafePtr(), mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); + + if (mesh.attributeProtectMask != 0) + { + var maxAttributes = mesh.vertexAttributesStride / sizeof(float); + for (nuint i = 0; i < mesh.vertexCount; i++) + { + var 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]) + { + ((byte*)locks.GetUnsafePtr())[(int)i] |= Api.meshopt_SimplifyVertex_Protect & 0xFF; + } + } + } + } + } + + using var clusters = Clusterize(config, mesh, mesh.indices, mesh.indexCount, Allocator.FreeList); + + for (var i = 0; i < clusters.Count; i++) + { + clusters[i].bounds = ComputeBounds(mesh, clusters[i].indices, 0.0f); + } + + using var pending = new UnsafeList(clusters.Count, Allocator.FreeList); + for (var i = 0; i < clusters.Count; i++) + { + pending.Add(i); + } + + var depth = 0; + + while (pending.Count > 1) + { + using var groups = Partition(config, mesh, clusters, pending, remap, Allocator.FreeList); + pending.Clear(); + + LockBoundary(locks, groups, clusters, remap, mesh.vertexLock); + + for (var i = 0; i < groups.Count; i++) + { + using var merged = new UnsafeList(groups[i].Count * (int)config.maxTriangles * 3, Allocator.FreeList); + for (var j = 0; j < groups[i].Count; j++) + { + var clusterIndices = clusters[groups[i][j]].indices; + for (var k = 0; k < clusterIndices.Count; k++) + { + merged.Add(clusterIndices[k]); + } + } + + var targetSize = ((nuint)merged.Count / 3) * (nuint)config.simplifyRatio * 3; + var bounds = MergeBounds(clusters, groups[i]); + + var error = 0.0f; + using var simplified = Simplify(config, mesh, merged.AsReadOnly(), locks.AsReadOnly(), targetSize, &error, Allocator.FreeList); + + if ((nuint)simplified.Length > (nuint)(merged.Count * config.simplifyThreshold)) + { + bounds.error = float.MaxValue; + OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); + continue; + } + + bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive; + + var refined = OutputGroup(config, mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); + + for (var j = 0; j < groups[i].Count; j++) + { + clusters[groups[i][j]].Dispose(); + } + + using var split = Clusterize(config, mesh, (uint*)simplified.GetUnsafePtr(), (nuint)simplified.Length, Allocator.FreeList); + for (var j = 0; j < split.Count; j++) + { + split[j].refined = refined; + split[j].bounds = bounds; + clusters.Add(split[j]); + pending.Add(clusters.Count - 1); + } + } + + for (var i = 0; i < groups.Count; i++) + { + groups[i].Dispose(); + } + + depth++; + } + + if (pending.Count > 0) + { + var bounds = clusters[pending[0]].bounds; + bounds.error = float.MaxValue; + OutputGroup(config, mesh, clusters, pending, bounds, depth, outputContext, outputCallback); + } + + var finalClusterCount = (nuint)clusters.Count; + + for (var i = 0; i < clusters.Count; i++) + { + clusters[i].Dispose(); + } + + return finalClusterCount; + } +} \ No newline at end of file