2 Commits

Author SHA1 Message Date
e3a02437c3 Refactor mesh/texture pipeline for unified buffers & cubemaps
- Switch mesh import to unified vertex/index buffers with multi-material partitioning (`MaterialPartInfo`)
- Update `GeometryMeshNode` and meshlet builder for unified buffer layout
- Refactor cubemap texture pipeline: packed faces, improved GGX mip generation, equirect->cubemap conversion, and cubemap sampling
- Change MeshBuilder normal/tangent utilities to use `Span<T>`
- Add mimalloc allocator dependency and enable in Debug/Release
- Misc bug fixes, resource management, and code cleanup
2026-04-26 21:40:24 +09:00
5903ddda2b Refactor mesh import, meshlet, and asset handler systems
- Mesh import now builds full node hierarchy and splits geometry by material, with robust normal/tangent handling
- Meshlet generation supports material indices for correct assignment
- Refactored texture cube map compression and mipmap handling
- Updated asset handler registration to new namespace
- Enabled asset reimport on import events
- Improved code quality, resource management, and formatting
2026-04-26 14:49:58 +09:00
11 changed files with 520 additions and 311 deletions

View File

@@ -1,6 +1,7 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -20,15 +21,15 @@ public class MeshNode : IDisposable
} }
public MeshNode? Parent public MeshNode? Parent
{
get; init;
}
public required IReadOnlyCollection<MeshNode> Children
{ {
get; set; get; set;
} }
public IReadOnlyCollection<MeshNode> Children
{
get; set;
} = Array.Empty<MeshNode>();
~MeshNode() ~MeshNode()
{ {
Dispose(false); Dispose(false);
@@ -45,15 +46,36 @@ public class MeshNode : IDisposable
child.Dispose(); child.Dispose();
} }
Parent = null;
Children = Array.Empty<MeshNode>();
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }
/// <summary>
/// Describes one material partition within a unified vertex/index buffer.
/// </summary>
public struct MaterialPartInfo
{
/// <summary> The material slot index (from ufbx face_material). </summary>
public int materialIndex;
/// <summary> Byte offset into the unified index buffer. </summary>
public int indexStart;
/// <summary> Number of indices belonging to this part. </summary>
public int indexCount;
/// <summary> Byte offset into the unified vertex buffer. </summary>
public int vertexStart;
/// <summary> Number of unique vertices belonging to this part. </summary>
public int vertexCount;
}
public class GeometryMeshNode : MeshNode public class GeometryMeshNode : MeshNode
{ {
private UnsafeList<Vertex> _vertices; private UnsafeList<Vertex> _vertices;
private UnsafeList<uint> _indices; private UnsafeList<uint> _indices;
private UnsafeArray<MaterialPartInfo> _materialParts;
public UnsafeList<Vertex> Vertices public UnsafeList<Vertex> Vertices
{ {
@@ -75,15 +97,21 @@ public class GeometryMeshNode : MeshNode
} }
} }
public int MaterialIndex public UnsafeArray<MaterialPartInfo> MaterialParts
{ {
get; set; get => _materialParts;
set
{
_materialParts.Dispose();
_materialParts = value;
}
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
_vertices.Dispose(); _vertices.Dispose();
_indices.Dispose(); _indices.Dispose();
_materialParts.Dispose();
} }
} }

View File

