using Ghost.Core; using Ghost.Graphics.RHI; using System.Diagnostics; using System.Runtime.CompilerServices; 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, } [Flags] public enum ResourceExtractionFlags : byte { None = 0, /// /// Releases the old heap after extraction. /// ReleaseAfterExtract = 1 << 0, } 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 heap based on the specified desc. /// /// A structure that defines the properties and configuration of the texture to create. /// The name of the texture heap. /// An identifier for the newly created texture heap. Identifier CreateTexture(in RGTextureDesc desc, string name); /// /// Creates a new buffer heap based on the specified desc. /// /// A structure that defines the properties and configuration of the buffer to create. /// The name of the buffer heap. /// An identifier for the newly created buffer heap. 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. /// An identifier for the buffer. Identifier UseBuffer(Identifier buffer, AccessFlags accessMode); /// /// Extracts the actual texture heap associated with the given identifier for use in outside of the render graph execution context. /// /// The identifier of the texture to be extracted. /// A handle to receive the actual GPU texture heap. void QueryTextureExtraction(Identifier src, Handle dst, ResourceExtractionFlags flags = ResourceExtractionFlags.ReleaseAfterExtract); /// /// Extracts the actual buffer heap associated with the given identifier for use in outside of the render graph execution context. /// /// The identifier of the buffer to be extracted. /// A handle to receive the actual GPU buffer heap. void QueryBufferExtraction(Identifier src, Handle dst, ResourceExtractionFlags flags = ResourceExtractionFlags.ReleaseAfterExtract); } 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 heap. /// 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 heap. /// 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; } [Conditional("DEBUG")] [MethodImpl(MethodImplOptions.AggressiveInlining)] 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) { ThrowIfDisposed(); return UseResource(buffer.AsResource(), flags, RenderGraphResourceType.Buffer).AsBuffer(); } // TODO: Implement QueryTextureExtraction and QueryBufferExtraction to allow users to get the actual GPU resources for use outside of the render graph execution context. public void QueryTextureExtraction(Identifier src, Handle dst, ResourceExtractionFlags flags = ResourceExtractionFlags.ReleaseAfterExtract) { throw new NotImplementedException(); } public void QueryBufferExtraction(Identifier src, Handle dst, ResourceExtractionFlags flags = ResourceExtractionFlags.ReleaseAfterExtract) { throw new NotImplementedException(); } 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(); Logger.DebugAssert(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); var usage = new ResourceBarrierData(BarrierLayout.RenderTarget, BarrierAccess.RenderTarget, BarrierSync.RenderTarget); _pass.colorAccess[index] = new TextureAccess(id, flags, usage); } else { throw new InvalidOperationException($"Color attachment at index {index} is already set to a different texture."); } } public void SetDepthAttachment(Identifier texture, AccessFlags flags = AccessFlags.ReadWrite) { ThrowIfDisposed(); var id = UseTexture(texture, flags); if (_pass.depthAccess.id == id || _pass.depthAccess.id.IsInvalid) { var layout = flags.HasFlag(AccessFlags.Write) ? BarrierLayout.DepthStencilWrite : BarrierLayout.DepthStencilRead; var access = flags.HasFlag(AccessFlags.Write) ? BarrierAccess.DepthStencilWrite : BarrierAccess.DepthStencilRead; var sync = BarrierSync.DepthStencil; var usage = new ResourceBarrierData(layout, access, sync); _pass.depthAccess = new TextureAccess(id, flags, usage); } 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."); } if (_pass.type == RenderPassType.Raster && _pass.colorAccess[0].id.IsInvalid && _pass.depthAccess.id.IsInvalid) { throw new InvalidOperationException("Raster render pass must have at least one color or depth attachment."); } _graph = null!; _pass = null!; _resources = null!; _disposed = true; } }