Update render graph

This commit is contained in:
2026-01-13 13:46:50 +09:00
parent 954e3756aa
commit 02df8d7732
11 changed files with 291 additions and 261 deletions

View File

@@ -20,7 +20,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.3" /> <PackageReference Include="Misaki.HighPerformance" Version="1.0.4" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.2.2" /> <PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.2.2" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.3.3" /> <PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.3.3" />
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.1" /> <PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.1" />

View File

@@ -5,7 +5,7 @@ namespace Ghost.Core.Utilities;
public class CollectionPool<TCollection, TItem> public class CollectionPool<TCollection, TItem>
where TCollection : class, ICollection<TItem>, new() where TCollection : class, ICollection<TItem>, new()
{ {
internal static readonly ObjectPool<TCollection> s_pool = new ObjectPool<TCollection>(() => new TCollection(), 1); internal static readonly ObjectPool<TCollection> s_pool = new ObjectPool<TCollection>(() => new TCollection(), null, 1);
public static TCollection Rent() public static TCollection Rent()
{ {

View File

@@ -2,7 +2,6 @@ using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using Ghost.Graphics.Core;
namespace Ghost.Graphics.Core; namespace Ghost.Graphics.Core;

View File

@@ -73,7 +73,7 @@ public class RenderGraphBenchmark
// Create output texture // Create output texture
lightingOutput = builder.CreateTexture( lightingOutput = builder.CreateTexture(
new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "LightingResult")); new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "LightingResult"));
builder.SetColorAttachment(lightingOutput, 1); builder.SetColorAttachment(lightingOutput, 0);
lightingData.OutputLighting = lightingOutput; lightingData.OutputLighting = lightingOutput;

View File

@@ -1,38 +1,38 @@
using Ghost.Core;
using Ghost.RenderGraph.Concept; using Ghost.RenderGraph.Concept;
using Ghost.RenderGraph.Concept.Benchmark; using Ghost.RenderGraph.Concept.Benchmark;
var renderGraph = new RenderGraph();
#if !DEBUG #if !DEBUG
BenchmarkDotNet.Running.BenchmarkRunner.Run<RenderGraphBenchmark>(); BenchmarkDotNet.Running.BenchmarkRunner.Run<RenderGraphBenchmark>();
return; return;
//const int _ITERATION = 500000; var renderGraph = new RenderGraph();
//for (var i = 0; i < _ITERATION; i++) const int _ITERATION = 500000;
//{ for (var i = 0; i < _ITERATION; i++)
// RenderGraphBenchmark.ExecuteGraph(renderGraph); {
//} RenderGraphBenchmark.ExecuteGraph(renderGraph);
}
//GC.Collect(); GC.Collect();
//GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
////Thread.Sleep(1000); // Leave a gap in visual studio allocations timeline //Thread.Sleep(1000); // Leave a gap in visual studio allocations timeline
//var sw = new System.Diagnostics.Stopwatch(); var sw = new System.Diagnostics.Stopwatch();
//var gcBefore = GC.GetAllocatedBytesForCurrentThread(); var gcBefore = GC.GetAllocatedBytesForCurrentThread();
//sw.Start(); sw.Start();
//for (var i = 0; i < _ITERATION; i++) for (var i = 0; i < _ITERATION; i++)
//{ {
// RenderGraphBenchmark.ExecuteGraph(renderGraph); RenderGraphBenchmark.ExecuteGraph(renderGraph);
//} }
//sw.Stop(); sw.Stop();
//var gcAfter = GC.GetAllocatedBytesForCurrentThread(); var gcAfter = GC.GetAllocatedBytesForCurrentThread();
//Console.WriteLine($"{sw.Elapsed.TotalNanoseconds / _ITERATION} ns (per iteration)"); Console.WriteLine($"{sw.Elapsed.TotalNanoseconds / _ITERATION} ns (per iteration)");
//Console.WriteLine($"GC Allocated Bytes: {(gcAfter - gcBefore) / _ITERATION} bytes (per iteration)"); Console.WriteLine($"GC Allocated Bytes: {(gcAfter - gcBefore) / _ITERATION} bytes (per iteration)");
#else #else
var renderGraph = new RenderGraph();
// Run twice to demonstrate cache hit // Run twice to demonstrate cache hit
Console.WriteLine("=== FRAME 1 (Cache Miss Expected) ==="); Console.WriteLine("=== FRAME 1 (Cache Miss Expected) ===");
RenderGraphBenchmark.ExecuteGraph(renderGraph); RenderGraphBenchmark.ExecuteGraph(renderGraph);

View File

@@ -105,11 +105,32 @@ public sealed class RenderGraph
return _builder; return _builder;
} }
/// <summary> private unsafe int ComputeTextureHash(byte* pData, int offset, Identifier<RGTexture> texture)
/// Computes a _hasher of the render graph structure for caching. {
/// Does NOT include pass names (they don't affect compilation). if (texture.IsInvalid)
/// Uses XxHash3 with SIMD optimizations for fast hashing. {
/// </summary> 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() private unsafe ulong ComputeGraphHash()
{ {
using var scope = AllocationManager.CreateStackScope(); using var scope = AllocationManager.CreateStackScope();
@@ -135,15 +156,14 @@ public sealed class RenderGraph
*(bool*)(pData + offset) = pass.asyncCompute; *(bool*)(pData + offset) = pass.asyncCompute;
offset += sizeof(bool); offset += sizeof(bool);
*(TextureAccess*)(pData + offset) = pass.depthAccess; // Hash depth attachment
offset += sizeof(TextureAccess); offset = ComputeTextureHash(pData, offset, pass.depthAccess.id);
*(int*)(pData + offset) = pass.maxColorIndex; *(int*)(pData + offset) = pass.maxColorIndex;
offset += sizeof(int); offset += sizeof(int);
for (var j = 0; j <= pass.maxColorIndex; j++) for (var j = 0; j <= pass.maxColorIndex; j++)
{ {
*(TextureAccess*)(pData + offset) = pass.colorAccess[j]; offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id);
offset += sizeof(TextureAccess);
} }
*(int*)(pData + offset) = pass.resourceReads.Count; *(int*)(pData + offset) = pass.resourceReads.Count;
@@ -171,23 +191,23 @@ public sealed class RenderGraph
} }
} }
// Hash resource descriptors //// Hash resource descriptors
for (var i = 0; i < _resources.TextureResourceCount; i++) //for (var i = 0; i < _resources.TextureResourceCount; i++)
{ //{
var resource = _resources.GetTextureResourceByIndex(i); // var resource = _resources.GetTextureResourceByIndex(i);
*(int*)(pData + offset) = resource.Descriptor.Width; // *(int*)(pData + offset) = resource.descriptor.width;
offset += sizeof(int); // offset += sizeof(int);
*(int*)(pData + offset) = resource.Descriptor.Height; // *(int*)(pData + offset) = resource.descriptor.height;
offset += sizeof(int); // offset += sizeof(int);
*(TextureFormat*)(pData + offset) = resource.Descriptor.Format; // *(TextureFormat*)(pData + offset) = resource.descriptor.format;
offset += sizeof(TextureFormat); // offset += sizeof(TextureFormat);
*(bool*)(pData + offset) = resource.IsImported; // *(bool*)(pData + offset) = resource.isImported;
offset += sizeof(bool); // offset += sizeof(bool);
} //}
var span = new Span<byte>(pData, offset); var span = new Span<byte>(pData, offset);
return XxHash64.HashToUInt64(span); return XxHash64.HashToUInt64(span);
@@ -208,7 +228,7 @@ public sealed class RenderGraph
#endif #endif
// Step 0: Check cache // Step 0: Check cache
var graphHash = ComputeGraphHash(); // 1321433047288519964 var graphHash = ComputeGraphHash(); // 17020363347016000737
#if DEBUG #if DEBUG
var hashTime = sw.Elapsed.TotalMicroseconds; var hashTime = sw.Elapsed.TotalMicroseconds;
@@ -245,7 +265,7 @@ public sealed class RenderGraph
{ {
var writeHandle = pass.resourceWrites[j]; var writeHandle = pass.resourceWrites[j];
var resource = _resources.GetResource(writeHandle); var resource = _resources.GetResource(writeHandle);
if (resource.IsImported) if (resource.isImported)
{ {
pass.hasSideEffects = true; pass.hasSideEffects = true;
break; break;
@@ -299,31 +319,31 @@ public sealed class RenderGraph
{ {
// Restore compiled pass list // Restore compiled pass list
_compiledPasses.Clear(); _compiledPasses.Clear();
for (var i = 0; i < cached.CompiledPassIndices.Count; i++) for (var i = 0; i < cached.compiledPassIndices.Count; i++)
{ {
var passIndex = cached.CompiledPassIndices[i]; var passIndex = cached.compiledPassIndices[i];
_compiledPasses.Add(_passes[passIndex]); _compiledPasses.Add(_passes[passIndex]);
} }
// Restore culling flags // Restore culling flags
for (var i = 0; i < _passes.Count && i < cached.PassCulledFlags.Count; i++) for (var i = 0; i < _passes.Count && i < cached.passCulledFlags.Count; i++)
{ {
_passes[i].culled = cached.PassCulledFlags[i]; _passes[i].culled = cached.passCulledFlags[i];
} }
// Restore aliasing mappings (need to update ResourceAliasingManager) // Restore aliasing mappings (need to update ResourceAliasingManager)
_aliasingManager.RestoreFromCache(cached.LogicalToPhysical, cached.PhysicalResources); _aliasingManager.RestoreFromCache(cached.logicalToPhysical, cached.physicalResources);
// Restore barriers (deep copy to avoid shared references) // Restore barriers (deep copy to avoid shared references)
_barriers.Clear(); _barriers.Clear();
for (var i = 0; i < cached.Barriers.Count; i++) for (var i = 0; i < cached.barriers.Count; i++)
{ {
_barriers.Add(cached.Barriers[i]); _barriers.Add(cached.barriers[i]);
} }
// Restore resource states // Restore resource states
_resourceStates.Clear(); _resourceStates.Clear();
foreach (var kvp in cached.ResourceStates) foreach (var kvp in cached.resourceStates)
{ {
_resourceStates[kvp.Key] = kvp.Value; _resourceStates[kvp.Key] = kvp.Value;
} }
@@ -339,47 +359,39 @@ public sealed class RenderGraph
// Store compiled pass indices // Store compiled pass indices
for (var i = 0; i < _compiledPasses.Count; i++) for (var i = 0; i < _compiledPasses.Count; i++)
{ {
cacheData.CompiledPassIndices.Add(_compiledPasses[i].index); cacheData.compiledPassIndices.Add(_compiledPasses[i].index);
} }
// Store culling flags for all passes // Store culling flags for all passes
for (var i = 0; i < _passes.Count; i++) for (var i = 0; i < _passes.Count; i++)
{ {
cacheData.PassCulledFlags.Add(_passes[i].culled); cacheData.passCulledFlags.Add(_passes[i].culled);
} }
// Store aliasing mappings // Store aliasing mappings
_aliasingManager.StoreToCache(cacheData.LogicalToPhysical, cacheData.PhysicalResources); _aliasingManager.StoreToCache(cacheData.logicalToPhysical, cacheData.physicalResources);
// Store barriers // Store barriers
for (var i = 0; i < _barriers.Count; i++) for (var i = 0; i < _barriers.Count; i++)
{ {
cacheData.Barriers.Add(_barriers[i]); cacheData.barriers.Add(_barriers[i]);
} }
// Store resource states // Store resource states
foreach (var kvp in _resourceStates) foreach (var kvp in _resourceStates)
{ {
cacheData.ResourceStates[kvp.Key] = kvp.Value; cacheData.resourceStates[kvp.Key] = kvp.Value;
} }
_compilationCache.Store(graphHash, cacheData); _compilationCache.Store(graphHash, cacheData);
} }
/// <summary> private void UnculProducer(Identifier<RGResource> resource)
/// Recursively un-cull passes that a given pass depends on.
/// </summary>
private void UnculDependencies(RenderGraphPassBase pass)
{ {
// Un-cull all producers of textures we read var res = _resources.GetResource(resource);
for (var i = 0; i < pass.resourceReads.Count; i++) if (res.producerPass >= 0)
{ {
var readHandle = pass.resourceReads[i]; var producer = _passes[res.producerPass];
var resource = _resources.GetResource(readHandle);
if (resource.ProducerPass >= 0)
{
var producer = _passes[resource.ProducerPass];
if (producer.culled) if (producer.culled)
{ {
producer.culled = false; producer.culled = false;
@@ -387,6 +399,35 @@ public sealed class RenderGraph
} }
} }
} }
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> /// <summary>
@@ -431,11 +472,11 @@ public sealed class RenderGraph
var resource = _resources.GetResource(id); var resource = _resources.GetResource(id);
// Skip imported resources // Skip imported resources
if (resource.IsImported) if (resource.isImported)
continue; continue;
// Check if this is the first use of this logical resource // Check if this is the first use of this logical resource
if (resource.FirstUsePass == pass.index) if (resource.firstUsePass == pass.index)
{ {
// Rent the physical resource // Rent the physical resource
var physicalIndex = _aliasingManager.GetPhysicalResourceIndex(id.Value); var physicalIndex = _aliasingManager.GetPhysicalResourceIndex(id.Value);
@@ -445,22 +486,22 @@ public sealed class RenderGraph
// If this physical resource has multiple aliased resources, // If this physical resource has multiple aliased resources,
// we need an aliasing barrier when switching between them // we need an aliasing barrier when switching between them
if (physical != null && physical.AliasedLogicalResources.Count > 1) if (physical != null && physical.aliasedLogicalResources.Count > 1)
{ {
// Find the resource that used this physical memory most recently before this pass // Find the resource that used this physical memory most recently before this pass
Identifier<RGResource> resourceBefore = default; Identifier<RGResource> resourceBefore = default;
var mostRecentLastUse = -1; var mostRecentLastUse = -1;
foreach (var otherLogicalIndex in physical.AliasedLogicalResources) foreach (var otherLogicalIndex in physical.aliasedLogicalResources)
{ {
if (otherLogicalIndex != id.Value) if (otherLogicalIndex != id.Value)
{ {
var otherResource = _resources.GetTextureResourceByIndex(otherLogicalIndex); var otherResource = _resources.GetTextureResourceByIndex(otherLogicalIndex);
// Check if this resource finished before our resource starts // Check if this resource finished before our resource starts
if (otherResource.LastUsePass < pass.index && if (otherResource.lastUsePass < pass.index &&
otherResource.LastUsePass > mostRecentLastUse) otherResource.lastUsePass > mostRecentLastUse)
{ {
mostRecentLastUse = otherResource.LastUsePass; mostRecentLastUse = otherResource.lastUsePass;
resourceBefore = otherLogicalIndex; resourceBefore = otherLogicalIndex;
} }
} }
@@ -596,6 +637,13 @@ public sealed class RenderGraph
barrier.StateAfter barrier.StateAfter
); );
} }
else if (barrier.Type == BarrierType.Aliasing)
{
_commandBuffer.AliasBarrier(
barrier.ResourceBefore,
barrier.ResourceAfter
);
}
#endif #endif
// In a real implementation, you would execute the barrier here: // In a real implementation, you would execute the barrier here:
// ExecuteBarrier(_barriers[barrierIndex]); // ExecuteBarrier(_barriers[barrierIndex]);

View File

@@ -7,54 +7,54 @@ namespace Ghost.RenderGraph.Concept;
/// </summary> /// </summary>
internal sealed class PhysicalResource internal sealed class PhysicalResource
{ {
public int Index; public int index;
public int Width; public int width;
public int Height; public int height;
public TextureFormat Format; public TextureFormat format;
public int SizeInBytes; public int sizeInBytes;
// Lifetime tracking // Lifetime tracking
public int FirstUsePass = int.MaxValue; public int firstUsePass = int.MaxValue;
public int LastUsePass = -1; public int lastUsePass = -1;
// Aliasing tracking // Aliasing tracking
public readonly List<int> AliasedLogicalResources = new(4); public readonly List<int> aliasedLogicalResources = new(4);
public void Reset() public void Reset()
{ {
Index = -1; index = -1;
Width = 0; width = 0;
Height = 0; height = 0;
Format = TextureFormat.RGBA8; format = TextureFormat.RGBA8;
SizeInBytes = 0; sizeInBytes = 0;
FirstUsePass = int.MaxValue; firstUsePass = int.MaxValue;
LastUsePass = -1; lastUsePass = -1;
AliasedLogicalResources.Clear(); aliasedLogicalResources.Clear();
} }
public bool CanAlias(TextureDescriptor descriptor) public bool CanAlias(TextureDescriptor descriptor)
{ {
// For aliasing, resources must be identical in size and format // 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) // In a real implementation, you could be more flexible (e.g., same size but different format)
return Width == descriptor.Width && return width == descriptor.width &&
Height == descriptor.Height && height == descriptor.height &&
Format == descriptor.Format; format == descriptor.format;
} }
public void UpdateLifetime(int passIndex) public void UpdateLifetime(int passIndex)
{ {
FirstUsePass = Math.Min(FirstUsePass, passIndex); firstUsePass = Math.Min(firstUsePass, passIndex);
LastUsePass = Math.Max(LastUsePass, passIndex); lastUsePass = Math.Max(lastUsePass, passIndex);
} }
public bool IsAliveAt(int passIndex) public bool IsAliveAt(int passIndex)
{ {
return passIndex >= FirstUsePass && passIndex <= LastUsePass; return passIndex >= firstUsePass && passIndex <= lastUsePass;
} }
public int CalculateSize() public int CalculateSize()
{ {
int bytesPerPixel = Format switch int bytesPerPixel = format switch
{ {
TextureFormat.RGBA8 => 4, TextureFormat.RGBA8 => 4,
TextureFormat.RGBA16F => 8, TextureFormat.RGBA16F => 8,
@@ -63,7 +63,7 @@ internal sealed class PhysicalResource
TextureFormat.Depth24Stencil8 => 4, TextureFormat.Depth24Stencil8 => 4,
_ => 4 _ => 4
}; };
return Width * Height * bytesPerPixel; return width * height * bytesPerPixel;
} }
} }
@@ -109,21 +109,21 @@ internal sealed class ResourceAliasingManager
for (int i = 0; i < registry.TextureResourceCount; i++) for (int i = 0; i < registry.TextureResourceCount; i++)
{ {
var resource = registry.GetTextureResourceByIndex(i); var resource = registry.GetTextureResourceByIndex(i);
if (!resource.IsImported) // Don't alias imported resources if (!resource.isImported) // Don't alias imported resources
{ {
logicalResources.Add((i, resource)); logicalResources.Add((i, resource));
#if DEBUG #if DEBUG
int size = CalculateSize(resource.Descriptor); int size = CalculateSize(resource.descriptor);
totalLogicalSize += size; totalLogicalSize += size;
Console.WriteLine($"Logical Resource {i}: {resource.Descriptor.Name}"); Console.WriteLine($"Logical Resource {i}: {resource.descriptor.name}");
Console.WriteLine($" Lifetime: Pass {resource.FirstUsePass} -> {resource.LastUsePass}"); Console.WriteLine($" Lifetime: Pass {resource.firstUsePass} -> {resource.lastUsePass}");
Console.WriteLine($" Size: {size / 1024.0:F2} KB"); Console.WriteLine($" Size: {size / 1024.0:F2} KB");
#endif #endif
} }
} }
// Sort by first use pass (earlier resources first) // Sort by first use pass (earlier resources first)
logicalResources.Sort((a, b) => a.resource.FirstUsePass.CompareTo(b.resource.FirstUsePass)); logicalResources.Sort((a, b) => a.resource.firstUsePass.CompareTo(b.resource.firstUsePass));
// Greedy interval scheduling: assign each logical resource to a physical resource // Greedy interval scheduling: assign each logical resource to a physical resource
foreach (var (logicalIndex, logicalResource) in logicalResources) foreach (var (logicalIndex, logicalResource) in logicalResources)
@@ -137,7 +137,7 @@ internal sealed class ResourceAliasingManager
{ {
var physical = _physicalResources[i]; var physical = _physicalResources[i];
if (physical.CanAlias(logicalResource.Descriptor) && if (physical.CanAlias(logicalResource.descriptor) &&
!HasLifetimeOverlap(physical, logicalResource)) !HasLifetimeOverlap(physical, logicalResource))
{ {
assignedPhysical = physical; assignedPhysical = physical;
@@ -149,40 +149,40 @@ internal sealed class ResourceAliasingManager
if (assignedPhysical == null) if (assignedPhysical == null)
{ {
assignedPhysical = GetOrCreatePhysicalResource(); assignedPhysical = GetOrCreatePhysicalResource();
assignedPhysical.Index = _physicalResourceCount - 1; assignedPhysical.index = _physicalResourceCount - 1;
assignedPhysical.Width = logicalResource.Descriptor.Width; assignedPhysical.width = logicalResource.descriptor.width;
assignedPhysical.Height = logicalResource.Descriptor.Height; assignedPhysical.height = logicalResource.descriptor.height;
assignedPhysical.Format = logicalResource.Descriptor.Format; assignedPhysical.format = logicalResource.descriptor.format;
assignedPhysical.SizeInBytes = assignedPhysical.CalculateSize(); assignedPhysical.sizeInBytes = assignedPhysical.CalculateSize();
#if DEBUG #if DEBUG
Console.WriteLine($"\nAllocated NEW Physical Resource {assignedPhysical.Index}:"); Console.WriteLine($"\nAllocated NEW Physical Resource {assignedPhysical.index}:");
Console.WriteLine($" Size: {assignedPhysical.Width}x{assignedPhysical.Height}"); Console.WriteLine($" Size: {assignedPhysical.width}x{assignedPhysical.height}");
Console.WriteLine($" Format: {assignedPhysical.Format}"); Console.WriteLine($" Format: {assignedPhysical.format}");
Console.WriteLine($" Memory: {assignedPhysical.SizeInBytes / 1024.0:F2} KB"); Console.WriteLine($" Memory: {assignedPhysical.sizeInBytes / 1024.0:F2} KB");
#endif #endif
} }
#if DEBUG #if DEBUG
else else
{ {
Console.WriteLine($"\nALIASING: {logicalResource.Descriptor.Name} -> Physical Resource {assignedPhysical.Index}"); Console.WriteLine($"\nALIASING: {logicalResource.descriptor.name} -> Physical Resource {assignedPhysical.index}");
} }
#endif #endif
// Update physical resource lifetime // Update physical resource lifetime
assignedPhysical.UpdateLifetime(logicalResource.FirstUsePass); assignedPhysical.UpdateLifetime(logicalResource.firstUsePass);
assignedPhysical.UpdateLifetime(logicalResource.LastUsePass); assignedPhysical.UpdateLifetime(logicalResource.lastUsePass);
assignedPhysical.AliasedLogicalResources.Add(logicalIndex); assignedPhysical.aliasedLogicalResources.Add(logicalIndex);
// Record the mapping // Record the mapping
_logicalToPhysical[logicalIndex] = assignedPhysical.Index; _logicalToPhysical[logicalIndex] = assignedPhysical.index;
} }
#if DEBUG #if DEBUG
int totalPhysicalSize = 0; int totalPhysicalSize = 0;
for (int i = 0; i < _physicalResourceCount; i++) for (int i = 0; i < _physicalResourceCount; i++)
{ {
totalPhysicalSize += _physicalResources[i].SizeInBytes; totalPhysicalSize += _physicalResources[i].sizeInBytes;
} }
Console.WriteLine($"\n=== Aliasing Summary ==="); Console.WriteLine($"\n=== Aliasing Summary ===");
@@ -213,8 +213,8 @@ internal sealed class ResourceAliasingManager
{ {
// Check if the lifetimes overlap // Check if the lifetimes overlap
// No overlap if: logical.First > physical.Last OR logical.Last < physical.First // No overlap if: logical.First > physical.Last OR logical.Last < physical.First
return !(logical.FirstUsePass > physical.LastUsePass || return !(logical.firstUsePass > physical.lastUsePass ||
logical.LastUsePass < physical.FirstUsePass); logical.lastUsePass < physical.firstUsePass);
} }
private PhysicalResource GetOrCreatePhysicalResource() private PhysicalResource GetOrCreatePhysicalResource()
@@ -238,7 +238,7 @@ internal sealed class ResourceAliasingManager
private static int CalculateSize(TextureDescriptor descriptor) private static int CalculateSize(TextureDescriptor descriptor)
{ {
int bytesPerPixel = descriptor.Format switch int bytesPerPixel = descriptor.format switch
{ {
TextureFormat.RGBA8 => 4, TextureFormat.RGBA8 => 4,
TextureFormat.RGBA16F => 8, TextureFormat.RGBA16F => 8,
@@ -247,7 +247,7 @@ internal sealed class ResourceAliasingManager
TextureFormat.Depth24Stencil8 => 4, TextureFormat.Depth24Stencil8 => 4,
_ => 4 _ => 4
}; };
return descriptor.Width * descriptor.Height * bytesPerPixel; return descriptor.width * descriptor.height * bytesPerPixel;
} }
public void Clear() public void Clear()
@@ -290,13 +290,13 @@ internal sealed class ResourceAliasingManager
} }
var data = physicalData[i]; var data = physicalData[i];
physical.Index = data.Index; physical.index = data.index;
physical.Width = data.Width; physical.width = data.width;
physical.Height = data.Height; physical.height = data.height;
physical.Format = data.Format; physical.format = data.format;
physical.FirstUsePass = data.FirstUsePass; physical.firstUsePass = data.firstUsePass;
physical.LastUsePass = data.LastUsePass; physical.lastUsePass = data.lastUsePass;
physical.SizeInBytes = physical.CalculateSize(); physical.sizeInBytes = physical.CalculateSize();
} }
} }
@@ -317,12 +317,12 @@ internal sealed class ResourceAliasingManager
var physical = _physicalResources[i]; var physical = _physicalResources[i];
outPhysicalData.Add(new PhysicalResourceData outPhysicalData.Add(new PhysicalResourceData
{ {
Index = physical.Index, index = physical.index,
Width = physical.Width, width = physical.width,
Height = physical.Height, height = physical.height,
Format = physical.Format, format = physical.format,
FirstUsePass = physical.FirstUsePass, firstUsePass = physical.firstUsePass,
LastUsePass = physical.LastUsePass lastUsePass = physical.lastUsePass
}); });
} }
} }