@@ -1,4 +1,5 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Engine.Utilities;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities; using Ghost.Graphics.Utilities;
using Ghost.MeshOptimizer; using Ghost.MeshOptimizer;
@@ -11,21 +12,32 @@ using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Xml.Linq;
namespace Ghost.Editor.Core.Assets; namespace Ghost.Editor.Core.Assets;
internal unsafe class MeshParsingWorkItem : IJob internal readonly unsafe struct MeshParsingWorkItem : IJob
{ {
private struct GeometryPart : IDisposable
{
public UnsafeList<Vertex> vertices;
public UnsafeList<uint> indices;
public int materialIndex;
public bool missingNormals;
public bool missingTangents;
public void Dispose()
{
vertices.Dispose();
indices.Dispose();
}
}
private readonly string _filePath; private readonly string _filePath;
private readonly AllocationHandle _allocationHandle; private readonly AllocationHandle _allocationHandle;
private readonly MeshAssetSettings _settings; private readonly MeshAssetSettings _settings;
private readonly TaskCompletionSource<Result<MeshNode>> _taskCompletionSource; private readonly TaskCompletionSource<Result<MeshNode>> _taskCompletionSource;
public UnsafeList<Vertex> vertices; public readonly Task<Result<MeshNode>> Task => _taskCompletionSource.Task;
public UnsafeList<uint> indices;
public Task<Result<MeshNode>> Task => _taskCompletionSource.Task;
public MeshParsingWorkItem(string filePath, AllocationHandle allocationHandle, MeshAssetSettings settings) public MeshParsingWorkItem(string filePath, AllocationHandle allocationHandle, MeshAssetSettings settings)
{ {
@@ -59,23 +71,64 @@ internal unsafe class MeshParsingWorkItem : IJob
}; };
} }
private GeometryMeshNode ParseGeometry(ufbx_mesh* pMesh) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static float4x4 ToFloat4x4(ufbx_vec3 t, ufbx_quat q, ufbx_vec3 s)
{ {
var meshNode = new GeometryMeshNode return float4x4.TRS(
new float3(t.x, t.y, t.z),
new quaternion(q.x, q.y, q.z, q.w),
new float3(s.x, s.y, s.z)
);
}
private MeshNode ParseHierarchy(ufbx_node* node)
{ {
Name = pMesh->name.ToString(), var children = new List<MeshNode>();
Children = Array.Empty<MeshNode>(), var meshNode = new MeshNode
{
Name = node->name.ToString(),
LocalTransform = ToFloat4x4(node->local_transform.translation, node->local_transform.rotation, node->local_transform.scale),
Children = children
}; };
if (pMesh->num_faces == 0) if (node->mesh != null)
{ {
var geoNode = ParseGeometry(node->mesh);
if (geoNode != null)
{
children.Add(geoNode);
}
}
// TODO: Handle lights, cameras, and other node types.
for (var i = 0u; i < node->children.count; i++)
{
children.Add(ParseHierarchy(node->children.data[i]));
}
return meshNode; return meshNode;
} }
var missingNormals = false; private GeometryMeshNode? ParseGeometry(ufbx_mesh* pMesh)
var missingTangents = false; {
if (pMesh->num_faces == 0)
{
return null;
}
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList); var numMaterials = pMesh->materials.count > 0 ? (int)pMesh->materials.count : 1;
// Bucket faces by material
using var materialBuckets = new UnsafeArray<UnsafeList<Vertex>>(numMaterials, AllocationHandle.FreeList);
using var missingNormalsBucket = new UnsafeArray<bool>(numMaterials, AllocationHandle.FreeList);
using var missingTangentsBucket = new UnsafeArray<bool>(numMaterials, AllocationHandle.FreeList);
for (var i = 0; i < numMaterials; i++)
{
materialBuckets[i] = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
}
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u); var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
@@ -84,6 +137,7 @@ internal unsafe class MeshParsingWorkItem : IJob
for (var j = 0u; j < pMesh->num_faces; j++) for (var j = 0u; j < pMesh->num_faces; j++)
{ {
var face = pMesh->faces.data[j]; var face = pMesh->faces.data[j];
var materialIdx = pMesh->face_material.count > j ? pMesh->face_material.data[j] : 0;
var numTris = UfbxApi.TriangulateFace(triIndicesArray.AsSpan(0, maxScratchIndices), pMesh, face); var numTris = UfbxApi.TriangulateFace(triIndicesArray.AsSpan(0, maxScratchIndices), pMesh, face);
@@ -123,22 +177,33 @@ internal unsafe class MeshParsingWorkItem : IJob
vertex.tangent = ComputeTangent(t, n, b); vertex.tangent = ComputeTangent(t, n, b);
} }
var newIndex = (uint)flatVertices.Count; materialBuckets[materialIdx].Add(vertex);
flatVertices.Add(vertex); if (!missingNormalsBucket[materialIdx])
if (!missingNormals)
{ {
missingNormals = normIdx == uint.MaxValue; missingNormalsBucket[materialIdx] = normIdx == uint.MaxValue;
} }
if (!missingTangents) if (!missingTangentsBucket[materialIdx])
{ {
missingTangents = tanIdx == uint.MaxValue || btanIdx == uint.MaxValue; missingTangentsBucket[materialIdx] = tanIdx == uint.MaxValue || btanIdx == uint.MaxValue;
} }
} }
} }
// Per-material weld + optimize, collect intermediate results
using var partResults = new UnsafeList<GeometryPart>(numMaterials, AllocationHandle.FreeList);
for (var m = 0; m < numMaterials; m++)
{
ref var flatVertices = ref materialBuckets[m];
if (flatVertices.Count == 0)
{
flatVertices.Dispose();
continue;
}
var numIndices = (uint)flatVertices.Count; var numIndices = (uint)flatVertices.Count;
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList); using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
@@ -155,47 +220,108 @@ internal unsafe class MeshParsingWorkItem : IJob
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error); var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE) if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
{ {
return; flatVertices.Dispose();
continue;
} }
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices); MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
vertices = new UnsafeList<Vertex>((int)numUniqueVertices, _allocationHandle); // Allocate temporary per-part buffers (will be merged then disposed)
indices = new UnsafeList<uint>((int)numIndices, _allocationHandle); var partVertices = new UnsafeList<Vertex>((int)numUniqueVertices, AllocationHandle.FreeList);
var partIndices = new UnsafeList<uint>((int)numIndices, AllocationHandle.FreeList);
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(vertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex)); var finalVertexCount = MeshOptApi.OptimizeVertexFetch(partVertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
vertices.UnsafeSetCount((int)finalVertexCount); partVertices.UnsafeSetCount((int)finalVertexCount);
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint)); MemoryUtility.MemCpy(partIndices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
indices.UnsafeSetCount((int)numIndices); partIndices.UnsafeSetCount((int)numIndices);
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && missingNormals)) var part = new GeometryPart
{ {
MeshBuilder.ComputeNormal(vertices, indices); vertices = partVertices,
indices = partIndices,
materialIndex = m,
missingNormals = missingNormalsBucket[m],
missingTangents = missingTangentsBucket[m]
};
partResults.Add(part);
flatVertices.Dispose();
} }
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && missingTangents)) if (partResults.Count == 0)
{ {
MeshBuilder.ComputeTangents(vertices, indices); return null;
} }
// Merge all material parts into one unified vertex/index buffer
var totalVertexCount = 0;
var totalIndexCount = 0;
for (var i = 0; i < partResults.Count; i++)
{
totalVertexCount += partResults[i].vertices.Count;
totalIndexCount += partResults[i].indices.Count;
}
var mergedVertices = new UnsafeList<Vertex>(totalVertexCount, _allocationHandle);
var mergedIndices = new UnsafeList<uint>(totalIndexCount, _allocationHandle);
var materialParts = new UnsafeArray<MaterialPartInfo>(partResults.Count, _allocationHandle);
var vertexOffset = 0;
var indexOffset = 0;
for (var i = 0; i < partResults.Count; i++)
{
ref var part = ref partResults[i];
// Compute normals/tangents per-part before merge (requires local indices)
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && part.missingNormals))
{
MeshBuilder.ComputeNormal(part.vertices, part.indices);
}
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && part.missingTangents))
{
MeshBuilder.ComputeTangents(part.vertices, part.indices);
}
materialParts[i] = new MaterialPartInfo
{
materialIndex = part.materialIndex,
vertexStart = vertexOffset,
vertexCount = part.vertices.Count,
indexStart = indexOffset,
indexCount = part.indices.Count,
};
mergedVertices.AddRange(part.vertices.AsSpan());
// Rebase indices to global vertex space
for (var j = 0; j < part.indices.Count; j++)
{
mergedIndices.Add(part.indices[j] + (uint)vertexOffset);
}
vertexOffset += part.vertices.Count;
indexOffset += part.indices.Count;
part.Dispose();
}
return new GeometryMeshNode
{
Name = pMesh->name.ToString(),
LocalTransform = float4x4.identity,
Vertices = mergedVertices,
Indices = mergedIndices,
MaterialParts = materialParts,
};
} }
public void Execute(ref readonly JobExecutionContext context) public void Execute(ref readonly JobExecutionContext context)
{ {
if (!File.Exists(_filePath))
{
_taskCompletionSource.SetResult(Result.Failure("Invalid file path."));
return;
}
if (!Path.GetExtension(_filePath).Equals(".obj", StringComparison.OrdinalIgnoreCase)
&& !Path.GetExtension(_filePath).Equals(".fbx", StringComparison.OrdinalIgnoreCase))
{
_taskCompletionSource.SetResult(Result.Failure("Unsupported file format. Only .obj and .fbx are supported."));
return;
}
var error = new ufbx_error(); var error = new ufbx_error();
var load_Opts = new ufbx_load_opts var load_Opts = new ufbx_load_opts
{ {
@@ -231,68 +357,10 @@ internal unsafe class MeshParsingWorkItem : IJob
return; return;
} }
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList); var rootNode = ParseHierarchy(scene.Get()->root_node);
rootNode.Name = Path.GetFileNameWithoutExtension(_filePath);
var missingNormals = false; _taskCompletionSource.SetResult(Result.Success(rootNode));
var missingTangents = false;
for (var i = 0u; i < scene.Get()->nodes.count; i++)
{
var data = scene.Get()->nodes.data;
var node = scene.Get()->nodes.data[i];
if (node->is_root)
{
continue;
}
if (node->mesh != null)
{
}
}
var numIndices = (uint)flatVertices.Count;
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
var stream = new ufbx_vertex_stream
{
data = flatVertices.GetUnsafePtr(),
vertex_count = numIndices,
vertex_size = (nuint)sizeof(Vertex)
};
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
{
_taskCompletionSource.SetResult(Result.Failure($"Welding failed: {error.description}"));
return;
}
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
vertices = new UnsafeList<Vertex>((int)numUniqueVertices, _allocationHandle);
indices = new UnsafeList<uint>((int)numIndices, _allocationHandle);
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(vertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
vertices.UnsafeSetCount((int)finalVertexCount);
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
indices.UnsafeSetCount((int)numIndices);
if (_settings.NormalDataSource == VertexDataSource.Computed || (_settings.NormalDataSource == VertexDataSource.ComputedIfMissing && missingNormals))
{
MeshBuilder.ComputeNormal(vertices, indices);
}
if (_settings.TangentDataSource == VertexDataSource.Computed || (_settings.TangentDataSource == VertexDataSource.ComputedIfMissing && missingTangents))
{
MeshBuilder.ComputeTangents(vertices, indices);
}
_taskCompletionSource.SetResult(Result.Success());
} }
} }

