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:
@@ -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>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user