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:
2026-01-12 23:48:56 +09:00
parent 1fc9df1812
commit 954e3756aa
15 changed files with 940 additions and 776 deletions

View File

@@ -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);
}
}