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>
|
<DefineConstants>$(DefineConstants);PROFILING</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
|
||||||
namespace Ghost.RenderGraph.Concept;
|
namespace Ghost.RenderGraph.Concept;
|
||||||
|
|
||||||
// ===== Pass Data Structures =====
|
// ===== Pass Data Structures =====
|
||||||
@@ -5,44 +7,44 @@ namespace Ghost.RenderGraph.Concept;
|
|||||||
|
|
||||||
public sealed class GBufferData : IPassData
|
public sealed class GBufferData : IPassData
|
||||||
{
|
{
|
||||||
public RenderGraphTextureHandle Albedo;
|
public Identifier<RGTexture> Albedo;
|
||||||
public RenderGraphTextureHandle Normal;
|
public Identifier<RGTexture> Normal;
|
||||||
public RenderGraphTextureHandle Depth;
|
public Identifier<RGTexture> Depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class LightingPassData : IPassData
|
public sealed class LightingPassData : IPassData
|
||||||
{
|
{
|
||||||
public RenderGraphTextureHandle GBufferAlbedo;
|
public Identifier<RGTexture> GBufferAlbedo;
|
||||||
public RenderGraphTextureHandle GBufferNormal;
|
public Identifier<RGTexture> GBufferNormal;
|
||||||
public RenderGraphTextureHandle GBufferDepth;
|
public Identifier<RGTexture> GBufferDepth;
|
||||||
public RenderGraphTextureHandle OutputLighting;
|
public Identifier<RGTexture> OutputLighting;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SSAOPassData : IPassData
|
public sealed class SSAOPassData : IPassData
|
||||||
{
|
{
|
||||||
public RenderGraphTextureHandle GBufferDepth;
|
public Identifier<RGTexture> GBufferDepth;
|
||||||
public RenderGraphTextureHandle GBufferNormal;
|
public Identifier<RGTexture> GBufferNormal;
|
||||||
public RenderGraphTextureHandle OutputSSAO;
|
public Identifier<RGTexture> OutputSSAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class BloomDownsampleData : IPassData
|
public sealed class BloomDownsampleData : IPassData
|
||||||
{
|
{
|
||||||
public RenderGraphTextureHandle Input;
|
public Identifier<RGTexture> Input;
|
||||||
public RenderGraphTextureHandle Output;
|
public Identifier<RGTexture> Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TAAPassData : IPassData
|
public sealed class TAAPassData : IPassData
|
||||||
{
|
{
|
||||||
public RenderGraphTextureHandle InputLighting;
|
public Identifier<RGTexture> InputLighting;
|
||||||
public RenderGraphTextureHandle OutputTAA;
|
public Identifier<RGTexture> OutputTAA;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PostProcessingPassDataV2 : IPassData
|
public sealed class PostProcessingPassDataV2 : IPassData
|
||||||
{
|
{
|
||||||
public RenderGraphTextureHandle InputTAA;
|
public Identifier<RGTexture> InputTAA;
|
||||||
public RenderGraphTextureHandle InputSSAO;
|
public Identifier<RGTexture> InputSSAO;
|
||||||
public RenderGraphTextureHandle InputBloom;
|
public Identifier<RGTexture> InputBloom;
|
||||||
public RenderGraphTextureHandle OutputBackbuffer;
|
public Identifier<RGTexture> OutputBackbuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ProfilerMarkerData : IPassData
|
public sealed class ProfilerMarkerData : IPassData
|
||||||
@@ -51,5 +53,5 @@ public sealed class ProfilerMarkerData : IPassData
|
|||||||
|
|
||||||
public sealed class DebugPassData : 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;
|
||||||
|
using Ghost.RenderGraph.Concept.Benchmark;
|
||||||
|
|
||||||
var renderGraph = new RenderGraph();
|
var renderGraph = new RenderGraph();
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
const int _ITERATION = 500;
|
|
||||||
for (var i = 0; i < _ITERATION; i++)
|
|
||||||
{
|
|
||||||
ExecuteGraph(renderGraph);
|
|
||||||
}
|
|
||||||
|
|
||||||
GC.Collect();
|
BenchmarkDotNet.Running.BenchmarkRunner.Run<RenderGraphBenchmark>();
|
||||||
GC.WaitForPendingFinalizers();
|
return;
|
||||||
// Thread.Sleep(1000); // Leave a gap in visual studio allocations timeline
|
|
||||||
var sw = new System.Diagnostics.Stopwatch();
|
|
||||||
var gcBefore = GC.GetAllocatedBytesForCurrentThread();
|
|
||||||
sw.Start();
|
|
||||||
|
|
||||||
for (var i = 0; i < _ITERATION; i++)
|
//const int _ITERATION = 500000;
|
||||||
{
|
//for (var i = 0; i < _ITERATION; i++)
|
||||||
ExecuteGraph(renderGraph);
|
//{
|
||||||
}
|
// RenderGraphBenchmark.ExecuteGraph(renderGraph);
|
||||||
|
//}
|
||||||
|
|
||||||
sw.Stop();
|
//GC.Collect();
|
||||||
var gcAfter = GC.GetAllocatedBytesForCurrentThread();
|
//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)");
|
//for (var i = 0; i < _ITERATION; i++)
|
||||||
Console.WriteLine($"GC Allocated Bytes: {(gcAfter - gcBefore) / _ITERATION} bytes (per iteration)");
|
//{
|
||||||
|
// 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
|
#else
|
||||||
// Run twice to demonstrate cache hit
|
// Run twice to demonstrate cache hit
|
||||||
Console.WriteLine("=== FRAME 1 (Cache Miss Expected) ===");
|
Console.WriteLine("=== FRAME 1 (Cache Miss Expected) ===");
|
||||||
ExecuteGraph(renderGraph);
|
RenderGraphBenchmark.ExecuteGraph(renderGraph);
|
||||||
|
|
||||||
Console.WriteLine("\n\n=== FRAME 2 (Cache Hit Expected) ===");
|
Console.WriteLine("\n\n=== FRAME 2 (Cache Hit Expected) ===");
|
||||||
ExecuteGraph(renderGraph);
|
RenderGraphBenchmark.ExecuteGraph(renderGraph);
|
||||||
#endif
|
#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.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System.IO.Hashing;
|
using System.IO.Hashing;
|
||||||
using System.Threading;
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
namespace Ghost.RenderGraph.Concept;
|
namespace Ghost.RenderGraph.Concept;
|
||||||
|
|
||||||
@@ -28,9 +29,6 @@ public sealed class RenderGraph
|
|||||||
private readonly List<ResourceBarrier> _barriers = new(128);
|
private readonly List<ResourceBarrier> _barriers = new(128);
|
||||||
private readonly RenderGraphCompilationCache _compilationCache = new();
|
private readonly RenderGraphCompilationCache _compilationCache = new();
|
||||||
|
|
||||||
private readonly XxHash64 _hasher = new();
|
|
||||||
|
|
||||||
private int _passCount;
|
|
||||||
private bool _compiled;
|
private bool _compiled;
|
||||||
|
|
||||||
public RenderGraphBlackboard Blackboard { get; } = new();
|
public RenderGraphBlackboard Blackboard { get; } = new();
|
||||||
@@ -60,13 +58,13 @@ public sealed class RenderGraph
|
|||||||
_barriers.Clear();
|
_barriers.Clear();
|
||||||
|
|
||||||
// Return passes to the pool and reset count
|
// 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];
|
var pass = _passes[i];
|
||||||
pass.Clear();
|
pass.Reset(_objectPool);
|
||||||
_objectPool.Release(pass);
|
|
||||||
}
|
}
|
||||||
_passCount = 0;
|
|
||||||
|
_passes.Clear();
|
||||||
|
|
||||||
// Clear compiled passes list
|
// Clear compiled passes list
|
||||||
_compiledPasses.Clear();
|
_compiledPasses.Clear();
|
||||||
@@ -76,57 +74,34 @@ public sealed class RenderGraph
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Imports an external texture into the render graph.
|
/// Imports an external texture into the render graph.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RenderGraphTextureHandle ImportTexture(string name, TextureDescriptor descriptor)
|
public Identifier<RGTexture> ImportTexture(TextureDescriptor descriptor)
|
||||||
{
|
{
|
||||||
return _resources.ImportTexture(descriptor);
|
return _resources.ImportTexture(descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public IRasterRenderGraphBuilder AddRasterRenderPass<TPassData>(string name, out TPassData passData)
|
||||||
/// Adds a new render pass to the graph.
|
|
||||||
/// </summary>
|
|
||||||
public RenderGraphBuilder AddRenderPass<TPassData>(string name, out TPassData passData)
|
|
||||||
where TPassData : class, new()
|
where TPassData : class, new()
|
||||||
{
|
{
|
||||||
// Get or create pass from pool
|
var renderPass = _objectPool.Rent<RasterRenderGraphPass<TPassData>>();
|
||||||
RenderGraphPass<TPassData> pass;
|
renderPass.Init(_passes.Count, _objectPool.Rent<TPassData>(), name, RenderPassType.Raster);
|
||||||
if (_passCount < _passes.Count)
|
passData = renderPass.passData;
|
||||||
{
|
|
||||||
// Reuse existing slot
|
_passes.Add(renderPass);
|
||||||
var existingPass = _passes[_passCount];
|
|
||||||
if (existingPass is RenderGraphPass<TPassData> typedPass)
|
_builder.Init(this, renderPass, _resources);
|
||||||
{
|
return _builder;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize pass
|
public IComputeRenderGraphBuilder AddComputeRenderPass<TPassData>(string name, out TPassData passData)
|
||||||
pass.Name = name;
|
where TPassData : class, new()
|
||||||
pass.Index = _passCount;
|
{
|
||||||
|
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
|
_passes.Add(renderPass);
|
||||||
passData = _objectPool.Get<TPassData>();
|
|
||||||
pass.PassData = passData;
|
|
||||||
|
|
||||||
_passCount++;
|
_builder.Init(this, renderPass, _resources);
|
||||||
|
|
||||||
// Initialize builder
|
|
||||||
_builder.Initialize(pass, _resources);
|
|
||||||
return _builder;
|
return _builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,82 +113,84 @@ public sealed class RenderGraph
|
|||||||
private unsafe ulong ComputeGraphHash()
|
private unsafe ulong ComputeGraphHash()
|
||||||
{
|
{
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
var bufferPool = new UnsafeList<byte>(4096, scope.AllocationHandle);
|
var bufferPool = new UnsafeList<byte>(2048, scope.AllocationHandle);
|
||||||
int offset = 0;
|
|
||||||
var pData = (byte*)bufferPool.GetUnsafePtr();
|
var pData = (byte*)bufferPool.GetUnsafePtr();
|
||||||
|
var offset = 0;
|
||||||
_hasher.Reset();
|
|
||||||
|
|
||||||
// Hash pass count
|
// Hash pass count
|
||||||
_hasher.AppendInt(_passCount);
|
*(int*)(pData + offset) = _passes.Count;
|
||||||
|
offset += sizeof(int);
|
||||||
|
|
||||||
// Hash each pass structure (excluding names)
|
// 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];
|
var pass = _passes[i];
|
||||||
// Save 0.004ms.
|
|
||||||
|
|
||||||
//// Hash pass properties that affect compilation
|
*(RenderPassType*)(pData + offset) = pass.type;
|
||||||
//_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;
|
|
||||||
offset += sizeof(RenderPassType);
|
offset += sizeof(RenderPassType);
|
||||||
|
|
||||||
*(bool*)(pData + offset) = pass.AllowCulling;
|
*(bool*)(pData + offset) = pass.allowCulling;
|
||||||
offset += sizeof(bool);
|
offset += sizeof(bool);
|
||||||
|
|
||||||
*(bool*)(pData + offset) = pass.AsyncCompute;
|
*(bool*)(pData + offset) = pass.asyncCompute;
|
||||||
offset += sizeof(bool);
|
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);
|
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);
|
offset += sizeof(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
*(int*)(pData + offset) = pass.TextureWrites.Count;
|
*(int*)(pData + offset) = pass.resourceWrites.Count;
|
||||||
offset += sizeof(int);
|
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);
|
offset += sizeof(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
*(int*)(pData + offset) = pass.TextureCreates.Count;
|
*(int*)(pData + offset) = pass.resourceCreates.Count;
|
||||||
offset += sizeof(int);
|
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);
|
offset += sizeof(int);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash resource descriptors
|
// Hash resource descriptors
|
||||||
for (int i = 0; i < _resources.TextureResourceCount; i++)
|
for (var i = 0; i < _resources.TextureResourceCount; i++)
|
||||||
{
|
{
|
||||||
var resource = _resources.GetTextureResourceByIndex(i);
|
var resource = _resources.GetTextureResourceByIndex(i);
|
||||||
|
|
||||||
*(int*)(pData + offset) = resource.Descriptor.Width;
|
*(int*)(pData + offset) = resource.Descriptor.Width;
|
||||||
offset += sizeof(int);
|
offset += sizeof(int);
|
||||||
|
|
||||||
*(int*)(pData + offset) = resource.Descriptor.Height;
|
*(int*)(pData + offset) = resource.Descriptor.Height;
|
||||||
offset += sizeof(int);
|
offset += sizeof(int);
|
||||||
|
|
||||||
*(TextureFormat*)(pData + offset) = resource.Descriptor.Format;
|
*(TextureFormat*)(pData + offset) = resource.Descriptor.Format;
|
||||||
offset += sizeof(TextureFormat);
|
offset += sizeof(TextureFormat);
|
||||||
|
|
||||||
*(bool*)(pData + offset) = resource.IsImported;
|
*(bool*)(pData + offset) = resource.IsImported;
|
||||||
offset += sizeof(bool);
|
offset += sizeof(bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
var span = new Span<byte>(pData, offset);
|
var span = new Span<byte>(pData, offset);
|
||||||
_hasher.Append(span);
|
return XxHash64.HashToUInt64(span);
|
||||||
return _hasher.GetCurrentHashAsUInt64();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -222,14 +199,16 @@ public sealed class RenderGraph
|
|||||||
public void Compile()
|
public void Compile()
|
||||||
{
|
{
|
||||||
if (_compiled)
|
if (_compiled)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Step 0: Check cache
|
// Step 0: Check cache
|
||||||
ulong graphHash = ComputeGraphHash();
|
var graphHash = ComputeGraphHash(); // 1321433047288519964
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var hashTime = sw.Elapsed.TotalMicroseconds;
|
var hashTime = sw.Elapsed.TotalMicroseconds;
|
||||||
@@ -257,18 +236,18 @@ public sealed class RenderGraph
|
|||||||
_compiledPasses.Clear();
|
_compiledPasses.Clear();
|
||||||
|
|
||||||
// Step 1: Mark passes with side effects (writes to imported resources)
|
// 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];
|
var pass = _passes[i];
|
||||||
|
|
||||||
// Check if this pass writes to any imported textures
|
// 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 writeHandle = pass.resourceWrites[j];
|
||||||
var resource = _resources.GetTextureResource(writeHandle);
|
var resource = _resources.GetResource(writeHandle);
|
||||||
if (resource.IsImported)
|
if (resource.IsImported)
|
||||||
{
|
{
|
||||||
pass.HasSideEffects = true;
|
pass.hasSideEffects = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,33 +255,33 @@ public sealed class RenderGraph
|
|||||||
|
|
||||||
// Step 2: Cull passes based on dependency analysis
|
// Step 2: Cull passes based on dependency analysis
|
||||||
// Mark all passes as culled initially
|
// 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
|
// 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];
|
var pass = _passes[i];
|
||||||
if (!pass.Culled)
|
if (!pass.culled)
|
||||||
{
|
{
|
||||||
UnculDependencies(pass);
|
UnculDependencies(pass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Build final pass list (only non-culled passes)
|
// 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];
|
var pass = _passes[i];
|
||||||
if (!pass.Culled)
|
if (!pass.culled)
|
||||||
{
|
{
|
||||||
_compiledPasses.Add(pass);
|
_compiledPasses.Add(pass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Perform resource aliasing to minimize memory usage
|
// 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
|
// Step 6: Generate barriers for state transitions and aliasing
|
||||||
GenerateBarriers();
|
GenerateBarriers();
|
||||||
@@ -320,16 +299,16 @@ public sealed class RenderGraph
|
|||||||
{
|
{
|
||||||
// Restore compiled pass list
|
// Restore compiled pass list
|
||||||
_compiledPasses.Clear();
|
_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]);
|
_compiledPasses.Add(_passes[passIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore culling flags
|
// 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)
|
// Restore aliasing mappings (need to update ResourceAliasingManager)
|
||||||
@@ -337,7 +316,7 @@ public sealed class RenderGraph
|
|||||||
|
|
||||||
// Restore barriers (deep copy to avoid shared references)
|
// Restore barriers (deep copy to avoid shared references)
|
||||||
_barriers.Clear();
|
_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]);
|
_barriers.Add(cached.Barriers[i]);
|
||||||
}
|
}
|
||||||
@@ -358,22 +337,22 @@ public sealed class RenderGraph
|
|||||||
var cacheData = new CachedCompilation();
|
var cacheData = new CachedCompilation();
|
||||||
|
|
||||||
// Store compiled pass indices
|
// 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
|
// 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
|
// Store aliasing mappings
|
||||||
_aliasingManager.StoreToCache(cacheData.LogicalToPhysical, cacheData.PhysicalResources);
|
_aliasingManager.StoreToCache(cacheData.LogicalToPhysical, cacheData.PhysicalResources);
|
||||||
|
|
||||||
// Store barriers
|
// Store barriers
|
||||||
for (int i = 0; i < _barriers.Count; i++)
|
for (var i = 0; i < _barriers.Count; i++)
|
||||||
{
|
{
|
||||||
cacheData.Barriers.Add(_barriers[i]);
|
cacheData.Barriers.Add(_barriers[i]);
|
||||||
}
|
}
|
||||||
@@ -393,17 +372,17 @@ public sealed class RenderGraph
|
|||||||
private void UnculDependencies(RenderGraphPassBase pass)
|
private void UnculDependencies(RenderGraphPassBase pass)
|
||||||
{
|
{
|
||||||
// Un-cull all producers of textures we read
|
// 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 readHandle = pass.resourceReads[i];
|
||||||
var resource = _resources.GetTextureResource(readHandle);
|
var resource = _resources.GetResource(readHandle);
|
||||||
|
|
||||||
if (resource.ProducerPass >= 0)
|
if (resource.ProducerPass >= 0)
|
||||||
{
|
{
|
||||||
var producer = _passes[resource.ProducerPass];
|
var producer = _passes[resource.ProducerPass];
|
||||||
if (producer.Culled)
|
if (producer.culled)
|
||||||
{
|
{
|
||||||
producer.Culled = false;
|
producer.culled = false;
|
||||||
UnculDependencies(producer);
|
UnculDependencies(producer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,20 +425,20 @@ public sealed class RenderGraph
|
|||||||
private void InsertAliasingBarriers(RenderGraphPassBase pass, int passIdx)
|
private void InsertAliasingBarriers(RenderGraphPassBase pass, int passIdx)
|
||||||
{
|
{
|
||||||
// Check all resources written by this pass
|
// 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 id = pass.resourceWrites[i];
|
||||||
var resource = _resources.GetTextureResource(handle);
|
var resource = _resources.GetResource(id);
|
||||||
|
|
||||||
// Skip imported resources
|
// Skip imported resources
|
||||||
if (resource.IsImported)
|
if (resource.IsImported)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check if this is the first use of this logical resource
|
// 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
|
// Rent the physical resource
|
||||||
int physicalIndex = _aliasingManager.GetPhysicalResourceIndex(handle.Index);
|
var physicalIndex = _aliasingManager.GetPhysicalResourceIndex(id.Value);
|
||||||
if (physicalIndex >= 0)
|
if (physicalIndex >= 0)
|
||||||
{
|
{
|
||||||
var physical = _aliasingManager.GetPhysicalResource(physicalIndex);
|
var physical = _aliasingManager.GetPhysicalResource(physicalIndex);
|
||||||
@@ -469,23 +448,20 @@ public sealed class RenderGraph
|
|||||||
if (physical != null && physical.AliasedLogicalResources.Count > 1)
|
if (physical != null && physical.AliasedLogicalResources.Count > 1)
|
||||||
{
|
{
|
||||||
// Find the resource that used this physical memory most recently before this pass
|
// Find the resource that used this physical memory most recently before this pass
|
||||||
RenderGraphTextureHandle resourceBefore = default;
|
Identifier<RGResource> resourceBefore = default;
|
||||||
int mostRecentLastUse = -1;
|
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);
|
var otherResource = _resources.GetTextureResourceByIndex(otherLogicalIndex);
|
||||||
// Check if this resource finished before our resource starts
|
// Check if this resource finished before our resource starts
|
||||||
if (otherResource.LastUsePass < pass.Index &&
|
if (otherResource.LastUsePass < pass.index &&
|
||||||
otherResource.LastUsePass > mostRecentLastUse)
|
otherResource.LastUsePass > mostRecentLastUse)
|
||||||
{
|
{
|
||||||
mostRecentLastUse = otherResource.LastUsePass;
|
mostRecentLastUse = otherResource.LastUsePass;
|
||||||
resourceBefore = new RenderGraphTextureHandle(
|
resourceBefore = otherLogicalIndex;
|
||||||
otherLogicalIndex,
|
|
||||||
otherResource.Version,
|
|
||||||
otherResource.Descriptor.Name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,7 +471,7 @@ public sealed class RenderGraph
|
|||||||
{
|
{
|
||||||
var barrier = ResourceBarrier.CreateAliasingBarrier(
|
var barrier = ResourceBarrier.CreateAliasingBarrier(
|
||||||
resourceBefore,
|
resourceBefore,
|
||||||
handle,
|
id,
|
||||||
passIdx
|
passIdx
|
||||||
);
|
);
|
||||||
_barriers.Add(barrier);
|
_barriers.Add(barrier);
|
||||||
@@ -516,42 +492,65 @@ public sealed class RenderGraph
|
|||||||
private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx)
|
private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx)
|
||||||
{
|
{
|
||||||
// Process reads (transition to shader resource)
|
// 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);
|
InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process writes (transition to render target or UAV)
|
switch (pass.type)
|
||||||
for (var i = 0; i < pass.TextureWrites.Count; i++)
|
|
||||||
{
|
{
|
||||||
var handle = pass.TextureWrites[i];
|
case RenderPassType.Raster:
|
||||||
var targetState = ResourceState.RenderTarget; // Could be UAV for compute
|
for (var i = 0; i <= pass.maxColorIndex; i++)
|
||||||
InsertTransitionIfNeeded(handle, targetState, passIdx);
|
{
|
||||||
|
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>
|
/// <summary>
|
||||||
/// Inserts a transition barrier if the resource state changes.
|
/// Inserts a transition barrier if the resource state changes.
|
||||||
/// </summary>
|
/// </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
|
// First time seeing this resource, assume undefined
|
||||||
currentState = ResourceState.Undefined;
|
currentState = ResourceState.Common;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentState != newState)
|
if (currentState != newState)
|
||||||
{
|
{
|
||||||
var barrier = ResourceBarrier.CreateTransitionBarrier(
|
var barrier = ResourceBarrier.CreateTransitionBarrier(
|
||||||
handle,
|
resource,
|
||||||
currentState,
|
currentState,
|
||||||
newState,
|
newState,
|
||||||
passIdx
|
passIdx
|
||||||
);
|
);
|
||||||
_barriers.Add(barrier);
|
_barriers.Add(barrier);
|
||||||
_resourceStates[handle.Index] = newState;
|
_resourceStates[resource.Value] = newState;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine($" {barrier}");
|
Console.WriteLine($" {barrier}");
|
||||||
@@ -570,8 +569,8 @@ public sealed class RenderGraph
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute each non-culled pass
|
// Execute each non-culled pass
|
||||||
int barrierIndex = 0;
|
var barrierIndex = 0;
|
||||||
for (int i = 0; i < _compiledPasses.Count; i++)
|
for (var i = 0; i < _compiledPasses.Count; i++)
|
||||||
{
|
{
|
||||||
var pass = _compiledPasses[i];
|
var pass = _compiledPasses[i];
|
||||||
|
|
||||||
@@ -584,10 +583,19 @@ public sealed class RenderGraph
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!hasBarriers)
|
if (!hasBarriers)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"\n=== Barriers before Pass {i}: {pass.Name} ===");
|
Console.WriteLine($"\n=== Barriers before Pass {i}: {pass.name} ===");
|
||||||
hasBarriers = true;
|
hasBarriers = true;
|
||||||
}
|
}
|
||||||
Console.WriteLine($" {_barriers[barrierIndex]}");
|
|
||||||
|
var barrier = _barriers[barrierIndex];
|
||||||
|
if (barrier.Type == BarrierType.Transition)
|
||||||
|
{
|
||||||
|
_commandBuffer.ResourceBarrier(
|
||||||
|
barrier.Resource,
|
||||||
|
barrier.StateBefore,
|
||||||
|
barrier.StateAfter
|
||||||
|
);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
// In a real implementation, you would execute the barrier here:
|
// In a real implementation, you would execute the barrier here:
|
||||||
// ExecuteBarrier(_barriers[barrierIndex]);
|
// ExecuteBarrier(_barriers[barrierIndex]);
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ internal sealed class ResourceAliasingManager
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Build list of all logical resources with their lifetimes
|
// 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++)
|
for (int i = 0; i < registry.TextureResourceCount; i++)
|
||||||
{
|
{
|
||||||
@@ -194,7 +194,7 @@ internal sealed class ResourceAliasingManager
|
|||||||
Console.WriteLine("================================\n");
|
Console.WriteLine("================================\n");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ListPool<(int index, TextureResource resource)>.Return(logicalResources);
|
ListPool<(int index, RenderGraphResource resource)>.Return(logicalResources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetPhysicalResourceIndex(int logicalIndex)
|
public int GetPhysicalResourceIndex(int logicalIndex)
|
||||||
@@ -209,7 +209,7 @@ internal sealed class ResourceAliasingManager
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasLifetimeOverlap(PhysicalResource physical, TextureResource logical)
|
private bool HasLifetimeOverlap(PhysicalResource physical, RenderGraphResource logical)
|
||||||
{
|
{
|
||||||
// Check if the lifetimes overlap
|
// Check if the lifetimes overlap
|
||||||
// No overlap if: logical.First > physical.Last OR logical.Last < physical.First
|
// No overlap if: logical.First > physical.Last OR logical.Last < physical.First
|
||||||
@@ -227,7 +227,7 @@ internal sealed class ResourceAliasingManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
resource = _pool.Get<PhysicalResource>();
|
resource = _pool.Rent<PhysicalResource>();
|
||||||
resource.Reset();
|
resource.Reset();
|
||||||
_physicalResources.Add(resource);
|
_physicalResources.Add(resource);
|
||||||
}
|
}
|
||||||
@@ -254,7 +254,7 @@ internal sealed class ResourceAliasingManager
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < _physicalResources.Count; i++)
|
for (int i = 0; i < _physicalResources.Count; i++)
|
||||||
{
|
{
|
||||||
_pool.Release(_physicalResources[i]);
|
_pool.Return(_physicalResources[i]);
|
||||||
}
|
}
|
||||||
_physicalResources.Clear();
|
_physicalResources.Clear();
|
||||||
_physicalResourceCount = 0;
|
_physicalResourceCount = 0;
|
||||||
@@ -284,7 +284,7 @@ internal sealed class ResourceAliasingManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
physical = _pool.Get<PhysicalResource>();
|
physical = _pool.Rent<PhysicalResource>();
|
||||||
physical.Reset();
|
physical.Reset();
|
||||||
_physicalResources.Add(physical);
|
_physicalResources.Add(physical);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ghost.Core;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.RenderGraph.Concept;
|
namespace Ghost.RenderGraph.Concept;
|
||||||
@@ -9,7 +10,7 @@ namespace Ghost.RenderGraph.Concept;
|
|||||||
[Flags]
|
[Flags]
|
||||||
public enum ResourceState
|
public enum ResourceState
|
||||||
{
|
{
|
||||||
Undefined = 0,
|
Common = 0,
|
||||||
RenderTarget = 1 << 0,
|
RenderTarget = 1 << 0,
|
||||||
DepthWrite = 1 << 1,
|
DepthWrite = 1 << 1,
|
||||||
DepthRead = 1 << 2,
|
DepthRead = 1 << 2,
|
||||||
@@ -27,7 +28,6 @@ public enum BarrierType
|
|||||||
{
|
{
|
||||||
Transition, // State transition (e.g., RenderTarget -> ShaderResource)
|
Transition, // State transition (e.g., RenderTarget -> ShaderResource)
|
||||||
Aliasing, // Aliasing barrier (new resource reusing memory)
|
Aliasing, // Aliasing barrier (new resource reusing memory)
|
||||||
UAV, // UAV barrier (synchronize UAV access)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -41,71 +41,69 @@ internal struct ResourceBarrier
|
|||||||
{
|
{
|
||||||
internal struct barrier_union_transition
|
internal struct barrier_union_transition
|
||||||
{
|
{
|
||||||
public RenderGraphTextureHandle Resource;
|
public Identifier<RGResource> resource;
|
||||||
public ResourceState StateBefore;
|
public ResourceState stateBefore;
|
||||||
public ResourceState StateAfter;
|
public ResourceState stateAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal struct barrier_union_aliasing
|
internal struct barrier_union_aliasing
|
||||||
{
|
{
|
||||||
public RenderGraphTextureHandle ResourceBefore;
|
public Identifier<RGResource> resourceBefore;
|
||||||
public RenderGraphTextureHandle ResourceAfter;
|
public Identifier<RGResource> resourceAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: union can not have non-blittable types
|
// TODO: union can not have non-blittable types
|
||||||
|
|
||||||
[FieldOffset(0)]
|
[FieldOffset(0)]
|
||||||
public barrier_union_transition Transition;
|
public barrier_union_transition transition;
|
||||||
[FieldOffset(0)]
|
[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
|
// For Transition and UAV barriers
|
||||||
public RenderGraphTextureHandle Resource;
|
public readonly Identifier<RGResource> Resource => _union.transition.resource;
|
||||||
public ResourceState StateBefore;
|
public readonly ResourceState StateBefore => _union.transition.stateBefore;
|
||||||
public ResourceState StateAfter;
|
public readonly ResourceState StateAfter => _union.transition.stateAfter;
|
||||||
|
|
||||||
// For Aliasing barriers (D3D12_RESOURCE_BARRIER::Aliasing)
|
// For Aliasing barriers (D3D12_RESOURCE_BARRIER::Aliasing)
|
||||||
public RenderGraphTextureHandle ResourceBefore; // pResourceBefore
|
public readonly Identifier<RGResource> ResourceBefore => _union.aliasing.resourceBefore;
|
||||||
public RenderGraphTextureHandle ResourceAfter; // pResourceAfter
|
public readonly Identifier<RGResource> ResourceAfter => _union.aliasing.resourceAfter;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor for Aliasing barriers
|
// Constructor for Aliasing barriers
|
||||||
public static ResourceBarrier CreateAliasingBarrier(
|
public static ResourceBarrier CreateAliasingBarrier(
|
||||||
RenderGraphTextureHandle resourceBefore,
|
Identifier<RGResource> resourceBefore,
|
||||||
RenderGraphTextureHandle resourceAfter,
|
Identifier<RGResource> resourceAfter,
|
||||||
int passIndex)
|
int passIndex)
|
||||||
{
|
{
|
||||||
return new ResourceBarrier
|
return new ResourceBarrier
|
||||||
{
|
{
|
||||||
Type = BarrierType.Aliasing,
|
Type = BarrierType.Aliasing,
|
||||||
ResourceBefore = resourceBefore,
|
|
||||||
ResourceAfter = resourceAfter,
|
|
||||||
PassIndex = passIndex,
|
PassIndex = passIndex,
|
||||||
Resource = default,
|
_union = new barrier_union
|
||||||
StateBefore = ResourceState.Undefined,
|
{
|
||||||
StateAfter = ResourceState.Undefined
|
aliasing = new barrier_union.barrier_union_aliasing
|
||||||
|
{
|
||||||
|
resourceBefore = resourceBefore,
|
||||||
|
resourceAfter = resourceAfter
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceBarrier CreateTransitionBarrier(
|
public static ResourceBarrier CreateTransitionBarrier(
|
||||||
RenderGraphTextureHandle resource,
|
Identifier<RGResource> resource,
|
||||||
ResourceState before,
|
ResourceState before,
|
||||||
ResourceState after,
|
ResourceState after,
|
||||||
int passIndex)
|
int passIndex)
|
||||||
@@ -113,27 +111,18 @@ internal struct ResourceBarrier
|
|||||||
return new ResourceBarrier
|
return new ResourceBarrier
|
||||||
{
|
{
|
||||||
Type = BarrierType.Transition,
|
Type = BarrierType.Transition,
|
||||||
Resource = resource,
|
|
||||||
StateBefore = before,
|
|
||||||
StateAfter = after,
|
|
||||||
PassIndex = passIndex,
|
PassIndex = passIndex,
|
||||||
ResourceBefore = default,
|
_union = new barrier_union
|
||||||
ResourceAfter = default
|
{
|
||||||
|
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>
|
/// <summary>
|
||||||
@@ -142,13 +131,13 @@ internal struct ResourceBarrier
|
|||||||
internal sealed class ResourceStateTracker
|
internal sealed class ResourceStateTracker
|
||||||
{
|
{
|
||||||
public int ResourceIndex;
|
public int ResourceIndex;
|
||||||
public ResourceState CurrentState = ResourceState.Undefined;
|
public ResourceState CurrentState = ResourceState.Common;
|
||||||
public int LastAccessPass = -1;
|
public int LastAccessPass = -1;
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
ResourceIndex = -1;
|
ResourceIndex = -1;
|
||||||
CurrentState = ResourceState.Undefined;
|
CurrentState = ResourceState.Common;
|
||||||
LastAccessPass = -1;
|
LastAccessPass = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ namespace Ghost.RenderGraph.Concept;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class RenderGraphBlackboard
|
public sealed class RenderGraphBlackboard
|
||||||
{
|
{
|
||||||
private readonly Dictionary<Type, object> _data = new(16);
|
private readonly Dictionary<Type, IPassData> _data = new(16);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds or updates pass data in the blackboard.
|
/// Adds or updates pass data in the blackboard.
|
||||||
/// </summary>
|
/// </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);
|
var type = typeof(T);
|
||||||
_data[type] = data;
|
_data[type] = data;
|
||||||
@@ -21,20 +22,23 @@ public sealed class RenderGraphBlackboard
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves pass data from the blackboard.
|
/// Retrieves pass data from the blackboard.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T Get<T>() where T : class, IPassData
|
public T Get<T>()
|
||||||
|
where T : class, IPassData
|
||||||
{
|
{
|
||||||
var type = typeof(T);
|
var type = typeof(T);
|
||||||
if (_data.TryGetValue(type, out var obj))
|
if (_data.TryGetValue(type, out var obj))
|
||||||
{
|
{
|
||||||
return (T)obj;
|
return (T)obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new KeyNotFoundException($"Pass data of type {type.Name} not found in blackboard");
|
throw new KeyNotFoundException($"Pass data of type {type.Name} not found in blackboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to get pass data from the blackboard.
|
/// Tries to get pass data from the blackboard.
|
||||||
/// </summary>
|
/// </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);
|
var type = typeof(T);
|
||||||
if (_data.TryGetValue(type, out var obj))
|
if (_data.TryGetValue(type, out var obj))
|
||||||
@@ -42,6 +46,7 @@ public sealed class RenderGraphBlackboard
|
|||||||
data = (T)obj;
|
data = (T)obj;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = null;
|
data = null;
|
||||||
return false;
|
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;
|
namespace Ghost.RenderGraph.Concept;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mock command buffer for recording GPU commands.
|
/// Mock command buffer for recording GPU commands.
|
||||||
/// In a real implementation, this would wrap D3D12 command lists.
|
/// In a real implementation, this would wrap D3D12 command lists.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MockCommandBuffer
|
internal sealed class MockCommandBuffer
|
||||||
{
|
{
|
||||||
public void SetRenderTarget(string name)
|
public void SetRenderTarget(Identifier<RGTexture> texture)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine(nameof(SetRenderTarget) + ": " + name);
|
Console.WriteLine(nameof(SetRenderTarget) + ": " + texture);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDepthStencil(string name)
|
public void SetDepthStencil(Identifier<RGTexture> texture)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine(nameof(SetDepthStencil) + ": " + name);
|
Console.WriteLine(nameof(SetDepthStencil) + ": " + texture);
|
||||||
#endif
|
#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
|
#if DEBUG
|
||||||
Console.WriteLine(nameof(ClearRenderTarget) + ": " + name);
|
Console.WriteLine(nameof(ClearRenderTarget) + ": " + texture);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearDepth(string name, float depth)
|
public void ClearDepth(Identifier<RGTexture> texture, float depth)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine(nameof(ClearDepth) + ": " + name);
|
Console.WriteLine(nameof(ClearDepth) + ": " + texture);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,17 +43,17 @@ public sealed class MockCommandBuffer
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BindShaderResource(string name, int slot)
|
public void BindShaderResource(Identifier<RGResource> resource, int slot)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine(nameof(BindShaderResource) + ": " + name + ", slot " + slot);
|
Console.WriteLine(nameof(BindShaderResource) + ": " + resource + ", slot " + slot);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BindUnorderedAccess(string name, int slot)
|
public void BindUnorderedAccess(Identifier<RGResource> resource, int slot)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine(nameof(BindUnorderedAccess) + ": " + name + ", slot " + slot);
|
Console.WriteLine(nameof(BindUnorderedAccess) + ": " + resource + ", slot " + slot);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,14 +64,14 @@ public sealed class MockCommandBuffer
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResourceBarrier(string resourceName, string stateBefore, string stateAfter)
|
public void ResourceBarrier(Identifier<RGResource> resource, ResourceState stateBefore, ResourceState stateAfter)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine(nameof(ResourceBarrier) + ": " + resourceName + " from " + stateBefore + " to " + stateAfter);
|
Console.WriteLine(nameof(ResourceBarrier) + ": " + resource + " from " + stateBefore + " to " + stateAfter);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AliasBarrier(string resourceBefore, string resourceAfter)
|
public void AliasBarrier(Identifier<RGResource> resourceBefore, Identifier<RGResource> resourceAfter)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine(nameof(AliasBarrier) + ": " + resourceBefore + " to " + resourceAfter);
|
Console.WriteLine(nameof(AliasBarrier) + ": " + resourceBefore + " to " + resourceAfter);
|
||||||
@@ -85,18 +87,18 @@ public readonly struct RasterRenderContext
|
|||||||
{
|
{
|
||||||
private readonly MockCommandBuffer _cmd;
|
private readonly MockCommandBuffer _cmd;
|
||||||
|
|
||||||
public RasterRenderContext(MockCommandBuffer cmd)
|
internal RasterRenderContext(MockCommandBuffer cmd)
|
||||||
{
|
{
|
||||||
_cmd = cmd;
|
_cmd = cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose command buffer methods directly
|
// Expose command buffer methods directly
|
||||||
public void SetRenderTarget(string name) => _cmd.SetRenderTarget(name);
|
public void SetRenderTarget(Identifier<RGTexture> texture) => _cmd.SetRenderTarget(texture);
|
||||||
public void SetDepthStencil(string name) => _cmd.SetDepthStencil(name);
|
public void SetDepthStencil(Identifier<RGTexture> texture) => _cmd.SetDepthStencil(texture);
|
||||||
public void ClearRenderTarget(string name, float r, float g, float b, float a) => _cmd.ClearRenderTarget(name, r, g, b, a);
|
public void ClearRenderTarget(Identifier<RGTexture> texture, float r, float g, float b, float a) => _cmd.ClearRenderTarget(texture, r, g, b, a);
|
||||||
public void ClearDepth(string name, float depth) => _cmd.ClearDepth(name, depth);
|
public void ClearDepth(Identifier<RGTexture> texture, float depth) => _cmd.ClearDepth(texture, depth);
|
||||||
public void Draw(int vertexCount) => _cmd.Draw(vertexCount);
|
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>
|
/// <summary>
|
||||||
@@ -107,14 +109,14 @@ public readonly struct ComputeRenderContext
|
|||||||
{
|
{
|
||||||
private readonly MockCommandBuffer _cmd;
|
private readonly MockCommandBuffer _cmd;
|
||||||
|
|
||||||
public ComputeRenderContext(MockCommandBuffer cmd)
|
internal ComputeRenderContext(MockCommandBuffer cmd)
|
||||||
{
|
{
|
||||||
_cmd = cmd;
|
_cmd = cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose command buffer methods directly
|
// Expose command buffer methods directly
|
||||||
public void BindShaderResource(string name, int slot) => _cmd.BindShaderResource(name, slot);
|
public void BindShaderResource(Identifier<RGResource> resource, int slot) => _cmd.BindShaderResource(resource, slot);
|
||||||
public void BindUnorderedAccess(string name, int slot) => _cmd.BindUnorderedAccess(name, 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);
|
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;
|
namespace Ghost.RenderGraph.Concept;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -15,201 +18,112 @@ public enum RenderPassType : byte
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal abstract class RenderGraphPassBase
|
internal abstract class RenderGraphPassBase
|
||||||
{
|
{
|
||||||
public string Name = string.Empty;
|
public string name = string.Empty;
|
||||||
public int Index;
|
public int index;
|
||||||
public RenderPassType Type;
|
public RenderPassType type;
|
||||||
public bool AllowCulling = true;
|
public bool allowCulling = true;
|
||||||
public bool AsyncCompute;
|
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
|
// Resource dependencies
|
||||||
public readonly List<RenderGraphTextureHandle> TextureReads = new(8);
|
public readonly List<Identifier<RGResource>> resourceReads = new(8);
|
||||||
public readonly List<RenderGraphTextureHandle> TextureWrites = new(4);
|
public readonly List<Identifier<RGResource>> resourceWrites = new(4);
|
||||||
public readonly List<RenderGraphTextureHandle> TextureCreates = new(4);
|
public readonly List<Identifier<RGResource>> resourceCreates = new(4);
|
||||||
|
|
||||||
// Execution state
|
// Execution state
|
||||||
public bool Culled;
|
public bool culled;
|
||||||
public bool HasSideEffects;
|
public bool hasSideEffects;
|
||||||
|
|
||||||
public abstract void Execute(RenderContext context);
|
public abstract void Execute(RenderContext context);
|
||||||
public abstract void Clear();
|
public abstract void Clear();
|
||||||
|
public abstract bool HasRenderFunc();
|
||||||
|
|
||||||
public virtual void Reset()
|
public virtual void Reset(RenderGraphObjectPool pool)
|
||||||
{
|
{
|
||||||
Name = string.Empty;
|
name = string.Empty;
|
||||||
Index = -1;
|
index = -1;
|
||||||
Type = RenderPassType.Raster;
|
type = RenderPassType.Raster;
|
||||||
AllowCulling = true;
|
allowCulling = true;
|
||||||
AsyncCompute = false;
|
asyncCompute = false;
|
||||||
TextureReads.Clear();
|
|
||||||
TextureWrites.Clear();
|
depthAccess = default;
|
||||||
TextureCreates.Clear();
|
colorAccess.AsSpan().Clear();
|
||||||
Culled = false;
|
maxColorIndex = -1;
|
||||||
HasSideEffects = false;
|
|
||||||
|
randomAccess.Clear();
|
||||||
|
|
||||||
|
resourceReads.Clear();
|
||||||
|
resourceWrites.Clear();
|
||||||
|
resourceCreates.Clear();
|
||||||
|
culled = false;
|
||||||
|
hasSideEffects = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal abstract class RenderGraphPassT<TPassData, TRenderContext> : RenderGraphPassBase
|
||||||
/// Typed render pass with user data.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class RenderGraphPass<TPassData> : RenderGraphPassBase
|
|
||||||
where TPassData : class, new()
|
where TPassData : class, new()
|
||||||
{
|
{
|
||||||
public TPassData? PassData;
|
public TPassData passData = null!;
|
||||||
public Action<TPassData, RasterRenderContext>? RasterRenderFunc;
|
public Action<TPassData, TRenderContext>? renderFunc;
|
||||||
public Action<TPassData, ComputeRenderContext>? ComputeRenderFunc;
|
|
||||||
|
|
||||||
public override void Execute(RenderContext context)
|
public void Init(int index, TPassData passData, string name, RenderPassType type)
|
||||||
{
|
{
|
||||||
if (PassData == null)
|
this.index = index;
|
||||||
return;
|
this.passData = passData;
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
if (Type == RenderPassType.Raster && RasterRenderFunc != null)
|
public sealed override bool HasRenderFunc()
|
||||||
{
|
{
|
||||||
RasterRenderFunc(PassData, context.RasterContext);
|
return renderFunc != null;
|
||||||
}
|
|
||||||
else if (Type == RenderPassType.Compute && ComputeRenderFunc != null)
|
|
||||||
{
|
|
||||||
ComputeRenderFunc(PassData, context.ComputeContext);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Clear()
|
public override void Clear()
|
||||||
{
|
{
|
||||||
PassData = null;
|
passData = null!;
|
||||||
RasterRenderFunc = null;
|
renderFunc = null;
|
||||||
ComputeRenderFunc = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Reset()
|
public override void Reset(RenderGraphObjectPool pool)
|
||||||
{
|
{
|
||||||
base.Reset();
|
base.Reset(pool);
|
||||||
|
pool.Return(passData);
|
||||||
Clear();
|
Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal sealed class RasterRenderGraphPass<TPassData> : RenderGraphPassT<TPassData, RasterRenderContext>
|
||||||
/// Builder for constructing render passes.
|
where TPassData : class, new()
|
||||||
/// Implements IDisposable for using() pattern.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class RenderGraphBuilder : IDisposable
|
|
||||||
{
|
{
|
||||||
private RenderGraphPassBase? _pass;
|
public override void Execute(RenderContext context)
|
||||||
private RenderGraphResourceRegistry? _resources;
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
internal void Initialize(RenderGraphPassBase pass, RenderGraphResourceRegistry resources)
|
|
||||||
{
|
{
|
||||||
_pass = pass;
|
renderFunc!(passData, context.RasterContext);
|
||||||
_resources = resources;
|
|
||||||
_disposed = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public override void Reset(RenderGraphObjectPool pool)
|
||||||
/// Creates a new transient texture that only lives for this pass.
|
|
||||||
/// </summary>
|
|
||||||
public RenderGraphTextureHandle CreateTexture(TextureDescriptor descriptor)
|
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
base.Reset(pool);
|
||||||
var handle = _resources!.CreateTexture(descriptor);
|
pool.Return(this);
|
||||||
_pass!.TextureCreates.Add(handle);
|
}
|
||||||
_resources.SetProducer(handle, _pass.Index);
|
}
|
||||||
return handle;
|
|
||||||
}
|
internal sealed class ComputeRenderGraphPass<TPassData> : RenderGraphPassT<TPassData, ComputeRenderContext>
|
||||||
|
where TPassData : class, new()
|
||||||
/// <summary>
|
{
|
||||||
/// Marks a texture as being read by this pass.
|
public override void Execute(RenderContext context)
|
||||||
/// </summary>
|
{
|
||||||
public RenderGraphTextureHandle ReadTexture(RenderGraphTextureHandle handle)
|
renderFunc!(passData, context.ComputeContext);
|
||||||
{
|
}
|
||||||
ThrowIfDisposed();
|
|
||||||
_pass!.TextureReads.Add(handle);
|
public override void Reset(RenderGraphObjectPool pool)
|
||||||
_resources!.AddConsumer(handle, _pass.Index);
|
{
|
||||||
return handle;
|
base.Reset(pool);
|
||||||
}
|
pool.Return(this);
|
||||||
|
|
||||||
/// <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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Misaki.HighPerformance.Buffer;
|
||||||
|
|
||||||
namespace Ghost.RenderGraph.Concept;
|
namespace Ghost.RenderGraph.Concept;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -6,44 +9,76 @@ namespace Ghost.RenderGraph.Concept;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class RenderGraphObjectPool
|
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);
|
public SharedObjectPoolBase() { }
|
||||||
if (_pools.TryGetValue(type, out var pool) && pool.Count > 0)
|
public virtual void Clear() { }
|
||||||
{
|
|
||||||
return (T)pool.Pop();
|
|
||||||
}
|
|
||||||
return new T();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
private static ObjectPool<T> AllocatePool()
|
||||||
if (!_pools.TryGetValue(type, out var pool))
|
|
||||||
{
|
{
|
||||||
pool = new Stack<object>(16);
|
var newPool = new ObjectPool<T>(() => new T());
|
||||||
_pools[type] = pool;
|
// 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()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_pools.Clear();
|
for (var i = 0; i < s_allocatedPools.Count; i++)
|
||||||
|
{
|
||||||
|
s_allocatedPools[i].Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a texture resource in the render graph.
|
/// Represents a texture resource in the render graph.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class TextureResource
|
internal sealed class RenderGraphResource
|
||||||
{
|
{
|
||||||
|
public RenderGraphResourceType type;
|
||||||
public int Index;
|
public int Index;
|
||||||
public int Version;
|
|
||||||
public TextureDescriptor Descriptor;
|
public TextureDescriptor Descriptor;
|
||||||
public bool IsImported;
|
public bool IsImported;
|
||||||
public int FirstUsePass = -1;
|
public int FirstUsePass = -1;
|
||||||
@@ -55,7 +90,6 @@ internal sealed class TextureResource
|
|||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
Index = -1;
|
Index = -1;
|
||||||
Version = 0;
|
|
||||||
Descriptor = default;
|
Descriptor = default;
|
||||||
IsImported = false;
|
IsImported = false;
|
||||||
FirstUsePass = -1;
|
FirstUsePass = -1;
|
||||||
@@ -72,7 +106,7 @@ internal sealed class TextureResource
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class RenderGraphResourceRegistry
|
internal sealed class RenderGraphResourceRegistry
|
||||||
{
|
{
|
||||||
private readonly List<TextureResource> _textureResources = new(64);
|
private readonly List<RenderGraphResource> _resources = new(64);
|
||||||
private readonly RenderGraphObjectPool _pool = new();
|
private readonly RenderGraphObjectPool _pool = new();
|
||||||
private int _textureResourceCount;
|
private int _textureResourceCount;
|
||||||
|
|
||||||
@@ -85,76 +119,78 @@ internal sealed class RenderGraphResourceRegistry
|
|||||||
_textureResourceCount = 0;
|
_textureResourceCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RenderGraphTextureHandle ImportTexture(TextureDescriptor descriptor)
|
public Identifier<RGTexture> ImportTexture(TextureDescriptor descriptor)
|
||||||
{
|
{
|
||||||
var resource = GetOrCreateTextureResource();
|
var resource = GetOrCreateTextureResource();
|
||||||
resource.Index = _textureResourceCount - 1;
|
resource.Index = _textureResourceCount - 1;
|
||||||
resource.Version = 0;
|
|
||||||
resource.Descriptor = descriptor;
|
resource.Descriptor = descriptor;
|
||||||
resource.IsImported = true;
|
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();
|
var resource = GetOrCreateTextureResource();
|
||||||
resource.Index = _textureResourceCount - 1;
|
resource.Index = _textureResourceCount - 1;
|
||||||
resource.Version = 0;
|
|
||||||
resource.Descriptor = descriptor;
|
resource.Descriptor = descriptor;
|
||||||
resource.IsImported = false;
|
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)
|
if (resource.Value < 0 || resource.Value >= _textureResourceCount)
|
||||||
throw new ArgumentException($"Invalid texture handle: {handle.Index}");
|
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)
|
if (index < 0 || index >= _textureResourceCount)
|
||||||
throw new ArgumentException($"Invalid texture index: {index}");
|
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;
|
resource.ProducerPass = passIndex;
|
||||||
if (resource.FirstUsePass < 0)
|
if (resource.FirstUsePass < 0)
|
||||||
|
{
|
||||||
resource.FirstUsePass = passIndex;
|
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.ConsumerPasses.Add(passIndex);
|
||||||
resource.LastUsePass = passIndex;
|
resource.LastUsePass = passIndex;
|
||||||
if (resource.FirstUsePass < 0)
|
if (resource.FirstUsePass < 0)
|
||||||
|
{
|
||||||
resource.FirstUsePass = passIndex;
|
resource.FirstUsePass = passIndex;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private TextureResource GetOrCreateTextureResource()
|
private RenderGraphResource GetOrCreateTextureResource()
|
||||||
{
|
{
|
||||||
TextureResource resource;
|
RenderGraphResource resource;
|
||||||
if (_textureResourceCount < _textureResources.Count)
|
if (_textureResourceCount < _resources.Count)
|
||||||
{
|
{
|
||||||
// Reuse existing slot
|
// Reuse existing slot
|
||||||
resource = _textureResources[_textureResourceCount];
|
resource = _resources[_textureResourceCount];
|
||||||
resource.Reset();
|
resource.Reset();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Need to grow the list
|
// Need to grow the list
|
||||||
resource = _pool.Get<TextureResource>();
|
resource = _pool.Rent<RenderGraphResource>();
|
||||||
resource.Reset();
|
resource.Reset();
|
||||||
_textureResources.Add(resource);
|
_resources.Add(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
_textureResourceCount++;
|
_textureResourceCount++;
|
||||||
@@ -163,11 +199,11 @@ internal sealed class RenderGraphResourceRegistry
|
|||||||
|
|
||||||
public void Clear()
|
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;
|
_textureResourceCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,57 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.RenderGraph.Concept;
|
namespace Ghost.RenderGraph.Concept;
|
||||||
|
|
||||||
/// <summary>
|
internal enum RenderGraphResourceType
|
||||||
/// Opaque handle to a render graph texture resource.
|
|
||||||
/// </summary>
|
|
||||||
public readonly struct RenderGraphTextureHandle : IEquatable<RenderGraphTextureHandle>
|
|
||||||
{
|
{
|
||||||
public readonly int Index;
|
Texture,
|
||||||
public readonly int Version;
|
Buffer,
|
||||||
internal readonly string InternalName;
|
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;
|
return new Identifier<RGResource>(texture.Value);
|
||||||
Version = version;
|
|
||||||
InternalName = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public override readonly bool Equals(object? obj) => obj is RenderGraphTextureHandle other && Equals(other);
|
internal static Identifier<RGBuffer> AsBuffer(this Identifier<RGResource> resource)
|
||||||
public override readonly int GetHashCode() => HashCode.Combine(Index, Version);
|
{
|
||||||
public static bool operator ==(RenderGraphTextureHandle left, RenderGraphTextureHandle right) => left.Equals(right);
|
return new Identifier<RGBuffer>(resource.Value);
|
||||||
public static bool operator !=(RenderGraphTextureHandle left, RenderGraphTextureHandle right) => !left.Equals(right);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user