using Ghost.Graphics.Data;
namespace Ghost.Graphics.RenderGraphModule;
///
/// Descriptor for render graph configuration
///
public readonly struct RenderGraphDesc
{
public readonly int InitialResourceCapacity;
public readonly int InitialPassCapacity;
public RenderGraphDesc(int initialResourceCapacity = 256, int initialPassCapacity = 64)
{
InitialResourceCapacity = initialResourceCapacity;
InitialPassCapacity = initialPassCapacity;
}
}
///
/// Main render graph class for managing transient resources and render passes
///
public sealed class RenderGraph : IDisposable
{
private readonly string _name;
private readonly List _resources;
private readonly List _passes;
private readonly List _compiledPassOrder;
private readonly List _barriers;
private bool _isRecording;
private bool _isCompiled;
private bool _isExecuted;
private bool _disposed;
public RenderGraph(string name, RenderGraphDesc desc = default)
{
_name = name;
_resources = new(desc.InitialResourceCapacity > 0 ? desc.InitialResourceCapacity : 256);
_passes = new(desc.InitialPassCapacity > 0 ? desc.InitialPassCapacity : 64);
_compiledPassOrder = new();
_barriers = new();
}
public string Name => _name;
public bool IsRecording => _isRecording;
public bool IsCompiled => _isCompiled;
///
/// Begin recording render passes
///
public void BeginRecord()
{
if (_isRecording)
throw new InvalidOperationException("Render graph is already recording");
if (_isCompiled)
throw new InvalidOperationException("Cannot record on a compiled render graph");
_isRecording = true;
_isExecuted = false;
// Clear previous frame data
foreach (var resource in _resources)
{
if (resource.Lifetime == ResourceLifetime.Transient)
{
resource.ReleaseResource();
}
}
_resources.RemoveAll(r => r.Lifetime == ResourceLifetime.Transient);
_passes.Clear();
_compiledPassOrder.Clear();
_barriers.Clear();
}
///
/// End recording render passes
///
public void EndRecord()
{
if (!_isRecording)
throw new InvalidOperationException("Render graph is not recording");
_isRecording = false;
}
///
/// Create a new render pass
///
public RenderPassCreator CreatePass(string passName)
where TPassData : struct
{
if (!_isRecording)
throw new InvalidOperationException("Cannot create pass when not recording");
return new RenderPassCreator(this, passName);
}
///
/// Create a transient texture resource
///
public RGTextureHandle CreateTexture(string name, ResourceLifetime lifetime, TextureDescription description)
{
var texture = new RenderGraphTexture(_resources.Count, name, lifetime, description);
_resources.Add(texture);
return new RGTextureHandle(texture.Id, this);
}
///
/// Create a transient buffer resource
///
public RGBufferHandle CreateBuffer(string name, ResourceLifetime lifetime, BufferDescription description)
{
var buffer = new RenderGraphBuffer(_resources.Count, name, lifetime, description);
_resources.Add(buffer);
return new RGBufferHandle(buffer.Id, this);
}
///
/// Import an external texture (e.g., from previous frame, swap chain)
///
public RGTextureHandle ImportTexture(string name, TextureHandle externalHandle, TextureDescription description)
{
var texture = new RenderGraphTexture(_resources.Count, name, externalHandle, description);
_resources.Add(texture);
return new RGTextureHandle(texture.Id, this);
}
///
/// Import an external buffer (e.g., from previous frame)
///
public RGBufferHandle ImportBuffer(string name, BufferHandle externalHandle, BufferDescription description)
{
var buffer = new RenderGraphBuffer(_resources.Count, name, externalHandle, description);
_resources.Add(buffer);
return new RGBufferHandle(buffer.Id, this);
}
///
/// Export a resource for use in the next frame (for history buffers)
///
public TextureHandle ExportTexture(RGTextureHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid texture handle", nameof(handle));
var texture = (RenderGraphTexture)_resources[handle._resourceId];
texture.Lifetime = ResourceLifetime.Persistent;
return texture.Handle;
}
///
/// Export a buffer for use in the next frame
///
public BufferHandle ExportBuffer(RGBufferHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid buffer handle", nameof(handle));
var buffer = (RenderGraphBuffer)_resources[handle._resourceId];
buffer.Lifetime = ResourceLifetime.Persistent;
return buffer.Handle;
}
public RenderGraphTexture GetTextureResource(RGTextureHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid texture handle", nameof(handle));
return (RenderGraphTexture)_resources[handle._resourceId];
}
public RenderGraphBuffer GetBufferResource(RGBufferHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid buffer handle", nameof(handle));
return (RenderGraphBuffer)_resources[handle._resourceId];
}
///
/// Internal method to add a pass to the render graph
///
internal void AddPass(RenderPass pass)
{
pass.Index = _passes.Count;
_passes.Add(pass);
}
///
/// Internal method to update resource lifetime during pass setup
///
internal void UpdateResourceLifetime(int resourceId, int passIndex)
{
if (resourceId >= _resources.Count)
return;
var resource = _resources[resourceId];
if (resource.FirstPassIndex == -1)
resource.FirstPassIndex = passIndex;
resource.LastPassIndex = Math.Max(resource.LastPassIndex, passIndex);
}
///
/// Compile the render graph - performs dependency analysis, topological sort, and resource lifetime analysis
///
public void Compile()
{
if (_isRecording)
throw new InvalidOperationException("Cannot compile while recording");
if (_isCompiled)
throw new InvalidOperationException("Render graph is already compiled");
// Setup all passes to gather resource dependencies
SetupPasses();
// Build dependency graph and perform topological sort
BuildDependencyGraph();
TopologicalSort();
// Analyze resource lifetimes and generate barriers
AnalyzeResourceLifetimes();
GenerateBarriers();
_isCompiled = true;
}
///
/// Execute the compiled render graph
///
public void Execute()
{
if (!_isCompiled)
throw new InvalidOperationException("Render graph must be compiled before execution");
if (_isExecuted)
throw new InvalidOperationException("Render graph has already been executed");
// Execute passes in topological order
foreach (var passIndex in _compiledPassOrder)
{
var pass = _passes[passIndex];
var context = new RenderPassContext(passIndex);
CreateTransientResources(passIndex);
ApplyBarriersForPass(passIndex);
// Execute the pass
pass.Execute(context);
}
_isExecuted = true;
}
private void SetupPasses()
{
for (var i = 0; i < _passes.Count; i++)
{
var pass = _passes[i];
var builder = new RenderPassBuilder(this, i);
pass.Setup(builder);
}
}
private void BuildDependencyGraph()
{
// Build dependencies based on resource usage
foreach (var pass in _passes)
{
var writeResources = new HashSet();
var readResources = new HashSet();
// Categorize resource accesses
foreach (var access in pass.ResourceAccesses)
{
switch (access.accessType)
{
case ResourceAccessType.Write:
writeResources.Add(access.resourceId);
break;
case ResourceAccessType.Read:
readResources.Add(access.resourceId);
break;
case ResourceAccessType.ReadWrite:
writeResources.Add(access.resourceId);
readResources.Add(access.resourceId);
break;
}
}
// Add dependencies based on Write-After-Read, Read-After-Write, Write-After-Write
for (var otherPassIndex = 0; otherPassIndex < pass.Index; otherPassIndex++)
{
var otherPass = _passes[otherPassIndex];
var hasDependency = false;
foreach (var otherAccess in otherPass.ResourceAccesses)
{
// WAR, RAW, WAW dependencies
if ((writeResources.Contains(otherAccess.resourceId) && otherAccess.accessType == ResourceAccessType.Read) ||
(readResources.Contains(otherAccess.resourceId) && otherAccess.accessType != ResourceAccessType.Read) ||
(writeResources.Contains(otherAccess.resourceId) && otherAccess.accessType != ResourceAccessType.Read))
{
hasDependency = true;
break;
}
}
if (hasDependency && !pass.Dependencies.Contains(otherPassIndex))
{
pass.Dependencies.Add(otherPassIndex);
}
}
}
}
private void TopologicalSort()
{
_compiledPassOrder.Clear();
var visited = new bool[_passes.Count];
var inDegree = new int[_passes.Count];
// Calculate in-degrees
foreach (var pass in _passes)
{
foreach (var dependency in pass.Dependencies)
{
inDegree[pass.Index]++;
}
}
// Kahn's algorithm for topological sorting
var queue = new Queue();
for (var i = 0; i < _passes.Count; i++)
{
if (inDegree[i] == 0)
{
queue.Enqueue(i);
}
}
while (queue.Count > 0)
{
var passIndex = queue.Dequeue();
_compiledPassOrder.Add(passIndex);
var currentPass = _passes[passIndex];
foreach (var dependentPassIndex in _passes.Where(p => p.Dependencies.Contains(passIndex)).Select(p => p.Index))
{
inDegree[dependentPassIndex]--;
if (inDegree[dependentPassIndex] == 0)
{
queue.Enqueue(dependentPassIndex);
}
}
}
if (_compiledPassOrder.Count != _passes.Count)
{
throw new InvalidOperationException("Circular dependency detected in render graph");
}
}
private void AnalyzeResourceLifetimes()
{
// Resource lifetimes are already tracked during pass setup
// Additional analysis can be added here if needed
}
private void GenerateBarriers()
{
_barriers.Clear();
// TODO: Implement barrier generation based on resource state transitions
// This would analyze the resource usage patterns and generate appropriate D3D12 barriers
}
private void CreateTransientResources(int passIndex)
{
var pass = _passes[passIndex];
foreach (var access in pass.ResourceAccesses)
{
var resource = _resources[access.resourceId];
if (!resource.IsCreated)
{
resource.CreateResource();
}
}
}
private void ApplyBarriersForPass(int passIndex)
{
// TODO: Apply the generated barriers for the given pass
// This would involve creating D3D12 resource barriers and executing them on the command list
}
public void Dispose()
{
if (_disposed)
return;
foreach (var resource in _resources)
{
resource.ReleaseResource();
}
_resources.Clear();
_passes.Clear();
_compiledPassOrder.Clear();
_barriers.Clear();
_disposed = true;
}
}