Add sub-asset import and mesh asset support

- Implement sub-asset import for mesh/model assets with manifest generation and deterministic GUIDs
- Extend AssetCatalog for sub-asset tracking and management
- Update AssetRegistry and ImportCoordinator for sub-asset workflows
- Add mesh asset parsing, GPU upload, and resource management
- Update mesh data structures for meshlet groups/hierarchy and LODs
- Improve tests for sub-asset import and mesh handling
- Enhance mocks for mesh asset testing and resource mapping
- Fix path handling and native DLL loading issues
- Miscellaneous bug fixes and refactoring
This commit is contained in:
2026-05-04 21:25:03 +09:00
parent bffe05f0ef
commit 8d3e1c91d7
30 changed files with 1604 additions and 85 deletions

View File

@@ -8,7 +8,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_MIMALLOC;MHP_FASTMATH</DefineConstants>
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_MIMALLOC;MHP_FASTMATH;MHP_ENABLE_STACKTRACE</DefineConstants>
<IsAotCompatible>True</IsAotCompatible>
<IsTrimmable>True</IsTrimmable>
</PropertyGroup>

View File

@@ -0,0 +1,286 @@
using Ghost.Core;
using Ghost.Graphics;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using Misaki.HighPerformance.Mathematics.Geometry;
using System.Runtime.CompilerServices;
namespace Ghost.Engine;
internal unsafe partial class AssetEntry
{
private sealed unsafe class MeshParsedData
{
public MeshContentHeader header;
public byte* pVertices;
public byte* pIndices;
public byte* pMeshlets;
public byte* pMeshletGroups;
public byte* pMeshletHierarchyNodes;
public byte* pMeshletVertices;
public byte* pMeshletTriangles;
}
private static void RegisterMeshCallback()
{
s_onCreation[(int)AssetType.Mesh] = static (e) =>
{
var handle = e._resourceManager.CreateEmptyMesh();
e.SetStorage(handle);
};
s_onParseRawData[(int)AssetType.Mesh] = static (e) => e.ParseMeshData();
s_onRecordUpload[(int)AssetType.Mesh] = static (e, ctx) => e.RecordMeshUpload(ctx);
s_onUploadComplete[(int)AssetType.Mesh] = static (e, ctx) => e.OnMeshUploadComplete(ctx);
s_onReleaseResource[(int)AssetType.Mesh] = static (e) =>
{
var handle = e.GetStorage<Handle<Mesh>>();
if (handle.IsValid)
{
e._resourceManager.ReleaseMesh(handle);
}
};
}
private Result ParseMeshData()
{
var pData = (byte*)_rawData.GetUnsafePtr();
Logger.DebugAssert(pData != null);
if (_rawData.Size < (nuint)sizeof(MeshContentHeader))
{
return Result.Failure("Mesh content is smaller than the header.");
}
var header = Unsafe.ReadUnaligned<MeshContentHeader>(pData);
if (header.magic != MeshContentHeader.MAGIC || header.version != MeshContentHeader.VERSION)
{
return Result.Failure("Unsupported mesh content format.");
}
if (header.vertexCount == 0 || header.indexCount == 0 ||
header.meshletCount == 0 || header.meshletGroupCount == 0 ||
header.meshletHierarchyNodeCount == 0 || header.meshletVertexCount == 0 ||
header.meshletTriangleCount == 0)
{
return Result.Failure("Mesh content is missing required geometry or meshlet data.");
}
if (!ValidateRange(header.vertexOffset, header.vertexCount, (uint)sizeof(Vertex)) ||
!ValidateRange(header.indexOffset, header.indexCount, sizeof(uint)) ||
!ValidateRange(header.meshletOffset, header.meshletCount, (uint)sizeof(Meshlet)) ||
!ValidateRange(header.meshletGroupOffset, header.meshletGroupCount, (uint)sizeof(MeshletGroup)) ||
!ValidateRange(header.meshletHierarchyNodeOffset, header.meshletHierarchyNodeCount, (uint)sizeof(MeshletHierarchyNode)) ||
!ValidateRange(header.meshletVertexOffset, header.meshletVertexCount, sizeof(uint)) ||
!ValidateRange(header.meshletTriangleOffset, header.meshletTriangleCount, sizeof(uint)))
{
return Result.Failure("Mesh content contains an invalid data range.");
}
if (header.materialPartCount > 0 && !ValidateRange(header.materialPartOffset, header.materialPartCount, (uint)sizeof(MeshContentMaterialPart)))
{
return Result.Failure("Mesh content contains an invalid material part range.");
}
_parsedObject = new MeshParsedData
{
header = header,
pVertices = pData + header.vertexOffset,
pIndices = pData + header.indexOffset,
pMeshlets = pData + header.meshletOffset,
pMeshletGroups = pData + header.meshletGroupOffset,
pMeshletHierarchyNodes = pData + header.meshletHierarchyNodeOffset,
pMeshletVertices = pData + header.meshletVertexOffset,
pMeshletTriangles = pData + header.meshletTriangleOffset,
};
return Result.Success();
bool ValidateRange(ulong offset, uint count, uint stride)
{
var size = (ulong)count * stride;
return offset <= _rawData.Size && size <= _rawData.Size - (nuint)offset;
}
}
private Result RecordMeshUpload(ResourceStreamingContext context)
{
if (_parsedObject is not MeshParsedData data)
{
return Result.Failure("Mesh parse data is missing.");
}
ref readonly var header = ref data.header;
var vertexBuffer = CreateBuffer(context, data.pVertices, header.vertexCount, (uint)sizeof(Vertex),
BufferUsage.Vertex | BufferUsage.ShaderResource | BufferUsage.Raw, "Mesh_VertexBuffer");
var indexBuffer = CreateBuffer(context, data.pIndices, header.indexCount, sizeof(uint),
BufferUsage.Index | BufferUsage.ShaderResource | BufferUsage.Raw, "Mesh_IndexBuffer");
var meshletBuffer = CreateBuffer(context, data.pMeshlets, header.meshletCount, (uint)sizeof(Meshlet),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_Meshlets");
var meshletVerticesBuffer = CreateBuffer(context, data.pMeshletVertices, header.meshletVertexCount, sizeof(uint),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletVertices");
var meshletTrianglesBuffer = CreateBuffer(context, data.pMeshletTriangles, header.meshletTriangleCount, sizeof(uint),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletTriangles");
var meshletGroupBuffer = CreateBuffer(context, data.pMeshletGroups, header.meshletGroupCount, (uint)sizeof(MeshletGroup),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletGroups");
var meshletHierarchyBuffer = CreateBuffer(context, data.pMeshletHierarchyNodes, header.meshletHierarchyNodeCount, (uint)sizeof(MeshletHierarchyNode),
BufferUsage.Raw | BufferUsage.ShaderResource, "Mesh_MeshletHierarchy");
if (vertexBuffer.IsInvalid || indexBuffer.IsInvalid || meshletBuffer.IsInvalid ||
meshletVerticesBuffer.IsInvalid || meshletTrianglesBuffer.IsInvalid ||
meshletGroupBuffer.IsInvalid || meshletHierarchyBuffer.IsInvalid)
{
return Result.Failure("Failed to create one or more mesh GPU buffers.");
}
var meshData = new MeshData
{
worldBoundsMin = header.boundsMin,
worldBoundsMax = header.boundsMax,
vertexBuffer = context.ResourceDatabase.GetBindlessIndex(vertexBuffer.AsResource()),
indexBuffer = context.ResourceDatabase.GetBindlessIndex(indexBuffer.AsResource()),
meshletBuffer = context.ResourceDatabase.GetBindlessIndex(meshletBuffer.AsResource()),
meshletVerticesBuffer = context.ResourceDatabase.GetBindlessIndex(meshletVerticesBuffer.AsResource()),
meshletTrianglesBuffer = context.ResourceDatabase.GetBindlessIndex(meshletTrianglesBuffer.AsResource()),
meshletGroupBuffer = context.ResourceDatabase.GetBindlessIndex(meshletGroupBuffer.AsResource()),
meshletHierarchyBuffer = context.ResourceDatabase.GetBindlessIndex(meshletHierarchyBuffer.AsResource()),
meshletCount = header.meshletCount,
lodLevelCount = header.lodLevelCount,
materialSlotCount = header.materialSlotCount,
};
var meshDataBufferDesc = new BufferDesc
{
Size = (ulong)sizeof(MeshData),
Stride = (uint)sizeof(MeshData),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
var meshDataBuffer = RenderingUtility.CreateBuffer(
context.ResourceManager,
context.ResourceDatabase,
context.ResourceAllocator,
context.CopyPipeline.GetCommandBuffer(),
&meshData,
(nuint)sizeof(MeshData),
in meshDataBufferDesc,
"Mesh_MeshDataBuffer");
if (meshDataBuffer.IsInvalid)
{
return Result.Failure("Failed to create mesh data buffer.");
}
var newHandle = context.ResourceManager.CreateUploadedMesh(
vertexBuffer,
indexBuffer,
meshletBuffer,
meshletVerticesBuffer,
meshletTrianglesBuffer,
meshletGroupBuffer,
meshletHierarchyBuffer,
meshDataBuffer,
(int)header.vertexCount,
(int)header.indexCount,
(int)header.meshletCount,
(int)header.lodLevelCount,
(int)header.materialSlotCount,
new AABB(header.boundsMin, header.boundsMax));
if (newHandle.IsInvalid)
{
return Result.Failure("Failed to register uploaded mesh.");
}
var oldHandle = GetStorage<Handle<Mesh>>();
SetStorage((oldHandle, newHandle));
return Result.Success();
}
private static Handle<GPUBuffer> CreateBuffer(ResourceStreamingContext context, void* pData, uint count, uint stride, BufferUsage usage, string name)
{
var desc = new BufferDesc
{
Size = (ulong)count * stride,
Stride = stride,
Usage = usage,
HeapType = HeapType.Default,
};
return RenderingUtility.CreateBuffer(
context.ResourceManager,
context.ResourceDatabase,
context.ResourceAllocator,
context.CopyPipeline.GetCommandBuffer(),
pData,
(nuint)desc.Size,
in desc,
name);
}
private void OnMeshUploadComplete(ResourceStreamingContext context)
{
var (oldHandle, newHandle) = GetStorage<(Handle<Mesh>, Handle<Mesh>)>();
var actualHandle = context.ResourceManager.ReplaceMesh(oldHandle, newHandle);
if (actualHandle.IsInvalid)
{
SetStorage(oldHandle);
return;
}
var result = context.ResourceManager.GetMeshReference(actualHandle);
if (result.IsSuccess)
{
ref readonly var mesh = ref result.Value;
context.GraphicsCommandBuffer.Barrier(
BarrierDesc.Buffer(mesh.VertexBuffer.AsResource(), BarrierSync.VertexShading, BarrierAccess.VertexBuffer | BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.IndexBuffer.AsResource(), BarrierSync.IndexInput, BarrierAccess.IndexBuffer | BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshLetBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshletVerticesBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshletTrianglesBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshletGroupBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshletHierarchyBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource),
BarrierDesc.Buffer(mesh.MeshDataBuffer.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource));
}
SetStorage(actualHandle);
_rawData.Dispose();
_parsedObject = null;
}
}
internal partial class AssetManager
{
public Handle<Mesh> ResolveMesh(Guid assetID)
{
if (assetID == Guid.Empty)
{
return Handle<Mesh>.Invalid;
}
var entry = GetOrCreateEntry(assetID);
Logger.DebugAssert(entry.AssetType == AssetType.Mesh);
return entry.GetStorage<Handle<Mesh>>();
}
public int ReleaseMesh(Guid assetID)
{
if (assetID == Guid.Empty)
{
return 0;
}
if (!_entries.TryGetValue(assetID, out var entry) || entry.AssetType != AssetType.Mesh)
{
return 0;
}
return entry.Release();
}
}

View File

@@ -1,6 +1,7 @@
using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Graphics;
using Ghost.Graphics.Services;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel;
@@ -60,6 +61,7 @@ internal partial class AssetEntry
static AssetEntry()
{
RegisterTextureCallback();
RegisterMeshCallback();
}
}
@@ -72,6 +74,7 @@ internal unsafe partial class AssetEntry
private readonly AssetManager _assetManager;
private readonly IResourceDatabase _resourceDatabase;
private readonly ResourceManager _resourceManager;
private readonly Guid _assetId;
private readonly AssetType _assetType;
@@ -101,10 +104,11 @@ internal unsafe partial class AssetEntry
set => Volatile.Write(ref _state, (int)value);
}
public AssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, Guid assetId, AssetType assetType, Guid[] dependencies)
public AssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, ResourceManager resourceManager, Guid assetId, AssetType assetType, Guid[] dependencies)
{
_assetManager = manager;
_resourceDatabase = resourceDatabase;
_resourceManager = resourceManager;
_assetId = assetId;
_assetType = assetType;
@@ -288,6 +292,7 @@ internal struct LoadAssetJob : IJob
internal partial class AssetManager : IDisposable
{
private readonly IResourceDatabase _resourceDatabase;
private readonly ResourceManager _resourceManager;
private readonly IContentProvider _contentProvider;
private readonly ResourceStreamingProcessor _streamingProcessor;
private readonly JobScheduler _jobScheduler;
@@ -297,9 +302,10 @@ internal partial class AssetManager : IDisposable
public IContentProvider ContentProvider => _contentProvider;
public ResourceStreamingProcessor StreamingProcessor => _streamingProcessor;
internal AssetManager(IResourceDatabase resourceDatabase, IContentProvider contentProvider, ResourceStreamingProcessor streamingProcessor, JobScheduler jobScheduler)
internal AssetManager(IResourceDatabase resourceDatabase, ResourceManager resourceManager, IContentProvider contentProvider, ResourceStreamingProcessor streamingProcessor, JobScheduler jobScheduler)
{
_resourceDatabase = resourceDatabase;
_resourceManager = resourceManager;
_contentProvider = contentProvider;
_streamingProcessor = streamingProcessor;
_jobScheduler = jobScheduler;
@@ -320,9 +326,13 @@ internal partial class AssetManager : IDisposable
private void EnsureScheduled(AssetEntry entry)
{
if (Interlocked.CompareExchange(ref entry.StateValue, (int)AssetState.Scheduled, (int)AssetState.Unloaded) != (int)AssetState.Unloaded)
var previousState = Interlocked.CompareExchange(ref entry.StateValue, (int)AssetState.Scheduled, (int)AssetState.Unloaded);
if (previousState != (int)AssetState.Unloaded)
{
return;
if (previousState != (int)AssetState.Scheduled || entry.LoadJobHandle.IsValid)
{
return;
}
}
// TODO: Can this be jobified? If the dependency tree is not deep, it should be fine to do it in main thread, otherwise we might need to schedule a job to do it.
@@ -397,7 +407,7 @@ internal partial class AssetManager : IDisposable
var type = self._contentProvider.GetAssetType(id);
var deps = self._contentProvider.GetDependencies(id);
var entry = new AssetEntry(self, self._resourceDatabase, id, type, deps);
var entry = new AssetEntry(self, self._resourceDatabase, self._resourceManager, id, type, deps);
self.EnsureScheduled(entry);
return entry;
@@ -420,6 +430,7 @@ internal partial class AssetManager : IDisposable
// Go directly to Scheduled -> Loading -> Loaded -> Uploading -> Ready again.
// The swap cycle in RecordTextureUpload/OnTextureUploadComplete handles the
// v1 to v2 transition exactly like the fallback to v1 transition.
entry.SetLoadJobHandle(JobHandle.Invalid);
EnsureScheduled(entry);
}
else

View File

@@ -47,7 +47,7 @@ public sealed partial class EngineCore : IDisposable
};
_renderSystem = new RenderSystem(renderingDesc);
_assetManager = new AssetManager(_renderSystem.GraphicsEngine.ResourceDatabase, _contentProvider, _streamingProcessor, _jobScheduler);
_assetManager = new AssetManager(_renderSystem.GraphicsEngine.ResourceDatabase, _renderSystem.ResourceManager, _contentProvider, _streamingProcessor, _jobScheduler);
}
public void Dispose()
@@ -56,4 +56,4 @@ public sealed partial class EngineCore : IDisposable
_renderSystem.Dispose();
_jobScheduler.Dispose();
}
}
}

