small backup

This commit is contained in:
2026-01-13 14:50:55 +09:00
parent 02df8d7732
commit ac36bbf8c7
9 changed files with 1522 additions and 61 deletions

View File

@@ -383,7 +383,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
for (var i = 0; i < rtDescs.Length; i++) for (var i = 0; i < rtDescs.Length; i++)
{ {
var rtDesc = rtDescs[i]; var rtDesc = rtDescs[i];
if (!rtDesc.Texture.IsValid) if (rtDesc.Texture.IsInvalid)
{ {
RecordError(nameof(BeginRenderPass), ErrorStatus.InvalidArgument); RecordError(nameof(BeginRenderPass), ErrorStatus.InvalidArgument);
continue; continue;
@@ -396,7 +396,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
continue; continue;
} }
var record = recordResult.Value; ref var record = ref recordResult.Value;
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv); var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
var format = record.desc.TextureDescription.Format.ToDXGIFormat(); var format = record.desc.TextureDescription.Format.ToDXGIFormat();
var clearColor = rtDesc.ClearColor; var clearColor = rtDesc.ClearColor;
@@ -431,7 +431,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
return; return;
} }
var record = recordResult.Value; ref var record = ref recordResult.Value;
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv); var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv);
var format = record.desc.TextureDescription.Format.ToDXGIFormat(); var format = record.desc.TextureDescription.Format.ToDXGIFormat();
@@ -544,7 +544,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
return; return;
} }
var record = recordResult.Value; ref var record = ref recordResult.Value;
var vbView = new D3D12_VERTEX_BUFFER_VIEW var vbView = new D3D12_VERTEX_BUFFER_VIEW
{ {
BufferLocation = record.ResourcePtr.Get()->GetGPUVirtualAddress() + offset, BufferLocation = record.ResourcePtr.Get()->GetGPUVirtualAddress() + offset,

View File

@@ -166,35 +166,42 @@ public sealed class RenderGraph
offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id); offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id);
} }
*(int*)(pData + offset) = pass.resourceReads.Count; for (var j = 0; j < (int)RenderGraphResourceType.Count; j++)
offset += sizeof(int);
for (var j = 0; j < pass.resourceReads.Count; j++)
{ {
*(int*)(pData + offset) = pass.resourceReads[j].Value; var readList = pass.resourceReads[j];
var writeList = pass.resourceWrites[j];
var createList = pass.resourceCreates[j];
*(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); offset += sizeof(int);
} }
*(int*)(pData + offset) = pass.resourceWrites.Count; *(int*)(pData + offset) = writeList.Count;
offset += sizeof(int); offset += sizeof(int);
for (var j = 0; j < pass.resourceWrites.Count; j++) for (var k = 0; k < writeList.Count; k++)
{ {
*(int*)(pData + offset) = pass.resourceWrites[j].Value; *(int*)(pData + offset) = writeList[k].Value;
offset += sizeof(int); offset += sizeof(int);
} }
*(int*)(pData + offset) = pass.resourceCreates.Count; *(int*)(pData + offset) = createList.Count;
offset += sizeof(int); offset += sizeof(int);
for (var j = 0; j < pass.resourceCreates.Count; j++) for (var k = 0; k < createList.Count; k++)
{ {
*(int*)(pData + offset) = pass.resourceCreates[j].Value; *(int*)(pData + offset) = createList[k].Value;
offset += sizeof(int); offset += sizeof(int);
} }
} }
}
//// Hash resource descriptors //// 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; // *(int*)(pData + offset) = resource.descriptor.width;
// offset += sizeof(int); // offset += sizeof(int);
@@ -260,10 +267,13 @@ public sealed class RenderGraph
{ {
var pass = _passes[i]; var pass = _passes[i];
// Check if this pass writes to any imported textures // Check if this pass writes to any imported resources
for (var j = 0; j < pass.resourceWrites.Count; j++) for (var j = 0; j < (int)RenderGraphResourceType.Count; j++)
{ {
var writeHandle = pass.resourceWrites[j]; var writeList = pass.resourceWrites[j];
for (var k = 0; k < writeList.Count; k++)
{
var writeHandle = writeList[k];
var resource = _resources.GetResource(writeHandle); var resource = _resources.GetResource(writeHandle);
if (resource.isImported) if (resource.isImported)
{ {
@@ -272,6 +282,7 @@ public sealed class RenderGraph
} }
} }
} }
}
// Step 2: Cull passes based on dependency analysis // Step 2: Cull passes based on dependency analysis
// Mark all passes as culled initially // Mark all passes as culled initially
@@ -403,13 +414,17 @@ public sealed class RenderGraph
private void UnculDependencies(RenderGraphPassBase pass) private void UnculDependencies(RenderGraphPassBase pass)
{ {
// Un-cull producers of read resources // 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 // 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) if (pass.colorAccess[i].id.IsValid)
{ {
@@ -533,16 +548,20 @@ public sealed class RenderGraph
private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx) private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx)
{ {
// Process reads (transition to shader resource) // 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]; var readList = pass.resourceReads[i];
for (var j = 0; j < readList.Count; j++)
{
var handle = readList[j];
InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx); InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx);
} }
}
switch (pass.type) switch (pass.type)
{ {
case RenderPassType.Raster: case RenderPassType.Raster:
for (var i = 0; i <= pass.maxColorIndex; i++) for (var i = 0; i < pass.maxColorIndex; i++)
{ {
var access = pass.colorAccess[i]; var access = pass.colorAccess[i];
InsertTransitionIfNeeded(access.id.AsResource(), ResourceState.RenderTarget, passIdx); InsertTransitionIfNeeded(access.id.AsResource(), ResourceState.RenderTarget, passIdx);
@@ -561,11 +580,15 @@ public sealed class RenderGraph
break; break;
case RenderPassType.Compute: 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]; var writeList = pass.resourceWrites[i];
for (var j = 0; j < writeList.Count; j++)
{
var id = writeList[j];
InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx); InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx);
} }
}
break; break;
} }

