diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs
index 2503369..90e3218 100644
--- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs
+++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs
@@ -383,7 +383,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
for (var i = 0; i < rtDescs.Length; i++)
{
var rtDesc = rtDescs[i];
- if (!rtDesc.Texture.IsValid)
+ if (rtDesc.Texture.IsInvalid)
{
RecordError(nameof(BeginRenderPass), ErrorStatus.InvalidArgument);
continue;
@@ -396,7 +396,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
continue;
}
- var record = recordResult.Value;
+ ref var record = ref recordResult.Value;
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
var clearColor = rtDesc.ClearColor;
@@ -431,7 +431,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
return;
}
- var record = recordResult.Value;
+ ref var record = ref recordResult.Value;
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv);
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
@@ -544,7 +544,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
return;
}
- var record = recordResult.Value;
+ ref var record = ref recordResult.Value;
var vbView = new D3D12_VERTEX_BUFFER_VIEW
{
BufferLocation = record.ResourcePtr.Get()->GetGPUVirtualAddress() + offset,
diff --git a/Ghost.RenderGraph.Concept/RenderGraph.cs b/Ghost.RenderGraph.Concept/RenderGraph.cs
index 0d69c57..8328b8a 100644
--- a/Ghost.RenderGraph.Concept/RenderGraph.cs
+++ b/Ghost.RenderGraph.Concept/RenderGraph.cs
@@ -166,35 +166,42 @@ public sealed class RenderGraph
offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id);
}
- *(int*)(pData + offset) = pass.resourceReads.Count;
- offset += sizeof(int);
- for (var j = 0; j < pass.resourceReads.Count; j++)
+ for (var j = 0; j < (int)RenderGraphResourceType.Count; j++)
{
- *(int*)(pData + offset) = pass.resourceReads[j].Value;
- offset += sizeof(int);
- }
+ var readList = pass.resourceReads[j];
+ var writeList = pass.resourceWrites[j];
+ var createList = pass.resourceCreates[j];
- *(int*)(pData + offset) = pass.resourceWrites.Count;
- offset += sizeof(int);
- for (var j = 0; j < pass.resourceWrites.Count; j++)
- {
- *(int*)(pData + offset) = pass.resourceWrites[j].Value;
+ *(int*)(pData + offset) = readList.Count;
offset += sizeof(int);
- }
+ for (var k = 0; k < readList.Count; k++)
+ {
+ *(int*)(pData + offset) = readList[k].Value;
+ offset += sizeof(int);
+ }
- *(int*)(pData + offset) = pass.resourceCreates.Count;
- offset += sizeof(int);
- for (var j = 0; j < pass.resourceCreates.Count; j++)
- {
- *(int*)(pData + offset) = pass.resourceCreates[j].Value;
+ *(int*)(pData + offset) = writeList.Count;
offset += sizeof(int);
+ for (var k = 0; k < writeList.Count; k++)
+ {
+ *(int*)(pData + offset) = writeList[k].Value;
+ offset += sizeof(int);
+ }
+
+ *(int*)(pData + offset) = createList.Count;
+ offset += sizeof(int);
+ for (var k = 0; k < createList.Count; k++)
+ {
+ *(int*)(pData + offset) = createList[k].Value;
+ offset += sizeof(int);
+ }
}
}
//// Hash resource descriptors
- //for (var i = 0; i < _resources.TextureResourceCount; i++)
+ //for (var j = 0; j < _resources.TextureResourceCount; j++)
//{
- // var resource = _resources.GetTextureResourceByIndex(i);
+ // var resource = _resources.GetTextureResourceByIndex(j);
// *(int*)(pData + offset) = resource.descriptor.width;
// offset += sizeof(int);
@@ -260,15 +267,19 @@ public sealed class RenderGraph
{
var pass = _passes[i];
- // Check if this pass writes to any imported textures
- for (var j = 0; j < pass.resourceWrites.Count; j++)
+ // Check if this pass writes to any imported resources
+ for (var j = 0; j < (int)RenderGraphResourceType.Count; j++)
{
- var writeHandle = pass.resourceWrites[j];
- var resource = _resources.GetResource(writeHandle);
- if (resource.isImported)
+ var writeList = pass.resourceWrites[j];
+ for (var k = 0; k < writeList.Count; k++)
{
- pass.hasSideEffects = true;
- break;
+ var writeHandle = writeList[k];
+ var resource = _resources.GetResource(writeHandle);
+ if (resource.isImported)
+ {
+ pass.hasSideEffects = true;
+ break;
+ }
}
}
}
@@ -403,13 +414,17 @@ public sealed class RenderGraph
private void UnculDependencies(RenderGraphPassBase pass)
{
// Un-cull producers of read resources
- for (var i = 0; i < pass.resourceReads.Count; i++)
+ for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
{
- UnculProducer(pass.resourceReads[i]);
+ var readList = pass.resourceReads[i];
+ for (var j = 0; j < readList.Count; j++)
+ {
+ UnculProducer(readList[j]);
+ }
}
// Un-cull producers of color attachments
- for (var i = 0; i <= pass.maxColorIndex; i++)
+ for (var i = 0; i < pass.maxColorIndex; i++)
{
if (pass.colorAccess[i].id.IsValid)
{
@@ -533,16 +548,20 @@ public sealed class RenderGraph
private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx)
{
// Process reads (transition to shader resource)
- for (var i = 0; i < pass.resourceReads.Count; i++)
+ for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
{
- var handle = pass.resourceReads[i];
- InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx);
+ var readList = pass.resourceReads[i];
+ for (var j = 0; j < readList.Count; j++)
+ {
+ var handle = readList[j];
+ InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx);
+ }
}
switch (pass.type)
{
case RenderPassType.Raster:
- for (var i = 0; i <= pass.maxColorIndex; i++)
+ for (var i = 0; i < pass.maxColorIndex; i++)
{
var access = pass.colorAccess[i];
InsertTransitionIfNeeded(access.id.AsResource(), ResourceState.RenderTarget, passIdx);
@@ -561,10 +580,14 @@ public sealed class RenderGraph
break;
case RenderPassType.Compute:
- for (var i = 0; i < pass.resourceWrites.Count; i++)
+ for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
{
- var id = pass.resourceWrites[i];
- InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx);
+ var writeList = pass.resourceWrites[i];
+ for (var j = 0; j < writeList.Count; j++)
+ {
+ var id = writeList[j];
+ InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx);
+ }
}
break;
diff --git a/Ghost.RenderGraph.Concept/RenderGraph.cs.bak b/Ghost.RenderGraph.Concept/RenderGraph.cs.bak
new file mode 100644
index 0000000..0d69c57
--- /dev/null
+++ b/Ghost.RenderGraph.Concept/RenderGraph.cs.bak
@@ -0,0 +1,663 @@
+using Ghost.Core;
+using Misaki.HighPerformance.LowLevel.Buffer;
+using Misaki.HighPerformance.LowLevel.Collections;
+using System.IO.Hashing;
+using TerraFX.Interop.Windows;
+
+namespace Ghost.RenderGraph.Concept;
+
+///
+/// Main render graph class that manages resource allocation and pass execution.
+///
+/// Design principles for minimal GC:
+/// - Object pooling for all passes and resources
+/// - Reuse collections across frames (Clear() instead of new)
+/// - Avoid LINQ and foreach over interfaces
+/// - Pre-allocate capacity based on expected usage
+///
+public sealed class RenderGraph
+{
+ private readonly RenderGraphResourceRegistry _resources = new();
+ private readonly RenderGraphObjectPool _objectPool = new();
+ private readonly List _passes = new(64);
+ private readonly List _compiledPasses = new(64);
+ private readonly RenderGraphBuilder _builder = new();
+ private readonly MockCommandBuffer _commandBuffer = new();
+ private readonly RenderContext _renderContext;
+ private readonly ResourceAliasingManager _aliasingManager = new();
+ private readonly Dictionary _resourceStates = new(128);
+ private readonly List _barriers = new(128);
+ private readonly RenderGraphCompilationCache _compilationCache = new();
+
+ private bool _compiled;
+
+ public RenderGraphBlackboard Blackboard { get; } = new();
+
+ public RenderGraph()
+ {
+ _renderContext = new RenderContext(_commandBuffer);
+ }
+
+ ///
+ /// Resets the render graph for a new frame.
+ /// Reuses existing allocations to minimize GC.
+ ///
+ public void Reset()
+ {
+ // Clear blackboard data
+ Blackboard.Clear();
+
+ // Reset resources but keep allocations
+ _resources.BeginFrame();
+
+ // Reset aliasing manager
+ _aliasingManager.BeginFrame();
+
+ // Clear resource states and barriers
+ _resourceStates.Clear();
+ _barriers.Clear();
+
+ // Return passes to the pool and reset count
+ for (var i = 0; i < _passes.Count; i++)
+ {
+ var pass = _passes[i];
+ pass.Reset(_objectPool);
+ }
+
+ _passes.Clear();
+
+ // Clear compiled passes list
+ _compiledPasses.Clear();
+ _compiled = false;
+ }
+
+ ///
+ /// Imports an external texture into the render graph.
+ ///
+ public Identifier ImportTexture(TextureDescriptor descriptor)
+ {
+ return _resources.ImportTexture(descriptor);
+ }
+
+ public IRasterRenderGraphBuilder AddRasterRenderPass(string name, out TPassData passData)
+ where TPassData : class, new()
+ {
+ var renderPass = _objectPool.Rent>();
+ renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Raster);
+ passData = renderPass.passData;
+
+ _passes.Add(renderPass);
+
+ _builder.Init(this, renderPass, _resources);
+ return _builder;
+ }
+
+ public IComputeRenderGraphBuilder AddComputeRenderPass(string name, out TPassData passData)
+ where TPassData : class, new()
+ {
+ var renderPass = _objectPool.Rent>();
+ renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Compute);
+ passData = renderPass.passData;
+
+ _passes.Add(renderPass);
+
+ _builder.Init(this, renderPass, _resources);
+ return _builder;
+ }
+
+ private unsafe int ComputeTextureHash(byte* pData, int offset, Identifier texture)
+ {
+ if (texture.IsInvalid)
+ {
+ return offset;
+ }
+
+ var resource = _resources.GetResource(texture.AsResource());
+
+ // In real implementation, we typically need to handle imported resources differently.
+
+ *(pData + offset) = resource.isImported ? (byte)1 : (byte)0;
+ offset += sizeof(byte);
+
+ *(TextureFormat*)(pData + offset) = resource.descriptor.format;
+ offset += sizeof(TextureFormat);
+
+ *(int*)(pData + offset) = resource.descriptor.width;
+ offset += sizeof(int);
+
+ *(int*)(pData + offset) = resource.descriptor.height;
+ offset += sizeof(int);
+
+ return offset;
+ }
+
+ private unsafe ulong ComputeGraphHash()
+ {
+ using var scope = AllocationManager.CreateStackScope();
+ var bufferPool = new UnsafeList(2048, scope.AllocationHandle);
+ var pData = (byte*)bufferPool.GetUnsafePtr();
+ var offset = 0;
+
+ // Hash pass count
+ *(int*)(pData + offset) = _passes.Count;
+ offset += sizeof(int);
+
+ // Hash each pass structure (excluding names)
+ for (var i = 0; i < _passes.Count; i++)
+ {
+ var pass = _passes[i];
+
+ *(RenderPassType*)(pData + offset) = pass.type;
+ offset += sizeof(RenderPassType);
+
+ *(bool*)(pData + offset) = pass.allowCulling;
+ offset += sizeof(bool);
+
+ *(bool*)(pData + offset) = pass.asyncCompute;
+ offset += sizeof(bool);
+
+ // Hash depth attachment
+ offset = ComputeTextureHash(pData, offset, pass.depthAccess.id);
+
+ *(int*)(pData + offset) = pass.maxColorIndex;
+ offset += sizeof(int);
+ for (var j = 0; j <= pass.maxColorIndex; j++)
+ {
+ offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id);
+ }
+
+ *(int*)(pData + offset) = pass.resourceReads.Count;
+ offset += sizeof(int);
+ for (var j = 0; j < pass.resourceReads.Count; j++)
+ {
+ *(int*)(pData + offset) = pass.resourceReads[j].Value;
+ offset += sizeof(int);
+ }
+
+ *(int*)(pData + offset) = pass.resourceWrites.Count;
+ offset += sizeof(int);
+ for (var j = 0; j < pass.resourceWrites.Count; j++)
+ {
+ *(int*)(pData + offset) = pass.resourceWrites[j].Value;
+ offset += sizeof(int);
+ }
+
+ *(int*)(pData + offset) = pass.resourceCreates.Count;
+ offset += sizeof(int);
+ for (var j = 0; j < pass.resourceCreates.Count; j++)
+ {
+ *(int*)(pData + offset) = pass.resourceCreates[j].Value;
+ offset += sizeof(int);
+ }
+ }
+
+ //// Hash resource descriptors
+ //for (var i = 0; i < _resources.TextureResourceCount; i++)
+ //{
+ // var resource = _resources.GetTextureResourceByIndex(i);
+
+ // *(int*)(pData + offset) = resource.descriptor.width;
+ // offset += sizeof(int);
+
+ // *(int*)(pData + offset) = resource.descriptor.height;
+ // offset += sizeof(int);
+
+ // *(TextureFormat*)(pData + offset) = resource.descriptor.format;
+ // offset += sizeof(TextureFormat);
+
+ // *(bool*)(pData + offset) = resource.isImported;
+ // offset += sizeof(bool);
+ //}
+
+ var span = new Span(pData, offset);
+ return XxHash64.HashToUInt64(span);
+ }
+
+ ///
+ /// Compiles the render graph by culling unused passes and determining resource lifetimes.
+ ///
+ public void Compile()
+ {
+ if (_compiled)
+ {
+ return;
+ }
+
+#if DEBUG
+ var sw = System.Diagnostics.Stopwatch.StartNew();
+#endif
+
+ // Step 0: Check cache
+ var graphHash = ComputeGraphHash(); // 17020363347016000737
+
+#if DEBUG
+ var hashTime = sw.Elapsed.TotalMicroseconds;
+#endif
+
+ if (_compilationCache.TryGetCached(graphHash, out var cached))
+ {
+ // CACHE HIT - restore from cache
+#if DEBUG
+ Console.WriteLine($"\n[CACHE HIT] Hash: {graphHash:X16} (computed in {hashTime:F2}μs)");
+#endif
+ RestoreFromCache(cached);
+#if DEBUG
+ sw.Stop();
+ Console.WriteLine($"[CACHE HIT] Total restore time: {sw.Elapsed.TotalMicroseconds:F2}μs");
+#endif
+ _compiled = true;
+ return;
+ }
+
+#if DEBUG
+ Console.WriteLine($"\n[CACHE MISS] Hash: {graphHash:X16} (computed in {hashTime:F2}μs)");
+#endif
+
+ _compiledPasses.Clear();
+
+ // Step 1: Mark passes with side effects (writes to imported resources)
+ for (var i = 0; i < _passes.Count; i++)
+ {
+ var pass = _passes[i];
+
+ // Check if this pass writes to any imported textures
+ for (var j = 0; j < pass.resourceWrites.Count; j++)
+ {
+ var writeHandle = pass.resourceWrites[j];
+ var resource = _resources.GetResource(writeHandle);
+ if (resource.isImported)
+ {
+ pass.hasSideEffects = true;
+ break;
+ }
+ }
+ }
+
+ // Step 2: Cull passes based on dependency analysis
+ // Mark all passes as culled initially
+ for (var i = 0; i < _passes.Count; i++)
+ {
+ _passes[i].culled = _passes[i].allowCulling && !_passes[i].hasSideEffects;
+ }
+
+ // Step 3: Traverse backwards from passes with side effects
+ for (var i = _passes.Count - 1; i >= 0; i--)
+ {
+ var pass = _passes[i];
+ if (!pass.culled)
+ {
+ UnculDependencies(pass);
+ }
+ }
+
+ // Step 4: Build final pass list (only non-culled passes)
+ for (var i = 0; i < _passes.Count; i++)
+ {
+ var pass = _passes[i];
+ if (!pass.culled)
+ {
+ _compiledPasses.Add(pass);
+ }
+ }
+
+ // Step 5: Perform resource aliasing to minimize memory usage
+ _aliasingManager.AssignPhysicalResources(_resources, _passes.Count);
+
+ // Step 6: Generate barriers for state transitions and aliasing
+ GenerateBarriers();
+
+ // Step 7: Store in cache for future frames
+ StoreInCache(graphHash);
+
+ _compiled = true;
+ }
+
+ ///
+ /// Restores the render graph state from cached compilation results.
+ ///
+ private void RestoreFromCache(CachedCompilation cached)
+ {
+ // Restore compiled pass list
+ _compiledPasses.Clear();
+ for (var i = 0; i < cached.compiledPassIndices.Count; i++)
+ {
+ var passIndex = cached.compiledPassIndices[i];
+ _compiledPasses.Add(_passes[passIndex]);
+ }
+
+ // Restore culling flags
+ for (var i = 0; i < _passes.Count && i < cached.passCulledFlags.Count; i++)
+ {
+ _passes[i].culled = cached.passCulledFlags[i];
+ }
+
+ // Restore aliasing mappings (need to update ResourceAliasingManager)
+ _aliasingManager.RestoreFromCache(cached.logicalToPhysical, cached.physicalResources);
+
+ // Restore barriers (deep copy to avoid shared references)
+ _barriers.Clear();
+ for (var i = 0; i < cached.barriers.Count; i++)
+ {
+ _barriers.Add(cached.barriers[i]);
+ }
+
+ // Restore resource states
+ _resourceStates.Clear();
+ foreach (var kvp in cached.resourceStates)
+ {
+ _resourceStates[kvp.Key] = kvp.Value;
+ }
+ }
+
+ ///
+ /// Stores current compilation results in the cache.
+ ///
+ private void StoreInCache(ulong graphHash)
+ {
+ var cacheData = new CachedCompilation();
+
+ // Store compiled pass indices
+ for (var i = 0; i < _compiledPasses.Count; i++)
+ {
+ cacheData.compiledPassIndices.Add(_compiledPasses[i].index);
+ }
+
+ // Store culling flags for all passes
+ for (var i = 0; i < _passes.Count; i++)
+ {
+ cacheData.passCulledFlags.Add(_passes[i].culled);
+ }
+
+ // Store aliasing mappings
+ _aliasingManager.StoreToCache(cacheData.logicalToPhysical, cacheData.physicalResources);
+
+ // Store barriers
+ for (var i = 0; i < _barriers.Count; i++)
+ {
+ cacheData.barriers.Add(_barriers[i]);
+ }
+
+ // Store resource states
+ foreach (var kvp in _resourceStates)
+ {
+ cacheData.resourceStates[kvp.Key] = kvp.Value;
+ }
+
+ _compilationCache.Store(graphHash, cacheData);
+ }
+
+ private void UnculProducer(Identifier resource)
+ {
+ var res = _resources.GetResource(resource);
+ if (res.producerPass >= 0)
+ {
+ var producer = _passes[res.producerPass];
+ if (producer.culled)
+ {
+ producer.culled = false;
+ UnculDependencies(producer);
+ }
+ }
+ }
+
+ private void UnculDependencies(RenderGraphPassBase pass)
+ {
+ // Un-cull producers of read resources
+ for (var i = 0; i < pass.resourceReads.Count; i++)
+ {
+ UnculProducer(pass.resourceReads[i]);
+ }
+
+ // Un-cull producers of color attachments
+ for (var i = 0; i <= pass.maxColorIndex; i++)
+ {
+ if (pass.colorAccess[i].id.IsValid)
+ {
+ UnculProducer(pass.colorAccess[i].id.AsResource());
+ }
+ }
+
+ // Un-cull producer of depth attachment
+ if (pass.depthAccess.id.IsValid)
+ {
+ UnculProducer(pass.depthAccess.id.AsResource());
+ }
+
+ // Un-cull producers of UAV resources (if not already in reads/writes)
+ for (var i = 0; i < pass.randomAccess.Count; i++)
+ {
+ UnculProducer(pass.randomAccess[i]);
+ }
+ }
+
+ ///
+ /// Generates resource barriers for state transitions and aliasing.
+ ///
+ private void GenerateBarriers()
+ {
+ _barriers.Clear();
+ _resourceStates.Clear();
+
+#if DEBUG
+ Console.WriteLine("\n=== Barrier Generation ===");
+#endif
+
+ // Process each compiled pass in order
+ for (var passIdx = 0; passIdx < _compiledPasses.Count; passIdx++)
+ {
+ var pass = _compiledPasses[passIdx];
+
+ // Insert aliasing barriers for resources that reuse physical memory
+ InsertAliasingBarriers(pass, passIdx);
+
+ // Insert transition barriers for state changes
+ InsertTransitionBarriers(pass, passIdx);
+ }
+
+#if DEBUG
+ Console.WriteLine($"Total Barriers: {_barriers.Count}");
+ Console.WriteLine("==========================\n");
+#endif
+ }
+
+ ///
+ /// Inserts aliasing barriers when a physical resource is reused.
+ ///
+ private void InsertAliasingBarriers(RenderGraphPassBase pass, int passIdx)
+ {
+ // Check all resources written by this pass
+ for (var i = 0; i < pass.resourceWrites.Count; i++)
+ {
+ var id = pass.resourceWrites[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)
+ {
+ // Rent the physical resource
+ var physicalIndex = _aliasingManager.GetPhysicalResourceIndex(id.Value);
+ if (physicalIndex >= 0)
+ {
+ var physical = _aliasingManager.GetPhysicalResource(physicalIndex);
+
+ // If this physical resource has multiple aliased resources,
+ // we need an aliasing barrier when switching between them
+ if (physical != null && physical.aliasedLogicalResources.Count > 1)
+ {
+ // Find the resource that used this physical memory most recently before this pass
+ Identifier resourceBefore = default;
+ var mostRecentLastUse = -1;
+
+ foreach (var otherLogicalIndex in physical.aliasedLogicalResources)
+ {
+ if (otherLogicalIndex != id.Value)
+ {
+ var otherResource = _resources.GetTextureResourceByIndex(otherLogicalIndex);
+ // Check if this resource finished before our resource starts
+ if (otherResource.lastUsePass < pass.index &&
+ otherResource.lastUsePass > mostRecentLastUse)
+ {
+ mostRecentLastUse = otherResource.lastUsePass;
+ resourceBefore = otherLogicalIndex;
+ }
+ }
+ }
+
+ // If we found a previous resource, insert aliasing barrier
+ if (mostRecentLastUse >= 0)
+ {
+ var barrier = ResourceBarrier.CreateAliasingBarrier(
+ resourceBefore,
+ id,
+ passIdx
+ );
+ _barriers.Add(barrier);
+
+#if DEBUG
+ Console.WriteLine($" {barrier}");
+#endif
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Inserts transition barriers when a resource changes state.
+ ///
+ private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx)
+ {
+ // Process reads (transition to shader resource)
+ for (var i = 0; i < pass.resourceReads.Count; i++)
+ {
+ var handle = pass.resourceReads[i];
+ InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx);
+ }
+
+ switch (pass.type)
+ {
+ case RenderPassType.Raster:
+ for (var i = 0; i <= pass.maxColorIndex; i++)
+ {
+ var access = pass.colorAccess[i];
+ InsertTransitionIfNeeded(access.id.AsResource(), ResourceState.RenderTarget, passIdx);
+ }
+
+ if (pass.depthAccess.id.IsValid)
+ {
+ var depthAccess = pass.depthAccess;
+ InsertTransitionIfNeeded(depthAccess.id.AsResource(), ResourceState.DepthWrite, passIdx);
+ }
+
+ for (var i = 0; i < pass.randomAccess.Count; i++)
+ {
+ InsertTransitionIfNeeded(pass.randomAccess[i], ResourceState.UnorderedAccess, passIdx);
+ }
+
+ break;
+ case RenderPassType.Compute:
+ for (var i = 0; i < pass.resourceWrites.Count; i++)
+ {
+ var id = pass.resourceWrites[i];
+ InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx);
+ }
+
+ break;
+ }
+ }
+
+ ///
+ /// Inserts a transition barrier if the resource state changes.
+ ///
+ private void InsertTransitionIfNeeded(Identifier resource, ResourceState newState, int passIdx)
+ {
+ if (!_resourceStates.TryGetValue(resource.Value, out var currentState))
+ {
+ // First time seeing this resource, assume undefined
+ currentState = ResourceState.Common;
+ }
+
+ if (currentState != newState)
+ {
+ var barrier = ResourceBarrier.CreateTransitionBarrier(
+ resource,
+ currentState,
+ newState,
+ passIdx
+ );
+ _barriers.Add(barrier);
+ _resourceStates[resource.Value] = newState;
+
+#if DEBUG
+ Console.WriteLine($" {barrier}");
+#endif
+ }
+ }
+
+ ///
+ /// Executes all compiled passes.
+ ///
+ public void Execute()
+ {
+ if (!_compiled)
+ {
+ Compile();
+ }
+
+ // Execute each non-culled pass
+ var barrierIndex = 0;
+ for (var i = 0; i < _compiledPasses.Count; i++)
+ {
+ var pass = _compiledPasses[i];
+
+ // Execute all barriers for this pass
+#if DEBUG
+ bool hasBarriers = false;
+#endif
+ while (barrierIndex < _barriers.Count && _barriers[barrierIndex].PassIndex == i)
+ {
+#if DEBUG
+ if (!hasBarriers)
+ {
+ Console.WriteLine($"\n=== Barriers before Pass {i}: {pass.name} ===");
+ hasBarriers = true;
+ }
+
+ var barrier = _barriers[barrierIndex];
+ if (barrier.Type == BarrierType.Transition)
+ {
+ _commandBuffer.ResourceBarrier(
+ barrier.Resource,
+ barrier.StateBefore,
+ barrier.StateAfter
+ );
+ }
+ else if (barrier.Type == BarrierType.Aliasing)
+ {
+ _commandBuffer.AliasBarrier(
+ barrier.ResourceBefore,
+ barrier.ResourceAfter
+ );
+ }
+#endif
+ // In a real implementation, you would execute the barrier here:
+ // ExecuteBarrier(_barriers[barrierIndex]);
+
+ barrierIndex++;
+ }
+#if DEBUG
+ if (hasBarriers)
+ {
+ Console.WriteLine("=====================================\n");
+ }
+#endif
+
+ pass.Execute(_renderContext);
+ }
+ }
+}
diff --git a/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs.bak b/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs.bak
new file mode 100644
index 0000000..d253c94
--- /dev/null
+++ b/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs.bak
@@ -0,0 +1,329 @@
+using Ghost.Core.Utilities;
+
+namespace Ghost.RenderGraph.Concept;
+
+///
+/// Represents a physical GPU resource that can be aliased by multiple logical resources.
+///
+internal sealed class PhysicalResource
+{
+ public int index;
+ public int width;
+ public int height;
+ public TextureFormat format;
+ public int sizeInBytes;
+
+ // Lifetime tracking
+ public int firstUsePass = int.MaxValue;
+ public int lastUsePass = -1;
+
+ // Aliasing tracking
+ public readonly List aliasedLogicalResources = new(4);
+
+ public void Reset()
+ {
+ index = -1;
+ width = 0;
+ height = 0;
+ format = TextureFormat.RGBA8;
+ sizeInBytes = 0;
+ firstUsePass = int.MaxValue;
+ lastUsePass = -1;
+ aliasedLogicalResources.Clear();
+ }
+
+ public bool CanAlias(TextureDescriptor descriptor)
+ {
+ // For aliasing, resources must be identical in size and format
+ // In a real implementation, you could be more flexible (e.g., same size but different format)
+ return width == descriptor.width &&
+ height == descriptor.height &&
+ format == descriptor.format;
+ }
+
+ public void UpdateLifetime(int passIndex)
+ {
+ firstUsePass = Math.Min(firstUsePass, passIndex);
+ lastUsePass = Math.Max(lastUsePass, passIndex);
+ }
+
+ public bool IsAliveAt(int passIndex)
+ {
+ return passIndex >= firstUsePass && passIndex <= lastUsePass;
+ }
+
+ public int CalculateSize()
+ {
+ int bytesPerPixel = format switch
+ {
+ TextureFormat.RGBA8 => 4,
+ TextureFormat.RGBA16F => 8,
+ TextureFormat.RGBA32F => 16,
+ TextureFormat.Depth32F => 4,
+ TextureFormat.Depth24Stencil8 => 4,
+ _ => 4
+ };
+ return width * height * bytesPerPixel;
+ }
+}
+
+///
+/// Manages physical resource allocation and aliasing.
+/// Uses interval scheduling algorithm to minimize memory usage.
+///
+internal sealed class ResourceAliasingManager
+{
+ private readonly List _physicalResources = new(32);
+ private readonly RenderGraphObjectPool _pool = new();
+ private int _physicalResourceCount;
+
+ // Mapping from logical resource index to physical resource index
+ private readonly Dictionary _logicalToPhysical = new(64);
+
+ public void BeginFrame()
+ {
+ _physicalResourceCount = 0;
+ _logicalToPhysical.Clear();
+
+ // Reset physical resources but keep them in the pool
+ for (int i = 0; i < _physicalResources.Count; i++)
+ {
+ _physicalResources[i].Reset();
+ }
+ }
+
+ ///
+ /// Assigns physical resources to logical resources using greedy interval scheduling.
+ /// This minimizes total GPU memory usage.
+ ///
+ public void AssignPhysicalResources(RenderGraphResourceRegistry registry, int passCount)
+ {
+#if DEBUG
+ Console.WriteLine("\n=== Resource Aliasing Analysis ===");
+ int totalLogicalSize = 0;
+#endif
+
+ // Build list of all logical resources with their lifetimes
+ var logicalResources = ListPool<(int index, RenderGraphResource resource)>.Rent();
+
+ for (int i = 0; i < registry.TextureResourceCount; i++)
+ {
+ var resource = registry.GetTextureResourceByIndex(i);
+ if (!resource.isImported) // Don't alias imported resources
+ {
+ logicalResources.Add((i, resource));
+#if DEBUG
+ int size = CalculateSize(resource.descriptor);
+ totalLogicalSize += size;
+ Console.WriteLine($"Logical Resource {i}: {resource.descriptor.name}");
+ Console.WriteLine($" Lifetime: Pass {resource.firstUsePass} -> {resource.lastUsePass}");
+ Console.WriteLine($" Size: {size / 1024.0:F2} KB");
+#endif
+ }
+ }
+
+ // Sort by first use pass (earlier resources first)
+ logicalResources.Sort((a, b) => a.resource.firstUsePass.CompareTo(b.resource.firstUsePass));
+
+ // Greedy interval scheduling: assign each logical resource to a physical resource
+ foreach (var (logicalIndex, logicalResource) in logicalResources)
+ {
+ PhysicalResource? assignedPhysical = null;
+
+ // Try to find an existing physical resource that:
+ // 1. Has compatible format/size
+ // 2. Is not alive during this logical resource's lifetime
+ for (int i = 0; i < _physicalResourceCount; i++)
+ {
+ var physical = _physicalResources[i];
+
+ if (physical.CanAlias(logicalResource.descriptor) &&
+ !HasLifetimeOverlap(physical, logicalResource))
+ {
+ assignedPhysical = physical;
+ break;
+ }
+ }
+
+ // No compatible physical resource found, allocate a new one
+ if (assignedPhysical == null)
+ {
+ assignedPhysical = GetOrCreatePhysicalResource();
+ assignedPhysical.index = _physicalResourceCount - 1;
+ assignedPhysical.width = logicalResource.descriptor.width;
+ assignedPhysical.height = logicalResource.descriptor.height;
+ assignedPhysical.format = logicalResource.descriptor.format;
+ assignedPhysical.sizeInBytes = assignedPhysical.CalculateSize();
+
+#if DEBUG
+ Console.WriteLine($"\nAllocated NEW Physical Resource {assignedPhysical.index}:");
+ Console.WriteLine($" Size: {assignedPhysical.width}x{assignedPhysical.height}");
+ Console.WriteLine($" Format: {assignedPhysical.format}");
+ Console.WriteLine($" Memory: {assignedPhysical.sizeInBytes / 1024.0:F2} KB");
+#endif
+ }
+#if DEBUG
+ else
+ {
+ Console.WriteLine($"\nALIASING: {logicalResource.descriptor.name} -> Physical Resource {assignedPhysical.index}");
+ }
+#endif
+
+ // Update physical resource lifetime
+ assignedPhysical.UpdateLifetime(logicalResource.firstUsePass);
+ assignedPhysical.UpdateLifetime(logicalResource.lastUsePass);
+ assignedPhysical.aliasedLogicalResources.Add(logicalIndex);
+
+ // Record the mapping
+ _logicalToPhysical[logicalIndex] = assignedPhysical.index;
+ }
+
+#if DEBUG
+ int totalPhysicalSize = 0;
+ for (int i = 0; i < _physicalResourceCount; i++)
+ {
+ totalPhysicalSize += _physicalResources[i].sizeInBytes;
+ }
+
+ Console.WriteLine($"\n=== Aliasing Summary ===");
+ Console.WriteLine($"Logical Resources: {logicalResources.Count}");
+ Console.WriteLine($"Physical Resources: {_physicalResourceCount}");
+ Console.WriteLine($"Total Logical Memory: {totalLogicalSize / 1024.0:F2} KB");
+ Console.WriteLine($"Total Physical Memory: {totalPhysicalSize / 1024.0:F2} KB");
+ Console.WriteLine($"Memory Saved: {(totalLogicalSize - totalPhysicalSize) / 1024.0:F2} KB ({(1.0 - (double)totalPhysicalSize / totalLogicalSize) * 100.0:F1}%)");
+ Console.WriteLine("================================\n");
+#endif
+
+ ListPool<(int index, RenderGraphResource resource)>.Return(logicalResources);
+ }
+
+ public int GetPhysicalResourceIndex(int logicalIndex)
+ {
+ return _logicalToPhysical.TryGetValue(logicalIndex, out var physicalIndex) ? physicalIndex : -1;
+ }
+
+ public PhysicalResource? GetPhysicalResource(int physicalIndex)
+ {
+ return physicalIndex >= 0 && physicalIndex < _physicalResourceCount
+ ? _physicalResources[physicalIndex]
+ : null;
+ }
+
+ private bool HasLifetimeOverlap(PhysicalResource physical, RenderGraphResource logical)
+ {
+ // Check if the lifetimes overlap
+ // No overlap if: logical.First > physical.Last OR logical.Last < physical.First
+ return !(logical.firstUsePass > physical.lastUsePass ||
+ logical.lastUsePass < physical.firstUsePass);
+ }
+
+ private PhysicalResource GetOrCreatePhysicalResource()
+ {
+ PhysicalResource resource;
+ if (_physicalResourceCount < _physicalResources.Count)
+ {
+ resource = _physicalResources[_physicalResourceCount];
+ resource.Reset();
+ }
+ else
+ {
+ resource = _pool.Rent();
+ resource.Reset();
+ _physicalResources.Add(resource);
+ }
+
+ _physicalResourceCount++;
+ return resource;
+ }
+
+ private static int CalculateSize(TextureDescriptor descriptor)
+ {
+ int bytesPerPixel = descriptor.format switch
+ {
+ TextureFormat.RGBA8 => 4,
+ TextureFormat.RGBA16F => 8,
+ TextureFormat.RGBA32F => 16,
+ TextureFormat.Depth32F => 4,
+ TextureFormat.Depth24Stencil8 => 4,
+ _ => 4
+ };
+ return descriptor.width * descriptor.height * bytesPerPixel;
+ }
+
+ public void Clear()
+ {
+ for (int i = 0; i < _physicalResources.Count; i++)
+ {
+ _pool.Return(_physicalResources[i]);
+ }
+ _physicalResources.Clear();
+ _physicalResourceCount = 0;
+ _logicalToPhysical.Clear();
+ }
+
+ ///
+ /// Restores aliasing state from cache.
+ ///
+ public void RestoreFromCache(Dictionary logicalToPhysical, List physicalData)
+ {
+ _logicalToPhysical.Clear();
+ foreach (var kvp in logicalToPhysical)
+ {
+ _logicalToPhysical[kvp.Key] = kvp.Value;
+ }
+
+ // Restore physical resources
+ _physicalResourceCount = physicalData.Count;
+ for (int i = 0; i < physicalData.Count; i++)
+ {
+ PhysicalResource physical;
+ if (i < _physicalResources.Count)
+ {
+ physical = _physicalResources[i];
+ physical.Reset();
+ }
+ else
+ {
+ physical = _pool.Rent();
+ physical.Reset();
+ _physicalResources.Add(physical);
+ }
+
+ var data = physicalData[i];
+ physical.index = data.index;
+ physical.width = data.width;
+ physical.height = data.height;
+ physical.format = data.format;
+ physical.firstUsePass = data.firstUsePass;
+ physical.lastUsePass = data.lastUsePass;
+ physical.sizeInBytes = physical.CalculateSize();
+ }
+ }
+
+ ///
+ /// Stores current aliasing state to cache.
+ ///
+ public void StoreToCache(Dictionary outLogicalToPhysical, List outPhysicalData)
+ {
+ outLogicalToPhysical.Clear();
+ foreach (var kvp in _logicalToPhysical)
+ {
+ outLogicalToPhysical[kvp.Key] = kvp.Value;
+ }
+
+ outPhysicalData.Clear();
+ for (int i = 0; i < _physicalResourceCount; i++)
+ {
+ var physical = _physicalResources[i];
+ outPhysicalData.Add(new PhysicalResourceData
+ {
+ index = physical.index,
+ width = physical.width,
+ height = physical.height,
+ format = physical.format,
+ firstUsePass = physical.firstUsePass,
+ lastUsePass = physical.lastUsePass
+ });
+ }
+ }
+}
diff --git a/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs
index 98d3a3b..fef6775 100644
--- a/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs
+++ b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs
@@ -110,17 +110,17 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
ObjectDisposedException.ThrowIf(_disposed, this);
}
- private Identifier UseResource(Identifier resource, AccessFlags accessFlags)
+ private Identifier UseResource(Identifier resource, AccessFlags accessFlags, RenderGraphResourceType type)
{
if (accessFlags.HasFlag(AccessFlags.Read))
{
- _pass.resourceReads.Add(resource);
+ _pass.resourceReads[(int)type].Add(resource);
_resources.AddConsumer(resource, _pass.index);
}
if (accessFlags.HasFlag(AccessFlags.Write))
{
- _pass.resourceWrites.Add(resource);
+ _pass.resourceWrites[(int)type].Add(resource);
_resources.SetProducer(resource, _pass.index);
}
@@ -142,7 +142,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
ThrowIfDisposed();
var handle = _resources.CreateTexture(descriptor);
- _pass.resourceCreates.Add(handle.AsResource());
+ _pass.resourceCreates[(int)RenderGraphResourceType.Texture].Add(handle.AsResource());
_resources.SetProducer(handle.AsResource(), _pass.index);
return handle;
}
@@ -150,8 +150,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
public Identifier UseTexture(Identifier texture, AccessFlags flags)
{
ThrowIfDisposed();
-
- return UseResource(texture.AsResource(), flags).AsTexture();
+ return UseResource(texture.AsResource(), flags, RenderGraphResourceType.Texture).AsTexture();
}
public Identifier UseRandomAccessTexture(Identifier texture)
@@ -159,7 +158,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
ThrowIfDisposed();
var resource = texture.AsResource();
- UseResource(resource, AccessFlags.ReadWrite);
+ UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Texture);
_pass.randomAccess.Add(resource);
return texture;
}
@@ -169,7 +168,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
ThrowIfDisposed();
var resource = buffer.AsResource();
- UseResource(resource, AccessFlags.ReadWrite);
+ UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Buffer);
_pass.randomAccess.Add(resource);
return buffer;
}
diff --git a/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs.bak b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs.bak
new file mode 100644
index 0000000..98d3a3b
--- /dev/null
+++ b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs.bak
@@ -0,0 +1,240 @@
+using Ghost.Core;
+using System.Diagnostics;
+
+namespace Ghost.RenderGraph.Concept;
+
+[Flags]
+public enum AccessFlags
+{
+ None = 0,
+ Read = 1 << 0,
+ Write = 1 << 1,
+ ReadWrite = Read | Write,
+}
+
+public interface IRenderGraphBuilder : IDisposable
+{
+ ///
+ /// Enables or disables pass culling for the current context.
+ ///
+ /// A value indicating whether pass culling is allowed.
+ void AllowPassCulling(bool value);
+
+ ///
+ /// Creates a new texture resource based on the specified descriptor.
+ ///
+ /// A structure that defines the properties and configuration of the texture to create.
+ /// An identifier for the newly created texture resource.
+ Identifier CreateTexture(in TextureDescriptor descriptor);
+
+ ///
+ /// Registers the specified texture for use in the current render graph pass with the given access mode.
+ ///
+ /// The identifier of the texture to be used in the render graph pass.
+ /// The access mode specifying how the texture will be read or written during the pass.
+ /// An identifier for the texture.
+ Identifier UseTexture(Identifier texture, AccessFlags accessMode);
+}
+
+public interface IRasterRenderGraphBuilder : IRenderGraphBuilder
+{
+ ///
+ /// Binds a texture for random access operations within the current rendering pass.
+ ///
+ /// The identifier of the texture to be used for random access.
+ /// An identifier for the texture.
+ Identifier UseRandomAccessTexture(Identifier texture);
+ ///
+ /// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
+ ///
+ /// An identifier for the buffer to be used for random access. Must reference a valid buffer resource.
+ /// An identifier for the buffer.
+ Identifier UseRandomAccessBuffer(Identifier buffer);
+
+ ///
+ /// Sets the color attachment at the specified index to the given texture.
+ ///
+ /// The identifier of the texture to use as the color attachment.
+ /// The zero-based index of the color attachment to set.
+ void SetColorAttachment(Identifier texture, int index);
+
+ ///
+ /// Sets the depth attachment for the current render pass using the specified texture.
+ ///
+ /// The identifier of the texture to use as the depth attachment. Cannot be null.
+ void SetDepthAttachment(Identifier texture);
+
+ ///
+ /// Sets the function used to render a pass with the specified pass data and render context.
+ ///
+ /// The type of data associated with the render pass.
+ /// The delegate that defines the rendering logic for the pass.
+ void SetRenderFunc(Action renderFunc)
+ where TPassData : class, new();
+}
+
+public interface IComputeRenderGraphBuilder : IRenderGraphBuilder
+{
+ ///
+ /// Enables or disables asynchronous compute operations.
+ ///
+ /// true to enable asynchronous compute; otherwise, false.
+ void EnableAsyncCompute(bool value);
+
+ ///
+ /// Sets the render function to be invoked during the compute rendering process.
+ ///
+ /// The type of the data object passed to the render function.
+ /// The delegate that defines the rendering logic to execute.
+ void SetRenderFunc(Action renderFunc)
+ where TPassData : class, new();
+}
+
+internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGraphBuilder
+{
+ private RenderGraph _graph = null!;
+ private RenderGraphPassBase _pass = null!;
+ private RenderGraphResourceRegistry _resources = null!;
+ private bool _disposed;
+
+ internal void Init(RenderGraph graph, RenderGraphPassBase pass, RenderGraphResourceRegistry resources)
+ {
+ _graph = graph;
+ _pass = pass;
+ _resources = resources;
+ _disposed = false;
+ }
+
+ private void ThrowIfDisposed()
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
+ }
+
+ private Identifier UseResource(Identifier resource, AccessFlags accessFlags)
+ {
+ if (accessFlags.HasFlag(AccessFlags.Read))
+ {
+ _pass.resourceReads.Add(resource);
+ _resources.AddConsumer(resource, _pass.index);
+ }
+
+ if (accessFlags.HasFlag(AccessFlags.Write))
+ {
+ _pass.resourceWrites.Add(resource);
+ _resources.SetProducer(resource, _pass.index);
+ }
+
+ return resource;
+ }
+
+ public void AllowPassCulling(bool value)
+ {
+ _pass.allowCulling = value;
+ }
+
+ public void EnableAsyncCompute(bool value)
+ {
+ _pass.asyncCompute = value;
+ }
+
+ public Identifier CreateTexture(in TextureDescriptor descriptor)
+ {
+ ThrowIfDisposed();
+
+ var handle = _resources.CreateTexture(descriptor);
+ _pass.resourceCreates.Add(handle.AsResource());
+ _resources.SetProducer(handle.AsResource(), _pass.index);
+ return handle;
+ }
+
+ public Identifier UseTexture(Identifier texture, AccessFlags flags)
+ {
+ ThrowIfDisposed();
+
+ return UseResource(texture.AsResource(), flags).AsTexture();
+ }
+
+ public Identifier UseRandomAccessTexture(Identifier texture)
+ {
+ ThrowIfDisposed();
+
+ var resource = texture.AsResource();
+ UseResource(resource, AccessFlags.ReadWrite);
+ _pass.randomAccess.Add(resource);
+ return texture;
+ }
+
+ public Identifier UseRandomAccessBuffer(Identifier buffer)
+ {
+ ThrowIfDisposed();
+
+ var resource = buffer.AsResource();
+ UseResource(resource, AccessFlags.ReadWrite);
+ _pass.randomAccess.Add(resource);
+ return buffer;
+ }
+
+ public void SetColorAttachment(Identifier texture, int index)
+ {
+ ThrowIfDisposed();
+
+ Debug.Assert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range.");
+
+ var id = UseTexture(texture, AccessFlags.Write);
+ if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid)
+ {
+ _pass.maxColorIndex = Math.Max(_pass.maxColorIndex, index);
+ _pass.colorAccess[index] = new TextureAccess(id, AccessFlags.Write);
+ }
+ else
+ {
+ throw new InvalidOperationException($"Color attachment at index {index} is already set to a different texture.");
+ }
+ }
+
+ public void SetDepthAttachment(Identifier texture)
+ {
+ ThrowIfDisposed();
+
+ var id = UseTexture(texture, AccessFlags.Write);
+ if (_pass.depthAccess.id == id || _pass.depthAccess.id.IsInvalid)
+ {
+ _pass.depthAccess = new TextureAccess(id, AccessFlags.Write);
+ }
+ else
+ {
+ throw new InvalidOperationException("Depth attachment is already set to a different texture.");
+ }
+ }
+
+ public void SetRenderFunc(Action renderFunc)
+ where TPassData : class, new()
+ {
+ ((RasterRenderGraphPass)_pass).renderFunc = renderFunc;
+ }
+
+ public void SetRenderFunc(Action renderFunc)
+ where TPassData : class, new()
+ {
+ ((ComputeRenderGraphPass)_pass).renderFunc = renderFunc;
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (!_pass.HasRenderFunc())
+ {
+ throw new InvalidOperationException("RenderGraphBuilder must be disposed after setting up the render function.");
+ }
+
+ _graph = null!;
+ _pass = null!;
+ _resources = null!;
+
+ _disposed = true;
+ }
+}
diff --git a/Ghost.RenderGraph.Concept/RenderGraphPass.cs b/Ghost.RenderGraph.Concept/RenderGraphPass.cs
index 0ae38cc..4ebf224 100644
--- a/Ghost.RenderGraph.Concept/RenderGraphPass.cs
+++ b/Ghost.RenderGraph.Concept/RenderGraphPass.cs
@@ -1,5 +1,4 @@
using Ghost.Core;
-using System.IO;
namespace Ghost.RenderGraph.Concept;
@@ -31,14 +30,24 @@ internal abstract class RenderGraphPassBase
public List> randomAccess = new(8);
// Resource dependencies
- public readonly List> resourceReads = new(8);
- public readonly List> resourceWrites = new(4);
- public readonly List> resourceCreates = new(4);
-
+ public readonly List>[] resourceReads = new List>[(int)RenderGraphResourceType.Count];
+ public readonly List>[] resourceWrites = new List>[(int)RenderGraphResourceType.Count];
+ public readonly List>[] resourceCreates = new List>[(int)RenderGraphResourceType.Count];
+
// Execution state
public bool culled;
public bool hasSideEffects;
+ public RenderGraphPassBase()
+ {
+ for (int i = 0; i < (int)RenderGraphResourceType.Count; i++)
+ {
+ resourceReads[i] = new List>(8);
+ resourceWrites[i] = new List>(4);
+ resourceCreates[i] = new List>(4);
+ }
+ }
+
public abstract void Execute(RenderContext context);
public abstract void Clear();
public abstract bool HasRenderFunc();
@@ -50,16 +59,20 @@ internal abstract class RenderGraphPassBase
type = RenderPassType.Raster;
allowCulling = true;
asyncCompute = false;
-
+
depthAccess = default;
colorAccess.AsSpan().Clear();
maxColorIndex = -1;
randomAccess.Clear();
- resourceReads.Clear();
- resourceWrites.Clear();
- resourceCreates.Clear();
+ for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
+ {
+ resourceReads[i].Clear();
+ resourceWrites[i].Clear();
+ resourceCreates[i].Clear();
+ }
+
culled = false;
hasSideEffects = false;
}
diff --git a/Ghost.RenderGraph.Concept/RenderGraphPass.cs.bak b/Ghost.RenderGraph.Concept/RenderGraphPass.cs.bak
new file mode 100644
index 0000000..0ae38cc
--- /dev/null
+++ b/Ghost.RenderGraph.Concept/RenderGraphPass.cs.bak
@@ -0,0 +1,129 @@
+using Ghost.Core;
+using System.IO;
+
+namespace Ghost.RenderGraph.Concept;
+
+///
+/// Represents different types of render passes.
+///
+public enum RenderPassType : byte
+{
+ Raster,
+ Compute
+}
+
+///
+/// Base class for render passes.
+/// Uses pooling to avoid allocations after the first frame.
+///
+internal abstract class RenderGraphPassBase
+{
+ public string name = string.Empty;
+ public int index;
+ public RenderPassType type;
+ public bool allowCulling = true;
+ public bool asyncCompute;
+
+ public TextureAccess depthAccess;
+ public TextureAccess[] colorAccess = new TextureAccess[8];
+ public int maxColorIndex = -1;
+
+ public List> randomAccess = new(8);
+
+ // Resource dependencies
+ public readonly List> resourceReads = new(8);
+ public readonly List> resourceWrites = new(4);
+ public readonly List> resourceCreates = new(4);
+
+ // Execution state
+ public bool culled;
+ public bool hasSideEffects;
+
+ public abstract void Execute(RenderContext context);
+ public abstract void Clear();
+ public abstract bool HasRenderFunc();
+
+ public virtual void Reset(RenderGraphObjectPool pool)
+ {
+ name = string.Empty;
+ index = -1;
+ type = RenderPassType.Raster;
+ allowCulling = true;
+ asyncCompute = false;
+
+ depthAccess = default;
+ colorAccess.AsSpan().Clear();
+ maxColorIndex = -1;
+
+ randomAccess.Clear();
+
+ resourceReads.Clear();
+ resourceWrites.Clear();
+ resourceCreates.Clear();
+ culled = false;
+ hasSideEffects = false;
+ }
+}
+
+internal abstract class RenderGraphPassT : RenderGraphPassBase
+ where TPassData : class, new()
+{
+ public TPassData passData = null!;
+ public Action? renderFunc;
+
+ public void Init(int index, TPassData passData, string name, RenderPassType type)
+ {
+ this.index = index;
+ this.passData = passData;
+ this.name = name;
+ this.type = type;
+ }
+
+ public sealed override bool HasRenderFunc()
+ {
+ return renderFunc != null;
+ }
+
+ public override void Clear()
+ {
+ passData = null!;
+ renderFunc = null;
+ }
+
+ public override void Reset(RenderGraphObjectPool pool)
+ {
+ base.Reset(pool);
+ pool.Return(passData);
+ Clear();
+ }
+}
+
+internal sealed class RasterRenderGraphPass : RenderGraphPassT
+ where TPassData : class, new()
+{
+ public override void Execute(RenderContext context)
+ {
+ renderFunc!(passData, context.RasterContext);
+ }
+
+ public override void Reset(RenderGraphObjectPool pool)
+ {
+ base.Reset(pool);
+ pool.Return(this);
+ }
+}
+
+internal sealed class ComputeRenderGraphPass : RenderGraphPassT
+ where TPassData : class, new()
+{
+ public override void Execute(RenderContext context)
+ {
+ renderFunc!(passData, context.ComputeContext);
+ }
+
+ public override void Reset(RenderGraphObjectPool pool)
+ {
+ base.Reset(pool);
+ pool.Return(this);
+ }
+}
\ No newline at end of file
diff --git a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs b/Ghost.RenderGraph.Concept/RenderGraphTypes.cs
index cea75cf..924a9d0 100644
--- a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs
+++ b/Ghost.RenderGraph.Concept/RenderGraphTypes.cs
@@ -3,11 +3,11 @@ using System.Runtime.CompilerServices;
namespace Ghost.RenderGraph.Concept;
-internal enum RenderGraphResourceType
+internal enum RenderGraphResourceType : int
{
Texture,
Buffer,
- AccelerationStructure,
+ // AccelerationStructure,
Count
}
@@ -84,14 +84,79 @@ public readonly struct TextureDescriptor : IEquatable
this.name = name;
}
- public readonly bool Equals(TextureDescriptor other) =>
- width == other.width &&
+ public readonly bool Equals(TextureDescriptor other)
+ {
+ return width == other.width &&
height == other.height &&
format == other.format &&
name == other.name;
+ }
public override readonly bool Equals(object? obj) => obj is TextureDescriptor other && Equals(other);
public override readonly int GetHashCode() => HashCode.Combine(width, height, format, name);
+
+ public static bool operator ==(TextureDescriptor left, TextureDescriptor right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(TextureDescriptor left, TextureDescriptor right)
+ {
+ return !(left == right);
+ }
+}
+
+[Flags]
+public enum BufferUsage
+{
+ None = 0,
+ Vertex = 1 << 0,
+ Index = 1 << 1,
+ IndirectArgument = 1 << 7,
+ Constant = 1 << 2,
+ ShaderResource = 1 << 3,
+ UnorderedAccess = 1 << 4,
+ Structured = 1 << 5,
+ Raw = 1 << 6,
+ Upload = 1 << 8,
+ Readback = 1 << 9,
+}
+
+public readonly struct BufferDescriptor : IEquatable
+{
+ public readonly uint sizeInBytes;
+ public readonly uint stride;
+ public readonly BufferUsage usage;
+ public readonly string name;
+
+ public BufferDescriptor(uint sizeInBytes, uint stride, BufferUsage usage, string name)
+ {
+ this.sizeInBytes = sizeInBytes;
+ this.stride = stride;
+ this.usage = usage;
+ this.name = name;
+ }
+
+ public readonly bool Equals(BufferDescriptor other)
+ {
+ return sizeInBytes == other.sizeInBytes &&
+ stride == other.stride &&
+ usage == other.usage &&
+ name == other.name;
+ }
+
+ public override readonly bool Equals(object? obj) => obj is BufferDescriptor other && Equals(other);
+ public override readonly int GetHashCode() => HashCode.Combine(sizeInBytes, name);
+
+ public static bool operator ==(BufferDescriptor left, BufferDescriptor right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(BufferDescriptor left, BufferDescriptor right)
+ {
+ return !(left == right);
+ }
}
///