View File

@@ -123,6 +123,16 @@ internal struct ResourceBarrier
} }
}; };
} }
public override readonly string ToString()
{
return Type switch
{
BarrierType.Transition => $"[Pass {PassIndex}] Transition Barrier: Resource {Resource.Value} from {StateBefore} to {StateAfter}",
BarrierType.Aliasing => $"[Pass {PassIndex}] Aliasing Barrier: ResourceBefore {ResourceBefore.Value} to ResourceAfter {ResourceAfter.Value}",
_ => "Unknown Barrier Type"
};
}
} }
/// <summary> /// <summary>

View File

@@ -9,31 +9,31 @@ namespace Ghost.RenderGraph.Concept;
internal sealed class CachedCompilation internal sealed class CachedCompilation
{ {
// Compiled pass indices (indices into the _passes list) // Compiled pass indices (indices into the _passes list)
public readonly List<int> CompiledPassIndices = new(64); public readonly List<int> compiledPassIndices = new(64);
// Culling decisions for each pass // Culling decisions for each pass
public readonly List<bool> PassCulledFlags = new(64); public readonly List<bool> passCulledFlags = new(64);
// Physical resource aliasing mappings (logical index -> physical index) // Physical resource aliasing mappings (logical index -> physical index)
public readonly Dictionary<int, int> LogicalToPhysical = new(128); public readonly Dictionary<int, int> logicalToPhysical = new(128);
// Physical resource metadata // Physical resource metadata
public readonly List<PhysicalResourceData> PhysicalResources = new(32); public readonly List<PhysicalResourceData> physicalResources = new(32);
// Resource barriers // Resource barriers
public readonly List<ResourceBarrier> Barriers = new(128); public readonly List<ResourceBarrier> barriers = new(128);
// Resource state mappings (for barrier generation) // Resource state mappings (for barrier generation)
public readonly Dictionary<int, ResourceState> ResourceStates = new(128); public readonly Dictionary<int, ResourceState> resourceStates = new(128);
public void Clear() public void Clear()
{ {
CompiledPassIndices.Clear(); compiledPassIndices.Clear();
PassCulledFlags.Clear(); passCulledFlags.Clear();
LogicalToPhysical.Clear(); logicalToPhysical.Clear();
PhysicalResources.Clear(); physicalResources.Clear();
Barriers.Clear(); barriers.Clear();
ResourceStates.Clear(); resourceStates.Clear();
} }
} }
@@ -42,12 +42,12 @@ internal sealed class CachedCompilation
/// </summary> /// </summary>
internal struct PhysicalResourceData internal struct PhysicalResourceData
{ {
public int Index; public int index;
public int Width; public int width;
public int Height; public int height;
public TextureFormat Format; public TextureFormat format;
public int FirstUsePass; public int firstUsePass;
public int LastUsePass; public int lastUsePass;
} }
/// <summary> /// <summary>
@@ -92,20 +92,20 @@ internal sealed class RenderGraphCompilationCache
// Deep copy the data // Deep copy the data
_cached.Clear(); _cached.Clear();
_cached.CompiledPassIndices.AddRange(data.CompiledPassIndices); _cached.compiledPassIndices.AddRange(data.compiledPassIndices);
_cached.PassCulledFlags.AddRange(data.PassCulledFlags); _cached.passCulledFlags.AddRange(data.passCulledFlags);
foreach (var kvp in data.LogicalToPhysical) foreach (var kvp in data.logicalToPhysical)
{ {
_cached.LogicalToPhysical[kvp.Key] = kvp.Value; _cached.logicalToPhysical[kvp.Key] = kvp.Value;
} }
_cached.PhysicalResources.AddRange(data.PhysicalResources); _cached.physicalResources.AddRange(data.physicalResources);
_cached.Barriers.AddRange(data.Barriers); _cached.barriers.AddRange(data.barriers);
foreach (var kvp in data.ResourceStates) foreach (var kvp in data.resourceStates)
{ {
_cached.ResourceStates[kvp.Key] = kvp.Value; _cached.resourceStates[kvp.Key] = kvp.Value;
} }
} }