View File

@@ -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;
/// <summary>
/// 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
/// </summary>
public sealed class RenderGraph
{
private readonly RenderGraphResourceRegistry _resources = new();
private readonly RenderGraphObjectPool _objectPool = new();
private readonly List<RenderGraphPassBase> _passes = new(64);
private readonly List<RenderGraphPassBase> _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<int, ResourceState> _resourceStates = new(128);
private readonly List<ResourceBarrier> _barriers = new(128);
private readonly RenderGraphCompilationCache _compilationCache = new();
private bool _compiled;
public RenderGraphBlackboard Blackboard { get; } = new();
public RenderGraph()
{
_renderContext = new RenderContext(_commandBuffer);
}
/// <summary>
/// Resets the render graph for a new frame.
/// Reuses existing allocations to minimize GC.
/// </summary>
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;
}
/// <summary>
/// Imports an external texture into the render graph.
/// </summary>
public Identifier<RGTexture> ImportTexture(TextureDescriptor descriptor)
{
return _resources.ImportTexture(descriptor);
}
public IRasterRenderGraphBuilder AddRasterRenderPass<TPassData>(string name, out TPassData passData)
where TPassData : class, new()
{
var renderPass = _objectPool.Rent<RasterRenderGraphPass<TPassData>>();
renderPass.Init(_passes.Count, _objectPool.Rent<TPassData>(), name, RenderPassType.Raster);
passData = renderPass.passData;
_passes.Add(renderPass);
_builder.Init(this, renderPass, _resources);
return _builder;
}
public IComputeRenderGraphBuilder AddComputeRenderPass<TPassData>(string name, out TPassData passData)
where TPassData : class, new()
{
var renderPass = _objectPool.Rent<ComputeRenderGraphPass<TPassData>>();
renderPass.Init(_passes.Count, _objectPool.Rent<TPassData>(), 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<RGTexture> 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<byte>(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<byte>(pData, offset);
return XxHash64.HashToUInt64(span);
}
/// <summary>
/// Compiles the render graph by culling unused passes and determining resource lifetimes.
/// </summary>
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;
}
/// <summary>
/// Restores the render graph state from cached compilation results.
/// </summary>
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;
}
}
/// <summary>
/// Stores current compilation results in the cache.
/// </summary>
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<RGResource> 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]);
}
}
/// <summary>
/// Generates resource barriers for state transitions and aliasing.
/// </summary>
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
}
/// <summary>
/// Inserts aliasing barriers when a physical resource is reused.
/// </summary>
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<RGResource> 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
}
}
}
}
}
}
/// <summary>
/// Inserts transition barriers when a resource changes state.
/// </summary>
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;
}
}
/// <summary>
/// Inserts a transition barrier if the resource state changes.
/// </summary>
private void InsertTransitionIfNeeded(Identifier<RGResource> 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
}
}
/// <summary>
/// Executes all compiled passes.
/// </summary>
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);
}
}
}