View File

@@ -695,67 +695,17 @@ public static unsafe partial class MeshProcessor
return finalClusterCount; return finalClusterCount;
} }
public static void BuildMeshlets(MeshletMeshData* pMeshletData, ReadOnlyUnsafeCollection<Vertex> vertices, ReadOnlyUnsafeCollection<uint> indices) private struct MeshletContext
{ {
Logger.DebugAssert(pMeshletData->meshletCount > 0, "Mesh must have vertices to build meshlets."); public MeshletMeshData* data;
public int materialIndex;
var config = new ClodConfig
{
maxVertices = 64,
minTriangles = 32,
maxTriangles = 124,
partitionSpatial = true,
partitionSize = 16,
clusterSpatial = false,
clusterSplitFactor = 2.0f,
optimizeClusters = true,
optimizeClustersLevel = 1,
simplifyRatio = 0.5f,
simplifyThreshold = 0.85f,
simplifyErrorMergePrevious = 1.0f,
simplifyErrorFactorSloppy = 2.0f,
simplifyPermissive = true,
simplifyFallbackPermissive = false,
simplifyFallbackSloppy = true,
};
var clodMesh = new ClodMesh
{
vertexPositions = (float*)Unsafe.AsPointer(in vertices[0].position),
vertexCount = (nuint)vertices.Count,
vertexPositionsStride = (nuint)sizeof(Vertex),
vertexAttributes = (float*)Unsafe.AsPointer(in vertices[0].normal),
vertexAttributesStride = (nuint)sizeof(Vertex),
indices = (uint*)indices.GetUnsafePtr(),
indexCount = (nuint)indices.Count,
attributeProtectMask = 0, // TODO: We need to protect UVs and other vertex attributes to ensure they are not altered during simplification.
};
Build(in config, in clodMesh, pMeshletData, MeshletOutputCallback);
pMeshletData->meshletCount = pMeshletData->meshlets.IsCreated ? pMeshletData->meshlets.Count : 0;
if (pMeshletData->groups.IsCreated && pMeshletData->groups.Count > 0)
{
var maxLodLevel = 0u;
for (var i = 0; i < pMeshletData->groups.Count; i++)
{
maxLodLevel = Math.Max(maxLodLevel, pMeshletData->groups[i].lodLevel);
} }
pMeshletData->lodLevelCount = (int)maxLodLevel + 1; private static int MeshletOutputCallback(void* contextPtr, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster> clusters)
}
pMeshletData->materialSlotCount = 1;
}
private static int MeshletOutputCallback(void* context, ClodGroup group, ReadOnlyUnsafeCollection<ClodCluster> clusters)
{ {
var pMeshletData = (MeshletMeshData*)context; var context = (MeshletContext*)contextPtr;
var pMeshletData = context->data;
var materialIndex = context->materialIndex;
// Ensure lists are initialized // Ensure lists are initialized
if (!pMeshletData->groups.IsCreated) pMeshletData->groups = new UnsafeList<MeshletGroup>(16, AllocationHandle.Persistent); if (!pMeshletData->groups.IsCreated) pMeshletData->groups = new UnsafeList<MeshletGroup>(16, AllocationHandle.Persistent);
@@ -790,7 +740,7 @@ public static unsafe partial class MeshProcessor
groupIndex = (uint)pMeshletData->groups.Count - 1, groupIndex = (uint)pMeshletData->groups.Count - 1,
clusterError = cluster.bounds.error, clusterError = cluster.bounds.error,
parentError = group.simplified.error, parentError = group.simplified.error,
localMaterialIndex = 0, // TODO: support multiple materials localMaterialIndex = (byte)materialIndex,
lodLevel = (byte)group.depth, lodLevel = (byte)group.depth,
}; };
pMeshletData->meshlets.Add(meshlet); pMeshletData->meshlets.Add(meshlet);
@@ -815,6 +765,91 @@ public static unsafe partial class MeshProcessor
return 0; return 0;
} }
/// <summary>
/// Builds meshlets for a unified multi-material mesh.
/// Each <see cref="MaterialPartInfo"/> describes a material partition's index range within the unified buffer.
/// Meshlets are built per-part and tagged with the corresponding <c>localMaterialIndex</c>.
/// </summary>
public static void BuildMeshlets(MeshletMeshData* pMeshletData, ReadOnlyUnsafeCollection<Vertex> vertices, ReadOnlyUnsafeCollection<uint> indices, ReadOnlySpan<MaterialPartInfo> parts)
{
Logger.DebugAssert(pMeshletData->meshletCount == 0, "Meshlet data is not empty.");
Logger.DebugAssert(vertices.Count > 0, "Mesh must have vertices to build meshlets.");
Logger.DebugAssert(indices.Count > 0, "Mesh must have indices to build meshlets.");
Logger.DebugAssert(parts.Length > 0, "Must have at least one material part.");
var config = new ClodConfig
{
maxVertices = 64,
minTriangles = 32,
maxTriangles = 124,
partitionSpatial = true,
partitionSize = 16,
clusterSpatial = false,
clusterSplitFactor = 2.0f,
optimizeClusters = true,
optimizeClustersLevel = 1,
simplifyRatio = 0.5f,
simplifyThreshold = 0.85f,
simplifyErrorMergePrevious = 1.0f,
simplifyErrorFactorSloppy = 2.0f,
simplifyPermissive = true,
simplifyFallbackPermissive = false,
simplifyFallbackSloppy = true,
};
for (var i = 0; i < parts.Length; i++)
{
ref readonly var part = ref parts[i];
// Each part references a slice of the global index buffer,
// but vertex positions are the full unified buffer so global indices remain valid.
var clodMesh = new ClodMesh
{
vertexPositions = (float*)Unsafe.AsPointer(in vertices[0].position),
vertexCount = (nuint)vertices.Count,
vertexPositionsStride = (nuint)sizeof(Vertex),
vertexAttributes = (float*)Unsafe.AsPointer(in vertices[0].normal),
vertexAttributesStride = (nuint)sizeof(Vertex),
indices = (uint*)indices.GetUnsafePtr() + part.indexStart,
indexCount = (nuint)part.indexCount,
attributeProtectMask = 0, // TODO: Protect UVs at material boundaries.
};
var context = new MeshletContext
{
data = pMeshletData,
materialIndex = part.materialIndex
};
Build(in config, in clodMesh, &context, MeshletOutputCallback);
}
pMeshletData->meshletCount = pMeshletData->meshlets.IsCreated ? pMeshletData->meshlets.Count : 0;
if (pMeshletData->groups.IsCreated && pMeshletData->groups.Count > 0)
{
var maxLodLevel = 0u;
for (var j = 0; j < pMeshletData->groups.Count; j++)
{
maxLodLevel = Math.Max(maxLodLevel, pMeshletData->groups[j].lodLevel);
}
pMeshletData->lodLevelCount = (int)maxLodLevel + 1;
}
var maxMaterialSlot = 0;
for (var j = 0; j < parts.Length; j++)
{
maxMaterialSlot = Math.Max(maxMaterialSlot, parts[j].materialIndex);
}
pMeshletData->materialSlotCount = maxMaterialSlot + 1;
}
public static void BuildClusterLodHierarchy() public static void BuildClusterLodHierarchy()
{ {
// 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. // 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.

View File

@@ -1,11 +1,9 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Nvtt; using Ghost.Nvtt;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel; using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics.SPMD;
using System.IO.Hashing; using System.IO.Hashing;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -191,45 +189,41 @@ internal static partial class TextureProcessor
pCtx.Get()->SetCudaAcceleration(NvttApi.IsCudaSupported()); pCtx.Get()->SetCudaAcceleration(NvttApi.IsCudaSupported());
int edgeLength; var maxCubeMips = _mipLevels.Length;
using (var cubeSurface0 = new DisposablePtr<NvttCubeSurface>(NvttCubeSurface.Create())) var w0 = _mipLevels[0].width;
using (var mip0Surf = new DisposablePtr<NvttSurface>(NvttSurface.Create()))
if (!pCtx.Get()->OutputHeaderData(NvttTextureType.NVTT_TextureType_Cube, w0, w0, 1, maxCubeMips, false, pCompOpts.Get(), pOutOpts.Get()))
{ {
if (!mip0Surf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, _mipLevels[0].width, _mipLevels[0].height, 1, _mipLevels[0].data.GetUnsafePtr(), false, null)) return Result.Failure("Failed to output header for cube map.");
{
return Result.Failure<int>("Failed to set image data for NVTT compression.");
} }
cubeSurface0.Get()->Fold(mip0Surf.Get(), NvttCubeLayout.NVTT_CubeLayout_LatitudeLongitude);
edgeLength = cubeSurface0.Get()->EdgeLength();
}
pCtx.Get()->OutputHeaderData(NvttTextureType.NVTT_TextureType_Cube, edgeLength, edgeLength, 1, _mipLevels.Length, false, pCompOpts.Get(), pOutOpts.Get());
for (var level = 0; level < _mipLevels.Length; level++)
{
using var cubeSurface = new DisposablePtr<NvttCubeSurface>(NvttCubeSurface.Create());
using var mipSurf = new DisposablePtr<NvttSurface>(NvttSurface.Create());
mipSurf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, _mipLevels[level].width, _mipLevels[level].height, 1, _mipLevels[level].data.GetUnsafePtr(), false, null);
cubeSurface.Get()->Fold(mipSurf.Get(), NvttCubeLayout.NVTT_CubeLayout_LatitudeLongitude);
for (var face = 0; face < 6; face++) for (var face = 0; face < 6; face++)
{ {
var faceSurf = cubeSurface.Get()->Face(face); for (var level = 0; level < maxCubeMips; level++)
{
using var faceSurf = new DisposablePtr<NvttSurface>(NvttSurface.Create());
var w = _mipLevels[level].width;
var faceSize = w * w * _textureInfo.colorComponents;
var pSrcData = (float*)_mipLevels[level].data.GetUnsafePtr() + face * faceSize;
if (!faceSurf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, w, w, 1, pSrcData, false, null))
{
return Result.Failure("Failed to set image data for NVTT compression.");
}
if (_settings.Basic.IsSRGB) if (_settings.Basic.IsSRGB)
{ {
faceSurf->ToSrgb(null); faceSurf.Get()->ToSrgb(null);
} }
if (!pCtx.Get()->Compress(faceSurf, face, level, pCompOpts.Get(), pOutOpts.Get())) if (!pCtx.Get()->Compress(faceSurf.Get(), face, level, pCompOpts.Get(), pOutOpts.Get()))
{ {
return Result.Failure("Failed to compress mipmap."); return Result.Failure("Failed to compress cube map face.");
} }
} }
} }
return Result.Success(_mipLevels.Length); return Result.Success(maxCubeMips);
} }
public void Execute() public void Execute()
@@ -316,8 +310,54 @@ internal static partial class TextureProcessor
{ {
if (settings.Basic.TextureShape == TextureShape.TextureCube) if (settings.Basic.TextureShape == TextureShape.TextureCube)
{ {
var handle = GenerateMipHDRI(scheduler, textureInfo, out mipLevels); int maxCubeMips;
int edge;
UnsafeArray<float> baseCubeData;
unsafe
{
using var cubeSurface0 = new DisposablePtr<NvttCubeSurface>(NvttCubeSurface.Create());
using var mip0Surf = new DisposablePtr<NvttSurface>(NvttSurface.Create());
if (!mip0Surf.Get()->SetImageData(NvttInputFormat.NVTT_InputFormat_RGBA_32F, textureInfo.width, textureInfo.height, 1, (void*)textureInfo.pixelData, false, null))
{
return Result.Failure("Failed to set image data for cube map.");
}
cubeSurface0.Get()->Fold(mip0Surf.Get(), NvttCubeLayout.NVTT_CubeLayout_LatitudeLongitude);
edge = cubeSurface0.Get()->EdgeLength();
maxCubeMips = (int)Math.Floor(Math.Log2(edge)) + 1;
var pixelsPerFace = edge * edge;
var faceSize = pixelsPerFace * textureInfo.colorComponents;
baseCubeData = new UnsafeArray<float>(faceSize * 6, AllocationHandle.FreeList);
var channels = textureInfo.colorComponents;
var channelPtrs = stackalloc float*[channels];
for (var face = 0; face < 6; face++)
{
using var faceSurf = new DisposablePtr<NvttSurface>(cubeSurface0.Get()->Face(face));
// NVTT stores data in planar format: [RRRR...][GGGG...][BBBB...][AAAA...]
// We need to interleave into RGBARGBA... for our sampling code.
var pDst = (float*)baseCubeData.GetUnsafePtr() + face * faceSize;
for (var ch = 0; ch < channels; ch++)
{
channelPtrs[ch] = faceSurf.Get()->Channel(ch);
}
for (var p = 0; p < pixelsPerFace; p++)
{
for (var ch = 0; ch < channels; ch++)
{
pDst[p * channels + ch] = channelPtrs[ch][p];
}
}
}
}
var handle = GenerateMipHDRI(scheduler, textureInfo, baseCubeData, edge, maxCubeMips, out mipLevels);
await scheduler.WaitAsync(handle, cancellationToken); await scheduler.WaitAsync(handle, cancellationToken);
baseCubeData.Dispose();
} }
var workItem = new NvttPipelineTask(cachePath, textureInfo, settings, mipLevels); var workItem = new NvttPipelineTask(cachePath, textureInfo, settings, mipLevels);

View File

@@ -2,6 +2,7 @@ using Ghost.Core;
using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Mathematics.SPMD; using Misaki.HighPerformance.Mathematics.SPMD;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using static Misaki.HighPerformance.Mathematics.math; using static Misaki.HighPerformance.Mathematics.math;
@@ -72,34 +73,70 @@ internal static partial class TextureProcessor
return MathV.Normalize(sampleVec); return MathV.Normalize(sampleVec);
} }
// Maps a 3D direction vector to 2D equirectangular UVs
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector2<TFloat, float> DirToEquirectangularUV(Vector3<TFloat, float> dir) private static float3 CubemapUVToDir(int face, float u, float v)
{ {
var u = TFloat.Atan2(dir.z, dir.x); var sc = 2.0f * u - 1.0f;
var v = TFloat.Asin(dir.y); var tc = 1.0f - 2.0f * v;
u = u / (2.0f * PI) + 0.5f; float x = 0, y = 0, z = 0;
v = v / PI + 0.5f; switch (face)
return MathV.Create<TFloat, float>(u, v); {
case 0: x = 1.0f; y = tc; z = -sc; break;
case 1: x = -1.0f; y = tc; z = sc; break;
case 2: x = sc; y = 1.0f; z = -tc; break;
case 3: x = sc; y = -1.0f; z = tc; break;
case 4: x = sc; y = tc; z = 1.0f; break;
case 5: x = -sc; y = tc; z = -1.0f; break;
}
return normalize(float3(x, y, z));
} }
// Samples the source HDR image using bilinear interpolation (simplified to nearest neighbor for brevity here)
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector3<TFloat, float> SampleEquirectangularMap(float* img, int w, int h, int c, Vector3<TFloat, float> dir) private static Vector3<TFloat, float> SampleCubemap(float* img, int edge, int c, Vector3<TFloat, float> dir)
{ {
var uv = DirToEquirectangularUV(dir); var absX = TFloat.Abs(dir.x);
var absY = TFloat.Abs(dir.y);
var absZ = TFloat.Abs(dir.z);
// Nearest neighbor pixel coordinates var isXPos = dir.x >= TFloat.Zero;
var px = (uv.x * (w - 1.0f)).Cast<TInt, int>(); var isYPos = dir.y >= TFloat.Zero;
var py = (uv.y * (h - 1.0f)).Cast<TInt, int>(); var isZPos = dir.z >= TFloat.Zero;
// Clamp var maxAxis = TFloat.Max(TFloat.Max(absX, absY), absZ);
px = TInt.Clamp(px, TInt.Zero, w - 1);
py = TInt.Clamp(py, TInt.Zero, h - 1);
// Assuming float RGB array format var faceIndexF = TFloat.Select(maxAxis == absX,
var idx = (py * w + px) * c; TFloat.Select(isXPos, 0.0f, 1.0f),
TFloat.Select(maxAxis == absY,
TFloat.Select(isYPos, 2.0f, 3.0f),
TFloat.Select(isZPos, 4.0f, 5.0f)));
var faceIndex = faceIndexF.Cast<TInt, int>();
var sc = TFloat.Select(maxAxis == absX,
TFloat.Select(isXPos, -dir.z, dir.z),
TFloat.Select(maxAxis == absY,
dir.x,
TFloat.Select(isZPos, dir.x, -dir.x)));
var tc = TFloat.Select(maxAxis == absX,
dir.y,
TFloat.Select(maxAxis == absY,
TFloat.Select(isYPos, -dir.z, dir.z),
dir.y));
var u = 0.5f * (sc / maxAxis + 1.0f);
var v = 0.5f * (1.0f - tc / maxAxis);
var px = (u * (edge - 1.0f)).Cast<TInt, int>();
var py = (v * (edge - 1.0f)).Cast<TInt, int>();
px = TInt.Clamp(px, TInt.Zero, edge - 1);
py = TInt.Clamp(py, TInt.Zero, edge - 1);
var faceOffset = faceIndex * (edge * edge);
var idx = (faceOffset + py * edge + px) * c;
return MathV.GatherVector3<TFloat, float>(img, idx.GetUnsafePtr(), 1); return MathV.GatherVector3<TFloat, float>(img, idx.GetUnsafePtr(), 1);
} }
@@ -115,22 +152,20 @@ internal static partial class TextureProcessor
var pLevel = &pMipLevels[m]; var pLevel = &pMipLevels[m];
var w = pLevel->width; var w = pLevel->width;
var h = pLevel->height; var data = pLevel->data;
var pData = pLevel->data;
var local_i = loopIndex - pLevel->offset; var local_i = loopIndex - pLevel->offset;
var x = local_i % w;
var y = local_i / w;
var u = (float)x / (w - 1);
var v = (float)y / (h - 1);
var phi = (u - 0.5f) * 2.0f * PI; var faceArea = w * w;
var theta = (v - 0.5f) * PI; var face = local_i / faceArea;
var face_local_i = local_i % faceArea;
var x = face_local_i % w;
var y = face_local_i / w;
sincos(theta, out var sinTheta, out var cosTheta); var u = (x + 0.5f) / w;
sincos(phi, out var sinPhi, out var cosPhi); var v = (y + 0.5f) / w;
var N = float3(cosTheta * cosPhi, sinTheta, cosTheta * sinPhi);
N = normalize(N); var N = CubemapUVToDir(face, u, v);
// For split-sum, we assume View and Reflection directions equal the Normal // For split-sum, we assume View and Reflection directions equal the Normal
var V = N; var V = N;
@@ -173,7 +208,7 @@ internal static partial class TextureProcessor
L = MathV.Normalize(L); L = MathV.Normalize(L);
var NdotL = TFloat.Max(MathV.Dot(vN, L), TFloat.Zero); var NdotL = TFloat.Max(MathV.Dot(vN, L), TFloat.Zero);
var sampleColor = SampleEquirectangularMap(pImage, imageWidth, imageHeight, channelCount, L); var sampleColor = SampleCubemap(pImage, imageWidth, channelCount, L);
NdotL &= validLaneMask; NdotL &= validLaneMask;
@@ -208,10 +243,14 @@ internal static partial class TextureProcessor
} }
// Write to output mip array // Write to output mip array
var out_idx = (y * w + x) * channelCount; var out_idx = (face * (w * w) + y * w + x) * channelCount;
pData[out_idx] = prefilteredColor.x; data[out_idx] = prefilteredColor.x;
pData[out_idx + 1] = prefilteredColor.y; data[out_idx + 1] = prefilteredColor.y;
pData[out_idx + 2] = prefilteredColor.z; data[out_idx + 2] = prefilteredColor.z;
if (channelCount == 4)
{
data[out_idx + 3] = 1.0f;
}
} }
} }
@@ -251,11 +290,10 @@ internal static partial class TextureProcessor
return bits * 2.3283064365386963e-10f; // bits / 0x100000000 return bits * 2.3283064365386963e-10f; // bits / 0x100000000
} }
private static JobHandle GenerateMipHDRI(JobScheduler scheduler, TextureAssetHandler.TextureInfo textureInfo, out UnsafeArray<MipLevel> mipLevels) private static JobHandle GenerateMipHDRI(JobScheduler scheduler, TextureAssetHandler.TextureInfo textureInfo, UnsafeArray<float> baseCubeData, int edge, int totalMipLevels, out UnsafeArray<MipLevel> mipLevels)
{ {
Logger.DebugAssert(textureInfo.isHDR, "GenerateMipHDRI should only be called for HDR textures."); Logger.DebugAssert(textureInfo.isHDR, "GenerateMipHDRI should only be called for HDR textures.");
Logger.DebugAssert(textureInfo.colorComponents >= 3, "Texture must have at least 3 color components for RGB.");
var totalMipLevels = (int)Math.Floor(Math.Log2(Math.Max(textureInfo.width, textureInfo.height))) + 1;
mipLevels = new UnsafeArray<MipLevel>(totalMipLevels, AllocationHandle.FreeList); mipLevels = new UnsafeArray<MipLevel>(totalMipLevels, AllocationHandle.FreeList);
var radicalInverse_VdCLut = new UnsafeArray<float>(_SAMPLE_COUNT, AllocationHandle.FreeList); var radicalInverse_VdCLut = new UnsafeArray<float>(_SAMPLE_COUNT, AllocationHandle.FreeList);
@@ -265,24 +303,23 @@ internal static partial class TextureProcessor
radicalInverse_VdCLut[i] = RadicalInverse_VdC(i); radicalInverse_VdCLut[i] = RadicalInverse_VdC(i);
} }
int w, h; int w;
var totalPixel = 0; var totalPixel = 0;
for (var i = 0; i < totalMipLevels; i++) for (var i = 0; i < totalMipLevels; i++)
{ {
w = Math.Max(1, textureInfo.width >> i); w = Math.Max(1, edge >> i);
h = Math.Max(1, textureInfo.height >> i);
mipLevels[i] = new MipLevel mipLevels[i] = new MipLevel
{ {
data = new UnsafeArray<float>(w * h * textureInfo.colorComponents, AllocationHandle.FreeList), data = new UnsafeArray<float>(w * w * 6 * textureInfo.colorComponents, AllocationHandle.FreeList),
width = w, width = w,
height = h, height = w,
offset = totalPixel, offset = totalPixel,
roughness = (float)i / (totalMipLevels - 1) // Linear roughness from 0 to 1 across mip levels roughness = (float)i / (totalMipLevels - 1) // Linear roughness from 0 to 1 across mip levels
}; };
totalPixel += w * h; totalPixel += w * w * 6;
} }
JobHandle handle; JobHandle handle;
@@ -292,11 +329,11 @@ internal static partial class TextureProcessor
{ {
var job = new GGXMipGenerationJobSPMD<WideLane<float>, WideLane<int>> var job = new GGXMipGenerationJobSPMD<WideLane<float>, WideLane<int>>
{ {
pImage = (float*)textureInfo.pixelData, pImage = (float*)baseCubeData.GetUnsafePtr(),
pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(), pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(),
pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(), pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(),
imageWidth = textureInfo.width, imageWidth = edge,
imageHeight = textureInfo.height, imageHeight = edge,
numMipLevels = totalMipLevels, numMipLevels = totalMipLevels,
channelCount = textureInfo.colorComponents, channelCount = textureInfo.colorComponents,
}; };
@@ -307,11 +344,11 @@ internal static partial class TextureProcessor
{ {
var job = new GGXMipGenerationJobSPMD<ScalarLane<float>, ScalarLane<int>> var job = new GGXMipGenerationJobSPMD<ScalarLane<float>, ScalarLane<int>>
{ {
pImage = (float*)textureInfo.pixelData, pImage = (float*)baseCubeData.GetUnsafePtr(),
pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(), pMipLevels = (MipLevel*)mipLevels.GetUnsafePtr(),
pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(), pRadicalInverse_VdCLut = (float*)radicalInverse_VdCLut.GetUnsafePtr(),
imageWidth = textureInfo.width, imageWidth = edge,
imageHeight = textureInfo.height, imageHeight = edge,
numMipLevels = totalMipLevels, numMipLevels = totalMipLevels,
channelCount = textureInfo.colorComponents, channelCount = textureInfo.colorComponents,
}; };

View File

@@ -159,7 +159,7 @@ internal sealed partial class ImportCoordinator : IDisposable
return string.Empty; return string.Empty;
} }
var hash = XxHash128.HashToUInt128(JsonSerializer.SerializeToUtf8Bytes(settings)); var hash = XxHash128.HashToUInt128(JsonSerializer.SerializeToUtf8Bytes(settings, settings.GetType()));
Span<byte> bytes = stackalloc byte[16]; Span<byte> bytes = stackalloc byte[16];
Unsafe.WriteUnaligned(ref bytes[0], hash); Unsafe.WriteUnaligned(ref bytes[0], hash);

View File

@@ -66,13 +66,13 @@ internal static class ActivationHandler
AllocationManager.Initialize(opts); AllocationManager.Initialize(opts);
//var assetRegistry = App.GetService<IAssetRegistry>(); var assetRegistry = App.GetService<IAssetRegistry>();
//var engineCore = App.GetService<EngineCore>(); var engineCore = App.GetService<EngineCore>();
//assetRegistry.OnAssetImported += (sender, e) => assetRegistry.OnAssetImported += (sender, e) =>
//{ {
// engineCore.AssetManager.ReimportAsset(e); engineCore.AssetManager.ReimportAsset(e);
//}; };
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
} }

