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:
@@ -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>
|
||||
|
||||
286
src/Runtime/Ghost.Engine/AssetManager.Mesh.cs
Normal file
286
src/Runtime/Ghost.Engine/AssetManager.Mesh.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
src/Runtime/Ghost.Engine/MeshContent.cs
Normal file
47
src/Runtime/Ghost.Engine/MeshContent.cs
Normal 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;
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user