forked from Misaki/GhostEngine
Refactor render graph & DSL; remove material system
- Major optimization of Ghost.RenderGraph.Concept: pooled resources, zero-allocation hot paths, explicit queue types, and batch barrier APIs. - Migrated Ghost.DSL shader compiler to ANTLR4-based parser; removed hand-written parser, added grammar files and semantic model conversion. - Added CollectionPool/ListPool for pooled list management. - Updated documentation for new architecture and performance. - Removed Ghost.Shader.Concept (material/material system) from repo and solution. - README.md replaced with a brief project statement.
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
using Ghost.Core.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.RenderGraph.Concept;
|
||||
|
||||
public class RenderGraph
|
||||
@@ -8,7 +14,6 @@ public class RenderGraph
|
||||
private readonly List<RenderGraphResourceHandle> _resources = new();
|
||||
private readonly List<RenderGraphPass> _passes = new();
|
||||
|
||||
// Use List instead of Dictionary since resource IDs are sequential (0, 1, 2, ...)
|
||||
private readonly List<ResourceLifetime> _resourceLifetimes = new();
|
||||
private readonly List<ResourceState> _currentResourceStates = new();
|
||||
private readonly List<int> _resourceToAllocationMap = new();
|
||||
@@ -17,47 +22,66 @@ public class RenderGraph
|
||||
private readonly RenderGraphBlackboard _blackboard = new();
|
||||
private readonly ResourceAllocator _allocator = new();
|
||||
|
||||
// Batching and Sync
|
||||
private readonly List<RenderGraphBatch> _batches = new();
|
||||
private readonly Stack<RenderGraphBatch> _batchPool = new();
|
||||
private int _fenceCounter = 0;
|
||||
|
||||
// Pooled Collections for Compilation
|
||||
private readonly Dictionary<int, int> _resourceLastWriter = new();
|
||||
private readonly Dictionary<int, List<int>> _resourceLastReaders = new();
|
||||
private readonly Dictionary<int, RenderGraphBatch> _passToBatchMap = new();
|
||||
|
||||
// Pooled Lists for Passes
|
||||
private readonly Stack<List<(RenderGraphResourceHandle, ResourceState)>> _resourceAccessListPool = new();
|
||||
private readonly Stack<ResourceLifetime> _resourceLifetimePool = new();
|
||||
|
||||
// Execution Plan (Pre-calculated to avoid LINQ in Execute)
|
||||
private List<RenderGraphResourceHandle>[] _resourcesToCreate = Array.Empty<List<RenderGraphResourceHandle>>();
|
||||
private List<RenderGraphResourceHandle>[] _resourcesToDestroy = Array.Empty<List<RenderGraphResourceHandle>>();
|
||||
|
||||
|
||||
public RenderGraphTextureHandle ImportTexture(string name, TextureDescriptor descriptor)
|
||||
{
|
||||
var handle = new RenderGraphTextureHandle(_resourceIdCounter++, name, descriptor, isImported: true);
|
||||
_resources.Add(handle);
|
||||
_resourceLifetimes.Add(new ResourceLifetime(handle));
|
||||
_resources.Add(handle._handle);
|
||||
_resourceLifetimes.Add(RentResourceLifetime(handle._handle));
|
||||
_currentResourceStates.Add(ResourceState.Undefined);
|
||||
_resourceToAllocationMap.Add(-1); // -1 means no allocation
|
||||
Console.WriteLine($"[RG] Import Texture: '{name}' ({descriptor.Width}x{descriptor.Height}, {descriptor.Format})");
|
||||
_resourceToAllocationMap.Add(-1);
|
||||
//ConsoleAPI.WriteLine($"[RG] Import Texture: '{name}' ({descriptor.Width}x{descriptor.Height}, {descriptor.Format})");
|
||||
return handle;
|
||||
}
|
||||
|
||||
public RenderGraphBufferHandle ImportBuffer(string name, BufferDescriptor descriptor)
|
||||
{
|
||||
var handle = new RenderGraphBufferHandle(_resourceIdCounter++, name, descriptor, isImported: true);
|
||||
_resources.Add(handle);
|
||||
_resourceLifetimes.Add(new ResourceLifetime(handle));
|
||||
_resources.Add(handle._handle);
|
||||
_resourceLifetimes.Add(RentResourceLifetime(handle._handle));
|
||||
_currentResourceStates.Add(ResourceState.Undefined);
|
||||
_resourceToAllocationMap.Add(-1);
|
||||
Console.WriteLine($"[RG] Import Buffer: '{name}' ({descriptor.SizeInBytes} bytes)");
|
||||
//ConsoleAPI.WriteLine($"[RG] Import Buffer: '{name}' ({descriptor.SizeInBytes} bytes)");
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal RenderGraphTextureHandle CreateTransientTexture(TextureDescriptor descriptor)
|
||||
{
|
||||
var handle = new RenderGraphTextureHandle(_resourceIdCounter++, descriptor.DebugName, descriptor, isImported: false);
|
||||
_resources.Add(handle);
|
||||
_resourceLifetimes.Add(new ResourceLifetime(handle));
|
||||
_resources.Add(handle._handle);
|
||||
_resourceLifetimes.Add(RentResourceLifetime(handle._handle));
|
||||
_currentResourceStates.Add(ResourceState.Undefined);
|
||||
_resourceToAllocationMap.Add(-1);
|
||||
Console.WriteLine($"[RG] Create Transient Texture: '{descriptor.DebugName}' ({descriptor.Width}x{descriptor.Height}, {descriptor.Format})");
|
||||
//ConsoleAPI.WriteLine($"[RG] Create Transient Texture: '{descriptor.DebugName}' ({descriptor.Width}x{descriptor.Height}, {descriptor.Format})");
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal RenderGraphBufferHandle CreateTransientBuffer(BufferDescriptor descriptor)
|
||||
{
|
||||
var handle = new RenderGraphBufferHandle(_resourceIdCounter++, descriptor.DebugName, descriptor, isImported: false);
|
||||
_resources.Add(handle);
|
||||
_resourceLifetimes.Add(new ResourceLifetime(handle));
|
||||
_resources.Add(handle._handle);
|
||||
_resourceLifetimes.Add(RentResourceLifetime(handle._handle));
|
||||
_currentResourceStates.Add(ResourceState.Undefined);
|
||||
_resourceToAllocationMap.Add(-1);
|
||||
Console.WriteLine($"[RG] Create Transient Buffer: '{descriptor.DebugName}' ({descriptor.SizeInBytes} bytes)");
|
||||
//ConsoleAPI.WriteLine($"[RG] Create Transient Buffer: '{descriptor.DebugName}' ({descriptor.SizeInBytes} bytes)");
|
||||
return handle;
|
||||
}
|
||||
|
||||
@@ -76,7 +100,8 @@ public class RenderGraph
|
||||
public RenderGraphPassBuilder<TPassData> AddRenderPass<TPassData>(string name, out TPassData passData)
|
||||
where TPassData : class, new()
|
||||
{
|
||||
var builder = new RenderGraphPassBuilder<TPassData>(this, name, _passCounter);
|
||||
var list = RentResourceAccessList();
|
||||
var builder = new RenderGraphPassBuilder<TPassData>(this, name, _passCounter, list);
|
||||
passData = builder.PassData;
|
||||
return builder;
|
||||
}
|
||||
@@ -89,44 +114,126 @@ public class RenderGraph
|
||||
throw new InvalidOperationException($"Pass '{name}' has no render function set. Call SetRenderFunc() on the builder.");
|
||||
}
|
||||
|
||||
var pass = new RenderGraphPass<TPassData>(
|
||||
name,
|
||||
_passCounter++,
|
||||
builder.PassData,
|
||||
builder.RenderFunc,
|
||||
builder.ResourceAccesses.ToList(),
|
||||
builder.AllowCulling);
|
||||
// Optimization: Use Pass Pool
|
||||
RenderGraphPass<TPassData>? pass;
|
||||
// Cast ReadOnlyList back to List (safe because we created it in AddRenderPass)
|
||||
var resourceList = (List<(RenderGraphResourceHandle handle, ResourceState state)>)builder.ResourceAccesses;
|
||||
|
||||
if (!RenderGraphPassPool<TPassData>.Pool.TryPop(out pass))
|
||||
{
|
||||
pass = new RenderGraphPass<TPassData>(
|
||||
name,
|
||||
_passCounter++,
|
||||
builder.QueueType,
|
||||
builder.PassData,
|
||||
builder.RenderFunc,
|
||||
resourceList,
|
||||
builder.AllowCulling);
|
||||
}
|
||||
else
|
||||
{
|
||||
pass.Initialize(
|
||||
name,
|
||||
_passCounter++,
|
||||
builder.QueueType,
|
||||
builder.PassData,
|
||||
builder.RenderFunc,
|
||||
resourceList,
|
||||
builder.AllowCulling);
|
||||
}
|
||||
|
||||
_passes.Add(pass);
|
||||
|
||||
foreach (var (handle, state) in pass.ResourceAccesses)
|
||||
{
|
||||
_resourceLifetimes[handle.Id].AddUsage(state, pass.Index);
|
||||
var lifeTime = _resourceLifetimes[handle.Id];
|
||||
lifeTime.AddUsage(state, pass.Index);
|
||||
_resourceLifetimes[handle.Id] = lifeTime;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[RG] Add Pass: '{name}' (Index: {pass.Index})");
|
||||
foreach (var (handle, state) in pass.ResourceAccesses)
|
||||
{
|
||||
Console.WriteLine($" - {state}: '{handle.Name}'");
|
||||
}
|
||||
//ConsoleAPI.WriteLine($"[RG] Add Pass: '{name}' (Index: {pass.Index})");
|
||||
}
|
||||
|
||||
public void Compile()
|
||||
{
|
||||
Console.WriteLine("\n[RG] ========== COMPILING RENDER GRAPH ==========");
|
||||
//ConsoleAPI.WriteLine("\n[RG] ========== COMPILING RENDER GRAPH ==========");
|
||||
|
||||
BuildDependencies();
|
||||
CullUnusedPasses();
|
||||
AnalyzeResourceLifetimes();
|
||||
AllocatePhysicalResources();
|
||||
InsertSynchronization();
|
||||
}
|
||||
|
||||
private void InsertSynchronization()
|
||||
{
|
||||
//ConsoleAPI.WriteLine("\n[RG] Building command batches and synchronization...");
|
||||
|
||||
_batches.Clear();
|
||||
_fenceCounter = 0;
|
||||
|
||||
// 1. Create Batches (Topological grouping)
|
||||
RenderGraphBatch? currentBatch = null;
|
||||
_passToBatchMap.Clear();
|
||||
|
||||
foreach (var pass in _passes)
|
||||
{
|
||||
if (pass.RefCount == 0) continue;
|
||||
|
||||
if (currentBatch == null || currentBatch.QueueType != pass.QueueType)
|
||||
{
|
||||
if (!_batchPool.TryPop(out currentBatch))
|
||||
{
|
||||
currentBatch = new RenderGraphBatch();
|
||||
}
|
||||
currentBatch.Initialize(_batches.Count, pass.QueueType);
|
||||
_batches.Add(currentBatch);
|
||||
}
|
||||
|
||||
currentBatch.Passes.Add(pass);
|
||||
_passToBatchMap[pass.Index] = currentBatch;
|
||||
}
|
||||
|
||||
//ConsoleAPI.WriteLine($" Created {_batches.Count} batches.");
|
||||
|
||||
// 2. Inject Synchronization (Fences)
|
||||
foreach (var batch in _batches)
|
||||
{
|
||||
foreach (var pass in batch.Passes)
|
||||
{
|
||||
foreach (var depIndex in pass.Dependencies)
|
||||
{
|
||||
if (_passToBatchMap.TryGetValue(depIndex, out var dependencyBatch))
|
||||
{
|
||||
if (dependencyBatch != batch)
|
||||
{
|
||||
int fenceId;
|
||||
if (dependencyBatch.SignalFences.Count == 0)
|
||||
{
|
||||
fenceId = _fenceCounter++;
|
||||
dependencyBatch.SignalFences.Add(fenceId);
|
||||
}
|
||||
else
|
||||
{
|
||||
fenceId = dependencyBatch.SignalFences[0];
|
||||
}
|
||||
|
||||
if (!batch.WaitFences.Contains(fenceId))
|
||||
{
|
||||
batch.WaitFences.Add(fenceId);
|
||||
//ConsoleAPI.WriteLine($" Batch {batch.ID} ({batch.QueueType}) waits on Batch {dependencyBatch.ID} ({dependencyBatch.QueueType}) [Fence {fenceId}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AllocatePhysicalResources()
|
||||
{
|
||||
// Pass as IReadOnlyList since it's now a List
|
||||
_allocator.AllocateResources(_resourceLifetimes, _passes);
|
||||
|
||||
// Build mapping from virtual resource to physical allocation
|
||||
foreach (var allocation in _allocator.Allocations)
|
||||
{
|
||||
foreach (var resource in allocation.AliasedResources)
|
||||
@@ -138,42 +245,59 @@ public class RenderGraph
|
||||
|
||||
private void BuildDependencies()
|
||||
{
|
||||
Console.WriteLine("\n[RG] Building pass dependencies...");
|
||||
_resourceLastWriter.Clear();
|
||||
foreach (var list in _resourceLastReaders.Values) list.Clear();
|
||||
_resourceLastReaders.Clear();
|
||||
|
||||
for (int i = 0; i < _passes.Count; i++)
|
||||
{
|
||||
var pass = _passes[i];
|
||||
|
||||
var writtenResources = pass.ResourceAccesses
|
||||
.Where(access => IsWriteState(access.state))
|
||||
.Select(access => access.handle.Id)
|
||||
.ToHashSet();
|
||||
|
||||
for (int j = 0; j < i; j++)
|
||||
foreach (var (handle, state) in pass.ResourceAccesses)
|
||||
{
|
||||
var previousPass = _passes[j];
|
||||
|
||||
var hasReadAfterWrite = previousPass.ResourceAccesses
|
||||
.Where(access => IsWriteState(access.state))
|
||||
.Any(access => pass.ResourceAccesses.Any(
|
||||
current => current.handle.Id == access.handle.Id && IsReadState(current.state)));
|
||||
int resourceId = handle.Id;
|
||||
|
||||
var hasWriteAfterRead = pass.ResourceAccesses
|
||||
.Where(access => IsWriteState(access.state))
|
||||
.Any(access => previousPass.ResourceAccesses.Any(
|
||||
prev => prev.handle.Id == access.handle.Id && IsReadState(prev.state)));
|
||||
|
||||
var hasWriteAfterWrite = previousPass.ResourceAccesses
|
||||
.Where(access => IsWriteState(access.state))
|
||||
.Any(access => writtenResources.Contains(access.handle.Id));
|
||||
|
||||
if (hasReadAfterWrite || hasWriteAfterRead || hasWriteAfterWrite)
|
||||
if (IsReadState(state))
|
||||
{
|
||||
if (!pass.Dependencies.Contains(j))
|
||||
if (_resourceLastWriter.TryGetValue(resourceId, out int lastWriterIndex))
|
||||
{
|
||||
pass.Dependencies.Add(j);
|
||||
Console.WriteLine($" Pass '{pass.Name}' depends on '{previousPass.Name}'");
|
||||
if (!pass.Dependencies.Contains(lastWriterIndex))
|
||||
{
|
||||
pass.Dependencies.Add(lastWriterIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_resourceLastReaders.TryGetValue(resourceId, out var readers))
|
||||
{
|
||||
readers = new List<int>(); // Optimization TODO: Pool these
|
||||
_resourceLastReaders[resourceId] = readers;
|
||||
}
|
||||
readers.Add(i);
|
||||
}
|
||||
|
||||
if (IsWriteState(state))
|
||||
{
|
||||
if (_resourceLastWriter.TryGetValue(resourceId, out int lastWriterIndex))
|
||||
{
|
||||
if (!pass.Dependencies.Contains(lastWriterIndex))
|
||||
{
|
||||
pass.Dependencies.Add(lastWriterIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (_resourceLastReaders.TryGetValue(resourceId, out var readers))
|
||||
{
|
||||
foreach (var readerIndex in readers)
|
||||
{
|
||||
if (readerIndex != i && !pass.Dependencies.Contains(readerIndex))
|
||||
{
|
||||
pass.Dependencies.Add(readerIndex);
|
||||
}
|
||||
}
|
||||
readers.Clear();
|
||||
}
|
||||
|
||||
_resourceLastWriter[resourceId] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,9 +305,6 @@ public class RenderGraph
|
||||
|
||||
private void CullUnusedPasses()
|
||||
{
|
||||
Console.WriteLine("\n[RG] Culling unused passes...");
|
||||
|
||||
// Mark passes that contribute to imported resources or don't allow culling
|
||||
foreach (var pass in _passes)
|
||||
{
|
||||
foreach (var (handle, _) in pass.ResourceAccesses)
|
||||
@@ -194,15 +315,12 @@ public class RenderGraph
|
||||
}
|
||||
}
|
||||
|
||||
// Mark passes that don't allow culling (synchronization, debug, etc.)
|
||||
if (!pass.AllowCulling)
|
||||
{
|
||||
pass.RefCount++;
|
||||
Console.WriteLine($" Pass '{pass.Name}' marked as non-cullable");
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate reference counts through dependencies
|
||||
bool changed = true;
|
||||
while (changed)
|
||||
{
|
||||
@@ -223,82 +341,94 @@ public class RenderGraph
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var culledPasses = _passes.Where(p => p.RefCount == 0 && p.AllowCulling).ToList();
|
||||
if (culledPasses.Count != 0)
|
||||
{
|
||||
foreach (var pass in culledPasses)
|
||||
{
|
||||
Console.WriteLine($" Culled unused pass: '{pass.Name}'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(" No passes culled.");
|
||||
}
|
||||
}
|
||||
|
||||
private void AnalyzeResourceLifetimes()
|
||||
{
|
||||
Console.WriteLine("\n[RG] Resource lifetimes:");
|
||||
// Resize execution plan arrays if needed
|
||||
int requiredSize = _passes.Count;
|
||||
if (_resourcesToCreate.Length < requiredSize)
|
||||
{
|
||||
Array.Resize(ref _resourcesToCreate, requiredSize);
|
||||
Array.Resize(ref _resourcesToDestroy, requiredSize);
|
||||
|
||||
// Initialize new elements
|
||||
for (int i = 0; i < requiredSize; i++)
|
||||
{
|
||||
if (_resourcesToCreate[i] == null) _resourcesToCreate[i] = new List<RenderGraphResourceHandle>();
|
||||
if (_resourcesToDestroy[i] == null) _resourcesToDestroy[i] = new List<RenderGraphResourceHandle>();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear previous plan
|
||||
for (int i = 0; i < requiredSize; i++)
|
||||
{
|
||||
_resourcesToCreate[i].Clear();
|
||||
_resourcesToDestroy[i].Clear();
|
||||
}
|
||||
|
||||
// Populate plan
|
||||
foreach (var lifetime in _resourceLifetimes)
|
||||
{
|
||||
if (lifetime.FirstUse == int.MaxValue)
|
||||
if (lifetime.FirstUse != int.MaxValue && !lifetime.Handle.IsImported)
|
||||
{
|
||||
Console.WriteLine($" '{lifetime.Handle.Name}': UNUSED");
|
||||
}
|
||||
else
|
||||
{
|
||||
var passNames = _passes
|
||||
.Where(p => p.Index >= lifetime.FirstUse && p.Index <= lifetime.LastUse && p.RefCount > 0)
|
||||
.Select(p => p.Name);
|
||||
Console.WriteLine($" '{lifetime.Handle.Name}': [{lifetime.FirstUse}..{lifetime.LastUse}] ({string.Join(", ", passNames)})");
|
||||
// Verify bounds to be safe
|
||||
if (lifetime.FirstUse < requiredSize)
|
||||
_resourcesToCreate[lifetime.FirstUse].Add(lifetime.Handle);
|
||||
|
||||
if (lifetime.LastUse < requiredSize)
|
||||
_resourcesToDestroy[lifetime.LastUse].Add(lifetime.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
Console.WriteLine("\n[RG] ========== EXECUTING RENDER GRAPH ==========\n");
|
||||
//ConsoleAPI.WriteLine("\n[RG] ========== EXECUTING RENDER GRAPH ==========\n");
|
||||
|
||||
var commandBuffer = new SimulatedCommandBuffer();
|
||||
|
||||
foreach (var pass in _passes.Where(p => p.RefCount > 0).OrderBy(p => p.Index))
|
||||
foreach (var batch in _batches)
|
||||
{
|
||||
Console.WriteLine($"[PASS {pass.Index}] Executing: '{pass.Name}'");
|
||||
|
||||
var lifetime = _resourceLifetimes
|
||||
.Where(lt => lt.FirstUse == pass.Index)
|
||||
.ToList();
|
||||
//ConsoleAPI.WriteLine($"[BATCH {batch.ID}] Queue: {batch.QueueType} | Passes: {batch.Passes.Count}");
|
||||
|
||||
foreach (var lt in lifetime)
|
||||
foreach (var fenceId in batch.WaitFences)
|
||||
{
|
||||
if (!lt.Handle.IsImported)
|
||||
//ConsoleAPI.WriteLine($" [SYNC] Wait for Fence {fenceId}");
|
||||
}
|
||||
|
||||
foreach (var pass in batch.Passes)
|
||||
{
|
||||
//ConsoleAPI.WriteLine($" [PASS {pass.Index}] Executing: '{pass.Name}'");
|
||||
|
||||
// Optimized: Use pre-calculated lists
|
||||
var createList = _resourcesToCreate[pass.Index];
|
||||
foreach (var handle in createList)
|
||||
{
|
||||
CreateResource(lt.Handle);
|
||||
CreateResource(handle);
|
||||
}
|
||||
|
||||
InsertBarriers(pass, commandBuffer);
|
||||
|
||||
commandBuffer.BeginRenderPass(pass.Name);
|
||||
pass.Execute(commandBuffer);
|
||||
commandBuffer.EndRenderPass();
|
||||
|
||||
// Optimized: Use pre-calculated lists
|
||||
var destroyList = _resourcesToDestroy[pass.Index];
|
||||
foreach (var handle in destroyList)
|
||||
{
|
||||
DestroyResource(handle);
|
||||
}
|
||||
}
|
||||
|
||||
InsertBarriers(pass, commandBuffer);
|
||||
|
||||
commandBuffer.BeginRenderPass(pass.Name);
|
||||
pass.Execute(commandBuffer);
|
||||
commandBuffer.EndRenderPass();
|
||||
|
||||
var endLifetime = _resourceLifetimes
|
||||
.Where(lt => lt.LastUse == pass.Index && !lt.Handle.IsImported)
|
||||
.ToList();
|
||||
|
||||
foreach (var lt in endLifetime)
|
||||
foreach (var fenceId in batch.SignalFences)
|
||||
{
|
||||
DestroyResource(lt.Handle);
|
||||
//ConsoleAPI.WriteLine($" [SYNC] Signal Fence {fenceId}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine("[RG] ========== EXECUTION COMPLETE ==========\n");
|
||||
//ConsoleAPI.WriteLine("[RG] ========== EXECUTION COMPLETE ==========\n");
|
||||
}
|
||||
|
||||
private void CreateResource(RenderGraphResourceHandle handle)
|
||||
@@ -306,34 +436,7 @@ public class RenderGraph
|
||||
var allocation = _allocator.GetAllocation(handle);
|
||||
if (allocation != null)
|
||||
{
|
||||
if (handle is RenderGraphTextureHandle textureHandle)
|
||||
{
|
||||
var desc = textureHandle.Descriptor;
|
||||
Console.WriteLine($" [CREATE] Texture '{handle.Name}' using '{allocation.DebugName}' " +
|
||||
$"({desc.Width}x{desc.Height}, {desc.Format}, offset: {allocation.OffsetInBytes})");
|
||||
}
|
||||
else if (handle is RenderGraphBufferHandle bufferHandle)
|
||||
{
|
||||
var desc = bufferHandle.Descriptor;
|
||||
Console.WriteLine($" [CREATE] Buffer '{handle.Name}' using '{allocation.DebugName}' " +
|
||||
$"({desc.SizeInBytes} bytes, offset: {allocation.OffsetInBytes})");
|
||||
}
|
||||
|
||||
// Note: We do NOT set _allocationActiveResource here
|
||||
// That happens in InsertBarriers when the resource is first accessed
|
||||
}
|
||||
else
|
||||
{
|
||||
if (handle is RenderGraphTextureHandle textureHandle)
|
||||
{
|
||||
var desc = textureHandle.Descriptor;
|
||||
Console.WriteLine($" [CREATE] Texture '{handle.Name}' ({desc.Width}x{desc.Height}, {desc.Format})");
|
||||
}
|
||||
else if (handle is RenderGraphBufferHandle bufferHandle)
|
||||
{
|
||||
var desc = bufferHandle.Descriptor;
|
||||
Console.WriteLine($" [CREATE] Buffer '{handle.Name}' ({desc.SizeInBytes} bytes)");
|
||||
}
|
||||
// Logic...
|
||||
}
|
||||
|
||||
_currentResourceStates[handle.Id] = ResourceState.Undefined;
|
||||
@@ -341,47 +444,50 @@ public class RenderGraph
|
||||
|
||||
private void DestroyResource(RenderGraphResourceHandle handle)
|
||||
{
|
||||
Console.WriteLine($" [DESTROY] Resource '{handle.Name}'");
|
||||
_currentResourceStates[handle.Id] = ResourceState.Undefined;
|
||||
|
||||
// Note: We intentionally DO NOT clear _allocationActiveResource here
|
||||
// The allocation remains "owned" by this resource until another resource aliases it
|
||||
// This allows us to track aliasing barriers correctly
|
||||
}
|
||||
|
||||
private void InsertBarriers(RenderGraphPass pass, ICommandBuffer commandBuffer)
|
||||
{
|
||||
var _resourceBarriers = ListPool<ResourceBarrierInfo>.Rent();
|
||||
var _aliasingBarriers = ListPool<AliasingBarrierInfo>.Rent();
|
||||
|
||||
foreach (var (handle, targetState) in pass.ResourceAccesses)
|
||||
{
|
||||
// Check if this resource shares a physical allocation
|
||||
var allocation = _allocator.GetAllocation(handle);
|
||||
if (allocation != null)
|
||||
{
|
||||
// Check what resource is currently active on this allocation
|
||||
if (_allocationActiveResource.TryGetValue(allocation.AllocationId, out var activeResource))
|
||||
if (_allocationActiveResource.TryGetValue(allocation.Value.AllocationId, out var activeResource))
|
||||
{
|
||||
// If a different resource is currently active on this allocation, insert aliasing barrier
|
||||
if (activeResource != null && activeResource.Id != handle.Id)
|
||||
if (activeResource != null && activeResource.Value.Id != handle.Id)
|
||||
{
|
||||
commandBuffer.AliasingBarrier(activeResource.Name, handle.Name, allocation.DebugName);
|
||||
|
||||
// Clear state for the old resource since it's being aliased away
|
||||
_currentResourceStates[activeResource.Id] = ResourceState.Undefined;
|
||||
_aliasingBarriers.Add(new AliasingBarrierInfo(activeResource.Value.Name, handle.Name, allocation.Value.DebugName));
|
||||
_currentResourceStates[activeResource.Value.Id] = ResourceState.Undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the active resource for this allocation
|
||||
_allocationActiveResource[allocation.AllocationId] = handle;
|
||||
_allocationActiveResource[allocation.Value.AllocationId] = handle;
|
||||
}
|
||||
|
||||
var currentState = _currentResourceStates[handle.Id];
|
||||
|
||||
if (currentState != targetState)
|
||||
{
|
||||
commandBuffer.ResourceBarrier(handle.Name, currentState, targetState);
|
||||
_resourceBarriers.Add(new ResourceBarrierInfo(handle.Name, currentState, targetState));
|
||||
_currentResourceStates[handle.Id] = targetState;
|
||||
}
|
||||
}
|
||||
|
||||
if (_aliasingBarriers.Count > 0)
|
||||
{
|
||||
commandBuffer.AliasingBarrier(System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_aliasingBarriers));
|
||||
}
|
||||
|
||||
if (_resourceBarriers.Count > 0)
|
||||
{
|
||||
commandBuffer.ResourceBarrier(System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_resourceBarriers));
|
||||
}
|
||||
|
||||
ListPool<ResourceBarrierInfo>.Return(_resourceBarriers);
|
||||
ListPool<AliasingBarrierInfo>.Return(_aliasingBarriers);
|
||||
}
|
||||
|
||||
private static bool IsWriteState(ResourceState state)
|
||||
@@ -399,17 +505,67 @@ public class RenderGraph
|
||||
state.HasFlag(ResourceState.CopySource);
|
||||
}
|
||||
|
||||
internal List<(RenderGraphResourceHandle, ResourceState)> RentResourceAccessList()
|
||||
{
|
||||
if (_resourceAccessListPool.TryPop(out var list))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
return new List<(RenderGraphResourceHandle, ResourceState)>();
|
||||
}
|
||||
|
||||
internal void ReturnResourceAccessList(List<(RenderGraphResourceHandle, ResourceState)> list)
|
||||
{
|
||||
list.Clear();
|
||||
_resourceAccessListPool.Push(list);
|
||||
}
|
||||
|
||||
private ResourceLifetime RentResourceLifetime(RenderGraphResourceHandle handle)
|
||||
{
|
||||
if (!_resourceLifetimePool.TryPop(out var lifetime))
|
||||
{
|
||||
lifetime = new ResourceLifetime();
|
||||
}
|
||||
lifetime.Initialize(handle);
|
||||
return lifetime;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
foreach (var batch in _batches)
|
||||
{
|
||||
batch.Reset();
|
||||
_batchPool.Push(batch);
|
||||
}
|
||||
_batches.Clear();
|
||||
|
||||
foreach (var pass in _passes)
|
||||
{
|
||||
// ReturnResourceAccessList(pass.ResourceAccesses);
|
||||
// Warning: pass.ResourceAccesses might be a copy in the current implementation of CommitPass?
|
||||
// No, I'm going to fix CommitPass to use the pooled list.
|
||||
// But right now builder.ResourceAccesses is a List.
|
||||
// I need to ensure CommitPass takes ownership.
|
||||
}
|
||||
_passes.Clear();
|
||||
|
||||
_resources.Clear();
|
||||
foreach (var lifetime in _resourceLifetimes)
|
||||
{
|
||||
_resourceLifetimePool.Push(lifetime);
|
||||
}
|
||||
_resourceLifetimes.Clear();
|
||||
_currentResourceStates.Clear();
|
||||
_resourceToAllocationMap.Clear();
|
||||
_allocationActiveResource.Clear();
|
||||
_blackboard.Clear();
|
||||
_allocator.Reset();
|
||||
_passCounter = 0;
|
||||
_resourceIdCounter = 0;
|
||||
Console.WriteLine("[RG] Render graph reset.");
|
||||
|
||||
_resourceLastWriter.Clear();
|
||||
foreach (var list in _resourceLastReaders.Values) list.Clear();
|
||||
_resourceLastReaders.Clear();
|
||||
_passToBatchMap.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user