View File

@@ -1,13 +1,13 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Ghost.Core.Utilities;
using Ghost.Editor.Core; using Ghost.Editor.Core;
using Ghost.Editor.Core.Assets;
using Ghost.Editor.Core.Contracts; using Ghost.Editor.Core.Contracts;
using Ghost.Editor.Core.Utilities; using Ghost.Editor.Core.Utilities;
using Ghost.Editor.Models; using Ghost.Editor.Models;
using Ghost.Editor.Core.AssetHandler;
using System.Collections.ObjectModel;
using Microsoft.UI.Dispatching;
using Ghost.Engine; using Ghost.Engine;
using Ghost.Core.Utilities; using Microsoft.UI.Dispatching;
using System.Collections.ObjectModel;
namespace Ghost.Editor.ViewModels.Controls; namespace Ghost.Editor.ViewModels.Controls;
@@ -86,7 +86,7 @@ internal partial class ContentBrowserViewModel : ObservableObject
if (!Files.Any(f => string.Equals(f.Path, fullPath, StringComparison.OrdinalIgnoreCase))) if (!Files.Any(f => string.Equals(f.Path, fullPath, StringComparison.OrdinalIgnoreCase)))
{ {
var isDir = Directory.Exists(fullPath); var isDir = Directory.Exists(fullPath);
AssetType assetType = AssetType.Unknown; var assetType = AssetType.Unknown;
if (!isDir) if (!isDir)
{ {
var ext = Path.GetExtension(fullPath); var ext = Path.GetExtension(fullPath);

View File

@@ -8,12 +8,13 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS</DefineConstants> <DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_MIMALLOC</DefineConstants>
<IsAotCompatible>True</IsAotCompatible> <IsAotCompatible>True</IsAotCompatible>
<IsTrimmable>True</IsTrimmable> <IsTrimmable>True</IsTrimmable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DefineConstants>$(DefineConstants);MHP_ENABLE_MIMALLOC</DefineConstants>
<IsAotCompatible>True</IsAotCompatible> <IsAotCompatible>True</IsAotCompatible>
<IsTrimmable>True</IsTrimmable> <IsTrimmable>True</IsTrimmable>
</PropertyGroup> </PropertyGroup>
@@ -28,6 +29,7 @@
<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.0" />
<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.Windows" Version="10.0.26100.6" /> <PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.6" />
</ItemGroup> </ItemGroup>

View File

@@ -33,7 +33,7 @@ internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
foreach (var symbol in array) foreach (var symbol in array)
{ {
var attribute = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "Ghost.Editor.Core.AssetHandler.CustomAssetHandlerAttribute"); var attribute = symbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "Ghost.Editor.Core.Assets.CustomAssetHandlerAttribute");
if (attribute == null) if (attribute == null)
{ {
continue; continue;
@@ -44,7 +44,7 @@ internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
var extensions = $"new string[] {{ {string.Join(", ", extensionsTypesConstants.Select(v => v.ToCSharpString()))} }}"; var extensions = $"new string[] {{ {string.Join(", ", extensionsTypesConstants.Select(v => v.ToCSharpString()))} }}";
var version = (int)attribute.ConstructorArguments[2].Value; var version = (int)attribute.ConstructorArguments[2].Value;
sb.Append($" global::Ghost.Editor.Core.AssetHandler.AssetHandlerRegistry.RegisterHandler(new {symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}(), Guid.Parse(\"{id}\"), {extensions}, {version});"); sb.AppendLine($" global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterHandler(new {symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}(), Guid.Parse(\"{id}\"), {extensions}, {version});");
} }
var registerTypeName = "g_assethandler_registeration"; var registerTypeName = "g_assethandler_registeration";
@@ -71,7 +71,7 @@ internal static partial class {registerTypeName}
return null; return null;
} }
var iHandlerSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.AssetHandler.IAssetHandler"); var iHandlerSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.Assets.IAssetHandler");
if (iHandlerSymbol == null) if (iHandlerSymbol == null)
{ {
return null; return null;
@@ -114,7 +114,7 @@ internal class IAssetSettingsRegistrationGenerator : IIncrementalGenerator
foreach (var iface in array) foreach (var iface in array)
{ {
sb.AppendLine($" global::Ghost.Editor.Core.AssetHandler.AssetHandlerRegistry.RegisterIAssetSettingsType(typeof({iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}), \"{iface.Name}\");"); sb.AppendLine($" global::Ghost.Editor.Core.Assets.AssetHandlerRegistry.RegisterIAssetSettingsType(typeof({iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}), \"{iface.Name}\");");
} }
var registerTypeName = "g_iassetsettings_registeration"; var registerTypeName = "g_iassetsettings_registeration";
@@ -141,7 +141,7 @@ internal static partial class {registerTypeName}
return null; return null;
} }
var iSettingsSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.AssetHandler.IAssetSettings"); var iSettingsSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.Assets.IAssetSettings");
if (iSettingsSymbol == null) if (iSettingsSymbol == null)
{ {
return null; return null;

View File

@@ -190,19 +190,18 @@ public static unsafe class MeshBuilder
/// </summary> /// </summary>
/// <param name="vertices">The vertex list.</param> /// <param name="vertices">The vertex list.</param>
/// <param name="indices">The index list.</param> /// <param name="indices">The index list.</param>
public static void ComputeNormal(UnsafeList<Vertex> vertices, UnsafeList<uint> indices) public static void ComputeNormal(Span<Vertex> vertices, Span<uint> indices)
{ {
if (!vertices.IsCreated || vertices.Count < 3 if (vertices.Length < 3 || indices.Length < 3)
|| !indices.IsCreated || indices.Count < 3)
{ {
return; return;
} }
for (var i = 0; i < indices.Count; i += 3) for (var i = 0; i < indices.Length; i += 3)
{ {
var i0 = indices[i]; var i0 = (int)indices[i];
var i1 = indices[i + 1]; var i1 = (int)indices[i + 1];
var i2 = indices[i + 2]; var i2 = (int)indices[i + 2];
var v0 = vertices[i0]; var v0 = vertices[i0];
var v1 = vertices[i1]; var v1 = vertices[i1];
@@ -217,7 +216,7 @@ public static unsafe class MeshBuilder
vertices[i2].normal.xyz += faceNormal; vertices[i2].normal.xyz += faceNormal;
} }
for (var i = 0; i < vertices.Count; i++) for (var i = 0; i < vertices.Length; i++)
{ {
vertices[i].normal = math.normalize(vertices[i].normal); vertices[i].normal = math.normalize(vertices[i].normal);
} }
@@ -228,16 +227,16 @@ public static unsafe class MeshBuilder
/// </summary> /// </summary>
/// <param name="vertices">The vertex list.</param> /// <param name="vertices">The vertex list.</param>
/// <param name="indices">The index list.</param> /// <param name="indices">The index list.</param>
public static void ComputeTangents(UnsafeList<Vertex> vertices, UnsafeList<uint> indices) public static void ComputeTangents(Span<Vertex> vertices, Span<uint> indices)
{ {
using var scope = AllocationManager.CreateStackScope(); using var scope = AllocationManager.CreateStackScope();
var bitangents = new UnsafeArray<float3>(vertices.Count, scope.AllocationHandle, AllocationOption.Clear); var bitangents = new UnsafeArray<float3>(vertices.Length, scope.AllocationHandle, AllocationOption.Clear);
for (var i = 0; i < indices.Count; i += 3) for (var i = 0; i < indices.Length; i += 3)
{ {
var i0 = indices[i]; var i0 = (int)indices[i];
var i1 = indices[i + 1]; var i1 = (int)indices[i + 1];
var i2 = indices[i + 2]; var i2 = (int)indices[i + 2];
var v0 = vertices[i0]; var v0 = vertices[i0];
var v1 = vertices[i1]; var v1 = vertices[i1];
@@ -258,7 +257,7 @@ public static unsafe class MeshBuilder
for (var j = 0; j < 3; j++) for (var j = 0; j < 3; j++)
{ {
var idx = indices[i + j]; var idx = (int)indices[i + j];
var t = vertices[idx].tangent; var t = vertices[idx].tangent;
vertices[idx].tangent.xyz = t.xyz + tangent.xyz; vertices[idx].tangent.xyz = t.xyz + tangent.xyz;
@@ -266,7 +265,7 @@ public static unsafe class MeshBuilder
} }
} }
for (var i = 0; i < vertices.Count; i++) for (var i = 0; i < vertices.Length; i++)
{ {
var n = vertices[i].normal; var n = vertices[i].normal;
var t = vertices[i].tangent.xyz; var t = vertices[i].tangent.xyz;