View File

@@ -0,0 +1,47 @@
using Misaki.HighPerformance.Mathematics;
using System.Runtime.InteropServices;
namespace Ghost.Engine;
[StructLayout(LayoutKind.Sequential)]
public struct MeshContentHeader
{
public const uint MAGIC = 0x48534D47; // GMSH
public const uint VERSION = 1;
public uint magic;
public uint version;
public uint vertexCount;
public uint indexCount;
public uint materialPartCount;
public uint meshletCount;
public uint meshletGroupCount;
public uint meshletHierarchyNodeCount;
public uint meshletVertexCount;
public uint meshletTriangleCount;
public uint materialSlotCount;
public uint lodLevelCount;
public float3 boundsMin;
public float3 boundsMax;
public ulong vertexOffset;
public ulong indexOffset;
public ulong materialPartOffset;
public ulong meshletOffset;
public ulong meshletGroupOffset;
public ulong meshletHierarchyNodeOffset;
public ulong meshletVertexOffset;
public ulong meshletTriangleOffset;
}
[StructLayout(LayoutKind.Sequential)]
public struct MeshContentMaterialPart
{
public int materialIndex;
public int indexStart;
public int indexCount;
public int vertexStart;
public int vertexCount;
}

View File

@@ -141,6 +141,11 @@ internal static partial class {registerTypeName}
return null;
}
if (symbol.IsAbstract)
{
return null;
}
var iSettingsSymbol = context.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Editor.Core.Assets.IAssetSettings");
if (iSettingsSymbol == null)
{

View File

@@ -70,5 +70,9 @@ public struct MeshData
public uint meshletBuffer;
public uint meshletVerticesBuffer;
public uint meshletTrianglesBuffer;
public uint meshletGroupBuffer;
public uint meshletHierarchyBuffer;
public uint meshletCount;
public uint lodLevelCount;
public uint materialSlotCount; // number of material slots baked into this mesh's meshlets
};

