forked from Misaki/GhostEngine
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:
@@ -1,3 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using System.IO;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
/// <summary>
|
||||
@@ -15,201 +18,112 @@ public enum RenderPassType : byte
|
||||
/// </summary>
|
||||
internal abstract class RenderGraphPassBase
|
||||
{
|
||||
public string Name = string.Empty;
|
||||
public int Index;
|
||||
public RenderPassType Type;
|
||||
public bool AllowCulling = true;
|
||||
public bool AsyncCompute;
|
||||
|
||||
public string name = string.Empty;
|
||||
public int index;
|
||||
public RenderPassType type;
|
||||
public bool allowCulling = true;
|
||||
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
|
||||
public readonly List<RenderGraphTextureHandle> TextureReads = new(8);
|
||||
public readonly List<RenderGraphTextureHandle> TextureWrites = new(4);
|
||||
public readonly List<RenderGraphTextureHandle> TextureCreates = new(4);
|
||||
public readonly List<Identifier<RGResource>> resourceReads = new(8);
|
||||
public readonly List<Identifier<RGResource>> resourceWrites = new(4);
|
||||
public readonly List<Identifier<RGResource>> resourceCreates = new(4);
|
||||
|
||||
// Execution state
|
||||
public bool Culled;
|
||||
public bool HasSideEffects;
|
||||
public bool culled;
|
||||
public bool hasSideEffects;
|
||||
|
||||
public abstract void Execute(RenderContext context);
|
||||
public abstract void Clear();
|
||||
public abstract bool HasRenderFunc();
|
||||
|
||||
public virtual void Reset()
|
||||
public virtual void Reset(RenderGraphObjectPool pool)
|
||||
{
|
||||
Name = string.Empty;
|
||||
Index = -1;
|
||||
Type = RenderPassType.Raster;
|
||||
AllowCulling = true;
|
||||
AsyncCompute = false;
|
||||
TextureReads.Clear();
|
||||
TextureWrites.Clear();
|
||||
TextureCreates.Clear();
|
||||
Culled = false;
|
||||
HasSideEffects = false;
|
||||
name = string.Empty;
|
||||
index = -1;
|
||||
type = RenderPassType.Raster;
|
||||
allowCulling = true;
|
||||
asyncCompute = false;
|
||||
|
||||
depthAccess = default;
|
||||
colorAccess.AsSpan().Clear();
|
||||
maxColorIndex = -1;
|
||||
|
||||
randomAccess.Clear();
|
||||
|
||||
resourceReads.Clear();
|
||||
resourceWrites.Clear();
|
||||
resourceCreates.Clear();
|
||||
culled = false;
|
||||
hasSideEffects = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Typed render pass with user data.
|
||||
/// </summary>
|
||||
internal sealed class RenderGraphPass<TPassData> : RenderGraphPassBase
|
||||
internal abstract class RenderGraphPassT<TPassData, TRenderContext> : RenderGraphPassBase
|
||||
where TPassData : class, new()
|
||||
{
|
||||
public TPassData? PassData;
|
||||
public Action<TPassData, RasterRenderContext>? RasterRenderFunc;
|
||||
public Action<TPassData, ComputeRenderContext>? ComputeRenderFunc;
|
||||
public TPassData passData = null!;
|
||||
public Action<TPassData, TRenderContext>? renderFunc;
|
||||
|
||||
public override void Execute(RenderContext context)
|
||||
public void Init(int index, TPassData passData, string name, RenderPassType type)
|
||||
{
|
||||
if (PassData == null)
|
||||
return;
|
||||
this.index = index;
|
||||
this.passData = passData;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
if (Type == RenderPassType.Raster && RasterRenderFunc != null)
|
||||
{
|
||||
RasterRenderFunc(PassData, context.RasterContext);
|
||||
}
|
||||
else if (Type == RenderPassType.Compute && ComputeRenderFunc != null)
|
||||
{
|
||||
ComputeRenderFunc(PassData, context.ComputeContext);
|
||||
}
|
||||
public sealed override bool HasRenderFunc()
|
||||
{
|
||||
return renderFunc != null;
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
PassData = null;
|
||||
RasterRenderFunc = null;
|
||||
ComputeRenderFunc = null;
|
||||
passData = null!;
|
||||
renderFunc = null;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
public override void Reset(RenderGraphObjectPool pool)
|
||||
{
|
||||
base.Reset();
|
||||
base.Reset(pool);
|
||||
pool.Return(passData);
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builder for constructing render passes.
|
||||
/// Implements IDisposable for using() pattern.
|
||||
/// </summary>
|
||||
public sealed class RenderGraphBuilder : IDisposable
|
||||
internal sealed class RasterRenderGraphPass<TPassData> : RenderGraphPassT<TPassData, RasterRenderContext>
|
||||
where TPassData : class, new()
|
||||
{
|
||||
private RenderGraphPassBase? _pass;
|
||||
private RenderGraphResourceRegistry? _resources;
|
||||
private bool _disposed;
|
||||
|
||||
internal void Initialize(RenderGraphPassBase pass, RenderGraphResourceRegistry resources)
|
||||
public override void Execute(RenderContext context)
|
||||
{
|
||||
_pass = pass;
|
||||
_resources = resources;
|
||||
_disposed = false;
|
||||
renderFunc!(passData, context.RasterContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new transient texture that only lives for this pass.
|
||||
/// </summary>
|
||||
public RenderGraphTextureHandle CreateTexture(TextureDescriptor descriptor)
|
||||
public override void Reset(RenderGraphObjectPool pool)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var handle = _resources!.CreateTexture(descriptor);
|
||||
_pass!.TextureCreates.Add(handle);
|
||||
_resources.SetProducer(handle, _pass.Index);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a texture as being read by this pass.
|
||||
/// </summary>
|
||||
public RenderGraphTextureHandle ReadTexture(RenderGraphTextureHandle handle)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_pass!.TextureReads.Add(handle);
|
||||
_resources!.AddConsumer(handle, _pass.Index);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <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));
|
||||
base.Reset(pool);
|
||||
pool.Return(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ComputeRenderGraphPass<TPassData> : RenderGraphPassT<TPassData, ComputeRenderContext>
|
||||
where TPassData : class, new()
|
||||
{
|
||||
public override void Execute(RenderContext context)
|
||||
{
|
||||
renderFunc!(passData, context.ComputeContext);
|
||||
}
|
||||
|
||||
public override void Reset(RenderGraphObjectPool pool)
|
||||
{
|
||||
base.Reset(pool);
|
||||
pool.Return(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user