View File

@@ -0,0 +1,329 @@
using Ghost.Core.Utilities;
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// Represents a physical GPU resource that can be aliased by multiple logical resources.
/// </summary>
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<int> 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;
}
}
/// <summary>
/// Manages physical resource allocation and aliasing.
/// Uses interval scheduling algorithm to minimize memory usage.
/// </summary>
internal sealed class ResourceAliasingManager
{
private readonly List<PhysicalResource> _physicalResources = new(32);
private readonly RenderGraphObjectPool _pool = new();
private int _physicalResourceCount;
// Mapping from logical resource index to physical resource index
private readonly Dictionary<int, int> _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();
}
}
/// <summary>
/// Assigns physical resources to logical resources using greedy interval scheduling.
/// This minimizes total GPU memory usage.
/// </summary>
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<PhysicalResource>();
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();
}
/// <summary>
/// Restores aliasing state from cache.
/// </summary>
public void RestoreFromCache(Dictionary<int, int> logicalToPhysical, List<PhysicalResourceData> 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<PhysicalResource>();
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();
}
}
/// <summary>
/// Stores current aliasing state to cache.
/// </summary>
public void StoreToCache(Dictionary<int, int> outLogicalToPhysical, List<PhysicalResourceData> 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
});
}
}
}

View File

@@ -110,17 +110,17 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
} }
private Identifier<RGResource> UseResource(Identifier<RGResource> resource, AccessFlags accessFlags) private Identifier<RGResource> UseResource(Identifier<RGResource> resource, AccessFlags accessFlags, RenderGraphResourceType type)
{ {
if (accessFlags.HasFlag(AccessFlags.Read)) if (accessFlags.HasFlag(AccessFlags.Read))
{ {
_pass.resourceReads.Add(resource); _pass.resourceReads[(int)type].Add(resource);
_resources.AddConsumer(resource, _pass.index); _resources.AddConsumer(resource, _pass.index);
} }
if (accessFlags.HasFlag(AccessFlags.Write)) if (accessFlags.HasFlag(AccessFlags.Write))
{ {
_pass.resourceWrites.Add(resource); _pass.resourceWrites[(int)type].Add(resource);
_resources.SetProducer(resource, _pass.index); _resources.SetProducer(resource, _pass.index);
} }
@@ -142,7 +142,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
ThrowIfDisposed(); ThrowIfDisposed();
var handle = _resources.CreateTexture(descriptor); var handle = _resources.CreateTexture(descriptor);
_pass.resourceCreates.Add(handle.AsResource()); _pass.resourceCreates[(int)RenderGraphResourceType.Texture].Add(handle.AsResource());
_resources.SetProducer(handle.AsResource(), _pass.index); _resources.SetProducer(handle.AsResource(), _pass.index);
return handle; return handle;
} }
@@ -150,8 +150,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
public Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags flags) public Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags flags)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
return UseResource(texture.AsResource(), flags, RenderGraphResourceType.Texture).AsTexture();
return UseResource(texture.AsResource(), flags).AsTexture();
} }
public Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture) public Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture)
@@ -159,7 +158,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
ThrowIfDisposed(); ThrowIfDisposed();
var resource = texture.AsResource(); var resource = texture.AsResource();
UseResource(resource, AccessFlags.ReadWrite); UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Texture);
_pass.randomAccess.Add(resource); _pass.randomAccess.Add(resource);
return texture; return texture;
} }
@@ -169,7 +168,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra
ThrowIfDisposed(); ThrowIfDisposed();
var resource = buffer.AsResource(); var resource = buffer.AsResource();
UseResource(resource, AccessFlags.ReadWrite); UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Buffer);
_pass.randomAccess.Add(resource); _pass.randomAccess.Add(resource);
return buffer; return buffer;
} }

View File

