forked from Misaki/GhostEngine
Update render graph
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,47 +359,39 @@ 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 UnculDependencies(RenderGraphPassBase pass)
|
||||
private void UnculProducer(Identifier<RGResource> resource)
|
||||
{
|
||||
// Un-cull all producers of textures we read
|
||||
for (var i = 0; i < pass.resourceReads.Count; i++)
|
||||
var res = _resources.GetResource(resource);
|
||||
if (res.producerPass >= 0)
|
||||
{
|
||||
var readHandle = pass.resourceReads[i];
|
||||
var resource = _resources.GetResource(readHandle);
|
||||
|
||||
if (resource.ProducerPass >= 0)
|
||||
{
|
||||
var producer = _passes[resource.ProducerPass];
|
||||
var producer = _passes[res.producerPass];
|
||||
if (producer.culled)
|
||||
{
|
||||
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>
|
||||
@@ -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]);
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user