View File

@@ -23,7 +23,7 @@ internal sealed class RenderGraphObjectPool
private static ObjectPool<T> AllocatePool() private static ObjectPool<T> AllocatePool()
{ {
var newPool = new ObjectPool<T>(() => new T()); var newPool = new ObjectPool<T>(() => new T(), null);
// Storing instance to clear the static pool of the same type if needed // Storing instance to clear the static pool of the same type if needed
s_allocatedPools.Add(new SharedObjectPool<T>()); s_allocatedPools.Add(new SharedObjectPool<T>());
return newPool; return newPool;
@@ -42,6 +42,8 @@ internal sealed class RenderGraphObjectPool
/// Rent a new instance from the pool. /// Rent a new instance from the pool.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
// FIX: ObjectPool<T>.Rent() has a critical bug that it will put the newly created object into the pool directly and give out the same instance again.
// This will cause multiple renters to get the same instance.
public static T Rent() => s_pool.Rent(); public static T Rent() => s_pool.Rent();
/// <summary> /// <summary>
@@ -78,25 +80,25 @@ internal sealed class RenderGraphObjectPool
internal sealed class RenderGraphResource internal sealed class RenderGraphResource
{ {
public RenderGraphResourceType type; public RenderGraphResourceType type;
public int Index; public int index;
public TextureDescriptor Descriptor; public TextureDescriptor descriptor;
public bool IsImported; public bool isImported;
public int FirstUsePass = -1; public int firstUsePass = -1;
public int LastUsePass = -1; public int lastUsePass = -1;
public int ProducerPass = -1; public int producerPass = -1;
public List<int> ConsumerPasses = new(4); public List<int> consumerPasses = new(4);
public int RefCount; public int refCount;
public void Reset() public void Reset()
{ {
Index = -1; index = -1;
Descriptor = default; descriptor = default;
IsImported = false; isImported = false;
FirstUsePass = -1; firstUsePass = -1;
LastUsePass = -1; lastUsePass = -1;
ProducerPass = -1; producerPass = -1;
ConsumerPasses.Clear(); consumerPasses.Clear();
RefCount = 0; refCount = 0;
} }
} }
@@ -108,102 +110,73 @@ internal sealed class RenderGraphResourceRegistry
{ {
private readonly List<RenderGraphResource> _resources = new(64); private readonly List<RenderGraphResource> _resources = new(64);
private readonly RenderGraphObjectPool _pool = new(); private readonly RenderGraphObjectPool _pool = new();
private int _textureResourceCount;
public int TextureResourceCount => _textureResourceCount; public int TextureResourceCount => _resources.Count;
public void BeginFrame() public void BeginFrame()
{ {
// Don't clear the lists, just reset the count for (var i = 0; i < _resources.Count; i++)
// This avoids reallocating the backing arrays {
_textureResourceCount = 0; _pool.Return(_resources[i]);
}
_resources.Clear();
} }
public Identifier<RGTexture> ImportTexture(TextureDescriptor descriptor) public Identifier<RGTexture> ImportTexture(TextureDescriptor descriptor)
{ {
var resource = GetOrCreateTextureResource(); var resource = _pool.Rent<RenderGraphResource>();
resource.Index = _textureResourceCount - 1; resource.type = RenderGraphResourceType.Texture;
resource.Descriptor = descriptor; resource.index = _resources.Count;
resource.IsImported = true; resource.descriptor = descriptor;
resource.isImported = true;
return new Identifier<RGTexture>(resource.Index); _resources.Add(resource);
return new Identifier<RGTexture>(resource.index);
} }
public Identifier<RGTexture> CreateTexture(TextureDescriptor descriptor) public Identifier<RGTexture> CreateTexture(TextureDescriptor descriptor)
{ {
var resource = GetOrCreateTextureResource(); var resource = _pool.Rent<RenderGraphResource>();
resource.Index = _textureResourceCount - 1; resource.type = RenderGraphResourceType.Texture;
resource.Descriptor = descriptor; resource.index = _resources.Count;
resource.IsImported = false; resource.descriptor = descriptor;
resource.isImported = false;
return new Identifier<RGTexture>(resource.Index); _resources.Add(resource);
return new Identifier<RGTexture>(resource.index);
} }
public RenderGraphResource GetResource(Identifier<RGResource> resource) public RenderGraphResource GetResource(Identifier<RGResource> resource)
{ {
if (resource.Value < 0 || resource.Value >= _textureResourceCount)
throw new ArgumentException($"Invalid texture handle: {resource}");
return _resources[resource.Value]; return _resources[resource.Value];
} }
public RenderGraphResource GetTextureResourceByIndex(int index) public RenderGraphResource GetTextureResourceByIndex(int index)
{ {
if (index < 0 || index >= _textureResourceCount)
throw new ArgumentException($"Invalid texture index: {index}");
return _resources[index]; return _resources[index];
} }
public void SetProducer(Identifier<RGResource> resourceID, int passIndex) public void SetProducer(Identifier<RGResource> resourceID, int passIndex)
{ {
var resource = GetResource(resourceID); var resource = GetResource(resourceID);
resource.ProducerPass = passIndex; resource.producerPass = passIndex;
if (resource.FirstUsePass < 0) if (resource.firstUsePass < 0)
{ {
resource.FirstUsePass = passIndex; resource.firstUsePass = passIndex;
} }
} }
public void AddConsumer(Identifier<RGResource> resourceID, int passIndex) public void AddConsumer(Identifier<RGResource> resourceID, int passIndex)
{ {
var resource = GetResource(resourceID); var resource = GetResource(resourceID);
resource.ConsumerPasses.Add(passIndex); resource.consumerPasses.Add(passIndex);
resource.LastUsePass = passIndex; resource.lastUsePass = passIndex;
if (resource.FirstUsePass < 0) if (resource.firstUsePass < 0)
{ {
resource.FirstUsePass = passIndex; resource.firstUsePass = passIndex;
} }
} }
private RenderGraphResource GetOrCreateTextureResource()
{
RenderGraphResource resource;
if (_textureResourceCount < _resources.Count)
{
// Reuse existing slot
resource = _resources[_textureResourceCount];
resource.Reset();
}
else
{
// Need to grow the list
resource = _pool.Rent<RenderGraphResource>();
resource.Reset();
_resources.Add(resource);
}
_textureResourceCount++;
return resource;
}
public void Clear()
{
for (var i = 0; i < _resources.Count; i++)
{
_pool.Return(_resources[i]);
}
_resources.Clear();
_textureResourceCount = 0;
}
} }

View File

@@ -71,27 +71,27 @@ public enum TextureFormat : int
/// </summary> /// </summary>
public readonly struct TextureDescriptor : IEquatable<TextureDescriptor> public readonly struct TextureDescriptor : IEquatable<TextureDescriptor>
{ {
public readonly int Width; public readonly int width;
public readonly int Height; public readonly int height;
public readonly TextureFormat Format; public readonly TextureFormat format;
public readonly string Name; public readonly string name;
public TextureDescriptor(int width, int height, TextureFormat format, string name) public TextureDescriptor(int width, int height, TextureFormat format, string name)
{ {
Width = width; this.width = width;
Height = height; this.height = height;
Format = format; this.format = format;
Name = name; this.name = name;
} }
public readonly bool Equals(TextureDescriptor other) => public readonly bool Equals(TextureDescriptor other) =>
Width == other.Width && 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);
} }
/// <summary> /// <summary>