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>
<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.LowLevel" Version="1.3.3" />
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.3.1" />

View File

@@ -5,7 +5,7 @@ namespace Ghost.Core.Utilities;
public class CollectionPool<TCollection, TItem>
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()
{

View File

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

View File

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

View File

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

View File

@@ -105,11 +105,32 @@ public sealed class RenderGraph
return _builder;
}
/// <summary>
/// Computes a _hasher of the render graph structure for caching.
/// Does NOT include pass names (they don't affect compilation).
/// Uses XxHash3 with SIMD optimizations for fast hashing.
/// </summary>
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();
@@ -135,15 +156,14 @@ public sealed class RenderGraph
*(bool*)(pData + offset) = pass.asyncCompute;
offset += sizeof(bool);
*(TextureAccess*)(pData + offset) = pass.depthAccess;
offset += sizeof(TextureAccess);
// 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++)
{
*(TextureAccess*)(pData + offset) = pass.colorAccess[j];
offset += sizeof(TextureAccess);
offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id);
}
*(int*)(pData + offset) = pass.resourceReads.Count;
@@ -171,23 +191,23 @@ public sealed class RenderGraph
}
}
// Hash resource descriptors
for (var i = 0; i < _resources.TextureResourceCount; i++)
{
var resource = _resources.GetTextureResourceByIndex(i);
//// 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.width;
// offset += sizeof(int);
*(int*)(pData + offset) = resource.Descriptor.Height;
offset += sizeof(int);
// *(int*)(pData + offset) = resource.descriptor.height;
// offset += sizeof(int);
*(TextureFormat*)(pData + offset) = resource.Descriptor.Format;
offset += sizeof(TextureFormat);
// *(TextureFormat*)(pData + offset) = resource.descriptor.format;
// offset += sizeof(TextureFormat);
*(bool*)(pData + offset) = resource.IsImported;
offset += sizeof(bool);
}
// *(bool*)(pData + offset) = resource.isImported;
// offset += sizeof(bool);
//}
var span = new Span<byte>(pData, offset);
return XxHash64.HashToUInt64(span);
@@ -208,7 +228,7 @@ public sealed class RenderGraph
#endif
// Step 0: Check cache
var graphHash = ComputeGraphHash(); // 1321433047288519964
var graphHash = ComputeGraphHash(); // 17020363347016000737
#if DEBUG
var hashTime = sw.Elapsed.TotalMicroseconds;
@@ -245,7 +265,7 @@ public sealed class RenderGraph
{
var writeHandle = pass.resourceWrites[j];
var resource = _resources.GetResource(writeHandle);
if (resource.IsImported)
if (resource.isImported)
{
pass.hasSideEffects = true;
break;
@@ -299,31 +319,31 @@ public sealed class RenderGraph
{
// Restore compiled pass list
_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]);
}
// 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)
_aliasingManager.RestoreFromCache(cached.LogicalToPhysical, cached.PhysicalResources);
_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++)
for (var i = 0; i < cached.barriers.Count; i++)
{
_barriers.Add(cached.Barriers[i]);
_barriers.Add(cached.barriers[i]);
}
// Restore resource states
_resourceStates.Clear();
foreach (var kvp in cached.ResourceStates)
foreach (var kvp in cached.resourceStates)
{
_resourceStates[kvp.Key] = kvp.Value;
}
@@ -339,54 +359,75 @@ public sealed class RenderGraph
// Store compiled pass indices
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
for (var i = 0; i < _passes.Count; i++)
{
cacheData.PassCulledFlags.Add(_passes[i].culled);
cacheData.passCulledFlags.Add(_passes[i].culled);
}
// Store aliasing mappings
_aliasingManager.StoreToCache(cacheData.LogicalToPhysical, cacheData.PhysicalResources);
_aliasingManager.StoreToCache(cacheData.logicalToPhysical, cacheData.physicalResources);
// Store barriers
for (var i = 0; i < _barriers.Count; i++)
{
cacheData.Barriers.Add(_barriers[i]);
cacheData.barriers.Add(_barriers[i]);
}
// Store resource states
foreach (var kvp in _resourceStates)
{
cacheData.ResourceStates[kvp.Key] = kvp.Value;
cacheData.resourceStates[kvp.Key] = kvp.Value;
}
_compilationCache.Store(graphHash, cacheData);
}
/// <summary>
/// Recursively un-cull passes that a given pass depends on.
/// </summary>
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 all producers of textures we read
// Un-cull producers of read resources
for (var i = 0; i < pass.resourceReads.Count; i++)
{
var readHandle = pass.resourceReads[i];
var resource = _resources.GetResource(readHandle);
UnculProducer(pass.resourceReads[i]);
}
if (resource.ProducerPass >= 0)
// Un-cull producers of color attachments
for (var i = 0; i <= pass.maxColorIndex; i++)
{
if (pass.colorAccess[i].id.IsValid)
{
var producer = _passes[resource.ProducerPass];
if (producer.culled)
{
producer.culled = false;
UnculDependencies(producer);
}
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>
@@ -431,11 +472,11 @@ public sealed class RenderGraph
var resource = _resources.GetResource(id);
// Skip imported resources
if (resource.IsImported)
if (resource.isImported)
continue;
// 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
var physicalIndex = _aliasingManager.GetPhysicalResourceIndex(id.Value);
@@ -445,22 +486,22 @@ public sealed class RenderGraph
// If this physical resource has multiple aliased resources,
// 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
Identifier<RGResource> resourceBefore = default;
var mostRecentLastUse = -1;
foreach (var otherLogicalIndex in physical.AliasedLogicalResources)
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)
if (otherResource.lastUsePass < pass.index &&
otherResource.lastUsePass > mostRecentLastUse)
{
mostRecentLastUse = otherResource.LastUsePass;
mostRecentLastUse = otherResource.lastUsePass;
resourceBefore = otherLogicalIndex;
}
}
@@ -596,6 +637,13 @@ public sealed class RenderGraph
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]);

