feat(render): add meshlet rendering and ECS query ref API

Introduces meshlet-based rendering pipeline with new HLSL structures and push constant layouts. Refactors meshlet upload/cooking, updates RenderGraphContext for global/view/instance data, and enhances ECS QueryBuilder with ref returns and [UnscopedRef] for fluent chaining. Improves resource management and disposal patterns, updates D3D12 interop for compatibility, and refines test/app infrastructure. Includes dependency updates, bug fixes, and code cleanups.
This commit is contained in:
2026-03-25 20:27:46 +09:00
parent b729ca86f5
commit 447a4e6904
28 changed files with 407 additions and 165 deletions

View File

@@ -52,6 +52,7 @@
<ProjectReference Include="..\..\Runtime\Ghost.Engine\Ghost.Engine.csproj" />
<ProjectReference Include="..\..\Test\Ghost.Test.Core\Ghost.Test.Core.csproj" />
<ProjectReference Include="..\..\Runtime\Ghost.Graphics\Ghost.Graphics.csproj" />
<ProjectReference Include="..\..\Editor\Ghost.DSL\Ghost.DSL.csproj" />
</ItemGroup>
<!--

View File

@@ -1,9 +1,12 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.DSL.ShaderCompiler;
using Ghost.Graphics.Core;
using Ghost.Graphics.RenderGraphModule;
using Ghost.Graphics.RenderPipeline;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using Misaki.HighPerformance.Utilities;
namespace Ghost.Graphics.Test.RenderPasses;
@@ -21,10 +24,15 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
{
public Identifier<RGTexture> backbuffer;
public RenderList renderList;
public Handle<Material> material;
public uint globalIndex;
public uint viewIndex;
}
private readonly RenderGraph _renderGraph;
private readonly RenderSystem _renderSystem;
private Identifier<Shader> _meshletShader;
private Handle<Material> _meshletMaterial;
private bool _disposed;
@@ -41,6 +49,25 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
renderSystem.GraphicsEngine.ResourceDatabase,
renderSystem.GraphicsEngine.PipelineLibrary,
renderSystem.GraphicsEngine.ShaderCompiler);
var shaderDescriptor = DSLShaderCompiler.CompileShader("F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/test.gshdr", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow();
_meshletShader = renderSystem.ResourceManager.CreateGraphicsShader(shaderDescriptor);
_meshletMaterial = renderSystem.ResourceManager.CreateMaterial(_meshletShader);
var config = new ShaderCompilationConfig
{
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
tier = CompilerTier.Tier2
};
var pass = shaderDescriptor.passes[0];
var emptyKeywords = new LocalKeywordSet();
var variantKey = RHIUtility.CreateShaderVariantKey(
RHIUtility.CreateShaderPassKey(pass.identifier),
in emptyKeywords);
renderSystem.GraphicsEngine.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
}
public void Render(RenderContext ctx, ReadOnlySpan<RenderRequest> requests)
@@ -137,7 +164,9 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
}
else
{
var backBuffer = _renderGraph.ImportTexture(request.colorTarget, "BackBuffer", clearAtFirstUse: false, discardAtLastUse: false);
var backBuffer = _renderGraph.ImportTexture(request.colorTarget, "BackBuffer", clearAtFirstUse: true, discardAtLastUse: false);
MeshletDebugPass(backBuffer, request.opaqueRenderList, resourceDatabase.GetBindlessIndex(frameBufferResource.AsResource()), resourceDatabase.GetBindlessIndex(viewBufferResource.AsResource()));
}
// We must enqueue a return for the pooled resources so they are freed next frame.
@@ -147,16 +176,35 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
}
}
private void MeshletDebugPass(Identifier<RGTexture> backbuffer, RenderList renderList)
private void MeshletDebugPass(Identifier<RGTexture> backbuffer, RenderList renderList, uint globalIndex, uint viewIndex)
{
using (var builder = _renderGraph.AddRasterRenderPass<MeshletDebugPassData>("Meshlet Debug Pass", out var passData))
{
passData.renderList = renderList;
passData.globalIndex = globalIndex;
passData.viewIndex = viewIndex;
passData.material = _meshletMaterial;
builder.SetColorAttachment(backbuffer, 0);
builder.SetRenderFunc<MeshletDebugPassData>(static (data, ctx)=>
{
ctx.SetGlobalData(data.globalIndex, data.viewIndex);
ctx.SetActiveMaterial(data.material);
var instanceIndex = 0u;
foreach (var record in data.renderList)
{
ctx.SetActiveMesh(record.mesh);
ctx.SetInstanceIndex(instanceIndex);
var meshRefResult = ctx.ResourceManager.GetMeshReference(record.mesh);
if (meshRefResult.IsSuccess)
{
var meshletCount = (uint)meshRefResult.Value.MeshletData.meshlets.Count;
ctx.DispatchMesh(new uint3(meshletCount, 1, 1));
}
instanceIndex++;
}
});
}
}
@@ -168,6 +216,9 @@ public unsafe partial class TestRenderPipeline : IRenderPipeline
return;
}
_renderSystem.ResourceManager.ReleaseMaterial(_meshletMaterial);
_renderSystem.ResourceManager.ReleaseShader(_meshletShader);
_renderGraph.Dispose();
_disposed = true;

