diff --git a/Ghost.Core/Ghost.Core.csproj b/Ghost.Core/Ghost.Core.csproj index 76b3753..41339dd 100644 --- a/Ghost.Core/Ghost.Core.csproj +++ b/Ghost.Core/Ghost.Core.csproj @@ -20,7 +20,7 @@ - + diff --git a/Ghost.Core/Utilities/CollectionPool.cs b/Ghost.Core/Utilities/CollectionPool.cs index b388bdf..5d88832 100644 --- a/Ghost.Core/Utilities/CollectionPool.cs +++ b/Ghost.Core/Utilities/CollectionPool.cs @@ -5,7 +5,7 @@ namespace Ghost.Core.Utilities; public class CollectionPool where TCollection : class, ICollection, new() { - internal static readonly ObjectPool s_pool = new ObjectPool(() => new TCollection(), 1); + internal static readonly ObjectPool s_pool = new ObjectPool(() => new TCollection(), null, 1); public static TCollection Rent() { diff --git a/Ghost.Graphics/Core/Vertex.cs b/Ghost.Graphics/Core/Vertex.cs index b47b066..4f15fa5 100644 --- a/Ghost.Graphics/Core/Vertex.cs +++ b/Ghost.Graphics/Core/Vertex.cs @@ -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; diff --git a/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs b/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs index dcb2223..32d4751 100644 --- a/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs +++ b/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs @@ -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; diff --git a/Ghost.RenderGraph.Concept/Program.cs b/Ghost.RenderGraph.Concept/Program.cs index ee17701..bf1232e 100644 --- a/Ghost.RenderGraph.Concept/Program.cs +++ b/Ghost.RenderGraph.Concept/Program.cs @@ -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(); 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); diff --git a/Ghost.RenderGraph.Concept/RenderGraph.cs b/Ghost.RenderGraph.Concept/RenderGraph.cs index 302e765..0d69c57 100644 --- a/Ghost.RenderGraph.Concept/RenderGraph.cs +++ b/Ghost.RenderGraph.Concept/RenderGraph.cs @@ -105,11 +105,32 @@ public sealed class RenderGraph return _builder; } - /// - /// 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. - /// + private unsafe int ComputeTextureHash(byte* pData, int offset, Identifier texture) + { + if (texture.IsInvalid) + { + return offset; + } + + var resource = _resources.GetResource(texture.AsResource()); + + // In real implementation, we typically need to handle imported resources differently. + + *(pData + offset) = resource.isImported ? (byte)1 : (byte)0; + offset += sizeof(byte); + + *(TextureFormat*)(pData + offset) = resource.descriptor.format; + offset += sizeof(TextureFormat); + + *(int*)(pData + offset) = resource.descriptor.width; + offset += sizeof(int); + + *(int*)(pData + offset) = resource.descriptor.height; + offset += sizeof(int); + + return offset; + } + private unsafe ulong ComputeGraphHash() { using var scope = AllocationManager.CreateStackScope(); @@ -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(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); } - /// - /// Recursively un-cull passes that a given pass depends on. - /// + private void UnculProducer(Identifier resource) + { + var res = _resources.GetResource(resource); + if (res.producerPass >= 0) + { + var producer = _passes[res.producerPass]; + if (producer.culled) + { + producer.culled = false; + UnculDependencies(producer); + } + } + } + private void UnculDependencies(RenderGraphPassBase pass) { - // Un-cull 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]); + } } /// @@ -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 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]); diff --git a/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs b/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs index ab06565..d253c94 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs @@ -7,54 +7,54 @@ namespace Ghost.RenderGraph.Concept; /// 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 AliasedLogicalResources = new(4); + public readonly List aliasedLogicalResources = new(4); public void Reset() { - Index = -1; - Width = 0; - Height = 0; - Format = TextureFormat.RGBA8; - SizeInBytes = 0; - FirstUsePass = int.MaxValue; - LastUsePass = -1; - AliasedLogicalResources.Clear(); + 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 }); } } diff --git a/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs b/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs index abe72d7..55bd437 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs @@ -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" + }; + } } /// diff --git a/Ghost.RenderGraph.Concept/RenderGraphCompilationCache.cs b/Ghost.RenderGraph.Concept/RenderGraphCompilationCache.cs index cbfe955..bb7f6df 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphCompilationCache.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphCompilationCache.cs @@ -9,31 +9,31 @@ namespace Ghost.RenderGraph.Concept; internal sealed class CachedCompilation { // Compiled pass indices (indices into the _passes list) - public readonly List CompiledPassIndices = new(64); - + public readonly List compiledPassIndices = new(64); + // Culling decisions for each pass - public readonly List PassCulledFlags = new(64); + public readonly List passCulledFlags = new(64); // Physical resource aliasing mappings (logical index -> physical index) - public readonly Dictionary LogicalToPhysical = new(128); + public readonly Dictionary logicalToPhysical = new(128); // Physical resource metadata - public readonly List PhysicalResources = new(32); + public readonly List physicalResources = new(32); // Resource barriers - public readonly List Barriers = new(128); + public readonly List barriers = new(128); // Resource state mappings (for barrier generation) - public readonly Dictionary ResourceStates = new(128); + public readonly Dictionary 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 /// 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; } /// @@ -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; } } diff --git a/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs b/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs index 5908d45..e4b7954 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs @@ -23,7 +23,7 @@ internal sealed class RenderGraphObjectPool private static ObjectPool AllocatePool() { - var newPool = new ObjectPool(() => new T()); + var newPool = new ObjectPool(() => new T(), null); // Storing instance to clear the static pool of the same type if needed s_allocatedPools.Add(new SharedObjectPool()); return newPool; @@ -42,6 +42,8 @@ internal sealed class RenderGraphObjectPool /// Rent a new instance from the pool. /// /// + // FIX: ObjectPool.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(); /// @@ -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 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 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 _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 ImportTexture(TextureDescriptor descriptor) { - var resource = GetOrCreateTextureResource(); - resource.Index = _textureResourceCount - 1; - resource.Descriptor = descriptor; - resource.IsImported = true; + var resource = _pool.Rent(); + resource.type = RenderGraphResourceType.Texture; + resource.index = _resources.Count; + resource.descriptor = descriptor; + resource.isImported = true; - return new Identifier(resource.Index); + _resources.Add(resource); + + return new Identifier(resource.index); } public Identifier CreateTexture(TextureDescriptor descriptor) { - var resource = GetOrCreateTextureResource(); - resource.Index = _textureResourceCount - 1; - resource.Descriptor = descriptor; - resource.IsImported = false; + var resource = _pool.Rent(); + resource.type = RenderGraphResourceType.Texture; + resource.index = _resources.Count; + resource.descriptor = descriptor; + resource.isImported = false; - return new Identifier(resource.Index); + _resources.Add(resource); + + return new Identifier(resource.index); } public RenderGraphResource GetResource(Identifier 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 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 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(); - 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; - } } diff --git a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs b/Ghost.RenderGraph.Concept/RenderGraphTypes.cs index 7a735b9..cea75cf 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphTypes.cs @@ -71,27 +71,27 @@ public enum TextureFormat : int /// public readonly struct TextureDescriptor : IEquatable { - 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); } ///