using Ghost.Core; using Ghost.Graphics.RHI; using System.Diagnostics; namespace Ghost.Graphics.RenderGraphModule; [Flags] public enum AccessFlags : byte { None = 0, Read = 1 << 0, Write = 1 << 1, Discard = 1 << 2, WriteAll = Write | Discard, ReadWrite = Read | Write, } public interface IRenderGraphBuilder : IDisposable { /// /// Enables or disables pass culling for the current context. /// /// A value indicating whether pass culling is allowed. void AllowPassCulling(bool value); /// /// Creates a new texture resource based on the specified desc. /// /// A structure that defines the properties and configuration of the texture to create. /// The name of the texture resource. /// An identifier for the newly created texture resource. Identifier CreateTexture(in RGTextureDesc desc, string name); /// /// Creates a new buffer resource based on the specified desc. /// /// A structure that defines the properties and configuration of the buffer to create. /// The name of the buffer resource. /// An identifier for the newly created buffer resource. Identifier CreateBuffer(in BufferDesc desc, string name); /// /// Registers the specified texture for use in the current render graph pass with the given access mode. /// /// The identifier of the texture to be used in the render graph pass. /// The access mode specifying how the texture will be read or written during the pass. /// An identifier for the texture. Identifier UseTexture(Identifier texture, AccessFlags accessMode); /// /// Registers the specified buffer for use in the current render graph pass with the given access mode. /// /// The identifier of the buffer to be used in the render graph pass. /// The access mode specifying how the buffer will be read or written during the pass. /// Optional hint about how the buffer will be used (e.g., IndirectArgument). Default is None (ByteAddressBuffer SRV). /// An identifier for the buffer. Identifier UseBuffer(Identifier buffer, AccessFlags accessMode, BufferHint hint = BufferHint.None); } public interface IRasterRenderGraphBuilder : IRenderGraphBuilder { /// /// Binds a texture for random access operations within the current rendering pass. /// /// The identifier of the texture to be used for random access. /// An identifier for the texture. Identifier UseRandomAccessTexture(Identifier texture); /// /// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context. /// /// An identifier for the buffer to be used for random access. Must reference a valid buffer resource. /// An identifier for the buffer. Identifier UseRandomAccessBuffer(Identifier buffer); /// /// Sets the color attachment at the specified index to the given texture. /// /// The identifier of the texture to use as the color attachment. /// The zero-based index of the color attachment to set. /// Access flags. Default is Write (assumes partial update). Use WriteAll for fullscreen passes. void SetColorAttachment(Identifier texture, int index, AccessFlags flags = AccessFlags.Write); /// /// Sets the depth attachment for the current render pass using the specified texture. /// /// The identifier of the texture to use as the depth attachment. Cannot be null. /// Access flags. Default is ReadWrite (assumes partial update). Use WriteAll for fullscreen passes. void SetDepthAttachment(Identifier texture, AccessFlags flags = AccessFlags.ReadWrite); /// /// Sets the function used to render a pass with the specified pass data and render context. /// /// The type of data associated with the render pass. /// The delegate that defines the rendering logic for the pass. void SetRenderFunc(Action renderFunc) where TPassData : class, new(); } public interface IComputeRenderGraphBuilder : IRenderGraphBuilder { /// /// Enables or disables asynchronous compute operations. /// /// true to enable asynchronous compute; otherwise, false. void EnableAsyncCompute(bool value); /// /// Sets the render function to be invoked during the compute rendering process. /// /// The type of the data object passed to the render function. /// The delegate that defines the rendering logic to execute. void SetRenderFunc(Action renderFunc) where TPassData : class, new(); } public interface IUnsafeRenderGraphBuilder : IRenderGraphBuilder { /// /// Binds a texture for random access operations within the current rendering pass. /// /// The identifier of the texture to be used for random access. /// An identifier for the texture. Identifier UseRandomAccessTexture(Identifier texture); /// /// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context. /// /// An identifier for the buffer to be used for random access. Must reference a valid buffer resource. /// An identifier for the buffer. Identifier UseRandomAccessBuffer(Identifier buffer); /// /// Sets the function used to render a pass with the specified pass data and render context. /// /// The type of data associated with the render pass. /// The delegate that defines the rendering logic for the pass. void SetRenderFunc(Action renderFunc) where TPassData : class, new(); } internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGraphBuilder, IUnsafeRenderGraphBuilder { private RenderGraph _graph = null!; private RenderGraphPassBase _pass = null!; private RenderGraphResourceRegistry _resources = null!; private bool _disposed; internal void Init(RenderGraph graph, RenderGraphPassBase pass, RenderGraphResourceRegistry resources) { _graph = graph; _pass = pass; _resources = resources; _disposed = false; } private void ThrowIfDisposed() { ObjectDisposedException.ThrowIf(_disposed, this); } private Identifier UseResource(Identifier resource, AccessFlags accessFlags, RenderGraphResourceType type) { if (accessFlags.HasFlag(AccessFlags.Read)) { _pass.resourceReads[(int)type].Add(resource); _resources.AddConsumer(resource, _pass.index); } if (accessFlags.HasFlag(AccessFlags.Write)) { _pass.resourceWrites[(int)type].Add(resource); _resources.SetProducer(resource, _pass.index); } return resource; } public void AllowPassCulling(bool value) { _pass.allowCulling = value; } public void EnableAsyncCompute(bool value) { _pass.asyncCompute = value; } public Identifier CreateTexture(in RGTextureDesc desc, string name) { ThrowIfDisposed(); var handle = _resources.CreateTexture(in desc, name); _pass.resourceCreates[(int)RenderGraphResourceType.Texture].Add(handle.AsResource()); _resources.SetProducer(handle.AsResource(), _pass.index); return handle; } public Identifier CreateBuffer(in BufferDesc desc, string name) { ThrowIfDisposed(); var handle = _resources.CreateBuffer(in desc, name); _pass.resourceCreates[(int)RenderGraphResourceType.Buffer].Add(handle.AsResource()); _resources.SetProducer(handle.AsResource(), _pass.index); return handle; } public Identifier UseTexture(Identifier texture, AccessFlags flags) { ThrowIfDisposed(); return UseResource(texture.AsResource(), flags, RenderGraphResourceType.Texture).AsTexture(); } public Identifier UseBuffer(Identifier buffer, AccessFlags flags, BufferHint hint = BufferHint.None) { ThrowIfDisposed(); // Store buffer hint if not None if (hint != BufferHint.None) { _pass.bufferHints[buffer.AsResource().Value] = hint; } return UseResource(buffer.AsResource(), flags, RenderGraphResourceType.Buffer).AsBuffer(); } public Identifier UseRandomAccessTexture(Identifier texture) { ThrowIfDisposed(); var resource = texture.AsResource(); UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Texture); _pass.randomAccess.Add(resource); return texture; } public Identifier UseRandomAccessBuffer(Identifier buffer) { ThrowIfDisposed(); var resource = buffer.AsResource(); UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Buffer); _pass.randomAccess.Add(resource); return buffer; } public void SetColorAttachment(Identifier texture, int index, AccessFlags flags = AccessFlags.Write) { ThrowIfDisposed(); Debug.Assert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range."); var id = UseTexture(texture, flags); if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid) { _pass.maxColorIndex = Math.Max(_pass.maxColorIndex, index); _pass.colorAccess[index] = new TextureAccess(id, flags); } else { throw new InvalidOperationException($"Color attachment at index {index} is already set to a different texture."); } } public void SetDepthAttachment(Identifier texture, AccessFlags flags = AccessFlags.Write) { ThrowIfDisposed(); var id = UseTexture(texture, flags); if (_pass.depthAccess.id == id || _pass.depthAccess.id.IsInvalid) { _pass.depthAccess = new TextureAccess(id, flags); } else { throw new InvalidOperationException("Depth attachment is already set to a different texture."); } } public void SetRenderFunc(Action renderFunc) where TPassData : class, new() { ((RasterRenderGraphPass)_pass).renderFunc = renderFunc; } public void SetRenderFunc(Action renderFunc) where TPassData : class, new() { ((ComputeRenderGraphPass)_pass).renderFunc = renderFunc; } public void SetRenderFunc(Action renderFunc) where TPassData : class, new() { ((UnsafeRenderGraphPass)_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; } }