View File

@@ -2,6 +2,7 @@ using Ghost.Core;
using Ghost.Graphics.Test.Windows;
using Microsoft.UI.Xaml;
using Misaki.HighPerformance.LowLevel.Buffer;
using System.Runtime.InteropServices;
// To learn more about WinUI, the WinUI project structure,
@@ -32,22 +33,22 @@ public partial class UnitTestApp : Application
OperatingSystem.IsMacOS() ? "osx" : "unknown";
var arch = Environment.Is64BitProcess ? "x64" : "x86";
var nativeDllDir = Path.Combine(currentDir, "runtimes", platform + "-" + arch, "native");
//if (Directory.Exists(nativeDllDir))
//{
// foreach (var dll in Directory.EnumerateFiles(nativeDllDir, "*.dll"))
// {
// NativeLibrary.Load(dll);
// }
//}
NativeLibrary.SetDllImportResolver(typeof(UnitTestApp).Assembly, (libraryName, assembly, searchPath) =>
if (Directory.Exists(nativeDllDir))
{
if (libraryName == "dxcompiler")
foreach (var dll in Directory.EnumerateFiles(nativeDllDir, "*.dll"))
{
NativeLibrary.Load(Path.Combine(nativeDllDir, "dxil.dll"));
NativeLibrary.Load(dll);
}
}
//NativeLibrary.SetDllImportResolver(typeof(UnitTestApp).Assembly, (libraryName, assembly, searchPath) =>
//{
// if (libraryName == "dxcompiler")
// {
// NativeLibrary.Load(Path.Combine(nativeDllDir, "dxil.dll"));
// }
return IntPtr.Zero;
});
// return IntPtr.Zero;
//});
}
/// <summary>
@@ -58,6 +59,15 @@ public partial class UnitTestApp : Application
{
LoadDll();
var opts = new AllocationManagerInitOpts
{
ArenaCapacity = 1024 * 1024 * 1024, // 1GB
StackCapacity = 1024 * 1024 * 32, // 32MB
FreeListConcurrencyLevel = Environment.ProcessorCount,
};
AllocationManager.Initialize(opts);
_window = new GraphicsTestWindow();
_window.Activate();

View File

@@ -20,6 +20,8 @@ public sealed partial class GraphicsTestWindow : Window
private ISwapChain? _swapChain;
private World? _world;
private Handle<Mesh> _meshHandle;
private bool _isFirstActivationHandled;
public GraphicsTestWindow()
@@ -64,7 +66,9 @@ public sealed partial class GraphicsTestWindow : Window
_world.AddService(_renderSystem);
// Add Systems
_world.SystemManager.GetSystem<DefaultSystemGroup>().AddSystem<RenderExtractionSystem>();
var group = _world.SystemManager.GetSystem<DefaultSystemGroup>();
group.AddSystem<RenderExtractionSystem>();
group.SortSystems();
_world.SystemManager.InitializeAll(default);
@@ -101,16 +105,8 @@ public sealed partial class GraphicsTestWindow : Window
using var cmdAllocator = _renderSystem.GraphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics);
directCmd.Begin(cmdAllocator);
var meshHandle = ctx.CreateMesh(vertices, indices, true);
var meshRefResult = _renderSystem.ResourceManager.GetMeshReference(meshHandle);
if (meshRefResult.IsSuccess)
{
meshRefResult.Value.CookMeshlets();
}
ctx.UploadMeshlets(meshHandle);
ctx.UpdateObjectData(meshHandle);
_meshHandle = ctx.CreateMesh(vertices, indices, true);
ctx.UpdateObjectData(_meshHandle);
directCmd.End().ThrowIfFailed();
_renderSystem.GraphicsEngine.Device.GraphicsQueue.Submit(directCmd);
@@ -121,8 +117,8 @@ public sealed partial class GraphicsTestWindow : Window
var meshEntity = _world.EntityManager.CreateEntity(meshSet);
_world.EntityManager.SetComponent(meshEntity, new MeshInstance
{
mesh = meshHandle,
renderingLayerMask = new RenderingLayerMask(uint.MaxValue),
mesh = _meshHandle,
renderingLayerMask = RenderingLayerMask.All,
shadowCastingMode = Engine.ShadowCastingMode.On
});
@@ -147,6 +143,8 @@ public sealed partial class GraphicsTestWindow : Window
World.Destroy(_world.ID);
}
_renderSystem?.ResourceManager.ReleaseMesh(_meshHandle);
_swapChain?.Dispose();
_renderSystem?.Dispose();
@@ -188,6 +186,8 @@ public sealed partial class GraphicsTestWindow : Window
var queryID = new QueryBuilder().WithAll<Camera>().Build(_world);
ref var query = ref _world.ComponentManager.GetEntityQueryReference(queryID);
// FIX: A critical bug that resize happens on the render thread, but OnRendering invoke on the UI thread, and there is a chance that our extraction system already send the request to the render thread with old back buffer handle, which is already become invalid after resize.
// A proper solution is to use swap chain manager, camera only reference the id of the swap chain, and we will extract the current back buffer handle on the render thread.
foreach (ref var cam in query.GetComponentIterator<Camera>())
{
cam.colorTarget = _swapChain.GetCurrentBackBuffer();