forked from Misaki/GhostEngine
Major architecture upgrade: - Add native render pass merging (hardware pass grouping, load/store op inference) - Implement heap-based aliasing for textures & buffers (D3D12-style) - Unify resource model: buffers and textures in one registry - Extend builder API for buffer creation/usage, access flags, hints - Improve barrier/state tracking (buffer hints, indirect argument state) - Update caching, hashing, and debug output for new model - Add enums/structs: AttachmentLoadOp, StoreOp, BufferHint, etc. - D3D12 backend: support named resources, temp upload buffers, correct heap usage - Update docs, benchmarks, and project files for new features Brings render graph closer to AAA engine standards, enabling efficient memory usage, lower driver overhead, and a more flexible API.
218 lines
9.2 KiB
C#
218 lines
9.2 KiB
C#
using BenchmarkDotNet.Attributes;
|
|
using Ghost.Core;
|
|
|
|
namespace Ghost.RenderGraph.Concept.Benchmark;
|
|
|
|
[MemoryDiagnoser]
|
|
public class RenderGraphBenchmark
|
|
{
|
|
private RenderGraph _renderGraph = null!;
|
|
|
|
[GlobalSetup]
|
|
public void Setup()
|
|
{
|
|
_renderGraph = new RenderGraph();
|
|
|
|
// Warm up
|
|
ExecuteGraph(_renderGraph);
|
|
}
|
|
|
|
[Benchmark]
|
|
public void Execute()
|
|
{
|
|
_renderGraph.Reset();
|
|
ExecuteGraph(_renderGraph);
|
|
}
|
|
|
|
public static void ExecuteGraph(RenderGraph renderGraph, int idx = 0)
|
|
{
|
|
// Import external resources
|
|
var backbuffer = renderGraph.ImportTexture(
|
|
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "Backbuffer"));
|
|
|
|
// ===== GBuffer Pass =====
|
|
GBufferData gbufferData;
|
|
using (var builder = renderGraph.AddRasterRenderPass<GBufferData>("GBuffer Pass", out gbufferData))
|
|
{
|
|
// Create GBuffer textures
|
|
gbufferData.Albedo = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "GBuffer.Albedo"));
|
|
gbufferData.Normal = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "GBuffer.Normal"));
|
|
gbufferData.Depth = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.Depth32F, "GBuffer.Depth"));
|
|
|
|
// Store in pass data and mark as written
|
|
builder.SetColorAttachment(gbufferData.Albedo, 0);
|
|
builder.SetColorAttachment(gbufferData.Normal, 1);
|
|
builder.SetDepthAttachment(gbufferData.Depth);
|
|
|
|
builder.SetRenderFunc<GBufferData>(static (data, cmd) =>
|
|
{
|
|
// New api will handle render target setup and clearing automatically
|
|
|
|
//cmd.SetRenderTarget(data.Albedo.Name);
|
|
//cmd.SetRenderTarget(data.Normal.Name);
|
|
//cmd.SetDepthStencil(data.Depth.Name);
|
|
//cmd.ClearRenderTarget(data.Albedo.Name, 0, 0, 0, 1);
|
|
//cmd.ClearRenderTarget(data.Normal.Name, 0.5f, 0.5f, 1.0f, 1);
|
|
//cmd.ClearDepth(data.Depth.Name, 1.0f);
|
|
cmd.Draw(36000);
|
|
});
|
|
}
|
|
|
|
// Store GBuffer data in blackboard for other passes
|
|
renderGraph.Blackboard.Add(gbufferData);
|
|
|
|
// ===== Lighting Pass =====
|
|
Identifier<RGTexture> lightingOutput;
|
|
using (var builder = renderGraph.AddRasterRenderPass<LightingPassData>("Lighting Pass", out var lightingData))
|
|
{
|
|
// Read GBuffer from blackboard
|
|
var gbuffer = renderGraph.Blackboard.Get<GBufferData>();
|
|
|
|
lightingData.GBufferAlbedo = builder.UseTexture(gbuffer.Albedo, AccessFlags.Read);
|
|
lightingData.GBufferNormal = builder.UseTexture(gbuffer.Normal, AccessFlags.Read);
|
|
lightingData.GBufferDepth = builder.UseTexture(gbuffer.Depth, AccessFlags.Read);
|
|
|
|
// Create output texture
|
|
lightingOutput = builder.CreateTexture(
|
|
new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "LightingResult"));
|
|
builder.SetColorAttachment(lightingOutput, 0);
|
|
|
|
lightingData.OutputLighting = lightingOutput;
|
|
|
|
builder.SetRenderFunc<LightingPassData>(static (data, cmd) =>
|
|
{
|
|
cmd.BindShaderResource(data.GBufferAlbedo.AsResource(), 0);
|
|
cmd.BindShaderResource(data.GBufferNormal.AsResource(), 1);
|
|
cmd.BindShaderResource(data.GBufferDepth.AsResource(), 2);
|
|
cmd.Draw(3);
|
|
});
|
|
}
|
|
|
|
// ===== SSAO Pass (Async Compute) =====
|
|
Identifier<RGTexture> ssaoOutput;
|
|
Identifier<RGBuffer> ssaoBufferOutput;
|
|
using (var builder = renderGraph.AddComputeRenderPass<SSAOPassData>("SSAO Pass (Async)", out var ssaoData))
|
|
{
|
|
var gbuffer = renderGraph.Blackboard.Get<GBufferData>();
|
|
|
|
ssaoData.GBufferDepth = builder.UseTexture(gbuffer.Depth, AccessFlags.Read);
|
|
ssaoData.GBufferNormal = builder.UseTexture(gbuffer.Normal, AccessFlags.Read);
|
|
|
|
// SSAO Output
|
|
ssaoOutput = builder.CreateTexture(
|
|
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "SSAO"));
|
|
ssaoData.OutputSSAO = builder.UseTexture(ssaoOutput, AccessFlags.Write);
|
|
ssaoBufferOutput = builder.CreateBuffer(
|
|
new BufferDescriptor(1920 * 1080 * 4, sizeof(byte), BufferUsage.UnorderedAccess, "SSAO.Buffer"));
|
|
ssaoData.OutputSSAOBuffer = builder.UseBuffer(ssaoBufferOutput, AccessFlags.WriteAll);
|
|
|
|
builder.EnableAsyncCompute(true);
|
|
|
|
// Use SetComputeFunc with asyncCompute: true
|
|
builder.SetRenderFunc<SSAOPassData>(static (data, cmd) =>
|
|
{
|
|
cmd.BindShaderResource(data.GBufferDepth.AsResource(), 0);
|
|
cmd.BindShaderResource(data.GBufferNormal.AsResource(), 1);
|
|
cmd.BindUnorderedAccess(data.OutputSSAO.AsResource(), 0);
|
|
cmd.Dispatch(1920 / 8, 1080 / 8, 1);
|
|
});
|
|
}
|
|
|
|
// ===== Bloom Downsample Pass (will alias with albedo) =====
|
|
Identifier<RGTexture> bloomOutput;
|
|
using (var builder = renderGraph.AddRasterRenderPass<BloomDownsampleData>("Bloom Downsample", out var bloomData))
|
|
{
|
|
bloomData.Input = builder.UseTexture(lightingOutput, AccessFlags.Read);
|
|
|
|
// Create a texture that will alias with SSAO (same size, same format)
|
|
bloomOutput = builder.CreateTexture(
|
|
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "BloomDownsample"));
|
|
builder.SetColorAttachment(bloomOutput, 0);
|
|
builder.UseBuffer(ssaoBufferOutput, AccessFlags.Read);
|
|
|
|
bloomData.Output = bloomOutput;
|
|
|
|
builder.SetRenderFunc<BloomDownsampleData>(static (data, cmd) =>
|
|
{
|
|
cmd.BindShaderResource(data.Input.AsResource(), 0);
|
|
cmd.Draw(3);
|
|
});
|
|
}
|
|
|
|
// ===== Temporal AA Pass =====
|
|
Identifier<RGTexture> taaOutput;
|
|
using (var builder = renderGraph.AddRasterRenderPass<TAAPassData>("Temporal AA", out var taaData))
|
|
{
|
|
taaData.InputLighting = builder.UseTexture(lightingOutput, AccessFlags.Read);
|
|
|
|
taaOutput = builder.CreateTexture(
|
|
new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "TAA.Result"));
|
|
builder.SetColorAttachment(taaOutput, 0);
|
|
|
|
taaData.OutputTAA = taaOutput;
|
|
|
|
builder.SetRenderFunc<TAAPassData>(static (data, cmd) =>
|
|
{
|
|
cmd.BindShaderResource(data.InputLighting.AsResource(), 0);
|
|
cmd.Draw(3);
|
|
});
|
|
}
|
|
|
|
// ===== Post Processing Pass =====
|
|
using (var builder = renderGraph.AddRasterRenderPass<PostProcessingPassDataV1>("Post Processing 1", out var postData))
|
|
{
|
|
postData.InputLighting = lightingOutput;
|
|
|
|
builder.SetColorAttachment(backbuffer, 0);
|
|
builder.SetRenderFunc<PostProcessingPassDataV1>(static (data, cmd) =>
|
|
{
|
|
cmd.BindShaderResource(data.InputLighting.AsResource(), 0);
|
|
cmd.Draw(3);
|
|
});
|
|
}
|
|
|
|
using (var builder = renderGraph.AddRasterRenderPass<PostProcessingPassDataV2>("Post Processing 2", out var postData))
|
|
{
|
|
postData.InputTAA = builder.UseTexture(taaOutput, AccessFlags.Read);
|
|
postData.InputSSAO = builder.UseTexture(ssaoOutput, AccessFlags.Read);
|
|
postData.InputBloom = builder.UseTexture(bloomOutput, AccessFlags.Read);
|
|
|
|
builder.SetColorAttachment(backbuffer, 0);
|
|
builder.SetRenderFunc<PostProcessingPassDataV2>(static (data, cmd) =>
|
|
{
|
|
cmd.BindShaderResource(data.InputTAA.AsResource(), 0);
|
|
cmd.BindShaderResource(data.InputSSAO.AsResource(), 1);
|
|
cmd.BindShaderResource(data.InputBloom.AsResource(), 2);
|
|
cmd.Draw(3);
|
|
});
|
|
}
|
|
|
|
// ===== GPU Profiler Marker Pass (non-cullable, textureless) =====
|
|
using (var builder = renderGraph.AddRasterRenderPass<ProfilerMarkerData>("GPU Profiler Begin Frame", out var profilerData))
|
|
{
|
|
builder.AllowPassCulling(false); // Never cull this - it's for debugging/profiling
|
|
builder.SetRenderFunc<ProfilerMarkerData>(static (data, cmd) =>
|
|
{
|
|
// Note: In a real implementation we would have specific profiler commands
|
|
// For now, since RasterRenderContext doesn't expose generic console write, we skip the print
|
|
// or we would add a specific Profiler method to the context
|
|
});
|
|
}
|
|
|
|
// ===== Unused Debug Pass (will be culled) =====
|
|
using (var builder = renderGraph.AddRasterRenderPass<DebugPassData>("Unused Debug Pass", out var debugData))
|
|
{
|
|
builder.SetColorAttachment(
|
|
builder.CreateTexture(new TextureDescriptor(512, 512, TextureFormat.RGBA8, "DebugTexture")), 0);
|
|
|
|
builder.SetRenderFunc<DebugPassData>(static (data, cmd) =>
|
|
{
|
|
cmd.Draw(100);
|
|
});
|
|
}
|
|
|
|
// Compile and execute the render graph
|
|
renderGraph.Compile();
|
|
renderGraph.Execute();
|
|
}
|
|
} |