View File

@@ -7,54 +7,54 @@ namespace Ghost.RenderGraph.Concept;
/// </summary>
internal sealed class PhysicalResource
{
public int Index;
public int Width;
public int Height;
public TextureFormat Format;
public int SizeInBytes;
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;
public int firstUsePass = int.MaxValue;
public int lastUsePass = -1;
// Aliasing tracking
public readonly List<int> AliasedLogicalResources = new(4);
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();
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;
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);
firstUsePass = Math.Min(firstUsePass, passIndex);
lastUsePass = Math.Max(lastUsePass, passIndex);
}
public bool IsAliveAt(int passIndex)
{
return passIndex >= FirstUsePass && passIndex <= LastUsePass;
return passIndex >= firstUsePass && passIndex <= lastUsePass;
}
public int CalculateSize()
{
int bytesPerPixel = Format switch
int bytesPerPixel = format switch
{
TextureFormat.RGBA8 => 4,
TextureFormat.RGBA16F => 8,
@@ -63,7 +63,7 @@ internal sealed class PhysicalResource
TextureFormat.Depth24Stencil8 => 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++)
{
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));
#if DEBUG
int size = CalculateSize(resource.Descriptor);
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($"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));
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)
@@ -137,7 +137,7 @@ internal sealed class ResourceAliasingManager
{
var physical = _physicalResources[i];
if (physical.CanAlias(logicalResource.Descriptor) &&
if (physical.CanAlias(logicalResource.descriptor) &&
!HasLifetimeOverlap(physical, logicalResource))
{
assignedPhysical = physical;
@@ -149,40 +149,40 @@ internal sealed class ResourceAliasingManager
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();
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");
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}");
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);
assignedPhysical.UpdateLifetime(logicalResource.firstUsePass);
assignedPhysical.UpdateLifetime(logicalResource.lastUsePass);
assignedPhysical.aliasedLogicalResources.Add(logicalIndex);
// Record the mapping
_logicalToPhysical[logicalIndex] = assignedPhysical.Index;
_logicalToPhysical[logicalIndex] = assignedPhysical.index;
}
#if DEBUG
int totalPhysicalSize = 0;
for (int i = 0; i < _physicalResourceCount; i++)
{
totalPhysicalSize += _physicalResources[i].SizeInBytes;
totalPhysicalSize += _physicalResources[i].sizeInBytes;
}
Console.WriteLine($"\n=== Aliasing Summary ===");
@@ -213,8 +213,8 @@ internal sealed class ResourceAliasingManager
{
// 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);
return !(logical.firstUsePass > physical.lastUsePass ||
logical.lastUsePass < physical.firstUsePass);
}
private PhysicalResource GetOrCreatePhysicalResource()
@@ -238,7 +238,7 @@ internal sealed class ResourceAliasingManager
private static int CalculateSize(TextureDescriptor descriptor)
{
int bytesPerPixel = descriptor.Format switch
int bytesPerPixel = descriptor.format switch
{
TextureFormat.RGBA8 => 4,
TextureFormat.RGBA16F => 8,
@@ -247,7 +247,7 @@ internal sealed class ResourceAliasingManager
TextureFormat.Depth24Stencil8 => 4,
_ => 4
};
return descriptor.Width * descriptor.Height * bytesPerPixel;
return descriptor.width * descriptor.height * bytesPerPixel;
}
public void Clear()
@@ -290,13 +290,13 @@ internal sealed class ResourceAliasingManager
}
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();
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();
}
}
@@ -317,12 +317,12 @@ internal sealed class ResourceAliasingManager
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
index = physical.index,
width = physical.width,
height = physical.height,
format = physical.format,
firstUsePass = physical.firstUsePass,
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>