View File

@@ -185,6 +185,22 @@ public struct Mesh : IResourceReleasable
get; internal set;
}
/// <summary>
/// Gets the handle to the meshlet group buffer on the GPU.
/// </summary>
public Handle<GPUBuffer> MeshletGroupBuffer
{
get; internal set;
}
/// <summary>
/// Gets the handle to the meshlet hierarchy buffer on the GPU.
/// </summary>
public Handle<GPUBuffer> MeshletHierarchyBuffer
{
get; internal set;
}
/// <summary>
/// Gets the handle to the mesh data buffer on the GPU.
/// </summary>
@@ -193,6 +209,19 @@ public struct Mesh : IResourceReleasable
get; internal set;
}
internal void SetMeshletSummary(int meshletCount, int lodLevelCount, int materialSlotCount)
{
_meshletData.meshletCount = meshletCount;
_meshletData.lodLevelCount = lodLevelCount;
_meshletData.materialSlotCount = materialSlotCount;
}
internal void SetCounts(int vertexCount, int indexCount)
{
VertexCount = vertexCount;
IndexCount = indexCount;
}
public void ReleaseCpuResources()
{
_vertices.Dispose();
@@ -209,6 +238,8 @@ public struct Mesh : IResourceReleasable
database.ReleaseResource(MeshLetBuffer.AsResource());
database.ReleaseResource(MeshletVerticesBuffer.AsResource());
database.ReleaseResource(MeshletTrianglesBuffer.AsResource());
database.ReleaseResource(MeshletGroupBuffer.AsResource());
database.ReleaseResource(MeshletHierarchyBuffer.AsResource());
database.ReleaseResource(MeshDataBuffer.AsResource());
}
}

