forked from Misaki/GhostEngine
Major overhaul of render graph system for modularity and performance: - Split compilation and execution logic into dedicated classes (Compiler, Executor, NativePassBuilder, Barriers) - Overhauled barrier system: now uses CompiledBarrier with target state only, querying before state at execution - Resource size/alignment now queried from D3D12 device for accurate heap allocation - ResourceDesc now includes Type field and asserts correct union access - Centralized D3D12 interop logic in D3D12Utility extensions - Added RenderGraphHasher for structural graph hashing and cache invalidation - RenderGraph class simplified to orchestrate specialized components - ResourceAliasingManager now uses allocator for size queries - Compilation cache now stores compiled barriers, reducing memory usage - Improved comments, debug assertions, and removed redundant code Result: more maintainable, efficient, and robust render graph pipeline.
224 lines
8.6 KiB
C#
224 lines
8.6 KiB
C#
using Ghost.Core;
|
|
using Ghost.Graphics.Core;
|
|
using Ghost.Graphics.RHI;
|
|
|
|
namespace Ghost.Graphics.RenderGraphModule;
|
|
|
|
/// <summary>
|
|
/// Handles execution of compiled render graphs, including barrier execution and native render passes.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes all compiled passes using native render passes where possible.
|
|
/// </summary>
|
|
public unsafe void Execute(
|
|
ICommandBuffer cmd,
|
|
List<RenderGraphPassBase> compiledPasses,
|
|
List<NativeRenderPass> nativePasses,
|
|
List<CompiledBarrier> 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<Texture>.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<PassRenderTargetDesc>(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<TextureFormat>(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++;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes all barriers for a specific pass.
|
|
/// Uses pre-compiled barriers and queries before state from ResourceDatabase.
|
|
/// </summary>
|
|
private unsafe void ExecuteBarriersForPass(
|
|
ICommandBuffer cmd,
|
|
int passIndex,
|
|
ref int barrierIndex,
|
|
List<CompiledBarrier> compiledBarriers)
|
|
{
|
|
const int MaxBatch = 64;
|
|
var barriers = stackalloc BarrierDesc[MaxBatch];
|
|
var barrierCount = 0;
|
|
|
|
void Flush()
|
|
{
|
|
if (barrierCount > 0)
|
|
{
|
|
cmd.ResourceBarrier(new ReadOnlySpan<BarrierDesc>(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();
|
|
}
|
|
}
|