using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.RenderGraphModule;
///
/// Handles execution of compiled render graphs, including barrier execution and native render passes.
///
internal sealed class RenderGraphExecutor
{
private readonly IGraphicsEngine _graphicsEngine;
private readonly RenderGraphResourceRegistry _resources;
private readonly RenderGraphContext _context;
public RenderGraphExecutor(
IGraphicsEngine graphicsEngine,
RenderGraphResourceRegistry resources,
RenderGraphContext context)
{
_graphicsEngine = graphicsEngine;
_resources = resources;
_context = context;
}
///
/// Executes all compiled passes using native render passes where possible.
///
public unsafe void Execute(
ICommandBuffer cmd,
List compiledPasses,
List nativePasses,
List compiledBarriers)
{
var barrierIndex = 0;
var nativePassIndex = 0;
var logicalPassIndex = 0;
_context.SetCommandBuffer(cmd);
var pPassRTDescs = stackalloc PassRenderTargetDesc[8];
var pRtFormats = stackalloc TextureFormat[8];
while (logicalPassIndex < compiledPasses.Count)
{
var pass = compiledPasses[logicalPassIndex];
// Check if this pass is part of a native render pass
if (pass.type == RenderPassType.Raster && nativePassIndex < nativePasses.Count)
{
var nativePass = nativePasses[nativePassIndex];
// Build barriers for ALL merged passes before beginning the native render pass
for (var i = 0; i < nativePass.mergedPassIndices.Count; i++)
{
var mergedPassIdx = nativePass.mergedPassIndices[i];
ExecuteBarriersForPass(cmd, mergedPassIdx, ref barrierIndex, compiledBarriers);
}
// Begin native render pass
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
{
var attachment = nativePass.colorAttachments[i];
pPassRTDescs[i] = new PassRenderTargetDesc
{
Texture = _resources.GetResource(attachment.texture).backingResource.AsTexture(),
ClearColor = attachment.clearColor,
LoadOp = attachment.loadOp,
StoreOp = attachment.storeOp
};
}
var depthDesc = new PassDepthStencilDesc
{
Texture = nativePass.hasDepthAttachment
? _resources.GetResource(nativePass.depthAttachment.texture).backingResource.AsTexture()
: Handle.Invalid,
ClearDepth = nativePass.depthAttachment.clearDepth,
ClearStencil = nativePass.depthAttachment.clearStencil,
DepthLoadOp = nativePass.hasDepthAttachment
? nativePass.depthAttachment.loadOp
: AttachmentLoadOp.DontCare,
DepthStoreOp = nativePass.hasDepthAttachment
? nativePass.depthAttachment.storeOp
: AttachmentStoreOp.DontCare,
StencilLoadOp = nativePass.hasDepthAttachment
? nativePass.depthAttachment.loadOp
: AttachmentLoadOp.DontCare,
StencilStoreOp = nativePass.hasDepthAttachment
? nativePass.depthAttachment.storeOp
: AttachmentStoreOp.DontCare
};
cmd.BeginRenderPass(new Span(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc);
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
{
var attachment = nativePass.colorAttachments[i];
var resource = _resources.GetResource(attachment.texture);
pRtFormats[i] = resource.rgTextureDesc.format;
}
var depthFormat = nativePass.hasDepthAttachment
? _resources.GetResource(nativePass.depthAttachment.texture).rgTextureDesc.format
: TextureFormat.Unknown;
_context.SetRenderTargetFormats(new ReadOnlySpan(pRtFormats, nativePass.colorAttachmentCount), depthFormat);
// Build all merged logical passes within this native render pass
for (var i = 0; i < nativePass.mergedPassIndices.Count; i++)
{
var mergedPassIdx = nativePass.mergedPassIndices[i];
var mergedPass = compiledPasses[mergedPassIdx];
mergedPass.Execute(_context);
logicalPassIndex++;
}
cmd.EndRenderPass();
nativePassIndex++;
}
else
{
// Compute pass or standalone raster pass (not merged) or Unsafe pass
ExecuteBarriersForPass(cmd, logicalPassIndex, ref barrierIndex, compiledBarriers);
pass.Execute(_context);
logicalPassIndex++;
}
}
}
///
/// Executes all barriers for a specific pass.
/// Uses pre-compiled barriers and queries before state from ResourceDatabase.
///
private unsafe void ExecuteBarriersForPass(
ICommandBuffer cmd,
int passIndex,
ref int barrierIndex,
List compiledBarriers)
{
const int MaxBatch = 64;
var barriers = stackalloc BarrierDesc[MaxBatch];
var barrierCount = 0;
void Flush()
{
if (barrierCount > 0)
{
cmd.ResourceBarrier(new ReadOnlySpan(barriers, barrierCount));
barrierCount = 0;
}
}
// Process all pre-compiled barriers for this pass
while (barrierIndex < compiledBarriers.Count && compiledBarriers[barrierIndex].PassIndex == passIndex)
{
var compiledBarrier = compiledBarriers[barrierIndex++];
var resource = _resources.GetResource(compiledBarrier.Resource);
var resourceHandle = resource.backingResource;
// Always query the before state from ResourceDatabase (single source of truth)
var currentState = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(resourceHandle).GetValueOrThrow();
BarrierLayout layoutBefore;
BarrierAccess accessBefore;
BarrierSync syncBefore;
// Handle aliasing barriers specially
if (compiledBarrier.AliasingPredecessor.IsValid)
{
var predHandle = _resources.GetResource(compiledBarrier.AliasingPredecessor).backingResource;
var predState = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(predHandle).GetValueOrThrow();
layoutBefore = BarrierLayout.Undefined;
accessBefore = BarrierAccess.NoAccess;
syncBefore = predState.sync;
}
else
{
layoutBefore = currentState.layout;
accessBefore = currentState.access;
syncBefore = currentState.sync;
}
var target = compiledBarrier.TargetState;
// Skip if already in target state (optimization)
if (!compiledBarrier.AliasingPredecessor.IsValid &&
layoutBefore == target.layout &&
accessBefore == target.access &&
syncBefore == target.sync)
{
continue;
}
// Create barrier descriptor
BarrierDesc desc;
if (compiledBarrier.ResourceType == RenderGraphResourceType.Texture)
{
desc = BarrierDesc.Texture(resourceHandle,
syncBefore, target.sync,
accessBefore, target.access,
layoutBefore, target.layout,
discard: compiledBarrier.Flags.HasFlag(BarrierFlags.Discard));
}
else
{
desc = BarrierDesc.Buffer(resourceHandle,
syncBefore, target.sync,
accessBefore, target.access);
}
if (barrierCount >= MaxBatch)
{
Flush();
}
barriers[barrierCount++] = desc;
}
Flush();
}
}