forked from Misaki/GhostEngine
Refactor Render Graph: unified resources, benchmarking
Major overhaul of Render Graph system: - Replaced texture handles with generic Identifier<T> for unified, type-safe resource management (textures, buffers, etc.) - Refactored resource registry and pooling for performance and extensibility - Added AccessFlags and TextureAccess for precise resource usage tracking - Split passes into Raster and Compute types; introduced builder interfaces for safer pass construction - Modernized pass setup API (SetColorAttachment, UseTexture, etc.) - Updated command buffer and context structs to use new resource system - Refactored barrier and aliasing logic for improved correctness - Integrated BenchmarkDotNet for performance/memory benchmarking - Improved blackboard type safety and removed obsolete code/extensions - Added BenchmarkDotNet NuGet package These changes make the Render Graph more extensible, efficient, and ready for future resource types and advanced features.
This commit is contained in:
200
Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs
Normal file
200
Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
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();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Execute()
|
||||
{
|
||||
ExecuteGraph(_renderGraph);
|
||||
}
|
||||
|
||||
public static void ExecuteGraph(RenderGraph renderGraph)
|
||||
{
|
||||
renderGraph.Reset(); // new RenderGraph()
|
||||
|
||||
// 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, 1);
|
||||
|
||||
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;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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<PostProcessingPassDataV2>("Post Processing", 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();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
<DefineConstants>$(DefineConstants);PROFILING</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
// ===== Pass Data Structures =====
|
||||
@@ -5,44 +7,44 @@ namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
public sealed class GBufferData : IPassData
|
||||
{
|
||||
public RenderGraphTextureHandle Albedo;
|
||||
public RenderGraphTextureHandle Normal;
|
||||
public RenderGraphTextureHandle Depth;
|
||||
public Identifier<RGTexture> Albedo;
|
||||
public Identifier<RGTexture> Normal;
|
||||
public Identifier<RGTexture> Depth;
|
||||
}
|
||||
|
||||
public sealed class LightingPassData : IPassData
|
||||
{
|
||||
public RenderGraphTextureHandle GBufferAlbedo;
|
||||
public RenderGraphTextureHandle GBufferNormal;
|
||||
public RenderGraphTextureHandle GBufferDepth;
|
||||
public RenderGraphTextureHandle OutputLighting;
|
||||
public Identifier<RGTexture> GBufferAlbedo;
|
||||
public Identifier<RGTexture> GBufferNormal;
|
||||
public Identifier<RGTexture> GBufferDepth;
|
||||
public Identifier<RGTexture> OutputLighting;
|
||||
}
|
||||
|
||||
public sealed class SSAOPassData : IPassData
|
||||
{
|
||||
public RenderGraphTextureHandle GBufferDepth;
|
||||
public RenderGraphTextureHandle GBufferNormal;
|
||||
public RenderGraphTextureHandle OutputSSAO;
|
||||
public Identifier<RGTexture> GBufferDepth;
|
||||
public Identifier<RGTexture> GBufferNormal;
|
||||
public Identifier<RGTexture> OutputSSAO;
|
||||
}
|
||||
|
||||
public sealed class BloomDownsampleData : IPassData
|
||||
{
|
||||
public RenderGraphTextureHandle Input;
|
||||
public RenderGraphTextureHandle Output;
|
||||
public Identifier<RGTexture> Input;
|
||||
public Identifier<RGTexture> Output;
|
||||
}
|
||||
|
||||
public sealed class TAAPassData : IPassData
|
||||
{
|
||||
public RenderGraphTextureHandle InputLighting;
|
||||
public RenderGraphTextureHandle OutputTAA;
|
||||
public Identifier<RGTexture> InputLighting;
|
||||
public Identifier<RGTexture> OutputTAA;
|
||||
}
|
||||
|
||||
public sealed class PostProcessingPassDataV2 : IPassData
|
||||
{
|
||||
public RenderGraphTextureHandle InputTAA;
|
||||
public RenderGraphTextureHandle InputSSAO;
|
||||
public RenderGraphTextureHandle InputBloom;
|
||||
public RenderGraphTextureHandle OutputBackbuffer;
|
||||
public Identifier<RGTexture> InputTAA;
|
||||
public Identifier<RGTexture> InputSSAO;
|
||||
public Identifier<RGTexture> InputBloom;
|
||||
public Identifier<RGTexture> OutputBackbuffer;
|
||||
}
|
||||
|
||||
public sealed class ProfilerMarkerData : IPassData
|
||||
@@ -51,5 +53,5 @@ public sealed class ProfilerMarkerData : IPassData
|
||||
|
||||
public sealed class DebugPassData : IPassData
|
||||
{
|
||||
public RenderGraphTextureHandle DebugTexture;
|
||||
public Identifier<RGTexture> DebugTexture;
|
||||
}
|
||||
|
||||
@@ -1,210 +1,42 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.RenderGraph.Concept;
|
||||
using Ghost.RenderGraph.Concept.Benchmark;
|
||||
|
||||
var renderGraph = new RenderGraph();
|
||||
|
||||
#if !DEBUG
|
||||
const int _ITERATION = 500;
|
||||
for (var i = 0; i < _ITERATION; i++)
|
||||
{
|
||||
ExecuteGraph(renderGraph);
|
||||
}
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
// Thread.Sleep(1000); // Leave a gap in visual studio allocations timeline
|
||||
var sw = new System.Diagnostics.Stopwatch();
|
||||
var gcBefore = GC.GetAllocatedBytesForCurrentThread();
|
||||
sw.Start();
|
||||
BenchmarkDotNet.Running.BenchmarkRunner.Run<RenderGraphBenchmark>();
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _ITERATION; i++)
|
||||
{
|
||||
ExecuteGraph(renderGraph);
|
||||
}
|
||||
//const int _ITERATION = 500000;
|
||||
//for (var i = 0; i < _ITERATION; i++)
|
||||
//{
|
||||
// RenderGraphBenchmark.ExecuteGraph(renderGraph);
|
||||
//}
|
||||
|
||||
sw.Stop();
|
||||
var gcAfter = GC.GetAllocatedBytesForCurrentThread();
|
||||
//GC.Collect();
|
||||
//GC.WaitForPendingFinalizers();
|
||||
////Thread.Sleep(1000); // Leave a gap in visual studio allocations timeline
|
||||
//var sw = new System.Diagnostics.Stopwatch();
|
||||
//var gcBefore = GC.GetAllocatedBytesForCurrentThread();
|
||||
//sw.Start();
|
||||
|
||||
Console.WriteLine($"{sw.Elapsed.TotalNanoseconds / _ITERATION} ns (per iteration)");
|
||||
Console.WriteLine($"GC Allocated Bytes: {(gcAfter - gcBefore) / _ITERATION} bytes (per iteration)");
|
||||
//for (var i = 0; i < _ITERATION; i++)
|
||||
//{
|
||||
// RenderGraphBenchmark.ExecuteGraph(renderGraph);
|
||||
//}
|
||||
|
||||
//sw.Stop();
|
||||
//var gcAfter = GC.GetAllocatedBytesForCurrentThread();
|
||||
|
||||
//Console.WriteLine($"{sw.Elapsed.TotalNanoseconds / _ITERATION} ns (per iteration)");
|
||||
//Console.WriteLine($"GC Allocated Bytes: {(gcAfter - gcBefore) / _ITERATION} bytes (per iteration)");
|
||||
#else
|
||||
// Run twice to demonstrate cache hit
|
||||
Console.WriteLine("=== FRAME 1 (Cache Miss Expected) ===");
|
||||
ExecuteGraph(renderGraph);
|
||||
RenderGraphBenchmark.ExecuteGraph(renderGraph);
|
||||
|
||||
Console.WriteLine("\n\n=== FRAME 2 (Cache Hit Expected) ===");
|
||||
ExecuteGraph(renderGraph);
|
||||
RenderGraphBenchmark.ExecuteGraph(renderGraph);
|
||||
#endif
|
||||
|
||||
static void ExecuteGraph(RenderGraph renderGraph)
|
||||
{
|
||||
renderGraph.Reset(); // new RenderGraph()
|
||||
|
||||
// Import external resources
|
||||
var backbuffer = renderGraph.ImportTexture(
|
||||
"Backbuffer",
|
||||
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "Backbuffer"));
|
||||
|
||||
// ===== GBuffer Pass =====
|
||||
GBufferData gbufferData;
|
||||
using (var builder = renderGraph.AddRenderPass<GBufferData>("GBuffer Pass", out gbufferData))
|
||||
{
|
||||
// Create GBuffer textures
|
||||
var albedo = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "GBuffer.Albedo"));
|
||||
var normal = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "GBuffer.Normal"));
|
||||
var depth = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.Depth32F, "GBuffer.Depth"));
|
||||
|
||||
// Store in pass data and mark as written
|
||||
gbufferData.Albedo = builder.WriteTexture(albedo);
|
||||
gbufferData.Normal = builder.WriteTexture(normal);
|
||||
gbufferData.Depth = builder.UseDepthBuffer(depth, writeAccess: true);
|
||||
|
||||
builder.SetRenderFunc<GBufferData>((data, cmd) =>
|
||||
{
|
||||
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 =====
|
||||
RenderGraphTextureHandle lightingOutput;
|
||||
using (var builder = renderGraph.AddRenderPass<LightingPassData>("Lighting Pass", out var lightingData))
|
||||
{
|
||||
// Read GBuffer from blackboard
|
||||
var gbuffer = renderGraph.Blackboard.Get<GBufferData>();
|
||||
|
||||
lightingData.GBufferAlbedo = builder.ReadTexture(gbuffer.Albedo);
|
||||
lightingData.GBufferNormal = builder.ReadTexture(gbuffer.Normal);
|
||||
lightingData.GBufferDepth = builder.ReadTexture(gbuffer.Depth);
|
||||
|
||||
// Create output texture
|
||||
lightingOutput = builder.CreateTexture(
|
||||
new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "LightingResult"));
|
||||
lightingData.OutputLighting = builder.WriteTexture(lightingOutput);
|
||||
|
||||
builder.SetRenderFunc<LightingPassData>((data, cmd) =>
|
||||
{
|
||||
cmd.BindShaderResource(data.GBufferAlbedo.Name, 0);
|
||||
cmd.BindShaderResource(data.GBufferNormal.Name, 1);
|
||||
cmd.BindShaderResource(data.GBufferDepth.Name, 2);
|
||||
cmd.SetRenderTarget(data.OutputLighting.Name);
|
||||
cmd.Draw(3);
|
||||
});
|
||||
}
|
||||
|
||||
// ===== SSAO Pass (Async Compute) =====
|
||||
RenderGraphTextureHandle ssaoOutput;
|
||||
using (var builder = renderGraph.AddRenderPass<SSAOPassData>("SSAO Pass (Async)", out var ssaoData))
|
||||
{
|
||||
var gbuffer = renderGraph.Blackboard.Get<GBufferData>();
|
||||
|
||||
ssaoData.GBufferDepth = builder.ReadTexture(gbuffer.Depth);
|
||||
ssaoData.GBufferNormal = builder.ReadTexture(gbuffer.Normal);
|
||||
|
||||
// SSAO Output
|
||||
ssaoOutput = builder.CreateTexture(
|
||||
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "SSAO"));
|
||||
ssaoData.OutputSSAO = builder.WriteTexture(ssaoOutput);
|
||||
|
||||
// Use SetComputeFunc with asyncCompute: true
|
||||
builder.SetComputeFunc<SSAOPassData>((data, cmd) =>
|
||||
{
|
||||
cmd.BindShaderResource(data.GBufferDepth.Name, 0);
|
||||
cmd.BindShaderResource(data.GBufferNormal.Name, 1);
|
||||
cmd.BindUnorderedAccess(data.OutputSSAO.Name, 0);
|
||||
cmd.Dispatch(1920 / 8, 1080 / 8, 1);
|
||||
}, asyncCompute: true);
|
||||
}
|
||||
|
||||
// ===== Bloom Downsample Pass (will alias with albedo) =====
|
||||
RenderGraphTextureHandle bloomOutput;
|
||||
using (var builder = renderGraph.AddRenderPass<BloomDownsampleData>("Bloom Downsample", out var bloomData))
|
||||
{
|
||||
bloomData.Input = builder.ReadTexture(lightingOutput);
|
||||
|
||||
// Create a texture that will alias with SSAO (same size, same format)
|
||||
bloomOutput = builder.CreateTexture(
|
||||
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "BloomDownsample"));
|
||||
bloomData.Output = builder.WriteTexture(bloomOutput);
|
||||
|
||||
builder.SetRenderFunc<BloomDownsampleData>((data, cmd) =>
|
||||
{
|
||||
cmd.BindShaderResource(data.Input.Name, 0);
|
||||
cmd.SetRenderTarget(data.Output.Name);
|
||||
cmd.Draw(3);
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Temporal AA Pass =====
|
||||
RenderGraphTextureHandle taaOutput;
|
||||
using (var builder = renderGraph.AddRenderPass<TAAPassData>("Temporal AA", out var taaData))
|
||||
{
|
||||
taaData.InputLighting = builder.ReadTexture(lightingOutput);
|
||||
|
||||
taaOutput = builder.CreateTexture(
|
||||
new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "TAA.Result"));
|
||||
taaData.OutputTAA = builder.WriteTexture(taaOutput);
|
||||
|
||||
builder.SetRenderFunc<TAAPassData>((data, cmd) =>
|
||||
{
|
||||
cmd.BindShaderResource(data.InputLighting.Name, 0);
|
||||
cmd.SetRenderTarget(data.OutputTAA.Name);
|
||||
cmd.Draw(3);
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Post Processing Pass =====
|
||||
using (var builder = renderGraph.AddRenderPass<PostProcessingPassDataV2>("Post Processing", out var postData))
|
||||
{
|
||||
postData.InputTAA = builder.ReadTexture(taaOutput);
|
||||
postData.InputSSAO = builder.ReadTexture(ssaoOutput);
|
||||
postData.InputBloom = builder.ReadTexture(bloomOutput);
|
||||
postData.OutputBackbuffer = builder.WriteTexture(backbuffer);
|
||||
|
||||
builder.SetRenderFunc<PostProcessingPassDataV2>((data, cmd) =>
|
||||
{
|
||||
cmd.BindShaderResource(data.InputTAA.Name, 0);
|
||||
cmd.BindShaderResource(data.InputSSAO.Name, 1);
|
||||
cmd.BindShaderResource(data.InputBloom.Name, 2);
|
||||
cmd.SetRenderTarget(data.OutputBackbuffer.Name);
|
||||
cmd.Draw(3);
|
||||
});
|
||||
}
|
||||
|
||||
// ===== GPU Profiler Marker Pass (non-cullable, textureless) =====
|
||||
using (var builder = renderGraph.AddRenderPass<ProfilerMarkerData>("GPU Profiler Begin Frame", out var profilerData))
|
||||
{
|
||||
builder.SetAllowCulling(false); // Never cull this - it's for debugging/profiling
|
||||
builder.SetRenderFunc<ProfilerMarkerData>((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.AddRenderPass<DebugPassData>("Unused Debug Pass", out var debugData))
|
||||
{
|
||||
debugData.DebugTexture = builder.WriteTexture(
|
||||
builder.CreateTexture(new TextureDescriptor(512, 512, TextureFormat.RGBA8, "DebugTexture")));
|
||||
|
||||
builder.SetRenderFunc<DebugPassData>((data, cmd) =>
|
||||
{
|
||||
cmd.SetRenderTarget(data.DebugTexture.Name);
|
||||
cmd.ClearRenderTarget(data.DebugTexture.Name, 1, 0, 1, 1);
|
||||
cmd.Draw(100);
|
||||
});
|
||||
}
|
||||
|
||||
// Compile and execute the render graph
|
||||
renderGraph.Compile();
|
||||
renderGraph.Execute();
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.IO.Hashing;
|
||||
using System.Threading;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
@@ -28,9 +29,6 @@ public sealed class RenderGraph
|
||||
private readonly List<ResourceBarrier> _barriers = new(128);
|
||||
private readonly RenderGraphCompilationCache _compilationCache = new();
|
||||
|
||||
private readonly XxHash64 _hasher = new();
|
||||
|
||||
private int _passCount;
|
||||
private bool _compiled;
|
||||
|
||||
public RenderGraphBlackboard Blackboard { get; } = new();
|
||||
@@ -60,13 +58,13 @@ public sealed class RenderGraph
|
||||
_barriers.Clear();
|
||||
|
||||
// Return passes to the pool and reset count
|
||||
for (var i = 0; i < _passCount; i++)
|
||||
for (var i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
var pass = _passes[i];
|
||||
pass.Clear();
|
||||
_objectPool.Release(pass);
|
||||
pass.Reset(_objectPool);
|
||||
}
|
||||
_passCount = 0;
|
||||
|
||||
_passes.Clear();
|
||||
|
||||
// Clear compiled passes list
|
||||
_compiledPasses.Clear();
|
||||
@@ -76,57 +74,34 @@ public sealed class RenderGraph
|
||||
/// <summary>
|
||||
/// Imports an external texture into the render graph.
|
||||
/// </summary>
|
||||
public RenderGraphTextureHandle ImportTexture(string name, TextureDescriptor descriptor)
|
||||
public Identifier<RGTexture> ImportTexture(TextureDescriptor descriptor)
|
||||
{
|
||||
return _resources.ImportTexture(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new render pass to the graph.
|
||||
/// </summary>
|
||||
public RenderGraphBuilder AddRenderPass<TPassData>(string name, out TPassData passData)
|
||||
public IRasterRenderGraphBuilder AddRasterRenderPass<TPassData>(string name, out TPassData passData)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
// Get or create pass from pool
|
||||
RenderGraphPass<TPassData> pass;
|
||||
if (_passCount < _passes.Count)
|
||||
{
|
||||
// Reuse existing slot
|
||||
var existingPass = _passes[_passCount];
|
||||
if (existingPass is RenderGraphPass<TPassData> typedPass)
|
||||
{
|
||||
pass = typedPass;
|
||||
pass.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Type mismatch, need to replace
|
||||
_objectPool.Release(existingPass);
|
||||
pass = _objectPool.Get<RenderGraphPass<TPassData>>();
|
||||
pass.Reset();
|
||||
_passes[_passCount] = pass;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Need to grow the list
|
||||
pass = _objectPool.Get<RenderGraphPass<TPassData>>();
|
||||
pass.Reset();
|
||||
_passes.Add(pass);
|
||||
var renderPass = _objectPool.Rent<RasterRenderGraphPass<TPassData>>();
|
||||
renderPass.Init(_passes.Count, _objectPool.Rent<TPassData>(), name, RenderPassType.Raster);
|
||||
passData = renderPass.passData;
|
||||
|
||||
_passes.Add(renderPass);
|
||||
|
||||
_builder.Init(this, renderPass, _resources);
|
||||
return _builder;
|
||||
}
|
||||
|
||||
// Initialize pass
|
||||
pass.Name = name;
|
||||
pass.Index = _passCount;
|
||||
public IComputeRenderGraphBuilder AddComputeRenderPass<TPassData>(string name, out TPassData passData)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
var renderPass = _objectPool.Rent<ComputeRenderGraphPass<TPassData>>();
|
||||
renderPass.Init(_passes.Count, _objectPool.Rent<TPassData>(), name, RenderPassType.Compute);
|
||||
passData = renderPass.passData;
|
||||
|
||||
// Get or create pass data from pool
|
||||
passData = _objectPool.Get<TPassData>();
|
||||
pass.PassData = passData;
|
||||
_passes.Add(renderPass);
|
||||
|
||||
_passCount++;
|
||||
|
||||
// Initialize builder
|
||||
_builder.Initialize(pass, _resources);
|
||||
_builder.Init(this, renderPass, _resources);
|
||||
return _builder;
|
||||
}
|
||||
|
||||
@@ -138,82 +113,84 @@ public sealed class RenderGraph
|
||||
private unsafe ulong ComputeGraphHash()
|
||||
{
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var bufferPool = new UnsafeList<byte>(4096, scope.AllocationHandle);
|
||||
int offset = 0;
|
||||
var bufferPool = new UnsafeList<byte>(2048, scope.AllocationHandle);
|
||||
var pData = (byte*)bufferPool.GetUnsafePtr();
|
||||
|
||||
_hasher.Reset();
|
||||
var offset = 0;
|
||||
|
||||
// Hash pass count
|
||||
_hasher.AppendInt(_passCount);
|
||||
*(int*)(pData + offset) = _passes.Count;
|
||||
offset += sizeof(int);
|
||||
|
||||
// Hash each pass structure (excluding names)
|
||||
for (int i = 0; i < _passCount; i++)
|
||||
for (var i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
var pass = _passes[i];
|
||||
// Save 0.004ms.
|
||||
|
||||
//// Hash pass properties that affect compilation
|
||||
//_hasher.AppendEnum(pass.Type);
|
||||
//_hasher.AppendBool(pass.AllowCulling);
|
||||
//_hasher.AppendBool(pass.AsyncCompute);
|
||||
|
||||
//// Hash texture dependencies (only indices, not versions or names)
|
||||
//_hasher.AppendHandleList(pass.TextureReads);
|
||||
//_hasher.AppendHandleList(pass.TextureWrites);
|
||||
//_hasher.AppendHandleList(pass.TextureCreates);
|
||||
*(RenderPassType*)(pData + offset) = pass.Type;
|
||||
*(RenderPassType*)(pData + offset) = pass.type;
|
||||
offset += sizeof(RenderPassType);
|
||||
|
||||
*(bool*)(pData + offset) = pass.AllowCulling;
|
||||
*(bool*)(pData + offset) = pass.allowCulling;
|
||||
offset += sizeof(bool);
|
||||
|
||||
*(bool*)(pData + offset) = pass.AsyncCompute;
|
||||
*(bool*)(pData + offset) = pass.asyncCompute;
|
||||
offset += sizeof(bool);
|
||||
|
||||
*(int*)(pData + offset) = pass.TextureReads.Count;
|
||||
*(TextureAccess*)(pData + offset) = pass.depthAccess;
|
||||
offset += sizeof(TextureAccess);
|
||||
|
||||
*(int*)(pData + offset) = pass.maxColorIndex;
|
||||
offset += sizeof(int);
|
||||
for (int j = 0; j < pass.TextureReads.Count; j++)
|
||||
for (var j = 0; j <= pass.maxColorIndex; j++)
|
||||
{
|
||||
*(int*)(pData + offset) = pass.TextureReads[j].Index;
|
||||
*(TextureAccess*)(pData + offset) = pass.colorAccess[j];
|
||||
offset += sizeof(TextureAccess);
|
||||
}
|
||||
|
||||
*(int*)(pData + offset) = pass.resourceReads.Count;
|
||||
offset += sizeof(int);
|
||||
for (var j = 0; j < pass.resourceReads.Count; j++)
|
||||
{
|
||||
*(int*)(pData + offset) = pass.resourceReads[j].Value;
|
||||
offset += sizeof(int);
|
||||
}
|
||||
|
||||
*(int*)(pData + offset) = pass.TextureWrites.Count;
|
||||
*(int*)(pData + offset) = pass.resourceWrites.Count;
|
||||
offset += sizeof(int);
|
||||
for (int j = 0; j < pass.TextureWrites.Count; j++)
|
||||
for (var j = 0; j < pass.resourceWrites.Count; j++)
|
||||
{
|
||||
*(int*)(pData + offset) = pass.TextureWrites[j].Index;
|
||||
*(int*)(pData + offset) = pass.resourceWrites[j].Value;
|
||||
offset += sizeof(int);
|
||||
}
|
||||
|
||||
*(int*)(pData + offset) = pass.TextureCreates.Count;
|
||||
*(int*)(pData + offset) = pass.resourceCreates.Count;
|
||||
offset += sizeof(int);
|
||||
for (int j = 0; j < pass.TextureCreates.Count; j++)
|
||||
for (var j = 0; j < pass.resourceCreates.Count; j++)
|
||||
{
|
||||
*(int*)(pData + offset) = pass.TextureCreates[j].Index;
|
||||
*(int*)(pData + offset) = pass.resourceCreates[j].Value;
|
||||
offset += sizeof(int);
|
||||
}
|
||||
}
|
||||
|
||||
// Hash resource descriptors
|
||||
for (int i = 0; i < _resources.TextureResourceCount; i++)
|
||||
for (var i = 0; i < _resources.TextureResourceCount; i++)
|
||||
{
|
||||
var resource = _resources.GetTextureResourceByIndex(i);
|
||||
|
||||
*(int*)(pData + offset) = resource.Descriptor.Width;
|
||||
offset += sizeof(int);
|
||||
|
||||
*(int*)(pData + offset) = resource.Descriptor.Height;
|
||||
offset += sizeof(int);
|
||||
|
||||
*(TextureFormat*)(pData + offset) = resource.Descriptor.Format;
|
||||
offset += sizeof(TextureFormat);
|
||||
|
||||
*(bool*)(pData + offset) = resource.IsImported;
|
||||
offset += sizeof(bool);
|
||||
}
|
||||
|
||||
var span = new Span<byte>(pData, offset);
|
||||
_hasher.Append(span);
|
||||
return _hasher.GetCurrentHashAsUInt64();
|
||||
return XxHash64.HashToUInt64(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -222,14 +199,16 @@ public sealed class RenderGraph
|
||||
public void Compile()
|
||||
{
|
||||
if (_compiled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
#endif
|
||||
|
||||
// Step 0: Check cache
|
||||
ulong graphHash = ComputeGraphHash();
|
||||
var graphHash = ComputeGraphHash(); // 1321433047288519964
|
||||
|
||||
#if DEBUG
|
||||
var hashTime = sw.Elapsed.TotalMicroseconds;
|
||||
@@ -257,18 +236,18 @@ public sealed class RenderGraph
|
||||
_compiledPasses.Clear();
|
||||
|
||||
// Step 1: Mark passes with side effects (writes to imported resources)
|
||||
for (var i = 0; i < _passCount; i++)
|
||||
for (var i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
var pass = _passes[i];
|
||||
|
||||
// Check if this pass writes to any imported textures
|
||||
for (var j = 0; j < pass.TextureWrites.Count; j++)
|
||||
for (var j = 0; j < pass.resourceWrites.Count; j++)
|
||||
{
|
||||
var writeHandle = pass.TextureWrites[j];
|
||||
var resource = _resources.GetTextureResource(writeHandle);
|
||||
var writeHandle = pass.resourceWrites[j];
|
||||
var resource = _resources.GetResource(writeHandle);
|
||||
if (resource.IsImported)
|
||||
{
|
||||
pass.HasSideEffects = true;
|
||||
pass.hasSideEffects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -276,33 +255,33 @@ public sealed class RenderGraph
|
||||
|
||||
// Step 2: Cull passes based on dependency analysis
|
||||
// Mark all passes as culled initially
|
||||
for (var i = 0; i < _passCount; i++)
|
||||
for (var i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
_passes[i].Culled = _passes[i].AllowCulling && !_passes[i].HasSideEffects;
|
||||
_passes[i].culled = _passes[i].allowCulling && !_passes[i].hasSideEffects;
|
||||
}
|
||||
|
||||
// Step 3: Traverse backwards from passes with side effects
|
||||
for (var i = _passCount - 1; i >= 0; i--)
|
||||
for (var i = _passes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var pass = _passes[i];
|
||||
if (!pass.Culled)
|
||||
if (!pass.culled)
|
||||
{
|
||||
UnculDependencies(pass);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Build final pass list (only non-culled passes)
|
||||
for (var i = 0; i < _passCount; i++)
|
||||
for (var i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
var pass = _passes[i];
|
||||
if (!pass.Culled)
|
||||
if (!pass.culled)
|
||||
{
|
||||
_compiledPasses.Add(pass);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Perform resource aliasing to minimize memory usage
|
||||
_aliasingManager.AssignPhysicalResources(_resources, _passCount);
|
||||
_aliasingManager.AssignPhysicalResources(_resources, _passes.Count);
|
||||
|
||||
// Step 6: Generate barriers for state transitions and aliasing
|
||||
GenerateBarriers();
|
||||
@@ -320,16 +299,16 @@ public sealed class RenderGraph
|
||||
{
|
||||
// Restore compiled pass list
|
||||
_compiledPasses.Clear();
|
||||
for (int i = 0; i < cached.CompiledPassIndices.Count; i++)
|
||||
for (var i = 0; i < cached.CompiledPassIndices.Count; i++)
|
||||
{
|
||||
int passIndex = cached.CompiledPassIndices[i];
|
||||
var passIndex = cached.CompiledPassIndices[i];
|
||||
_compiledPasses.Add(_passes[passIndex]);
|
||||
}
|
||||
|
||||
// Restore culling flags
|
||||
for (int i = 0; i < _passCount && i < cached.PassCulledFlags.Count; i++)
|
||||
for (var i = 0; i < _passes.Count && i < cached.PassCulledFlags.Count; i++)
|
||||
{
|
||||
_passes[i].Culled = cached.PassCulledFlags[i];
|
||||
_passes[i].culled = cached.PassCulledFlags[i];
|
||||
}
|
||||
|
||||
// Restore aliasing mappings (need to update ResourceAliasingManager)
|
||||
@@ -337,7 +316,7 @@ public sealed class RenderGraph
|
||||
|
||||
// Restore barriers (deep copy to avoid shared references)
|
||||
_barriers.Clear();
|
||||
for (int i = 0; i < cached.Barriers.Count; i++)
|
||||
for (var i = 0; i < cached.Barriers.Count; i++)
|
||||
{
|
||||
_barriers.Add(cached.Barriers[i]);
|
||||
}
|
||||
@@ -358,22 +337,22 @@ public sealed class RenderGraph
|
||||
var cacheData = new CachedCompilation();
|
||||
|
||||
// Store compiled pass indices
|
||||
for (int i = 0; i < _compiledPasses.Count; i++)
|
||||
for (var i = 0; i < _compiledPasses.Count; i++)
|
||||
{
|
||||
cacheData.CompiledPassIndices.Add(_compiledPasses[i].Index);
|
||||
cacheData.CompiledPassIndices.Add(_compiledPasses[i].index);
|
||||
}
|
||||
|
||||
// Store culling flags for all passes
|
||||
for (int i = 0; i < _passCount; i++)
|
||||
for (var i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
cacheData.PassCulledFlags.Add(_passes[i].Culled);
|
||||
cacheData.PassCulledFlags.Add(_passes[i].culled);
|
||||
}
|
||||
|
||||
// Store aliasing mappings
|
||||
_aliasingManager.StoreToCache(cacheData.LogicalToPhysical, cacheData.PhysicalResources);
|
||||
|
||||
// Store barriers
|
||||
for (int i = 0; i < _barriers.Count; i++)
|
||||
for (var i = 0; i < _barriers.Count; i++)
|
||||
{
|
||||
cacheData.Barriers.Add(_barriers[i]);
|
||||
}
|
||||
@@ -393,17 +372,17 @@ public sealed class RenderGraph
|
||||
private void UnculDependencies(RenderGraphPassBase pass)
|
||||
{
|
||||
// Un-cull all producers of textures we read
|
||||
for (var i = 0; i < pass.TextureReads.Count; i++)
|
||||
for (var i = 0; i < pass.resourceReads.Count; i++)
|
||||
{
|
||||
var readHandle = pass.TextureReads[i];
|
||||
var resource = _resources.GetTextureResource(readHandle);
|
||||
var readHandle = pass.resourceReads[i];
|
||||
var resource = _resources.GetResource(readHandle);
|
||||
|
||||
if (resource.ProducerPass >= 0)
|
||||
{
|
||||
var producer = _passes[resource.ProducerPass];
|
||||
if (producer.Culled)
|
||||
if (producer.culled)
|
||||
{
|
||||
producer.Culled = false;
|
||||
producer.culled = false;
|
||||
UnculDependencies(producer);
|
||||
}
|
||||
}
|
||||
@@ -446,20 +425,20 @@ public sealed class RenderGraph
|
||||
private void InsertAliasingBarriers(RenderGraphPassBase pass, int passIdx)
|
||||
{
|
||||
// Check all resources written by this pass
|
||||
for (int i = 0; i < pass.TextureWrites.Count; i++)
|
||||
for (var i = 0; i < pass.resourceWrites.Count; i++)
|
||||
{
|
||||
var handle = pass.TextureWrites[i];
|
||||
var resource = _resources.GetTextureResource(handle);
|
||||
var id = pass.resourceWrites[i];
|
||||
var resource = _resources.GetResource(id);
|
||||
|
||||
// Skip imported resources
|
||||
if (resource.IsImported)
|
||||
continue;
|
||||
|
||||
// Check if this is the first use of this logical resource
|
||||
if (resource.FirstUsePass == pass.Index)
|
||||
if (resource.FirstUsePass == pass.index)
|
||||
{
|
||||
// Get the physical resource
|
||||
int physicalIndex = _aliasingManager.GetPhysicalResourceIndex(handle.Index);
|
||||
// Rent the physical resource
|
||||
var physicalIndex = _aliasingManager.GetPhysicalResourceIndex(id.Value);
|
||||
if (physicalIndex >= 0)
|
||||
{
|
||||
var physical = _aliasingManager.GetPhysicalResource(physicalIndex);
|
||||
@@ -469,23 +448,20 @@ public sealed class RenderGraph
|
||||
if (physical != null && physical.AliasedLogicalResources.Count > 1)
|
||||
{
|
||||
// Find the resource that used this physical memory most recently before this pass
|
||||
RenderGraphTextureHandle resourceBefore = default;
|
||||
int mostRecentLastUse = -1;
|
||||
Identifier<RGResource> resourceBefore = default;
|
||||
var mostRecentLastUse = -1;
|
||||
|
||||
foreach (int otherLogicalIndex in physical.AliasedLogicalResources)
|
||||
foreach (var otherLogicalIndex in physical.AliasedLogicalResources)
|
||||
{
|
||||
if (otherLogicalIndex != handle.Index)
|
||||
if (otherLogicalIndex != id.Value)
|
||||
{
|
||||
var otherResource = _resources.GetTextureResourceByIndex(otherLogicalIndex);
|
||||
// Check if this resource finished before our resource starts
|
||||
if (otherResource.LastUsePass < pass.Index &&
|
||||
if (otherResource.LastUsePass < pass.index &&
|
||||
otherResource.LastUsePass > mostRecentLastUse)
|
||||
{
|
||||
mostRecentLastUse = otherResource.LastUsePass;
|
||||
resourceBefore = new RenderGraphTextureHandle(
|
||||
otherLogicalIndex,
|
||||
otherResource.Version,
|
||||
otherResource.Descriptor.Name);
|
||||
resourceBefore = otherLogicalIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,7 +471,7 @@ public sealed class RenderGraph
|
||||
{
|
||||
var barrier = ResourceBarrier.CreateAliasingBarrier(
|
||||
resourceBefore,
|
||||
handle,
|
||||
id,
|
||||
passIdx
|
||||
);
|
||||
_barriers.Add(barrier);
|
||||
@@ -516,42 +492,65 @@ public sealed class RenderGraph
|
||||
private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx)
|
||||
{
|
||||
// Process reads (transition to shader resource)
|
||||
for (var i = 0; i < pass.TextureReads.Count; i++)
|
||||
for (var i = 0; i < pass.resourceReads.Count; i++)
|
||||
{
|
||||
var handle = pass.TextureReads[i];
|
||||
var handle = pass.resourceReads[i];
|
||||
InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx);
|
||||
}
|
||||
|
||||
// Process writes (transition to render target or UAV)
|
||||
for (var i = 0; i < pass.TextureWrites.Count; i++)
|
||||
switch (pass.type)
|
||||
{
|
||||
var handle = pass.TextureWrites[i];
|
||||
var targetState = ResourceState.RenderTarget; // Could be UAV for compute
|
||||
InsertTransitionIfNeeded(handle, targetState, passIdx);
|
||||
case RenderPassType.Raster:
|
||||
for (var i = 0; i <= pass.maxColorIndex; i++)
|
||||
{
|
||||
var access = pass.colorAccess[i];
|
||||
InsertTransitionIfNeeded(access.id.AsResource(), ResourceState.RenderTarget, passIdx);
|
||||
}
|
||||
|
||||
if (pass.depthAccess.id.IsValid)
|
||||
{
|
||||
var depthAccess = pass.depthAccess;
|
||||
InsertTransitionIfNeeded(depthAccess.id.AsResource(), ResourceState.DepthWrite, passIdx);
|
||||
}
|
||||
|
||||
for (var i = 0; i < pass.randomAccess.Count; i++)
|
||||
{
|
||||
InsertTransitionIfNeeded(pass.randomAccess[i], ResourceState.UnorderedAccess, passIdx);
|
||||
}
|
||||
|
||||
break;
|
||||
case RenderPassType.Compute:
|
||||
for (var i = 0; i < pass.resourceWrites.Count; i++)
|
||||
{
|
||||
var id = pass.resourceWrites[i];
|
||||
InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a transition barrier if the resource state changes.
|
||||
/// </summary>
|
||||
private void InsertTransitionIfNeeded(RenderGraphTextureHandle handle, ResourceState newState, int passIdx)
|
||||
private void InsertTransitionIfNeeded(Identifier<RGResource> resource, ResourceState newState, int passIdx)
|
||||
{
|
||||
if (!_resourceStates.TryGetValue(handle.Index, out var currentState))
|
||||
if (!_resourceStates.TryGetValue(resource.Value, out var currentState))
|
||||
{
|
||||
// First time seeing this resource, assume undefined
|
||||
currentState = ResourceState.Undefined;
|
||||
currentState = ResourceState.Common;
|
||||
}
|
||||
|
||||
if (currentState != newState)
|
||||
{
|
||||
var barrier = ResourceBarrier.CreateTransitionBarrier(
|
||||
handle,
|
||||
resource,
|
||||
currentState,
|
||||
newState,
|
||||
passIdx
|
||||
);
|
||||
_barriers.Add(barrier);
|
||||
_resourceStates[handle.Index] = newState;
|
||||
_resourceStates[resource.Value] = newState;
|
||||
|
||||
#if DEBUG
|
||||
Console.WriteLine($" {barrier}");
|
||||
@@ -570,8 +569,8 @@ public sealed class RenderGraph
|
||||
}
|
||||
|
||||
// Execute each non-culled pass
|
||||
int barrierIndex = 0;
|
||||
for (int i = 0; i < _compiledPasses.Count; i++)
|
||||
var barrierIndex = 0;
|
||||
for (var i = 0; i < _compiledPasses.Count; i++)
|
||||
{
|
||||
var pass = _compiledPasses[i];
|
||||
|
||||
@@ -584,10 +583,19 @@ public sealed class RenderGraph
|
||||
#if DEBUG
|
||||
if (!hasBarriers)
|
||||
{
|
||||
Console.WriteLine($"\n=== Barriers before Pass {i}: {pass.Name} ===");
|
||||
Console.WriteLine($"\n=== Barriers before Pass {i}: {pass.name} ===");
|
||||
hasBarriers = true;
|
||||
}
|
||||
Console.WriteLine($" {_barriers[barrierIndex]}");
|
||||
|
||||
var barrier = _barriers[barrierIndex];
|
||||
if (barrier.Type == BarrierType.Transition)
|
||||
{
|
||||
_commandBuffer.ResourceBarrier(
|
||||
barrier.Resource,
|
||||
barrier.StateBefore,
|
||||
barrier.StateAfter
|
||||
);
|
||||
}
|
||||
#endif
|
||||
// In a real implementation, you would execute the barrier here:
|
||||
// ExecuteBarrier(_barriers[barrierIndex]);
|
||||
|
||||
@@ -104,7 +104,7 @@ internal sealed class ResourceAliasingManager
|
||||
#endif
|
||||
|
||||
// Build list of all logical resources with their lifetimes
|
||||
var logicalResources = ListPool<(int index, TextureResource resource)>.Rent();
|
||||
var logicalResources = ListPool<(int index, RenderGraphResource resource)>.Rent();
|
||||
|
||||
for (int i = 0; i < registry.TextureResourceCount; i++)
|
||||
{
|
||||
@@ -194,7 +194,7 @@ internal sealed class ResourceAliasingManager
|
||||
Console.WriteLine("================================\n");
|
||||
#endif
|
||||
|
||||
ListPool<(int index, TextureResource resource)>.Return(logicalResources);
|
||||
ListPool<(int index, RenderGraphResource resource)>.Return(logicalResources);
|
||||
}
|
||||
|
||||
public int GetPhysicalResourceIndex(int logicalIndex)
|
||||
@@ -209,7 +209,7 @@ internal sealed class ResourceAliasingManager
|
||||
: null;
|
||||
}
|
||||
|
||||
private bool HasLifetimeOverlap(PhysicalResource physical, TextureResource logical)
|
||||
private bool HasLifetimeOverlap(PhysicalResource physical, RenderGraphResource logical)
|
||||
{
|
||||
// Check if the lifetimes overlap
|
||||
// No overlap if: logical.First > physical.Last OR logical.Last < physical.First
|
||||
@@ -227,7 +227,7 @@ internal sealed class ResourceAliasingManager
|
||||
}
|
||||
else
|
||||
{
|
||||
resource = _pool.Get<PhysicalResource>();
|
||||
resource = _pool.Rent<PhysicalResource>();
|
||||
resource.Reset();
|
||||
_physicalResources.Add(resource);
|
||||
}
|
||||
@@ -254,7 +254,7 @@ internal sealed class ResourceAliasingManager
|
||||
{
|
||||
for (int i = 0; i < _physicalResources.Count; i++)
|
||||
{
|
||||
_pool.Release(_physicalResources[i]);
|
||||
_pool.Return(_physicalResources[i]);
|
||||
}
|
||||
_physicalResources.Clear();
|
||||
_physicalResourceCount = 0;
|
||||
@@ -284,7 +284,7 @@ internal sealed class ResourceAliasingManager
|
||||
}
|
||||
else
|
||||
{
|
||||
physical = _pool.Get<PhysicalResource>();
|
||||
physical = _pool.Rent<PhysicalResource>();
|
||||
physical.Reset();
|
||||
_physicalResources.Add(physical);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ghost.Core;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
@@ -9,7 +10,7 @@ namespace Ghost.RenderGraph.Concept;
|
||||
[Flags]
|
||||
public enum ResourceState
|
||||
{
|
||||
Undefined = 0,
|
||||
Common = 0,
|
||||
RenderTarget = 1 << 0,
|
||||
DepthWrite = 1 << 1,
|
||||
DepthRead = 1 << 2,
|
||||
@@ -27,7 +28,6 @@ public enum BarrierType
|
||||
{
|
||||
Transition, // State transition (e.g., RenderTarget -> ShaderResource)
|
||||
Aliasing, // Aliasing barrier (new resource reusing memory)
|
||||
UAV, // UAV barrier (synchronize UAV access)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -41,71 +41,69 @@ internal struct ResourceBarrier
|
||||
{
|
||||
internal struct barrier_union_transition
|
||||
{
|
||||
public RenderGraphTextureHandle Resource;
|
||||
public ResourceState StateBefore;
|
||||
public ResourceState StateAfter;
|
||||
public Identifier<RGResource> resource;
|
||||
public ResourceState stateBefore;
|
||||
public ResourceState stateAfter;
|
||||
}
|
||||
|
||||
internal struct barrier_union_aliasing
|
||||
{
|
||||
public RenderGraphTextureHandle ResourceBefore;
|
||||
public RenderGraphTextureHandle ResourceAfter;
|
||||
public Identifier<RGResource> resourceBefore;
|
||||
public Identifier<RGResource> resourceAfter;
|
||||
}
|
||||
|
||||
// TODO: union can not have non-blittable types
|
||||
|
||||
[FieldOffset(0)]
|
||||
public barrier_union_transition Transition;
|
||||
public barrier_union_transition transition;
|
||||
[FieldOffset(0)]
|
||||
public barrier_union_aliasing Aliasing;
|
||||
public barrier_union_aliasing aliasing;
|
||||
}
|
||||
|
||||
public BarrierType Type;
|
||||
private barrier_union _union;
|
||||
|
||||
public BarrierType Type
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public int PassIndex
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
// For Transition and UAV barriers
|
||||
public RenderGraphTextureHandle Resource;
|
||||
public ResourceState StateBefore;
|
||||
public ResourceState StateAfter;
|
||||
public readonly Identifier<RGResource> Resource => _union.transition.resource;
|
||||
public readonly ResourceState StateBefore => _union.transition.stateBefore;
|
||||
public readonly ResourceState StateAfter => _union.transition.stateAfter;
|
||||
|
||||
// For Aliasing barriers (D3D12_RESOURCE_BARRIER::Aliasing)
|
||||
public RenderGraphTextureHandle ResourceBefore; // pResourceBefore
|
||||
public RenderGraphTextureHandle ResourceAfter; // pResourceAfter
|
||||
|
||||
public int PassIndex;
|
||||
|
||||
// Constructor for Transition and UAV barriers
|
||||
public ResourceBarrier(BarrierType type, RenderGraphTextureHandle resource,
|
||||
ResourceState before, ResourceState after, int passIndex)
|
||||
{
|
||||
Type = type;
|
||||
Resource = resource;
|
||||
StateBefore = before;
|
||||
StateAfter = after;
|
||||
ResourceBefore = default;
|
||||
ResourceAfter = default;
|
||||
PassIndex = passIndex;
|
||||
}
|
||||
public readonly Identifier<RGResource> ResourceBefore => _union.aliasing.resourceBefore;
|
||||
public readonly Identifier<RGResource> ResourceAfter => _union.aliasing.resourceAfter;
|
||||
|
||||
// Constructor for Aliasing barriers
|
||||
public static ResourceBarrier CreateAliasingBarrier(
|
||||
RenderGraphTextureHandle resourceBefore,
|
||||
RenderGraphTextureHandle resourceAfter,
|
||||
Identifier<RGResource> resourceBefore,
|
||||
Identifier<RGResource> resourceAfter,
|
||||
int passIndex)
|
||||
{
|
||||
return new ResourceBarrier
|
||||
{
|
||||
Type = BarrierType.Aliasing,
|
||||
ResourceBefore = resourceBefore,
|
||||
ResourceAfter = resourceAfter,
|
||||
PassIndex = passIndex,
|
||||
Resource = default,
|
||||
StateBefore = ResourceState.Undefined,
|
||||
StateAfter = ResourceState.Undefined
|
||||
_union = new barrier_union
|
||||
{
|
||||
aliasing = new barrier_union.barrier_union_aliasing
|
||||
{
|
||||
resourceBefore = resourceBefore,
|
||||
resourceAfter = resourceAfter
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ResourceBarrier CreateTransitionBarrier(
|
||||
RenderGraphTextureHandle resource,
|
||||
Identifier<RGResource> resource,
|
||||
ResourceState before,
|
||||
ResourceState after,
|
||||
int passIndex)
|
||||
@@ -113,27 +111,18 @@ internal struct ResourceBarrier
|
||||
return new ResourceBarrier
|
||||
{
|
||||
Type = BarrierType.Transition,
|
||||
Resource = resource,
|
||||
StateBefore = before,
|
||||
StateAfter = after,
|
||||
PassIndex = passIndex,
|
||||
ResourceBefore = default,
|
||||
ResourceAfter = default
|
||||
_union = new barrier_union
|
||||
{
|
||||
transition = new barrier_union.barrier_union_transition
|
||||
{
|
||||
resource = resource,
|
||||
stateBefore = before,
|
||||
stateAfter = after
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override readonly string ToString()
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
BarrierType.Transition => $"[Pass {PassIndex}] TRANSITION: {Resource.Name} ({StateBefore} -> {StateAfter})",
|
||||
BarrierType.Aliasing => $"[Pass {PassIndex}] ALIASING: {ResourceBefore.Name} -> {ResourceAfter.Name} (reusing physical memory)",
|
||||
BarrierType.UAV => $"[Pass {PassIndex}] UAV: {Resource.Name}",
|
||||
_ => $"[Pass {PassIndex}] UNKNOWN BARRIER"
|
||||
};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -142,13 +131,13 @@ internal struct ResourceBarrier
|
||||
internal sealed class ResourceStateTracker
|
||||
{
|
||||
public int ResourceIndex;
|
||||
public ResourceState CurrentState = ResourceState.Undefined;
|
||||
public ResourceState CurrentState = ResourceState.Common;
|
||||
public int LastAccessPass = -1;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ResourceIndex = -1;
|
||||
CurrentState = ResourceState.Undefined;
|
||||
CurrentState = ResourceState.Common;
|
||||
LastAccessPass = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ namespace Ghost.RenderGraph.Concept;
|
||||
/// </summary>
|
||||
public sealed class RenderGraphBlackboard
|
||||
{
|
||||
private readonly Dictionary<Type, object> _data = new(16);
|
||||
private readonly Dictionary<Type, IPassData> _data = new(16);
|
||||
|
||||
/// <summary>
|
||||
/// Adds or updates pass data in the blackboard.
|
||||
/// </summary>
|
||||
public void Add<T>(T data) where T : class, IPassData
|
||||
public void Add<T>(T data)
|
||||
where T : class, IPassData
|
||||
{
|
||||
var type = typeof(T);
|
||||
_data[type] = data;
|
||||
@@ -21,20 +22,23 @@ public sealed class RenderGraphBlackboard
|
||||
/// <summary>
|
||||
/// Retrieves pass data from the blackboard.
|
||||
/// </summary>
|
||||
public T Get<T>() where T : class, IPassData
|
||||
public T Get<T>()
|
||||
where T : class, IPassData
|
||||
{
|
||||
var type = typeof(T);
|
||||
if (_data.TryGetValue(type, out var obj))
|
||||
{
|
||||
return (T)obj;
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"Pass data of type {type.Name} not found in blackboard");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get pass data from the blackboard.
|
||||
/// </summary>
|
||||
public bool TryGet<T>(out T? data) where T : class, IPassData
|
||||
public bool TryGet<T>(out T? data)
|
||||
where T : class, IPassData
|
||||
{
|
||||
var type = typeof(T);
|
||||
if (_data.TryGetValue(type, out var obj))
|
||||
@@ -42,6 +46,7 @@ public sealed class RenderGraphBlackboard
|
||||
data = (T)obj;
|
||||
return true;
|
||||
}
|
||||
|
||||
data = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
240
Ghost.RenderGraph.Concept/RenderGraphBuilder.cs
Normal file
240
Ghost.RenderGraph.Concept/RenderGraphBuilder.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using Ghost.Core;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
[Flags]
|
||||
public enum AccessFlags
|
||||
{
|
||||
None = 0,
|
||||
Read = 1 << 0,
|
||||
Write = 1 << 1,
|
||||
ReadWrite = Read | Write,
|
||||
}
|
||||
|
||||
public interface IRenderGraphBuilder : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables or disables pass culling for the current context.
|
||||
/// </summary>
|
||||
/// <param name="value">A value indicating whether pass culling is allowed.</param>
|
||||
void AllowPassCulling(bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new texture resource based on the specified descriptor.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">A structure that defines the properties and configuration of the texture to create.</param>
|
||||
/// <returns>An identifier for the newly created texture resource.</returns>
|
||||
Identifier<RGTexture> CreateTexture(in TextureDescriptor descriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Registers the specified texture for use in the current render graph pass with the given access mode.
|
||||
/// </summary>
|
||||
/// <param name="texture">The identifier of the texture to be used in the render graph pass.</param>
|
||||
/// <param name="accessMode">The access mode specifying how the texture will be read or written during the pass.</param>
|
||||
/// <returns>An identifier for the texture.</returns>
|
||||
Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags accessMode);
|
||||
}
|
||||
|
||||
public interface IRasterRenderGraphBuilder : IRenderGraphBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Binds a texture for random access operations within the current rendering pass.
|
||||
/// </summary>
|
||||
/// <param name="texture">The identifier of the texture to be used for random access.</param>
|
||||
/// <returns>An identifier for the texture.</returns>
|
||||
Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture);
|
||||
/// <summary>
|
||||
/// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer resource.</param>
|
||||
/// <returns>An identifier for the buffer.</returns>
|
||||
Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color attachment at the specified index to the given texture.
|
||||
/// </summary>
|
||||
/// <param name="texture">The identifier of the texture to use as the color attachment.</param>
|
||||
/// <param name="index">The zero-based index of the color attachment to set.</param>
|
||||
void SetColorAttachment(Identifier<RGTexture> texture, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the depth attachment for the current render pass using the specified texture.
|
||||
/// </summary>
|
||||
/// <param name="texture">The identifier of the texture to use as the depth attachment. Cannot be null.</param>
|
||||
void SetDepthAttachment(Identifier<RGTexture> texture);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the function used to render a pass with the specified pass data and render context.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPassData">The type of data associated with the render pass.</typeparam>
|
||||
/// <param name="renderFunc">The delegate that defines the rendering logic for the pass.</param>
|
||||
void SetRenderFunc<TPassData>(Action<TPassData, RasterRenderContext> renderFunc)
|
||||
where TPassData : class, new();
|
||||
}
|
||||
|
||||
public interface IComputeRenderGraphBuilder : IRenderGraphBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables or disables asynchronous compute operations.
|
||||
/// </summary>
|
||||
/// <param name="value">true to enable asynchronous compute; otherwise, false.</param>
|
||||
void EnableAsyncCompute(bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the render function to be invoked during the compute rendering process.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPassData">The type of the data object passed to the render function.</typeparam>
|
||||
/// <param name="renderFunc">The delegate that defines the rendering logic to execute.</param>
|
||||
void SetRenderFunc<TPassData>(Action<TPassData, ComputeRenderContext> renderFunc)
|
||||
where TPassData : class, new();
|
||||
}
|
||||
|
||||
internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGraphBuilder
|
||||
{
|
||||
private RenderGraph _graph = null!;
|
||||
private RenderGraphPassBase _pass = null!;
|
||||
private RenderGraphResourceRegistry _resources = null!;
|
||||
private bool _disposed;
|
||||
|
||||
internal void Init(RenderGraph graph, RenderGraphPassBase pass, RenderGraphResourceRegistry resources)
|
||||
{
|
||||
_graph = graph;
|
||||
_pass = pass;
|
||||
_resources = resources;
|
||||
_disposed = false;
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
}
|
||||
|
||||
private Identifier<RGResource> UseResource(Identifier<RGResource> resource, AccessFlags accessFlags)
|
||||
{
|
||||
if (accessFlags.HasFlag(AccessFlags.Read))
|
||||
{
|
||||
_pass.resourceReads.Add(resource);
|
||||
_resources.AddConsumer(resource, _pass.index);
|
||||
}
|
||||
|
||||
if (accessFlags.HasFlag(AccessFlags.Write))
|
||||
{
|
||||
_pass.resourceWrites.Add(resource);
|
||||
_resources.SetProducer(resource, _pass.index);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void AllowPassCulling(bool value)
|
||||
{
|
||||
_pass.allowCulling = value;
|
||||
}
|
||||
|
||||
public void EnableAsyncCompute(bool value)
|
||||
{
|
||||
_pass.asyncCompute = value;
|
||||
}
|
||||
|
||||
public Identifier<RGTexture> CreateTexture(in TextureDescriptor descriptor)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var handle = _resources.CreateTexture(descriptor);
|
||||
_pass.resourceCreates.Add(handle.AsResource());
|
||||
_resources.SetProducer(handle.AsResource(), _pass.index);
|
||||
return handle;
|
||||
}
|
||||
|
||||
public Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags flags)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return UseResource(texture.AsResource(), flags).AsTexture();
|
||||
}
|
||||
|
||||
public Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var resource = texture.AsResource();
|
||||
UseResource(resource, AccessFlags.ReadWrite);
|
||||
_pass.randomAccess.Add(resource);
|
||||
return texture;
|
||||
}
|
||||
|
||||
public Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var resource = buffer.AsResource();
|
||||
UseResource(resource, AccessFlags.ReadWrite);
|
||||
_pass.randomAccess.Add(resource);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void SetColorAttachment(Identifier<RGTexture> texture, int index)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
Debug.Assert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range.");
|
||||
|
||||
var id = UseTexture(texture, AccessFlags.Write);
|
||||
if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid)
|
||||
{
|
||||
_pass.maxColorIndex = Math.Max(_pass.maxColorIndex, index);
|
||||
_pass.colorAccess[index] = new TextureAccess(id, AccessFlags.Write);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Color attachment at index {index} is already set to a different texture.");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDepthAttachment(Identifier<RGTexture> texture)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var id = UseTexture(texture, AccessFlags.Write);
|
||||
if (_pass.depthAccess.id == id || _pass.depthAccess.id.IsInvalid)
|
||||
{
|
||||
_pass.depthAccess = new TextureAccess(id, AccessFlags.Write);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Depth attachment is already set to a different texture.");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRenderFunc<TPassData>(Action<TPassData, RasterRenderContext> renderFunc)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
((RasterRenderGraphPass<TPassData>)_pass).renderFunc = renderFunc;
|
||||
}
|
||||
|
||||
public void SetRenderFunc<TPassData>(Action<TPassData, ComputeRenderContext> renderFunc)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
((ComputeRenderGraphPass<TPassData>)_pass).renderFunc = renderFunc;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_pass.HasRenderFunc())
|
||||
{
|
||||
throw new InvalidOperationException("RenderGraphBuilder must be disposed after setting up the render function.");
|
||||
}
|
||||
|
||||
_graph = null!;
|
||||
_pass = null!;
|
||||
_resources = null!;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods to provide a cleaner API for setting render functions.
|
||||
/// These avoid the need for explicit type annotations in lambdas.
|
||||
/// </summary>
|
||||
public static class RenderGraphBuilderExtensions
|
||||
{
|
||||
// Internal helper to cast and set
|
||||
private static void SetRasterFunc<TPassData>(this RenderGraphBuilder builder, object pass, Action<TPassData, RasterRenderContext> func)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
if (pass is RenderGraphPass<TPassData> typedPass)
|
||||
{
|
||||
builder.SetRenderFunc(func);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetCompFunc<TPassData>(this RenderGraphBuilder builder, object pass, Action<TPassData, ComputeRenderContext> func, bool async)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
if (pass is RenderGraphPass<TPassData> typedPass)
|
||||
{
|
||||
builder.SetComputeFunc(func, async);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,38 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
/// <summary>
|
||||
/// Mock command buffer for recording GPU commands.
|
||||
/// In a real implementation, this would wrap D3D12 command lists.
|
||||
/// </summary>
|
||||
public sealed class MockCommandBuffer
|
||||
internal sealed class MockCommandBuffer
|
||||
{
|
||||
public void SetRenderTarget(string name)
|
||||
public void SetRenderTarget(Identifier<RGTexture> texture)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(nameof(SetRenderTarget) + ": " + name);
|
||||
Console.WriteLine(nameof(SetRenderTarget) + ": " + texture);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void SetDepthStencil(string name)
|
||||
public void SetDepthStencil(Identifier<RGTexture> texture)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(nameof(SetDepthStencil) + ": " + name);
|
||||
Console.WriteLine(nameof(SetDepthStencil) + ": " + texture);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ClearRenderTarget(string name, float r, float g, float b, float a)
|
||||
public void ClearRenderTarget(Identifier<RGTexture> texture, float r, float g, float b, float a)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(nameof(ClearRenderTarget) + ": " + name);
|
||||
Console.WriteLine(nameof(ClearRenderTarget) + ": " + texture);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ClearDepth(string name, float depth)
|
||||
public void ClearDepth(Identifier<RGTexture> texture, float depth)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(nameof(ClearDepth) + ": " + name);
|
||||
Console.WriteLine(nameof(ClearDepth) + ": " + texture);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -41,17 +43,17 @@ public sealed class MockCommandBuffer
|
||||
#endif
|
||||
}
|
||||
|
||||
public void BindShaderResource(string name, int slot)
|
||||
public void BindShaderResource(Identifier<RGResource> resource, int slot)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(nameof(BindShaderResource) + ": " + name + ", slot " + slot);
|
||||
Console.WriteLine(nameof(BindShaderResource) + ": " + resource + ", slot " + slot);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void BindUnorderedAccess(string name, int slot)
|
||||
public void BindUnorderedAccess(Identifier<RGResource> resource, int slot)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(nameof(BindUnorderedAccess) + ": " + name + ", slot " + slot);
|
||||
Console.WriteLine(nameof(BindUnorderedAccess) + ": " + resource + ", slot " + slot);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -62,14 +64,14 @@ public sealed class MockCommandBuffer
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ResourceBarrier(string resourceName, string stateBefore, string stateAfter)
|
||||
public void ResourceBarrier(Identifier<RGResource> resource, ResourceState stateBefore, ResourceState stateAfter)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(nameof(ResourceBarrier) + ": " + resourceName + " from " + stateBefore + " to " + stateAfter);
|
||||
Console.WriteLine(nameof(ResourceBarrier) + ": " + resource + " from " + stateBefore + " to " + stateAfter);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void AliasBarrier(string resourceBefore, string resourceAfter)
|
||||
public void AliasBarrier(Identifier<RGResource> resourceBefore, Identifier<RGResource> resourceAfter)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.WriteLine(nameof(AliasBarrier) + ": " + resourceBefore + " to " + resourceAfter);
|
||||
@@ -85,18 +87,18 @@ public readonly struct RasterRenderContext
|
||||
{
|
||||
private readonly MockCommandBuffer _cmd;
|
||||
|
||||
public RasterRenderContext(MockCommandBuffer cmd)
|
||||
internal RasterRenderContext(MockCommandBuffer cmd)
|
||||
{
|
||||
_cmd = cmd;
|
||||
}
|
||||
|
||||
// Expose command buffer methods directly
|
||||
public void SetRenderTarget(string name) => _cmd.SetRenderTarget(name);
|
||||
public void SetDepthStencil(string name) => _cmd.SetDepthStencil(name);
|
||||
public void ClearRenderTarget(string name, float r, float g, float b, float a) => _cmd.ClearRenderTarget(name, r, g, b, a);
|
||||
public void ClearDepth(string name, float depth) => _cmd.ClearDepth(name, depth);
|
||||
public void SetRenderTarget(Identifier<RGTexture> texture) => _cmd.SetRenderTarget(texture);
|
||||
public void SetDepthStencil(Identifier<RGTexture> texture) => _cmd.SetDepthStencil(texture);
|
||||
public void ClearRenderTarget(Identifier<RGTexture> texture, float r, float g, float b, float a) => _cmd.ClearRenderTarget(texture, r, g, b, a);
|
||||
public void ClearDepth(Identifier<RGTexture> texture, float depth) => _cmd.ClearDepth(texture, depth);
|
||||
public void Draw(int vertexCount) => _cmd.Draw(vertexCount);
|
||||
public void BindShaderResource(string name, int slot) => _cmd.BindShaderResource(name, slot);
|
||||
public void BindShaderResource(Identifier<RGResource> resource, int slot) => _cmd.BindShaderResource(resource, slot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,14 +109,14 @@ public readonly struct ComputeRenderContext
|
||||
{
|
||||
private readonly MockCommandBuffer _cmd;
|
||||
|
||||
public ComputeRenderContext(MockCommandBuffer cmd)
|
||||
internal ComputeRenderContext(MockCommandBuffer cmd)
|
||||
{
|
||||
_cmd = cmd;
|
||||
}
|
||||
|
||||
// Expose command buffer methods directly
|
||||
public void BindShaderResource(string name, int slot) => _cmd.BindShaderResource(name, slot);
|
||||
public void BindUnorderedAccess(string name, int slot) => _cmd.BindUnorderedAccess(name, slot);
|
||||
public void BindShaderResource(Identifier<RGResource> resource, int slot) => _cmd.BindShaderResource(resource, slot);
|
||||
public void BindUnorderedAccess(Identifier<RGResource> resource, int slot) => _cmd.BindUnorderedAccess(resource, slot);
|
||||
public void Dispatch(int x, int y, int z) => _cmd.Dispatch(x, y, z);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
using System.IO.Hashing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
/// <summary>
|
||||
/// Helper extensions for XxHash3 to hash common types without string allocation.
|
||||
/// Uses SIMD-optimized hashing via System.IO.Hashing.XxHash3.
|
||||
/// </summary>
|
||||
internal static class RenderGraphHashExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Appends an int to the hash.
|
||||
/// </summary>
|
||||
public static void AppendInt(this XxHash64 hash, int value)
|
||||
{
|
||||
ReadOnlySpan<int> span = stackalloc int[1] { value };
|
||||
hash.Append(MemoryMarshal.AsBytes(span));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a bool to the hash.
|
||||
/// </summary>
|
||||
public static void AppendBool(this XxHash64 hash, bool value)
|
||||
{
|
||||
ReadOnlySpan<bool> span = stackalloc bool[1] { value };
|
||||
hash.Append(MemoryMarshal.AsBytes(span));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends an enum to the hash.
|
||||
/// </summary>
|
||||
public static void AppendEnum<TEnum>(this XxHash64 hash, TEnum value) where TEnum : unmanaged, Enum
|
||||
{
|
||||
ReadOnlySpan<TEnum> span = stackalloc TEnum[1] { value };
|
||||
hash.Append(MemoryMarshal.AsBytes(span));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a struct to the hash (must be unmanaged).
|
||||
/// </summary>
|
||||
public static void AppendStruct<T>(this XxHash64 hash, in T value) where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<T> span = stackalloc T[1] { value };
|
||||
hash.Append(MemoryMarshal.AsBytes(span));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a list of resource handle indices to the hash.
|
||||
/// </summary>
|
||||
public static void AppendHandleList(this XxHash64 hash, List<RenderGraphTextureHandle> handles)
|
||||
{
|
||||
// Only hash the indices, not the versions (versions change but structure doesn't)
|
||||
int count = handles.Count;
|
||||
hash.AppendInt(count);
|
||||
|
||||
//for (int i = 0; i < count; i++)
|
||||
//{
|
||||
// hash.AppendInt(handles[i].Index);
|
||||
//}
|
||||
Span<int> indices = stackalloc int[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
indices[i] = handles[i].Index;
|
||||
}
|
||||
hash.Append(MemoryMarshal.AsBytes(indices));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using System.IO;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
/// <summary>
|
||||
@@ -15,201 +18,112 @@ public enum RenderPassType : byte
|
||||
/// </summary>
|
||||
internal abstract class RenderGraphPassBase
|
||||
{
|
||||
public string Name = string.Empty;
|
||||
public int Index;
|
||||
public RenderPassType Type;
|
||||
public bool AllowCulling = true;
|
||||
public bool AsyncCompute;
|
||||
public string name = string.Empty;
|
||||
public int index;
|
||||
public RenderPassType type;
|
||||
public bool allowCulling = true;
|
||||
public bool asyncCompute;
|
||||
|
||||
public TextureAccess depthAccess;
|
||||
public TextureAccess[] colorAccess = new TextureAccess[8];
|
||||
public int maxColorIndex = -1;
|
||||
|
||||
public List<Identifier<RGResource>> randomAccess = new(8);
|
||||
|
||||
// Resource dependencies
|
||||
public readonly List<RenderGraphTextureHandle> TextureReads = new(8);
|
||||
public readonly List<RenderGraphTextureHandle> TextureWrites = new(4);
|
||||
public readonly List<RenderGraphTextureHandle> TextureCreates = new(4);
|
||||
public readonly List<Identifier<RGResource>> resourceReads = new(8);
|
||||
public readonly List<Identifier<RGResource>> resourceWrites = new(4);
|
||||
public readonly List<Identifier<RGResource>> resourceCreates = new(4);
|
||||
|
||||
// Execution state
|
||||
public bool Culled;
|
||||
public bool HasSideEffects;
|
||||
public bool culled;
|
||||
public bool hasSideEffects;
|
||||
|
||||
public abstract void Execute(RenderContext context);
|
||||
public abstract void Clear();
|
||||
public abstract bool HasRenderFunc();
|
||||
|
||||
public virtual void Reset()
|
||||
public virtual void Reset(RenderGraphObjectPool pool)
|
||||
{
|
||||
Name = string.Empty;
|
||||
Index = -1;
|
||||
Type = RenderPassType.Raster;
|
||||
AllowCulling = true;
|
||||
AsyncCompute = false;
|
||||
TextureReads.Clear();
|
||||
TextureWrites.Clear();
|
||||
TextureCreates.Clear();
|
||||
Culled = false;
|
||||
HasSideEffects = false;
|
||||
name = string.Empty;
|
||||
index = -1;
|
||||
type = RenderPassType.Raster;
|
||||
allowCulling = true;
|
||||
asyncCompute = false;
|
||||
|
||||
depthAccess = default;
|
||||
colorAccess.AsSpan().Clear();
|
||||
maxColorIndex = -1;
|
||||
|
||||
randomAccess.Clear();
|
||||
|
||||
resourceReads.Clear();
|
||||
resourceWrites.Clear();
|
||||
resourceCreates.Clear();
|
||||
culled = false;
|
||||
hasSideEffects = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Typed render pass with user data.
|
||||
/// </summary>
|
||||
internal sealed class RenderGraphPass<TPassData> : RenderGraphPassBase
|
||||
internal abstract class RenderGraphPassT<TPassData, TRenderContext> : RenderGraphPassBase
|
||||
where TPassData : class, new()
|
||||
{
|
||||
public TPassData? PassData;
|
||||
public Action<TPassData, RasterRenderContext>? RasterRenderFunc;
|
||||
public Action<TPassData, ComputeRenderContext>? ComputeRenderFunc;
|
||||
public TPassData passData = null!;
|
||||
public Action<TPassData, TRenderContext>? renderFunc;
|
||||
|
||||
public override void Execute(RenderContext context)
|
||||
public void Init(int index, TPassData passData, string name, RenderPassType type)
|
||||
{
|
||||
if (PassData == null)
|
||||
return;
|
||||
this.index = index;
|
||||
this.passData = passData;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
if (Type == RenderPassType.Raster && RasterRenderFunc != null)
|
||||
public sealed override bool HasRenderFunc()
|
||||
{
|
||||
RasterRenderFunc(PassData, context.RasterContext);
|
||||
}
|
||||
else if (Type == RenderPassType.Compute && ComputeRenderFunc != null)
|
||||
{
|
||||
ComputeRenderFunc(PassData, context.ComputeContext);
|
||||
}
|
||||
return renderFunc != null;
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
PassData = null;
|
||||
RasterRenderFunc = null;
|
||||
ComputeRenderFunc = null;
|
||||
passData = null!;
|
||||
renderFunc = null;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
public override void Reset(RenderGraphObjectPool pool)
|
||||
{
|
||||
base.Reset();
|
||||
base.Reset(pool);
|
||||
pool.Return(passData);
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builder for constructing render passes.
|
||||
/// Implements IDisposable for using() pattern.
|
||||
/// </summary>
|
||||
public sealed class RenderGraphBuilder : IDisposable
|
||||
internal sealed class RasterRenderGraphPass<TPassData> : RenderGraphPassT<TPassData, RasterRenderContext>
|
||||
where TPassData : class, new()
|
||||
{
|
||||
private RenderGraphPassBase? _pass;
|
||||
private RenderGraphResourceRegistry? _resources;
|
||||
private bool _disposed;
|
||||
|
||||
internal void Initialize(RenderGraphPassBase pass, RenderGraphResourceRegistry resources)
|
||||
public override void Execute(RenderContext context)
|
||||
{
|
||||
_pass = pass;
|
||||
_resources = resources;
|
||||
_disposed = false;
|
||||
renderFunc!(passData, context.RasterContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new transient texture that only lives for this pass.
|
||||
/// </summary>
|
||||
public RenderGraphTextureHandle CreateTexture(TextureDescriptor descriptor)
|
||||
public override void Reset(RenderGraphObjectPool pool)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var handle = _resources!.CreateTexture(descriptor);
|
||||
_pass!.TextureCreates.Add(handle);
|
||||
_resources.SetProducer(handle, _pass.Index);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a texture as being read by this pass.
|
||||
/// </summary>
|
||||
public RenderGraphTextureHandle ReadTexture(RenderGraphTextureHandle handle)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_pass!.TextureReads.Add(handle);
|
||||
_resources!.AddConsumer(handle, _pass.Index);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a texture as being written by this pass.
|
||||
/// </summary>
|
||||
public RenderGraphTextureHandle WriteTexture(RenderGraphTextureHandle handle)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_pass!.TextureWrites.Add(handle);
|
||||
_resources!.SetProducer(handle, _pass.Index);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a depth buffer for this pass.
|
||||
/// </summary>
|
||||
public RenderGraphTextureHandle UseDepthBuffer(RenderGraphTextureHandle handle, bool writeAccess)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (writeAccess)
|
||||
{
|
||||
_pass!.TextureWrites.Add(handle);
|
||||
_resources!.SetProducer(handle, _pass.Index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_pass!.TextureReads.Add(handle);
|
||||
_resources!.AddConsumer(handle, _pass.Index);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the render function for a raster pass.
|
||||
/// </summary>
|
||||
public void SetRenderFunc<TPassData>(Action<TPassData, RasterRenderContext> renderFunc)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (_pass is RenderGraphPass<TPassData> typedPass)
|
||||
{
|
||||
typedPass.RasterRenderFunc = renderFunc;
|
||||
typedPass.Type = RenderPassType.Raster;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the compute function for a compute pass.
|
||||
/// </summary>
|
||||
public void SetComputeFunc<TPassData>(Action<TPassData, ComputeRenderContext> computeFunc, bool asyncCompute = false)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (_pass is RenderGraphPass<TPassData> typedPass)
|
||||
{
|
||||
typedPass.ComputeRenderFunc = computeFunc;
|
||||
typedPass.Type = RenderPassType.Compute;
|
||||
typedPass.AsyncCompute = asyncCompute;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether this pass can be culled if its outputs are unused.
|
||||
/// </summary>
|
||||
public void SetAllowCulling(bool allow)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_pass!.AllowCulling = allow;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
_pass = null;
|
||||
_resources = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed || _pass == null)
|
||||
throw new ObjectDisposedException(nameof(RenderGraphBuilder));
|
||||
base.Reset(pool);
|
||||
pool.Return(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ComputeRenderGraphPass<TPassData> : RenderGraphPassT<TPassData, ComputeRenderContext>
|
||||
where TPassData : class, new()
|
||||
{
|
||||
public override void Execute(RenderContext context)
|
||||
{
|
||||
renderFunc!(passData, context.ComputeContext);
|
||||
}
|
||||
|
||||
public override void Reset(RenderGraphObjectPool pool)
|
||||
{
|
||||
base.Reset(pool);
|
||||
pool.Return(this);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.Buffer;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
/// <summary>
|
||||
@@ -6,44 +9,76 @@ namespace Ghost.RenderGraph.Concept;
|
||||
/// </summary>
|
||||
internal sealed class RenderGraphObjectPool
|
||||
{
|
||||
private readonly Dictionary<Type, Stack<object>> _pools = new();
|
||||
private static readonly List<SharedObjectPoolBase> s_allocatedPools = new();
|
||||
|
||||
public T Get<T>() where T : class, new()
|
||||
private class SharedObjectPoolBase
|
||||
{
|
||||
var type = typeof(T);
|
||||
if (_pools.TryGetValue(type, out var pool) && pool.Count > 0)
|
||||
{
|
||||
return (T)pool.Pop();
|
||||
}
|
||||
return new T();
|
||||
public SharedObjectPoolBase() { }
|
||||
public virtual void Clear() { }
|
||||
}
|
||||
|
||||
public void Release<T>(T obj) where T : class
|
||||
private class SharedObjectPool<T> : SharedObjectPoolBase where T : class, new()
|
||||
{
|
||||
if (obj == null) return;
|
||||
private static readonly ObjectPool<T> s_pool = AllocatePool();
|
||||
|
||||
var type = typeof(T);
|
||||
if (!_pools.TryGetValue(type, out var pool))
|
||||
private static ObjectPool<T> AllocatePool()
|
||||
{
|
||||
pool = new Stack<object>(16);
|
||||
_pools[type] = pool;
|
||||
var newPool = new ObjectPool<T>(() => new T());
|
||||
// Storing instance to clear the static pool of the same type if needed
|
||||
s_allocatedPools.Add(new SharedObjectPool<T>());
|
||||
return newPool;
|
||||
}
|
||||
pool.Push(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Clear the pool using SharedObjectPool instance.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override void Clear()
|
||||
{
|
||||
s_pool.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rent a new instance from the pool.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static T Rent() => s_pool.Rent();
|
||||
|
||||
/// <summary>
|
||||
/// Return an object to the pool.
|
||||
/// </summary>
|
||||
/// <param name="toRelease">instance to release.</param>
|
||||
public static void Return(T toRelease) => s_pool.Return(toRelease);
|
||||
}
|
||||
|
||||
public T Rent<T>()
|
||||
where T : class, new()
|
||||
{
|
||||
return SharedObjectPool<T>.Rent();
|
||||
}
|
||||
|
||||
public void Return<T>(T obj)
|
||||
where T : class, new()
|
||||
{
|
||||
SharedObjectPool<T>.Return(obj);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_pools.Clear();
|
||||
for (var i = 0; i < s_allocatedPools.Count; i++)
|
||||
{
|
||||
s_allocatedPools[i].Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a texture resource in the render graph.
|
||||
/// </summary>
|
||||
internal sealed class TextureResource
|
||||
internal sealed class RenderGraphResource
|
||||
{
|
||||
public RenderGraphResourceType type;
|
||||
public int Index;
|
||||
public int Version;
|
||||
public TextureDescriptor Descriptor;
|
||||
public bool IsImported;
|
||||
public int FirstUsePass = -1;
|
||||
@@ -55,7 +90,6 @@ internal sealed class TextureResource
|
||||
public void Reset()
|
||||
{
|
||||
Index = -1;
|
||||
Version = 0;
|
||||
Descriptor = default;
|
||||
IsImported = false;
|
||||
FirstUsePass = -1;
|
||||
@@ -72,7 +106,7 @@ internal sealed class TextureResource
|
||||
/// </summary>
|
||||
internal sealed class RenderGraphResourceRegistry
|
||||
{
|
||||
private readonly List<TextureResource> _textureResources = new(64);
|
||||
private readonly List<RenderGraphResource> _resources = new(64);
|
||||
private readonly RenderGraphObjectPool _pool = new();
|
||||
private int _textureResourceCount;
|
||||
|
||||
@@ -85,76 +119,78 @@ internal sealed class RenderGraphResourceRegistry
|
||||
_textureResourceCount = 0;
|
||||
}
|
||||
|
||||
public RenderGraphTextureHandle ImportTexture(TextureDescriptor descriptor)
|
||||
public Identifier<RGTexture> ImportTexture(TextureDescriptor descriptor)
|
||||
{
|
||||
var resource = GetOrCreateTextureResource();
|
||||
resource.Index = _textureResourceCount - 1;
|
||||
resource.Version = 0;
|
||||
resource.Descriptor = descriptor;
|
||||
resource.IsImported = true;
|
||||
|
||||
return new RenderGraphTextureHandle(resource.Index, resource.Version, descriptor.Name);
|
||||
return new Identifier<RGTexture>(resource.Index);
|
||||
}
|
||||
|
||||
public RenderGraphTextureHandle CreateTexture(TextureDescriptor descriptor)
|
||||
public Identifier<RGTexture> CreateTexture(TextureDescriptor descriptor)
|
||||
{
|
||||
var resource = GetOrCreateTextureResource();
|
||||
resource.Index = _textureResourceCount - 1;
|
||||
resource.Version = 0;
|
||||
resource.Descriptor = descriptor;
|
||||
resource.IsImported = false;
|
||||
|
||||
return new RenderGraphTextureHandle(resource.Index, resource.Version, descriptor.Name);
|
||||
return new Identifier<RGTexture>(resource.Index);
|
||||
}
|
||||
|
||||
public TextureResource GetTextureResource(RenderGraphTextureHandle handle)
|
||||
public RenderGraphResource GetResource(Identifier<RGResource> resource)
|
||||
{
|
||||
if (handle.Index < 0 || handle.Index >= _textureResourceCount)
|
||||
throw new ArgumentException($"Invalid texture handle: {handle.Index}");
|
||||
if (resource.Value < 0 || resource.Value >= _textureResourceCount)
|
||||
throw new ArgumentException($"Invalid texture handle: {resource}");
|
||||
|
||||
return _textureResources[handle.Index];
|
||||
return _resources[resource.Value];
|
||||
}
|
||||
|
||||
public TextureResource GetTextureResourceByIndex(int index)
|
||||
public RenderGraphResource GetTextureResourceByIndex(int index)
|
||||
{
|
||||
if (index < 0 || index >= _textureResourceCount)
|
||||
throw new ArgumentException($"Invalid texture index: {index}");
|
||||
|
||||
return _textureResources[index];
|
||||
return _resources[index];
|
||||
}
|
||||
|
||||
public void SetProducer(RenderGraphTextureHandle handle, int passIndex)
|
||||
public void SetProducer(Identifier<RGResource> resourceID, int passIndex)
|
||||
{
|
||||
var resource = GetTextureResource(handle);
|
||||
var resource = GetResource(resourceID);
|
||||
resource.ProducerPass = passIndex;
|
||||
if (resource.FirstUsePass < 0)
|
||||
{
|
||||
resource.FirstUsePass = passIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddConsumer(RenderGraphTextureHandle handle, int passIndex)
|
||||
public void AddConsumer(Identifier<RGResource> resourceID, int passIndex)
|
||||
{
|
||||
var resource = GetTextureResource(handle);
|
||||
var resource = GetResource(resourceID);
|
||||
resource.ConsumerPasses.Add(passIndex);
|
||||
resource.LastUsePass = passIndex;
|
||||
if (resource.FirstUsePass < 0)
|
||||
{
|
||||
resource.FirstUsePass = passIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private TextureResource GetOrCreateTextureResource()
|
||||
private RenderGraphResource GetOrCreateTextureResource()
|
||||
{
|
||||
TextureResource resource;
|
||||
if (_textureResourceCount < _textureResources.Count)
|
||||
RenderGraphResource resource;
|
||||
if (_textureResourceCount < _resources.Count)
|
||||
{
|
||||
// Reuse existing slot
|
||||
resource = _textureResources[_textureResourceCount];
|
||||
resource = _resources[_textureResourceCount];
|
||||
resource.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Need to grow the list
|
||||
resource = _pool.Get<TextureResource>();
|
||||
resource = _pool.Rent<RenderGraphResource>();
|
||||
resource.Reset();
|
||||
_textureResources.Add(resource);
|
||||
_resources.Add(resource);
|
||||
}
|
||||
|
||||
_textureResourceCount++;
|
||||
@@ -163,11 +199,11 @@ internal sealed class RenderGraphResourceRegistry
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < _textureResources.Count; i++)
|
||||
for (var i = 0; i < _resources.Count; i++)
|
||||
{
|
||||
_pool.Release(_textureResources[i]);
|
||||
_pool.Return(_resources[i]);
|
||||
}
|
||||
_textureResources.Clear();
|
||||
_resources.Clear();
|
||||
_textureResourceCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,57 @@
|
||||
using Ghost.Core;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
/// <summary>
|
||||
/// Opaque handle to a render graph texture resource.
|
||||
/// </summary>
|
||||
public readonly struct RenderGraphTextureHandle : IEquatable<RenderGraphTextureHandle>
|
||||
internal enum RenderGraphResourceType
|
||||
{
|
||||
public readonly int Index;
|
||||
public readonly int Version;
|
||||
internal readonly string InternalName;
|
||||
Texture,
|
||||
Buffer,
|
||||
AccelerationStructure,
|
||||
Count
|
||||
}
|
||||
|
||||
public RenderGraphTextureHandle(int index, int version, string name = "")
|
||||
public struct RGResource;
|
||||
public struct RGTexture;
|
||||
public struct RGBuffer;
|
||||
|
||||
public static class RGResourceExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Identifier<RGResource> AsResource(this Identifier<RGTexture> texture)
|
||||
{
|
||||
Index = index;
|
||||
Version = version;
|
||||
InternalName = name;
|
||||
return new Identifier<RGResource>(texture.Value);
|
||||
}
|
||||
|
||||
public string Name => InternalName;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Identifier<RGResource> AsResource(this Identifier<RGBuffer> buffer)
|
||||
{
|
||||
return new Identifier<RGResource>(buffer.Value);
|
||||
}
|
||||
|
||||
public bool IsValid() => Index >= 0;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static Identifier<RGTexture> AsTexture(this Identifier<RGResource> resource)
|
||||
{
|
||||
return new Identifier<RGTexture>(resource.Value);
|
||||
}
|
||||
|
||||
public readonly bool Equals(RenderGraphTextureHandle other) => Index == other.Index && Version == other.Version;
|
||||
public override readonly bool Equals(object? obj) => obj is RenderGraphTextureHandle other && Equals(other);
|
||||
public override readonly int GetHashCode() => HashCode.Combine(Index, Version);
|
||||
public static bool operator ==(RenderGraphTextureHandle left, RenderGraphTextureHandle right) => left.Equals(right);
|
||||
public static bool operator !=(RenderGraphTextureHandle left, RenderGraphTextureHandle right) => !left.Equals(right);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static Identifier<RGBuffer> AsBuffer(this Identifier<RGResource> resource)
|
||||
{
|
||||
return new Identifier<RGBuffer>(resource.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct TextureAccess
|
||||
{
|
||||
public readonly Identifier<RGTexture> id;
|
||||
public readonly AccessFlags accessFlags;
|
||||
|
||||
public TextureAccess(Identifier<RGTexture> id, AccessFlags accessFlags)
|
||||
{
|
||||
this.id = id;
|
||||
this.accessFlags = accessFlags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user