From ac36bbf8c78e1e839f4dcca4cf759d058725c626 Mon Sep 17 00:00:00 2001 From: Misaki Date: Tue, 13 Jan 2026 14:50:55 +0900 Subject: [PATCH] small backup --- Ghost.Graphics/D3D12/D3D12CommandBuffer.cs | 8 +- Ghost.RenderGraph.Concept/RenderGraph.cs | 95 ++- Ghost.RenderGraph.Concept/RenderGraph.cs.bak | 663 ++++++++++++++++++ .../RenderGraphAliasing.cs.bak | 329 +++++++++ .../RenderGraphBuilder.cs | 15 +- .../RenderGraphBuilder.cs.bak | 240 +++++++ Ghost.RenderGraph.Concept/RenderGraphPass.cs | 31 +- .../RenderGraphPass.cs.bak | 129 ++++ Ghost.RenderGraph.Concept/RenderGraphTypes.cs | 73 +- 9 files changed, 1522 insertions(+), 61 deletions(-) create mode 100644 Ghost.RenderGraph.Concept/RenderGraph.cs.bak create mode 100644 Ghost.RenderGraph.Concept/RenderGraphAliasing.cs.bak create mode 100644 Ghost.RenderGraph.Concept/RenderGraphBuilder.cs.bak create mode 100644 Ghost.RenderGraph.Concept/RenderGraphPass.cs.bak diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs index 2503369..90e3218 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs @@ -383,7 +383,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer for (var i = 0; i < rtDescs.Length; i++) { var rtDesc = rtDescs[i]; - if (!rtDesc.Texture.IsValid) + if (rtDesc.Texture.IsInvalid) { RecordError(nameof(BeginRenderPass), ErrorStatus.InvalidArgument); continue; @@ -396,7 +396,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer continue; } - var record = recordResult.Value; + ref var record = ref recordResult.Value; var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv); var format = record.desc.TextureDescription.Format.ToDXGIFormat(); var clearColor = rtDesc.ClearColor; @@ -431,7 +431,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer return; } - var record = recordResult.Value; + ref var record = ref recordResult.Value; var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv); var format = record.desc.TextureDescription.Format.ToDXGIFormat(); @@ -544,7 +544,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer return; } - var record = recordResult.Value; + ref var record = ref recordResult.Value; var vbView = new D3D12_VERTEX_BUFFER_VIEW { BufferLocation = record.ResourcePtr.Get()->GetGPUVirtualAddress() + offset, diff --git a/Ghost.RenderGraph.Concept/RenderGraph.cs b/Ghost.RenderGraph.Concept/RenderGraph.cs index 0d69c57..8328b8a 100644 --- a/Ghost.RenderGraph.Concept/RenderGraph.cs +++ b/Ghost.RenderGraph.Concept/RenderGraph.cs @@ -166,35 +166,42 @@ public sealed class RenderGraph offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id); } - *(int*)(pData + offset) = pass.resourceReads.Count; - offset += sizeof(int); - for (var j = 0; j < pass.resourceReads.Count; j++) + for (var j = 0; j < (int)RenderGraphResourceType.Count; j++) { - *(int*)(pData + offset) = pass.resourceReads[j].Value; - offset += sizeof(int); - } + var readList = pass.resourceReads[j]; + var writeList = pass.resourceWrites[j]; + var createList = pass.resourceCreates[j]; - *(int*)(pData + offset) = pass.resourceWrites.Count; - offset += sizeof(int); - for (var j = 0; j < pass.resourceWrites.Count; j++) - { - *(int*)(pData + offset) = pass.resourceWrites[j].Value; + *(int*)(pData + offset) = readList.Count; offset += sizeof(int); - } + for (var k = 0; k < readList.Count; k++) + { + *(int*)(pData + offset) = readList[k].Value; + offset += sizeof(int); + } - *(int*)(pData + offset) = pass.resourceCreates.Count; - offset += sizeof(int); - for (var j = 0; j < pass.resourceCreates.Count; j++) - { - *(int*)(pData + offset) = pass.resourceCreates[j].Value; + *(int*)(pData + offset) = writeList.Count; offset += sizeof(int); + for (var k = 0; k < writeList.Count; k++) + { + *(int*)(pData + offset) = writeList[k].Value; + offset += sizeof(int); + } + + *(int*)(pData + offset) = createList.Count; + offset += sizeof(int); + for (var k = 0; k < createList.Count; k++) + { + *(int*)(pData + offset) = createList[k].Value; + offset += sizeof(int); + } } } //// Hash resource descriptors - //for (var i = 0; i < _resources.TextureResourceCount; i++) + //for (var j = 0; j < _resources.TextureResourceCount; j++) //{ - // var resource = _resources.GetTextureResourceByIndex(i); + // var resource = _resources.GetTextureResourceByIndex(j); // *(int*)(pData + offset) = resource.descriptor.width; // offset += sizeof(int); @@ -260,15 +267,19 @@ public sealed class RenderGraph { var pass = _passes[i]; - // Check if this pass writes to any imported textures - for (var j = 0; j < pass.resourceWrites.Count; j++) + // Check if this pass writes to any imported resources + for (var j = 0; j < (int)RenderGraphResourceType.Count; j++) { - var writeHandle = pass.resourceWrites[j]; - var resource = _resources.GetResource(writeHandle); - if (resource.isImported) + var writeList = pass.resourceWrites[j]; + for (var k = 0; k < writeList.Count; k++) { - pass.hasSideEffects = true; - break; + var writeHandle = writeList[k]; + var resource = _resources.GetResource(writeHandle); + if (resource.isImported) + { + pass.hasSideEffects = true; + break; + } } } } @@ -403,13 +414,17 @@ public sealed class RenderGraph private void UnculDependencies(RenderGraphPassBase pass) { // Un-cull producers of read resources - for (var i = 0; i < pass.resourceReads.Count; i++) + for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) { - UnculProducer(pass.resourceReads[i]); + var readList = pass.resourceReads[i]; + for (var j = 0; j < readList.Count; j++) + { + UnculProducer(readList[j]); + } } // Un-cull producers of color attachments - for (var i = 0; i <= pass.maxColorIndex; i++) + for (var i = 0; i < pass.maxColorIndex; i++) { if (pass.colorAccess[i].id.IsValid) { @@ -533,16 +548,20 @@ public sealed class RenderGraph private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx) { // Process reads (transition to shader resource) - for (var i = 0; i < pass.resourceReads.Count; i++) + for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) { - var handle = pass.resourceReads[i]; - InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx); + var readList = pass.resourceReads[i]; + for (var j = 0; j < readList.Count; j++) + { + var handle = readList[j]; + InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx); + } } switch (pass.type) { case RenderPassType.Raster: - for (var i = 0; i <= pass.maxColorIndex; i++) + for (var i = 0; i < pass.maxColorIndex; i++) { var access = pass.colorAccess[i]; InsertTransitionIfNeeded(access.id.AsResource(), ResourceState.RenderTarget, passIdx); @@ -561,10 +580,14 @@ public sealed class RenderGraph break; case RenderPassType.Compute: - for (var i = 0; i < pass.resourceWrites.Count; i++) + for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) { - var id = pass.resourceWrites[i]; - InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx); + var writeList = pass.resourceWrites[i]; + for (var j = 0; j < writeList.Count; j++) + { + var id = writeList[j]; + InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx); + } } break; diff --git a/Ghost.RenderGraph.Concept/RenderGraph.cs.bak b/Ghost.RenderGraph.Concept/RenderGraph.cs.bak new file mode 100644 index 0000000..0d69c57 --- /dev/null +++ b/Ghost.RenderGraph.Concept/RenderGraph.cs.bak @@ -0,0 +1,663 @@ +using Ghost.Core; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; +using System.IO.Hashing; +using TerraFX.Interop.Windows; + +namespace Ghost.RenderGraph.Concept; + +/// +/// Main render graph class that manages resource allocation and pass execution. +/// +/// Design principles for minimal GC: +/// - Object pooling for all passes and resources +/// - Reuse collections across frames (Clear() instead of new) +/// - Avoid LINQ and foreach over interfaces +/// - Pre-allocate capacity based on expected usage +/// +public sealed class RenderGraph +{ + private readonly RenderGraphResourceRegistry _resources = new(); + private readonly RenderGraphObjectPool _objectPool = new(); + private readonly List _passes = new(64); + private readonly List _compiledPasses = new(64); + private readonly RenderGraphBuilder _builder = new(); + private readonly MockCommandBuffer _commandBuffer = new(); + private readonly RenderContext _renderContext; + private readonly ResourceAliasingManager _aliasingManager = new(); + private readonly Dictionary _resourceStates = new(128); + private readonly List _barriers = new(128); + private readonly RenderGraphCompilationCache _compilationCache = new(); + + private bool _compiled; + + public RenderGraphBlackboard Blackboard { get; } = new(); + + public RenderGraph() + { + _renderContext = new RenderContext(_commandBuffer); + } + + /// + /// Resets the render graph for a new frame. + /// Reuses existing allocations to minimize GC. + /// + public void Reset() + { + // Clear blackboard data + Blackboard.Clear(); + + // Reset resources but keep allocations + _resources.BeginFrame(); + + // Reset aliasing manager + _aliasingManager.BeginFrame(); + + // Clear resource states and barriers + _resourceStates.Clear(); + _barriers.Clear(); + + // Return passes to the pool and reset count + for (var i = 0; i < _passes.Count; i++) + { + var pass = _passes[i]; + pass.Reset(_objectPool); + } + + _passes.Clear(); + + // Clear compiled passes list + _compiledPasses.Clear(); + _compiled = false; + } + + /// + /// Imports an external texture into the render graph. + /// + public Identifier ImportTexture(TextureDescriptor descriptor) + { + return _resources.ImportTexture(descriptor); + } + + public IRasterRenderGraphBuilder AddRasterRenderPass(string name, out TPassData passData) + where TPassData : class, new() + { + var renderPass = _objectPool.Rent>(); + renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Raster); + passData = renderPass.passData; + + _passes.Add(renderPass); + + _builder.Init(this, renderPass, _resources); + return _builder; + } + + public IComputeRenderGraphBuilder AddComputeRenderPass(string name, out TPassData passData) + where TPassData : class, new() + { + var renderPass = _objectPool.Rent>(); + renderPass.Init(_passes.Count, _objectPool.Rent(), name, RenderPassType.Compute); + passData = renderPass.passData; + + _passes.Add(renderPass); + + _builder.Init(this, renderPass, _resources); + return _builder; + } + + private unsafe int ComputeTextureHash(byte* pData, int offset, Identifier texture) + { + if (texture.IsInvalid) + { + return offset; + } + + var resource = _resources.GetResource(texture.AsResource()); + + // In real implementation, we typically need to handle imported resources differently. + + *(pData + offset) = resource.isImported ? (byte)1 : (byte)0; + offset += sizeof(byte); + + *(TextureFormat*)(pData + offset) = resource.descriptor.format; + offset += sizeof(TextureFormat); + + *(int*)(pData + offset) = resource.descriptor.width; + offset += sizeof(int); + + *(int*)(pData + offset) = resource.descriptor.height; + offset += sizeof(int); + + return offset; + } + + private unsafe ulong ComputeGraphHash() + { + using var scope = AllocationManager.CreateStackScope(); + var bufferPool = new UnsafeList(2048, scope.AllocationHandle); + var pData = (byte*)bufferPool.GetUnsafePtr(); + var offset = 0; + + // Hash pass count + *(int*)(pData + offset) = _passes.Count; + offset += sizeof(int); + + // Hash each pass structure (excluding names) + for (var i = 0; i < _passes.Count; i++) + { + var pass = _passes[i]; + + *(RenderPassType*)(pData + offset) = pass.type; + offset += sizeof(RenderPassType); + + *(bool*)(pData + offset) = pass.allowCulling; + offset += sizeof(bool); + + *(bool*)(pData + offset) = pass.asyncCompute; + offset += sizeof(bool); + + // Hash depth attachment + offset = ComputeTextureHash(pData, offset, pass.depthAccess.id); + + *(int*)(pData + offset) = pass.maxColorIndex; + offset += sizeof(int); + for (var j = 0; j <= pass.maxColorIndex; j++) + { + offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id); + } + + *(int*)(pData + offset) = pass.resourceReads.Count; + offset += sizeof(int); + for (var j = 0; j < pass.resourceReads.Count; j++) + { + *(int*)(pData + offset) = pass.resourceReads[j].Value; + offset += sizeof(int); + } + + *(int*)(pData + offset) = pass.resourceWrites.Count; + offset += sizeof(int); + for (var j = 0; j < pass.resourceWrites.Count; j++) + { + *(int*)(pData + offset) = pass.resourceWrites[j].Value; + offset += sizeof(int); + } + + *(int*)(pData + offset) = pass.resourceCreates.Count; + offset += sizeof(int); + for (var j = 0; j < pass.resourceCreates.Count; j++) + { + *(int*)(pData + offset) = pass.resourceCreates[j].Value; + offset += sizeof(int); + } + } + + //// Hash resource descriptors + //for (var i = 0; i < _resources.TextureResourceCount; i++) + //{ + // var resource = _resources.GetTextureResourceByIndex(i); + + // *(int*)(pData + offset) = resource.descriptor.width; + // offset += sizeof(int); + + // *(int*)(pData + offset) = resource.descriptor.height; + // offset += sizeof(int); + + // *(TextureFormat*)(pData + offset) = resource.descriptor.format; + // offset += sizeof(TextureFormat); + + // *(bool*)(pData + offset) = resource.isImported; + // offset += sizeof(bool); + //} + + var span = new Span(pData, offset); + return XxHash64.HashToUInt64(span); + } + + /// + /// Compiles the render graph by culling unused passes and determining resource lifetimes. + /// + public void Compile() + { + if (_compiled) + { + return; + } + +#if DEBUG + var sw = System.Diagnostics.Stopwatch.StartNew(); +#endif + + // Step 0: Check cache + var graphHash = ComputeGraphHash(); // 17020363347016000737 + +#if DEBUG + var hashTime = sw.Elapsed.TotalMicroseconds; +#endif + + if (_compilationCache.TryGetCached(graphHash, out var cached)) + { + // CACHE HIT - restore from cache +#if DEBUG + Console.WriteLine($"\n[CACHE HIT] Hash: {graphHash:X16} (computed in {hashTime:F2}μs)"); +#endif + RestoreFromCache(cached); +#if DEBUG + sw.Stop(); + Console.WriteLine($"[CACHE HIT] Total restore time: {sw.Elapsed.TotalMicroseconds:F2}μs"); +#endif + _compiled = true; + return; + } + +#if DEBUG + Console.WriteLine($"\n[CACHE MISS] Hash: {graphHash:X16} (computed in {hashTime:F2}μs)"); +#endif + + _compiledPasses.Clear(); + + // Step 1: Mark passes with side effects (writes to imported resources) + for (var i = 0; i < _passes.Count; i++) + { + var pass = _passes[i]; + + // Check if this pass writes to any imported textures + for (var j = 0; j < pass.resourceWrites.Count; j++) + { + var writeHandle = pass.resourceWrites[j]; + var resource = _resources.GetResource(writeHandle); + if (resource.isImported) + { + pass.hasSideEffects = true; + break; + } + } + } + + // Step 2: Cull passes based on dependency analysis + // Mark all passes as culled initially + for (var i = 0; i < _passes.Count; i++) + { + _passes[i].culled = _passes[i].allowCulling && !_passes[i].hasSideEffects; + } + + // Step 3: Traverse backwards from passes with side effects + for (var i = _passes.Count - 1; i >= 0; i--) + { + var pass = _passes[i]; + if (!pass.culled) + { + UnculDependencies(pass); + } + } + + // Step 4: Build final pass list (only non-culled passes) + for (var i = 0; i < _passes.Count; i++) + { + var pass = _passes[i]; + if (!pass.culled) + { + _compiledPasses.Add(pass); + } + } + + // Step 5: Perform resource aliasing to minimize memory usage + _aliasingManager.AssignPhysicalResources(_resources, _passes.Count); + + // Step 6: Generate barriers for state transitions and aliasing + GenerateBarriers(); + + // Step 7: Store in cache for future frames + StoreInCache(graphHash); + + _compiled = true; + } + + /// + /// Restores the render graph state from cached compilation results. + /// + private void RestoreFromCache(CachedCompilation cached) + { + // Restore compiled pass list + _compiledPasses.Clear(); + for (var i = 0; i < cached.compiledPassIndices.Count; i++) + { + var passIndex = cached.compiledPassIndices[i]; + _compiledPasses.Add(_passes[passIndex]); + } + + // Restore culling flags + for (var i = 0; i < _passes.Count && i < cached.passCulledFlags.Count; i++) + { + _passes[i].culled = cached.passCulledFlags[i]; + } + + // Restore aliasing mappings (need to update ResourceAliasingManager) + _aliasingManager.RestoreFromCache(cached.logicalToPhysical, cached.physicalResources); + + // Restore barriers (deep copy to avoid shared references) + _barriers.Clear(); + for (var i = 0; i < cached.barriers.Count; i++) + { + _barriers.Add(cached.barriers[i]); + } + + // Restore resource states + _resourceStates.Clear(); + foreach (var kvp in cached.resourceStates) + { + _resourceStates[kvp.Key] = kvp.Value; + } + } + + /// + /// Stores current compilation results in the cache. + /// + private void StoreInCache(ulong graphHash) + { + var cacheData = new CachedCompilation(); + + // Store compiled pass indices + for (var i = 0; i < _compiledPasses.Count; i++) + { + cacheData.compiledPassIndices.Add(_compiledPasses[i].index); + } + + // Store culling flags for all passes + for (var i = 0; i < _passes.Count; i++) + { + cacheData.passCulledFlags.Add(_passes[i].culled); + } + + // Store aliasing mappings + _aliasingManager.StoreToCache(cacheData.logicalToPhysical, cacheData.physicalResources); + + // Store barriers + for (var i = 0; i < _barriers.Count; i++) + { + cacheData.barriers.Add(_barriers[i]); + } + + // Store resource states + foreach (var kvp in _resourceStates) + { + cacheData.resourceStates[kvp.Key] = kvp.Value; + } + + _compilationCache.Store(graphHash, cacheData); + } + + private void UnculProducer(Identifier resource) + { + var res = _resources.GetResource(resource); + if (res.producerPass >= 0) + { + var producer = _passes[res.producerPass]; + if (producer.culled) + { + producer.culled = false; + UnculDependencies(producer); + } + } + } + + private void UnculDependencies(RenderGraphPassBase pass) + { + // Un-cull producers of read resources + for (var i = 0; i < pass.resourceReads.Count; i++) + { + UnculProducer(pass.resourceReads[i]); + } + + // Un-cull producers of color attachments + for (var i = 0; i <= pass.maxColorIndex; i++) + { + if (pass.colorAccess[i].id.IsValid) + { + UnculProducer(pass.colorAccess[i].id.AsResource()); + } + } + + // Un-cull producer of depth attachment + if (pass.depthAccess.id.IsValid) + { + UnculProducer(pass.depthAccess.id.AsResource()); + } + + // Un-cull producers of UAV resources (if not already in reads/writes) + for (var i = 0; i < pass.randomAccess.Count; i++) + { + UnculProducer(pass.randomAccess[i]); + } + } + + /// + /// Generates resource barriers for state transitions and aliasing. + /// + private void GenerateBarriers() + { + _barriers.Clear(); + _resourceStates.Clear(); + +#if DEBUG + Console.WriteLine("\n=== Barrier Generation ==="); +#endif + + // Process each compiled pass in order + for (var passIdx = 0; passIdx < _compiledPasses.Count; passIdx++) + { + var pass = _compiledPasses[passIdx]; + + // Insert aliasing barriers for resources that reuse physical memory + InsertAliasingBarriers(pass, passIdx); + + // Insert transition barriers for state changes + InsertTransitionBarriers(pass, passIdx); + } + +#if DEBUG + Console.WriteLine($"Total Barriers: {_barriers.Count}"); + Console.WriteLine("==========================\n"); +#endif + } + + /// + /// Inserts aliasing barriers when a physical resource is reused. + /// + private void InsertAliasingBarriers(RenderGraphPassBase pass, int passIdx) + { + // Check all resources written by this pass + for (var i = 0; i < pass.resourceWrites.Count; i++) + { + var id = pass.resourceWrites[i]; + var resource = _resources.GetResource(id); + + // Skip imported resources + if (resource.isImported) + continue; + + // Check if this is the first use of this logical resource + if (resource.firstUsePass == pass.index) + { + // Rent the physical resource + var physicalIndex = _aliasingManager.GetPhysicalResourceIndex(id.Value); + if (physicalIndex >= 0) + { + var physical = _aliasingManager.GetPhysicalResource(physicalIndex); + + // If this physical resource has multiple aliased resources, + // we need an aliasing barrier when switching between them + if (physical != null && physical.aliasedLogicalResources.Count > 1) + { + // Find the resource that used this physical memory most recently before this pass + Identifier resourceBefore = default; + var mostRecentLastUse = -1; + + foreach (var otherLogicalIndex in physical.aliasedLogicalResources) + { + if (otherLogicalIndex != id.Value) + { + var otherResource = _resources.GetTextureResourceByIndex(otherLogicalIndex); + // Check if this resource finished before our resource starts + if (otherResource.lastUsePass < pass.index && + otherResource.lastUsePass > mostRecentLastUse) + { + mostRecentLastUse = otherResource.lastUsePass; + resourceBefore = otherLogicalIndex; + } + } + } + + // If we found a previous resource, insert aliasing barrier + if (mostRecentLastUse >= 0) + { + var barrier = ResourceBarrier.CreateAliasingBarrier( + resourceBefore, + id, + passIdx + ); + _barriers.Add(barrier); + +#if DEBUG + Console.WriteLine($" {barrier}"); +#endif + } + } + } + } + } + } + + /// + /// Inserts transition barriers when a resource changes state. + /// + private void InsertTransitionBarriers(RenderGraphPassBase pass, int passIdx) + { + // Process reads (transition to shader resource) + for (var i = 0; i < pass.resourceReads.Count; i++) + { + var handle = pass.resourceReads[i]; + InsertTransitionIfNeeded(handle, ResourceState.ShaderResource, passIdx); + } + + switch (pass.type) + { + case RenderPassType.Raster: + for (var i = 0; i <= pass.maxColorIndex; i++) + { + var access = pass.colorAccess[i]; + InsertTransitionIfNeeded(access.id.AsResource(), ResourceState.RenderTarget, passIdx); + } + + if (pass.depthAccess.id.IsValid) + { + var depthAccess = pass.depthAccess; + InsertTransitionIfNeeded(depthAccess.id.AsResource(), ResourceState.DepthWrite, passIdx); + } + + for (var i = 0; i < pass.randomAccess.Count; i++) + { + InsertTransitionIfNeeded(pass.randomAccess[i], ResourceState.UnorderedAccess, passIdx); + } + + break; + case RenderPassType.Compute: + for (var i = 0; i < pass.resourceWrites.Count; i++) + { + var id = pass.resourceWrites[i]; + InsertTransitionIfNeeded(id, ResourceState.UnorderedAccess, passIdx); + } + + break; + } + } + + /// + /// Inserts a transition barrier if the resource state changes. + /// + private void InsertTransitionIfNeeded(Identifier resource, ResourceState newState, int passIdx) + { + if (!_resourceStates.TryGetValue(resource.Value, out var currentState)) + { + // First time seeing this resource, assume undefined + currentState = ResourceState.Common; + } + + if (currentState != newState) + { + var barrier = ResourceBarrier.CreateTransitionBarrier( + resource, + currentState, + newState, + passIdx + ); + _barriers.Add(barrier); + _resourceStates[resource.Value] = newState; + +#if DEBUG + Console.WriteLine($" {barrier}"); +#endif + } + } + + /// + /// Executes all compiled passes. + /// + public void Execute() + { + if (!_compiled) + { + Compile(); + } + + // Execute each non-culled pass + var barrierIndex = 0; + for (var i = 0; i < _compiledPasses.Count; i++) + { + var pass = _compiledPasses[i]; + + // Execute all barriers for this pass +#if DEBUG + bool hasBarriers = false; +#endif + while (barrierIndex < _barriers.Count && _barriers[barrierIndex].PassIndex == i) + { +#if DEBUG + if (!hasBarriers) + { + Console.WriteLine($"\n=== Barriers before Pass {i}: {pass.name} ==="); + hasBarriers = true; + } + + var barrier = _barriers[barrierIndex]; + if (barrier.Type == BarrierType.Transition) + { + _commandBuffer.ResourceBarrier( + barrier.Resource, + barrier.StateBefore, + barrier.StateAfter + ); + } + else if (barrier.Type == BarrierType.Aliasing) + { + _commandBuffer.AliasBarrier( + barrier.ResourceBefore, + barrier.ResourceAfter + ); + } +#endif + // In a real implementation, you would execute the barrier here: + // ExecuteBarrier(_barriers[barrierIndex]); + + barrierIndex++; + } +#if DEBUG + if (hasBarriers) + { + Console.WriteLine("=====================================\n"); + } +#endif + + pass.Execute(_renderContext); + } + } +} diff --git a/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs.bak b/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs.bak new file mode 100644 index 0000000..d253c94 --- /dev/null +++ b/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs.bak @@ -0,0 +1,329 @@ +using Ghost.Core.Utilities; + +namespace Ghost.RenderGraph.Concept; + +/// +/// Represents a physical GPU resource that can be aliased by multiple logical resources. +/// +internal sealed class PhysicalResource +{ + public int index; + public int width; + public int height; + public TextureFormat format; + public int sizeInBytes; + + // Lifetime tracking + public int firstUsePass = int.MaxValue; + public int lastUsePass = -1; + + // Aliasing tracking + public readonly List aliasedLogicalResources = new(4); + + public void Reset() + { + index = -1; + width = 0; + height = 0; + format = TextureFormat.RGBA8; + sizeInBytes = 0; + firstUsePass = int.MaxValue; + lastUsePass = -1; + aliasedLogicalResources.Clear(); + } + + public bool CanAlias(TextureDescriptor descriptor) + { + // For aliasing, resources must be identical in size and format + // In a real implementation, you could be more flexible (e.g., same size but different format) + return width == descriptor.width && + height == descriptor.height && + format == descriptor.format; + } + + public void UpdateLifetime(int passIndex) + { + firstUsePass = Math.Min(firstUsePass, passIndex); + lastUsePass = Math.Max(lastUsePass, passIndex); + } + + public bool IsAliveAt(int passIndex) + { + return passIndex >= firstUsePass && passIndex <= lastUsePass; + } + + public int CalculateSize() + { + int bytesPerPixel = format switch + { + TextureFormat.RGBA8 => 4, + TextureFormat.RGBA16F => 8, + TextureFormat.RGBA32F => 16, + TextureFormat.Depth32F => 4, + TextureFormat.Depth24Stencil8 => 4, + _ => 4 + }; + return width * height * bytesPerPixel; + } +} + +/// +/// Manages physical resource allocation and aliasing. +/// Uses interval scheduling algorithm to minimize memory usage. +/// +internal sealed class ResourceAliasingManager +{ + private readonly List _physicalResources = new(32); + private readonly RenderGraphObjectPool _pool = new(); + private int _physicalResourceCount; + + // Mapping from logical resource index to physical resource index + private readonly Dictionary _logicalToPhysical = new(64); + + public void BeginFrame() + { + _physicalResourceCount = 0; + _logicalToPhysical.Clear(); + + // Reset physical resources but keep them in the pool + for (int i = 0; i < _physicalResources.Count; i++) + { + _physicalResources[i].Reset(); + } + } + + /// + /// Assigns physical resources to logical resources using greedy interval scheduling. + /// This minimizes total GPU memory usage. + /// + public void AssignPhysicalResources(RenderGraphResourceRegistry registry, int passCount) + { +#if DEBUG + Console.WriteLine("\n=== Resource Aliasing Analysis ==="); + int totalLogicalSize = 0; +#endif + + // Build list of all logical resources with their lifetimes + var logicalResources = ListPool<(int index, RenderGraphResource resource)>.Rent(); + + for (int i = 0; i < registry.TextureResourceCount; i++) + { + var resource = registry.GetTextureResourceByIndex(i); + if (!resource.isImported) // Don't alias imported resources + { + logicalResources.Add((i, resource)); +#if DEBUG + int size = CalculateSize(resource.descriptor); + totalLogicalSize += size; + Console.WriteLine($"Logical Resource {i}: {resource.descriptor.name}"); + Console.WriteLine($" Lifetime: Pass {resource.firstUsePass} -> {resource.lastUsePass}"); + Console.WriteLine($" Size: {size / 1024.0:F2} KB"); +#endif + } + } + + // Sort by first use pass (earlier resources first) + logicalResources.Sort((a, b) => a.resource.firstUsePass.CompareTo(b.resource.firstUsePass)); + + // Greedy interval scheduling: assign each logical resource to a physical resource + foreach (var (logicalIndex, logicalResource) in logicalResources) + { + PhysicalResource? assignedPhysical = null; + + // Try to find an existing physical resource that: + // 1. Has compatible format/size + // 2. Is not alive during this logical resource's lifetime + for (int i = 0; i < _physicalResourceCount; i++) + { + var physical = _physicalResources[i]; + + if (physical.CanAlias(logicalResource.descriptor) && + !HasLifetimeOverlap(physical, logicalResource)) + { + assignedPhysical = physical; + break; + } + } + + // No compatible physical resource found, allocate a new one + if (assignedPhysical == null) + { + assignedPhysical = GetOrCreatePhysicalResource(); + assignedPhysical.index = _physicalResourceCount - 1; + assignedPhysical.width = logicalResource.descriptor.width; + assignedPhysical.height = logicalResource.descriptor.height; + assignedPhysical.format = logicalResource.descriptor.format; + assignedPhysical.sizeInBytes = assignedPhysical.CalculateSize(); + +#if DEBUG + Console.WriteLine($"\nAllocated NEW Physical Resource {assignedPhysical.index}:"); + Console.WriteLine($" Size: {assignedPhysical.width}x{assignedPhysical.height}"); + Console.WriteLine($" Format: {assignedPhysical.format}"); + Console.WriteLine($" Memory: {assignedPhysical.sizeInBytes / 1024.0:F2} KB"); +#endif + } +#if DEBUG + else + { + Console.WriteLine($"\nALIASING: {logicalResource.descriptor.name} -> Physical Resource {assignedPhysical.index}"); + } +#endif + + // Update physical resource lifetime + assignedPhysical.UpdateLifetime(logicalResource.firstUsePass); + assignedPhysical.UpdateLifetime(logicalResource.lastUsePass); + assignedPhysical.aliasedLogicalResources.Add(logicalIndex); + + // Record the mapping + _logicalToPhysical[logicalIndex] = assignedPhysical.index; + } + +#if DEBUG + int totalPhysicalSize = 0; + for (int i = 0; i < _physicalResourceCount; i++) + { + totalPhysicalSize += _physicalResources[i].sizeInBytes; + } + + Console.WriteLine($"\n=== Aliasing Summary ==="); + Console.WriteLine($"Logical Resources: {logicalResources.Count}"); + Console.WriteLine($"Physical Resources: {_physicalResourceCount}"); + Console.WriteLine($"Total Logical Memory: {totalLogicalSize / 1024.0:F2} KB"); + Console.WriteLine($"Total Physical Memory: {totalPhysicalSize / 1024.0:F2} KB"); + Console.WriteLine($"Memory Saved: {(totalLogicalSize - totalPhysicalSize) / 1024.0:F2} KB ({(1.0 - (double)totalPhysicalSize / totalLogicalSize) * 100.0:F1}%)"); + Console.WriteLine("================================\n"); +#endif + + ListPool<(int index, RenderGraphResource resource)>.Return(logicalResources); + } + + public int GetPhysicalResourceIndex(int logicalIndex) + { + return _logicalToPhysical.TryGetValue(logicalIndex, out var physicalIndex) ? physicalIndex : -1; + } + + public PhysicalResource? GetPhysicalResource(int physicalIndex) + { + return physicalIndex >= 0 && physicalIndex < _physicalResourceCount + ? _physicalResources[physicalIndex] + : null; + } + + private bool HasLifetimeOverlap(PhysicalResource physical, RenderGraphResource logical) + { + // Check if the lifetimes overlap + // No overlap if: logical.First > physical.Last OR logical.Last < physical.First + return !(logical.firstUsePass > physical.lastUsePass || + logical.lastUsePass < physical.firstUsePass); + } + + private PhysicalResource GetOrCreatePhysicalResource() + { + PhysicalResource resource; + if (_physicalResourceCount < _physicalResources.Count) + { + resource = _physicalResources[_physicalResourceCount]; + resource.Reset(); + } + else + { + resource = _pool.Rent(); + resource.Reset(); + _physicalResources.Add(resource); + } + + _physicalResourceCount++; + return resource; + } + + private static int CalculateSize(TextureDescriptor descriptor) + { + int bytesPerPixel = descriptor.format switch + { + TextureFormat.RGBA8 => 4, + TextureFormat.RGBA16F => 8, + TextureFormat.RGBA32F => 16, + TextureFormat.Depth32F => 4, + TextureFormat.Depth24Stencil8 => 4, + _ => 4 + }; + return descriptor.width * descriptor.height * bytesPerPixel; + } + + public void Clear() + { + for (int i = 0; i < _physicalResources.Count; i++) + { + _pool.Return(_physicalResources[i]); + } + _physicalResources.Clear(); + _physicalResourceCount = 0; + _logicalToPhysical.Clear(); + } + + /// + /// Restores aliasing state from cache. + /// + public void RestoreFromCache(Dictionary logicalToPhysical, List physicalData) + { + _logicalToPhysical.Clear(); + foreach (var kvp in logicalToPhysical) + { + _logicalToPhysical[kvp.Key] = kvp.Value; + } + + // Restore physical resources + _physicalResourceCount = physicalData.Count; + for (int i = 0; i < physicalData.Count; i++) + { + PhysicalResource physical; + if (i < _physicalResources.Count) + { + physical = _physicalResources[i]; + physical.Reset(); + } + else + { + physical = _pool.Rent(); + physical.Reset(); + _physicalResources.Add(physical); + } + + var data = physicalData[i]; + physical.index = data.index; + physical.width = data.width; + physical.height = data.height; + physical.format = data.format; + physical.firstUsePass = data.firstUsePass; + physical.lastUsePass = data.lastUsePass; + physical.sizeInBytes = physical.CalculateSize(); + } + } + + /// + /// Stores current aliasing state to cache. + /// + public void StoreToCache(Dictionary outLogicalToPhysical, List outPhysicalData) + { + outLogicalToPhysical.Clear(); + foreach (var kvp in _logicalToPhysical) + { + outLogicalToPhysical[kvp.Key] = kvp.Value; + } + + outPhysicalData.Clear(); + for (int i = 0; i < _physicalResourceCount; i++) + { + var physical = _physicalResources[i]; + outPhysicalData.Add(new PhysicalResourceData + { + index = physical.index, + width = physical.width, + height = physical.height, + format = physical.format, + firstUsePass = physical.firstUsePass, + lastUsePass = physical.lastUsePass + }); + } + } +} diff --git a/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs index 98d3a3b..fef6775 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs @@ -110,17 +110,17 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra ObjectDisposedException.ThrowIf(_disposed, this); } - private Identifier UseResource(Identifier resource, AccessFlags accessFlags) + private Identifier UseResource(Identifier resource, AccessFlags accessFlags, RenderGraphResourceType type) { if (accessFlags.HasFlag(AccessFlags.Read)) { - _pass.resourceReads.Add(resource); + _pass.resourceReads[(int)type].Add(resource); _resources.AddConsumer(resource, _pass.index); } if (accessFlags.HasFlag(AccessFlags.Write)) { - _pass.resourceWrites.Add(resource); + _pass.resourceWrites[(int)type].Add(resource); _resources.SetProducer(resource, _pass.index); } @@ -142,7 +142,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra ThrowIfDisposed(); var handle = _resources.CreateTexture(descriptor); - _pass.resourceCreates.Add(handle.AsResource()); + _pass.resourceCreates[(int)RenderGraphResourceType.Texture].Add(handle.AsResource()); _resources.SetProducer(handle.AsResource(), _pass.index); return handle; } @@ -150,8 +150,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra public Identifier UseTexture(Identifier texture, AccessFlags flags) { ThrowIfDisposed(); - - return UseResource(texture.AsResource(), flags).AsTexture(); + return UseResource(texture.AsResource(), flags, RenderGraphResourceType.Texture).AsTexture(); } public Identifier UseRandomAccessTexture(Identifier texture) @@ -159,7 +158,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra ThrowIfDisposed(); var resource = texture.AsResource(); - UseResource(resource, AccessFlags.ReadWrite); + UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Texture); _pass.randomAccess.Add(resource); return texture; } @@ -169,7 +168,7 @@ internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGra ThrowIfDisposed(); var resource = buffer.AsResource(); - UseResource(resource, AccessFlags.ReadWrite); + UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Buffer); _pass.randomAccess.Add(resource); return buffer; } diff --git a/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs.bak b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs.bak new file mode 100644 index 0000000..98d3a3b --- /dev/null +++ b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs.bak @@ -0,0 +1,240 @@ +using Ghost.Core; +using System.Diagnostics; + +namespace Ghost.RenderGraph.Concept; + +[Flags] +public enum AccessFlags +{ + None = 0, + Read = 1 << 0, + Write = 1 << 1, + ReadWrite = Read | Write, +} + +public interface IRenderGraphBuilder : IDisposable +{ + /// + /// Enables or disables pass culling for the current context. + /// + /// A value indicating whether pass culling is allowed. + void AllowPassCulling(bool value); + + /// + /// Creates a new texture resource based on the specified descriptor. + /// + /// A structure that defines the properties and configuration of the texture to create. + /// An identifier for the newly created texture resource. + Identifier CreateTexture(in TextureDescriptor descriptor); + + /// + /// Registers the specified texture for use in the current render graph pass with the given access mode. + /// + /// The identifier of the texture to be used in the render graph pass. + /// The access mode specifying how the texture will be read or written during the pass. + /// An identifier for the texture. + Identifier UseTexture(Identifier texture, AccessFlags accessMode); +} + +public interface IRasterRenderGraphBuilder : IRenderGraphBuilder +{ + /// + /// Binds a texture for random access operations within the current rendering pass. + /// + /// The identifier of the texture to be used for random access. + /// An identifier for the texture. + Identifier UseRandomAccessTexture(Identifier texture); + /// + /// Specifies that the given buffer will be used for random access operations with the specified access mode within the current context. + /// + /// An identifier for the buffer to be used for random access. Must reference a valid buffer resource. + /// An identifier for the buffer. + Identifier UseRandomAccessBuffer(Identifier buffer); + + /// + /// Sets the color attachment at the specified index to the given texture. + /// + /// The identifier of the texture to use as the color attachment. + /// The zero-based index of the color attachment to set. + void SetColorAttachment(Identifier texture, int index); + + /// + /// Sets the depth attachment for the current render pass using the specified texture. + /// + /// The identifier of the texture to use as the depth attachment. Cannot be null. + void SetDepthAttachment(Identifier texture); + + /// + /// Sets the function used to render a pass with the specified pass data and render context. + /// + /// The type of data associated with the render pass. + /// The delegate that defines the rendering logic for the pass. + void SetRenderFunc(Action renderFunc) + where TPassData : class, new(); +} + +public interface IComputeRenderGraphBuilder : IRenderGraphBuilder +{ + /// + /// Enables or disables asynchronous compute operations. + /// + /// true to enable asynchronous compute; otherwise, false. + void EnableAsyncCompute(bool value); + + /// + /// Sets the render function to be invoked during the compute rendering process. + /// + /// The type of the data object passed to the render function. + /// The delegate that defines the rendering logic to execute. + void SetRenderFunc(Action renderFunc) + where TPassData : class, new(); +} + +internal class RenderGraphBuilder : IRasterRenderGraphBuilder, IComputeRenderGraphBuilder +{ + private RenderGraph _graph = null!; + private RenderGraphPassBase _pass = null!; + private RenderGraphResourceRegistry _resources = null!; + private bool _disposed; + + internal void Init(RenderGraph graph, RenderGraphPassBase pass, RenderGraphResourceRegistry resources) + { + _graph = graph; + _pass = pass; + _resources = resources; + _disposed = false; + } + + private void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(_disposed, this); + } + + private Identifier UseResource(Identifier resource, AccessFlags accessFlags) + { + if (accessFlags.HasFlag(AccessFlags.Read)) + { + _pass.resourceReads.Add(resource); + _resources.AddConsumer(resource, _pass.index); + } + + if (accessFlags.HasFlag(AccessFlags.Write)) + { + _pass.resourceWrites.Add(resource); + _resources.SetProducer(resource, _pass.index); + } + + return resource; + } + + public void AllowPassCulling(bool value) + { + _pass.allowCulling = value; + } + + public void EnableAsyncCompute(bool value) + { + _pass.asyncCompute = value; + } + + public Identifier CreateTexture(in TextureDescriptor descriptor) + { + ThrowIfDisposed(); + + var handle = _resources.CreateTexture(descriptor); + _pass.resourceCreates.Add(handle.AsResource()); + _resources.SetProducer(handle.AsResource(), _pass.index); + return handle; + } + + public Identifier UseTexture(Identifier texture, AccessFlags flags) + { + ThrowIfDisposed(); + + return UseResource(texture.AsResource(), flags).AsTexture(); + } + + public Identifier UseRandomAccessTexture(Identifier texture) + { + ThrowIfDisposed(); + + var resource = texture.AsResource(); + UseResource(resource, AccessFlags.ReadWrite); + _pass.randomAccess.Add(resource); + return texture; + } + + public Identifier UseRandomAccessBuffer(Identifier buffer) + { + ThrowIfDisposed(); + + var resource = buffer.AsResource(); + UseResource(resource, AccessFlags.ReadWrite); + _pass.randomAccess.Add(resource); + return buffer; + } + + public void SetColorAttachment(Identifier texture, int index) + { + ThrowIfDisposed(); + + Debug.Assert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range."); + + var id = UseTexture(texture, AccessFlags.Write); + if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid) + { + _pass.maxColorIndex = Math.Max(_pass.maxColorIndex, index); + _pass.colorAccess[index] = new TextureAccess(id, AccessFlags.Write); + } + else + { + throw new InvalidOperationException($"Color attachment at index {index} is already set to a different texture."); + } + } + + public void SetDepthAttachment(Identifier texture) + { + ThrowIfDisposed(); + + var id = UseTexture(texture, AccessFlags.Write); + if (_pass.depthAccess.id == id || _pass.depthAccess.id.IsInvalid) + { + _pass.depthAccess = new TextureAccess(id, AccessFlags.Write); + } + else + { + throw new InvalidOperationException("Depth attachment is already set to a different texture."); + } + } + + public void SetRenderFunc(Action renderFunc) + where TPassData : class, new() + { + ((RasterRenderGraphPass)_pass).renderFunc = renderFunc; + } + + public void SetRenderFunc(Action renderFunc) + where TPassData : class, new() + { + ((ComputeRenderGraphPass)_pass).renderFunc = renderFunc; + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + if (!_pass.HasRenderFunc()) + { + throw new InvalidOperationException("RenderGraphBuilder must be disposed after setting up the render function."); + } + + _graph = null!; + _pass = null!; + _resources = null!; + + _disposed = true; + } +} diff --git a/Ghost.RenderGraph.Concept/RenderGraphPass.cs b/Ghost.RenderGraph.Concept/RenderGraphPass.cs index 0ae38cc..4ebf224 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphPass.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphPass.cs @@ -1,5 +1,4 @@ using Ghost.Core; -using System.IO; namespace Ghost.RenderGraph.Concept; @@ -31,14 +30,24 @@ internal abstract class RenderGraphPassBase public List> randomAccess = new(8); // Resource dependencies - public readonly List> resourceReads = new(8); - public readonly List> resourceWrites = new(4); - public readonly List> resourceCreates = new(4); - + public readonly List>[] resourceReads = new List>[(int)RenderGraphResourceType.Count]; + public readonly List>[] resourceWrites = new List>[(int)RenderGraphResourceType.Count]; + public readonly List>[] resourceCreates = new List>[(int)RenderGraphResourceType.Count]; + // Execution state public bool culled; public bool hasSideEffects; + public RenderGraphPassBase() + { + for (int i = 0; i < (int)RenderGraphResourceType.Count; i++) + { + resourceReads[i] = new List>(8); + resourceWrites[i] = new List>(4); + resourceCreates[i] = new List>(4); + } + } + public abstract void Execute(RenderContext context); public abstract void Clear(); public abstract bool HasRenderFunc(); @@ -50,16 +59,20 @@ internal abstract class RenderGraphPassBase type = RenderPassType.Raster; allowCulling = true; asyncCompute = false; - + depthAccess = default; colorAccess.AsSpan().Clear(); maxColorIndex = -1; randomAccess.Clear(); - resourceReads.Clear(); - resourceWrites.Clear(); - resourceCreates.Clear(); + for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) + { + resourceReads[i].Clear(); + resourceWrites[i].Clear(); + resourceCreates[i].Clear(); + } + culled = false; hasSideEffects = false; } diff --git a/Ghost.RenderGraph.Concept/RenderGraphPass.cs.bak b/Ghost.RenderGraph.Concept/RenderGraphPass.cs.bak new file mode 100644 index 0000000..0ae38cc --- /dev/null +++ b/Ghost.RenderGraph.Concept/RenderGraphPass.cs.bak @@ -0,0 +1,129 @@ +using Ghost.Core; +using System.IO; + +namespace Ghost.RenderGraph.Concept; + +/// +/// Represents different types of render passes. +/// +public enum RenderPassType : byte +{ + Raster, + Compute +} + +/// +/// Base class for render passes. +/// Uses pooling to avoid allocations after the first frame. +/// +internal abstract class RenderGraphPassBase +{ + public string name = string.Empty; + public int index; + public RenderPassType type; + public bool allowCulling = true; + public bool asyncCompute; + + public TextureAccess depthAccess; + public TextureAccess[] colorAccess = new TextureAccess[8]; + public int maxColorIndex = -1; + + public List> randomAccess = new(8); + + // Resource dependencies + public readonly List> resourceReads = new(8); + public readonly List> resourceWrites = new(4); + public readonly List> resourceCreates = new(4); + + // Execution state + public bool culled; + public bool hasSideEffects; + + public abstract void Execute(RenderContext context); + public abstract void Clear(); + public abstract bool HasRenderFunc(); + + public virtual void Reset(RenderGraphObjectPool pool) + { + name = string.Empty; + index = -1; + type = RenderPassType.Raster; + allowCulling = true; + asyncCompute = false; + + depthAccess = default; + colorAccess.AsSpan().Clear(); + maxColorIndex = -1; + + randomAccess.Clear(); + + resourceReads.Clear(); + resourceWrites.Clear(); + resourceCreates.Clear(); + culled = false; + hasSideEffects = false; + } +} + +internal abstract class RenderGraphPassT : RenderGraphPassBase + where TPassData : class, new() +{ + public TPassData passData = null!; + public Action? renderFunc; + + public void Init(int index, TPassData passData, string name, RenderPassType type) + { + this.index = index; + this.passData = passData; + this.name = name; + this.type = type; + } + + public sealed override bool HasRenderFunc() + { + return renderFunc != null; + } + + public override void Clear() + { + passData = null!; + renderFunc = null; + } + + public override void Reset(RenderGraphObjectPool pool) + { + base.Reset(pool); + pool.Return(passData); + Clear(); + } +} + +internal sealed class RasterRenderGraphPass : RenderGraphPassT + where TPassData : class, new() +{ + public override void Execute(RenderContext context) + { + renderFunc!(passData, context.RasterContext); + } + + public override void Reset(RenderGraphObjectPool pool) + { + base.Reset(pool); + pool.Return(this); + } +} + +internal sealed class ComputeRenderGraphPass : RenderGraphPassT + where TPassData : class, new() +{ + public override void Execute(RenderContext context) + { + renderFunc!(passData, context.ComputeContext); + } + + public override void Reset(RenderGraphObjectPool pool) + { + base.Reset(pool); + pool.Return(this); + } +} \ No newline at end of file diff --git a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs b/Ghost.RenderGraph.Concept/RenderGraphTypes.cs index cea75cf..924a9d0 100644 --- a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs +++ b/Ghost.RenderGraph.Concept/RenderGraphTypes.cs @@ -3,11 +3,11 @@ using System.Runtime.CompilerServices; namespace Ghost.RenderGraph.Concept; -internal enum RenderGraphResourceType +internal enum RenderGraphResourceType : int { Texture, Buffer, - AccelerationStructure, + // AccelerationStructure, Count } @@ -84,14 +84,79 @@ public readonly struct TextureDescriptor : IEquatable this.name = name; } - public readonly bool Equals(TextureDescriptor other) => - width == other.width && + public readonly bool Equals(TextureDescriptor other) + { + return 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 static bool operator ==(TextureDescriptor left, TextureDescriptor right) + { + return left.Equals(right); + } + + public static bool operator !=(TextureDescriptor left, TextureDescriptor right) + { + return !(left == right); + } +} + +[Flags] +public enum BufferUsage +{ + None = 0, + Vertex = 1 << 0, + Index = 1 << 1, + IndirectArgument = 1 << 7, + Constant = 1 << 2, + ShaderResource = 1 << 3, + UnorderedAccess = 1 << 4, + Structured = 1 << 5, + Raw = 1 << 6, + Upload = 1 << 8, + Readback = 1 << 9, +} + +public readonly struct BufferDescriptor : IEquatable +{ + public readonly uint sizeInBytes; + public readonly uint stride; + public readonly BufferUsage usage; + public readonly string name; + + public BufferDescriptor(uint sizeInBytes, uint stride, BufferUsage usage, string name) + { + this.sizeInBytes = sizeInBytes; + this.stride = stride; + this.usage = usage; + this.name = name; + } + + public readonly bool Equals(BufferDescriptor other) + { + return sizeInBytes == other.sizeInBytes && + stride == other.stride && + usage == other.usage && + name == other.name; + } + + public override readonly bool Equals(object? obj) => obj is BufferDescriptor other && Equals(other); + public override readonly int GetHashCode() => HashCode.Combine(sizeInBytes, name); + + public static bool operator ==(BufferDescriptor left, BufferDescriptor right) + { + return left.Equals(right); + } + + public static bool operator !=(BufferDescriptor left, BufferDescriptor right) + { + return !(left == right); + } } ///