GhostEngine Render Graph: major refactor & Unity RG ref

- Major architectural refactor for performance, extensibility, and feature completeness: resource pooling, pass culling, aliasing, and compilation caching.
- Introduces type-safe builder and context APIs, blackboard pattern, and unified resource management.
- Adds detailed documentation and cleans up obsolete files and APIs.
- Includes (commented) Unity Render Graph source for reference; not compiled, for parity and future extension.
This commit is contained in:
2026-01-11 23:43:17 +09:00
parent 87e315a588
commit 1fc9df1812
30 changed files with 7536 additions and 1545 deletions

View File

@@ -1,111 +1,215 @@
using System;
using System.Collections.Generic;
namespace Ghost.RenderGraph.Concept;
public enum RenderQueueType
/// <summary>
/// Represents different types of render passes.
/// </summary>
public enum RenderPassType : byte
{
Graphics,
Compute,
AsyncCompute,
Copy
Raster,
Compute
}
internal abstract class RenderGraphPass
/// <summary>
/// Base class for render passes.
/// Uses pooling to avoid allocations after the first frame.
/// </summary>
internal abstract class RenderGraphPassBase
{
public string Name { get; set; } = string.Empty;
public int Index { get; set; }
public RenderQueueType QueueType { get; set; }
public List<(RenderGraphResourceHandle handle, ResourceState state)> ResourceAccesses { get; set; }
public List<int> Dependencies { get; } = new();
public int RefCount { get; set; } = 0;
public bool AllowCulling { get; set; }
public string Name = string.Empty;
public int Index;
public RenderPassType Type;
public bool AllowCulling = true;
public bool AsyncCompute;
// Resource dependencies
public readonly List<RenderGraphTextureHandle> TextureReads = new(8);
public readonly List<RenderGraphTextureHandle> TextureWrites = new(4);
public readonly List<RenderGraphTextureHandle> TextureCreates = new(4);
// Execution state
public bool Culled;
public bool HasSideEffects;
protected RenderGraphPass(
string name,
int index,
RenderQueueType queueType,
List<(RenderGraphResourceHandle handle, ResourceState state)> resourceAccesses,
bool allowCulling)
public abstract void Execute(RenderContext context);
public abstract void Clear();
public virtual void Reset()
{
Name = name;
Index = index;
QueueType = queueType;
ResourceAccesses = resourceAccesses;
AllowCulling = allowCulling;
Name = string.Empty;
Index = -1;
Type = RenderPassType.Raster;
AllowCulling = true;
AsyncCompute = false;
TextureReads.Clear();
TextureWrites.Clear();
TextureCreates.Clear();
Culled = false;
HasSideEffects = false;
}
protected void InitializeBase(
string name,
int index,
RenderQueueType queueType,
List<(RenderGraphResourceHandle handle, ResourceState state)> resourceAccesses,
bool allowCulling)
{
Name = name;
Index = index;
QueueType = queueType;
ResourceAccesses = resourceAccesses;
AllowCulling = allowCulling;
Dependencies.Clear();
RefCount = 0;
}
public abstract void Execute(ICommandBuffer commandBuffer);
public abstract void Release();
}
internal static class RenderGraphPassPool<TPassData>
where TPassData : class
/// <summary>
/// Typed render pass with user data.
/// </summary>
internal sealed class RenderGraphPass<TPassData> : RenderGraphPassBase
where TPassData : class, new()
{
public static readonly Stack<RenderGraphPass<TPassData>> Pool = new();
public TPassData? PassData;
public Action<TPassData, RasterRenderContext>? RasterRenderFunc;
public Action<TPassData, ComputeRenderContext>? 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();
}
}
internal class RenderGraphPass<TPassData> : RenderGraphPass
where TPassData : class
/// <summary>
/// Builder for constructing render passes.
/// Implements IDisposable for using() pattern.
/// </summary>
public sealed class RenderGraphBuilder : IDisposable
{
public TPassData PassData { get; private set; }
public Action<TPassData, ICommandBuffer> RenderFunc { get; private set; }
private RenderGraphPassBase? _pass;
private RenderGraphResourceRegistry? _resources;
private bool _disposed;
public RenderGraphPass(
string name,
int index,
RenderQueueType queueType,
TPassData passData,
Action<TPassData, ICommandBuffer> renderFunc,
List<(RenderGraphResourceHandle handle, ResourceState state)> resourceAccesses,
bool allowCulling)
: base(name, index, queueType, resourceAccesses, allowCulling)
internal void Initialize(RenderGraphPassBase pass, RenderGraphResourceRegistry resources)
{
PassData = passData;
RenderFunc = renderFunc;
_pass = pass;
_resources = resources;
_disposed = false;
}
public void Initialize(
string name,
int index,
RenderQueueType queueType,
TPassData passData,
Action<TPassData, ICommandBuffer> renderFunc,
List<(RenderGraphResourceHandle handle, ResourceState state)> resourceAccesses,
bool allowCulling)
/// <summary>
/// Creates a new transient texture that only lives for this pass.
/// </summary>
public RenderGraphTextureHandle CreateTexture(TextureDescriptor descriptor)
{
InitializeBase(name, index, queueType, resourceAccesses, allowCulling);
PassData = passData;
RenderFunc = renderFunc;
ThrowIfDisposed();
var handle = _resources!.CreateTexture(descriptor);
_pass!.TextureCreates.Add(handle);
_resources.SetProducer(handle, _pass.Index);
return handle;
}
public override void Execute(ICommandBuffer commandBuffer)
/// <summary>
/// Marks a texture as being read by this pass.
/// </summary>
public RenderGraphTextureHandle ReadTexture(RenderGraphTextureHandle handle)
{
RenderFunc(PassData, commandBuffer);
ThrowIfDisposed();
_pass!.TextureReads.Add(handle);
_resources!.AddConsumer(handle, _pass.Index);
return handle;
}
public override void Release()
/// <summary>
/// Marks a texture as being written by this pass.
/// </summary>
public RenderGraphTextureHandle WriteTexture(RenderGraphTextureHandle handle)
{
PassData = null!;
RenderFunc = null!;
// ResourceAccesses list ownership is transferred back to RenderGraph
ResourceAccesses = null!;
RenderGraphPassPool<TPassData>.Pool.Push(this);
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));
}
}