feat(meshlet): add cluster LOD hierarchy & API upgrades

Implemented meshlet cluster LOD hierarchy with binary-to-4-ary conversion. Updated MeshletHierarchyNode to 4-ary structure. Enhanced SIMD optimizations in GGX mipmap generation. ResourceManager mesh/material creation now supports dynamic buffers and optional naming. Upgraded SPMD package to 1.3.2. Performed minor code cleanups and doc improvements.
This commit is contained in:
2026-05-01 15:06:27 +09:00
parent 0eaf7cd51d
commit e384a2f38c
7 changed files with 336 additions and 87 deletions

View File

@@ -27,7 +27,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.0" />
<PackageReference Include="Misaki.HighPerformance.Mathematics.SPMD" Version="1.3.2" />
<PackageReference Include="System.IO.Hashing" Version="10.0.7" />
<PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />

View File

@@ -317,7 +317,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
_resources.Remove(handle.ID, handle.Generation);
#if DEBUG || GHOST_EDITOR
_resourceName.Remove(handle, out var name);
_resourceName.Remove(handle, out _);
#endif
}

View File

@@ -41,10 +41,20 @@ public struct MeshletGroup
[StructLayout(LayoutKind.Sequential)]
public struct MeshletHierarchyNode
{
public SphereBounds boundingSphere; // 16 bytes
public AABB boundingBox; // 24 bytes
public float maxParentError; // maximum error in this subtree
public uint nodeData; // packed leaf/internal metadata
public float4 minX;
public float4 minY;
public float4 minZ;
public float4 maxX;
public float4 maxY;
public float4 maxZ;
public float4 maxParentError;
// x,y,z,w correspond to children 0,1,2,3.
// MSB (1 << 31) indicates it's an Internal Node.
// If MSB is 0, the remaining 31 bits are the MeshletIndex.
// If MSB is 1, the remaining 31 bits are the child MeshletHierarchyNode index.
// 0xFFFFFFFF means invalid/empty slot.
public uint4 nodeData;
}
[StructLayout(LayoutKind.Sequential)]
@@ -183,18 +193,6 @@ public struct Mesh : IResourceReleasable
get; internal set;
}
internal Mesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices, Handle<GPUBuffer> vertexBuffer, Handle<GPUBuffer> indexBuffer)
{
Vertices = new UnsafeList<Vertex>(vertices.Length, AllocationHandle.Persistent);
Indices = new UnsafeList<uint>(indices.Length, AllocationHandle.Persistent);
Vertices.CopyFrom(vertices);
Indices.CopyFrom(indices);
VertexBuffer = vertexBuffer;
IndexBuffer = indexBuffer;
this.ComputeBounds();
}
public void ReleaseCpuResources()
{
_vertices.Dispose();

View File

@@ -110,51 +110,55 @@ public sealed partial class ResourceManager : IDisposable
/// <summary>
/// Creates a new mesh from the specified vertex and index data.
/// </summary>
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh. Must contain at least one vertex.</param>
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives. Must contain at least one index.</param>
/// <param name="vertices">A UnsafeList containing the vertices that define the geometry of the mesh.</param>
/// <param name="indices">A UnsafeList containing the indices that specify how vertices are connected to form primitives.</param>
/// <param name="dynamic">Indicates whether the mesh is expected to be updated frequently. If true, the underlying GPU buffers will be created with upload heap type for better CPU write performance.</param>
/// <param name="name">The name of the mesh.</param>
/// <returns>An <see cref="Identifier{Mesh}"/> representing the newly created mesh.</returns>
public unsafe Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
public unsafe Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool dynamic = false, string? name = null)
{
Logger.DebugAssert(!_disposed);
var vertexBufferDesc = new BufferDesc
{
Size = (uint)(vertices.Count * sizeof(Vertex)),
Stride = (uint)sizeof(Vertex),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw,
HeapType = dynamic ? HeapType.Upload : HeapType.Default,
};
var indexBufferDesc = new BufferDesc
{
Size = (uint)(indices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw,
HeapType = dynamic ? HeapType.Upload : HeapType.Default,
};
var meshDataBufferDesc = new BufferDesc
{
Size = (uint)sizeof(MeshData),
Stride = (uint)sizeof(MeshData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = dynamic ? HeapType.Upload : HeapType.Default,
};
var hasName = name != null;
var vertexBuffer = _resourceAllocator.CreateBuffer(in vertexBufferDesc, hasName ? $"{name}_VertexBuffer" : "VertexBuffer");
var indexBuffer = _resourceAllocator.CreateBuffer(in indexBufferDesc, hasName ? $"{name}_IndexBuffer" : "IndexBuffer");
var meshDataBuffer = _resourceAllocator.CreateBuffer(in meshDataBufferDesc, hasName ? $"{name}_MeshDataBuffer" : "MeshDataBuffer");
var mesh = new Mesh
{
Vertices = vertices,
Indices = indices,
VertexBuffer = vertexBuffer,
IndexBuffer = indexBuffer,
MeshDataBuffer = meshDataBuffer,
};
lock (_meshWriteLock)
{
var vertexBufferDesc = new BufferDesc
{
Size = (uint)(vertices.Count * sizeof(Vertex)),
Stride = (uint)sizeof(Vertex),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw,
HeapType = HeapType.Default,
};
var indexBufferDesc = new BufferDesc
{
Size = (uint)(indices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw,
HeapType = HeapType.Default,
};
var objectBufferDesc = new BufferDesc
{
Size = (uint)sizeof(MeshData),
Stride = (uint)sizeof(MeshData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
var vertexBuffer = _resourceAllocator.CreateBuffer(in vertexBufferDesc, "VertexBuffer");
var indexBuffer = _resourceAllocator.CreateBuffer(in indexBufferDesc, "IndexBuffer");
var objectBuffer = _resourceAllocator.CreateBuffer(in objectBufferDesc, "ObjectBuffer");
var mesh = new Mesh
{
Vertices = vertices,
Indices = indices,
VertexBuffer = vertexBuffer,
IndexBuffer = indexBuffer,
MeshDataBuffer = objectBuffer,
};
var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation);
@@ -165,20 +169,20 @@ public sealed partial class ResourceManager : IDisposable
/// Creates a new material instance using the specified shader.
/// </summary>
/// <param name="shader">The identifier of the shader to associate with the new material.</param>
/// <param name="name">The name of the material.</param>
/// <returns>An <see cref="Handle{Material}"/> representing the newly created material.</returns>
public Handle<Material> CreateMaterial(Handle<Shader> shader)
public Handle<Material> CreateMaterial(Handle<Shader> shader, string? name = null)
{
Logger.DebugAssert(!_disposed);
var material = new Material();
if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None)
{
return Handle<Material>.Invalid;
}
lock (_materialWriteLock)
{
if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None)
{
return Handle<Material>.Invalid;
}
var id = _materials.Add(material, out var generation);
return new Handle<Material>(id, generation);
}