View File

@@ -9,31 +9,31 @@ namespace Ghost.RenderGraph.Concept;
internal sealed class CachedCompilation
{
// 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
public readonly List<bool> PassCulledFlags = new(64);
public readonly List<bool> passCulledFlags = new(64);
// 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
public readonly List<PhysicalResourceData> PhysicalResources = new(32);
public readonly List<PhysicalResourceData> physicalResources = new(32);
// Resource barriers
public readonly List<ResourceBarrier> Barriers = new(128);
public readonly List<ResourceBarrier> barriers = new(128);
// 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()
{
CompiledPassIndices.Clear();
PassCulledFlags.Clear();
LogicalToPhysical.Clear();
PhysicalResources.Clear();
Barriers.Clear();
ResourceStates.Clear();
compiledPassIndices.Clear();
passCulledFlags.Clear();
logicalToPhysical.Clear();
physicalResources.Clear();
barriers.Clear();
resourceStates.Clear();
}
}
@@ -42,12 +42,12 @@ internal sealed class CachedCompilation
/// </summary>
internal struct PhysicalResourceData
{
public int Index;
public int Width;
public int Height;
public TextureFormat Format;
public int FirstUsePass;
public int LastUsePass;
public int index;
public int width;
public int height;
public TextureFormat format;
public int firstUsePass;
public int lastUsePass;
}
/// <summary>
@@ -92,20 +92,20 @@ internal sealed class RenderGraphCompilationCache
// Deep copy the data
_cached.Clear();
_cached.CompiledPassIndices.AddRange(data.CompiledPassIndices);
_cached.PassCulledFlags.AddRange(data.PassCulledFlags);
_cached.compiledPassIndices.AddRange(data.compiledPassIndices);
_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.Barriers.AddRange(data.Barriers);
_cached.physicalResources.AddRange(data.physicalResources);
_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()
{
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
s_allocatedPools.Add(new SharedObjectPool<T>());
return newPool;
@@ -42,6 +42,8 @@ internal sealed class RenderGraphObjectPool
/// Rent a new instance from the pool.
/// </summary>
/// <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();
/// <summary>
@@ -78,25 +80,25 @@ internal sealed class RenderGraphObjectPool
internal sealed class RenderGraphResource
{
public RenderGraphResourceType type;
public int Index;
public TextureDescriptor Descriptor;
public bool IsImported;
public int FirstUsePass = -1;
public int LastUsePass = -1;
public int ProducerPass = -1;
public List<int> ConsumerPasses = new(4);
public int RefCount;
public int index;
public TextureDescriptor descriptor;
public bool isImported;
public int firstUsePass = -1;
public int lastUsePass = -1;
public int producerPass = -1;
public List<int> consumerPasses = new(4);
public int refCount;
public void Reset()
{
Index = -1;
Descriptor = default;
IsImported = false;
FirstUsePass = -1;
LastUsePass = -1;
ProducerPass = -1;
ConsumerPasses.Clear();
RefCount = 0;
index = -1;
descriptor = default;
isImported = false;
firstUsePass = -1;
lastUsePass = -1;
producerPass = -1;
consumerPasses.Clear();
refCount = 0;
}
}
@@ -108,102 +110,73 @@ internal sealed class RenderGraphResourceRegistry
{
private readonly List<RenderGraphResource> _resources = new(64);
private readonly RenderGraphObjectPool _pool = new();
private int _textureResourceCount;
public int TextureResourceCount => _textureResourceCount;
public int TextureResourceCount => _resources.Count;
public void BeginFrame()
{
// Don't clear the lists, just reset the count
// This avoids reallocating the backing arrays
_textureResourceCount = 0;
for (var i = 0; i < _resources.Count; i++)
{
_pool.Return(_resources[i]);
}
_resources.Clear();
}
public Identifier<RGTexture> ImportTexture(TextureDescriptor descriptor)
{
var resource = GetOrCreateTextureResource();
resource.Index = _textureResourceCount - 1;
resource.Descriptor = descriptor;
resource.IsImported = true;
var resource = _pool.Rent<RenderGraphResource>();
resource.type = RenderGraphResourceType.Texture;
resource.index = _resources.Count;
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)
{
var resource = GetOrCreateTextureResource();
resource.Index = _textureResourceCount - 1;
resource.Descriptor = descriptor;
resource.IsImported = false;
var resource = _pool.Rent<RenderGraphResource>();
resource.type = RenderGraphResourceType.Texture;
resource.index = _resources.Count;
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)
{
if (resource.Value < 0 || resource.Value >= _textureResourceCount)
throw new ArgumentException($"Invalid texture handle: {resource}");
return _resources[resource.Value];
}
public RenderGraphResource GetTextureResourceByIndex(int index)
{
if (index < 0 || index >= _textureResourceCount)
throw new ArgumentException($"Invalid texture index: {index}");
return _resources[index];
}
public void SetProducer(Identifier<RGResource> resourceID, int passIndex)
{
var resource = GetResource(resourceID);
resource.ProducerPass = passIndex;
if (resource.FirstUsePass < 0)
resource.producerPass = passIndex;
if (resource.firstUsePass < 0)
{
resource.FirstUsePass = passIndex;
resource.firstUsePass = passIndex;
}
}
public void AddConsumer(Identifier<RGResource> resourceID, int passIndex)
{
var resource = GetResource(resourceID);
resource.ConsumerPasses.Add(passIndex);
resource.LastUsePass = passIndex;
if (resource.FirstUsePass < 0)
resource.consumerPasses.Add(passIndex);
resource.lastUsePass = passIndex;
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>
public readonly struct TextureDescriptor : IEquatable<TextureDescriptor>
{
public readonly int Width;
public readonly int Height;
public readonly TextureFormat Format;
public readonly string Name;
public readonly int width;
public readonly int height;
public readonly TextureFormat format;
public readonly string name;
public TextureDescriptor(int width, int height, TextureFormat format, string name)
{
Width = width;
Height = height;
Format = format;
Name = name;
this.width = width;
this.height = height;
this.format = format;
this.name = name;
}
public readonly bool Equals(TextureDescriptor other) =>
Width == other.Width &&
Height == other.Height &&
Format == other.Format &&
Name == other.Name;
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 override readonly int GetHashCode() => HashCode.Combine(width, height, format, name);
}
/// <summary>