Refactor render graph: modular compilation & execution

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.
This commit is contained in:
2026-01-23 18:12:52 +09:00
parent 4173ff2432
commit e11a9ebb52
14 changed files with 2797 additions and 1317 deletions

View File

@@ -71,3 +71,263 @@ internal sealed class ResourceStateTracker
lastAccessPass = -1;
}
}
/// <summary>
/// Represents a compiled barrier with only the target state.
/// The before state is always queried from ResourceDatabase at execution time.
/// </summary>
internal struct CompiledBarrier
{
public int PassIndex;
public Identifier<RGResource> Resource;
public ResourceBarrierData TargetState;
public Identifier<RGResource> 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}";
}
}
/// <summary>
/// Static class containing barrier compilation logic.
/// Compiles barriers at graph compilation time, storing only target states.
/// </summary>
internal static class RenderGraphBarriers
{
/// <summary>
/// Compiles all barriers needed for execution, storing only target states.
/// Barriers include aliasing barriers and implicit state transitions.
/// </summary>
public static void CompileBarriers(
List<RenderGraphPassBase> compiledPasses,
List<CompiledBarrier> 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);
}
}
/// <summary>
/// Inserts aliasing barriers when a placed resource is reused.
/// </summary>
private static void InsertAliasingBarriers(
RenderGraphPassBase pass,
int passIdx,
List<CompiledBarrier> 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<RGResource> 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<RGResource>(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);
}
}
}
}
}
}
}
/// <summary>
/// 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.
/// </summary>
private static void CompileImplicitTransitions(
RenderGraphPassBase pass,
int passIdx,
List<CompiledBarrier> compiledBarriers,
RenderGraphResourceRegistry resources)
{
// Helper to add a compiled barrier for a resource transition
void AddTransition(Identifier<RGResource> id, ResourceBarrierData targetState)
{
var resource = resources.GetResource(id);
var barrier = new CompiledBarrier
{
PassIndex = passIdx,
Resource = id,
TargetState = targetState,
AliasingPredecessor = Identifier<RGResource>.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<RGResource> 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);
}
}