forked from Misaki/GhostEngine
- RenderGraph.Compile/Execute now return Error for better failure detection; error handling is propagated throughout compiler and executor. - Renamed ScheduleReleaseResource to ReleaseResource for clarity; updated all usages. - ResourceManager now calls ReleaseResource directly on Mesh, Material, and Shader types. - Camera exposes Actual/Virtual size properties and Render returns Error. - RenderingContext now uses IResourceManager for mesh/resource ops. - Replaced custom BinaryWriter with BufferWriter in RenderGraphHasher. - Improved variable naming, interface signatures, and code formatting. - Added Error extension for IsSuccess/IsFailure. - Minor FMOD/native interop and test code cleanups. - No breaking API changes except for new Error return values on some methods.
247 lines
9.2 KiB
C#
247 lines
9.2 KiB
C#
using Ghost.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 IResourceManager _resourceManager;
|
|
private readonly RenderGraphResourceRegistry _resources;
|
|
private readonly RenderGraphContext _context;
|
|
|
|
public RenderGraphExecutor(
|
|
IResourceManager resourceManager,
|
|
RenderGraphResourceRegistry resources,
|
|
RenderGraphContext context)
|
|
{
|
|
_resourceManager = resourceManager;
|
|
_resources = resources;
|
|
_context = context;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes all compiled passes using native render passes where possible.
|
|
/// </summary>
|
|
public unsafe Error 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];
|
|
var e = ExecuteBarriersForPass(cmd, mergedPassIdx, ref barrierIndex, compiledBarriers);
|
|
if (e != Error.None)
|
|
{
|
|
return e;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
var e = ExecuteBarriersForPass(cmd, logicalPassIndex, ref barrierIndex, compiledBarriers);
|
|
if (e != Error.None)
|
|
{
|
|
return e;
|
|
}
|
|
|
|
pass.Execute(_context);
|
|
logicalPassIndex++;
|
|
}
|
|
}
|
|
|
|
return Error.None;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes all barriers for a specific pass.
|
|
/// Uses pre-compiled barriers and queries before state from ResourceManager.
|
|
/// </summary>
|
|
private unsafe Error 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 ResourceManager (single source of truth)
|
|
var currentStateResult = _resourceManager.ResourceDatabase.GetResourceBarrierData(resourceHandle);
|
|
if (currentStateResult.IsFailure)
|
|
{
|
|
return currentStateResult.Error;
|
|
}
|
|
|
|
var currentState = currentStateResult.Value;
|
|
|
|
BarrierLayout layoutBefore;
|
|
BarrierAccess accessBefore;
|
|
BarrierSync syncBefore;
|
|
|
|
// Handle aliasing barriers specially
|
|
if (compiledBarrier.AliasingPredecessor.IsValid)
|
|
{
|
|
var predHandle = _resources.GetResource(compiledBarrier.AliasingPredecessor).backingResource;
|
|
var predStateResult = _resourceManager.ResourceDatabase.GetResourceBarrierData(predHandle);
|
|
if (predStateResult.IsFailure)
|
|
{
|
|
return predStateResult.Error;
|
|
}
|
|
|
|
var predState = predStateResult.Value;
|
|
|
|
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();
|
|
|
|
return Error.None;
|
|
}
|
|
}
|