diff --git a/src/Editor/Ghost.Editor.Core/Assets/AssetHandler.cs b/src/Editor/Ghost.Editor.Core/Assets/AssetHandler.cs index 8ba385c..fb6edaa 100644 --- a/src/Editor/Ghost.Editor.Core/Assets/AssetHandler.cs +++ b/src/Editor/Ghost.Editor.Core/Assets/AssetHandler.cs @@ -45,17 +45,12 @@ public interface IAssetHandler public interface IImportableAssetHandler : IAssetHandler { bool CanExport { get; } - ValueTask ImportAsync(string sourcePath, string targetPath, Guid id, IAssetSettings? settings, CancellationToken token = default); + ValueTask> ImportAsync(string sourcePath, string targetPath, Guid id, IAssetSettings? settings, CancellationToken token = default); ValueTask ExportAsync(string assetPath, string targetPath, IAssetExportOptions? options, CancellationToken token = default); } public readonly record struct ImportedSubAsset(Guid Guid, string Kind, string DisplayName, string StablePath, string VirtualSourcePath, Guid HandlerTypeId); -public interface ISubAssetImportableAssetHandler : IImportableAssetHandler -{ - ValueTask> ImportWithSubAssetsAsync(string sourcePath, string targetPath, Guid id, IAssetSettings? settings, CancellationToken token = default); -} - public interface IPackableAssetHandler : IAssetHandler { ValueTask PackAsync(string assetPath, MemoryStream targetStream, CancellationToken token = default); diff --git a/src/Editor/Ghost.Editor.Core/Assets/MeshAssetHandler.cs b/src/Editor/Ghost.Editor.Core/Assets/MeshAssetHandler.cs index 0327d56..70a8db6 100644 --- a/src/Editor/Ghost.Editor.Core/Assets/MeshAssetHandler.cs +++ b/src/Editor/Ghost.Editor.Core/Assets/MeshAssetHandler.cs @@ -1,6 +1,7 @@ using Ghost.Core; -using Ghost.Engine; +using Ghost.Core.Utilities; using Ghost.Editor.Core.Services; +using Ghost.Engine; using Ghost.Graphics.Core; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; @@ -382,7 +383,7 @@ internal class FbxAssetSettings : MeshAssetSettings } [CustomAssetHandler(FBXAsset.GUID, [".fbx", ".obj"], 1)] -internal class FBXAssetHandler : ISubAssetImportableAssetHandler, IPackableAssetHandler +internal class FBXAssetHandler : IImportableAssetHandler, IPackableAssetHandler { public AssetType RuntimeAssetType => AssetType.Mesh; @@ -422,12 +423,7 @@ internal class FBXAssetHandler : ISubAssetImportableAssetHandler, IPackableAsset return ValueTask.FromResult(Result.Failure("Saving model assets is not supported yet.")); } - public async ValueTask ImportAsync(string sourcePath, string targetPath, Guid id, IAssetSettings? settings, CancellationToken token = default) - { - return await ImportWithSubAssetsAsync(sourcePath, targetPath, id, settings, token).ConfigureAwait(false); - } - - public async ValueTask> ImportWithSubAssetsAsync(string sourcePath, string targetPath, Guid id, IAssetSettings? settings, CancellationToken token = default) + public async ValueTask> ImportAsync(string sourcePath, string targetPath, Guid id, IAssetSettings? settings, CancellationToken token = default) { if (!File.Exists(sourcePath)) { @@ -557,72 +553,69 @@ internal class FBXAssetHandler : ISubAssetImportableAssetHandler, IPackableAsset return manifestNode; } - private static ValueTask<(int materialSlotCount, int lodLevelCount)> WriteMeshContentAsync(string targetPath, GeometryMeshNode geometry, CancellationToken token) + private static async ValueTask<(int materialSlotCount, int lodLevelCount)> WriteMeshContentAsync(string targetPath, GeometryMeshNode geometry, CancellationToken token) { - unsafe + var meshletData = new MeshletMeshData(); + try { - var meshletData = new MeshletMeshData(); - try + MeshProcessor.BuildMeshlets(ref meshletData, geometry.Vertices.AsReadOnly(), geometry.Indices.AsReadOnly(), geometry.MaterialParts.AsSpan()); + MeshProcessor.BuildClusterLodHierarchy(ref meshletData); + + var bounds = ComputeBounds(geometry.Vertices); + var header = new MeshContentHeader { - MeshProcessor.BuildMeshlets(&meshletData, geometry.Vertices.AsReadOnly(), geometry.Indices.AsReadOnly(), geometry.MaterialParts.AsSpan()); - MeshProcessor.BuildClusterLodHierarchy(&meshletData); + magic = MeshContentHeader.MAGIC, + version = MeshContentHeader.VERSION, + vertexCount = (uint)geometry.Vertices.Count, + indexCount = (uint)geometry.Indices.Count, + materialPartCount = (uint)geometry.MaterialParts.Length, + meshletCount = (uint)meshletData.meshlets.Count, + meshletGroupCount = (uint)meshletData.groups.Count, + meshletHierarchyNodeCount = (uint)meshletData.hierarchyNodes.Count, + meshletVertexCount = (uint)meshletData.meshletVertices.Count, + meshletTriangleCount = (uint)meshletData.meshletTriangles.Count, + materialSlotCount = (uint)meshletData.materialSlotCount, + lodLevelCount = (uint)meshletData.lodLevelCount, + boundsMin = bounds.Min, + boundsMax = bounds.Max, + }; - var bounds = ComputeBounds(geometry.Vertices); - var header = new MeshContentHeader - { - magic = MeshContentHeader.MAGIC, - version = MeshContentHeader.VERSION, - vertexCount = (uint)geometry.Vertices.Count, - indexCount = (uint)geometry.Indices.Count, - materialPartCount = (uint)geometry.MaterialParts.Length, - meshletCount = (uint)meshletData.meshlets.Count, - meshletGroupCount = (uint)meshletData.groups.Count, - meshletHierarchyNodeCount = (uint)meshletData.hierarchyNodes.Count, - meshletVertexCount = (uint)meshletData.meshletVertices.Count, - meshletTriangleCount = (uint)meshletData.meshletTriangles.Count, - materialSlotCount = (uint)meshletData.materialSlotCount, - lodLevelCount = (uint)meshletData.lodLevelCount, - boundsMin = bounds.Min, - boundsMax = bounds.Max, - }; + using var stream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None); + stream.Write(header); - using var stream = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None); - WriteStruct(stream, in header); + header.vertexOffset = (ulong)stream.Position; + await stream.WriteAsync>(geometry.Vertices, token); - header.vertexOffset = (ulong)stream.Position; - WriteSpan(stream, geometry.Vertices.AsSpan()); + header.indexOffset = (ulong)stream.Position; + await stream.WriteAsync>(geometry.Indices, token); - header.indexOffset = (ulong)stream.Position; - WriteSpan(stream, geometry.Indices.AsSpan()); + header.materialPartOffset = (ulong)stream.Position; + WriteMaterialParts(stream, geometry.MaterialParts.AsSpan()); - header.materialPartOffset = (ulong)stream.Position; - WriteMaterialParts(stream, geometry.MaterialParts.AsSpan()); + header.meshletOffset = (ulong)stream.Position; + await stream.WriteAsync>(meshletData.meshlets, token); - header.meshletOffset = (ulong)stream.Position; - WriteSpan(stream, meshletData.meshlets.AsSpan()); + header.meshletGroupOffset = (ulong)stream.Position; + await stream.WriteAsync>(meshletData.groups, token); - header.meshletGroupOffset = (ulong)stream.Position; - WriteSpan(stream, meshletData.groups.AsSpan()); + header.meshletHierarchyNodeOffset = (ulong)stream.Position; + await stream.WriteAsync>(meshletData.hierarchyNodes, token); - header.meshletHierarchyNodeOffset = (ulong)stream.Position; - WriteSpan(stream, meshletData.hierarchyNodes.AsSpan()); + header.meshletVertexOffset = (ulong)stream.Position; + await stream.WriteAsync>(meshletData.meshletVertices, token); - header.meshletVertexOffset = (ulong)stream.Position; - WriteSpan(stream, meshletData.meshletVertices.AsSpan()); + header.meshletTriangleOffset = (ulong)stream.Position; + await stream.WriteAsync>(meshletData.meshletTriangles, token); - header.meshletTriangleOffset = (ulong)stream.Position; - WriteSpan(stream, meshletData.meshletTriangles.AsSpan()); + stream.Position = 0; + stream.Write(header); + stream.Flush(); - stream.Position = 0; - WriteStruct(stream, in header); - stream.Flush(); - - return ValueTask.FromResult((meshletData.materialSlotCount, meshletData.lodLevelCount)); - } - finally - { - meshletData.Dispose(); - } + return (meshletData.materialSlotCount, meshletData.lodLevelCount); + } + finally + { + meshletData.Dispose(); } } @@ -660,7 +653,7 @@ internal class FBXAssetHandler : ISubAssetImportableAssetHandler, IPackableAsset } var chars = value.ToCharArray(); - for (var i = 0; i < chars.Length; i++) + for (var i = 0; i < value.Length; i++) { if (chars[i] == '/' || chars[i] == '\\' || chars[i] == '#') { @@ -678,7 +671,7 @@ internal class FBXAssetHandler : ISubAssetImportableAssetHandler, IPackableAsset return; } - Span buffer = parts.Length <= 64 + var buffer = parts.Length <= 64 ? stackalloc MeshContentMaterialPart[parts.Length] : new MeshContentMaterialPart[parts.Length]; @@ -694,24 +687,6 @@ internal class FBXAssetHandler : ISubAssetImportableAssetHandler, IPackableAsset }; } - WriteSpan(stream, buffer); - } - - private static void WriteStruct(Stream stream, ref readonly T value) - where T : unmanaged - { - var span = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in value, 1)); - stream.Write(span); - } - - private static void WriteSpan(Stream stream, ReadOnlySpan value) - where T : unmanaged - { - if (value.IsEmpty) - { - return; - } - - stream.Write(MemoryMarshal.AsBytes(value)); + stream.Write(buffer); } } diff --git a/src/Editor/Ghost.Editor.Core/Assets/MeshProcessor.Import.cs b/src/Editor/Ghost.Editor.Core/Assets/MeshProcessor.Import.cs index af26767..3ed721a 100644 --- a/src/Editor/Ghost.Editor.Core/Assets/MeshProcessor.Import.cs +++ b/src/Editor/Ghost.Editor.Core/Assets/MeshProcessor.Import.cs @@ -10,6 +10,7 @@ using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.Mathematics; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; @@ -126,7 +127,7 @@ internal readonly unsafe struct MeshParsingJob : IJob for (var i = 0; i < numMaterials; i++) { - materialBuckets[i] = new UnsafeList(1024, AllocationHandle.FreeList); + materialBuckets[i] = new UnsafeList(40960, AllocationHandle.FreeList); } var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u); diff --git a/src/Editor/Ghost.Editor.Core/Assets/MeshProcessor.Meshlet.cs b/src/Editor/Ghost.Editor.Core/Assets/MeshProcessor.Meshlet.cs index 665967e..bb1eb52 100644 --- a/src/Editor/Ghost.Editor.Core/Assets/MeshProcessor.Meshlet.cs +++ b/src/Editor/Ghost.Editor.Core/Assets/MeshProcessor.Meshlet.cs @@ -158,13 +158,10 @@ public unsafe struct ClodCluster public nuint localIndexCount; } -/// -/// Delegate type for processing generated LOD groups. -/// -public unsafe delegate int ClodOutputDelegate(void* context, ClodGroup group, ReadOnlyUnsafeCollection clusters); - public static unsafe partial class MeshProcessor { + private delegate int ClodOutputDelegate(ref MeshletContext context, ClodGroup group, ReadOnlyUnsafeCollection clusters); + private static ClodBounds ComputeBounds(ref readonly ClodMesh mesh, UnsafeList indices, float error) { var bounds = MeshOptApi.ComputeClusterBounds((uint*)indices.GetUnsafePtr(), (nuint)indices.Count, mesh.vertexPositions, mesh.vertexCount, mesh.vertexPositionsStride); @@ -391,7 +388,7 @@ public static unsafe partial class MeshProcessor return partitions; } - private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList clusters, UnsafeList group, ClodBounds simplified, int depth, void* outputContext, ClodOutputDelegate? outputCallback) + private static int OutputGroup(ref readonly ClodConfig config, ref readonly ClodMesh mesh, UnsafeList clusters, UnsafeList group, ClodBounds simplified, int depth, ref MeshletContext outputContext, ClodOutputDelegate? outputCallback) { using var groupClusters = new UnsafeList(group.Count, AllocationHandle.FreeList); @@ -415,7 +412,7 @@ public static unsafe partial class MeshProcessor var clodGroup = new ClodGroup { depth = depth, simplified = simplified }; var result = outputCallback != null - ? outputCallback(outputContext, clodGroup, groupClusters.AsReadOnly()) + ? outputCallback(ref outputContext, clodGroup, groupClusters.AsReadOnly()) : -1; return result; @@ -575,7 +572,7 @@ public static unsafe partial class MeshProcessor /// 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(ref readonly ClodConfig config, ref readonly ClodMesh mesh, void* outputContext, ClodOutputDelegate? outputCallback) + private static nuint Build(ref readonly ClodConfig config, ref readonly ClodMesh mesh, ref MeshletContext outputContext, ClodOutputDelegate? outputCallback) { Logger.DebugAssert(mesh.vertexAttributesStride % sizeof(float) == 0, "vertexAttributesStride must be a multiple of sizeof(float)"); @@ -643,13 +640,13 @@ public static unsafe partial class MeshProcessor if ((nuint)simplified.Length > (nuint)(merged.Count * config.simplifyThreshold)) { bounds.error = float.MaxValue; - OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); + OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, ref outputContext, outputCallback); continue; } bounds.error = Math.Max(bounds.error * config.simplifyErrorMergePrevious, error) + error * config.simplifyErrorMergeAdditive; - var refined = OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, outputContext, outputCallback); + var refined = OutputGroup(in config, in mesh, clusters, groups[i], bounds, depth, ref outputContext, outputCallback); for (var j = 0; j < groups[i].Count; j++) { @@ -678,7 +675,7 @@ public static unsafe partial class MeshProcessor { var bounds = clusters[pending[0]].bounds; bounds.error = float.MaxValue; - OutputGroup(in config, in mesh, clusters, pending, bounds, depth, outputContext, outputCallback); + OutputGroup(in config, in mesh, clusters, pending, bounds, depth, ref outputContext, outputCallback); } var finalClusterCount = (nuint)clusters.Count; @@ -691,34 +688,33 @@ public static unsafe partial class MeshProcessor return finalClusterCount; } - private struct MeshletContext + private ref struct MeshletContext { - public MeshletMeshData* data; + public ref MeshletMeshData data; public int materialIndex; } - private static int MeshletOutputCallback(void* contextPtr, ClodGroup group, ReadOnlyUnsafeCollection clusters) + private static int MeshletOutputCallback(ref MeshletContext context, ClodGroup group, ReadOnlyUnsafeCollection clusters) { - var context = (MeshletContext*)contextPtr; - var pMeshletData = context->data; - var materialIndex = context->materialIndex; + ref var meshletData = ref context.data; + var materialIndex = context.materialIndex; // Ensure lists are initialized - if (!pMeshletData->groups.IsCreated) pMeshletData->groups = new UnsafeList(16, AllocationHandle.Persistent); - if (!pMeshletData->meshlets.IsCreated) pMeshletData->meshlets = new UnsafeList(64, AllocationHandle.Persistent); - if (!pMeshletData->meshletVertices.IsCreated) pMeshletData->meshletVertices = new UnsafeList(128, AllocationHandle.Persistent); - if (!pMeshletData->meshletTriangles.IsCreated) pMeshletData->meshletTriangles = new UnsafeList(128, AllocationHandle.Persistent); + if (!meshletData.groups.IsCreated) meshletData.groups = new UnsafeList(16, AllocationHandle.Persistent); + if (!meshletData.meshlets.IsCreated) meshletData.meshlets = new UnsafeList(64, AllocationHandle.Persistent); + if (!meshletData.meshletVertices.IsCreated) meshletData.meshletVertices = new UnsafeList(128, AllocationHandle.Persistent); + if (!meshletData.meshletTriangles.IsCreated) meshletData.meshletTriangles = new UnsafeList(128, AllocationHandle.Persistent); var meshletGroup = new MeshletGroup { boundingSphere = new SphereBounds(group.simplified.center, group.simplified.radius), boundingBox = new AABB(group.simplified.center - group.simplified.radius, group.simplified.center + group.simplified.radius), parentError = group.simplified.error, - meshletStartIndex = (uint)pMeshletData->meshlets.Count, + meshletStartIndex = (uint)meshletData.meshlets.Count, meshletCount = (uint)clusters.Count, lodLevel = (uint)group.depth }; - pMeshletData->groups.Add(meshletGroup); + meshletData.groups.Add(meshletGroup); for (var i = 0; i < clusters.Count; i++) { @@ -731,20 +727,20 @@ public static unsafe partial class MeshProcessor boundingBox = new AABB(cluster.bounds.center - cluster.bounds.radius, cluster.bounds.center + cluster.bounds.radius), vertexCount = (byte)cluster.vertexCount, triangleCount = (byte)(cluster.localIndexCount / 3), - vertexOffset = (uint)pMeshletData->meshletVertices.Count, - triangleOffset = (uint)pMeshletData->meshletTriangles.Count, - groupIndex = (uint)pMeshletData->groups.Count - 1, + vertexOffset = (uint)meshletData.meshletVertices.Count, + triangleOffset = (uint)meshletData.meshletTriangles.Count, + groupIndex = (uint)meshletData.groups.Count - 1, clusterError = cluster.bounds.error, parentError = group.simplified.error, localMaterialIndex = (byte)materialIndex, lodLevel = (byte)group.depth, }; - pMeshletData->meshlets.Add(meshlet); + meshletData.meshlets.Add(meshlet); // Add unique vertices for (nuint j = 0; j < cluster.vertexCount; j++) { - pMeshletData->meshletVertices.Add(cluster.uniqueVertices[j]); + meshletData.meshletVertices.Add(cluster.uniqueVertices[j]); } // Add local triangles (packed into uints) var triangleCount = cluster.localIndexCount / 3; @@ -754,11 +750,11 @@ public static unsafe partial class MeshProcessor uint i1 = cluster.localIndices[j * 3 + 1]; uint i2 = cluster.localIndices[j * 3 + 2]; var packedTriangle = i0 | (i1 << 8) | (i2 << 16); - pMeshletData->meshletTriangles.Add(packedTriangle); + meshletData.meshletTriangles.Add(packedTriangle); } } - return pMeshletData->groups.Count - 1; + return meshletData.groups.Count - 1; } /// @@ -766,9 +762,9 @@ public static unsafe partial class MeshProcessor /// Each describes a material partition's index range within the unified buffer. /// Meshlets are built per-part and tagged with the corresponding localMaterialIndex. /// - public static void BuildMeshlets(MeshletMeshData* pMeshletData, ReadOnlyUnsafeCollection vertices, ReadOnlyUnsafeCollection indices, ReadOnlySpan parts) + public static void BuildMeshlets(ref MeshletMeshData meshletData, ReadOnlyUnsafeCollection vertices, ReadOnlyUnsafeCollection indices, ReadOnlySpan parts) { - Logger.DebugAssert(pMeshletData->meshletCount == 0, "Meshlet data is not empty."); + Logger.DebugAssert(meshletData.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."); @@ -817,24 +813,24 @@ public static unsafe partial class MeshProcessor var context = new MeshletContext { - data = pMeshletData, + data = ref meshletData, materialIndex = part.materialIndex }; - Build(in config, in clodMesh, &context, MeshletOutputCallback); + Build(in config, in clodMesh, ref context, MeshletOutputCallback); } - pMeshletData->meshletCount = pMeshletData->meshlets.IsCreated ? pMeshletData->meshlets.Count : 0; + meshletData.meshletCount = meshletData.meshlets.IsCreated ? meshletData.meshlets.Count : 0; - if (pMeshletData->groups.IsCreated && pMeshletData->groups.Count > 0) + if (meshletData.groups.IsCreated && meshletData.groups.Count > 0) { var maxLodLevel = 0u; - for (var j = 0; j < pMeshletData->groups.Count; j++) + for (var j = 0; j < meshletData.groups.Count; j++) { - maxLodLevel = Math.Max(maxLodLevel, pMeshletData->groups[j].lodLevel); + maxLodLevel = Math.Max(maxLodLevel, meshletData.groups[j].lodLevel); } - pMeshletData->lodLevelCount = (int)maxLodLevel + 1; + meshletData.lodLevelCount = (int)maxLodLevel + 1; } var maxMaterialSlot = 0; @@ -843,7 +839,7 @@ public static unsafe partial class MeshProcessor maxMaterialSlot = Math.Max(maxMaterialSlot, parts[j].materialIndex); } - pMeshletData->materialSlotCount = maxMaterialSlot + 1; + meshletData.materialSlotCount = maxMaterialSlot + 1; } private struct TempBinaryNode @@ -1059,24 +1055,28 @@ public static unsafe partial class MeshProcessor return outNodeIndex; } - public static void BuildClusterLodHierarchy(MeshletMeshData* pMeshletData) + /// + /// Builds a cluster LOD hierarchy from the input meshlet data. + /// + /// The meshlet data. + public static void BuildClusterLodHierarchy(ref MeshletMeshData meshletData) { - if (pMeshletData->meshletCount == 0) return; + if (meshletData.meshletCount == 0) return; - using var meshletIndices = new UnsafeArray(pMeshletData->meshletCount, AllocationHandle.FreeList); - for (var i = 0; i < pMeshletData->meshletCount; i++) + using var meshletIndices = new UnsafeArray(meshletData.meshletCount, AllocationHandle.FreeList); + for (var i = 0; i < meshletData.meshletCount; i++) { meshletIndices[i] = i; } - var meshletsSpan = new ReadOnlySpan(pMeshletData->meshlets.GetUnsafePtr(), pMeshletData->meshlets.Count); + var meshletsSpan = new ReadOnlySpan(meshletData.meshlets.GetUnsafePtr(), meshletData.meshlets.Count); - using var binaryNodes = new UnsafeList(pMeshletData->meshletCount * 2, AllocationHandle.FreeList); + using var binaryNodes = new UnsafeList(meshletData.meshletCount * 2, AllocationHandle.FreeList); var rootIndex = BuildBinaryTree(binaryNodes, meshletIndices, 0, meshletIndices.Length, meshletsSpan); - if (!pMeshletData->hierarchyNodes.IsCreated) + if (!meshletData.hierarchyNodes.IsCreated) { - pMeshletData->hierarchyNodes = new UnsafeList(pMeshletData->meshletCount, AllocationHandle.Persistent); + meshletData.hierarchyNodes = new UnsafeList(meshletData.meshletCount, AllocationHandle.Persistent); } if (binaryNodes[rootIndex].leftChild == -1) @@ -1101,11 +1101,11 @@ public static unsafe partial class MeshProcessor bvhNode.maxParentError.x = childNode.maxParentError; bvhNode.nodeData.x = (uint)childNode.meshletIndex; - pMeshletData->hierarchyNodes.Add(bvhNode); + meshletData.hierarchyNodes.Add(bvhNode); } else { - CollapseTo4Ary(binaryNodes, rootIndex, pMeshletData->hierarchyNodes); + CollapseTo4Ary(binaryNodes, rootIndex, meshletData.hierarchyNodes); } } } diff --git a/src/Editor/Ghost.Editor.Core/Assets/TextureAssetHandler.cs b/src/Editor/Ghost.Editor.Core/Assets/TextureAssetHandler.cs index 97546c6..cb4074c 100644 --- a/src/Editor/Ghost.Editor.Core/Assets/TextureAssetHandler.cs +++ b/src/Editor/Ghost.Editor.Core/Assets/TextureAssetHandler.cs @@ -440,7 +440,7 @@ internal class TextureAssetHandler : IImportableAssetHandler, IPackableAssetHand }, token).ConfigureAwait(false); } - public async ValueTask ImportAsync(string sourcePath, string targetPath, Guid id, IAssetSettings? settings, CancellationToken token = default) + public async ValueTask> ImportAsync(string sourcePath, string targetPath, Guid id, IAssetSettings? settings, CancellationToken token = default) { if (!File.Exists(sourcePath)) { @@ -464,7 +464,7 @@ internal class TextureAssetHandler : IImportableAssetHandler, IPackableAssetHand if (result.IsFailure) { - return result; + return Result.Failure(result.Message); } var (cachePath, mip) = result.Value; @@ -486,7 +486,7 @@ internal class TextureAssetHandler : IImportableAssetHandler, IPackableAssetHand await ddsStream.CopyToAsync(targetStream, token).ConfigureAwait(false); await targetStream.FlushAsync(token).ConfigureAwait(false); - return Result.Success(); + return Result.Success(Array.Empty()); } catch (Exception ex) { diff --git a/src/Editor/Ghost.Editor.Core/Services/AssetCatalog.cs b/src/Editor/Ghost.Editor.Core/Services/AssetCatalog.cs index e9ecdbd..6d0ac1f 100644 --- a/src/Editor/Ghost.Editor.Core/Services/AssetCatalog.cs +++ b/src/Editor/Ghost.Editor.Core/Services/AssetCatalog.cs @@ -214,6 +214,12 @@ public sealed partial class AssetCatalog : IDisposable public bool Remove(Guid guid) { + var subAssets = GetSubAssets(guid); + foreach (var sub in subAssets) + { + Remove(sub.Guid); + } + lock (_writeLock) { _cmdDelete.Parameters.Clear(); diff --git a/src/Editor/Ghost.Editor.Core/Services/AssetRegistry.cs b/src/Editor/Ghost.Editor.Core/Services/AssetRegistry.cs index d2d29b4..c918179 100644 --- a/src/Editor/Ghost.Editor.Core/Services/AssetRegistry.cs +++ b/src/Editor/Ghost.Editor.Core/Services/AssetRegistry.cs @@ -49,7 +49,7 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable { IncludeSubdirectories = true, EnableRaisingEvents = true, - NotifyFilter = NotifyFilters.LastWrite + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, }; _watcher.Created += OnFileSystemEvent; @@ -165,6 +165,8 @@ internal sealed class AssetRegistry : IAssetRegistry, IDisposable _catalog.Remove(guid); changeType = AssetChangeType.Deleted; } + + Logger.DebugAssert(e.ChangeType == WatcherChangeTypes.Deleted); } else if (guid == Guid.Empty) { diff --git a/src/Editor/Ghost.Editor.Core/Services/ImportCoordinator.cs b/src/Editor/Ghost.Editor.Core/Services/ImportCoordinator.cs index 18f42b3..41e3901 100644 --- a/src/Editor/Ghost.Editor.Core/Services/ImportCoordinator.cs +++ b/src/Editor/Ghost.Editor.Core/Services/ImportCoordinator.cs @@ -112,22 +112,15 @@ internal sealed partial class ImportCoordinator : IDisposable } var importResult = Result.Success(); - ImportedSubAsset[] subAssets = Array.Empty(); + var subAssets = Array.Empty(); if (handler is IImportableAssetHandler importable) { var targetPath = GetImportedAssetPath(job.AssetGuid); - if (importable is ISubAssetImportableAssetHandler subAssetImportable) + var subAssetResult = await importable.ImportAsync(job.SourcePath, targetPath, job.AssetGuid, meta.Settings, token); + importResult = subAssetResult; + if (subAssetResult.IsSuccess) { - var subAssetResult = await subAssetImportable.ImportWithSubAssetsAsync(job.SourcePath, targetPath, job.AssetGuid, meta.Settings, token); - importResult = subAssetResult; - if (subAssetResult.IsSuccess) - { - subAssets = subAssetResult.Value; - } - } - else - { - importResult = await importable.ImportAsync(job.SourcePath, targetPath, job.AssetGuid, meta.Settings, token); + subAssets = subAssetResult.Value; } } @@ -164,7 +157,7 @@ internal sealed partial class ImportCoordinator : IDisposable _catalog.RemoveSubAssetsExcept(job.AssetGuid, dependencies); _catalog.SetDependencies(job.AssetGuid, dependencies); } - else if (handler is ISubAssetImportableAssetHandler) + else { _catalog.RemoveSubAssetsExcept(job.AssetGuid, ReadOnlySpan.Empty); _catalog.SetDependencies(job.AssetGuid, ReadOnlySpan.Empty); diff --git a/src/Editor/Ghost.Editor/App.xaml.cs b/src/Editor/Ghost.Editor/App.xaml.cs index 9646764..faaf93f 100644 --- a/src/Editor/Ghost.Editor/App.xaml.cs +++ b/src/Editor/Ghost.Editor/App.xaml.cs @@ -153,6 +153,7 @@ public partial class App : Application } catch (Exception ex) { + Logger.Error(ex); Environment.Exit(ex.HResult); } } @@ -169,7 +170,7 @@ public partial class App : Application } catch (Exception ex) { - Debugger.BreakForUserUnhandledException(ex); + Logger.Error(ex); } finally { diff --git a/src/Runtime/Ghost.Core/Ghost.Core.csproj b/src/Runtime/Ghost.Core/Ghost.Core.csproj index ffa5dc5..796390c 100644 --- a/src/Runtime/Ghost.Core/Ghost.Core.csproj +++ b/src/Runtime/Ghost.Core/Ghost.Core.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Runtime/Ghost.Core/Utilities/MemoryManager.cs b/src/Runtime/Ghost.Core/Utilities/MemoryManager.cs index 97e99e7..9c34cf7 100644 --- a/src/Runtime/Ghost.Core/Utilities/MemoryManager.cs +++ b/src/Runtime/Ghost.Core/Utilities/MemoryManager.cs @@ -20,7 +20,7 @@ public unsafe class NativeMemoryManager : MemoryManager } public static NativeMemoryManager FromUnsafeCollection(ref readonly C collection) - where C : unmanaged, IUnsafeCollection + where C : IUnsafeCollection { if (!collection.IsCreated) { @@ -30,6 +30,19 @@ public unsafe class NativeMemoryManager : MemoryManager return new NativeMemoryManager((T*)collection.GetUnsafePtr(), collection.Count); } + public static NativeMemoryManager FromUnsafeCollectionInterpolated(ref readonly C collection) + where U : unmanaged + where C : IUnsafeCollection + { + if (!collection.IsCreated) + { + throw new InvalidOperationException("The collection is not created."); + } + + var length = collection.Count * Unsafe.SizeOf() / Unsafe.SizeOf(); + return new NativeMemoryManager((T*)collection.GetUnsafePtr(), length); + } + public static NativeMemoryManager FromMemoryBlock(MemoryBlock memoryBlock, int start, int length) { return new NativeMemoryManager((T*)memoryBlock.GetUnsafePtr() + start, length); diff --git a/src/Runtime/Ghost.Core/Utilities/StreamUtility.cs b/src/Runtime/Ghost.Core/Utilities/StreamUtility.cs new file mode 100644 index 0000000..1a1ef43 --- /dev/null +++ b/src/Runtime/Ghost.Core/Utilities/StreamUtility.cs @@ -0,0 +1,55 @@ +using Misaki.HighPerformance.LowLevel.Collections.Contracts; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ghost.Core.Utilities; + +public static class StreamUtility +{ + public static void Write(this Stream stream, in T value) + where T : struct + { + stream.Write(MemoryMarshal.AsBytes(new ReadOnlySpan(in value))); + } + + public static void Write(this Stream stream, ReadOnlySpan values) + where T : struct + { + if (values.IsEmpty) + { + return; + } + + stream.Write(MemoryMarshal.AsBytes(values)); + } + + public static async ValueTask WriteAsync(this Stream stream, C collection, CancellationToken cancellationToken = default) + where T : unmanaged + where C : IUnsafeCollection + { + if (!collection.IsCreated || collection.Count == 0) + { + return; + } + + using var manager = NativeMemoryManager.FromUnsafeCollectionInterpolated(in collection); + await stream.WriteAsync(manager.Memory, cancellationToken).ConfigureAwait(false); + } + + public static async ValueTask WriteAsync(this Stream stream, T value, CancellationToken cancellationToken = default) + where T : unmanaged + { + var size = Unsafe.SizeOf(); + var buffer = ArrayPool.Shared.Rent(size); + try + { + Unsafe.WriteUnaligned(ref buffer[0], value); + await stream.WriteAsync(buffer.AsMemory(0, size), cancellationToken); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } +} diff --git a/src/Runtime/Ghost.Graphics/Services/ShaderLibrary.cs b/src/Runtime/Ghost.Graphics/Services/ShaderLibrary.cs index 90477fd..9e27ec6 100644 --- a/src/Runtime/Ghost.Graphics/Services/ShaderLibrary.cs +++ b/src/Runtime/Ghost.Graphics/Services/ShaderLibrary.cs @@ -188,6 +188,9 @@ internal unsafe class ShaderLibrary : IDisposable kvp.Value.Dispose(); } + _inMemoryCache.Dispose(); + _variantToCompiledHash.Dispose(); + GC.SuppressFinalize(this); } } diff --git a/src/Test/Ghost.MicroTest/Ghost.MicroTest.csproj b/src/Test/Ghost.MicroTest/Ghost.MicroTest.csproj index 5358e3c..49c01df 100644 --- a/src/Test/Ghost.MicroTest/Ghost.MicroTest.csproj +++ b/src/Test/Ghost.MicroTest/Ghost.MicroTest.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingContentProvider.cs b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingContentProvider.cs index 1659544..d812006 100644 --- a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingContentProvider.cs +++ b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingContentProvider.cs @@ -1,4 +1,5 @@ using Ghost.Core; +using Ghost.Core.Utilities; using Ghost.Engine; using Ghost.Graphics.Core; using Ghost.Graphics.RHI; @@ -143,18 +144,18 @@ internal class MockingContentProvider : IContentProvider boundsMax = new float3(1, 1, 0), }; - WriteStruct(stream, in header); - header.vertexOffset = (ulong)stream.Position; WriteSpan(stream, vertices); - header.indexOffset = (ulong)stream.Position; WriteSpan(stream, indices); - header.materialPartOffset = (ulong)stream.Position; WriteSpan(stream, materialParts); - header.meshletOffset = (ulong)stream.Position; WriteSpan(stream, meshlets); - header.meshletGroupOffset = (ulong)stream.Position; WriteSpan(stream, groups); - header.meshletHierarchyNodeOffset = (ulong)stream.Position; WriteSpan(stream, hierarchy); - header.meshletVertexOffset = (ulong)stream.Position; WriteSpan(stream, meshletVertices); - header.meshletTriangleOffset = (ulong)stream.Position; WriteSpan(stream, meshletTriangles); + stream.Write(header); + header.vertexOffset = (ulong)stream.Position; stream.Write(vertices); + header.indexOffset = (ulong)stream.Position; stream.Write(indices); + header.materialPartOffset = (ulong)stream.Position; stream.Write(materialParts); + header.meshletOffset = (ulong)stream.Position; stream.Write(meshlets); + header.meshletGroupOffset = (ulong)stream.Position; stream.Write(groups); + header.meshletHierarchyNodeOffset = (ulong)stream.Position; stream.Write(hierarchy); + header.meshletVertexOffset = (ulong)stream.Position; stream.Write(meshletVertices); + header.meshletTriangleOffset = (ulong)stream.Position; stream.Write(meshletTriangles); stream.Position = 0; - WriteStruct(stream, in header); + stream.Write(header); AddMockAsset(guid, new MockAssetData { @@ -164,18 +165,6 @@ internal class MockingContentProvider : IContentProvider }); } - private static void WriteStruct(Stream stream, ref readonly T value) - where T : unmanaged - { - stream.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in value, 1))); - } - - private static void WriteSpan(Stream stream, ReadOnlySpan value) - where T : unmanaged - { - stream.Write(MemoryMarshal.AsBytes(value)); - } - public AssetType GetAssetType(Guid guid) { return _assets.TryGetValue(guid, out var asset) ? asset.type : AssetType.Unknown;