View File

@@ -254,22 +254,44 @@ public readonly unsafe ref struct RenderContext
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
var groupsDesc = new BufferDesc
{
Size = (uint)(meshletData.groups.Count * sizeof(MeshletGroup)),
Stride = (uint)sizeof(MeshletGroup),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
var hierarchyDesc = new BufferDesc
{
Size = (uint)(meshletData.hierarchyNodes.Count * sizeof(MeshletHierarchyNode)),
Stride = (uint)sizeof(MeshletHierarchyNode),
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
HeapType = HeapType.Default,
};
meshRef.MeshLetBuffer = ResourceAllocator.CreateBuffer(in meshletDesc, "Meshlets");
meshRef.MeshletVerticesBuffer = ResourceAllocator.CreateBuffer(in verticesDesc, "MeshletVertices");
meshRef.MeshletTrianglesBuffer = ResourceAllocator.CreateBuffer(in trianglesDesc, "MeshletTriangles");
meshRef.MeshletGroupBuffer = ResourceAllocator.CreateBuffer(in groupsDesc, "MeshletGroups");
meshRef.MeshletHierarchyBuffer = ResourceAllocator.CreateBuffer(in hierarchyDesc, "MeshletHierarchy");
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletGroupBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
TransitionBarrier(meshRef.MeshletHierarchyBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
UploadBuffer(meshRef.MeshLetBuffer, meshletData.meshlets.AsSpan());
UploadBuffer(meshRef.MeshletVerticesBuffer, meshletData.meshletVertices.AsSpan());
UploadBuffer(meshRef.MeshletTrianglesBuffer, meshletData.meshletTriangles.AsSpan());
UploadBuffer(meshRef.MeshletGroupBuffer, meshletData.groups.AsSpan());
UploadBuffer(meshRef.MeshletHierarchyBuffer, meshletData.hierarchyNodes.AsSpan());
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletTrianglesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletGroupBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
TransitionBarrier(meshRef.MeshletHierarchyBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.NonPixelShading | BarrierSync.PixelShading);
}
public void UpdateObjectData(Handle<Mesh> mesh)
@@ -290,6 +312,10 @@ public readonly unsafe ref struct RenderContext
meshletBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshLetBuffer.AsResource()),
meshletVerticesBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletVerticesBuffer.AsResource()),
meshletTrianglesBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletTrianglesBuffer.AsResource()),
meshletGroupBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletGroupBuffer.AsResource()),
meshletHierarchyBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletHierarchyBuffer.AsResource()),
meshletCount = (uint)meshData.MeshletData.meshletCount,
lodLevelCount = (uint)meshData.MeshletData.lodLevelCount,
materialSlotCount = (uint)meshData.MeshletData.materialSlotCount,
};

