From 954e3756aacd8fa2f6ed6c4f70387587294b395a Mon Sep 17 00:00:00 2001 From: Misaki Date: Mon, 12 Jan 2026 23:48:56 +0900 Subject: [PATCH] Refactor Render Graph: unified resources, benchmarking Major overhaul of Render Graph system: - Replaced texture handles with generic Identifier 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. --- .../Benchmark/RenderGraphBenchmark.cs | 200 ++++++++++++ .../Ghost.RenderGraph.Concept.csproj | 4 + Ghost.RenderGraph.Concept/PassData.cs | 40 +-- Ghost.RenderGraph.Concept/Program.cs | 222 ++----------- Ghost.RenderGraph.Concept/RenderGraph.cs | 300 +++++++++--------- .../RenderGraphAliasing.cs | 12 +- .../RenderGraphBarriers.cs | 107 +++---- .../RenderGraphBlackboard.cs | 13 +- .../RenderGraphBuilder.cs | 240 ++++++++++++++ .../RenderGraphBuilderExtensions.cs | 27 -- .../RenderGraphContext.cs | 52 +-- Ghost.RenderGraph.Concept/RenderGraphHash.cs | 68 ---- Ghost.RenderGraph.Concept/RenderGraphPass.cs | 242 +++++--------- .../RenderGraphResourcePool.cs | 126 +++++--- Ghost.RenderGraph.Concept/RenderGraphTypes.cs | 63 ++-- 15 files changed, 940 insertions(+), 776 deletions(-) create mode 100644 Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs create mode 100644 Ghost.RenderGraph.Concept/RenderGraphBuilder.cs delete mode 100644 Ghost.RenderGraph.Concept/RenderGraphBuilderExtensions.cs delete mode 100644 Ghost.RenderGraph.Concept/RenderGraphHash.cs diff --git a/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs b/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs new file mode 100644 index 0000000..dcb2223 --- /dev/null +++ b/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs @@ -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("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(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 lightingOutput; + using (var builder = renderGraph.AddRasterRenderPass("Lighting Pass", out var lightingData)) + { + // Read GBuffer from blackboard + var gbuffer = renderGraph.Blackboard.Get(); + + 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(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 ssaoOutput; + using (var builder = renderGraph.AddComputeRenderPass("SSAO Pass (Async)", out var ssaoData)) + { + var gbuffer = renderGraph.Blackboard.Get(); + + 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(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 bloomOutput; + using (var builder = renderGraph.AddRasterRenderPass("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(static (data, cmd) => + { + cmd.BindShaderResource(data.Input.AsResource(), 0); + cmd.Draw(3); + }); + } + + // ===== Temporal AA Pass ===== + Identifier taaOutput; + using (var builder = renderGraph.AddRasterRenderPass("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(static (data, cmd) => + { + cmd.BindShaderResource(data.InputLighting.AsResource(), 0); + cmd.Draw(3); + }); + } + + // ===== Post Processing Pass ===== + using (var builder = renderGraph.AddRasterRenderPass("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(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("GPU Profiler Begin Frame", out var profilerData)) + { + builder.AllowPassCulling(false); // Never cull this - it's for debugging/profiling + builder.SetRenderFunc(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("Unused Debug Pass", out var debugData)) + { + builder.SetColorAttachment( + builder.CreateTexture(new TextureDescriptor(512, 512, TextureFormat.RGBA8, "DebugTexture")), 0); + + builder.SetRenderFunc(static (data, cmd) => + { + cmd.Draw(100); + }); + } + + // Compile and execute the render graph + renderGraph.Compile(); + renderGraph.Execute(); + } +} \ No newline at end of file diff --git a/Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj b/Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj index 9279c9d..6b24b95 100644 --- a/Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj +++ b/Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj @@ -16,6 +16,10 @@ $(DefineConstants);PROFILING + + + + diff --git a/Ghost.RenderGraph.Concept/PassData.cs b/Ghost.RenderGraph.Concept/PassData.cs index bb6cebb..c687fb5 100644 --- a/Ghost.RenderGraph.Concept/PassData.cs +++ b/Ghost.RenderGraph.Concept/PassData.cs @@ -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 Albedo; + public Identifier Normal; + public Identifier Depth; } public sealed class LightingPassData : IPassData { - public RenderGraphTextureHandle GBufferAlbedo; - public RenderGraphTextureHandle GBufferNormal; - public RenderGraphTextureHandle GBufferDepth; - public RenderGraphTextureHandle OutputLighting; + public Identifier GBufferAlbedo; + public Identifier GBufferNormal; + public Identifier GBufferDepth; + public Identifier OutputLighting; } public sealed class SSAOPassData : IPassData { - public RenderGraphTextureHandle GBufferDepth; - public RenderGraphTextureHandle GBufferNormal; - public RenderGraphTextureHandle OutputSSAO; + public Identifier GBufferDepth; + public Identifier GBufferNormal; + public Identifier OutputSSAO; } public sealed class BloomDownsampleData : IPassData { - public RenderGraphTextureHandle Input; - public RenderGraphTextureHandle Output; + public Identifier Input; + public Identifier Output; } public sealed class TAAPassData : IPassData { - public RenderGraphTextureHandle InputLighting; - public RenderGraphTextureHandle OutputTAA; + public Identifier InputLighting; + public Identifier OutputTAA; } public sealed class PostProcessingPassDataV2 : IPassData { - public RenderGraphTextureHandle InputTAA; - public RenderGraphTextureHandle InputSSAO; - public RenderGraphTextureHandle InputBloom; - public RenderGraphTextureHandle OutputBackbuffer; + public Identifier InputTAA; + public Identifier InputSSAO; + public Identifier InputBloom; + public Identifier 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 DebugTexture; } diff --git a/Ghost.RenderGraph.Concept/Program.cs b/Ghost.RenderGraph.Concept/Program.cs index 17d1bd4..ee17701 100644 --- a/Ghost.RenderGraph.Concept/Program.cs +++ b/Ghost.RenderGraph.Concept/Program.cs @@ -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(); +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("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((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("Lighting Pass", out var lightingData)) - { - // Read GBuffer from blackboard - var gbuffer = renderGraph.Blackboard.Get(); - - 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((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("SSAO Pass (Async)", out var ssaoData)) - { - var gbuffer = renderGraph.Blackboard.Get(); - - 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((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("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((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("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((data, cmd) => - { - cmd.BindShaderResource(data.InputLighting.Name, 0); - cmd.SetRenderTarget(data.OutputTAA.Name); - cmd.Draw(3); - }); - } - - // ===== Post Processing Pass ===== - using (var builder = renderGraph.AddRenderPass("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((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("GPU Profiler Begin Frame", out var profilerData)) - { - builder.SetAllowCulling(false); // Never cull this - it's for debugging/profiling - builder.SetRenderFunc((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("Unused Debug Pass", out var debugData)) - { - debugData.DebugTexture = builder.WriteTexture( - builder.CreateTexture(new TextureDescriptor(512, 512, TextureFormat.RGBA8, "DebugTexture"))); - - builder.SetRenderFunc((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(); -} \ No newline at end of file diff --git a/Ghost.RenderGraph.Concept/RenderGraph.cs b/Ghost.RenderGraph.Concept/RenderGraph.cs index 4a66b82..302e765 100644 --- a/Ghost.RenderGraph.Concept/RenderGraph.cs +++ b/Ghost.RenderGraph.Concept/RenderGraph.cs @@ -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 _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 /// /// Imports an external texture into the render graph. /// - public RenderGraphTextureHandle ImportTexture(string name, TextureDescriptor descriptor) + public Identifier ImportTexture(TextureDescriptor descriptor) { return _resources.ImportTexture(descriptor); } - /// - /// Adds a new render pass to the graph. - /// - public RenderGraphBuilder AddRenderPass(string name, out TPassData passData) + public IRasterRenderGraphBuilder AddRasterRenderPass(string name, out TPassData passData) where TPassData : class, new() { - // Get or create pass from pool - RenderGraphPass pass; - if (_passCount < _passes.Count) - { - // Reuse existing slot - var existingPass = _passes[_passCount]; - if (existingPass is RenderGraphPass typedPass) - { - pass = typedPass; - pass.Reset(); - } - else - { - // Type mismatch, need to replace - _objectPool.Release(existingPass); - pass = _objectPool.Get>(); - pass.Reset(); - _passes[_passCount] = pass; - } - } - else - { - // Need to grow the list - pass = _objectPool.Get>(); - pass.Reset(); - _passes.Add(pass); - } + var renderPass = _objectPool.Rent>(); + renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Raster); + passData = renderPass.passData; - // Initialize pass - pass.Name = name; - pass.Index = _passCount; + _passes.Add(renderPass); - // Get or create pass data from pool - passData = _objectPool.Get(); - pass.PassData = passData; + _builder.Init(this, renderPass, _resources); + return _builder; + } - _passCount++; + public IComputeRenderGraphBuilder AddComputeRenderPass(string name, out TPassData passData) + where TPassData : class, new() + { + var renderPass = _objectPool.Rent>(); + renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Compute); + passData = renderPass.passData; - // Initialize builder - _builder.Initialize(pass, _resources); + _passes.Add(renderPass); + + _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(4096, scope.AllocationHandle); - int offset = 0; + var bufferPool = new UnsafeList(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(pData, offset); - _hasher.Append(span); - return _hasher.GetCurrentHashAsUInt64(); + return XxHash64.HashToUInt64(span); } /// @@ -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,46 +425,43 @@ 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); - + // If this physical resource has multiple aliased resources, // we need an aliasing barrier when switching between them 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; - - foreach (int otherLogicalIndex in physical.AliasedLogicalResources) + Identifier resourceBefore = default; + var mostRecentLastUse = -1; + + 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; } } /// /// Inserts a transition barrier if the resource state changes. /// - private void InsertTransitionIfNeeded(RenderGraphTextureHandle handle, ResourceState newState, int passIdx) + private void InsertTransitionIfNeeded(Identifier 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,11 +569,11 @@ 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]; - + // Execute all barriers for this pass #if DEBUG bool hasBarriers = false; @@ -584,14 +583,23 @@ 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]); - + barrierIndex++; } #if DEBUG @@ -600,7 +608,7 @@ public sealed class RenderGraph Console.WriteLine("=====================================\n"); } #endif - + pass.Execute(_renderContext); } } diff --git a/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs b/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs index 23e892d..ab06565 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs @@ -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(); + resource = _pool.Rent(); 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(); + physical = _pool.Rent(); physical.Reset(); _physicalResources.Add(physical); } diff --git a/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs b/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs index d4126f2..abe72d7 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs @@ -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) } /// @@ -41,71 +41,69 @@ internal struct ResourceBarrier { internal struct barrier_union_transition { - public RenderGraphTextureHandle Resource; - public ResourceState StateBefore; - public ResourceState StateAfter; + public Identifier resource; + public ResourceState stateBefore; + public ResourceState stateAfter; } internal struct barrier_union_aliasing { - public RenderGraphTextureHandle ResourceBefore; - public RenderGraphTextureHandle ResourceAfter; + public Identifier resourceBefore; + public Identifier 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; - - // For Transition and UAV barriers - public RenderGraphTextureHandle Resource; - public ResourceState StateBefore; - public ResourceState StateAfter; - - // For Aliasing barriers (D3D12_RESOURCE_BARRIER::Aliasing) - public RenderGraphTextureHandle ResourceBefore; // pResourceBefore - public RenderGraphTextureHandle ResourceAfter; // pResourceAfter - - public int PassIndex; + private barrier_union _union; - // Constructor for Transition and UAV barriers - public ResourceBarrier(BarrierType type, RenderGraphTextureHandle resource, - ResourceState before, ResourceState after, int passIndex) + public BarrierType Type { - Type = type; - Resource = resource; - StateBefore = before; - StateAfter = after; - ResourceBefore = default; - ResourceAfter = default; - PassIndex = passIndex; + get; init; } + public int PassIndex + { + get; init; + } + + // For Transition and UAV barriers + public readonly Identifier 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 readonly Identifier ResourceBefore => _union.aliasing.resourceBefore; + public readonly Identifier ResourceAfter => _union.aliasing.resourceAfter; + // Constructor for Aliasing barriers public static ResourceBarrier CreateAliasingBarrier( - RenderGraphTextureHandle resourceBefore, - RenderGraphTextureHandle resourceAfter, + Identifier resourceBefore, + Identifier 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 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 } /// @@ -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; } } diff --git a/Ghost.RenderGraph.Concept/RenderGraphBlackboard.cs b/Ghost.RenderGraph.Concept/RenderGraphBlackboard.cs index e13a257..83e1a1d 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphBlackboard.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphBlackboard.cs @@ -7,12 +7,13 @@ namespace Ghost.RenderGraph.Concept; /// public sealed class RenderGraphBlackboard { - private readonly Dictionary _data = new(16); + private readonly Dictionary _data = new(16); /// /// Adds or updates pass data in the blackboard. /// - public void Add(T data) where T : class, IPassData + public void Add(T data) + where T : class, IPassData { var type = typeof(T); _data[type] = data; @@ -21,20 +22,23 @@ public sealed class RenderGraphBlackboard /// /// Retrieves pass data from the blackboard. /// - public T Get() where T : class, IPassData + public T Get() + 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"); } /// /// Tries to get pass data from the blackboard. /// - public bool TryGet(out T? data) where T : class, IPassData + public bool TryGet(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; } diff --git a/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs new file mode 100644 index 0000000..98d3a3b --- /dev/null +++ b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs @@ -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 +{ + /// + /// Enables or disables pass culling for the current context. + /// + /// A value indicating whether pass culling is allowed. + void AllowPassCulling(bool value); + + /// + /// Creates a new texture resource based on the specified descriptor. + /// + /// A structure that defines the properties and configuration of the texture to create. + /// An identifier for the newly created texture resource. + Identifier CreateTexture(in TextureDescriptor descriptor); + + /// + /// Registers the specified texture for use in the current render graph pass with the given access mode. + /// + /// The identifier of the texture to be used in the render graph pass. + /// The access mode specifying how the texture will be read or written during the pass. + /// An identifier for the texture. + Identifier UseTexture(Identifier texture, AccessFlags accessMode); +} + +public interface IRasterRenderGraphBuilder : IRenderGraphBuilder +{ + /// + /// Binds a texture for random access operations within the current rendering pass. + /// + /// The identifier of the texture to be used for random access. + /// An identifier for the texture. + Identifier UseRandomAccessTexture(Identifier texture); + /// + /// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context. + /// + /// An identifier for the buffer to be used for random access. Must reference a valid buffer resource. + /// An identifier for the buffer. + Identifier UseRandomAccessBuffer(Identifier buffer); + + /// + /// Sets the color attachment at the specified index to the given texture. + /// + /// The identifier of the texture to use as the color attachment. + /// The zero-based index of the color attachment to set. + void SetColorAttachment(Identifier texture, int index); + + /// + /// Sets the depth attachment for the current render pass using the specified texture. + /// + /// The identifier of the texture to use as the depth attachment. Cannot be null. + void SetDepthAttachment(Identifier texture); + + /// + /// Sets the function used to render a pass with the specified pass data and render context. + /// + /// The type of data associated with the render pass. + /// The delegate that defines the rendering logic for the pass. + void SetRenderFunc(Action renderFunc) + where TPassData : class, new(); +} + +public interface IComputeRenderGraphBuilder : IRenderGraphBuilder +{ + /// + /// Enables or disables asynchronous compute operations. + /// + /// true to enable asynchronous compute; otherwise, false. + void EnableAsyncCompute(bool value); + + /// + /// Sets the render function to be invoked during the compute rendering process. + /// + /// The type of the data object passed to the render function. + /// The delegate that defines the rendering logic to execute. + void SetRenderFunc(Action 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 UseResource(Identifier 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 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 UseTexture(Identifier texture, AccessFlags flags) + { + ThrowIfDisposed(); + + return UseResource(texture.AsResource(), flags).AsTexture(); + } + + public Identifier UseRandomAccessTexture(Identifier texture) + { + ThrowIfDisposed(); + + var resource = texture.AsResource(); + UseResource(resource, AccessFlags.ReadWrite); + _pass.randomAccess.Add(resource); + return texture; + } + + public Identifier UseRandomAccessBuffer(Identifier buffer) + { + ThrowIfDisposed(); + + var resource = buffer.AsResource(); + UseResource(resource, AccessFlags.ReadWrite); + _pass.randomAccess.Add(resource); + return buffer; + } + + public void SetColorAttachment(Identifier 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 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(Action renderFunc) + where TPassData : class, new() + { + ((RasterRenderGraphPass)_pass).renderFunc = renderFunc; + } + + public void SetRenderFunc(Action renderFunc) + where TPassData : class, new() + { + ((ComputeRenderGraphPass)_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; + } +} diff --git a/Ghost.RenderGraph.Concept/RenderGraphBuilderExtensions.cs b/Ghost.RenderGraph.Concept/RenderGraphBuilderExtensions.cs deleted file mode 100644 index 6894736..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphBuilderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Ghost.RenderGraph.Concept; - -/// -/// Extension methods to provide a cleaner API for setting render functions. -/// These avoid the need for explicit type annotations in lambdas. -/// -public static class RenderGraphBuilderExtensions -{ - // Internal helper to cast and set - private static void SetRasterFunc(this RenderGraphBuilder builder, object pass, Action func) - where TPassData : class, new() - { - if (pass is RenderGraphPass typedPass) - { - builder.SetRenderFunc(func); - } - } - - private static void SetCompFunc(this RenderGraphBuilder builder, object pass, Action func, bool async) - where TPassData : class, new() - { - if (pass is RenderGraphPass typedPass) - { - builder.SetComputeFunc(func, async); - } - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphContext.cs b/Ghost.RenderGraph.Concept/RenderGraphContext.cs index 1f1e8c2..8c89737 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphContext.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphContext.cs @@ -1,36 +1,38 @@ +using Ghost.Core; + namespace Ghost.RenderGraph.Concept; /// /// Mock command buffer for recording GPU commands. /// In a real implementation, this would wrap D3D12 command lists. /// -public sealed class MockCommandBuffer +internal sealed class MockCommandBuffer { - public void SetRenderTarget(string name) + public void SetRenderTarget(Identifier texture) { #if DEBUG - Console.WriteLine(nameof(SetRenderTarget) + ": " + name); + Console.WriteLine(nameof(SetRenderTarget) + ": " + texture); #endif } - public void SetDepthStencil(string name) + public void SetDepthStencil(Identifier 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 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 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 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 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 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 resourceBefore, Identifier 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 texture) => _cmd.SetRenderTarget(texture); + public void SetDepthStencil(Identifier texture) => _cmd.SetDepthStencil(texture); + public void ClearRenderTarget(Identifier texture, float r, float g, float b, float a) => _cmd.ClearRenderTarget(texture, r, g, b, a); + public void ClearDepth(Identifier 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 resource, int slot) => _cmd.BindShaderResource(resource, slot); } /// @@ -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 resource, int slot) => _cmd.BindShaderResource(resource, slot); + public void BindUnorderedAccess(Identifier resource, int slot) => _cmd.BindUnorderedAccess(resource, slot); public void Dispatch(int x, int y, int z) => _cmd.Dispatch(x, y, z); } diff --git a/Ghost.RenderGraph.Concept/RenderGraphHash.cs b/Ghost.RenderGraph.Concept/RenderGraphHash.cs deleted file mode 100644 index e5f1c68..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphHash.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.IO.Hashing; -using System.Runtime.InteropServices; - -namespace Ghost.RenderGraph.Concept; - -/// -/// Helper extensions for XxHash3 to hash common types without string allocation. -/// Uses SIMD-optimized hashing via System.IO.Hashing.XxHash3. -/// -internal static class RenderGraphHashExtensions -{ - /// - /// Appends an int to the hash. - /// - public static void AppendInt(this XxHash64 hash, int value) - { - ReadOnlySpan span = stackalloc int[1] { value }; - hash.Append(MemoryMarshal.AsBytes(span)); - } - - /// - /// Appends a bool to the hash. - /// - public static void AppendBool(this XxHash64 hash, bool value) - { - ReadOnlySpan span = stackalloc bool[1] { value }; - hash.Append(MemoryMarshal.AsBytes(span)); - } - - /// - /// Appends an enum to the hash. - /// - public static void AppendEnum(this XxHash64 hash, TEnum value) where TEnum : unmanaged, Enum - { - ReadOnlySpan span = stackalloc TEnum[1] { value }; - hash.Append(MemoryMarshal.AsBytes(span)); - } - - /// - /// Appends a struct to the hash (must be unmanaged). - /// - public static void AppendStruct(this XxHash64 hash, in T value) where T : unmanaged - { - ReadOnlySpan span = stackalloc T[1] { value }; - hash.Append(MemoryMarshal.AsBytes(span)); - } - - /// - /// Appends a list of resource handle indices to the hash. - /// - public static void AppendHandleList(this XxHash64 hash, List 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 indices = stackalloc int[count]; - for (int i = 0; i < count; i++) - { - indices[i] = handles[i].Index; - } - hash.Append(MemoryMarshal.AsBytes(indices)); - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphPass.cs b/Ghost.RenderGraph.Concept/RenderGraphPass.cs index fde3a05..0ae38cc 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphPass.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphPass.cs @@ -1,3 +1,6 @@ +using Ghost.Core; +using System.IO; + namespace Ghost.RenderGraph.Concept; /// @@ -15,201 +18,112 @@ public enum RenderPassType : byte /// 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> randomAccess = new(8); + // Resource dependencies - public readonly List TextureReads = new(8); - public readonly List TextureWrites = new(4); - public readonly List TextureCreates = new(4); + public readonly List> resourceReads = new(8); + public readonly List> resourceWrites = new(4); + public readonly List> 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; } } -/// -/// Typed render pass with user data. -/// -internal sealed class RenderGraphPass : RenderGraphPassBase +internal abstract class RenderGraphPassT : RenderGraphPassBase where TPassData : class, new() { - public TPassData? PassData; - public Action? RasterRenderFunc; - public Action? ComputeRenderFunc; + public TPassData passData = null!; + public Action? 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) - { - RasterRenderFunc(PassData, context.RasterContext); - } - else if (Type == RenderPassType.Compute && ComputeRenderFunc != null) - { - ComputeRenderFunc(PassData, context.ComputeContext); - } + public sealed override bool HasRenderFunc() + { + 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(); } } -/// -/// Builder for constructing render passes. -/// Implements IDisposable for using() pattern. -/// -public sealed class RenderGraphBuilder : IDisposable +internal sealed class RasterRenderGraphPass : RenderGraphPassT + 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); } - /// - /// Creates a new transient texture that only lives for this pass. - /// - 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; - } - - /// - /// Marks a texture as being read by this pass. - /// - public RenderGraphTextureHandle ReadTexture(RenderGraphTextureHandle handle) - { - ThrowIfDisposed(); - _pass!.TextureReads.Add(handle); - _resources!.AddConsumer(handle, _pass.Index); - return handle; - } - - /// - /// Marks a texture as being written by this pass. - /// - public RenderGraphTextureHandle WriteTexture(RenderGraphTextureHandle handle) - { - ThrowIfDisposed(); - _pass!.TextureWrites.Add(handle); - _resources!.SetProducer(handle, _pass.Index); - return handle; - } - - /// - /// Sets up a depth buffer for this pass. - /// - 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; - } - - /// - /// Sets the render function for a raster pass. - /// - public void SetRenderFunc(Action renderFunc) - where TPassData : class, new() - { - ThrowIfDisposed(); - - if (_pass is RenderGraphPass typedPass) - { - typedPass.RasterRenderFunc = renderFunc; - typedPass.Type = RenderPassType.Raster; - } - } - - /// - /// Sets the compute function for a compute pass. - /// - public void SetComputeFunc(Action computeFunc, bool asyncCompute = false) - where TPassData : class, new() - { - ThrowIfDisposed(); - - if (_pass is RenderGraphPass typedPass) - { - typedPass.ComputeRenderFunc = computeFunc; - typedPass.Type = RenderPassType.Compute; - typedPass.AsyncCompute = asyncCompute; - } - } - - /// - /// Controls whether this pass can be culled if its outputs are unused. - /// - 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 : RenderGraphPassT + 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); + } +} \ No newline at end of file diff --git a/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs b/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs index ae511a6..5908d45 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs @@ -1,3 +1,6 @@ +using Ghost.Core; +using Misaki.HighPerformance.Buffer; + namespace Ghost.RenderGraph.Concept; /// @@ -6,44 +9,76 @@ namespace Ghost.RenderGraph.Concept; /// internal sealed class RenderGraphObjectPool { - private readonly Dictionary> _pools = new(); + private static readonly List s_allocatedPools = new(); - public T Get() 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 obj) where T : class + private class SharedObjectPool : SharedObjectPoolBase where T : class, new() { - if (obj == null) return; + private static readonly ObjectPool s_pool = AllocatePool(); - var type = typeof(T); - if (!_pools.TryGetValue(type, out var pool)) + private static ObjectPool AllocatePool() { - pool = new Stack(16); - _pools[type] = pool; + var newPool = new ObjectPool(() => new T()); + // Storing instance to clear the static pool of the same type if needed + s_allocatedPools.Add(new SharedObjectPool()); + return newPool; } - pool.Push(obj); + + /// + /// Clear the pool using SharedObjectPool instance. + /// + /// + public override void Clear() + { + s_pool.Reset(); + } + + /// + /// Rent a new instance from the pool. + /// + /// + public static T Rent() => s_pool.Rent(); + + /// + /// Return an object to the pool. + /// + /// instance to release. + public static void Return(T toRelease) => s_pool.Return(toRelease); + } + + public T Rent() + where T : class, new() + { + return SharedObjectPool.Rent(); + } + + public void Return(T obj) + where T : class, new() + { + SharedObjectPool.Return(obj); } public void Clear() { - _pools.Clear(); + for (var i = 0; i < s_allocatedPools.Count; i++) + { + s_allocatedPools[i].Clear(); + } } } /// /// Represents a texture resource in the render graph. /// -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 /// internal sealed class RenderGraphResourceRegistry { - private readonly List _textureResources = new(64); + private readonly List _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 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(resource.Index); } - public RenderGraphTextureHandle CreateTexture(TextureDescriptor descriptor) + public Identifier 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(resource.Index); } - public TextureResource GetTextureResource(RenderGraphTextureHandle handle) + public RenderGraphResource GetResource(Identifier 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 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 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(); + resource = _pool.Rent(); 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; } } diff --git a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs b/Ghost.RenderGraph.Concept/RenderGraphTypes.cs index 75242b7..7a735b9 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphTypes.cs @@ -1,30 +1,57 @@ +using Ghost.Core; +using System.Runtime.CompilerServices; + namespace Ghost.RenderGraph.Concept; -/// -/// Opaque handle to a render graph texture resource. -/// -public readonly struct RenderGraphTextureHandle : IEquatable +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 AsResource(this Identifier texture) { - Index = index; - Version = version; - InternalName = name; + return new Identifier(texture.Value); } - public string Name => InternalName; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Identifier AsResource(this Identifier buffer) + { + return new Identifier(buffer.Value); + } - public bool IsValid() => Index >= 0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Identifier AsTexture(this Identifier resource) + { + return new Identifier(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 AsBuffer(this Identifier resource) + { + return new Identifier(resource.Value); + } +} + +internal readonly struct TextureAccess +{ + public readonly Identifier id; + public readonly AccessFlags accessFlags; + + public TextureAccess(Identifier id, AccessFlags accessFlags) + { + this.id = id; + this.accessFlags = accessFlags; + } } ///