using Ghost.Core;
using Ghost.Graphics.RHI;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.RenderGraphModule;
[Flags]
internal enum BarrierFlags
{
None = 0,
FirstUsage = 1 << 0,
Discard = 1 << 1
}
///
/// Represents a resource barrier requirement that needs to be resolved at runtime.
///
internal struct ResourceBarrier
{
public int PassIndex;
public Identifier Resource;
public ResourceBarrierData TargetState;
public Identifier AliasingPredecessor; // Invalid if not aliasing
public BarrierFlags Flags;
public static ResourceBarrier CreateTransition(int passIndex, Identifier resource, ResourceBarrierData targetState, BarrierFlags flags = BarrierFlags.None)
{
return new ResourceBarrier
{
PassIndex = passIndex,
Resource = resource,
TargetState = targetState,
AliasingPredecessor = Identifier.Invalid,
Flags = flags
};
}
public static ResourceBarrier CreateAliasing(int passIndex, Identifier resource, Identifier predecessor, ResourceBarrierData targetState)
{
return new ResourceBarrier
{
PassIndex = passIndex,
Resource = resource,
TargetState = targetState,
AliasingPredecessor = predecessor,
Flags = BarrierFlags.FirstUsage | BarrierFlags.Discard // Aliasing implies starting fresh
};
}
public override readonly string ToString()
{
return AliasingPredecessor.IsValid
? $"[Pass {PassIndex}] Aliasing Barrier: {AliasingPredecessor.Value}->{Resource.Value} Target: {TargetState.layout}"
: $"[Pass {PassIndex}] Barrier: {Resource.Value} Target: {TargetState.layout}";
}
}
///
/// Tracks the current state of a resource across passes during compilation.
///
internal sealed class ResourceStateTracker
{
public int resourceIndex;
public ResourceBarrierData currentState;
public int lastAccessPass = -1;
public void Reset()
{
resourceIndex = -1;
currentState = default;
lastAccessPass = -1;
}
}
///
/// Represents a compiled barrier with only the target state.
/// The before state is always queried from ResourceDatabase at execution time.
///
internal struct CompiledBarrier
{
public int PassIndex;
public Identifier Resource;
public ResourceBarrierData TargetState;
public Identifier AliasingPredecessor; // Invalid if not aliasing
public BarrierFlags Flags;
public RenderGraphResourceType ResourceType;
public override readonly string ToString()
{
return AliasingPredecessor.IsValid
? $"[Pass {PassIndex}] Aliasing: {AliasingPredecessor.Value}->{Resource.Value} -> {TargetState.layout}"
: $"[Pass {PassIndex}] Transition: {Resource.Value} -> {TargetState.layout}";
}
}
///
/// Static class containing barrier compilation logic.
/// Compiles barriers at graph compilation time, storing only target states.
///
internal static class RenderGraphBarriers
{
///
/// Compiles all barriers needed for execution, storing only target states.
/// Barriers include aliasing barriers and implicit state transitions.
///
public static void CompileBarriers(
List compiledPasses,
List compiledBarriers,
RenderGraphResourceRegistry resources,
ResourceAliasingManager aliasingManager)
{
compiledBarriers.Clear();
// Process each compiled pass in order
for (var passIdx = 0; passIdx < compiledPasses.Count; passIdx++)
{
var pass = compiledPasses[passIdx];
// 1. Insert aliasing barriers for resources that reuse physical memory
InsertAliasingBarriers(pass, passIdx, compiledBarriers, resources, aliasingManager);
// 2. Compile implicit transitions for all resources accessed by this pass
CompileImplicitTransitions(pass, passIdx, compiledBarriers, resources);
}
}
///
/// Inserts aliasing barriers when a placed resource is reused.
///
private static void InsertAliasingBarriers(
RenderGraphPassBase pass,
int passIdx,
List compiledBarriers,
RenderGraphResourceRegistry resources,
ResourceAliasingManager aliasingManager)
{
// Check all resources written by this pass (both textures and buffers)
for (var resType = 0; resType < (int)RenderGraphResourceType.Count; resType++)
{
var writeList = pass.resourceWrites[resType];
for (var i = 0; i < writeList.Count; i++)
{
var id = writeList[i];
var resource = resources.GetResource(id);
// Skip imported resources
if (resource.isImported)
{
continue;
}
// Check if this is the first use of this logical resource
if (resource.firstUsePass == pass.index)
{
// Get the placed resource
var placedIndex = aliasingManager.GetPlacedResourceIndex(id.Value);
if (placedIndex >= 0)
{
var placed = aliasingManager.GetPlacedResource(placedIndex);
// If this placed resource has multiple aliased resources,
// we need an aliasing barrier when switching between them
if (placed != null && placed.aliasedLogicalResources.Count > 1)
{
// Find the resource that used this placed memory most recently before this pass
Identifier resourceBefore = default;
var mostRecentLastUse = -1;
foreach (var otherLogicalIndex in placed.aliasedLogicalResources)
{
if (otherLogicalIndex != id.Value)
{
// Get resource by global index
var otherResource = resources.GetResourceByIndex(otherLogicalIndex);
// Check if this resource finished before our resource starts
if (otherResource.lastUsePass < pass.index &&
otherResource.lastUsePass > mostRecentLastUse)
{
mostRecentLastUse = otherResource.lastUsePass;
resourceBefore = new Identifier(otherLogicalIndex);
}
}
}
// If we found a previous resource, insert aliasing barrier
if (mostRecentLastUse >= 0)
{
// Aliasing Requirement: Transition to Undefined, Sync with Predecessor
var targetState = new ResourceBarrierData(BarrierLayout.Undefined, BarrierAccess.NoAccess, BarrierSync.None);
var barrier = new CompiledBarrier
{
PassIndex = passIdx,
Resource = id,
TargetState = targetState,
AliasingPredecessor = resourceBefore,
Flags = BarrierFlags.FirstUsage | BarrierFlags.Discard,
ResourceType = resource.type
};
compiledBarriers.Add(barrier);
}
}
}
}
}
}
}
///
/// Compiles implicit state transitions for all resources accessed by a pass.
/// Stores only the target state - the before state will be queried from ResourceDatabase at execution time.
///
private static void CompileImplicitTransitions(
RenderGraphPassBase pass,
int passIdx,
List compiledBarriers,
RenderGraphResourceRegistry resources)
{
// Helper to add a compiled barrier for a resource transition
void AddTransition(Identifier id, ResourceBarrierData targetState)
{
var resource = resources.GetResource(id);
var barrier = new CompiledBarrier
{
PassIndex = passIdx,
Resource = id,
TargetState = targetState,
AliasingPredecessor = Identifier.Invalid,
Flags = BarrierFlags.None,
ResourceType = resource.type
};
compiledBarriers.Add(barrier);
}
// Compile transitions for read resources
for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
{
var readList = pass.resourceReads[i];
for (var j = 0; j < readList.Count; j++)
{
var handle = readList[j];
var targetState = GetBufferReadBarrierData(handle, pass, (RenderGraphResourceType)i, resources);
AddTransition(handle, targetState);
}
}
// Compile transitions based on pass type
switch (pass.type)
{
case RenderPassType.Raster:
// Color attachments
for (var i = 0; i <= pass.maxColorIndex; i++)
{
if (pass.colorAccess[i].id.IsValid)
{
var usage = pass.colorAccess[i].usage;
var targetState = new ResourceBarrierData(usage.layout, usage.access, usage.sync);
AddTransition(pass.colorAccess[i].id.AsResource(), targetState);
}
}
// Depth attachment
if (pass.depthAccess.id.IsValid)
{
var usage = pass.depthAccess.usage;
var targetState = new ResourceBarrierData(usage.layout, usage.access, usage.sync);
AddTransition(pass.depthAccess.id.AsResource(), targetState);
}
// UAV resources
var uavState = new ResourceBarrierData(BarrierLayout.UnorderedAccess, BarrierAccess.UnorderedAccess, BarrierSync.AllShading);
for (var i = 0; i < pass.randomAccess.Count; i++)
{
AddTransition(pass.randomAccess[i], uavState);
}
break;
case RenderPassType.Compute:
var computeUavState = new ResourceBarrierData(BarrierLayout.UnorderedAccess, BarrierAccess.UnorderedAccess, BarrierSync.ComputeShading);
for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
{
var writeList = pass.resourceWrites[i];
for (var j = 0; j < writeList.Count; j++)
{
AddTransition(writeList[j], computeUavState);
}
}
break;
case RenderPassType.Unsafe:
var rtState = new ResourceBarrierData(BarrierLayout.RenderTarget, BarrierAccess.RenderTarget, BarrierSync.RenderTarget);
for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
{
var writeList = pass.resourceWrites[i];
for (var j = 0; j < writeList.Count; j++)
{
AddTransition(writeList[j], rtState);
}
}
var unsafeUavState = new ResourceBarrierData(BarrierLayout.UnorderedAccess, BarrierAccess.UnorderedAccess, BarrierSync.AllShading);
for (var i = 0; i < pass.randomAccess.Count; i++)
{
AddTransition(pass.randomAccess[i], unsafeUavState);
}
break;
}
}
private static ResourceBarrierData GetBufferReadBarrierData(
Identifier handle,
RenderGraphPassBase pass,
RenderGraphResourceType resourceType,
RenderGraphResourceRegistry resources)
{
if (resourceType == RenderGraphResourceType.Texture)
{
return new ResourceBarrierData(BarrierLayout.ShaderResource, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading);
}
var sync = BarrierSync.PixelShading | BarrierSync.NonPixelShading;
var access = BarrierAccess.ShaderResource;
var resource = resources.GetResource(handle);
if (resource.bufferDesc.Usage.HasFlag(BufferUsage.IndirectArgument))
{
sync = BarrierSync.ExecuteIndirect;
access = BarrierAccess.IndirectArgument;
}
return new ResourceBarrierData(BarrierLayout.Undefined, access, sync);
}
}