namespace Ghost.RenderGraph.Concept; /// /// Represents different types of render passes. /// public enum RenderPassType : byte { Raster, Compute } /// /// Base class for render passes. /// Uses pooling to avoid allocations after the first frame. /// internal abstract class RenderGraphPassBase { public string Name = string.Empty; public int Index; public RenderPassType Type; public bool AllowCulling = true; public bool AsyncCompute; // Resource dependencies public readonly List TextureReads = new(8); public readonly List TextureWrites = new(4); public readonly List TextureCreates = new(4); // Execution state public bool Culled; public bool HasSideEffects; public abstract void Execute(RenderContext context); public abstract void Clear(); public virtual void Reset() { Name = string.Empty; Index = -1; Type = RenderPassType.Raster; AllowCulling = true; AsyncCompute = false; TextureReads.Clear(); TextureWrites.Clear(); TextureCreates.Clear(); Culled = false; HasSideEffects = false; } } /// /// Typed render pass with user data. /// internal sealed class RenderGraphPass : RenderGraphPassBase where TPassData : class, new() { public TPassData? PassData; public Action? RasterRenderFunc; public Action? ComputeRenderFunc; public override void Execute(RenderContext context) { if (PassData == null) return; if (Type == RenderPassType.Raster && RasterRenderFunc != null) { RasterRenderFunc(PassData, context.RasterContext); } else if (Type == RenderPassType.Compute && ComputeRenderFunc != null) { ComputeRenderFunc(PassData, context.ComputeContext); } } public override void Clear() { PassData = null; RasterRenderFunc = null; ComputeRenderFunc = null; } public override void Reset() { base.Reset(); Clear(); } } /// /// Builder for constructing render passes. /// Implements IDisposable for using() pattern. /// public sealed class RenderGraphBuilder : IDisposable { private RenderGraphPassBase? _pass; private RenderGraphResourceRegistry? _resources; private bool _disposed; internal void Initialize(RenderGraphPassBase pass, RenderGraphResourceRegistry resources) { _pass = pass; _resources = resources; _disposed = false; } /// /// Creates a new transient texture that only lives for this pass. /// public RenderGraphTextureHandle CreateTexture(TextureDescriptor descriptor) { ThrowIfDisposed(); var handle = _resources!.CreateTexture(descriptor); _pass!.TextureCreates.Add(handle); _resources.SetProducer(handle, _pass.Index); return handle; } /// /// Marks a texture as being read by this pass. /// public RenderGraphTextureHandle ReadTexture(RenderGraphTextureHandle handle) { ThrowIfDisposed(); _pass!.TextureReads.Add(handle); _resources!.AddConsumer(handle, _pass.Index); return handle; } /// /// Marks a texture as being written by this pass. /// public RenderGraphTextureHandle WriteTexture(RenderGraphTextureHandle handle) { ThrowIfDisposed(); _pass!.TextureWrites.Add(handle); _resources!.SetProducer(handle, _pass.Index); return handle; } /// /// Sets up a depth buffer for this pass. /// public RenderGraphTextureHandle UseDepthBuffer(RenderGraphTextureHandle handle, bool writeAccess) { ThrowIfDisposed(); if (writeAccess) { _pass!.TextureWrites.Add(handle); _resources!.SetProducer(handle, _pass.Index); } else { _pass!.TextureReads.Add(handle); _resources!.AddConsumer(handle, _pass.Index); } return handle; } /// /// Sets the render function for a raster pass. /// public void SetRenderFunc(Action renderFunc) where TPassData : class, new() { ThrowIfDisposed(); if (_pass is RenderGraphPass typedPass) { typedPass.RasterRenderFunc = renderFunc; typedPass.Type = RenderPassType.Raster; } } /// /// Sets the compute function for a compute pass. /// public void SetComputeFunc(Action computeFunc, bool asyncCompute = false) where TPassData : class, new() { ThrowIfDisposed(); if (_pass is RenderGraphPass typedPass) { typedPass.ComputeRenderFunc = computeFunc; typedPass.Type = RenderPassType.Compute; typedPass.AsyncCompute = asyncCompute; } } /// /// Controls whether this pass can be culled if its outputs are unused. /// public void SetAllowCulling(bool allow) { ThrowIfDisposed(); _pass!.AllowCulling = allow; } public void Dispose() { if (!_disposed) { _disposed = true; _pass = null; _resources = null; } } private void ThrowIfDisposed() { if (_disposed || _pass == null) throw new ObjectDisposedException(nameof(RenderGraphBuilder)); } }