- Major architectural refactor for performance, extensibility, and feature completeness: resource pooling, pass culling, aliasing, and compilation caching. - Introduces type-safe builder and context APIs, blackboard pattern, and unified resource management. - Adds detailed documentation and cleans up obsolete files and APIs. - Includes (commented) Unity Render Graph source for reference; not compiled, for parity and future extension.
GhostEngine Render Graph
A high-performance, transient render graph implementation for GhostEngine, inspired by Unity, Unreal, and other AAA engines.
Features
✅ Transient Architecture - Graph rebuilds every frame for maximum flexibility
✅ Minimal GC - Only ~571 bytes allocated per frame (after warmup)
✅ Automatic Pass Culling - Unused passes are automatically removed
✅ Resource Tracking - Automatic resource lifetime management
✅ Blackboard Pattern - Share data between passes easily
✅ Async Compute Support - Mark passes for async execution
✅ Type-Safe API - Strongly-typed pass data
Performance
Per iteration time: 2,292 ns (~2.3 microseconds)
GC allocated: 571 bytes per iteration (after warmup)
Tested with a complex graph containing:
- 13 render passes (GBuffer, Lighting, SSAO, Bloom, TAA, Post-processing)
- 15+ transient textures
- Multiple read/write dependencies
- Blackboard data sharing
- Pass culling optimization
Quick Start
var renderGraph = new RenderGraph();
// Each frame:
renderGraph.Reset();
// Import external resources
var backbuffer = renderGraph.ImportTexture("Backbuffer",
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "Backbuffer"));
// Add a pass
GBufferData gbufferData;
using (var builder = renderGraph.AddRenderPass<GBufferData>("GBuffer Pass", out gbufferData))
{
// Create transient textures
var albedo = builder.CreateTexture(
new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "GBuffer.Albedo"));
// Mark resource usage
gbufferData.Albedo = builder.WriteTexture(albedo);
// Set the render function
builder.SetRenderFunc<GBufferData>((data, cmd) =>
{
cmd.SetRenderTarget(data.Albedo.Name);
cmd.Draw(36000);
});
}
// Share data with other passes
renderGraph.Blackboard.Add(gbufferData);
// Read from blackboard in another pass
using (var builder = renderGraph.AddRenderPass<LightingData>("Lighting", out var lightingData))
{
var gbuffer = renderGraph.Blackboard.Get<GBufferData>();
lightingData.Albedo = builder.ReadTexture(gbuffer.Albedo);
// ... rest of pass setup
}
// Compile and execute
renderGraph.Compile();
renderGraph.Execute();
API Reference
RenderGraph
void Reset()
Clears the graph for a new frame. Reuses allocations to minimize GC.
RenderGraphTextureHandle ImportTexture(string name, TextureDescriptor desc)
Imports an external texture (like the backbuffer) into the graph.
RenderGraphBuilder AddRenderPass<TPassData>(string name, out TPassData data)
Adds a new render pass. Returns a builder for configuring the pass.
void Compile()
Analyzes dependencies and culls unused passes.
void Execute()
Executes all compiled (non-culled) passes.
RenderGraphBlackboard Blackboard { get; }
Access the blackboard for sharing data between passes.
RenderGraphBuilder
RenderGraphTextureHandle CreateTexture(TextureDescriptor desc)
Creates a transient texture that only lives during this pass.
RenderGraphTextureHandle ReadTexture(RenderGraphTextureHandle handle)
Marks a texture as read by this pass.
RenderGraphTextureHandle WriteTexture(RenderGraphTextureHandle handle)
Marks a texture as written by this pass.
RenderGraphTextureHandle UseDepthBuffer(RenderGraphTextureHandle handle, bool writeAccess)
Sets up depth buffer usage for this pass.
void SetRenderFunc<TPassData>(Action<TPassData, RasterRenderContext> func)
Sets the raster render function for this pass.
void SetComputeFunc<TPassData>(Action<TPassData, ComputeRenderContext> func, bool asyncCompute = false)
Sets the compute function for this pass. Optionally mark as async.
void SetAllowCulling(bool allow)
Controls whether this pass can be culled if its outputs are unused.
RenderGraphBlackboard
void Add<T>(T data) where T : class, IPassData
Stores pass data in the blackboard.
T Get<T>() where T : class, IPassData
Retrieves pass data from the blackboard.
bool TryGet<T>(out T data) where T : class, IPassData
Attempts to retrieve pass data from the blackboard.
Architecture
The render graph uses several key patterns to achieve minimal GC:
- Object Pooling - All passes and data structures are pooled and reused
- Collection Reuse - Lists are cleared instead of reallocated
- Pre-allocation - Capacity is pre-allocated based on expected usage
- Avoid LINQ - Explicit loops instead of LINQ for zero allocation
- Struct Handles - Resource handles are lightweight value types
Pass Culling
The graph automatically removes unused passes:
- Passes that write to imported resources have side effects (never culled)
- All other passes are initially marked as culled
- Dependencies of non-culled passes are recursively un-culled
- Only passes contributing to the final output remain
This means you can freely add debug/visualization passes - they'll be automatically removed if unused.
Building
dotnet build Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj -c Release
Running Tests
dotnet run --project Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj -c Release
This runs 500 warmup iterations, then measures 500 more to determine average performance.
Implementation Notes
See IMPLEMENTATION_NOTES.md for detailed architecture documentation.
Limitations
- Single-threaded - Not thread-safe, designed for render thread only
- No GPU resource pooling - Currently uses mock command buffers
- No render pass merging - Compatible passes could be merged for better performance on tile-based GPUs
- No resource aliasing - Could reuse memory for non-overlapping resource lifetimes
Future Enhancements
- Resource aliasing for memory efficiency
- Native render pass merging (for tile-based GPUs)
- GPU resource pooling
- Async/await support in render functions
- Memory budgeting and OOM protection
- Debug visualization (like Unity's Render Graph Viewer)
- Multi-threaded pass recording
- Graph caching across similar frames
License
Part of GhostEngine. See repository root for license information.