@@ -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
{
/// <summary>
/// Enables or disables pass culling for the current context.
/// </summary>
/// <param name="value">A value indicating whether pass culling is allowed.</param>
void AllowPassCulling(bool value);
/// <summary>
/// Creates a new texture resource based on the specified descriptor.
/// </summary>
/// <param name="descriptor">A structure that defines the properties and configuration of the texture to create.</param>
/// <returns>An identifier for the newly created texture resource.</returns>
Identifier<RGTexture> CreateTexture(in TextureDescriptor descriptor);
/// <summary>
/// Registers the specified texture for use in the current render graph pass with the given access mode.
/// </summary>
/// <param name="texture">The identifier of the texture to be used in the render graph pass.</param>
/// <param name="accessMode">The access mode specifying how the texture will be read or written during the pass.</param>
/// <returns>An identifier for the texture.</returns>
Identifier<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags accessMode);
}
public interface IRasterRenderGraphBuilder : IRenderGraphBuilder
{
/// <summary>
/// Binds a texture for random access operations within the current rendering pass.
/// </summary>
/// <param name="texture">The identifier of the texture to be used for random access.</param>
/// <returns>An identifier for the texture.</returns>
Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture);
/// <summary>
/// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context.
/// </summary>
/// <param name="buffer">An identifier for the buffer to be used for random access. Must reference a valid buffer resource.</param>
/// <returns>An identifier for the buffer.</returns>
Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer);
/// <summary>
/// Sets the color attachment at the specified index to the given texture.
/// </summary>
/// <param name="texture">The identifier of the texture to use as the color attachment.</param>
/// <param name="index">The zero-based index of the color attachment to set.</param>
void SetColorAttachment(Identifier<RGTexture> texture, int index);
/// <summary>
/// Sets the depth attachment for the current render pass using the specified texture.
/// </summary>
/// <param name="texture">The identifier of the texture to use as the depth attachment. Cannot be null.</param>
void SetDepthAttachment(Identifier<RGTexture> texture);
/// <summary>
/// Sets the function used to render a pass with the specified pass data and render context.
/// </summary>
/// <typeparam name="TPassData">The type of data associated with the render pass.</typeparam>
/// <param name="renderFunc">The delegate that defines the rendering logic for the pass.</param>
void SetRenderFunc<TPassData>(Action<TPassData, RasterRenderContext> renderFunc)
where TPassData : class, new();
}
public interface IComputeRenderGraphBuilder : IRenderGraphBuilder
{
/// <summary>
/// Enables or disables asynchronous compute operations.
/// </summary>
/// <param name="value">true to enable asynchronous compute; otherwise, false.</param>
void EnableAsyncCompute(bool value);
/// <summary>
/// Sets the render function to be invoked during the compute rendering process.
/// </summary>
/// <typeparam name="TPassData">The type of the data object passed to the render function.</typeparam>
/// <param name="renderFunc">The delegate that defines the rendering logic to execute.</param>
void SetRenderFunc<TPassData>(Action<TPassData, ComputeRenderContext> 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<RGResource> UseResource(Identifier<RGResource> 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<RGTexture> 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<RGTexture> UseTexture(Identifier<RGTexture> texture, AccessFlags flags)
{
ThrowIfDisposed();
return UseResource(texture.AsResource(), flags).AsTexture();
}
public Identifier<RGTexture> UseRandomAccessTexture(Identifier<RGTexture> texture)
{
ThrowIfDisposed();
var resource = texture.AsResource();
UseResource(resource, AccessFlags.ReadWrite);
_pass.randomAccess.Add(resource);
return texture;
}
public Identifier<RGBuffer> UseRandomAccessBuffer(Identifier<RGBuffer> buffer)
{
ThrowIfDisposed();
var resource = buffer.AsResource();
UseResource(resource, AccessFlags.ReadWrite);
_pass.randomAccess.Add(resource);
return buffer;
}
public void SetColorAttachment(Identifier<RGTexture> 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<RGTexture> 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<TPassData>(Action<TPassData, RasterRenderContext> renderFunc)
where TPassData : class, new()
{
((RasterRenderGraphPass<TPassData>)_pass).renderFunc = renderFunc;
}
public void SetRenderFunc<TPassData>(Action<TPassData, ComputeRenderContext> renderFunc)
where TPassData : class, new()
{
((ComputeRenderGraphPass<TPassData>)_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;
}
}

View File

@@ -1,5 +1,4 @@
using Ghost.Core; using Ghost.Core;
using System.IO;
namespace Ghost.RenderGraph.Concept; namespace Ghost.RenderGraph.Concept;
@@ -31,14 +30,24 @@ internal abstract class RenderGraphPassBase
public List<Identifier<RGResource>> randomAccess = new(8); public List<Identifier<RGResource>> randomAccess = new(8);
// Resource dependencies // Resource dependencies
public readonly List<Identifier<RGResource>> resourceReads = new(8); public readonly List<Identifier<RGResource>>[] resourceReads = new List<Identifier<RGResource>>[(int)RenderGraphResourceType.Count];
public readonly List<Identifier<RGResource>> resourceWrites = new(4); public readonly List<Identifier<RGResource>>[] resourceWrites = new List<Identifier<RGResource>>[(int)RenderGraphResourceType.Count];
public readonly List<Identifier<RGResource>> resourceCreates = new(4); public readonly List<Identifier<RGResource>>[] resourceCreates = new List<Identifier<RGResource>>[(int)RenderGraphResourceType.Count];
// Execution state // Execution state
public bool culled; public bool culled;
public bool hasSideEffects; public bool hasSideEffects;
public RenderGraphPassBase()
{
for (int i = 0; i < (int)RenderGraphResourceType.Count; i++)
{
resourceReads[i] = new List<Identifier<RGResource>>(8);
resourceWrites[i] = new List<Identifier<RGResource>>(4);
resourceCreates[i] = new List<Identifier<RGResource>>(4);
}
}
public abstract void Execute(RenderContext context); public abstract void Execute(RenderContext context);
public abstract void Clear(); public abstract void Clear();
public abstract bool HasRenderFunc(); public abstract bool HasRenderFunc();
@@ -57,9 +66,13 @@ internal abstract class RenderGraphPassBase
randomAccess.Clear(); randomAccess.Clear();
resourceReads.Clear(); for (var i = 0; i < (int)RenderGraphResourceType.Count; i++)
resourceWrites.Clear(); {
resourceCreates.Clear(); resourceReads[i].Clear();
resourceWrites[i].Clear();
resourceCreates[i].Clear();
}
culled = false; culled = false;
hasSideEffects = false; hasSideEffects = false;
} }

View File

@@ -0,0 +1,129 @@
using Ghost.Core;
using System.IO;
namespace Ghost.RenderGraph.Concept;
/// <summary>
/// Represents different types of render passes.
/// </summary>
public enum RenderPassType : byte
{
Raster,
Compute
}
/// <summary>
/// Base class for render passes.
/// Uses pooling to avoid allocations after the first frame.
/// </summary>
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<Identifier<RGResource>> randomAccess = new(8);
// Resource dependencies
public readonly List<Identifier<RGResource>> resourceReads = new(8);
public readonly List<Identifier<RGResource>> resourceWrites = new(4);
public readonly List<Identifier<RGResource>> 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<TPassData, TRenderContext> : RenderGraphPassBase
where TPassData : class, new()
{
public TPassData passData = null!;
public Action<TPassData, TRenderContext>? 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<TPassData> : RenderGraphPassT<TPassData, RasterRenderContext>
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<TPassData> : RenderGraphPassT<TPassData, ComputeRenderContext>
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);
}
}

View File

@@ -3,11 +3,11 @@ using System.Runtime.CompilerServices;
namespace Ghost.RenderGraph.Concept; namespace Ghost.RenderGraph.Concept;
internal enum RenderGraphResourceType internal enum RenderGraphResourceType : int
{ {
Texture, Texture,
Buffer, Buffer,
AccelerationStructure, // AccelerationStructure,
Count Count
} }
@@ -84,14 +84,79 @@ public readonly struct TextureDescriptor : IEquatable<TextureDescriptor>
this.name = name; this.name = name;
} }
public readonly bool Equals(TextureDescriptor other) => public readonly bool Equals(TextureDescriptor other)
width == other.width && {
return width == other.width &&
height == other.height && height == other.height &&
format == other.format && format == other.format &&
name == other.name; name == other.name;
}
public override readonly bool Equals(object? obj) => obj is TextureDescriptor other && Equals(other); 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 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<BufferDescriptor>
{
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);
}
} }
/// <summary> /// <summary>