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:
7
src/.github/commit-instructions.md
vendored
7
src/.github/commit-instructions.md
vendored
@@ -1,7 +0,0 @@
|
|||||||
Use this instructions when writing a git commit message
|
|
||||||
|
|
||||||
The first line should be a single line with no more than 50 characters that summary the changes. The second line should be blank. Start at the third line for actual changes.
|
|
||||||
|
|
||||||
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
|
|
||||||
|
|
||||||
Commits MUST be prefixed with a type, which consists of a noun, feat, fix, etc., followed by the OPTIONAL scope, OPTIONAL !, and REQUIRED terminal colon and space. The type feat MUST be used when a commit adds a new feature to your application or library. The type fix MUST be used when a commit represents a bug fix for your application. A scope MAY be provided after a type. A scope MUST consist of a noun describing a section of the codebase surrounded by parenthesis, e.g., fix(parser) A description MUST immediately follow the colon and space after the typescope prefix. The description is a short summary of the code changes, e.g., fix array parsing issue when multiple spaces were contained in string. A longer commit body MAY be provided after the short description, providing additional contextual information about the code changes. The body MUST begin one blank line after the description. A commit body is free-form and MAY consist of any number of newline separated paragraphs. One or more footers MAY be provided one blank line after the body. Each footer MUST consist of a word token, followed by either a or # separator, followed by a string value (this is inspired by the git trailer convention). A footer’s token MUST use - in place of whitespace characters, e.g., Acked-by (this helps differentiate the footer section from a multi-paragraph body). An exception is made for BREAKING CHANGE, which MAY also be used as a token. A footer’s value MAY contain spaces and newlines, and parsing MUST terminate when the next valid footer tokenseparator pair is observed. Breaking changes MUST be indicated in the typescope prefix of a commit, or as an entry in the footer. If included as a footer, a breaking change MUST consist of the uppercase text BREAKING CHANGE, followed by a colon, space, and description, e.g., BREAKING CHANGE environment variables now take precedence over config files. If included in the typescope prefix, breaking changes MUST be indicated by a ! immediately before the . If ! is used, BREAKING CHANGE MAY be omitted from the footer section, and the commit description SHALL be used to describe the breaking change. Types other than feat and fix MAY be used in your commit messages, e.g., docs update ref docs. The units of information that make up Conventional Commits MUST NOT be treated as case-sensitive by implementors, with the exception of BREAKING CHANGE which MUST be uppercase. BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE, when used as a token in a footer.
|
|
||||||
@@ -758,7 +758,7 @@ public static unsafe partial class MeshProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return pMeshletData->groups.Count - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -846,8 +846,266 @@ public static unsafe partial class MeshProcessor
|
|||||||
pMeshletData->materialSlotCount = maxMaterialSlot + 1;
|
pMeshletData->materialSlotCount = maxMaterialSlot + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void BuildClusterLodHierarchy()
|
private struct TempBinaryNode
|
||||||
{
|
{
|
||||||
// TODO: Implement a function that builds a cluster LOD hierarchy for a mesh, which can be used for efficient rendering of large meshes with varying levels of detail.
|
public AABB bounds;
|
||||||
|
public float maxParentError;
|
||||||
|
public int leftChild;
|
||||||
|
public int rightChild;
|
||||||
|
public int meshletIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int BuildBinaryTree(UnsafeList<TempBinaryNode> nodes, UnsafeArray<int> meshletIndices, int start, int end, ReadOnlySpan<Meshlet> meshlets)
|
||||||
|
{
|
||||||
|
if (start == end - 1)
|
||||||
|
{
|
||||||
|
var meshletIndex = meshletIndices[start];
|
||||||
|
ref readonly var m = ref meshlets[meshletIndex];
|
||||||
|
|
||||||
|
var node = new TempBinaryNode
|
||||||
|
{
|
||||||
|
bounds = m.boundingBox,
|
||||||
|
maxParentError = m.parentError,
|
||||||
|
leftChild = -1,
|
||||||
|
rightChild = -1,
|
||||||
|
meshletIndex = meshletIndex
|
||||||
|
};
|
||||||
|
var nodeIndex = nodes.Count;
|
||||||
|
nodes.Add(node);
|
||||||
|
return nodeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute centroid bounds
|
||||||
|
var centroidMin = new float3(float.MaxValue);
|
||||||
|
var centroidMax = new float3(float.MinValue);
|
||||||
|
for (var i = start; i < end; i++)
|
||||||
|
{
|
||||||
|
var m = meshlets[meshletIndices[i]];
|
||||||
|
var center = m.boundingBox.Center;
|
||||||
|
centroidMin = math.min(centroidMin, center);
|
||||||
|
centroidMax = math.max(centroidMax, center);
|
||||||
|
}
|
||||||
|
|
||||||
|
var extents = centroidMax - centroidMin;
|
||||||
|
var splitAxis = 0;
|
||||||
|
if (extents.y > extents.x && extents.y > extents.z) splitAxis = 1;
|
||||||
|
if (extents.z > extents.x && extents.z > extents.y) splitAxis = 2;
|
||||||
|
|
||||||
|
var splitPoint = centroidMin[splitAxis] + extents[splitAxis] * 0.5f;
|
||||||
|
|
||||||
|
// Partition
|
||||||
|
var mid = start;
|
||||||
|
for (var i = start; i < end; i++)
|
||||||
|
{
|
||||||
|
var center = meshlets[meshletIndices[i]].boundingBox.Center;
|
||||||
|
if (center[splitAxis] < splitPoint)
|
||||||
|
{
|
||||||
|
var temp = meshletIndices[mid];
|
||||||
|
meshletIndices[mid] = meshletIndices[i];
|
||||||
|
meshletIndices[i] = temp;
|
||||||
|
mid++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mid == start || mid == end)
|
||||||
|
{
|
||||||
|
mid = start + (end - start) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var left = BuildBinaryTree(nodes, meshletIndices, start, mid, meshlets);
|
||||||
|
var right = BuildBinaryTree(nodes, meshletIndices, mid, end, meshlets);
|
||||||
|
|
||||||
|
var leftNode = nodes[left];
|
||||||
|
var rightNode = nodes[right];
|
||||||
|
|
||||||
|
var mergedBounds = new AABB(
|
||||||
|
math.min(leftNode.bounds.Min, rightNode.bounds.Min),
|
||||||
|
math.max(leftNode.bounds.Max, rightNode.bounds.Max)
|
||||||
|
);
|
||||||
|
|
||||||
|
var internalNodeIndex = nodes.Count;
|
||||||
|
nodes.Add(new TempBinaryNode
|
||||||
|
{
|
||||||
|
bounds = mergedBounds,
|
||||||
|
maxParentError = Math.Max(leftNode.maxParentError, rightNode.maxParentError),
|
||||||
|
leftChild = left,
|
||||||
|
rightChild = right,
|
||||||
|
meshletIndex = -1
|
||||||
|
});
|
||||||
|
|
||||||
|
return internalNodeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GatherChildren(UnsafeList<TempBinaryNode> binaryNodes, int nodeIndex, UnsafeList<int> gathered)
|
||||||
|
{
|
||||||
|
gathered.Clear();
|
||||||
|
var node = binaryNodes[nodeIndex];
|
||||||
|
if (node.leftChild != -1) gathered.Add(node.leftChild);
|
||||||
|
if (node.rightChild != -1) gathered.Add(node.rightChild);
|
||||||
|
|
||||||
|
while (gathered.Count < 4)
|
||||||
|
{
|
||||||
|
var largestInternalIndex = -1;
|
||||||
|
var maxSurfaceArea = -1.0f;
|
||||||
|
var listIndexToRemove = -1;
|
||||||
|
|
||||||
|
for (var i = 0; i < gathered.Count; i++)
|
||||||
|
{
|
||||||
|
var childIdx = gathered[i];
|
||||||
|
var childNode = binaryNodes[childIdx];
|
||||||
|
if (childNode.leftChild != -1) // is internal
|
||||||
|
{
|
||||||
|
var extents = childNode.bounds.Extents;
|
||||||
|
var sa = extents.x * extents.y + extents.y * extents.z + extents.z * extents.x;
|
||||||
|
if (sa > maxSurfaceArea)
|
||||||
|
{
|
||||||
|
maxSurfaceArea = sa;
|
||||||
|
largestInternalIndex = childIdx;
|
||||||
|
listIndexToRemove = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (largestInternalIndex == -1) break; // all gathered are leaves
|
||||||
|
|
||||||
|
gathered.RemoveAt(listIndexToRemove);
|
||||||
|
var largestNode = binaryNodes[largestInternalIndex];
|
||||||
|
if (largestNode.leftChild != -1) gathered.Add(largestNode.leftChild);
|
||||||
|
if (largestNode.rightChild != -1) gathered.Add(largestNode.rightChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CollapseTo4Ary(UnsafeList<TempBinaryNode> binaryNodes, int binaryNodeIndex, UnsafeList<MeshletHierarchyNode> hierarchyNodes)
|
||||||
|
{
|
||||||
|
var node = binaryNodes[binaryNodeIndex];
|
||||||
|
if (node.leftChild == -1)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var gathered = new UnsafeList<int>(4, AllocationHandle.FreeList);
|
||||||
|
GatherChildren(binaryNodes, binaryNodeIndex, gathered);
|
||||||
|
|
||||||
|
var bvhNode = new MeshletHierarchyNode();
|
||||||
|
|
||||||
|
var minX = new float4(float.PositiveInfinity);
|
||||||
|
var minY = new float4(float.PositiveInfinity);
|
||||||
|
var minZ = new float4(float.PositiveInfinity);
|
||||||
|
var maxX = new float4(float.NegativeInfinity);
|
||||||
|
var maxY = new float4(float.NegativeInfinity);
|
||||||
|
var maxZ = new float4(float.NegativeInfinity);
|
||||||
|
var maxParentError = new float4(0);
|
||||||
|
var nodeData = new uint4(0xFFFFFFFF);
|
||||||
|
|
||||||
|
var outNodeIndex = hierarchyNodes.Count;
|
||||||
|
hierarchyNodes.Add(bvhNode); // Reserve slot
|
||||||
|
|
||||||
|
for (var i = 0; i < gathered.Count; i++)
|
||||||
|
{
|
||||||
|
var childIdx = gathered[i];
|
||||||
|
var childNode = binaryNodes[childIdx];
|
||||||
|
|
||||||
|
uint data = 0;
|
||||||
|
if (childNode.leftChild == -1)
|
||||||
|
{
|
||||||
|
data = (uint)childNode.meshletIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var child4AryIndex = CollapseTo4Ary(binaryNodes, childIdx, hierarchyNodes);
|
||||||
|
data = (1u << 31) | (uint)child4AryIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
minX.x = childNode.bounds.Min.x; minY.x = childNode.bounds.Min.y; minZ.x = childNode.bounds.Min.z;
|
||||||
|
maxX.x = childNode.bounds.Max.x; maxY.x = childNode.bounds.Max.y; maxZ.x = childNode.bounds.Max.z;
|
||||||
|
maxParentError.x = childNode.maxParentError;
|
||||||
|
nodeData.x = data;
|
||||||
|
}
|
||||||
|
else if (i == 1)
|
||||||
|
{
|
||||||
|
minX.y = childNode.bounds.Min.x; minY.y = childNode.bounds.Min.y; minZ.y = childNode.bounds.Min.z;
|
||||||
|
maxX.y = childNode.bounds.Max.x; maxY.y = childNode.bounds.Max.y; maxZ.y = childNode.bounds.Max.z;
|
||||||
|
maxParentError.y = childNode.maxParentError;
|
||||||
|
nodeData.y = data;
|
||||||
|
}
|
||||||
|
else if (i == 2)
|
||||||
|
{
|
||||||
|
minX.z = childNode.bounds.Min.x; minY.z = childNode.bounds.Min.y; minZ.z = childNode.bounds.Min.z;
|
||||||
|
maxX.z = childNode.bounds.Max.x; maxY.z = childNode.bounds.Max.y; maxZ.z = childNode.bounds.Max.z;
|
||||||
|
maxParentError.z = childNode.maxParentError;
|
||||||
|
nodeData.z = data;
|
||||||
|
}
|
||||||
|
else if (i == 3)
|
||||||
|
{
|
||||||
|
minX.w = childNode.bounds.Min.x; minY.w = childNode.bounds.Min.y; minZ.w = childNode.bounds.Min.z;
|
||||||
|
maxX.w = childNode.bounds.Max.x; maxY.w = childNode.bounds.Max.y; maxZ.w = childNode.bounds.Max.z;
|
||||||
|
maxParentError.w = childNode.maxParentError;
|
||||||
|
nodeData.w = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bvhNode.minX = minX;
|
||||||
|
bvhNode.minY = minY;
|
||||||
|
bvhNode.minZ = minZ;
|
||||||
|
bvhNode.maxX = maxX;
|
||||||
|
bvhNode.maxY = maxY;
|
||||||
|
bvhNode.maxZ = maxZ;
|
||||||
|
bvhNode.maxParentError = maxParentError;
|
||||||
|
bvhNode.nodeData = nodeData;
|
||||||
|
|
||||||
|
hierarchyNodes[outNodeIndex] = bvhNode;
|
||||||
|
return outNodeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void BuildClusterLodHierarchy(MeshletMeshData* pMeshletData)
|
||||||
|
{
|
||||||
|
if (pMeshletData->meshletCount == 0) return;
|
||||||
|
|
||||||
|
using var meshletIndices = new UnsafeArray<int>(pMeshletData->meshletCount, AllocationHandle.FreeList);
|
||||||
|
for (var i = 0; i < pMeshletData->meshletCount; i++)
|
||||||
|
{
|
||||||
|
meshletIndices[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
var meshletsSpan = new ReadOnlySpan<Meshlet>(pMeshletData->meshlets.GetUnsafePtr(), pMeshletData->meshlets.Count);
|
||||||
|
|
||||||
|
using var binaryNodes = new UnsafeList<TempBinaryNode>(pMeshletData->meshletCount * 2, AllocationHandle.FreeList);
|
||||||
|
var rootIndex = BuildBinaryTree(binaryNodes, meshletIndices, 0, meshletIndices.Length, meshletsSpan);
|
||||||
|
|
||||||
|
if (!pMeshletData->hierarchyNodes.IsCreated)
|
||||||
|
{
|
||||||
|
pMeshletData->hierarchyNodes = new UnsafeList<MeshletHierarchyNode>(pMeshletData->meshletCount, AllocationHandle.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binaryNodes[rootIndex].leftChild == -1)
|
||||||
|
{
|
||||||
|
var bvhNode = new MeshletHierarchyNode();
|
||||||
|
bvhNode.minX = new float4(float.PositiveInfinity);
|
||||||
|
bvhNode.minY = new float4(float.PositiveInfinity);
|
||||||
|
bvhNode.minZ = new float4(float.PositiveInfinity);
|
||||||
|
bvhNode.maxX = new float4(float.NegativeInfinity);
|
||||||
|
bvhNode.maxY = new float4(float.NegativeInfinity);
|
||||||
|
bvhNode.maxZ = new float4(float.NegativeInfinity);
|
||||||
|
bvhNode.maxParentError = new float4(0);
|
||||||
|
bvhNode.nodeData = new uint4(0xFFFFFFFF);
|
||||||
|
|
||||||
|
var childNode = binaryNodes[rootIndex];
|
||||||
|
bvhNode.minX.x = childNode.bounds.Min.x;
|
||||||
|
bvhNode.minY.x = childNode.bounds.Min.y;
|
||||||
|
bvhNode.minZ.x = childNode.bounds.Min.z;
|
||||||
|
bvhNode.maxX.x = childNode.bounds.Max.x;
|
||||||
|
bvhNode.maxY.x = childNode.bounds.Max.y;
|
||||||
|
bvhNode.maxZ.x = childNode.bounds.Max.z;
|
||||||
|
bvhNode.maxParentError.x = childNode.maxParentError;
|
||||||
|
bvhNode.nodeData.x = (uint)childNode.meshletIndex;
|
||||||
|
|
||||||
|
pMeshletData->hierarchyNodes.Add(bvhNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CollapseTo4Ary(binaryNodes, rootIndex, pMeshletData->hierarchyNodes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ internal static partial class TextureProcessor
|
|||||||
public int numMipLevels;
|
public int numMipLevels;
|
||||||
public int channelCount;
|
public int channelCount;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private static Vector2<TFloat, float> Hammersley(TFloat i, int N, float* lut)
|
private static Vector2<TFloat, float> Hammersley(TFloat i, int N, float* lut)
|
||||||
{
|
{
|
||||||
var x = i / N;
|
var x = i / N;
|
||||||
@@ -43,23 +43,18 @@ internal static partial class TextureProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GGX Importance Sampling
|
// GGX Importance Sampling
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private static Vector3<TFloat, float> ImportanceSampleGGX(Vector2<TFloat, float> Xi, Vector3<TFloat, float> N, float roughness)
|
private static Vector3<TFloat, float> ImportanceSampleGGX(Vector2<TFloat, float> Xi, Vector3<TFloat, float> N, float roughness)
|
||||||
{
|
{
|
||||||
var a = roughness * roughness; // Disney remap roughness for better visual linearity
|
var a = roughness * roughness; // Disney remap roughness for better visual linearity
|
||||||
|
|
||||||
var phi = 2.0f * PI * Xi.x;
|
var phi = 2.0f * PI * Xi.x;
|
||||||
|
|
||||||
// Clamp the inside of the cosTheta Sqrt to prevent NaN on division precision edges
|
var cosTheta = TFloat.Sqrt((1.0f - Xi.y) / (1.0f + (a * a - 1.0f) * Xi.y));
|
||||||
var cosThetaInner = TFloat.Max((1.0f - Xi.y) / (1.0f + (a * a - 1.0f) * Xi.y), TFloat.Zero);
|
var sinTheta = TFloat.Sqrt(1.0f - cosTheta * cosTheta);
|
||||||
var cosTheta = TFloat.Sqrt(cosThetaInner);
|
|
||||||
|
|
||||||
// Clamp the inside of sinTheta to prevent sqrt of negative floating-point errors
|
|
||||||
var sinThetaInner = TFloat.Max(1.0f - cosTheta * cosTheta, TFloat.Zero);
|
|
||||||
var sinTheta = TFloat.Sqrt(sinThetaInner);
|
|
||||||
|
|
||||||
// Spherical to Cartesian coordinates (Halfway vector)
|
// Spherical to Cartesian coordinates (Halfway vector)
|
||||||
var (sinPhi, cosPhi) = TFloat.SinCos(phi);
|
TFloat.SinCos(phi, out var sinPhi, out var cosPhi);
|
||||||
var H = MathV.Create<TFloat, float>(cosPhi * sinTheta, sinPhi * sinTheta, cosTheta);
|
var H = MathV.Create<TFloat, float>(cosPhi * sinTheta, sinPhi * sinTheta, cosTheta);
|
||||||
|
|
||||||
// Tangent space to World space
|
// Tangent space to World space
|
||||||
@@ -73,13 +68,13 @@ internal static partial class TextureProcessor
|
|||||||
return MathV.Normalize(sampleVec);
|
return MathV.Normalize(sampleVec);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private static float3 CubemapUVToDir(int face, float u, float v)
|
private static float3 CubemapUVToDir(int face, float u, float v)
|
||||||
{
|
{
|
||||||
var sc = 2.0f * u - 1.0f;
|
var sc = 2.0f * u - 1.0f;
|
||||||
var tc = 1.0f - 2.0f * v;
|
var tc = 1.0f - 2.0f * v;
|
||||||
|
|
||||||
float x = 0, y = 0, z = 0;
|
float x = 0.0f, y = 0.0f, z = 0.0f;
|
||||||
switch (face)
|
switch (face)
|
||||||
{
|
{
|
||||||
case 0: x = 1.0f; y = tc; z = -sc; break;
|
case 0: x = 1.0f; y = tc; z = -sc; break;
|
||||||
@@ -93,7 +88,7 @@ internal static partial class TextureProcessor
|
|||||||
return normalize(float3(x, y, z));
|
return normalize(float3(x, y, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private static Vector3<TFloat, float> SampleCubemap(float* img, int edge, int c, Vector3<TFloat, float> dir)
|
private static Vector3<TFloat, float> SampleCubemap(float* img, int edge, int c, Vector3<TFloat, float> dir)
|
||||||
{
|
{
|
||||||
var absX = TFloat.Abs(dir.x);
|
var absX = TFloat.Abs(dir.x);
|
||||||
@@ -140,6 +135,7 @@ internal static partial class TextureProcessor
|
|||||||
return MathV.GatherVector3<TFloat, float>(img, idx.GetUnsafePtr(), 1);
|
return MathV.GatherVector3<TFloat, float>(img, idx.GetUnsafePtr(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||||
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
|
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
|
||||||
{
|
{
|
||||||
var m = 0;
|
var m = 0;
|
||||||
@@ -226,7 +222,7 @@ internal static partial class TextureProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
var totalWeight = 0.0f;
|
var totalWeight = 0.0f;
|
||||||
var prefilteredColor = float3(0, 0, 0);
|
var prefilteredColor = float3(0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
for (var i = 0; i < TFloat.LaneWidth; i++)
|
for (var i = 0; i < TFloat.LaneWidth; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.3" />
|
<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="System.IO.Hashing" Version="10.0.7" />
|
||||||
<PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" />
|
<PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" />
|
||||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
|
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
_resources.Remove(handle.ID, handle.Generation);
|
_resources.Remove(handle.ID, handle.Generation);
|
||||||
|
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
_resourceName.Remove(handle, out var name);
|
_resourceName.Remove(handle, out _);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,10 +41,20 @@ public struct MeshletGroup
|
|||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct MeshletHierarchyNode
|
public struct MeshletHierarchyNode
|
||||||
{
|
{
|
||||||
public SphereBounds boundingSphere; // 16 bytes
|
public float4 minX;
|
||||||
public AABB boundingBox; // 24 bytes
|
public float4 minY;
|
||||||
public float maxParentError; // maximum error in this subtree
|
public float4 minZ;
|
||||||
public uint nodeData; // packed leaf/internal metadata
|
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)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
@@ -183,18 +193,6 @@ public struct Mesh : IResourceReleasable
|
|||||||
get; internal set;
|
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()
|
public void ReleaseCpuResources()
|
||||||
{
|
{
|
||||||
_vertices.Dispose();
|
_vertices.Dispose();
|
||||||
|
|||||||
@@ -110,21 +110,21 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new mesh from the specified vertex and index data.
|
/// Creates a new mesh from the specified vertex and index data.
|
||||||
/// </summary>
|
/// </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="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. Must contain at least one index.</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>
|
/// <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);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
lock (_meshWriteLock)
|
|
||||||
{
|
|
||||||
var vertexBufferDesc = new BufferDesc
|
var vertexBufferDesc = new BufferDesc
|
||||||
{
|
{
|
||||||
Size = (uint)(vertices.Count * sizeof(Vertex)),
|
Size = (uint)(vertices.Count * sizeof(Vertex)),
|
||||||
Stride = (uint)sizeof(Vertex),
|
Stride = (uint)sizeof(Vertex),
|
||||||
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw,
|
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw,
|
||||||
HeapType = HeapType.Default,
|
HeapType = dynamic ? HeapType.Upload : HeapType.Default,
|
||||||
};
|
};
|
||||||
|
|
||||||
var indexBufferDesc = new BufferDesc
|
var indexBufferDesc = new BufferDesc
|
||||||
@@ -132,20 +132,21 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
Size = (uint)(indices.Count * sizeof(uint)),
|
Size = (uint)(indices.Count * sizeof(uint)),
|
||||||
Stride = sizeof(uint),
|
Stride = sizeof(uint),
|
||||||
Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw,
|
Usage = BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw,
|
||||||
HeapType = HeapType.Default,
|
HeapType = dynamic ? HeapType.Upload : HeapType.Default,
|
||||||
};
|
};
|
||||||
|
|
||||||
var objectBufferDesc = new BufferDesc
|
var meshDataBufferDesc = new BufferDesc
|
||||||
{
|
{
|
||||||
Size = (uint)sizeof(MeshData),
|
Size = (uint)sizeof(MeshData),
|
||||||
Stride = (uint)sizeof(MeshData),
|
Stride = (uint)sizeof(MeshData),
|
||||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||||
HeapType = HeapType.Default,
|
HeapType = dynamic ? HeapType.Upload : HeapType.Default,
|
||||||
};
|
};
|
||||||
|
|
||||||
var vertexBuffer = _resourceAllocator.CreateBuffer(in vertexBufferDesc, "VertexBuffer");
|
var hasName = name != null;
|
||||||
var indexBuffer = _resourceAllocator.CreateBuffer(in indexBufferDesc, "IndexBuffer");
|
var vertexBuffer = _resourceAllocator.CreateBuffer(in vertexBufferDesc, hasName ? $"{name}_VertexBuffer" : "VertexBuffer");
|
||||||
var objectBuffer = _resourceAllocator.CreateBuffer(in objectBufferDesc, "ObjectBuffer");
|
var indexBuffer = _resourceAllocator.CreateBuffer(in indexBufferDesc, hasName ? $"{name}_IndexBuffer" : "IndexBuffer");
|
||||||
|
var meshDataBuffer = _resourceAllocator.CreateBuffer(in meshDataBufferDesc, hasName ? $"{name}_MeshDataBuffer" : "MeshDataBuffer");
|
||||||
|
|
||||||
var mesh = new Mesh
|
var mesh = new Mesh
|
||||||
{
|
{
|
||||||
@@ -153,9 +154,12 @@ public sealed partial class ResourceManager : IDisposable
|
|||||||
Indices = indices,
|
Indices = indices,
|
||||||
VertexBuffer = vertexBuffer,
|
VertexBuffer = vertexBuffer,
|
||||||
IndexBuffer = indexBuffer,
|
IndexBuffer = indexBuffer,
|
||||||
MeshDataBuffer = objectBuffer,
|
MeshDataBuffer = meshDataBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
lock (_meshWriteLock)
|
||||||
|
{
|
||||||
|
|
||||||
var id = _meshes.Add(mesh, out var generation);
|
var id = _meshes.Add(mesh, out var generation);
|
||||||
return new Handle<Mesh>(id, 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.
|
/// Creates a new material instance using the specified shader.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="shader">The identifier of the shader to associate with the new material.</param>
|
/// <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>
|
/// <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);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
var material = new Material();
|
var material = new Material();
|
||||||
|
|
||||||
lock (_materialWriteLock)
|
|
||||||
{
|
|
||||||
if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None)
|
if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None)
|
||||||
{
|
{
|
||||||
return Handle<Material>.Invalid;
|
return Handle<Material>.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lock (_materialWriteLock)
|
||||||
|
{
|
||||||
var id = _materials.Add(material, out var generation);
|
var id = _materials.Add(material, out var generation);
|
||||||
return new Handle<Material>(id, generation);
|
return new Handle<Material>(id, generation);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user