View File

@@ -4,6 +4,7 @@ using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics.Geometry;
namespace Ghost.Graphics.Services;
@@ -165,6 +166,79 @@ public sealed partial class ResourceManager : IDisposable
}
}
public Handle<Mesh> CreateEmptyMesh(string? name = null)
{
Logger.DebugAssert(!_disposed);
lock (_meshWriteLock)
{
var id = _meshes.Add(new Mesh(), out var generation);
return new Handle<Mesh>(id, generation);
}
}
public Handle<Mesh> CreateUploadedMesh(
Handle<GPUBuffer> vertexBuffer,
Handle<GPUBuffer> indexBuffer,
Handle<GPUBuffer> meshletBuffer,
Handle<GPUBuffer> meshletVerticesBuffer,
Handle<GPUBuffer> meshletTrianglesBuffer,
Handle<GPUBuffer> meshletGroupBuffer,
Handle<GPUBuffer> meshletHierarchyBuffer,
Handle<GPUBuffer> meshDataBuffer,
int vertexCount,
int indexCount,
int meshletCount,
int lodLevelCount,
int materialSlotCount,
AABB boundingBox)
{
Logger.DebugAssert(!_disposed);
var mesh = new Mesh
{
VertexBuffer = vertexBuffer,
IndexBuffer = indexBuffer,
MeshLetBuffer = meshletBuffer,
MeshletVerticesBuffer = meshletVerticesBuffer,
MeshletTrianglesBuffer = meshletTrianglesBuffer,
MeshletGroupBuffer = meshletGroupBuffer,
MeshletHierarchyBuffer = meshletHierarchyBuffer,
MeshDataBuffer = meshDataBuffer,
BoundingBox = boundingBox,
};
mesh.SetCounts(vertexCount, indexCount);
mesh.SetMeshletSummary(meshletCount, lodLevelCount, materialSlotCount);
lock (_meshWriteLock)
{
var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation);
}
}
public Handle<Mesh> ReplaceMesh(Handle<Mesh> dst, Handle<Mesh> src)
{
Logger.DebugAssert(!_disposed);
lock (_meshWriteLock)
{
ref var dstMesh = ref _meshes.GetElementReferenceAt(dst.ID, dst.Generation, out var dstExists);
ref var srcMesh = ref _meshes.GetElementReferenceAt(src.ID, src.Generation, out var srcExists);
if (!dstExists || !srcExists)
{
return Handle<Mesh>.Invalid;
}
var oldMesh = dstMesh;
dstMesh = srcMesh;
_meshes.Remove(src.ID, src.Generation);
oldMesh.ReleaseResource(_resourceDatabase);
return dst;
}
}
/// <summary>
/// Creates a new material instance using the specified shader.
/// </summary>

View File

@@ -61,6 +61,10 @@ struct MeshData
BYTE_ADDRESS_BUFFER meshletBuffer;
BYTE_ADDRESS_BUFFER meshletVerticesBuffer;
BYTE_ADDRESS_BUFFER meshletTrianglesBuffer;
BYTE_ADDRESS_BUFFER meshletGroupBuffer;
BYTE_ADDRESS_BUFFER meshletHierarchyBuffer;
uint meshletCount;
uint lodLevelCount;
uint materialSlotCount;
};

View File

@@ -230,7 +230,7 @@ public static unsafe class MeshBuilder
public static void ComputeTangents(Span<Vertex> vertices, Span<uint> indices)
{
using var scope = AllocationManager.CreateStackScope();
var bitangents = new UnsafeArray<float3>(vertices.Length, scope.AllocationHandle, AllocationOption.Clear);
using var bitangents = new UnsafeArray<float3>(vertices.Length, scope.AllocationHandle, AllocationOption.Clear);
for (var i = 0; i < indices.Length; i += 3)
{