diff --git a/Ghost.Graphics/Core/Material.cs b/Ghost.Graphics/Core/Material.cs index 2a8cfaa..daa23bc 100644 --- a/Ghost.Graphics/Core/Material.cs +++ b/Ghost.Graphics/Core/Material.cs @@ -235,21 +235,39 @@ public struct Material : IResourceReleasable return _keywordMask.IsKeywordEnabled(localIndex); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly void UploadData(ICommandBuffer cmd, bool pixelOnlyResource = true) + public readonly void UploadData(ICommandBuffer cmd, IResourceDatabase resourceDatabase) { if (!_isDirty) { return; } - cmd.TransitionBarrier(_cBufferCache.GpuResource.AsResource(), ResourceState.CopyDest); - cmd.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan()); + var cbufferResource = _cBufferCache.GpuResource.AsResource(); + var r = resourceDatabase.GetResourceBarrierData(cbufferResource); + if (r.IsFailure) + { + return; + } - var state = pixelOnlyResource - ? ResourceState.PixelShaderResource - : ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource; - cmd.TransitionBarrier(_cBufferCache.GpuResource.AsResource(), state); + var barrierData = r.Value; + var desc = BarrierDesc.Buffer( + cbufferResource, + barrierData.Sync, + BarrierSync.Copy, + barrierData.Access, + BarrierAccess.CopyDest); + + cmd.ResourceBarrier(desc); + cmd.UploadBuffer(_cBufferCache.GpuResource, _cBufferCache.CpuData.AsSpan()); + + desc = BarrierDesc.Buffer( + cbufferResource, + BarrierSync.Copy, + BarrierSync.AllShading, + BarrierAccess.CopyDest, + BarrierAccess.ShaderResource); + + cmd.ResourceBarrier(desc); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Ghost.Graphics/Core/RenderOutput.cs b/Ghost.Graphics/Core/RenderOutput.cs index 295a844..a4f1a42 100644 --- a/Ghost.Graphics/Core/RenderOutput.cs +++ b/Ghost.Graphics/Core/RenderOutput.cs @@ -33,12 +33,22 @@ internal class SwapChainRenderOutput : IRenderOutput public void BeginRender(ICommandBuffer cmd) { - cmd.TransitionBarrier(GetRenderTarget().AsResource(), ResourceState.Present, ResourceState.RenderTarget); + var barrierDesc = BarrierDesc.Texture(_swapChain.GetCurrentBackBuffer().AsResource(), + BarrierSync.None, BarrierSync.RenderTarget, + BarrierAccess.NoAccess, BarrierAccess.RenderTarget, + BarrierLayout.Present, BarrierLayout.RenderTarget); + + cmd.ResourceBarrier(barrierDesc); } public void EndRender(ICommandBuffer cmd) { - cmd.TransitionBarrier(GetRenderTarget().AsResource(), ResourceState.RenderTarget, ResourceState.Present); + var barrierDesc = BarrierDesc.Texture(_swapChain.GetCurrentBackBuffer().AsResource(), + BarrierSync.RenderTarget, BarrierSync.None, + BarrierAccess.RenderTarget, BarrierAccess.NoAccess, + BarrierLayout.RenderTarget, BarrierLayout.Present); + + cmd.ResourceBarrier(barrierDesc); } public void Present() diff --git a/Ghost.Graphics/Core/RenderingContext.cs b/Ghost.Graphics/Core/RenderingContext.cs index de674f0..f2c7e3e 100644 --- a/Ghost.Graphics/Core/RenderingContext.cs +++ b/Ghost.Graphics/Core/RenderingContext.cs @@ -5,7 +5,6 @@ using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.Mathematics; -using System.Runtime.CompilerServices; namespace Ghost.Graphics.Core; @@ -47,6 +46,37 @@ public readonly unsafe ref struct RenderingContext queue.WaitIdle(); } + private void TransitionBarrier(Handle resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync) + { + var r = ResourceDatabase.GetResourceBarrierData(resource); + if (r.IsFailure) + { + return; + } + + var data = r.Value; + if (data.Layout == newLayout && data.Access == newAccess && data.Sync == newSync) + { + return; + } + + // For buffers, layout is usually Undefined/Common and doesn't change, but Access/Sync do. + // For textures, layout changes matter. + var desc = isTexture ? + BarrierDesc.Texture( + resource, + data.Sync, newSync, + data.Access, newAccess, + data.Layout, newLayout) + : BarrierDesc.Buffer( + resource, + data.Sync, newSync, + data.Access, newAccess); + + _directCmd.ResourceBarrier(new ReadOnlySpan(in desc)); + ResourceDatabase.SetResourceBarrierData(resource, new ResourceBarrierData(newLayout, newAccess, newSync)); + } + public Handle CreateMesh(UnsafeList vertices, UnsafeList indices, bool staticMesh) { var mesh = ResourceAllocator.CreateMesh(vertices, indices); @@ -60,8 +90,8 @@ public readonly unsafe ref struct RenderingContext var vertexHandle = meshData.VertexBuffer.AsResource(); var indexHandle = meshData.IndexBuffer.AsResource(); - _directCmd.TransitionBarrier(vertexHandle, ResourceState.CopyDest); - _directCmd.TransitionBarrier(indexHandle, ResourceState.CopyDest); + TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); + TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); _directCmd.UploadBuffer(meshData.VertexBuffer, meshData.Vertices.AsSpan()); _directCmd.UploadBuffer(meshData.IndexBuffer, meshData.Indices.AsSpan()); @@ -69,8 +99,8 @@ public readonly unsafe ref struct RenderingContext if (staticMesh) { meshData.ReleaseCpuResources(); - _directCmd.TransitionBarrier(vertexHandle, ResourceState.NonPixelShaderResource); - _directCmd.TransitionBarrier(indexHandle, ResourceState.NonPixelShaderResource); + TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading); + TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput); } return mesh; @@ -103,15 +133,17 @@ public readonly unsafe ref struct RenderingContext } ref readonly var meshRef = ref r.Value; + var vertexHandle = meshRef.VertexBuffer.AsResource(); + var indexHandle = meshRef.IndexBuffer.AsResource(); - _directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.CopyDest); - _directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.CopyDest); + TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); + TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); _directCmd.UploadBuffer(meshRef.VertexBuffer, meshRef.Vertices.AsSpan()); _directCmd.UploadBuffer(meshRef.IndexBuffer, meshRef.Indices.AsSpan()); - _directCmd.TransitionBarrier(meshRef.VertexBuffer.AsResource(), ResourceState.NonPixelShaderResource); - _directCmd.TransitionBarrier(meshRef.IndexBuffer.AsResource(), ResourceState.NonPixelShaderResource); + TransitionBarrier(vertexHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.VertexShading); + TransitionBarrier(indexHandle, false, BarrierLayout.Undefined, BarrierAccess.IndexBuffer, BarrierSync.IndexInput); if (markMeshStatic) { @@ -139,9 +171,9 @@ public readonly unsafe ref struct RenderingContext var bufferHandle = meshData.ObjectDataBuffer.AsResource(); - _directCmd.TransitionBarrier(bufferHandle, ResourceState.CopyDest); - _directCmd.UploadBuffer(meshData.ObjectDataBuffer, [data]); - _directCmd.TransitionBarrier(bufferHandle, ResourceState.NonPixelShaderResource | ResourceState.PixelShaderResource); + TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy); + _directCmd.UploadBuffer(meshData.ObjectDataBuffer, data); + TransitionBarrier(bufferHandle, false, BarrierLayout.Undefined, BarrierAccess.ShaderResource, BarrierSync.PixelShading | BarrierSync.NonPixelShading); } public Handle CreateTexture(ref readonly TextureDesc desc, ReadOnlySpan data, string name) @@ -166,7 +198,7 @@ public readonly unsafe ref struct RenderingContext desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _); - _directCmd.TransitionBarrier(texture.AsResource(), ResourceState.CopyDest); + TransitionBarrier(texture.AsResource(), true, BarrierLayout.CopyDest, BarrierAccess.CopyDest, BarrierSync.Copy); fixed (T* pData = data) { diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs index d712fc9..4b50e9a 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs @@ -193,7 +193,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList.Get()->RSSetScissorRects(1, &d3d12Rect); } - public void ResourceBarrier(ReadOnlySpan barrierDescs) + public void ResourceBarrier(params ReadOnlySpan barrierDescs) { ThrowIfDisposed(); ThrowIfNotRecording(); @@ -210,170 +210,120 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer return; } - var count = 0u; - var pBarriers = stackalloc D3D12_RESOURCE_BARRIER[barrierDescs.Length]; + var globalCount = 0; + var bufferCount = 0; + var textureCount = 0; + + for (var i = 0; i < barrierDescs.Length; i++) + { + switch (barrierDescs[i].Type) + { + case BarrierType.Global: globalCount++; break; + case BarrierType.Buffer: bufferCount++; break; + case BarrierType.Texture: textureCount++; break; + } + } + + var pGlobalBarriers = stackalloc D3D12_GLOBAL_BARRIER[globalCount]; + var pBufferBarriers = stackalloc D3D12_BUFFER_BARRIER[bufferCount]; + var pTextureBarriers = stackalloc D3D12_TEXTURE_BARRIER[textureCount]; + + var globalIndex = 0; + var bufferIndex = 0; + var textureIndex = 0; for (var i = 0; i < barrierDescs.Length; i++) { var desc = barrierDescs[i]; - D3D12_RESOURCE_BARRIER barrier = default; - - switch (desc.type) + switch (desc.Type) { - case BarrierType.Transition: - if (desc.transition.stateBefore == desc.transition.stateAfter) + case BarrierType.Global: + pGlobalBarriers[globalIndex++] = new D3D12_GLOBAL_BARRIER { - continue; - } - - var recordResult = _resourceDatabase.GetResourceRecord(desc.transition.resource); - if (recordResult.Error != ErrorStatus.None) - { - RecordError(nameof(TransitionBarrier), recordResult.Error); - continue; - } - - ref var record = ref recordResult.Value; - var stateBefore = desc.transition.stateBefore == ResourceState.Auto ? record.state : desc.transition.stateBefore; - - barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr, - stateBefore.ToD3D12States(), desc.transition.stateAfter.ToD3D12States()); - - record.state = desc.transition.stateAfter; + SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore, + SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter, + AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore, + AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter + }; break; - - case BarrierType.Aliasing: - var recordBeforeResult = _resourceDatabase.GetResourceRecord(desc.aliasing.resourceBefore); - if (recordBeforeResult.Error != ErrorStatus.None) + case BarrierType.Buffer: + { + var resource = _resourceDatabase.GetResource(desc.Resource); + pBufferBarriers[bufferIndex++] = new D3D12_BUFFER_BARRIER { - RecordError(nameof(TransitionBarrier), recordBeforeResult.Error); - continue; - } - - var recordAfterResult = _resourceDatabase.GetResourceRecord(desc.aliasing.resourceAfter); - if (recordAfterResult.Error != ErrorStatus.None) + SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore, + SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter, + AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore, + AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter, + pResource = resource, + Offset = 0, + Size = ulong.MaxValue + }; + } + break; + case BarrierType.Texture: + { + var resource = _resourceDatabase.GetResource(desc.Resource); + pTextureBarriers[textureIndex++] = new D3D12_TEXTURE_BARRIER { - RecordError(nameof(TransitionBarrier), recordAfterResult.Error); - continue; - } - - barrier = D3D12_RESOURCE_BARRIER.InitAliasing( - recordBeforeResult.Value.ResourcePtr, - recordAfterResult.Value.ResourcePtr); - break; - case BarrierType.UAV: - var recordUavResult = _resourceDatabase.GetResourceRecord(desc.uav.resource); - if (recordUavResult.Error != ErrorStatus.None) - { - RecordError(nameof(TransitionBarrier), recordUavResult.Error); - continue; - } - - barrier = D3D12_RESOURCE_BARRIER.InitUAV(recordUavResult.Value.ResourcePtr); - break; + SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore, + SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter, + AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore, + AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter, + LayoutBefore = (D3D12_BARRIER_LAYOUT)desc.LayoutBefore, + LayoutAfter = (D3D12_BARRIER_LAYOUT)desc.LayoutAfter, + pResource = resource, + Subresources = new D3D12_BARRIER_SUBRESOURCE_RANGE + { + IndexOrFirstMipLevel = desc.Subresources.IndexOrFirstMipLevel, + NumMipLevels = desc.Subresources.NumMipLevels, + FirstArraySlice = desc.Subresources.FirstArraySlice, + NumArraySlices = desc.Subresources.NumArraySlices + }, + Flags = desc.Discard ? D3D12_TEXTURE_BARRIER_FLAGS.D3D12_TEXTURE_BARRIER_FLAG_DISCARD : D3D12_TEXTURE_BARRIER_FLAGS.D3D12_TEXTURE_BARRIER_FLAG_NONE + }; + } + break; } - - pBarriers[count] = barrier; - count++; } - - _commandList.Get()->ResourceBarrier(count, pBarriers); - } - public void TransitionBarrier(Handle resource, ResourceState stateBefore, ResourceState stateAfter) - { - ThrowIfDisposed(); - ThrowIfNotRecording(); -#if !DEBUG - if (_lastError.Status != ErrorStatus.None) + var groups = stackalloc D3D12_BARRIER_GROUP[3]; + var groupCount = 0u; + + if (globalCount > 0) { - return; + groups[groupCount] = new D3D12_BARRIER_GROUP + { + Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_GLOBAL, + NumBarriers = (uint)globalCount, + }; + groups[groupCount].Anonymous.pGlobalBarriers = pGlobalBarriers; + groupCount++; } -#endif - IncrementCommandCount(); - if (stateBefore == stateAfter) + if (bufferCount > 0) { - return; + groups[groupCount] = new D3D12_BARRIER_GROUP + { + Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_BUFFER, + NumBarriers = (uint)bufferCount, + }; + groups[groupCount].Anonymous.pBufferBarriers = pBufferBarriers; + groupCount++; } - var recordResult = _resourceDatabase.GetResourceRecord(resource); - if (recordResult.Error != ErrorStatus.None) + if (textureCount > 0) { - RecordError(nameof(TransitionBarrier), recordResult.Error); - return; + groups[groupCount] = new D3D12_BARRIER_GROUP + { + Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_TEXTURE, + NumBarriers = (uint)textureCount, + }; + groups[groupCount].Anonymous.pTextureBarriers = pTextureBarriers; + groupCount++; } - ref var record = ref recordResult.Value; - var barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr, - stateBefore.ToD3D12States(), stateAfter.ToD3D12States()); - - _commandList.Get()->ResourceBarrier(1, &barrier); - record.state = stateAfter; - } - - public void TransitionBarrier(Handle resource, ResourceState stateAfter) - { - ThrowIfDisposed(); - ThrowIfNotRecording(); -#if !DEBUG - if (_lastError.Status != ErrorStatus.None) - { - return; - } -#endif - IncrementCommandCount(); - - var recordResult = _resourceDatabase.GetResourceRecord(resource); - if (recordResult.Error != ErrorStatus.None) - { - RecordError(nameof(TransitionBarrier), recordResult.Error); - return; - } - - ref var record = ref recordResult.Value; - if (record.state == stateAfter) - { - return; - } - - var barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr, - record.state.ToD3D12States(), stateAfter.ToD3D12States()); - - _commandList.Get()->ResourceBarrier(1, &barrier); - record.state = stateAfter; - } - - public void AliasBarrier(Handle resourceBefore, Handle resourceAfter) - { - ThrowIfDisposed(); - ThrowIfNotRecording(); -#if !DEBUG - if (_lastError.Status != ErrorStatus.None) - { - return; - } -#endif - IncrementCommandCount(); - - var recordBeforeResult = _resourceDatabase.GetResourceRecord(resourceBefore); - if (recordBeforeResult.Error != ErrorStatus.None) - { - RecordError(nameof(AliasBarrier), recordBeforeResult.Error); - return; - } - - var recordAfterResult = _resourceDatabase.GetResourceRecord(resourceAfter); - if (recordAfterResult.Error != ErrorStatus.None) - { - RecordError(nameof(AliasBarrier), recordAfterResult.Error); - return; - } - - var barrier = D3D12_RESOURCE_BARRIER.InitAliasing( - recordBeforeResult.Value.ResourcePtr, - recordAfterResult.Value.ResourcePtr); - _commandList.Get()->ResourceBarrier(1, &barrier); + _commandList.Get()->Barrier(groupCount, groups); } public void SetRenderTargets(ReadOnlySpan> renderTargets, Handle depthTarget) @@ -440,7 +390,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer } #endif IncrementCommandCount(); - + var recordResult = _resourceDatabase.GetResourceRecord(renderTarget.AsResource()); if (recordResult.Error != ErrorStatus.None) { @@ -914,7 +864,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer } - public void UploadBuffer(Handle buffer, ReadOnlySpan data) + public void UploadBuffer(Handle buffer, params ReadOnlySpan data) where T : unmanaged { ThrowIfDisposed(); @@ -944,7 +894,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList.Get()->CopyBufferRegion(pResource, 0, uploadResource, offset, sizeInBytes); } - public void UploadTexture(Handle texture, ReadOnlySpan subresources) + public void UploadTexture(Handle texture, params ReadOnlySpan subresources) { ThrowIfDisposed(); ThrowIfNotRecording(); diff --git a/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs b/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs index 626390a..91a15af 100644 --- a/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs +++ b/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs @@ -179,24 +179,11 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary return Result.Success(cbufferInfo); } - private static D3D12_COMPARISON_FUNC ToD3DCompare(ZTest z) => z switch - { - ZTest.Disabled => D3D12_COMPARISON_FUNC_NEVER, - ZTest.Less => D3D12_COMPARISON_FUNC_LESS, - ZTest.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL, - ZTest.Equal => D3D12_COMPARISON_FUNC_EQUAL, - ZTest.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL, - ZTest.Greater => D3D12_COMPARISON_FUNC_GREATER, - ZTest.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL, - ZTest.Always => D3D12_COMPARISON_FUNC_ALWAYS, - _ => D3D12_COMPARISON_FUNC_LESS_EQUAL - }; - private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ZTest ztest, ZWrite zwrite) { var depthEnabled = ztest != ZTest.Disabled; var writeEnabled = zwrite == ZWrite.On; - var cmp = ToD3DCompare(ztest); + var cmp = ztest.ToD3DCompare(); return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp); } diff --git a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs index d1cc9f3..bafbc9f 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs @@ -10,7 +10,6 @@ using Misaki.HighPerformance.LowLevel.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Xml.Linq; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -480,84 +479,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator _ => throw new ArgumentException($"Unsupported memory type: {memoryType}") }; } - - private static D3D12_RESOURCE_STATES DetermineInitialTextureState(TextureUsage usage) - { - if (usage.HasFlag(TextureUsage.RenderTarget)) - { - return D3D12_RESOURCE_STATE_RENDER_TARGET; - } - - if (usage.HasFlag(TextureUsage.DepthStencil)) - { - return D3D12_RESOURCE_STATE_DEPTH_WRITE; - } - - if (usage.HasFlag(TextureUsage.UnorderedAccess)) - { - return D3D12_RESOURCE_STATE_UNORDERED_ACCESS; - } - - return D3D12_RESOURCE_STATE_COMMON; - } - - private static D3D12_RESOURCE_STATES DetermineInitialBufferState(BufferUsage usage, ResourceMemoryType memoryType) - { - if (memoryType == ResourceMemoryType.Upload) - { - return D3D12_RESOURCE_STATE_GENERIC_READ; - } - - if (memoryType == ResourceMemoryType.Readback) - { - return D3D12_RESOURCE_STATE_COPY_DEST; - } - - var state = D3D12_RESOURCE_STATE_COMMON; -#if true - // D3D12 does not support state other than COMMON for buffers at creation. - return state; -#else - if (usage.HasFlag(BufferUsage.Vertex) || usage.HasFlag(BufferUsage.Constant)) - { - // Vertex and Constant buffers can share this state - state |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; - } - - if (usage.HasFlag(BufferUsage.Index)) - { - state |= D3D12_RESOURCE_STATE_INDEX_BUFFER; - } - - if (usage.HasFlag(BufferUsage.UnorderedAccess)) - { - state |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS; - } - - if (usage.HasFlag(BufferUsage.IndirectArgument)) - { - state |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT; - } - - // If only one state is set (e.g., just IndexBuffer), return it directly - // This is a common optimization to avoid an initial barrier - if (math.ispow2((int)state)) - { - return state; - } - - // If multiple roles (e.g., Vertex and Index), start in COMMON - // or return the combined state if they are compatible - // For simplicity, we'll just check for the common "Vertex/Constant" combo - if (state == D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER) - { - return D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; - } - - // If it's a mix, start in common and let the user barrier - return D3D12_RESOURCE_STATE_COMMON; -#endif - } } internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator @@ -624,9 +545,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Handle TrackAllocation(D3D12MA_Allocation* allocation, D3D12_RESOURCE_STATES state, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp) + private Handle TrackAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData barrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp) { - var handle = _resourceDatabase.AddAllocation(allocation, _fenceSynchronizer.CPUFenceValue, state.ToResourceState(), resourceDescriptor, desc, name); + var handle = _resourceDatabase.AddAllocation(allocation, _fenceSynchronizer.CPUFenceValue, barrierData, resourceDescriptor, desc, name); if (isTemp) { @@ -653,8 +574,8 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator } else { - var nuliid = IID.IID_NULL; - hr = _d3d12MA.Get()->CreateResource(pAllocationDesc, pResourceDesc, initialState, null, (D3D12MA_Allocation**)ppv, &nuliid, null); + var iid_null = IID.IID_NULL; + hr = _d3d12MA.Get()->CreateResource(pAllocationDesc, pResourceDesc, initialState, null, (D3D12MA_Allocation**)ppv, &iid_null, null); } return hr; @@ -696,7 +617,14 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator return Handle.Invalid; } - return TrackAllocation(alloc, D3D12_RESOURCE_STATE_COMMON, ResourceViewGroup.Invalid, default, name, false); + var barrierData = new ResourceBarrierData + { + Access = BarrierAccess.NoAccess, + Layout = BarrierLayout.Common, + Sync = BarrierSync.None + }; + + return TrackAllocation(alloc, barrierData, ResourceViewGroup.Invalid, default, name, false); } public Handle CreateTexture(ref readonly TextureDesc desc, string name, CreationOptions options = default) @@ -756,7 +684,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator Flags = D3D12MA_ALLOCATION_FLAG_NONE }; - var initialState = DetermineInitialTextureState(desc.Usage); var isSubAllocation = options.AllocationType == ResourceAllocationType.Suballocation; D3D12MA_Allocation* pAllocation = default; ID3D12Resource* pResource = default; @@ -764,18 +691,18 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator if (isSubAllocation) { - hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, __uuidof(pResource), (void**)&pResource); + hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, __uuidof(pResource), (void**)&pResource); } else { - hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation); + hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, null, (void**)&pAllocation); pResource = pAllocation->GetResource(); } if (hr.FAILED) { #if DEBUG - Marshal.ThrowExceptionForHR(hr); + ThrowIfFailed(hr); #endif return Handle.Invalid; } @@ -822,14 +749,21 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav); } + var barrierData = new ResourceBarrierData + { + Access = BarrierAccess.NoAccess, + Layout = BarrierLayout.Common, + Sync = BarrierSync.None + }; + Handle resource; if (isSubAllocation) { - resource = _resourceDatabase.ImportExternalResource(pResource, initialState.ToResourceState(), resourceDescriptor, name); + resource = _resourceDatabase.ImportExternalResource(pResource, barrierData, resourceDescriptor, name); } else { - resource = TrackAllocation(pAllocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp); + resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp); } return resource.AsTexture(); @@ -864,24 +798,34 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator Flags = D3D12MA_ALLOCATION_FLAG_NONE, }; - var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType); var isSubAllocation = options.Heap.IsValid; D3D12MA_Allocation* pAllocation = default; ID3D12Resource* pResource = default; HRESULT hr; + var initialState = desc.MemoryType switch + { + ResourceMemoryType.Default => D3D12_RESOURCE_STATE_COMMON, + ResourceMemoryType.Upload => D3D12_RESOURCE_STATE_GENERIC_READ, + ResourceMemoryType.Readback => D3D12_RESOURCE_STATE_COPY_DEST, + _ => D3D12_RESOURCE_STATE_COMMON + }; + if (isSubAllocation) { hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, __uuidof(pResource), (void**)&pResource); } else { - hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation); + hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation); pResource = pAllocation->GetResource(); } if (hr.FAILED) { +#if DEBUG + ThrowIfFailed(hr); +#endif return Handle.Invalid; } @@ -922,14 +866,21 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav); } + var barrierData = new ResourceBarrierData + { + Access = BarrierAccess.NoAccess, + Layout = BarrierLayout.Undefined, + Sync = BarrierSync.None + }; + Handle resource; if (isSubAllocation) { - resource = _resourceDatabase.ImportExternalResource(pResource, initialState.ToResourceState(), resourceDescriptor, name); + resource = _resourceDatabase.ImportExternalResource(pResource, barrierData, resourceDescriptor, name); } else { - resource = TrackAllocation(pAllocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp); + resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp); } return resource.AsGraphicsBuffer(); diff --git a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs index 9471de7..c985e81 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs @@ -37,32 +37,37 @@ internal class D3D12ResourceDatabase : IResourceDatabase public ResourceDesc desc; public ResourceViewGroup viewGroup; public ResourceUnion resource; - public ResourceState state; + + //public BarrierLayout layout; + //public BarrierAccess access; + //public BarrierSync sync; + public ResourceBarrierData barrierData; + public uint cpuFenceValue; public readonly bool isExternal; public readonly bool Allocated => isExternal ? resource.resource.Get() != null : resource.allocation.Get() != null; public readonly SharedPtr ResourcePtr => isExternal ? resource.resource.Get() : resource.allocation.Get()->GetResource(); - public ResourceRecord(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState state, ResourceViewGroup resourceDescriptor, ResourceDesc desc) + public ResourceRecord(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceBarrierData barrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc) { this.resource = new ResourceUnion(allocation); this.isExternal = false; this.viewGroup = resourceDescriptor; this.cpuFenceValue = cpuFenceValue; - this.state = state; + this.barrierData = barrierData; this.desc = desc; } - public ResourceRecord(ID3D12Resource* resource, ResourceState state, ResourceViewGroup viewGroup) + public ResourceRecord(ID3D12Resource* resource, ResourceBarrierData barrierData, ResourceViewGroup viewGroup) { this.resource = new ResourceUnion(resource); this.isExternal = true; this.viewGroup = viewGroup; this.cpuFenceValue = ~0u; - this.state = state; + this.barrierData = barrierData; this.desc = resource->GetDesc().ToResourceDesc(); } @@ -130,7 +135,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase resource = default!; } - public unsafe Handle ImportExternalResource(ID3D12Resource* pResource, ResourceState initialState, ResourceViewGroup viewGroup, string? name = null) + public unsafe Handle ImportExternalResource(ID3D12Resource* pResource, ResourceBarrierData initialBarrierData, ResourceViewGroup viewGroup, string? name = null) { ObjectDisposedException.ThrowIf(_disposed, this); if (pResource == null) @@ -141,7 +146,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase return Handle.Invalid; } - var id = _resources.Add(new ResourceRecord(pResource, initialState, viewGroup), out var generation); + var id = _resources.Add(new ResourceRecord(pResource, initialBarrierData, viewGroup), out var generation); var handle = new Handle(id, generation); #if DEBUG || GHOST_EDITOR @@ -155,7 +160,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase return handle; } - public unsafe Handle AddAllocation(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null) + public unsafe Handle AddAllocation(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null) { ObjectDisposedException.ThrowIf(_disposed, this); if (allocation == null) @@ -166,7 +171,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase return Handle.Invalid; } - var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation); + var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialBarrierData, resourceDescriptor, desc), out var generation); var handle = new Handle(id, generation); #if DEBUG || GHOST_EDITOR @@ -215,7 +220,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase return r.Value.ResourcePtr; } - public Result GetResourceState(Handle handle) + public Result GetResourceBarrierData(Handle handle) { var r = GetResourceRecord(handle); if (r.IsFailure) @@ -223,10 +228,10 @@ internal class D3D12ResourceDatabase : IResourceDatabase return r.Error; } - return r.Value.state; + return r.Value.barrierData; } - public ErrorStatus SetResourceState(Handle handle, ResourceState state) + public ErrorStatus SetResourceBarrierData(Handle handle, ResourceBarrierData data) { var r = GetResourceRecord(handle); if (r.IsFailure) @@ -234,7 +239,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase return r.Error; } - r.Value.state = state; + r.Value.barrierData = data; return ErrorStatus.None; } diff --git a/Ghost.Graphics/D3D12/D3D12SwapChain.cs b/Ghost.Graphics/D3D12/D3D12SwapChain.cs index cbae898..4a43980 100644 --- a/Ghost.Graphics/D3D12/D3D12SwapChain.cs +++ b/Ghost.Graphics/D3D12/D3D12SwapChain.cs @@ -156,7 +156,14 @@ internal unsafe class D3D12SwapChain : ISwapChain rtv = rtv }; - var handle = _resourceDatabase.ImportExternalResource(pBackBuffer, ResourceState.Present, view); + var barrierData = new ResourceBarrierData + { + Access = BarrierAccess.NoAccess, + Layout = BarrierLayout.Present, + Sync = BarrierSync.None, + }; + + var handle = _resourceDatabase.ImportExternalResource(pBackBuffer, barrierData, view); _backBuffers[i] = handle.AsTexture(); } } diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs index f26d60f..faf539b 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs @@ -1,3 +1,4 @@ +using Ghost.Core.Graphics; using Ghost.Graphics.RHI; using TerraFX.Interop.DirectX; @@ -79,62 +80,62 @@ internal unsafe static class D3D12Utility { var d3dStates = D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COMMON; - if ((state & ResourceState.VertexAndConstantBuffer) == ResourceState.VertexAndConstantBuffer) + if (state.HasFlag(ResourceState.VertexAndConstantBuffer)) { d3dStates |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; } - if ((state & ResourceState.IndexBuffer) == ResourceState.IndexBuffer) + if (state.HasFlag(ResourceState.IndexBuffer)) { d3dStates |= D3D12_RESOURCE_STATE_INDEX_BUFFER; } - if ((state & ResourceState.RenderTarget) == ResourceState.RenderTarget) + if (state.HasFlag(ResourceState.RenderTarget)) { d3dStates |= D3D12_RESOURCE_STATE_RENDER_TARGET; } - if ((state & ResourceState.UnorderedAccess) == ResourceState.UnorderedAccess) + if (state.HasFlag(ResourceState.UnorderedAccess)) { d3dStates |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS; } - if ((state & ResourceState.DepthWrite) == ResourceState.DepthWrite) + if (state.HasFlag(ResourceState.DepthWrite)) { d3dStates |= D3D12_RESOURCE_STATE_DEPTH_WRITE; } - if ((state & ResourceState.DepthRead) == ResourceState.DepthRead) + if (state.HasFlag(ResourceState.DepthRead)) { d3dStates |= D3D12_RESOURCE_STATE_DEPTH_READ; } - if ((state & ResourceState.PixelShaderResource) == ResourceState.PixelShaderResource) + if (state.HasFlag(ResourceState.PixelShaderResource)) { d3dStates |= D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; } - if ((state & ResourceState.CopyDest) == ResourceState.CopyDest) + if (state.HasFlag(ResourceState.CopyDest)) { d3dStates |= D3D12_RESOURCE_STATE_COPY_DEST; } - if ((state & ResourceState.CopySource) == ResourceState.CopySource) + if (state.HasFlag(ResourceState.CopySource)) { d3dStates |= D3D12_RESOURCE_STATE_COPY_SOURCE; } - if ((state & ResourceState.GenericRead) == ResourceState.GenericRead) + if (state.HasFlag(ResourceState.GenericRead)) { d3dStates |= D3D12_RESOURCE_STATE_GENERIC_READ; } - if ((state & ResourceState.IndirectArgument) == ResourceState.IndirectArgument) + if (state.HasFlag(ResourceState.IndirectArgument)) { d3dStates |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT; } - if ((state & ResourceState.NonPixelShaderResource) == ResourceState.NonPixelShaderResource) + if (state.HasFlag(ResourceState.NonPixelShaderResource)) { d3dStates |= D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; } @@ -250,6 +251,22 @@ internal unsafe static class D3D12Utility }; } + public static D3D12_COMPARISON_FUNC ToD3DCompare(this ZTest z) + { + return z switch + { + ZTest.Disabled => D3D12_COMPARISON_FUNC_NEVER, + ZTest.Less => D3D12_COMPARISON_FUNC_LESS, + ZTest.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL, + ZTest.Equal => D3D12_COMPARISON_FUNC_EQUAL, + ZTest.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL, + ZTest.Greater => D3D12_COMPARISON_FUNC_GREATER, + ZTest.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL, + ZTest.Always => D3D12_COMPARISON_FUNC_ALWAYS, + _ => D3D12_COMPARISON_FUNC_LESS_EQUAL + }; + } + public static D3D12_COMMAND_LIST_TYPE ToCommandListType(CommandBufferType type) { return type switch diff --git a/Ghost.Graphics/RHI/Common.cs b/Ghost.Graphics/RHI/Common.cs index 825de90..abb53de 100644 --- a/Ghost.Graphics/RHI/Common.cs +++ b/Ghost.Graphics/RHI/Common.cs @@ -294,35 +294,122 @@ public struct PassDepthStencilDesc } -[StructLayout(LayoutKind.Explicit)] +public struct BarrierSubresourceRange +{ + public uint IndexOrFirstMipLevel + { + get; set; + } + + public uint NumMipLevels + { + get; set; + } + + public uint FirstArraySlice + { + get; set; + } + + public uint NumArraySlices + { + get; set; + } +} + public struct BarrierDesc { - public struct barrierdesc_transition + public BarrierType Type { - public Handle resource; - public ResourceState stateBefore; - public ResourceState stateAfter; + get; set; } - public struct barrierdesc_aliasing + public BarrierSync SyncBefore { - public Handle resourceBefore; - public Handle resourceAfter; + get; set; } - public struct barrierdesc_uav + public BarrierSync SyncAfter { - public Handle resource; + get; set; } - [FieldOffset(0)] - public BarrierType type; - [FieldOffset(4)] - public barrierdesc_transition transition; - [FieldOffset(4)] - public barrierdesc_aliasing aliasing; - [FieldOffset(4)] - public barrierdesc_uav uav; + public BarrierAccess AccessBefore + { + get; set; + } + + public BarrierAccess AccessAfter + { + get; set; + } + + public BarrierLayout LayoutBefore + { + get; set; + } + + public BarrierLayout LayoutAfter + { + get; set; + } + + public Handle Resource + { + get; set; + } + + public BarrierSubresourceRange Subresources + { + get; set; + } + + public bool Discard + { + get; set; + } + + public static BarrierDesc Global(BarrierSync syncBefore, BarrierSync syncAfter, BarrierAccess accessBefore, BarrierAccess accessAfter) + { + return new BarrierDesc + { + Type = BarrierType.Global, + SyncBefore = syncBefore, + SyncAfter = syncAfter, + AccessBefore = accessBefore, + AccessAfter = accessAfter + }; + } + + public static BarrierDesc Buffer(Handle resource, BarrierSync syncBefore, BarrierSync syncAfter, BarrierAccess accessBefore, BarrierAccess accessAfter) + { + return new BarrierDesc + { + Type = BarrierType.Buffer, + Resource = resource, + SyncBefore = syncBefore, + SyncAfter = syncAfter, + AccessBefore = accessBefore, + AccessAfter = accessAfter + }; + } + + public static BarrierDesc Texture(Handle resource, BarrierSync syncBefore, BarrierSync syncAfter, BarrierAccess accessBefore, BarrierAccess accessAfter, BarrierLayout layoutBefore, BarrierLayout layoutAfter, BarrierSubresourceRange subresources = default, bool discard = false) + { + return new BarrierDesc + { + Type = BarrierType.Texture, + Resource = resource, + SyncBefore = syncBefore, + SyncAfter = syncAfter, + AccessBefore = accessBefore, + AccessAfter = accessAfter, + LayoutBefore = layoutBefore, + LayoutAfter = layoutAfter, + Subresources = subresources, + Discard = discard + }; + } } public struct ResourceDesc @@ -574,9 +661,6 @@ public struct TextureDesc } } -/// -/// Describes the parameters used to configure a texture sampler for graphics rendering operations. -/// public record struct SamplerDesc { public TextureFilterMode FilterMode @@ -625,14 +709,8 @@ public record struct SamplerDesc } } -/// -/// Buffer description -/// public struct BufferDesc { - /// - /// Size of the buffer in bytes - /// public ulong Size { get; set; @@ -643,17 +721,11 @@ public struct BufferDesc get; set; } - /// - /// Buffer usage flags - /// public BufferUsage Usage { get; set; } - /// - /// Memory space for the buffer - /// public ResourceMemoryType MemoryType { get; set; @@ -678,22 +750,13 @@ public struct CommandError } } -/// -/// Swap chain description -/// public struct SwapChainDesc { - /// - /// Width of the swap chain - /// public uint Width { get; set; } - /// - /// Height of the swap chain - /// public uint Height { get; set; @@ -709,50 +772,29 @@ public struct SwapChainDesc get; set; } - /// - /// Back buffer Format - /// public TextureFormat Format { get; set; } - - /// - /// Target for presentation (window handle or composition Target) - /// public SwapChainTarget Target { get; set; } } -/// -/// Swap chain Target (window handle or composition surface) -/// public struct SwapChainTarget { - /// - /// Target space - /// public SwapChainTargetType Type { get; set; } - - /// - /// Window handle for HWND targets - /// public nint WindowHandle { get; set; } - - /// - /// Composition surface for UWP/WinUI targets - /// public object? CompositionSurface { get; set; @@ -787,11 +829,97 @@ public enum SwapChainTargetType } -public enum BarrierType : int +public enum BarrierType { - Transition, - Aliasing, - UAV + Global, + Texture, + Buffer +} + +[Flags] +public enum BarrierSync : uint +{ + None = 0x0, + All = 0x0, + Draw = 0x1, + IndexInput = 0x2, + VertexShading = 0x4, + PixelShading = 0x8, + DepthStencil = 0x10, + RenderTarget = 0x20, + ComputeShading = 0x40, + Raytracing = 0x80, + Copy = 0x100, + Resolve = 0x200, + ExecuteIndirect = 0x400, + Predication = 0x800, + AllShading = VertexShading | PixelShading | ComputeShading | Raytracing, + NonPixelShading = VertexShading | ComputeShading | Raytracing, + EmitRaytracingAccelerationStructurePostbuildInfo = 0x1000, + ClearUnorderedAccessView = 0x2000, + VideoDecode = 0x40000, + VideoProcess = 0x80000, + VideoEncode = 0x100000, + BuildRaytracingAccelerationStructure = 0x200000, + CopyRaytracingAccelerationStructure = 0x400000, + Split = 0x800000 +} + +[Flags] +public enum BarrierAccess : uint +{ + Common = 0, + VertexBuffer = 0x1, + ConstantBuffer = 0x2, + IndexBuffer = 0x4, + RenderTarget = 0x8, + UnorderedAccess = 0x10, + DepthStencilWrite = 0x20, + DepthStencilRead = 0x40, + ShaderResource = 0x80, + StreamOutput = 0x100, + IndirectArgument = 0x200, + CopyDest = 0x400, + CopySource = 0x800, + ResolveDest = 0x1000, + ResolveSource = 0x2000, + RaytracingAccelerationStructureRead = 0x4000, + RaytracingAccelerationStructureWrite = 0x8000, + ShadingRateSource = 0x10000, + VideoDecodeRead = 0x20000, + VideoDecodeWrite = 0x40000, + VideoProcessRead = 0x80000, + VideoProcessWrite = 0x100000, + VideoEncodeRead = 0x200000, + VideoEncodeWrite = 0x400000, + NoAccess = 0x80000000 +} + +public enum BarrierLayout +{ + Undefined = -1, + Common = 0, + Present = 0, + GenericRead = 1, + RenderTarget = 2, + UnorderedAccess = 3, + DepthStencilWrite = 4, + DepthStencilRead = 5, + ShaderResource = 6, + CopyDest = 7, + CopySource = 8, + ResolveDest = 9, + ResolveSource = 10, + ShadingRateSource = 11, + VideoDecodeRead = 12, + VideoDecodeWrite = 13, + VideoProcessRead = 14, + VideoProcessWrite = 15, + VideoEncodeRead = 16, + VideoEncodeWrite = 17, + DirectQueueCommon = 18, + ComputeQueueCommon = 19, + VideoQueueCommon = 20 } [Flags] @@ -948,41 +1076,15 @@ public enum ComparisonFunction Always } -/// -/// Specifies how to load attachment contents at the start of a render pass. -/// public enum AttachmentLoadOp { - /// - /// Load existing contents from memory. Use when you need to preserve previous data. - /// Load, - - /// - /// Clear the attachment to a specified value. Use when you want to start with a clean slate. - /// Clear, - - /// - /// Don't care about previous contents. Use when you'll overwrite all pixels (fullscreen pass). - /// On tile-based deferred renderers (TBDR), this can save significant memory bandwidth. - /// DontCare } -/// -/// Specifies how to store attachment contents at the end of a render pass. -/// public enum AttachmentStoreOp { - /// - /// Store the contents to memory for later use. - /// Store, - - /// - /// Discard the contents (not needed after this pass). - /// On tile-based deferred renderers (TBDR), this can save significant memory bandwidth. - /// DontCare } diff --git a/Ghost.Graphics/RHI/ICommandBuffer.cs b/Ghost.Graphics/RHI/ICommandBuffer.cs index ec08cd3..62c1ab0 100644 --- a/Ghost.Graphics/RHI/ICommandBuffer.cs +++ b/Ghost.Graphics/RHI/ICommandBuffer.cs @@ -68,7 +68,7 @@ public interface ICommandBuffer : IDisposable void ClearRenderTargetView(Handle renderTarget, Color128 clearColor); - void ClearDepthStencilView(Handle depthStencil, bool inlcudeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0); + void ClearDepthStencilView(Handle depthStencil, bool inlcludeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0); /// /// Begins a render pass with the specified render Target @@ -89,29 +89,7 @@ public interface ICommandBuffer : IDisposable /// Inserts multiple resource barriers. /// /// Resource barrier descriptions - void ResourceBarrier(ReadOnlySpan barrierDescs); - - /// - /// Inserts a resource barrier for state transitions. - /// - /// A handle to the GPU resource to transition. - /// The current state of the resource before the transition. - /// The desired state of the resource after the transition. - void TransitionBarrier(Handle resource, ResourceState stateBefore, ResourceState stateAfter); - - /// - /// Inserts a resource barrier for state transitions. The current state is tracked internally. - /// - /// A handle to the GPU resource to transition. - /// The desired state of the resource after the transition. - void TransitionBarrier(Handle resource, ResourceState stateAfter); - - /// - /// Inserts a barrier to ensure correct aliasing transitions between two GPU resources. - /// - /// A handle to the GPU resource representing the state before the aliasing transition - /// A handle to the GPU resource representing the state after the aliasing transition - void AliasBarrier(Handle resourceBefore, Handle resourceAfter); + void ResourceBarrier(params ReadOnlySpan barrierDescs); /// /// Sets the pipeline state object @@ -204,18 +182,16 @@ public interface ICommandBuffer : IDisposable /// A handle to the buffer that will receive the uploaded data. /// A read-only span containing the data to upload to the buffer. The span must contain elements of space /// . - void UploadBuffer(Handle buffer, ReadOnlySpan data) + void UploadBuffer(Handle buffer, params ReadOnlySpan data) where T : unmanaged; /// /// Uploads texture data to the specified texture resource starting at the given subresource index. /// /// The texture resource to which the subresource data will be uploaded. Must be a valid, initialized texture handle. - /// The index of the first subresource in the texture to receive data. Must be less than the total number of subresources in the texture. /// A reference to the structure containing the subresource data to upload. The data must match the Format and layout expected by the texture. - /// The number of subresources to upload, starting from . /// Must be greater than zero and not exceed the remaining subresources in the texture. - void UploadTexture(Handle texture, ReadOnlySpan subresources); + void UploadTexture(Handle texture, params ReadOnlySpan subresources); /// /// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer. diff --git a/Ghost.Graphics/RHI/IResourceDatabase.cs b/Ghost.Graphics/RHI/IResourceDatabase.cs index e46842a..1e098a8 100644 --- a/Ghost.Graphics/RHI/IResourceDatabase.cs +++ b/Ghost.Graphics/RHI/IResourceDatabase.cs @@ -11,6 +11,20 @@ public interface IResourceReleasable void ReleaseResource(IResourceDatabase database); } +public struct ResourceBarrierData +{ + public BarrierLayout Layout; + public BarrierAccess Access; + public BarrierSync Sync; + + public ResourceBarrierData(BarrierLayout layout, BarrierAccess access, BarrierSync sync) + { + Layout = layout; + Access = access; + Sync = sync; + } +} + // TODO: Consider adding methods for resource enumeration, statistics, and bulk operations. // TODO: Consider adding async resource loading and streaming support. // TODO: Mesh, Material, Shader management could be separated into their own interfaces for better modularity because they are not bound to specific graphics API. @@ -35,19 +49,19 @@ public interface IResourceDatabase : IDisposable bool HasResource(Handle handle); /// - /// Retrieves the current state of the specified resource. + /// Retrieves the current barrier data of the specified resource. /// - /// The handle that uniquely identifies the resource whose state is to be retrieved. - /// A ResourceState Value representing the current state of the resource associated with the specified handle. - Result GetResourceState(Handle handle); + /// The handle that uniquely identifies the resource. + /// A ResourceBarrierData value representing the current barrier state. + Result GetResourceBarrierData(Handle handle); /// - /// Sets the state of the specified resource handle to the given Value. + /// Sets the barrier data of the specified resource handle. /// - /// The handle that identifies the resource whose state will be updated. - /// The new state to assign to the resource represented by . + /// The handle that identifies the resource. + /// The new barrier data. /// An ErrorStatus indicating the success or failure of the operation. - ErrorStatus SetResourceState(Handle handle, ResourceState state); + ErrorStatus SetResourceBarrierData(Handle handle, ResourceBarrierData data); /// /// Retrieves the description of a GPU resource associated with the specified handle. diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraph.cs b/Ghost.Graphics/RenderGraphModule/RenderGraph.cs index 6901e55..1ffcef2 100644 --- a/Ghost.Graphics/RenderGraphModule/RenderGraph.cs +++ b/Ghost.Graphics/RenderGraphModule/RenderGraph.cs @@ -744,11 +744,23 @@ public sealed class RenderGraph : IDisposable // If we found a previous resource, insert aliasing barrier if (mostRecentLastUse >= 0) { - var barrier = ResourceBarrier.CreateAliasingBarrier( - resourceBefore, - id, - passIdx - ); + BarrierDesc desc; + if (resource.type == RenderGraphResourceType.Texture) + { + desc = BarrierDesc.Texture(resource.backingResource, + BarrierSync.All, BarrierSync.None, + BarrierAccess.NoAccess, BarrierAccess.NoAccess, + BarrierLayout.Undefined, BarrierLayout.Common, + discard: true); + } + else + { + desc = BarrierDesc.Buffer(resource.backingResource, + BarrierSync.All, BarrierSync.None, + BarrierAccess.NoAccess, BarrierAccess.NoAccess); + } + + var barrier = ResourceBarrier.Create(passIdx, desc, id); _barriers.Add(barrier); } } @@ -843,17 +855,120 @@ public sealed class RenderGraph : IDisposable if (currentState != newState) { - var barrier = ResourceBarrier.CreateTransitionBarrier( - resource, - currentState, - newState, - passIdx - ); + var res = _resources.GetResource(resource); + GetBarrierInfo(currentState, out var syncBefore, out var accessBefore, out var layoutBefore); + GetBarrierInfo(newState, out var syncAfter, out var accessAfter, out var layoutAfter); + + BarrierDesc desc; + if (res.type == RenderGraphResourceType.Texture) + { + desc = BarrierDesc.Texture(res.backingResource, + syncBefore, syncAfter, + accessBefore, accessAfter, + layoutBefore, layoutAfter); + } + else + { + desc = BarrierDesc.Buffer(res.backingResource, + syncBefore, syncAfter, + accessBefore, accessAfter); + } + + var barrier = ResourceBarrier.Create(passIdx, desc, resource); _barriers.Add(barrier); _resourceStates[resource.Value] = newState; } } + private static void GetBarrierInfo(ResourceState state, out BarrierSync sync, out BarrierAccess access, out BarrierLayout layout) + { + sync = BarrierSync.None; + access = BarrierAccess.Common; + layout = BarrierLayout.Common; + + if (state == ResourceState.Common) + { + return; + } + + if (state.HasFlag(ResourceState.RenderTarget)) + { + sync |= BarrierSync.RenderTarget; + access |= BarrierAccess.RenderTarget; + layout = BarrierLayout.RenderTarget; + } + if (state.HasFlag(ResourceState.DepthWrite)) + { + sync |= BarrierSync.DepthStencil; + access |= BarrierAccess.DepthStencilWrite; + layout = BarrierLayout.DepthStencilWrite; + } + if (state.HasFlag(ResourceState.DepthRead)) + { + sync |= BarrierSync.DepthStencil; + access |= BarrierAccess.DepthStencilRead; + layout = BarrierLayout.DepthStencilRead; + } + if (state.HasFlag(ResourceState.UnorderedAccess)) + { + sync |= BarrierSync.AllShading; + access |= BarrierAccess.UnorderedAccess; + layout = BarrierLayout.UnorderedAccess; + } + if (state.HasFlag(ResourceState.PixelShaderResource)) + { + sync |= BarrierSync.PixelShading; + access |= BarrierAccess.ShaderResource; + layout = BarrierLayout.ShaderResource; + } + if (state.HasFlag(ResourceState.NonPixelShaderResource)) + { + sync |= BarrierSync.NonPixelShading; + access |= BarrierAccess.ShaderResource; + layout = BarrierLayout.ShaderResource; + } + if (state.HasFlag(ResourceState.CopyDest)) + { + sync |= BarrierSync.Copy; + access |= BarrierAccess.CopyDest; + layout = BarrierLayout.CopyDest; + } + if (state.HasFlag(ResourceState.CopySource)) + { + sync |= BarrierSync.Copy; + access |= BarrierAccess.CopySource; + layout = BarrierLayout.CopySource; + } + if (state.HasFlag(ResourceState.VertexAndConstantBuffer)) + { + sync |= BarrierSync.VertexShading; + access |= BarrierAccess.VertexBuffer | BarrierAccess.ConstantBuffer; + layout = BarrierLayout.Common; + } + if (state.HasFlag(ResourceState.IndexBuffer)) + { + sync |= BarrierSync.IndexInput; + access |= BarrierAccess.IndexBuffer; + layout = BarrierLayout.Common; + } + if (state.HasFlag(ResourceState.IndirectArgument)) + { + sync |= BarrierSync.ExecuteIndirect; + access |= BarrierAccess.IndirectArgument; + layout = BarrierLayout.GenericRead; + } + if (state.HasFlag(ResourceState.GenericRead)) + { + layout = BarrierLayout.GenericRead; + } + if (state.HasFlag(ResourceState.Present)) + { + sync = BarrierSync.All; + access = BarrierAccess.Common; + layout = BarrierLayout.Present; + } + } + /// /// Determines the appropriate resource state for a buffer read operation based on usage hints. /// @@ -1327,55 +1442,30 @@ public sealed class RenderGraph : IDisposable /// private unsafe void ExecuteBarriersForPass(ICommandBuffer cmd, int passIndex, ref int barrierIndex) { - var pBarrierDescs = stackalloc BarrierDesc[16]; // batch by 16 - var count = 0; - var hasRemain = false; + int start = barrierIndex; + int count = 0; - Start: while (barrierIndex < _barriers.Count && _barriers[barrierIndex].PassIndex == passIndex) { - var barrier = _barriers[barrierIndex]; - - var desc = new BarrierDesc(); - desc.type = barrier.Type; - switch (desc.type) - { - case BarrierType.Transition: - desc.transition.resource = _resources.GetResource(barrier.Resource).backingResource; - desc.transition.stateBefore = barrier.StateBefore; - desc.transition.stateAfter = barrier.StateAfter; - break; - case BarrierType.Aliasing: - desc.aliasing.resourceBefore = _resources.GetResource(barrier.ResourceBefore).backingResource; - desc.aliasing.resourceAfter = _resources.GetResource(barrier.ResourceAfter).backingResource; - break; - case BarrierType.UAV: - desc.uav.resource = _resources.GetResource(barrier.Resource).backingResource; - break; - } - - pBarrierDescs[count] = desc; count++; - barrierIndex++; - - if (count == 16) - { - hasRemain = _barriers.Count > barrierIndex && _barriers[barrierIndex].PassIndex == passIndex; - break; - } } if (count > 0) { - cmd.ResourceBarrier(new ReadOnlySpan(pBarrierDescs, count)); - } - - if (hasRemain) - { - count = 0; - hasRemain = false; - goto Start; + const int BatchSize = 64; + var descs = stackalloc BarrierDesc[BatchSize]; + int processed = 0; + while (processed < count) + { + int batch = Math.Min(count - processed, BatchSize); + for (int i = 0; i < batch; i++) + { + descs[i] = _barriers[start + processed + i].Desc; + } + cmd.ResourceBarrier(new ReadOnlySpan(descs, batch)); + processed += batch; + } } } diff --git a/Ghost.Graphics/RenderGraphModule/RenderGraphBarriers.cs b/Ghost.Graphics/RenderGraphModule/RenderGraphBarriers.cs index fe6ae16..608a2fb 100644 --- a/Ghost.Graphics/RenderGraphModule/RenderGraphBarriers.cs +++ b/Ghost.Graphics/RenderGraphModule/RenderGraphBarriers.cs @@ -6,95 +6,22 @@ namespace Ghost.Graphics.RenderGraphModule; /// /// Represents a resource barrier that needs to be inserted. -/// For D3D12 aliasing barriers: ResourceBefore is the old resource, ResourceAfter is the new resource. /// internal struct ResourceBarrier { - [StructLayout(LayoutKind.Explicit)] - private struct barrier_union - { - internal struct barrier_union_transition - { - public Identifier resource; - public ResourceState stateBefore; - public ResourceState stateAfter; - } + public int PassIndex; + public BarrierDesc Desc; + public Identifier LogicalResource; - internal struct barrier_union_aliasing - { - public Identifier resourceBefore; - public Identifier resourceAfter; - } + public readonly Identifier Resource => LogicalResource; - // TODO: union can not have non-blittable types - - [FieldOffset(0)] - public barrier_union_transition transition; - [FieldOffset(0)] - public barrier_union_aliasing aliasing; - } - - private barrier_union _union; - - public BarrierType Type - { - get; init; - } - - public int PassIndex - { - get; init; - } - - // For Transition and UAV barriers - public readonly Identifier Resource => _union.transition.resource; - public readonly ResourceState StateBefore => _union.transition.stateBefore; - public readonly ResourceState StateAfter => _union.transition.stateAfter; - - // For Aliasing barriers (D3D12_RESOURCE_BARRIER::Aliasing) - public readonly Identifier ResourceBefore => _union.aliasing.resourceBefore; - public readonly Identifier ResourceAfter => _union.aliasing.resourceAfter; - - // Constructor for Aliasing barriers - public static ResourceBarrier CreateAliasingBarrier( - Identifier resourceBefore, - Identifier resourceAfter, - int passIndex) + public static ResourceBarrier Create(int passIndex, BarrierDesc desc, Identifier logicalResource) { return new ResourceBarrier { - Type = BarrierType.Aliasing, PassIndex = passIndex, - _union = new barrier_union - { - aliasing = new barrier_union.barrier_union_aliasing - { - resourceBefore = resourceBefore, - resourceAfter = resourceAfter - } - } - }; - } - - public static ResourceBarrier CreateTransitionBarrier( - Identifier resource, - ResourceState before, - ResourceState after, - int passIndex) - { - return new ResourceBarrier - { - Type = BarrierType.Transition, - PassIndex = passIndex, - _union = new barrier_union - { - transition = new barrier_union.barrier_union_transition - { - resource = resource, - stateBefore = before, - stateAfter = after - } - } + Desc = desc, + LogicalResource = logicalResource }; } } diff --git a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs index eb866cc..60f4b03 100644 --- a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs +++ b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs @@ -245,7 +245,7 @@ internal class MeshRenderPass : IRenderPass }; matRef.SetPropertyCache(in matProps).ThrowIfFailed(); - matRef.UploadData(ctx.DirectCommandBuffer); + matRef.UploadData(ctx.DirectCommandBuffer, ctx.ResourceDatabase); } public void Build(RenderGraph graph, Identifier backbuffer) @@ -298,7 +298,7 @@ internal class MeshRenderPass : IRenderPass }; matRef.SetPropertyCache(in blitProps).ThrowIfFailed(); - matRef.UploadData(ctx.CommandBuffer); + matRef.UploadData(ctx.CommandBuffer, ctx.ResourceDatabase); ctx.CommandBuffer.SetRenderTargets([ctx.GetActualTexture(data.destination)], Handle.Invalid); diff --git a/Ghost.Graphics/Shaders/Blit.gsdef b/Ghost.Graphics/Shaders/Blit.gsdef index 24c0142..9d5b95a 100644 --- a/Ghost.Graphics/Shaders/Blit.gsdef +++ b/Ghost.Graphics/Shaders/Blit.gsdef @@ -10,7 +10,7 @@ shader "Hidden/Blit" { pipeline { - ztest = always; + ztest = disabled; zwrite = off; cull = off; blend = opaque; diff --git a/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs b/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs deleted file mode 100644 index e5d621d..0000000 --- a/Ghost.RenderGraph.Concept/Benchmark/RenderGraphBenchmark.cs +++ /dev/null @@ -1,218 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Ghost.Core; - -namespace Ghost.RenderGraph.Concept.Benchmark; - -[MemoryDiagnoser] -public class RenderGraphBenchmark -{ - private RenderGraph _renderGraph = null!; - - [GlobalSetup] - public void Setup() - { - _renderGraph = new RenderGraph(); - - // Warm up - ExecuteGraph(_renderGraph); - } - - [Benchmark] - public void Execute() - { - _renderGraph.Reset(); - ExecuteGraph(_renderGraph); - } - - public static void ExecuteGraph(RenderGraph renderGraph, int idx = 0) - { - // Import external resources - var backbuffer = renderGraph.ImportTexture( - new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "Backbuffer")); - - // ===== GBuffer Pass ===== - GBufferData gbufferData; - using (var builder = renderGraph.AddRasterRenderPass("GBuffer Pass", out gbufferData)) - { - // Create GBuffer textures - gbufferData.Albedo = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "GBuffer.Albedo")); - gbufferData.Normal = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "GBuffer.Normal")); - gbufferData.Depth = builder.CreateTexture(new TextureDescriptor(1920, 1080, TextureFormat.Depth32F, "GBuffer.Depth")); - - // Store in pass data and mark as written - builder.SetColorAttachment(gbufferData.Albedo, 0); - builder.SetColorAttachment(gbufferData.Normal, 1); - builder.SetDepthAttachment(gbufferData.Depth); - - builder.SetRenderFunc(static (data, cmd) => - { - // New api will handle render target setup and clearing automatically - - //cmd.SetRenderTarget(data.Albedo.Name); - //cmd.SetRenderTarget(data.Normal.Name); - //cmd.SetDepthStencil(data.Depth.Name); - //cmd.ClearRenderTarget(data.Albedo.Name, 0, 0, 0, 1); - //cmd.ClearRenderTarget(data.Normal.Name, 0.5f, 0.5f, 1.0f, 1); - //cmd.ClearDepth(data.Depth.Name, 1.0f); - cmd.Draw(36000); - }); - } - - // Store GBuffer data in blackboard for other passes - renderGraph.Blackboard.Add(gbufferData); - - // ===== Lighting Pass ===== - Identifier lightingOutput; - using (var builder = renderGraph.AddRasterRenderPass("Lighting Pass", out var lightingData)) - { - // Read GBuffer from blackboard - var gbuffer = renderGraph.Blackboard.Get(); - - lightingData.GBufferAlbedo = builder.UseTexture(gbuffer.Albedo, AccessFlags.Read); - lightingData.GBufferNormal = builder.UseTexture(gbuffer.Normal, AccessFlags.Read); - lightingData.GBufferDepth = builder.UseTexture(gbuffer.Depth, AccessFlags.Read); - - // Create output texture - lightingOutput = builder.CreateTexture( - new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "LightingResult")); - builder.SetColorAttachment(lightingOutput, 0); - - lightingData.OutputLighting = lightingOutput; - - builder.SetRenderFunc(static (data, cmd) => - { - cmd.BindShaderResource(data.GBufferAlbedo.AsResource(), 0); - cmd.BindShaderResource(data.GBufferNormal.AsResource(), 1); - cmd.BindShaderResource(data.GBufferDepth.AsResource(), 2); - cmd.Draw(3); - }); - } - - // ===== SSAO Pass (Async Compute) ===== - Identifier ssaoOutput; - Identifier ssaoBufferOutput; - using (var builder = renderGraph.AddComputeRenderPass("SSAO Pass (Async)", out var ssaoData)) - { - var gbuffer = renderGraph.Blackboard.Get(); - - ssaoData.GBufferDepth = builder.UseTexture(gbuffer.Depth, AccessFlags.Read); - ssaoData.GBufferNormal = builder.UseTexture(gbuffer.Normal, AccessFlags.Read); - - // SSAO Output - ssaoOutput = builder.CreateTexture( - new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "SSAO")); - ssaoData.OutputSSAO = builder.UseTexture(ssaoOutput, AccessFlags.Write); - ssaoBufferOutput = builder.CreateBuffer( - new BufferDescriptor(1920 * 1080 * 4, sizeof(byte), BufferUsage.UnorderedAccess, "SSAO.Buffer")); - ssaoData.OutputSSAOBuffer = builder.UseBuffer(ssaoBufferOutput, AccessFlags.WriteAll); - - builder.EnableAsyncCompute(true); - - // Use SetComputeFunc with asyncCompute: true - builder.SetRenderFunc(static (data, cmd) => - { - cmd.BindShaderResource(data.GBufferDepth.AsResource(), 0); - cmd.BindShaderResource(data.GBufferNormal.AsResource(), 1); - cmd.BindUnorderedAccess(data.OutputSSAO.AsResource(), 0); - cmd.Dispatch(1920 / 8, 1080 / 8, 1); - }); - } - - // ===== Bloom Downsample Pass (will alias with albedo) ===== - Identifier bloomOutput; - using (var builder = renderGraph.AddRasterRenderPass("Bloom Downsample", out var bloomData)) - { - bloomData.Input = builder.UseTexture(lightingOutput, AccessFlags.Read); - - // Create a texture that will alias with SSAO (same size, same format) - bloomOutput = builder.CreateTexture( - new TextureDescriptor(1920, 1080, TextureFormat.RGBA8, "BloomDownsample")); - builder.SetColorAttachment(bloomOutput, 0); - builder.UseBuffer(ssaoBufferOutput, AccessFlags.Read); - - bloomData.Output = bloomOutput; - - builder.SetRenderFunc(static (data, cmd) => - { - cmd.BindShaderResource(data.Input.AsResource(), 0); - cmd.Draw(3); - }); - } - - // ===== Temporal AA Pass ===== - Identifier taaOutput; - using (var builder = renderGraph.AddRasterRenderPass("Temporal AA", out var taaData)) - { - taaData.InputLighting = builder.UseTexture(lightingOutput, AccessFlags.Read); - - taaOutput = builder.CreateTexture( - new TextureDescriptor(1920, 1080, TextureFormat.RGBA16F, "TAA.Result")); - builder.SetColorAttachment(taaOutput, 0); - - taaData.OutputTAA = taaOutput; - - builder.SetRenderFunc(static (data, cmd) => - { - cmd.BindShaderResource(data.InputLighting.AsResource(), 0); - cmd.Draw(3); - }); - } - - // ===== Post Processing Pass ===== - using (var builder = renderGraph.AddRasterRenderPass("Post Processing 1", out var postData)) - { - postData.InputLighting = lightingOutput; - - builder.SetColorAttachment(backbuffer, 0); - builder.SetRenderFunc(static (data, cmd) => - { - cmd.BindShaderResource(data.InputLighting.AsResource(), 0); - cmd.Draw(3); - }); - } - - using (var builder = renderGraph.AddRasterRenderPass("Post Processing 2", out var postData)) - { - postData.InputTAA = builder.UseTexture(taaOutput, AccessFlags.Read); - postData.InputSSAO = builder.UseTexture(ssaoOutput, AccessFlags.Read); - postData.InputBloom = builder.UseTexture(bloomOutput, AccessFlags.Read); - - builder.SetColorAttachment(backbuffer, 0); - builder.SetRenderFunc(static (data, cmd) => - { - cmd.BindShaderResource(data.InputTAA.AsResource(), 0); - cmd.BindShaderResource(data.InputSSAO.AsResource(), 1); - cmd.BindShaderResource(data.InputBloom.AsResource(), 2); - cmd.Draw(3); - }); - } - - // ===== GPU Profiler Marker Pass (non-cullable, textureless) ===== - using (var builder = renderGraph.AddRasterRenderPass("GPU Profiler Begin Frame", out var profilerData)) - { - builder.AllowPassCulling(false); // Never cull this - it's for debugging/profiling - builder.SetRenderFunc(static (data, cmd) => - { - // Note: In a real implementation we would have specific profiler commands - // For now, since RasterRenderContext doesn't expose generic console write, we skip the print - // or we would add a specific Profiler method to the context - }); - } - - // ===== Unused Debug Pass (will be culled) ===== - using (var builder = renderGraph.AddRasterRenderPass("Unused Debug Pass", out var debugData)) - { - builder.SetColorAttachment( - builder.CreateTexture(new TextureDescriptor(512, 512, TextureFormat.RGBA8, "DebugTexture")), 0); - - builder.SetRenderFunc(static (data, cmd) => - { - cmd.Draw(100); - }); - } - - // Compile and execute the render graph - renderGraph.Compile(); - renderGraph.Execute(); - } -} \ No newline at end of file diff --git a/Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj b/Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj deleted file mode 100644 index 6b24b95..0000000 --- a/Ghost.RenderGraph.Concept/Ghost.RenderGraph.Concept.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - Exe - net10.0 - enable - enable - True - - - - $(DefineConstants);PROFILING - - - - $(DefineConstants);PROFILING - - - - - - - - - - - diff --git a/Ghost.RenderGraph.Concept/PassData.cs b/Ghost.RenderGraph.Concept/PassData.cs deleted file mode 100644 index bfcfa75..0000000 --- a/Ghost.RenderGraph.Concept/PassData.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Ghost.Core; - -namespace Ghost.RenderGraph.Concept; - -// ===== Pass Data Structures ===== -// These are user-defined data structures that get passed to render functions - -public sealed class GBufferData : IPassData -{ - public Identifier Albedo; - public Identifier Normal; - public Identifier Depth; -} - -public sealed class LightingPassData : IPassData -{ - public Identifier GBufferAlbedo; - public Identifier GBufferNormal; - public Identifier GBufferDepth; - public Identifier OutputLighting; -} - -public sealed class SSAOPassData : IPassData -{ - public Identifier GBufferDepth; - public Identifier GBufferNormal; - public Identifier OutputSSAO; - public Identifier OutputSSAOBuffer; -} - -public sealed class BloomDownsampleData : IPassData -{ - public Identifier Input; - public Identifier Output; -} - -public sealed class TAAPassData : IPassData -{ - public Identifier InputLighting; - public Identifier OutputTAA; -} - -public sealed class PostProcessingPassDataV1 : IPassData -{ - public Identifier InputLighting; - public Identifier OutputBackbuffer; -} - -public sealed class PostProcessingPassDataV2 : IPassData -{ - public Identifier InputTAA; - public Identifier InputSSAO; - public Identifier InputBloom; - public Identifier OutputBackbuffer; -} - -public sealed class ProfilerMarkerData : IPassData -{ -} - -public sealed class DebugPassData : IPassData -{ - public Identifier DebugTexture; -} diff --git a/Ghost.RenderGraph.Concept/Program.cs b/Ghost.RenderGraph.Concept/Program.cs deleted file mode 100644 index e5ae5ad..0000000 --- a/Ghost.RenderGraph.Concept/Program.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Ghost.RenderGraph.Concept; -using Ghost.RenderGraph.Concept.Benchmark; - -#if !DEBUG - -BenchmarkDotNet.Running.BenchmarkRunner.Run(); -return; - -var renderGraph = new RenderGraph(); -const int _ITERATION = 500000; -for (var i = 0; i < _ITERATION; i++) -{ - RenderGraphBenchmark.ExecuteGraph(renderGraph); - renderGraph.Reset(); -} - -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); - renderGraph.Reset(); -} - -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)"); -#else -var renderGraph = new RenderGraph(); - -// Run twice to demonstrate cache hit -Console.WriteLine("=== FRAME 1 (Cache Miss Expected) ==="); -RenderGraphBenchmark.ExecuteGraph(renderGraph); - -//Thread.Sleep(5000); - -//renderGraph.Reset(); -//Console.WriteLine("\n\n=== FRAME 2 (Cache Hit Expected) ==="); -//RenderGraphBenchmark.ExecuteGraph(renderGraph); -#endif diff --git a/Ghost.RenderGraph.Concept/RenderGraph.cs b/Ghost.RenderGraph.Concept/RenderGraph.cs deleted file mode 100644 index 63187a6..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraph.cs +++ /dev/null @@ -1,1161 +0,0 @@ -using Ghost.Core; -using Misaki.HighPerformance.LowLevel.Buffer; -using Misaki.HighPerformance.LowLevel.Collections; -using System.IO.Hashing; -using System.Runtime.CompilerServices; -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 List _nativePasses = new(32); - 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(); - - // Return native passes to pool - for (var i = 0; i < _nativePasses.Count; i++) - { - _objectPool.Return(_nativePasses[i]); - } - _nativePasses.Clear(); - - _compiled = false; - } - - /// - /// Imports an external texture into the render graph. - /// - public Identifier ImportTexture(TextureDescriptor descriptor) - { - return _resources.ImportTexture(descriptor); - } - - /// - /// Imports an external buffer into the render graph. - /// - public Identifier ImportBuffer(BufferDescriptor descriptor) - { - return _resources.ImportBuffer(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.textureDescriptor.format; - offset += sizeof(TextureFormat); - - *(int*)(pData + offset) = resource.textureDescriptor.width; - offset += sizeof(int); - - *(int*)(pData + offset) = resource.textureDescriptor.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); - - pData[offset] = (byte)pass.depthAccess.accessFlags; - offset += sizeof(AccessFlags); - - *(int*)(pData + offset) = pass.maxColorIndex; - offset += sizeof(int); - for (var j = 0; j <= pass.maxColorIndex; j++) - { - offset = ComputeTextureHash(pData, offset, pass.colorAccess[j].id); - - pData[offset] = (byte)pass.colorAccess[j].accessFlags; - offset += sizeof(AccessFlags); - } - - for (var j = 0; j < (int)RenderGraphResourceType.Count; j++) - { - var readList = pass.resourceReads[j]; - var writeList = pass.resourceWrites[j]; - var createList = pass.resourceCreates[j]; - - *(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) = 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); - } - - *(int*)(pData + offset) = pass.randomAccess.Count; - offset += sizeof(int); - for (var k = 0; k < pass.randomAccess.Count; k++) - { - *(int*)(pData + offset) = pass.randomAccess[k].Value; - offset += sizeof(int); - } - - // Hash buffer hints (important for correct barrier generation) - *(int*)(pData + offset) = pass.bufferHints.Count; - offset += sizeof(int); - foreach (var kvp in pass.bufferHints) - { - *(int*)(pData + offset) = kvp.Key; // Buffer resource ID - offset += sizeof(int); - *(int*)(pData + offset) = (int)kvp.Value; // BufferHint flags - offset += sizeof(int); - } - } - - *(int*)(pData + offset) = pass.GetRenderFuncHashCode(); - offset += sizeof(int); - } - - 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 resources - for (var j = 0; j < (int)RenderGraphResourceType.Count; j++) - { - var writeList = pass.resourceWrites[j]; - for (var k = 0; k < writeList.Count; k++) - { - var writeHandle = writeList[k]; - 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: Build native render passes by merging compatible passes - BuildNativeRenderPasses(); - - // Step 8: 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.placedResources); - - // 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.placedResources); - - // 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 < (int)RenderGraphResourceType.Count; 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++) - { - 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 placed resource is reused. - /// - private void InsertAliasingBarriers(RenderGraphPassBase pass, int passIdx) - { - // Check all resources written by this pass (both textures and buffers) - for (var resType = 0; resType < (int)RenderGraphResourceType.Count; resType++) - { - var writeList = pass.resourceWrites[resType]; - for (var i = 0; i < writeList.Count; i++) - { - var id = writeList[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) - { - // Get the placed resource - var placedIndex = _aliasingManager.GetPlacedResourceIndex(id.Value); - if (placedIndex >= 0) - { - var placed = _aliasingManager.GetPlacedResource(placedIndex); - - // If this placed resource has multiple aliased resources, - // we need an aliasing barrier when switching between them - if (placed != null && placed.aliasedLogicalResources.Count > 1) - { - // Find the resource that used this placed memory most recently before this pass - Identifier resourceBefore = default; - var mostRecentLastUse = -1; - - foreach (var otherLogicalIndex in placed.aliasedLogicalResources) - { - if (otherLogicalIndex != id.Value) - { - // Get resource by global index - var otherResource = _resources.GetResourceByIndex(otherLogicalIndex); - - // Check if this resource finished before our resource starts - if (otherResource.lastUsePass < pass.index && - otherResource.lastUsePass > mostRecentLastUse) - { - mostRecentLastUse = otherResource.lastUsePass; - resourceBefore = new Identifier(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 appropriate state based on resource type and hints) - for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) - { - var readList = pass.resourceReads[i]; - for (var j = 0; j < readList.Count; j++) - { - var handle = readList[j]; - var state = GetBufferReadState(handle, pass, (RenderGraphResourceType)i); - InsertTransitionIfNeeded(handle, state, 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 < (int)RenderGraphResourceType.Count; i++) - { - var writeList = pass.resourceWrites[i]; - for (var j = 0; j < writeList.Count; j++) - { - var id = writeList[j]; - 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 - } - } - - /// - /// Determines the appropriate resource state for a buffer read operation based on usage hints. - /// - private ResourceState GetBufferReadState(Identifier handle, RenderGraphPassBase pass, RenderGraphResourceType resourceType) - { - // Textures always use ShaderResource state - if (resourceType == RenderGraphResourceType.Texture) - { - return ResourceState.ShaderResource; - } - - // Check for buffer-specific usage hints - if (pass.bufferHints.TryGetValue(handle.Value, out var hint)) - { - if (hint.HasFlag(BufferHint.IndirectArgument)) - { - return ResourceState.IndirectArgument; - } - } - - // Default: ByteAddressBuffer read (SRV) - matches bindless architecture - return ResourceState.ShaderResource; - } - - /// - /// Builds native render passes by merging compatible consecutive raster passes. - /// Uses conservative merging: only merge passes with identical attachments and no barriers between them. - /// - private void BuildNativeRenderPasses() - { - // Clear previous native passes - for (var i = 0; i < _nativePasses.Count; i++) - { - _objectPool.Return(_nativePasses[i]); - } - _nativePasses.Clear(); - - NativeRenderPass? currentNativePass = null; - - for (var i = 0; i < _compiledPasses.Count; i++) - { - var pass = _compiledPasses[i]; - - // Only raster passes can be merged into native render passes - // Compute passes break the current native render pass - if (pass.type != RenderPassType.Raster) - { - // Close current native pass if open - if (currentNativePass != null) - { - _nativePasses.Add(currentNativePass); - currentNativePass = null; - } - continue; // Compute passes execute outside native render passes - } - - // Check if we can merge with current native pass - if (currentNativePass != null && CanMergePasses(currentNativePass, pass, i)) - { - // Merge into existing native pass - currentNativePass.mergedPassIndices.Add(i); - currentNativePass.lastLogicalPass = i; - } - else - { - // Start new native pass - if (currentNativePass != null) - { - _nativePasses.Add(currentNativePass); - } - - currentNativePass = CreateNativePass(pass, i); - } - } - - // Add final native pass - if (currentNativePass != null) - { - _nativePasses.Add(currentNativePass); - } - - // Infer load/store operations for all native passes - for (var i = 0; i < _nativePasses.Count; i++) - { - InferLoadStoreOps(_nativePasses[i]); - } - -#if DEBUG - Console.WriteLine("\n=== Native Render Passes ==="); - Console.WriteLine($"Logical passes: {_compiledPasses.Count}"); - Console.WriteLine($"Native passes: {_nativePasses.Count}"); - for (var i = 0; i < _nativePasses.Count; i++) - { - var nativePass = _nativePasses[i]; - Console.WriteLine($"\nNative Pass {i}:"); - Console.WriteLine($" Merged passes: [{string.Join(", ", nativePass.mergedPassIndices)}]"); - Console.WriteLine($" Color attachments: {nativePass.colorAttachmentCount}"); - for (var j = 0; j < nativePass.colorAttachmentCount; j++) - { - Console.WriteLine($" [{j}] {nativePass.colorAttachments[j].texture}"); - } - if (nativePass.hasDepthAttachment) - { - Console.WriteLine($" Depth attachment: {nativePass.depthAttachment.texture}"); - } - } - Console.WriteLine("============================\n"); -#endif - } - - /// - /// Creates a new native render pass from a logical pass. - /// - private NativeRenderPass CreateNativePass(RenderGraphPassBase pass, int passIndex) - { - var nativePass = _objectPool.Rent(); - nativePass.Reset(); - - nativePass.index = _nativePasses.Count; - nativePass.mergedPassIndices.Add(passIndex); - nativePass.firstLogicalPass = passIndex; - nativePass.lastLogicalPass = passIndex; - nativePass.allowUAVWrites = pass.randomAccess.Count > 0; - - // Copy color attachments - nativePass.colorAttachmentCount = pass.maxColorIndex + 1; - for (var i = 0; i <= pass.maxColorIndex; i++) - { - nativePass.colorAttachments[i] = new RenderTargetInfo - { - texture = pass.colorAccess[i].id, - access = pass.colorAccess[i].accessFlags - }; - } - - // Copy depth attachment - if (!pass.depthAccess.id.IsInvalid) - { - nativePass.hasDepthAttachment = true; - nativePass.depthAttachment = new DepthStencilInfo - { - texture = pass.depthAccess.id, - access = pass.depthAccess.accessFlags - }; - } - - return nativePass; - } - - /// - /// Checks if a logical pass can be merged into an existing native render pass. - /// Conservative merging: only merge if attachments match and no barriers needed. - /// - private bool CanMergePasses(NativeRenderPass nativePass, RenderGraphPassBase pass, int passIndex) - { - // Don't merge if UAVs are involved (conservative) - if (pass.randomAccess.Count > 0 || nativePass.allowUAVWrites) - { - return false; - } - - // Check if attachment configuration matches - if (!AttachmentsMatch(nativePass, pass)) - { - return false; - } - - // Check if barriers are needed between last merged pass and this pass - if (RequiresBarrierBetweenPasses(nativePass.lastLogicalPass, passIndex)) - { - return false; - } - - return true; - } - - /// - /// Checks if the attachment configuration of a pass matches the native pass. - /// - private static bool AttachmentsMatch(NativeRenderPass nativePass, RenderGraphPassBase pass) - { - // Check color attachment count - if (nativePass.colorAttachmentCount != pass.maxColorIndex + 1) - { - return false; - } - - // Check each color attachment - for (var i = 0; i < nativePass.colorAttachmentCount; i++) - { - if (nativePass.colorAttachments[i].texture != pass.colorAccess[i].id) - { - return false; - } - } - - // Check depth attachment - if (nativePass.hasDepthAttachment != !pass.depthAccess.id.IsInvalid) - { - return false; - } - - if (nativePass.hasDepthAttachment && nativePass.depthAttachment.texture != pass.depthAccess.id) - { - return false; - } - - return true; - } - - /// - /// Checks if any barriers are required between two passes that would prevent merging. - /// Only barriers affecting render targets prevent merging; SRV barriers are fine. - /// - private bool RequiresBarrierBetweenPasses(int passA, int passB) - { - var laterPass = _compiledPasses[passB]; - - // Build a set of render target resource IDs (color + depth) - var renderTargets = new HashSet>(); - for (var i = 0; i <= laterPass.maxColorIndex; i++) - { - if (!laterPass.colorAccess[i].id.IsInvalid) - { - renderTargets.Add(laterPass.colorAccess[i].id.AsResource()); - } - } - if (!laterPass.depthAccess.id.IsInvalid) - { - renderTargets.Add(laterPass.depthAccess.id.AsResource()); - } - - // Check if any barriers for passB affect render targets - for (var i = 0; i < _barriers.Count; i++) - { - if (_barriers[i].PassIndex == passB) - { - // Only prevent merge if barrier affects a render target - if (renderTargets.Contains(_barriers[i].Resource)) - { - return true; // Barrier affects render target, cannot merge - } - } - if (_barriers[i].PassIndex > passB) - { - break; // No more barriers for this pass - } - } - - return false; - } - - /// - /// Infers optimal load/store operations for all attachments in a native render pass. - /// Uses resource lifetime information to minimize memory bandwidth (critical for TBDR GPUs). - /// - private void InferLoadStoreOps(NativeRenderPass nativePass) - { - // Infer load/store ops for color attachments - for (var i = 0; i < nativePass.colorAttachmentCount; i++) - { - ref var attachment = ref nativePass.colorAttachments[i]; - var resource = _resources.GetResource(attachment.texture); - var flags = attachment.access; - - // ===== LOAD OP INFERENCE ===== - - // 1. WriteAll (Write | Discard): User guarantees full overwrite - if (flags.HasFlag(AccessFlags.Discard)) - { - attachment.loadOp = AttachmentLoadOp.DontCare; -#if DEBUG - Console.WriteLine($" Color[{i}] LoadOp=DontCare (WriteAll/Discard flag)"); -#endif - } - // 2. Read: Needs existing contents (e.g., blending) - else if (flags.HasFlag(AccessFlags.Read)) - { - attachment.loadOp = AttachmentLoadOp.Load; -#if DEBUG - Console.WriteLine($" Color[{i}] LoadOp=Load (Read flag - blending)"); -#endif - } - // 3. First use: Could use DontCare, but user didn't specify Discard flag - // Conservative: use Load to avoid bugs - else if (resource.firstUsePass == nativePass.firstLogicalPass) - { - attachment.loadOp = AttachmentLoadOp.Load; -#if DEBUG - Console.WriteLine($" Color[{i}] LoadOp=Load (first use, Write flag - conservative)"); -#endif - } - // 4. Continuation from previous pass - else - { - attachment.loadOp = AttachmentLoadOp.Load; -#if DEBUG - Console.WriteLine($" Color[{i}] LoadOp=Load (continuation from previous pass)"); -#endif - } - - // ===== STORE OP INFERENCE ===== - - // Last use: No one needs it after this native pass - if (resource.lastUsePass == nativePass.lastLogicalPass) - { - attachment.storeOp = AttachmentStoreOp.DontCare; -#if DEBUG - Console.WriteLine($" Color[{i}] StoreOp=DontCare (last use - discard)"); -#endif - } - // Intermediate: Store for future passes - else - { - attachment.storeOp = AttachmentStoreOp.Store; -#if DEBUG - Console.WriteLine($" Color[{i}] StoreOp=Store (used by later passes)"); -#endif - } - } - - // Infer load/store ops for depth attachment - if (nativePass.hasDepthAttachment) - { - ref var attachment = ref nativePass.depthAttachment; - var resource = _resources.GetResource(attachment.texture); - var flags = attachment.access; - - // ===== LOAD OP INFERENCE ===== - - if (flags.HasFlag(AccessFlags.Discard)) - { - attachment.loadOp = AttachmentLoadOp.DontCare; -#if DEBUG - Console.WriteLine($" Depth LoadOp=DontCare (WriteAll/Discard flag)"); -#endif - } - else if (flags.HasFlag(AccessFlags.Read)) - { - attachment.loadOp = AttachmentLoadOp.Load; -#if DEBUG - Console.WriteLine($" Depth LoadOp=Load (Read flag)"); -#endif - } - else if (resource.firstUsePass == nativePass.firstLogicalPass) - { - attachment.loadOp = AttachmentLoadOp.Load; -#if DEBUG - Console.WriteLine($" Depth LoadOp=Load (first use, Write flag - conservative)"); -#endif - } - else - { - attachment.loadOp = AttachmentLoadOp.Load; -#if DEBUG - Console.WriteLine($" Depth LoadOp=Load (continuation)"); -#endif - } - - // ===== STORE OP INFERENCE ===== - - // Depth is commonly discarded (depth-only passes, intermediate depth) - if (resource.lastUsePass == nativePass.lastLogicalPass) - { - attachment.storeOp = AttachmentStoreOp.DontCare; -#if DEBUG - Console.WriteLine($" Depth StoreOp=DontCare (last use)"); -#endif - } - else - { - attachment.storeOp = AttachmentStoreOp.Store; -#if DEBUG - Console.WriteLine($" Depth StoreOp=Store (used later)"); -#endif - } - } - } - - /// - /// Executes all compiled passes using native render passes where possible. - /// - public void Execute() - { - if (!_compiled) - { - Compile(); - } - - var barrierIndex = 0; - var nativePassIndex = 0; - var logicalPassIndex = 0; - - while (logicalPassIndex < _compiledPasses.Count) - { - var pass = _compiledPasses[logicalPassIndex]; - - // Check if this pass is part of a native render pass - if (pass.type == RenderPassType.Raster && nativePassIndex < _nativePasses.Count) - { - var nativePass = _nativePasses[nativePassIndex]; - - // Execute barriers for ALL merged passes before beginning the native render pass - foreach (var mergedPassIdx in nativePass.mergedPassIndices) - { - ExecuteBarriersForPass(mergedPassIdx, ref barrierIndex); - } - - // Begin native render pass - _commandBuffer.BeginRenderPass( - nativePass.index, - nativePass.colorAttachmentCount, - nativePass.hasDepthAttachment - ); - - // Execute all merged logical passes within this native render pass - for (var i = 0; i < nativePass.mergedPassIndices.Count; i++) - { - var mergedPassIdx = nativePass.mergedPassIndices[i]; - var mergedPass = _compiledPasses[mergedPassIdx]; - -#if DEBUG - Console.WriteLine($"\n--- Executing Pass {mergedPassIdx}: {mergedPass.name} (in Native Pass {nativePass.index}) ---"); -#endif - - mergedPass.Execute(_renderContext); - logicalPassIndex++; - } - - // End native render pass - _commandBuffer.EndRenderPass(); - - nativePassIndex++; - } - else - { - // Compute pass or standalone raster pass (not merged) - ExecuteBarriersForPass(logicalPassIndex, ref barrierIndex); - -#if DEBUG - Console.WriteLine($"\n--- Executing Pass {logicalPassIndex}: {pass.name} (Standalone) ---"); -#endif - - pass.Execute(_renderContext); - logicalPassIndex++; - } - } - } - - /// - /// Executes all barriers for a specific pass. - /// - private void ExecuteBarriersForPass(int passIndex, ref int barrierIndex) - { -#if DEBUG - bool hasBarriers = false; -#endif - while (barrierIndex < _barriers.Count && _barriers[barrierIndex].PassIndex == passIndex) - { -#if DEBUG - if (!hasBarriers) - { - var pass = _compiledPasses[passIndex]; - Console.WriteLine($"\n=== Barriers before Pass {passIndex}: {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 - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs b/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs deleted file mode 100644 index 058bf39..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphAliasing.cs +++ /dev/null @@ -1,686 +0,0 @@ -using Ghost.Core.Utilities; -using System.Runtime.InteropServices; - -namespace Ghost.RenderGraph.Concept; - -/// -/// Represents a memory block within a heap. -/// -internal struct MemoryBlock -{ - public ulong offset; - public ulong size; - public bool isFree; - public int firstUsePass; - public int lastUsePass; - public int logicalResourceIndex; // Which logical resource is currently using this block - - public MemoryBlock(ulong offset, ulong size) - { - this.offset = offset; - this.size = size; - isFree = true; - firstUsePass = int.MaxValue; - lastUsePass = -1; - logicalResourceIndex = -1; - } - - public void Reset() - { - isFree = true; - firstUsePass = int.MaxValue; - lastUsePass = -1; - logicalResourceIndex = -1; - } -} - -/// -/// Represents a GPU memory heap for placed resources. -/// Supports D3D12-style heap tier 2 (buffers and textures can alias). -/// -internal sealed class ResourceHeap -{ - public int index; - public ulong size; - private readonly List _blocks = new(32); - - // D3D12 heap alignment requirement (64KB for MSAA textures, 4KB for others) - private const ulong DefaultAlignment = 65536; // 64KB - - public ResourceHeap(int index, ulong initialSize = 16 * 1024 * 1024) // 16MB default - { - this.index = index; - this.size = initialSize; - - // Initially one large free block - _blocks.Add(new MemoryBlock(0, initialSize)); - } - - public void Reset() - { - _blocks.Clear(); - _blocks.Add(new MemoryBlock(0, size)); - } - - /// - /// Attempts to allocate a block of the requested size with proper alignment. - /// Uses best-fit algorithm with lifetime-aware allocation. - /// - public (bool success, ulong offset, MemoryBlock block) TryAllocate( - ulong requestedSize, - int firstUsePass, - int lastUsePass, - int logicalResourceIndex, - ulong alignment = DefaultAlignment) - { - var alignedSize = AlignUp(requestedSize, alignment); - - var bestFitIndex = -1; - ulong bestFitOffset = 0; - var smallestWaste = ulong.MaxValue; - - // Find the best fit block that doesn't overlap with lifetime - var blockSpan = CollectionsMarshal.AsSpan(_blocks); - for (var i = 0; i < blockSpan.Length; i++) - { - ref var block = ref blockSpan[i]; - - // Try to find space within this block - var alignedOffset = AlignUp(block.offset, alignment); - var endOffset = alignedOffset + alignedSize; - - if (endOffset <= block.offset + block.size) - { - // Check if this offset range conflicts with ANY existing allocations - var canUseOffset = CanPlaceAtOffset(alignedOffset, alignedSize, firstUsePass, lastUsePass); - - if (canUseOffset) - { - var waste = block.size - alignedSize; - - if (waste < smallestWaste) - { - smallestWaste = waste; - bestFitIndex = i; - bestFitOffset = alignedOffset; - } - } - } - } - - if (bestFitIndex == -1) - { - return (false, 0, default); - } - - ref var bestFit = ref CollectionsMarshal.AsSpan(_blocks)[bestFitIndex]; - - // If the block is free, we need to split it - if (bestFit.isFree) - { - var remainingSize = (bestFit.offset + bestFit.size) - (bestFitOffset + alignedSize); - - // Update the current block to be allocated - bestFit.offset = bestFitOffset; - bestFit.size = alignedSize; - bestFit.isFree = false; - bestFit.firstUsePass = firstUsePass; - bestFit.lastUsePass = lastUsePass; - bestFit.logicalResourceIndex = logicalResourceIndex; - - // Create a new free block for the remaining space if there is any - if (remainingSize > 0) - { - var newBlock = new MemoryBlock(bestFitOffset + alignedSize, remainingSize); - _blocks.Insert(bestFitIndex + 1, newBlock); - } - } - else - { - // Block is already allocated but lifetime doesn't overlap, we can alias it - // Create a new aliased block at the same location - var aliasedBlock = new MemoryBlock(bestFitOffset, alignedSize) - { - isFree = false, - firstUsePass = firstUsePass, - lastUsePass = lastUsePass, - logicalResourceIndex = logicalResourceIndex - }; - - // Insert in sorted order by offset - var insertIndex = 0; - for (var i = 0; i < _blocks.Count; i++) - { - if (_blocks[i].offset > bestFitOffset) - { - break; - } - insertIndex = i + 1; - } - _blocks.Insert(insertIndex, aliasedBlock); - // Update bestFit to point to the newly inserted block - bestFit = ref CollectionsMarshal.AsSpan(_blocks)[insertIndex]; - } - - return (true, bestFitOffset, bestFit); - } - - /// - /// Checks if a resource can be placed at the given offset without lifetime conflicts. - /// Must check ALL blocks that overlap with this offset range. - /// - private bool CanPlaceAtOffset(ulong offset, ulong size, int firstUsePass, int lastUsePass) - { - var endOffset = offset + size; - - foreach (var block in _blocks) - { - // Skip free blocks - they don't have lifetime constraints - if (block.isFree) - continue; - - // Check if this block's memory range overlaps with our target range - var blockEnd = block.offset + block.size; - var memoryOverlap = !(offset >= blockEnd || endOffset <= block.offset); - - if (memoryOverlap) - { - // Memory ranges overlap, check if lifetimes also overlap - var lifetimeOverlap = !(firstUsePass > block.lastUsePass || lastUsePass < block.firstUsePass); - - if (lifetimeOverlap) - { - // Both memory AND lifetime overlap - cannot place here! - return false; - } - } - } - - return true; - } - - /// - /// Gets the total memory that would be used if no aliasing occurred. - /// - public ulong GetTotalAllocatedWithoutAliasing() - { - ulong total = 0; - foreach (var block in _blocks) - { - if (!block.isFree) - { - total += block.size; - } - } - return total; - } - - /// - /// Gets the peak memory usage considering aliasing (max offset + size). - /// - public ulong GetPeakUsage() - { - ulong peak = 0; - foreach (var block in _blocks) - { - if (!block.isFree) - { - peak = Math.Max(peak, block.offset + block.size); - } - } - return peak; - } - - private static ulong AlignUp(ulong value, ulong alignment) - { - return (value + alignment - 1) & ~(alignment - 1); - } -} - -/// -/// Represents a placed resource within a heap. -/// -internal sealed class PlacedResource -{ - public int index; - public RenderGraphResourceType type; - public int heapIndex; - public ulong heapOffset; - public ulong sizeInBytes; - - // Original descriptor - public TextureDescriptor textureDesc; - public BufferDescriptor bufferDesc; - - // Lifetime tracking - public int firstUsePass = int.MaxValue; - public int lastUsePass = -1; - - // Aliasing tracking - public readonly List aliasedLogicalResources = new(4); - public MemoryBlock memoryBlock; - - public void Reset() - { - index = -1; - type = RenderGraphResourceType.Texture; - heapIndex = -1; - heapOffset = 0; - sizeInBytes = 0; - textureDesc = default; - bufferDesc = default; - firstUsePass = int.MaxValue; - lastUsePass = -1; - aliasedLogicalResources.Clear(); - memoryBlock = default; - } - - public void UpdateLifetime(int passIndex) - { - firstUsePass = Math.Min(firstUsePass, passIndex); - lastUsePass = Math.Max(lastUsePass, passIndex); - } -} - -/// -/// Manages physical resource allocation and aliasing using heap-based allocation. -/// Supports D3D12 heap tier 2: buffers and textures can alias as long as lifetimes don't overlap. -/// -internal sealed class ResourceAliasingManager -{ - private readonly List _heaps = new(4); - private readonly List _placedResources = new(32); - private readonly RenderGraphObjectPool _pool = new(); - - // Mapping from logical resource index to placed resource index - private readonly Dictionary _logicalToPlaced = new(64); - - // D3D12 alignment constants - private const ulong DefaultTextureAlignment = 65536; // 64KB - private const ulong DefaultBufferAlignment = 65536; // 64KB for D3D12 - - public void BeginFrame() - { - for (var i = 0; i < _placedResources.Count; i++) - { - _pool.Return(_placedResources[i]); - } - - _placedResources.Clear(); - _logicalToPlaced.Clear(); - - // Reset heaps - for (var i = 0; i < _heaps.Count; i++) - { - _heaps[i].Reset(); - } - } - - /// - /// Assigns physical resources (placed resources) to logical resources using heap-based allocation. - /// This is the modern D3D12 approach: check if resource fits in a hole, not if it matches size/format. - /// Uses a two-pass algorithm: - /// 1. First pass: Simulate allocation to determine peak memory usage - /// 2. Second pass: Create a single heap of the peak size and do the real allocation - /// - public void AssignPhysicalResources(RenderGraphResourceRegistry registry, int passCount) - { -#if DEBUG - Console.WriteLine("\n=== Heap-Based Resource Aliasing Analysis ==="); - ulong totalLogicalSize = 0; -#endif - - // Build list of all logical resources (both textures and buffers) with their lifetimes - var logicalResources = ListPool<(int index, RenderGraphResource resource)>.Rent(); - - // Iterate through all resources in unified list - for (var i = 0; i < registry.ResourceCount; i++) - { - var resource = registry.GetResourceByIndex(i); - if (!resource.isImported) // Don't alias imported resources - { - logicalResources.Add((resource.index, resource)); -#if DEBUG - var size = resource.type == RenderGraphResourceType.Texture - ? CalculateTextureSize(resource.textureDescriptor) - : resource.bufferDescriptor.sizeInBytes; - totalLogicalSize += size; - - var typeName = resource.type == RenderGraphResourceType.Texture ? "Texture" : "Buffer"; - var name = resource.type == RenderGraphResourceType.Texture - ? resource.textureDescriptor.name - : resource.bufferDescriptor.name; - - Console.WriteLine($"Logical {typeName} {i}: {name}"); - Console.WriteLine($" Lifetime: Pass {resource.firstUsePass} -> {resource.lastUsePass}"); - Console.WriteLine($" Size: {size / 1024.0:F2} KB"); -#endif - } - } - - // Sort by size descending (larger resources first for better packing) - logicalResources.Sort(static (a, b) => - { - var sizeA = a.resource.type == RenderGraphResourceType.Texture - ? CalculateTextureSize(a.resource.textureDescriptor) - : a.resource.bufferDescriptor.sizeInBytes; - var sizeB = b.resource.type == RenderGraphResourceType.Texture - ? CalculateTextureSize(b.resource.textureDescriptor) - : b.resource.bufferDescriptor.sizeInBytes; - return sizeB.CompareTo(sizeA); // Descending - }); - - // ===== PASS 1: Simulate allocation to determine peak memory usage ===== - var simulationHeap = new ResourceHeap(0, ulong.MaxValue); // Unlimited size for simulation - - foreach (var (logicalIndex, logicalResource) in logicalResources) - { - ulong size; - ulong alignment; - - if (logicalResource.type == RenderGraphResourceType.Texture) - { - size = CalculateTextureSize(logicalResource.textureDescriptor); - alignment = DefaultTextureAlignment; - } - else // Buffer - { - size = logicalResource.bufferDescriptor.sizeInBytes; - alignment = DefaultBufferAlignment; - } - - var (success, offset, block) = simulationHeap.TryAllocate( - size, - logicalResource.firstUsePass, - logicalResource.lastUsePass, - logicalIndex, - alignment); - - if (!success) - { - throw new InvalidOperationException("Simulation allocation failed - this should never happen with unlimited heap"); - } - } - - // Get peak usage from simulation - var peakMemoryUsage = simulationHeap.GetPeakUsage(); - - // Align peak usage to 64KB (D3D12 requirement) - peakMemoryUsage = AlignUp(peakMemoryUsage, DefaultTextureAlignment); - -#if DEBUG - Console.WriteLine($"\nPeak Memory Usage: {peakMemoryUsage / (1024.0 * 1024.0):F2} MB"); -#endif - - // ===== PASS 2: Create a single heap of the peak size and do the real allocation ===== - var mainHeap = new ResourceHeap(0, peakMemoryUsage); - _heaps.Add(mainHeap); - -#if DEBUG - Console.WriteLine($"Created Single Heap:"); - Console.WriteLine($" Size: {peakMemoryUsage / (1024.0 * 1024.0):F2} MB\n"); -#endif - - // Allocate each logical resource in the heap - foreach (var (logicalIndex, logicalResource) in logicalResources) - { - ulong size; - ulong alignment; - - if (logicalResource.type == RenderGraphResourceType.Texture) - { - size = CalculateTextureSize(logicalResource.textureDescriptor); - alignment = DefaultTextureAlignment; - } - else // Buffer - { - size = logicalResource.bufferDescriptor.sizeInBytes; - alignment = DefaultBufferAlignment; - } - - var (success, offset, block) = mainHeap.TryAllocate( - size, - logicalResource.firstUsePass, - logicalResource.lastUsePass, - logicalIndex, - alignment); - - if (!success) - { - throw new InvalidOperationException("Real allocation failed - this should match simulation"); - } - - var assignedHeapIndex = 0; - var assignedOffset = offset; - var assignedBlock = block; - - var assignedPlaced = _pool.Rent(); - assignedPlaced.index = _placedResources.Count; - assignedPlaced.type = logicalResource.type; - assignedPlaced.heapIndex = assignedHeapIndex; - assignedPlaced.heapOffset = assignedOffset; - assignedPlaced.sizeInBytes = size; - assignedPlaced.firstUsePass = logicalResource.firstUsePass; - assignedPlaced.lastUsePass = logicalResource.lastUsePass; - assignedPlaced.memoryBlock = assignedBlock; - - if (logicalResource.type == RenderGraphResourceType.Texture) - { - assignedPlaced.textureDesc = logicalResource.textureDescriptor; - } - else - { - assignedPlaced.bufferDesc = logicalResource.bufferDescriptor; - } - - assignedPlaced.aliasedLogicalResources.Clear(); - assignedPlaced.aliasedLogicalResources.Add(logicalIndex); - - _placedResources.Add(assignedPlaced); - -#if DEBUG - var isAliased = assignedBlock.logicalResourceIndex != logicalIndex && assignedBlock.logicalResourceIndex != -1; - var name = logicalResource.type == RenderGraphResourceType.Texture - ? logicalResource.textureDescriptor.name - : logicalResource.bufferDescriptor.name; - var typeName = logicalResource.type == RenderGraphResourceType.Texture ? "Texture" : "Buffer"; - - if (isAliased) - { - Console.WriteLine($"\nALIASING {typeName}: {name}"); - Console.WriteLine($" Placed in Heap {assignedHeapIndex} at offset {assignedOffset} ({assignedOffset / 1024.0:F2} KB)"); - Console.WriteLine($" Size: {size / 1024.0:F2} KB"); - } - else - { - Console.WriteLine($"\nAllocated {typeName}: {name}"); - Console.WriteLine($" Heap {assignedHeapIndex}, Offset {assignedOffset} ({assignedOffset / 1024.0:F2} KB)"); - Console.WriteLine($" Size: {size / 1024.0:F2} KB"); - } -#endif - - // Record the mapping - _logicalToPlaced[logicalIndex] = assignedPlaced.index; - } - - // Second pass: Populate aliasedLogicalResources lists - // For each placed resource, find all OTHER placed resources at the same heap+offset - for (var i = 0; i < _placedResources.Count; i++) - { - var placed = _placedResources[i]; - - // Find all logical resources that share the same heap location - for (var j = 0; j < _placedResources.Count; j++) - { - if (i == j) continue; // Skip self - - var other = _placedResources[j]; - - // Check if they're at the same heap+offset - if (other.heapIndex == placed.heapIndex && other.heapOffset == placed.heapOffset) - { - // Add the other's logical resource to this one's aliased list - var otherLogicalIndex = other.aliasedLogicalResources[0]; // Each has exactly one at this point - if (!placed.aliasedLogicalResources.Contains(otherLogicalIndex)) - { - placed.aliasedLogicalResources.Add(otherLogicalIndex); - } - } - } - } - -#if DEBUG - // Debug output: Show which resources alias with each other - Console.WriteLine("\n=== Aliasing Groups ==="); - var processedOffsets = new HashSet<(int heapIndex, ulong offset)>(); - for (var i = 0; i < _placedResources.Count; i++) - { - var placed = _placedResources[i]; - var key = (placed.heapIndex, placed.heapOffset); - - if (!processedOffsets.Contains(key) && placed.aliasedLogicalResources.Count > 1) - { - processedOffsets.Add(key); - Console.WriteLine($"Heap {placed.heapIndex} @ Offset {placed.heapOffset / 1024.0:F2} KB ({placed.aliasedLogicalResources.Count} resources):"); - - foreach (var logicalIdx in placed.aliasedLogicalResources) - { - var res = registry.GetResourceByIndex(logicalIdx); - var name = res.type == RenderGraphResourceType.Texture - ? res.textureDescriptor.name - : res.bufferDescriptor.name; - Console.WriteLine($" - {name} (Pass {res.firstUsePass}-{res.lastUsePass})"); - } - } - } - Console.WriteLine("=======================\n"); -#endif - -#if DEBUG - ulong totalPhysicalSize = 0; - for (var i = 0; i < _heaps.Count; i++) - { - totalPhysicalSize += _heaps[i].GetPeakUsage(); - } - - Console.WriteLine($"\n=== Heap-Based Aliasing Summary ==="); - Console.WriteLine($"Logical Resources: {logicalResources.Count}"); - Console.WriteLine($"Placed Resources: {_placedResources.Count}"); - Console.WriteLine($"Heaps: {_heaps.Count}"); - 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 GetPlacedResourceIndex(int logicalIndex) - { - return _logicalToPlaced.TryGetValue(logicalIndex, out var placedIndex) ? placedIndex : -1; - } - - public PlacedResource? GetPlacedResource(int placedIndex) - { - return placedIndex >= 0 && placedIndex < _placedResources.Count - ? _placedResources[placedIndex] - : null; - } - - private static ulong CalculateTextureSize(TextureDescriptor descriptor) - { - var bytesPerPixel = descriptor.format switch - { - TextureFormat.RGBA8 => 4, - TextureFormat.RGBA16F => 8, - TextureFormat.RGBA32F => 16, - TextureFormat.Depth32F => 4, - TextureFormat.Depth24Stencil8 => 4, - _ => 4 - }; - - // Add alignment padding (D3D12 requires 64KB alignment) - var size = (ulong)(descriptor.width * descriptor.height * bytesPerPixel); - return AlignUp(size, DefaultTextureAlignment); - } - - private static ulong AlignUp(ulong value, ulong alignment) - { - return (value + alignment - 1) & ~(alignment - 1); - } - - public void Clear() - { - for (var i = 0; i < _placedResources.Count; i++) - { - _pool.Return(_placedResources[i]); - } - _placedResources.Clear(); - _logicalToPlaced.Clear(); - _heaps.Clear(); - } - - /// - /// Restores aliasing state from cache. - /// - public void RestoreFromCache(Dictionary logicalToPlaced, List placedData) - { - _logicalToPlaced.Clear(); - foreach (var kvp in logicalToPlaced) - { - _logicalToPlaced[kvp.Key] = kvp.Value; - } - - // Restore placed resources - for (var i = 0; i < placedData.Count; i++) - { - var placed = _pool.Rent(); - - var data = placedData[i]; - placed.index = data.index; - placed.type = data.type; - placed.heapIndex = data.heapIndex; - placed.heapOffset = data.heapOffset; - placed.sizeInBytes = data.sizeInBytes; - placed.textureDesc = data.textureDesc; - placed.bufferDesc = data.bufferDesc; - placed.firstUsePass = data.firstUsePass; - placed.lastUsePass = data.lastUsePass; - - placed.aliasedLogicalResources.Clear(); - - _placedResources.Add(placed); - } - } - - /// - /// Stores current aliasing state to cache. - /// - public void StoreToCache(Dictionary outLogicalToPlaced, List outPlacedData) - { - outLogicalToPlaced.Clear(); - foreach (var kvp in _logicalToPlaced) - { - outLogicalToPlaced[kvp.Key] = kvp.Value; - } - - outPlacedData.Clear(); - for (var i = 0; i < _placedResources.Count; i++) - { - var placed = _placedResources[i]; - outPlacedData.Add(new PlacedResourceData - { - index = placed.index, - type = placed.type, - heapIndex = placed.heapIndex, - heapOffset = placed.heapOffset, - sizeInBytes = placed.sizeInBytes, - textureDesc = placed.textureDesc, - bufferDesc = placed.bufferDesc, - firstUsePass = placed.firstUsePass, - lastUsePass = placed.lastUsePass - }); - } - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs b/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs deleted file mode 100644 index 3ee2a84..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphBarriers.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Ghost.Core; -using System.Runtime.InteropServices; - -namespace Ghost.RenderGraph.Concept; - -/// -/// GPU resource states for barrier tracking. -/// Based on D3D12 resource states. -/// -[Flags] -public enum ResourceState -{ - Common = 0, - RenderTarget = 1 << 0, - DepthWrite = 1 << 1, - DepthRead = 1 << 2, - ShaderResource = 1 << 3, - UnorderedAccess = 1 << 4, - CopySource = 1 << 5, - CopyDest = 1 << 6, - Present = 1 << 7, - IndirectArgument = 1 << 8, -} - -/// -/// Types of barriers that can be inserted. -/// -public enum BarrierType -{ - Transition, // State transition (e.g., RenderTarget -> ShaderResource) - Aliasing, // Aliasing barrier (new resource reusing memory) -} - -/// -/// Represents a resource barrier that needs to be inserted. -/// For D3D12 aliasing barriers: ResourceBefore is the old resource, ResourceAfter is the new resource. -/// -internal struct ResourceBarrier -{ - [StructLayout(LayoutKind.Explicit)] - private struct barrier_union - { - internal struct barrier_union_transition - { - public Identifier resource; - public ResourceState stateBefore; - public ResourceState stateAfter; - } - - internal struct barrier_union_aliasing - { - public Identifier resourceBefore; - public Identifier resourceAfter; - } - - // TODO: union can not have non-blittable types - - [FieldOffset(0)] - public barrier_union_transition transition; - [FieldOffset(0)] - public barrier_union_aliasing aliasing; - } - - private barrier_union _union; - - public BarrierType Type - { - get; init; - } - - public int PassIndex - { - get; init; - } - - // For Transition and UAV barriers - public readonly Identifier Resource => _union.transition.resource; - public readonly ResourceState StateBefore => _union.transition.stateBefore; - public readonly ResourceState StateAfter => _union.transition.stateAfter; - - // For Aliasing barriers (D3D12_RESOURCE_BARRIER::Aliasing) - public readonly Identifier ResourceBefore => _union.aliasing.resourceBefore; - public readonly Identifier ResourceAfter => _union.aliasing.resourceAfter; - - // Constructor for Aliasing barriers - public static ResourceBarrier CreateAliasingBarrier( - Identifier resourceBefore, - Identifier resourceAfter, - int passIndex) - { - return new ResourceBarrier - { - Type = BarrierType.Aliasing, - PassIndex = passIndex, - _union = new barrier_union - { - aliasing = new barrier_union.barrier_union_aliasing - { - resourceBefore = resourceBefore, - resourceAfter = resourceAfter - } - } - }; - } - - public static ResourceBarrier CreateTransitionBarrier( - Identifier resource, - ResourceState before, - ResourceState after, - int passIndex) - { - return new ResourceBarrier - { - Type = BarrierType.Transition, - PassIndex = passIndex, - _union = new barrier_union - { - transition = new barrier_union.barrier_union_transition - { - resource = resource, - stateBefore = before, - stateAfter = after - } - } - }; - } - - 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" - }; - } -} - -/// -/// Tracks the current state of a resource across passes. -/// -internal sealed class ResourceStateTracker -{ - public int resourceIndex; - public ResourceState currentState = ResourceState.Common; - public int lastAccessPass = -1; - - public void Reset() - { - resourceIndex = -1; - currentState = ResourceState.Common; - lastAccessPass = -1; - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphBlackboard.cs b/Ghost.RenderGraph.Concept/RenderGraphBlackboard.cs deleted file mode 100644 index 83e1a1d..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphBlackboard.cs +++ /dev/null @@ -1,62 +0,0 @@ -namespace Ghost.RenderGraph.Concept; - -/// -/// Blackboard for sharing data between render passes. -/// Uses a dictionary with type keys to store different pass data types. -/// Avoids allocations by reusing the same dictionary across frames. -/// -public sealed class RenderGraphBlackboard -{ - private readonly Dictionary _data = new(16); - - /// - /// Adds or updates pass data in the blackboard. - /// - public void Add(T data) - where T : class, IPassData - { - var type = typeof(T); - _data[type] = data; - } - - /// - /// Retrieves pass data from the blackboard. - /// - public T Get() - where T : class, IPassData - { - var type = typeof(T); - if (_data.TryGetValue(type, out var obj)) - { - return (T)obj; - } - - throw new KeyNotFoundException($"Pass data of type {type.Name} not found in blackboard"); - } - - /// - /// Tries to get pass data from the blackboard. - /// - public bool TryGet(out T? data) - where T : class, IPassData - { - var type = typeof(T); - if (_data.TryGetValue(type, out var obj)) - { - data = (T)obj; - return true; - } - - data = null; - return false; - } - - /// - /// Clears all data from the blackboard. - /// Does not deallocate the backing dictionary to avoid allocations. - /// - public void Clear() - { - _data.Clear(); - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs b/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs deleted file mode 100644 index 351c588..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphBuilder.cs +++ /dev/null @@ -1,283 +0,0 @@ -using Ghost.Core; -using System.Diagnostics; - -namespace Ghost.RenderGraph.Concept; - -[Flags] -public enum AccessFlags : byte -{ - None = 0, - Read = 1 << 0, - Write = 1 << 1, - Discard = 1 << 2, - - WriteAll = Write | Discard, - 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); - - /// - /// Creates a new buffer resource based on the specified descriptor. - /// - /// A structure that defines the properties and configuration of the buffer to create. - /// An identifier for the newly created buffer resource. - Identifier CreateBuffer(in BufferDescriptor 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); - - /// - /// Registers the specified buffer for use in the current render graph pass with the given access mode. - /// - /// The identifier of the buffer to be used in the render graph pass. - /// The access mode specifying how the buffer will be read or written during the pass. - /// Optional hint about how the buffer will be used (e.g., IndirectArgument). Default is None (ByteAddressBuffer SRV). - /// An identifier for the buffer. - Identifier UseBuffer(Identifier buffer, AccessFlags accessMode, BufferHint hint = BufferHint.None); -} - -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. - /// Access flags. Default is Write (assumes partial update). Use WriteAll for fullscreen passes. - void SetColorAttachment(Identifier texture, int index, AccessFlags flags = AccessFlags.Write); - - /// - /// 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. - /// Access flags. Default is ReadWrite (assumes partial update). Use WriteAll for fullscreen passes. - void SetDepthAttachment(Identifier texture, AccessFlags flags = AccessFlags.ReadWrite); - - /// - /// 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, RenderGraphResourceType type) - { - if (accessFlags.HasFlag(AccessFlags.Read)) - { - _pass.resourceReads[(int)type].Add(resource); - _resources.AddConsumer(resource, _pass.index); - } - - if (accessFlags.HasFlag(AccessFlags.Write)) - { - _pass.resourceWrites[(int)type].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[(int)RenderGraphResourceType.Texture].Add(handle.AsResource()); - _resources.SetProducer(handle.AsResource(), _pass.index); - return handle; - } - - public Identifier CreateBuffer(in BufferDescriptor descriptor) - { - ThrowIfDisposed(); - - var handle = _resources.CreateBuffer(descriptor); - _pass.resourceCreates[(int)RenderGraphResourceType.Buffer].Add(handle.AsResource()); - _resources.SetProducer(handle.AsResource(), _pass.index); - return handle; - } - - public Identifier UseTexture(Identifier texture, AccessFlags flags) - { - ThrowIfDisposed(); - return UseResource(texture.AsResource(), flags, RenderGraphResourceType.Texture).AsTexture(); - } - - public Identifier UseBuffer(Identifier buffer, AccessFlags flags, BufferHint hint = BufferHint.None) - { - ThrowIfDisposed(); - - // Store buffer hint if not None - if (hint != BufferHint.None) - { - _pass.bufferHints[buffer.AsResource().Value] = hint; - } - - return UseResource(buffer.AsResource(), flags, RenderGraphResourceType.Buffer).AsBuffer(); - } - - public Identifier UseRandomAccessTexture(Identifier texture) - { - ThrowIfDisposed(); - - var resource = texture.AsResource(); - UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Texture); - _pass.randomAccess.Add(resource); - return texture; - } - - public Identifier UseRandomAccessBuffer(Identifier buffer) - { - ThrowIfDisposed(); - - var resource = buffer.AsResource(); - UseResource(resource, AccessFlags.ReadWrite, RenderGraphResourceType.Buffer); - _pass.randomAccess.Add(resource); - return buffer; - } - - public void SetColorAttachment(Identifier texture, int index, AccessFlags flags = AccessFlags.Write) - { - ThrowIfDisposed(); - - Debug.Assert(index >= 0 && index < _pass.colorAccess.Length, "Color attachment index out of range."); - - var id = UseTexture(texture, flags); - if (_pass.colorAccess[index].id == id || _pass.colorAccess[index].id.IsInvalid) - { - _pass.maxColorIndex = Math.Max(_pass.maxColorIndex, index); - _pass.colorAccess[index] = new TextureAccess(id, flags); - } - else - { - throw new InvalidOperationException($"Color attachment at index {index} is already set to a different texture."); - } - } - - public void SetDepthAttachment(Identifier texture, AccessFlags flags = AccessFlags.Write) - { - ThrowIfDisposed(); - - var id = UseTexture(texture, flags); - if (_pass.depthAccess.id == id || _pass.depthAccess.id.IsInvalid) - { - _pass.depthAccess = new TextureAccess(id, flags); - } - 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/RenderGraphCompilationCache.cs b/Ghost.RenderGraph.Concept/RenderGraphCompilationCache.cs deleted file mode 100644 index b406f72..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphCompilationCache.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Ghost.RenderGraph.Concept; - -/// -/// Represents cached compilation results for a render graph. -/// This avoids recompiling the graph when the structure hasn't changed. -/// -internal sealed class CachedCompilation -{ - // Compiled pass indices (indices into the _passes list) - public readonly List compiledPassIndices = new(64); - - // Culling decisions for each pass - public readonly List passCulledFlags = new(64); - - // Physical resource aliasing mappings (logical index -> physical index) - public readonly Dictionary logicalToPhysical = new(128); - - // Placed resource metadata - public readonly List placedResources = new(32); - - // Resource barriers - public readonly List barriers = new(128); - - // Resource state mappings (for barrier generation) - public readonly Dictionary resourceStates = new(128); - - public void Clear() - { - compiledPassIndices.Clear(); - passCulledFlags.Clear(); - logicalToPhysical.Clear(); - placedResources.Clear(); - barriers.Clear(); - resourceStates.Clear(); - } -} - -/// -/// Placed resource data for caching. -/// -internal struct PlacedResourceData -{ - public int index; - public RenderGraphResourceType type; - public int heapIndex; - public ulong heapOffset; - public ulong sizeInBytes; - public TextureDescriptor textureDesc; - public BufferDescriptor bufferDesc; - public int firstUsePass; - public int lastUsePass; -} - -/// -/// Manages compilation caching for render graphs. -/// Stores compiled results and allows cache hits when graph structure is unchanged. -/// -internal sealed class RenderGraphCompilationCache -{ - private ulong _cachedHash; - private readonly CachedCompilation _cached = new(); - private bool _hasCachedData; - - // Statistics - public int CacheHits { get; private set; } - public int CacheMisses { get; private set; } - - /// - /// Attempts to retrieve cached compilation results. - /// - public bool TryGetCached(ulong hash, [MaybeNullWhen(false)] out CachedCompilation result) - { - if (_hasCachedData && _cachedHash == hash) - { - result = _cached; - CacheHits++; - return true; - } - - result = null; - CacheMisses++; - return false; - } - - /// - /// Stores compilation results in the cache. - /// - public void Store(ulong hash, CachedCompilation data) - { - _cachedHash = hash; - _hasCachedData = true; - - // Deep copy the data - _cached.Clear(); - - _cached.compiledPassIndices.AddRange(data.compiledPassIndices); - _cached.passCulledFlags.AddRange(data.passCulledFlags); - - foreach (var kvp in data.logicalToPhysical) - { - _cached.logicalToPhysical[kvp.Key] = kvp.Value; - } - - _cached.placedResources.AddRange(data.placedResources); - _cached.barriers.AddRange(data.barriers); - - foreach (var kvp in data.resourceStates) - { - _cached.resourceStates[kvp.Key] = kvp.Value; - } - } - - /// - /// Invalidates the cache, forcing recompilation on next Compile(). - /// - public void Invalidate() - { - _hasCachedData = false; - _cachedHash = 0; - _cached.Clear(); - } - - /// - /// Gets cache statistics for debugging. - /// - public (int hits, int misses, double hitRate) GetStatistics() - { - int total = CacheHits + CacheMisses; - double hitRate = total > 0 ? (double)CacheHits / total : 0.0; - return (CacheHits, CacheMisses, hitRate); - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphContext.cs b/Ghost.RenderGraph.Concept/RenderGraphContext.cs deleted file mode 100644 index 7b531f6..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphContext.cs +++ /dev/null @@ -1,151 +0,0 @@ -using Ghost.Core; - -namespace Ghost.RenderGraph.Concept; - -/// -/// Mock command buffer for recording GPU commands. -/// In a real implementation, this would wrap D3D12 command lists. -/// -internal sealed class MockCommandBuffer -{ - public void SetRenderTarget(Identifier texture) - { -#if DEBUG - Console.WriteLine(nameof(SetRenderTarget) + ": " + texture); -#endif - } - - public void SetDepthStencil(Identifier texture) - { -#if DEBUG - Console.WriteLine(nameof(SetDepthStencil) + ": " + texture); -#endif - } - - public void ClearRenderTarget(Identifier texture, float r, float g, float b, float a) - { -#if DEBUG - Console.WriteLine(nameof(ClearRenderTarget) + ": " + texture); -#endif - } - - public void ClearDepth(Identifier texture, float depth) - { -#if DEBUG - Console.WriteLine(nameof(ClearDepth) + ": " + texture); -#endif - } - - public void Draw(int vertexCount) - { -#if DEBUG - Console.WriteLine(nameof(Draw) + ": " + vertexCount); -#endif - } - - public void BindShaderResource(Identifier resource, int slot) - { -#if DEBUG - Console.WriteLine(nameof(BindShaderResource) + ": " + resource + ", slot " + slot); -#endif - } - - public void BindUnorderedAccess(Identifier resource, int slot) - { -#if DEBUG - Console.WriteLine(nameof(BindUnorderedAccess) + ": " + resource + ", slot " + slot); -#endif - } - - public void Dispatch(int x, int y, int z) - { -#if DEBUG - Console.WriteLine(nameof(Dispatch) + ": " + x + ", " + y + ", " + z); -#endif - } - - public void ResourceBarrier(Identifier resource, ResourceState stateBefore, ResourceState stateAfter) - { -#if DEBUG - Console.WriteLine(nameof(ResourceBarrier) + ": " + resource + " from " + stateBefore + " to " + stateAfter); -#endif - } - - public void AliasBarrier(Identifier resourceBefore, Identifier resourceAfter) - { -#if DEBUG - Console.WriteLine(nameof(AliasBarrier) + ": " + resourceBefore + " to " + resourceAfter); -#endif - } - - public void BeginRenderPass(int nativePassIndex, int colorAttachmentCount, bool hasDepth) - { -#if DEBUG - Console.WriteLine($"\n=== BeginRenderPass (Native Pass {nativePassIndex}) ==="); - Console.WriteLine($" Color attachments: {colorAttachmentCount}, Depth: {hasDepth}"); -#endif - } - - public void EndRenderPass() - { -#if DEBUG - Console.WriteLine("=== EndRenderPass ===\n"); -#endif - } -} - -/// -/// Context for raster rendering passes. -/// Directly exposes command buffer methods. -/// -public readonly struct RasterRenderContext -{ - private readonly MockCommandBuffer _cmd; - - internal RasterRenderContext(MockCommandBuffer cmd) - { - _cmd = cmd; - } - - // Expose command buffer methods directly - public void SetRenderTarget(Identifier texture) => _cmd.SetRenderTarget(texture); - public void SetDepthStencil(Identifier texture) => _cmd.SetDepthStencil(texture); - public void ClearRenderTarget(Identifier texture, float r, float g, float b, float a) => _cmd.ClearRenderTarget(texture, r, g, b, a); - public void ClearDepth(Identifier texture, float depth) => _cmd.ClearDepth(texture, depth); - public void Draw(int vertexCount) => _cmd.Draw(vertexCount); - public void BindShaderResource(Identifier resource, int slot) => _cmd.BindShaderResource(resource, slot); -} - -/// -/// Context for compute rendering passes. -/// Directly exposes command buffer methods. -/// -public readonly struct ComputeRenderContext -{ - private readonly MockCommandBuffer _cmd; - - internal ComputeRenderContext(MockCommandBuffer cmd) - { - _cmd = cmd; - } - - // Expose command buffer methods directly - public void BindShaderResource(Identifier resource, int slot) => _cmd.BindShaderResource(resource, slot); - public void BindUnorderedAccess(Identifier resource, int slot) => _cmd.BindUnorderedAccess(resource, slot); - public void Dispatch(int x, int y, int z) => _cmd.Dispatch(x, y, z); -} - -/// -/// Unified render context containing both raster and compute contexts. -/// -internal readonly struct RenderContext -{ - public readonly RasterRenderContext RasterContext; - public readonly ComputeRenderContext ComputeContext; - - public RenderContext(MockCommandBuffer cmd) - { - RasterContext = new RasterRenderContext(cmd); - ComputeContext = new ComputeRenderContext(cmd); - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphNativePass.cs b/Ghost.RenderGraph.Concept/RenderGraphNativePass.cs deleted file mode 100644 index d13dbcf..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphNativePass.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Ghost.Core; - -namespace Ghost.RenderGraph.Concept; - -/// -/// Represents a native render pass that can contain multiple merged logical passes. -/// Maps to D3D12 BeginRenderPass/EndRenderPass or Vulkan vkCmdBeginRenderPass/vkCmdEndRenderPass. -/// -internal sealed class NativeRenderPass -{ - public int index; - - /// - /// Indices of logical passes merged into this native render pass. - /// - public readonly List mergedPassIndices = new(4); - - /// - /// Color attachments shared across all merged passes. - /// - public RenderTargetInfo[] colorAttachments = new RenderTargetInfo[8]; - public int colorAttachmentCount; - - /// - /// Depth-stencil attachment (optional). - /// - public DepthStencilInfo depthAttachment; - public bool hasDepthAttachment; - - /// - /// Range of logical passes included in this native pass. - /// - public int firstLogicalPass; - public int lastLogicalPass; - - /// - /// Whether UAV writes are allowed during this render pass. - /// - public bool allowUAVWrites; - - public void Reset() - { - index = -1; - mergedPassIndices.Clear(); - colorAttachmentCount = 0; - hasDepthAttachment = false; - depthAttachment = default; - firstLogicalPass = int.MaxValue; - lastLogicalPass = -1; - allowUAVWrites = false; - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphPass.cs b/Ghost.RenderGraph.Concept/RenderGraphPass.cs deleted file mode 100644 index 2f62c93..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphPass.cs +++ /dev/null @@ -1,155 +0,0 @@ -using Ghost.Core; -using System.Runtime.CompilerServices; - -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 List>[(int)RenderGraphResourceType.Count]; - public readonly List>[] resourceWrites = new List>[(int)RenderGraphResourceType.Count]; - public readonly List>[] resourceCreates = new List>[(int)RenderGraphResourceType.Count]; - - // Buffer usage hints (maps buffer resource ID to hint) - public readonly Dictionary bufferHints = new(8); - - // 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 bool HasRenderFunc(); - public abstract int GetRenderFuncHashCode(); - - 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(); - - for (var i = 0; i < (int)RenderGraphResourceType.Count; i++) - { - resourceReads[i].Clear(); - resourceWrites[i].Clear(); - resourceCreates[i].Clear(); - } - - bufferHints.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 int GetRenderFuncHashCode() - { - if (renderFunc == null) - { - return 0; - } - - var methodHashCode = RuntimeHelpers.GetHashCode(renderFunc.Method); - return renderFunc.Target == null ? methodHashCode : methodHashCode ^ RuntimeHelpers.GetHashCode(renderFunc.Target); // static deleget does not have target - } - - public override void Reset(RenderGraphObjectPool pool) - { - base.Reset(pool); - pool.Return(passData); - - passData = null!; - renderFunc = null; - } -} - -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/RenderGraphResourcePool.cs b/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs deleted file mode 100644 index 7ce4a3f..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphResourcePool.cs +++ /dev/null @@ -1,251 +0,0 @@ -using Ghost.Core; -using Misaki.HighPerformance.Buffer; - -namespace Ghost.RenderGraph.Concept; - -/// -/// Object pool for reusing allocated objects across frames. -/// This is key to minimizing GC allocations after the first frame. -/// -internal sealed class RenderGraphObjectPool -{ - private static readonly List s_allocatedPools = new(); - - private class SharedObjectPoolBase - { - public SharedObjectPoolBase() { } - public virtual void Clear() { } - } - - private class SharedObjectPool : SharedObjectPoolBase where T : class, new() - { - private static readonly ObjectPool s_pool = AllocatePool(); - - private static ObjectPool AllocatePool() - { - 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; - } - - /// - /// Clear the pool using SharedObjectPool instance. - /// - /// - public override void Clear() - { - s_pool.Reset(); - } - - /// - /// 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(); - - /// - /// Return an object to the pool. - /// - /// instance to release. - public static void Return(T toRelease) => s_pool.Return(toRelease); - } - - public T Rent() - where T : class, new() - { - return SharedObjectPool.Rent(); - } - - public void Return(T obj) - where T : class, new() - { - SharedObjectPool.Return(obj); - } - - public void Clear() - { - for (var i = 0; i < s_allocatedPools.Count; i++) - { - s_allocatedPools[i].Clear(); - } - } -} - -/// -/// Represents a resource in the render graph (texture or buffer). -/// -internal sealed class RenderGraphResource -{ - public RenderGraphResourceType type; - public int index; - public TextureDescriptor textureDescriptor; - public BufferDescriptor bufferDescriptor; - 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() - { - type = RenderGraphResourceType.Texture; - index = -1; - textureDescriptor = default; - bufferDescriptor = default; - isImported = false; - firstUsePass = -1; - lastUsePass = -1; - producerPass = -1; - consumerPasses.Clear(); - refCount = 0; - } -} - -/// -/// Registry for managing all resources in the render graph. -/// Uses pooling to minimize allocations after the first frame. -/// Uses a single unified list for both textures and buffers with global indexing. -/// -internal sealed class RenderGraphResourceRegistry -{ - private readonly List _resources = new(64); - private readonly RenderGraphObjectPool _pool = new(); - - public int ResourceCount => _resources.Count; - public int TextureResourceCount - { - get - { - int count = 0; - for (int i = 0; i < _resources.Count; i++) - { - if (_resources[i].type == RenderGraphResourceType.Texture) - count++; - } - return count; - } - } - public int BufferResourceCount - { - get - { - int count = 0; - for (int i = 0; i < _resources.Count; i++) - { - if (_resources[i].type == RenderGraphResourceType.Buffer) - count++; - } - return count; - } - } - - public void BeginFrame() - { - // Return all resources to pool - for (var i = 0; i < _resources.Count; i++) - { - _pool.Return(_resources[i]); - } - _resources.Clear(); - } - - public Identifier ImportTexture(TextureDescriptor descriptor) - { - var resource = _pool.Rent(); - resource.type = RenderGraphResourceType.Texture; - resource.index = _resources.Count; - resource.textureDescriptor = descriptor; - resource.isImported = true; - - _resources.Add(resource); - - return new Identifier(resource.index); - } - - public Identifier CreateTexture(TextureDescriptor descriptor) - { - var resource = _pool.Rent(); - resource.type = RenderGraphResourceType.Texture; - resource.index = _resources.Count; - resource.textureDescriptor = descriptor; - resource.isImported = false; - - _resources.Add(resource); - - return new Identifier(resource.index); - } - - public Identifier ImportBuffer(BufferDescriptor descriptor) - { - var resource = _pool.Rent(); - resource.type = RenderGraphResourceType.Buffer; - resource.index = _resources.Count; - resource.bufferDescriptor = descriptor; - resource.isImported = true; - - _resources.Add(resource); - - return new Identifier(resource.index); - } - - public Identifier CreateBuffer(BufferDescriptor descriptor) - { - var resource = _pool.Rent(); - resource.type = RenderGraphResourceType.Buffer; - resource.index = _resources.Count; - resource.bufferDescriptor = descriptor; - resource.isImported = false; - - _resources.Add(resource); - - return new Identifier(resource.index); - } - - public RenderGraphResource GetResource(Identifier resource) - { - return _resources[resource.Value]; - } - - public RenderGraphResource GetResource(Identifier texture) - { - return _resources[texture.Value]; - } - - public RenderGraphResource GetResource(Identifier buffer) - { - return _resources[buffer.Value]; - } - - /// - /// Gets resource by global index. Use this when iterating over all resources. - /// - public RenderGraphResource GetResourceByIndex(int index) - { - return _resources[index]; - } - - public void SetProducer(Identifier resourceID, int passIndex) - { - var resource = GetResource(resourceID); - resource.producerPass = passIndex; - if (resource.firstUsePass < 0) - { - 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.firstUsePass = passIndex; - } - } -} diff --git a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs b/Ghost.RenderGraph.Concept/RenderGraphTypes.cs deleted file mode 100644 index a067494..0000000 --- a/Ghost.RenderGraph.Concept/RenderGraphTypes.cs +++ /dev/null @@ -1,270 +0,0 @@ -using Ghost.Core; -using System.Runtime.CompilerServices; - -namespace Ghost.RenderGraph.Concept; - -internal enum RenderGraphResourceType : int -{ - Texture, - Buffer, - // AccelerationStructure, - Count -} - -public struct RGResource; -public struct RGTexture; -public struct RGBuffer; - -public static class RGResourceExtensions -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Identifier AsResource(this Identifier texture) - { - return new Identifier(texture.Value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Identifier AsResource(this Identifier buffer) - { - return new Identifier(buffer.Value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Identifier AsTexture(this Identifier resource) - { - return new Identifier(resource.Value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static Identifier AsBuffer(this Identifier resource) - { - return new Identifier(resource.Value); - } -} - -/// -/// Hints for how a buffer will be used in a pass. -/// Used to determine correct resource state transitions. -/// -[Flags] -public enum BufferHint -{ - /// - /// No special usage - buffer will be used as shader resource (SRV) or UAV based on AccessFlags. - /// - None = 0, - - /// - /// Buffer will be used as indirect argument buffer (ExecuteIndirect). - /// Requires ResourceState.IndirectArgument. - /// - IndirectArgument = 1 << 0, -} - -internal readonly struct TextureAccess -{ - public readonly Identifier id; - public readonly AccessFlags accessFlags; - - public TextureAccess(Identifier id, AccessFlags accessFlags) - { - this.id = id; - this.accessFlags = accessFlags; - } -} - -/// -/// Tracks buffer access information including usage hints. -/// -internal readonly struct BufferAccess -{ - public readonly Identifier id; - public readonly AccessFlags accessFlags; - public readonly BufferHint hint; - - public BufferAccess(Identifier id, AccessFlags accessFlags, BufferHint hint = BufferHint.None) - { - this.id = id; - this.accessFlags = accessFlags; - this.hint = hint; - } -} - -/// -/// Texture formats supported by the render graph. -/// -public enum TextureFormat : int -{ - RGBA8, - RGBA16F, - RGBA32F, - Depth32F, - Depth24Stencil8 -} - -/// -/// Descriptor for creating a texture resource. -/// -public readonly struct TextureDescriptor : IEquatable -{ - 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) - { - this.width = width; - this.height = height; - this.format = format; - this.name = name; - } - - 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); - } -} - -/// -/// Base interface for pass data that can be stored in the blackboard. -/// -public interface IPassData -{ -} - -/// -/// Specifies how to load attachment contents at the beginning of a render pass. -/// -public enum AttachmentLoadOp -{ - /// - /// Load existing contents from memory. Required when reading previous data. - /// - Load, - - /// - /// Clear attachment to a specified value. - /// - Clear, - - /// - /// Don't care about previous contents (best performance on TBDR GPUs). - /// Use when you guarantee to overwrite all pixels. - /// - DontCare -} - -/// -/// Specifies how to store attachment contents at the end of a render pass. -/// -public enum AttachmentStoreOp -{ - /// - /// Store contents to memory. Required if resource is used after this pass. - /// - Store, - - /// - /// Don't care about storing contents (best performance on TBDR GPUs). - /// Use when resource is not needed after this pass. - /// - DontCare -} - -/// -/// Information about a render target attachment in a native render pass. -/// -internal struct RenderTargetInfo -{ - public Identifier texture; - public AccessFlags access; - public AttachmentLoadOp loadOp; - public AttachmentStoreOp storeOp; - public float clearR; - public float clearG; - public float clearB; - public float clearA; -} - -/// -/// Information about a depth-stencil attachment in a native render pass. -/// -internal struct DepthStencilInfo -{ - public Identifier texture; - public AccessFlags access; - public AttachmentLoadOp loadOp; - public AttachmentStoreOp storeOp; - public float clearDepth; - public byte clearStencil; -} diff --git a/Ghost.RenderGraph.Concept/Unity/NativePassCompiler.cs b/Ghost.RenderGraph.Concept/Unity/NativePassCompiler.cs deleted file mode 100644 index 454d261..0000000 --- a/Ghost.RenderGraph.Concept/Unity/NativePassCompiler.cs +++ /dev/null @@ -1,2220 +0,0 @@ -#if false - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; - -namespace UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler -{ - internal partial class NativePassCompiler : IDisposable - { - internal struct RenderGraphInputInfo - { - public RenderGraphResourceRegistry m_ResourcesForDebugOnly; - public List m_RenderPasses; - public string debugName; - public bool disablePassCulling; - public bool disablePassMerging; - public RenderTextureUVOriginStrategy renderTextureUVOriginStrategy; - } - - internal RenderGraphInputInfo graph; - internal CompilerContextData contextData = null; - internal CompilerContextData defaultContextData; - internal CommandBuffer previousCommandBuffer; - Stack m_HasSideEffectPassIdCullingStack; - List> m_UnusedVersionedResourceIdCullingStacks; - Dictionary> m_DelayedLastUseListPerPassMap; - - RenderGraphCompilationCache m_CompilationCache; - - RenderTargetIdentifier[][] m_TempMRTArrays = null; - - internal const int k_EstimatedPassCount = 100; - internal const int k_MaxSubpass = 8; // Needs to match with RenderPassSetup.h - - NativeList m_BeginRenderPassAttachments; - - // Contains the index of the non culled passes for native render passes that has at least one pass culled. - internal NativeList m_NonCulledPassIndicesForRasterPasses; - - internal static bool s_ForceGenerateAuditsForTests = false; - - public NativePassCompiler(RenderGraphCompilationCache cache) - { - m_CompilationCache = cache; - defaultContextData = new CompilerContextData(); - m_HasSideEffectPassIdCullingStack = new Stack(k_EstimatedPassCount); - - m_UnusedVersionedResourceIdCullingStacks = new List>(); - for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) - m_UnusedVersionedResourceIdCullingStacks.Add(new Stack()); - - m_DelayedLastUseListPerPassMap = new Dictionary>(k_EstimatedPassCount); - for (int passId = 0; passId < k_EstimatedPassCount; ++passId) - m_DelayedLastUseListPerPassMap.Add(passId, new List()); - - m_TempMRTArrays = new RenderTargetIdentifier[RenderGraph.kMaxMRTCount][]; - for (int i = 0; i < RenderGraph.kMaxMRTCount; ++i) - m_TempMRTArrays[i] = new RenderTargetIdentifier[i + 1]; - } - - // IDisposable implementation - - ~NativePassCompiler() => Cleanup(); - - public void Dispose() - { - Cleanup(); - GC.SuppressFinalize(this); - } - - public void Cleanup() - { - // If caching enabled, the two can be different - contextData?.Dispose(); - defaultContextData?.Dispose(); - - if (m_BeginRenderPassAttachments.IsCreated) - { - m_BeginRenderPassAttachments.Dispose(); - } - - if (m_NonCulledPassIndicesForRasterPasses.IsCreated) - { - m_NonCulledPassIndicesForRasterPasses.Dispose(); - } - } - - public bool Initialize(RenderGraphResourceRegistry resources, List renderPasses, RenderGraphDebugParams debugParams, string debugName, bool useCompilationCaching, - int graphHash, int frameIndex, RenderTextureUVOriginStrategy renderTextureUVOriginStrategy) - { - bool cached = false; - if (!useCompilationCaching) - contextData = defaultContextData; - else - cached = m_CompilationCache.GetCompilationCache(graphHash, frameIndex, out contextData); - - graph.m_ResourcesForDebugOnly = resources; - graph.m_RenderPasses = renderPasses; - graph.disablePassCulling = debugParams.disablePassCulling; - graph.disablePassMerging = debugParams.disablePassMerging; - graph.debugName = debugName; - graph.renderTextureUVOriginStrategy = renderTextureUVOriginStrategy; - - Clear(clearContextData: !useCompilationCaching); - - return cached; - } - - void HandleExtendedFeatureFlags() - { - for (int nativePassIndex = 0; nativePassIndex < contextData.nativePassData.Length; nativePassIndex++) - { - int firstNativeSubPass = contextData.nativePassData[nativePassIndex].firstNativeSubPass; - // Does this native pass have any sub passes. - if (firstNativeSubPass >= 0) - { - int firstGraphPass = contextData.nativePassData[nativePassIndex].firstGraphPass; - int graphPassIndex = 0; - for (int nativeSubPassIndex = 0; nativeSubPassIndex < contextData.nativePassData[nativePassIndex].numNativeSubPasses; nativeSubPassIndex++) - { - // Start with the MVPVV compatible flag set so that it can be used for the & operation later - SubPassFlags extendedSubPassFlags = SubPassFlags.MultiviewRenderRegionsCompatible; - // Iterate over all graph passes that got merged into this sub pass - while ((graphPassIndex < contextData.nativePassData[nativePassIndex].numGraphPasses) && (contextData.passData[graphPassIndex + firstGraphPass].nativeSubPassIndex == nativeSubPassIndex)) - { - if (contextData.passData[graphPassIndex + firstGraphPass].extendedFeatureFlags.HasFlag(ExtendedFeatureFlags.TileProperties)) - { - extendedSubPassFlags |= SubPassFlags.TileProperties; - } - // A native sub pass is MultiviewRenderRegionsCompatible only if all of its graph passes are compatible - if (!contextData.passData[graphPassIndex + firstGraphPass].extendedFeatureFlags.HasFlag(ExtendedFeatureFlags.MultiviewRenderRegionsCompatible)) - { - extendedSubPassFlags &= ~SubPassFlags.MultiviewRenderRegionsCompatible; - } - graphPassIndex++; - } - contextData.nativeSubPassData.ElementAt(firstNativeSubPass + nativeSubPassIndex).flags |= extendedSubPassFlags; - } - } - } - } - - public void Compile(RenderGraphResourceRegistry resources) - { - ValidatePasses(); - - SetupContextData(resources); - - BuildGraph(); - - CullUnusedRenderGraphPasses(); - - TryMergeNativePasses(); - - HandleExtendedFeatureFlags(); - - FindResourceUsageRangeAndSynchronization(); - - DetectMemoryLessResources(); - - PrepareNativeRenderPasses(); - - if (graph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation) - PropagateTextureUVOrigin(); - - CompactNonCulledPassesForRasterPasses(); - } - - public void Clear(bool clearContextData) - { - if (clearContextData) - contextData.Clear(); - - m_HasSideEffectPassIdCullingStack.Clear(); - - for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) - m_UnusedVersionedResourceIdCullingStacks[type].Clear(); - - foreach (var resListPerPassId in m_DelayedLastUseListPerPassMap) - resListPerPassId.Value.Clear(); - - m_DelayedLastUseListPerPassMap.Clear(); - } - - void SetPassStatesForNativePass(int nativePassId) - { - NativePassData.SetPassStatesForNativePass(contextData, nativePassId); - } - - internal enum NativeCompilerProfileId - { - NRPRGComp_PrepareNativePass, - NRPRGComp_SetupContextData, - NRPRGComp_BuildGraph, - NRPRGComp_CullNodes, - NRPRGComp_TryMergeNativePasses, - NRPRGComp_FindResourceUsageRanges, - NRPRGComp_DetectMemorylessResources, - NRPRGComp_PropagateTextureUVOrigin, - NRPRGComp_ExecuteInitializeResources, - NRPRGComp_ExecuteBeginRenderpassCommand, - NRPRGComp_ExecuteDestroyResources, - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - void ValidatePasses() - { - if (RenderGraph.enableValidityChecks) - { - int tilePropertiesPassIndex = -1; - for (int passId = 0; passId < graph.m_RenderPasses.Count; passId++) - { - if (graph.m_RenderPasses[passId].extendedFeatureFlags.HasFlag(ExtendedFeatureFlags.TileProperties)) - { - if (tilePropertiesPassIndex > -1) - { - throw new Exception($"ExtendedFeatureFlags.TileProperties can only be set once per render graph (render graph {graph.debugName}, pass {graph.m_RenderPasses[passId].name}), previously set at (pass {graph.m_RenderPasses[tilePropertiesPassIndex].name})."); - } - tilePropertiesPassIndex = passId; - } - } - } - } - - void SetupContextData(RenderGraphResourceRegistry resources) - { - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_SetupContextData))) - { - contextData.Initialize(resources, k_EstimatedPassCount); - } - } - - // Returns true if the RasterFragmentList is successfully set up - bool TrySetupRasterFragmentList(ref PassData ctxPass, ref RenderGraphPass inputPass, out string errorMessage) - { - errorMessage = null; - var ctx = contextData; - // Grab offset in context fragment list to begin building the fragment list - ctxPass.firstFragment = ctx.fragmentData.Length; - - // Depth attachment is always at index 0 - if (inputPass.depthAccess.textureHandle.handle.IsValid()) - { - ctxPass.fragmentInfoHasDepth = true; - - if (ctx.TryAddToFragmentList(inputPass.depthAccess, ctxPass.firstFragment, ctxPass.numFragments, out errorMessage)) - { - ctxPass.TryAddFragment(inputPass.depthAccess.textureHandle.handle, ctx, out errorMessage); - } - - if (errorMessage != null) - { - errorMessage = - $"when trying to add depth attachment of type {inputPass.depthAccess.textureHandle.handle.type} at index {inputPass.depthAccess.textureHandle.handle.index} - {errorMessage}"; - return false; - } - } - - for (var ci = 0; ci < inputPass.colorBufferMaxIndex + 1; ++ci) - { - // Skip unused color slots - if (!inputPass.colorBufferAccess[ci].textureHandle.handle.IsValid()) continue; - - if (ctx.TryAddToFragmentList(inputPass.colorBufferAccess[ci], ctxPass.firstFragment, ctxPass.numFragments, out errorMessage)) - { - ctxPass.TryAddFragment(inputPass.colorBufferAccess[ci].textureHandle.handle, ctx, out errorMessage); - } - - if (errorMessage != null) - { - errorMessage = - $"when trying to add render attachment of type {inputPass.colorBufferAccess[ci].textureHandle.handle.type} at index {inputPass.colorBufferAccess[ci].textureHandle.handle.index} - {errorMessage}"; - return false; - } - } - - // shading rate image - this is a specific type of attachment (more of an image resource that can't be sampled, only used by the rasterizer) - if (inputPass.hasShadingRateImage && inputPass.shadingRateAccess.textureHandle.handle.IsValid()) - { - if (ctx.TryAddToFragmentList(inputPass.shadingRateAccess, ctxPass.firstFragment, ctxPass.numFragments, out errorMessage)) - { - ctxPass.shadingRateImageIndex = ctx.fragmentData.Length - 1; - } - - if (errorMessage != null) - { - errorMessage = - $"when trying to add VRS attachment of type {inputPass.shadingRateAccess.textureHandle.handle.type} at index {inputPass.shadingRateAccess.textureHandle.handle.index} - {errorMessage}"; - return false; - } - } - - // Grab offset in context fragment list to begin building the fragment input list - ctxPass.firstFragmentInput = ctx.fragmentData.Length; - - for (var ci = 0; ci < inputPass.fragmentInputMaxIndex + 1; ++ci) - { - // Skip unused fragment input slots - if (!inputPass.fragmentInputAccess[ci].textureHandle.IsValid()) continue; - - if (ctx.TryAddToFragmentList(inputPass.fragmentInputAccess[ci], ctxPass.firstFragmentInput, ctxPass.numFragmentInputs, out errorMessage)) - { - ctxPass.TryAddFragmentInput(inputPass.fragmentInputAccess[ci].textureHandle.handle, ctx, out errorMessage); - } - - if (errorMessage != null) - { - errorMessage = - $"when trying to add input attachment of type {inputPass.fragmentInputAccess[ci].textureHandle.handle.type} at index {inputPass.fragmentInputAccess[ci].textureHandle.handle.index} - {errorMessage}"; - return false; - } - } - - // Grab offset in context random write list to begin building the per pass random write lists - ctxPass.firstRandomAccessResource = ctx.randomAccessResourceData.Length; - - for (var ci = 0; ci < inputPass.randomAccessResourceMaxIndex + 1; ++ci) - { - ref var uav = ref inputPass.randomAccessResource[ci]; - - // Skip unused random write slots - if (!uav.h.IsValid()) continue; - - if (ctx.TryAddToRandomAccessResourceList(uav.h, ci, uav.preserveCounterValue, ctxPass.firstRandomAccessResource, ctxPass.numRandomAccessResources, out errorMessage)) - { - ctxPass.AddRandomAccessResource(); - } - - if (errorMessage != null) - { - errorMessage = $"when trying to add random access attachment of type {uav.h.type} at index {uav.h.index} - {errorMessage}"; - return false; - } - } - - // This is suspicious, there are frame buffer fetch inputs but nothing is output. We don't allow this for now. - // In theory you could fb-fetch inputs and write something to a uav and output nothing? This needs to be investigated - // so don't allow it for now. - if (ctxPass.numFragments == 0) - { - Debug.Assert(ctxPass.numFragmentInputs == 0); - } - - return true; - } - - void BuildGraph() - { - var ctx = contextData; - List passes = graph.m_RenderPasses; - - // Not clearing data, we will do it right after in the for loop - // This is to prevent unnecessary costly copies of pass struct (128bytes) - ctx.passData.ResizeUninitialized(passes.Count); - - // Build up the context graph and keep track of nodes we encounter that can't be culled - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_BuildGraph))) - { - for (int passId = 0; passId < passes.Count; passId++) - { - var inputPass = passes[passId]; - - // Accessing already existing passData in place in the container through reference to avoid deep copy - // Make sure everything is reset and initialized or we will use obsolete data from previous frame - ref var ctxPass = ref ctx.passData.ElementAt(passId); - ctxPass.ResetAndInitialize(inputPass, passId); - - ctx.passNames.Add(new Name(inputPass.name, true)); - - if (ctxPass.hasSideEffects) - { - m_HasSideEffectPassIdCullingStack.Push(passId); - } - - // Set up the list of fragment attachments for this pass - // Note: This doesn't set up the resource reader/writer list as the fragment attachments - // will also be in the pass read/write lists accordingly - if (ctxPass.type == RenderGraphPassType.Raster) - { - if (!TrySetupRasterFragmentList(ref ctxPass, ref inputPass, out var errorMessage)) - { - throw new Exception($"In pass '{inputPass.name}', {errorMessage}"); - } - } - - // Set up per resource type read/write lists for this pass - ctxPass.firstInput = ctx.inputData.Length; // Grab offset in context input list - ctxPass.firstOutput = ctx.outputData.Length; // Grab offset in context output list - for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) - { - var resourceWrite = inputPass.resourceWriteLists[type]; - var resourceWriteCount = resourceWrite.Count; - for (var i = 0; i < resourceWriteCount; ++i) - { - var resource = resourceWrite[i]; - - // Writing to an imported resource is a side effect so mark the pass if needed - ref var resData = ref ctx.UnversionedResourceData(resource); - if (resData.isImported) - { - if (!ctxPass.hasSideEffects) - { - ctxPass.hasSideEffects = true; - m_HasSideEffectPassIdCullingStack.Push(passId); - } - } - - // Mark this pass as writing to this version of the resource - ctx.resources[resource].SetWritingPass(ctx, resource, passId); - - ctx.outputData.Add(new PassOutputData(resource)); - - ctxPass.numOutputs++; - } - - var resourceRead = inputPass.resourceReadLists[type]; - var resourceReadCount = resourceRead.Count; - for (var i = 0; i < resourceReadCount; ++i) - { - var resource = resourceRead[i]; - - // Mark this pass as reading from this version of the resource - ctx.resources[resource].RegisterReadingPass(ctx, resource, passId, ctxPass.numInputs); - - ctx.inputData.Add(new PassInputData(resource)); - - ctxPass.numInputs++; - } - - var resourceTrans = inputPass.transientResourceList[type]; - var resourceTransCount = resourceTrans.Count; - - for (var i = 0; i < resourceTransCount; ++i) - { - var resource = resourceTrans[i]; - - // Mark this pass as reading from this version of the resource - ctx.resources[resource].RegisterReadingPass(ctx, resource, passId, ctxPass.numInputs); - - ctx.inputData.Add(new PassInputData(resource)); - - ctxPass.numInputs++; - - // Mark this pass as writing to this version of the resource - ctx.resources[resource].SetWritingPass(ctx, resource, passId); - - ctx.outputData.Add(new PassOutputData(resource)); - - ctxPass.numOutputs++; - } - - // For raster passes, we do an extra step to monitor textures sampled in the pass - // It can be a breaking change reason later on when building a native render pass - if (type == (int)RenderGraphResourceType.Texture && ctxPass.type == RenderGraphPassType.Raster) - { - ctxPass.firstSampledOnlyRaster = ctx.sampledData.Length; - foreach (ref readonly var input in ctxPass.Inputs(ctx)) - { - // Check if this input is the shading rate image - if (!ctxPass.IsUsedAsFragment(input.resource, ctx)) - { - ctx.sampledData.Add(input.resource); - ctxPass.numSampledOnlyRaster++; - } - } - } - } - } - } - } - - void CullUnusedRenderGraphPasses() - { - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_CullNodes))) - { - if (graph.disablePassCulling) - return; - - // Must come first - CullRenderGraphPassesWithNoSideEffect(); - - // Second step of the algorithm that comes later - CullRenderGraphPassesWritingOnlyUnusedResources(); - } - } - - void CullRenderGraphPassesWithNoSideEffect() - { - var ctx = contextData; - - // Cull all passes first - ctx.CullAllPasses(true); - - // Flood fill downstream algorithm using BFS, - // starting from the passes with side effects (writting to imported texture, not allowed to be culled, globals modification...) - // to all their dependencies - while (m_HasSideEffectPassIdCullingStack.Count != 0) - { - int passId = m_HasSideEffectPassIdCullingStack.Pop(); - - ref var passData = ref ctx.passData.ElementAt(passId); - - // We already found this node through another dependency chain - if (!passData.culled) continue; - - // Flow upstream from this node - foreach (ref readonly var input in passData.Inputs(ctx)) - { - ref var inputVersionedDataRes = ref ctx.resources[input.resource]; - - if (inputVersionedDataRes.written) - { - m_HasSideEffectPassIdCullingStack.Push(inputVersionedDataRes.writePassId); - } - } - - // We need this node, don't cull it - passData.culled = false; - } - - // Update graph based on freshly culled nodes, remove any connection to them - // We start from the latest passes to the first ones as we might need to decrement the version number of unwritten resources - var numPasses = ctx.passData.Length; - for (int passIndex = numPasses - 1; passIndex >= 0; passIndex--) - { - ref readonly var pass = ref ctx.passData.ElementAt(passIndex); - - // Remove the connections from the list so they won't be visited again - if (pass.culled) - { - pass.DisconnectFromResources(ctx); - } - } - } - - void CullRenderGraphPassesWritingOnlyUnusedResources() - { - var ctx = contextData; - - var numPasses = ctx.passData.Length; - for (int passIndex = 0; passIndex < numPasses; passIndex++) - { - ref var passData = ref ctx.passData.ElementAt(passIndex); - - // Use the generic tag to monitor the number of written resources that are used - passData.tag = passData.numOutputs; - - // Find all resources that are written by a pass but not read at all and add them to the stacks - foreach (ref readonly var output in passData.Outputs(ctx)) - { - ref readonly var outputResource = ref output.resource; - ref var outputVersionedDataRes = ref ctx.resources[outputResource]; - - if (outputVersionedDataRes.numReaders == 0) - m_UnusedVersionedResourceIdCullingStacks[outputResource.iType].Push(outputResource); - } - } - - // Go through each stack of unused resources and try to cull their producer - for (int type = 0; type < (int)RenderGraphResourceType.Count; ++type) - { - var unusedVersionedResourceIdCullingStack = m_UnusedVersionedResourceIdCullingStacks[type]; - - // Goal is to find the producers of the unused resources and culled them if they only write to unused resources - while (unusedVersionedResourceIdCullingStack.Count != 0) - { - var unusedResource = unusedVersionedResourceIdCullingStack.Pop(); - - ref var unusedUnversionedDataRes = ref ctx.resources.unversionedData[type].ElementAt(unusedResource.index); - if (unusedUnversionedDataRes.isImported) continue; // Not always unused as someone can read it outside the graph - - ref var unusedVersionedDataRes = ref ctx.resources[unusedResource]; - ref var producerData = ref ctx.passData.ElementAt(unusedVersionedDataRes.writePassId); - if (producerData.culled) continue; // Producer has been culled already - - // Decrement the number of written resources that are used for this pass - producerData.tag--; - - Debug.Assert(producerData.tag >= 0); - - // Producer is not necessary anymore, as it only writes to unused resources and has no side effects - if (producerData.tag == 0 && !producerData.hasSideEffects) - { - producerData.culled = true; - producerData.DisconnectFromResources(ctx, unusedVersionedResourceIdCullingStack, type); - } - else - { - // Producer is still necessary for now, but if the previous version is only implicitly read by it to write the unused resource - // then we can consider this version useless as well and add it to the stack. - // We purposefully keep the connection between the producer and this resource nevertheless to ensure proper lifetime handling and attachment setup. - // A more optimal approach memory-wise would be to cut the dependency, decrease the latestVersionNumber of the resource and release it earlier - // but we then need to create a transient resource with the right attachment properties and attach it to the non-culled producer or the native render pass setup will be incorrect. - - // We always add written resource to the stack so versionedIndex > 0 - var prevVersionedRes = new ResourceHandle(unusedResource, unusedResource.version - 1); - - bool isImplicitRead = graph.m_RenderPasses[producerData.passId].implicitReadsList.Contains(prevVersionedRes); - - if (isImplicitRead) - { - ref var prevVersionedDataRes = ref ctx.resources[prevVersionedRes]; - - // We add the previous version of the resource to the stack IF no other pass than current producer needs it - if (prevVersionedDataRes.written && prevVersionedDataRes.numReaders == 1) - { - unusedVersionedResourceIdCullingStack.Push(prevVersionedRes); - } - } - } - } - } - } - - void TryMergeNativePasses() - { - var ctx = contextData; - - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_TryMergeNativePasses))) - { - // Try to merge raster passes into the currently active native pass. This will not do pass reordering yet - // so it is simply greedy trying to add the next pass to a currently active one. - // In the future we want to try adding any available (i.e. that has no data dependencies on any future results) future pass - // and allow merging that into the pass thus greedily reordering passes. But reordering requires a lot of API validation - // that ensures rendering behaves accordingly with reordered passes so we don't allow that for now. - - // !!! Compilation caching warning !!! - // Merging of passes is highly dependent on render texture properties. - // When caching the render graph compilation, we hash a subset of those render texture properties to make sure we recompile the graph if needed. - // We only hash a subset for performance reason so if you add logic here that will change the behavior of pass merging, - // make sure that the relevant properties are hashed properly. See RenderGraphPass.ComputeHash() - - int activeNativePassId = -1; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - bool generatePassBreakAudits = RenderGraphDebugSession.hasActiveDebugSession || s_ForceGenerateAuditsForTests; -#endif - int indexSinceLastCulledPass = 0; - bool passWasCulled = false; - bool nonCulledPassIndicesListWasCleared = false; - - for (var passIdx = 0; passIdx < ctx.passData.Length; ++passIdx) - { - ref var passToAdd = ref ctx.passData.ElementAt(passIdx); - - // If the pass has been culled, just ignore it - if (passToAdd.culled) - { - passWasCulled = true; - continue; - } - - // If no active pass, there is nothing to merge... - if (activeNativePassId == -1) - { - //If raster, start a new native pass with the current pass - if (passToAdd.type == RenderGraphPassType.Raster) - { - // Allocate a stand-alone native renderpass based on the current pass - ctx.nativePassData.Add(new NativePassData(ref passToAdd, ctx)); - passToAdd.nativePassIndex = ctx.nativePassData.LastIndex(); - activeNativePassId = passToAdd.nativePassIndex; - - indexSinceLastCulledPass = passIdx; - passWasCulled = false; - } - } - // There is an native pass currently open, try to add the current graph pass to it - else - { - var mergeTestResult = graph.disablePassMerging ? new PassBreakAudit(PassBreakReason.PassMergingDisabled, passIdx) - : NativePassData.TryMerge(contextData, activeNativePassId, passIdx); - - // Merge failed, close current native render pass and create a new one - if (mergeTestResult.reason != PassBreakReason.Merged) - { - SetPassStatesForNativePass(activeNativePassId); -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generatePassBreakAudits) - { - ref var nativePassData = ref contextData.nativePassData.ElementAt(activeNativePassId); - nativePassData.breakAudit = mergeTestResult; - } -#endif - if (mergeTestResult.reason == PassBreakReason.NonRasterPass) - { - // Non-raster pass, no active native pass at all - activeNativePassId = -1; - } - else - { - // Raster but cannot be merged, allocate a new stand-alone native renderpass based on the current pass - ctx.nativePassData.Add(new NativePassData(ref passToAdd, ctx)); - passToAdd.nativePassIndex = ctx.nativePassData.LastIndex(); - activeNativePassId = passToAdd.nativePassIndex; - } - - if (passWasCulled) - { - CollectNonCulledPassIndicesForRasterPasses(passIdx, indexSinceLastCulledPass, mergeTestResult.reason != PassBreakReason.NonRasterPass, !nonCulledPassIndicesListWasCleared); - passWasCulled = false; - nonCulledPassIndicesListWasCleared = true; - } - - indexSinceLastCulledPass = passIdx; - } - } - } - - // Handle the last native pass - if (passWasCulled) - { - CollectNonCulledPassIndicesForRasterPasses(ctx.passData.Length, indexSinceLastCulledPass, clearList: !nonCulledPassIndicesListWasCleared); - nonCulledPassIndicesListWasCleared = true; - } - - // We need to clear this data to avoid reusing stale data from a previously compiled graph. - if (!nonCulledPassIndicesListWasCleared && m_NonCulledPassIndicesForRasterPasses.IsCreated) - { - m_NonCulledPassIndicesForRasterPasses.Clear(); - m_NonCulledPassIndicesForRasterPasses.SetCapacity(ctx.passData.Length); - } - - if (activeNativePassId >= 0) - { - // "Close" the last native pass by marking the last graph pass as end - SetPassStatesForNativePass(activeNativePassId); -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generatePassBreakAudits) - { - ref var nativePassData = ref contextData.nativePassData.ElementAt(activeNativePassId); - nativePassData.breakAudit = new PassBreakAudit(PassBreakReason.EndOfGraph, -1); - } -#endif - } - } - } - - void CollectNonCulledPassIndicesForRasterPasses(int currentPassIdx, int indexSinceLastCulledPass, bool usePreviousNativePass = false, bool clearList = false) - { - var ctx = contextData; - - // In some cases, we create a new native render pass (we allocate a new stand-alone native renderpass based on the current pass) - // but we need to update the data of the previous native pass and not the newly one created. - var indexLastNativePassData = (usePreviousNativePass) ? ctx.nativePassData.LastIndex() - 1 : ctx.nativePassData.LastIndex(); - - // In case we have a graph without any render pass. - if (indexLastNativePassData == -1) - return; - - // Filling the attachments array to be sent to the rendering command buffer - if (!m_NonCulledPassIndicesForRasterPasses.IsCreated) - { - m_NonCulledPassIndicesForRasterPasses = new NativeList(ctx.passData.Length, Allocator.Persistent); - } - else if (clearList) - { - m_NonCulledPassIndicesForRasterPasses.Clear(); - m_NonCulledPassIndicesForRasterPasses.SetCapacity(ctx.passData.Length); - } - - ref var lastNativePassData = ref ctx.nativePassData.ElementAt(indexLastNativePassData); - lastNativePassData.firstCompactedNonCulledRasterPass = m_NonCulledPassIndicesForRasterPasses.Length; - - // The native pass has at least one pass culled, so we iterate over each pass to retrieve - // the index of the non culled pass, so they can be copied later on in a new NativeList that - // will be contiguous in memory. - for (var nonCulledPassIdx = 0; nonCulledPassIdx < currentPassIdx - indexSinceLastCulledPass; ++nonCulledPassIdx) - { - ref var passToCopy = ref ctx.passData.ElementAt(indexSinceLastCulledPass + nonCulledPassIdx); - - if (!passToCopy.culled) - { - m_NonCulledPassIndicesForRasterPasses.Add(indexSinceLastCulledPass + nonCulledPassIdx); - } - } - - lastNativePassData.lastCompactedNonCulledRasterPass = m_NonCulledPassIndicesForRasterPasses.Length - 1; - } - - // Must be called at the end of the compilation, when PassData is not modified anymore. - void CompactNonCulledPassesForRasterPasses() - { - if (!m_NonCulledPassIndicesForRasterPasses.IsCreated || m_NonCulledPassIndicesForRasterPasses.Length == 0) - return; - - var ctx = contextData; - ctx.compactedNonCulledRasterPasses.ResizeUninitialized(m_NonCulledPassIndicesForRasterPasses.Length); - - // Copy and cache only the PassData that were not contiguous in memory because of culling. - // They are copied in a new NativeArray that is contiguous in memory so we avoid further copies - // later at many places (Initialize, Destroy, etc.) by using directly a ReadOnlySpan of this array. - for (int i = 0; i < m_NonCulledPassIndicesForRasterPasses.Length; ++i) - { - ctx.compactedNonCulledRasterPasses[i] = ctx.passData.ElementAt(m_NonCulledPassIndicesForRasterPasses[i]); - } - } - - bool FindFirstPassIdOnGraphicsQueueAwaitingFenceGoingForward(ref PassData startAsyncPass, out int firstPassIdAwaiting) - { - var ctx = contextData; - - Debug.Assert(startAsyncPass.asyncCompute && !startAsyncPass.culled); - - firstPassIdAwaiting = startAsyncPass.awaitingMyGraphicsFencePassId; - - // This async pass has no one waiting for it, try the next async passes - if (firstPassIdAwaiting == -1) - { - var nextPassIndex = startAsyncPass.passId + 1; - var lastPassIndex = ctx.passData.Length - 1; - - // Find the first async pass that is synchronized by the graphics queue - while (firstPassIdAwaiting == -1 && nextPassIndex <= lastPassIndex) - { - ref var nextPass = ref ctx.passData.ElementAt(nextPassIndex); - - if (nextPass.asyncCompute && !nextPass.culled) - firstPassIdAwaiting = nextPass.awaitingMyGraphicsFencePassId; - - nextPassIndex++; - } - - // We didn't find any fence, this should not happen? - if (nextPassIndex > lastPassIndex) - { - // For now we fallback to the last pass of the graph - firstPassIdAwaiting = lastPassIndex; - return false; - } - } - - // Found a pass awaiting a fence - return true; - } - - int FindFirstNonCulledPassIdGoingBackward(int startPassId, bool startPassIsIncluded) - { - var ctx = contextData; - - Debug.Assert(startPassId >= 0 && startPassId < ctx.passData.Length); - - var currPassId = startPassIsIncluded ? startPassId : Math.Max(0, startPassId - 1); - - ref var currPass = ref ctx.passData.ElementAt(currPassId); - - // If this pre pass is culled, fallback to the first previous one not culled - while (currPass.culled && currPassId > 0) - { - currPass = ref ctx.passData.ElementAt(--currPassId); - } - - return currPass.passId; - } - - void FindResourceUsageRangeAndSynchronization() - { - var ctx = contextData; - - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_FindResourceUsageRanges))) - { - // Algorithm is in two steps, traversing the list of passes twice - - // First forward traversal: - // - we find the passes that first use a resource - // - we increase the refcount of the last version for each resource used - // - we find where fences must be added in case of async compute/gfx queues and the related async pass dependencies - for (int passIndex = 0; passIndex < ctx.passData.Length; passIndex++) - { - ref var pass = ref ctx.passData.ElementAt(passIndex); - - if (pass.culled) - continue; - - // In case of an async pass, we need to extend the lifetime of the resource to the first pass on the graphics queue that waits for this async pass to be completed. - // By doing so, we ensure that the resource will not be released back to the pool right after adding the async pass commands to the async queue, - // as it could create a data race condition if a non async pass reuses the resource from the pool while the async pass is still processing it - // As contextData must be filled incrementally per pass, we store temporarily these delayed releases in a list. - // Here we clear this list before using it later in the second foward traversal. - ClearDelayedLastUseListAtPass(passIndex); - - pass.waitOnGraphicsFencePassId = -1; - pass.awaitingMyGraphicsFencePassId = -1; - pass.insertGraphicsFence = false; - - // Loop over all the resources this pass needs (=inputs) - foreach (ref readonly var input in pass.Inputs(ctx)) - { - var inputResource = input.resource; - ref var pointTo = ref ctx.UnversionedResourceData(inputResource); - ref var pointToVer = ref ctx.VersionedResourceData(inputResource); - pointTo.lastUsePassID = -1; - - // If nobody else is using it yet, - // mark this pass as the first using the resource. - // It can happen that two passes use v0, e.g.: - // pass1.UseTex(v0,Read) -> this will clear the pass but keep it at v0 - // pass2.UseTex(v0,Read) -> "reads" v0 - if (pointTo.firstUsePassID < 0) - { - pointTo.firstUsePassID = pass.passId; - pass.AddFirstUse(inputResource, ctx); - } - - // This pass uses the last version of a resource increase the ref count of this resource - var last = pointTo.latestVersionNumber; - if (last == inputResource.version) - { - pointTo.tag++; //Refcount of how many passes are using the last version of a resource - } - - // Verify if this pass needs to wait on a fence due to its inputs - // If no RG pass writes to the resource, no need to wait for anyone - if (pointToVer.written) - { - ref var writingPass = ref ctx.passData.ElementAt(pointToVer.writePassId); - if (writingPass.asyncCompute != pass.asyncCompute) - { - // Find the last pass on the opposite queue that the current pass must wait on - var currWaitForPassId = pass.waitOnGraphicsFencePassId; - pass.waitOnGraphicsFencePassId = Math.Max(writingPass.passId, currWaitForPassId); - } - } - } - - //Also look at outputs (but with version 1) for edge case were we do a Write (but no read) to a texture and the pass is manually excluded from culling - //As it isn't read it won't be in the inputs array with V0 - foreach (ref readonly var output in pass.Outputs(ctx)) - { - var outputResource = output.resource; - ref var pointTo = ref ctx.UnversionedResourceData(outputResource); - ref var pointToVer = ref ctx.VersionedResourceData(outputResource); - - // If nobody else is using it yet (no explicit read), - // Mark this pass as the first using the resource. - // It can happen that two passes use v0, e.g.: - // pass1.UseTex(v0, Write) -> implicit read of v0, writes v1 - culled because none explicitly reads v1 - // pass3.UseTex(v1, Write) -> implicit read of v1, writes v2 - not culled because of unrelated reason - if (pointTo.firstUsePassID < 0) - { - pointTo.firstUsePassID = pass.passId; - pass.AddFirstUse(outputResource, ctx); - } - - // This pass outputs the last version of a resource track that - var last = pointTo.latestVersionNumber; - if (last == outputResource.version) - { - Debug.Assert(pointTo.lastWritePassID == -1); // Only one can be the last writer - pointTo.lastWritePassID = pass.passId; - } - - // Resolve if this pass should insert a fence for its outputs - var numReaders = pointToVer.numReaders; - for (var i = 0; i < numReaders; ++i) - { - var readerIndex = ctx.resources.IndexReader(outputResource, i); - ref var readerData = ref ctx.resources.readerData[outputResource.iType].ElementAt(readerIndex); - ref var readerPass = ref ctx.passData.ElementAt(readerData.passId); - if (pass.asyncCompute != readerPass.asyncCompute) - { - // A subsequent pass on the opposite queue will read this resource written by the current pass, - // so this subsequent pass needs to wait for the completion of the current pass - // to do so, the current pass will insert a fence on its queue after its execution - pass.insertGraphicsFence = true; - - // Different async passes can wait for different resources - // Find the first pass on the opposite queue that will wait for this fence - var currFirstPassId = pass.awaitingMyGraphicsFencePassId; - pass.awaitingMyGraphicsFencePassId = currFirstPassId == -1 ? readerData.passId : Math.Min(currFirstPassId, readerData.passId); - } - } - } - } - - // Second forward traversal: - // - we decrease the refcount to detect which is the last pass using the last version of a resource, i.e when we can release it - // - in case of async processing, we must delay the release to the first pass on gfx queue waiting for a fence - for (int passIndex = 0; passIndex < ctx.passData.Length; passIndex++) - { - ref var pass = ref ctx.passData.ElementAt(passIndex); - - if (pass.culled) - continue; - - bool isAsync = pass.asyncCompute; - - foreach (ref readonly var input in pass.Inputs(ctx)) - { - var inputResource = input.resource; - ref var pointTo = ref ctx.UnversionedResourceData(inputResource); - var last = pointTo.latestVersionNumber; - if (last == inputResource.version) - { - var refC = pointTo.tag - 1; //Decrease refcount this pass is done using it - if (refC == 0) // We're the last pass done using it, this pass should destroy it. - { - if (isAsync) - { - // If no fence found, we fallback to the last non culled pass of the graph on graphics queue, not ideal but safe - bool foundFence = FindFirstPassIdOnGraphicsQueueAwaitingFenceGoingForward(ref pass, out int firstWaitingOrLastPassId); - var delayLastUsedPassId = FindFirstNonCulledPassIdGoingBackward(firstWaitingOrLastPassId, !foundFence); - pointTo.lastUsePassID = delayLastUsedPassId; - AddDelayedLastUseToPass(inputResource, delayLastUsedPassId); - } - else - { - pointTo.lastUsePassID = pass.passId; - pass.AddLastUse(inputResource, ctx); - } - } - - pointTo.tag = refC; - } - } - - // We're outputting a resource that is never used. - // This can happen if this pass has multiple outputs and only a portion of them are used - // as some are used, the whole pass is not culled but the unused output still should be freed - foreach (ref readonly var output in pass.Outputs(ctx)) - { - var outputResource = output.resource; - ref var pointTo = ref ctx.UnversionedResourceData(outputResource); - ref var pointToVer = ref ctx.VersionedResourceData(outputResource); - var last = pointTo.latestVersionNumber; - if (last == outputResource.version && pointToVer.numReaders == 0) - { - if (isAsync) - { - // If no fence found, we fallback to the last non culled pass of the graph, not ideal but safe - bool foundFence = FindFirstPassIdOnGraphicsQueueAwaitingFenceGoingForward(ref pass, out int firstWaitingOrLastPassId); - var delayLastUsedPassId = FindFirstNonCulledPassIdGoingBackward(firstWaitingOrLastPassId, !foundFence); - pointTo.lastUsePassID = delayLastUsedPassId; - AddDelayedLastUseToPass(outputResource, delayLastUsedPassId); - } - else - { - pointTo.lastUsePassID = pass.passId; - pass.AddLastUse(outputResource, ctx); - } - } - } - - // Add any potential delayed resource releases to the contextData - AddLastUseFromDelayedList(ref pass); - } - } - } - - void ClearDelayedLastUseListAtPass(int passId) - { - if (m_DelayedLastUseListPerPassMap.TryGetValue(passId, out var lastUseListForPassId)) - { - lastUseListForPassId.Clear(); - } - } - - void AddDelayedLastUseToPass(in ResourceHandle releaseResource, int passId) - { - if (!m_DelayedLastUseListPerPassMap.TryGetValue(passId, out var lastUseListForPassId)) - { - lastUseListForPassId = new List(); - m_DelayedLastUseListPerPassMap.Add(passId, lastUseListForPassId); - } - - lastUseListForPassId.Add(releaseResource); - } - - public void AddLastUseFromDelayedList(ref PassData passData) - { - if (m_DelayedLastUseListPerPassMap.TryGetValue(passData.passId, out var lastUseListForPassId)) - { - foreach (var resource in lastUseListForPassId) - { - passData.AddLastUse(resource, contextData); - } - - lastUseListForPassId.Clear(); - } - } - - void PrepareNativeRenderPasses() - { - // Prepare all native render pass execution info: - for (var passIdx = 0; passIdx < contextData.nativePassData.Length; ++passIdx) - { - ref var nativePassData = ref contextData.nativePassData.ElementAt(passIdx); - DetermineLoadStoreActions(ref nativePassData); - } - } - - void PropagateTextureUVOrigin() - { - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_PropagateTextureUVOrigin))) - { - // Work backwards through the native pass list and propagate the texture uv origin we store with to - // any texture attachments that are not explicitly known (usually intermediate memoryless attachments). - for (int passIdx = contextData.nativePassData.Length - 1; passIdx >= 0; --passIdx) - { - ref NativePassData nativePassData = ref contextData.nativePassData.ElementAt(passIdx); - - // Find a texture attachment that is storing to find out the orientation for this pass. - int attachmentsCount = nativePassData.attachments.size; - int firstStoreAttachmentIndex = 0; - TextureUVOriginSelection storeUVOrigin = TextureUVOriginSelection.Unknown; - for (int attIdx = 0; attIdx < attachmentsCount; ++attIdx) - { - ref NativePassAttachment nativePassAttachment = ref nativePassData.attachments[attIdx]; - if (nativePassAttachment.storeAction != RenderBufferStoreAction.DontCare) - { - if (nativePassAttachment.handle.type == RenderGraphResourceType.Texture) // Only textures have orientation - { - ref ResourceUnversionedData resData = ref contextData.UnversionedResourceData(nativePassAttachment.handle); - storeUVOrigin = resData.textureUVOrigin; // Inherit the orientation of the store if we are currently storing to an unknown orientation. - firstStoreAttachmentIndex = attIdx; - break; - } - } - } - - // Update any texture attachments with an unknown uv origin to the one we are going to use for storing and validate - // we don't have a mixture of uv origins on the texture attachment list as this would mean something is going to be - // read/written upside down. - for (int attIdx = 0; attIdx < attachmentsCount; ++attIdx) - { - ref NativePassAttachment nativePassAttachment = ref nativePassData.attachments[attIdx]; - - if (nativePassAttachment.handle.type == RenderGraphResourceType.Texture) - { - ref ResourceUnversionedData resData = ref contextData.UnversionedResourceData(nativePassAttachment.handle); - if (storeUVOrigin != TextureUVOriginSelection.Unknown && resData.textureUVOrigin != TextureUVOriginSelection.Unknown && resData.textureUVOrigin != storeUVOrigin) - { - ref NativePassAttachment firstStoreNativePassAttachment = ref nativePassData.attachments[firstStoreAttachmentIndex]; - var firstStoreAttachmentName = graph.m_ResourcesForDebugOnly.GetRenderGraphResourceName(firstStoreNativePassAttachment.handle); - var name = graph.m_ResourcesForDebugOnly.GetRenderGraphResourceName(nativePassAttachment.handle); - - throw new InvalidOperationException($"From pass '{contextData.passNames[nativePassData.firstGraphPass]}' to pass '{contextData.passNames[nativePassData.lastGraphPass]}' when trying to store resource '{name}' of type {nativePassAttachment.handle.type} at index {nativePassAttachment.handle.index} - " - + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOriginStore(firstStoreAttachmentName, storeUVOrigin, name, resData.textureUVOrigin)); - } - - resData.textureUVOrigin = storeUVOrigin; - } - } - } - } - } - - static bool IsGlobalTextureInPass(RenderGraphPass pass, in ResourceHandle handle) - { - foreach (var g in pass.setGlobalsList) - { - if (g.Item1.handle.index == handle.index) - { - return true; - } - } - - return false; - } - - void DetectMemoryLessResources() - { - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_DetectMemorylessResources))) - { - // No need to go further if we don't support memoryless textures - if (!SystemInfo.supportsMemorylessTextures) - return; - - // Native renderpasses and create/destroy lists have now been set-up. Detect memoryless resources, i.e resources that are created/destroyed - // within the scope of an nrp - foreach (ref readonly var nativePass in contextData.NativePasses) - { - // Loop over all created resources by this nrp - for (int passIdx = nativePass.firstGraphPass; passIdx < nativePass.lastGraphPass + 1; ++passIdx) - { - ref var subPass = ref contextData.passData.ElementAt(passIdx); - if (subPass.culled) - continue; - - foreach (ref readonly var createdRes in subPass.FirstUsedResources(contextData)) - { - ref var createInfo = ref contextData.UnversionedResourceData(createdRes); - if (createdRes.type == RenderGraphResourceType.Texture && createInfo.isImported == false) - { - bool isGlobal = IsGlobalTextureInPass(graph.m_RenderPasses[subPass.passId], createdRes); - - // Note: You could think but what if the texture is used as a regular non-frambuffer attachment texture - // surely it can't be memoryless then? - // That is true, but it can never happen as the fact these passes got merged means the textures cannot be used - // as regular textures halfway through a pass. If that were the case they would never have been merged in the first place. - // Except! If the pass consists of a single pass, in that case a texture could be allocated and freed within the single pass - // This is a somewhat degenerate case (e.g. a pass with culling forced off doing a uav write that is never used anywhere) - // But to avoid execution errors we still need to create the resource in this case. - - // Check if it is in the destroy list of any of the subpasses > if yes > memoryless - for (int passIdx2 = nativePass.firstGraphPass; passIdx2 < nativePass.lastGraphPass + 1; ++passIdx2) - { - ref var subPass2 = ref contextData.passData.ElementAt(passIdx2); - if (subPass2.culled) - continue; - - foreach (ref readonly var destroyedRes in subPass2.LastUsedResources(contextData)) - { - ref var destInfo = ref contextData.UnversionedResourceData(destroyedRes); - if (destroyedRes.type == RenderGraphResourceType.Texture && destInfo.isImported == false) - { - if (createdRes.index == destroyedRes.index && !isGlobal) - { - // If a single pass in the native pass we need to check fragment attachment otherwise we're good - // we could always check this in theory but it's an optimization not to check it. - if (nativePass.numNativeSubPasses > 1 || subPass2.IsUsedAsFragment(createdRes, contextData)) - { - createInfo.memoryLess = true; - destInfo.memoryLess = true; - } - } - } - } - } - } - } - } - } - } - } - - internal static bool IsSameNativeSubPass(ref SubPassDescriptor a, ref SubPassDescriptor b) - { - const SubPassFlags k_SubPassMergeIgnoreMask = ~(SubPassFlags.TileProperties | SubPassFlags.MultiviewRenderRegionsCompatible); - // Mask out the flags we can ignore. - SubPassFlags aflags = a.flags & k_SubPassMergeIgnoreMask; - SubPassFlags bflags = b.flags & k_SubPassMergeIgnoreMask; - if (aflags != bflags - || a.colorOutputs.Length != b.colorOutputs.Length - || a.inputs.Length != b.inputs.Length) - { - return false; - } - - for (int i = 0; i < a.colorOutputs.Length; i++) - { - if (a.colorOutputs[i] != b.colorOutputs[i]) - { - return false; - } - } - - for (int i = 0; i < a.inputs.Length; i++) - { - if (a.inputs[i] != b.inputs[i]) - { - return false; - } - } - - return true; - } - - private bool ExecuteInitializeResource(InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, in PassData pass) - { - bool haveGfxCommandsBeenAddedToCmd = false; - - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_ExecuteInitializeResources))) - { - resources.forceManualClearOfResource = true; - - // For raster passes we need to create resources for all the subpasses at the beginning of the native renderpass - if (pass.type == RenderGraphPassType.Raster && pass.nativePassIndex >= 0) - { - if (pass.mergeState == PassMergeState.Begin || pass.mergeState == PassMergeState.None) - { - ref var nativePass = ref contextData.nativePassData.ElementAt(pass.nativePassIndex); - foreach (ref readonly var subPass in nativePass.GraphPasses(contextData)) - { - foreach (ref readonly var res in subPass.FirstUsedResources(contextData)) - { - ref readonly var resInfo = ref contextData.UnversionedResourceData(res); - - bool usedAsFragmentThisPass = subPass.IsUsedAsFragment(res, contextData); - - // This resource is read for the first time as a regular texture and not as a framebuffer attachment - // so if requested we need to explicitly clear it, as loadAction.clear only works on framebuffer attachments - resources.forceManualClearOfResource = !usedAsFragmentThisPass; - - if (!resInfo.isImported) - { - // If the compiler has detected that this resource can be memoryless, - // we need to update the texture descriptor that will be used to create the memoryless RTHandle. - // Memoryless resources are created to allow implicit conversion from TextureHandle to RTHandle. - // Such conversions can happen on users side when manipulating texture handles. - if (resInfo.memoryLess) - { - resources.SetTextureAsMemoryLess(res); - } - - // We create the resources from a pool - // memoryless resources are also created but will not allocate in system memory - haveGfxCommandsBeenAddedToCmd |= resources.CreatePooledResource(rgContext, res.iType, res.index); - } - else // Imported resource - { - if (resInfo.clear && !resInfo.memoryLess && resources.forceManualClearOfResource) - { - haveGfxCommandsBeenAddedToCmd |= resources.ClearResource(rgContext, res.iType, res.index); - } - } - } - } - } - } - // Other passes just create them at the beginning of the individual pass - else - { - foreach (ref readonly var create in pass.FirstUsedResources(contextData)) - { - ref readonly var pointTo = ref contextData.UnversionedResourceData(create); - if (!pointTo.isImported) - { - haveGfxCommandsBeenAddedToCmd |= resources.CreatePooledResource(rgContext, create.iType, create.index); - } - else // Imported resource - { - if (pointTo.clear) - { - haveGfxCommandsBeenAddedToCmd |= resources.ClearResource(rgContext, create.iType, create.index); - } - } - } - } - - resources.forceManualClearOfResource = true; - } - - return haveGfxCommandsBeenAddedToCmd; - } - -#if UNITY_EDITOR || DEVELOPMENT_BUILD - static LoadAudit s_EmptyLoadAudit = new LoadAudit(LoadReason.InvalidReason); - static StoreAudit s_EmptyStoreAudit = new StoreAudit(StoreReason.InvalidReason); -#endif - - void DetermineLoadStoreActions(ref NativePassData nativePass) - { - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_PrepareNativePass))) - { - ref readonly var firstGraphPass = ref contextData.passData.ElementAt(nativePass.firstGraphPass); - ref readonly var lastGraphPass = ref contextData.passData.ElementAt(nativePass.lastGraphPass); - - // Some passes don't do any rendering only state changes so just skip them - // If these passes trigger any drawing the raster command buffer will warn users no render targets are set-up for their rendering - if (nativePass.fragments.size <= 0) - return; - - // Some sanity checks, these should not happen - Debug.Assert(firstGraphPass.mergeState is PassMergeState.Begin or PassMergeState.None); - Debug.Assert(lastGraphPass.mergeState is PassMergeState.End or PassMergeState.None); - - ref readonly var fragmentList = ref nativePass.fragments; - - // determine load store actions - // This pass also contains the latest versions used within this pass - // As we have no pass reordering for now the merged passes are always a consecutive list and we can simply do a range - // check on the create/destroy passid to see if it's allocated/freed in this native renderpass -#if UNITY_EDITOR || DEVELOPMENT_BUILD - bool generateAudits = RenderGraphDebugSession.hasActiveDebugSession || s_ForceGenerateAuditsForTests; - ref var currLoadAudit = ref s_EmptyLoadAudit; - ref var currStoreAudit = ref s_EmptyStoreAudit; -#endif - - for (int fragmentId = 0; fragmentId < fragmentList.size; ++fragmentId) - { - ref readonly var fragment = ref fragmentList[fragmentId]; - - // Default values - ResourceHandle handle = fragment.resource; - bool memoryless = false; - int mipLevel = fragment.mipLevel; - int depthSlice = fragment.depthSlice; - // Don't care by default - RenderBufferLoadAction loadAction = RenderBufferLoadAction.DontCare; - RenderBufferStoreAction storeAction = RenderBufferStoreAction.DontCare; - -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - { - nativePass.loadAudit.Add(new LoadAudit(LoadReason.FullyRewritten)); - currLoadAudit = ref nativePass.loadAudit[nativePass.loadAudit.size - 1]; // Get the last added element - - nativePass.storeAudit.Add(new StoreAudit(StoreReason.DiscardUnused)); - currStoreAudit = ref nativePass.storeAudit[nativePass.storeAudit.size - 1]; // Similarly for storeAudit - } -#endif - - // Writing by-default has to preserve the contents, think rendering only a few small triangles on top of a big framebuffer - // So it means we need to load/clear contents potentially. - // If a user pass knows it will write all pixels in a buffer (like a blit) it can use the WriteAll/Discard usage to indicate this to the graph - bool partialWrite = fragment.accessFlags.HasFlag(AccessFlags.Write) - && !fragment.accessFlags.HasFlag(AccessFlags.Discard); - - ref readonly var resourceData = ref contextData.UnversionedResourceData(fragment.resource); - bool isImported = resourceData.isImported; - - int destroyPassID = resourceData.lastUsePassID; - bool usedAfterThisNativePass = (destroyPassID >= (nativePass.lastGraphPass + 1)); - - // Read or partial-write logic - if (fragment.accessFlags.HasFlag(AccessFlags.Read) || partialWrite) - { - // The resource is already allocated before this pass so we need to load it - if (resourceData.firstUsePassID < nativePass.firstGraphPass) - { - loadAction = RenderBufferLoadAction.Load; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currLoadAudit = new LoadAudit(LoadReason.LoadPreviouslyWritten, resourceData.firstUsePassID); -#endif - // Once we decide to load a resource, we must default to the Store action if the resource is used after the current native pass. - // If we were to use the DontCare action in this case, the driver would be effectively be allowed to discard the - // contents of the resource. This is true even when we're only performing reads on it. - if (usedAfterThisNativePass) - { - storeAction = RenderBufferStoreAction.Store; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit(StoreReason.StoreUsedByLaterPass, destroyPassID); -#endif - } - } - // It's first used this native pass so we need to clear it so reads/partial writes return the correct clear value - // the clear colors are part of the resource description and set-up when executing the graph we don't need to care about that here. - else - { - if (isImported) - { - // Check if the user indicated he wanted clearing of his imported resource on it's first use by the graph - if (resourceData.clear) - { - loadAction = RenderBufferLoadAction.Clear; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currLoadAudit = new LoadAudit(LoadReason.ClearImported); -#endif - } - else - { - loadAction = RenderBufferLoadAction.Load; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currLoadAudit = new LoadAudit(LoadReason.LoadImported); -#endif - } - } - else - { - // Created by the graph internally clear on first read - loadAction = RenderBufferLoadAction.Clear; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currLoadAudit = new LoadAudit(LoadReason.ClearCreated); -#endif - } - } - } - - // Write logic - if (fragment.accessFlags.HasFlag(AccessFlags.Write)) - { - // Simple non-msaa case - if (nativePass.samples <= 1) - { - if (usedAfterThisNativePass) - { - // The resource is still used after this native pass so we need to store it. - storeAction = RenderBufferStoreAction.Store; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit(StoreReason.StoreUsedByLaterPass, destroyPassID); -#endif - } - else - { - // This is the last native pass that uses the resource. - // If it's imported, we store it because its contents may be used outside the graph. - // Otherwise, we can safely discard its contents. - // - // The one exception to this, is the user declared discard flag which allows us to assume an imported - // resource is not used outside the graph. - if (isImported) - { - if (resourceData.discard) - { - storeAction = RenderBufferStoreAction.DontCare; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit(StoreReason.DiscardImported); -#endif - } - else - { - storeAction = RenderBufferStoreAction.Store; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit(StoreReason.StoreImported); -#endif - } - } - else - { - storeAction = RenderBufferStoreAction.DontCare; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit(StoreReason.DiscardUnused); -#endif - } - } - } - // Complex msaa case - else - { - // The resource is still used after this native pass so we need to store it. - // as we don't know what happens with them and assume the contents are somewhow used outside the graph - // With MSAA we may access the resolved data for longer than the MSAA data so we track the destroyPass and lastPassThatNeedsUnresolved separately - // In theory the opposite could also be true (use MSAA after resolve data is no longer needed) but we consider it sufficiently strange to not - // consider it here. - storeAction = RenderBufferStoreAction.DontCare; - //Check if we're the last pass writing it by checking the output version of the current pass is the higherst version the resource will reach - bool lastWriter = (resourceData.latestVersionNumber == fragment.resource.version); - // Cheaper but same? = resourceData.lastWritePassID >= pass.firstGraphPass && resourceData.lastWritePassID < pass.firstGraphPass + pass.numSubPasses; - bool isImportedLastWriter = isImported && lastWriter; - - // Used outside this native render pass, we need to store something - if (destroyPassID >= nativePass.firstGraphPass + nativePass.numGraphPasses) - { - // Assume nothing is needed unless we are an imported texture (which doesn't require discarding) and we're the last ones writing it - bool needsMSAASamples = isImportedLastWriter && !resourceData.discard; - bool needsResolvedData = isImportedLastWriter && (resourceData.bindMS == false); - int userPassID = 0; - int msaaUserPassID = 0; - - // Check if we need msaa/resolved data by checking all the passes using this buffer - // Partial writes will register themselves as readers so this should be adequate - foreach (ref readonly var reader in contextData.Readers(fragment.resource)) - { - ref var readerPass = ref contextData.passData.ElementAt(reader.passId); - bool isFragmentUsed = readerPass.IsUsedAsFragment(fragment.resource, contextData); - - // Unsafe pass - we cannot know how it is used, so we need to both store and resolve - if (readerPass.type == RenderGraphPassType.Unsafe) - { - needsMSAASamples = true; - needsResolvedData = !resourceData.bindMS; - msaaUserPassID = reader.passId; - userPassID = reader.passId; - break; - } - // A fragment attachment use we need the msaa samples - if (isFragmentUsed) - { - needsMSAASamples = true; - msaaUserPassID = reader.passId; - } - else - { - // Used as a multisample-texture we need the msaa samples - if (resourceData.bindMS) - { - needsMSAASamples = true; - msaaUserPassID = reader.passId; - } - // Used as a regular non-multisample texture we need resolved data - else - { - needsResolvedData = true; - userPassID = reader.passId; - } - } - } - - if (needsMSAASamples && needsResolvedData) - { - storeAction = RenderBufferStoreAction.StoreAndResolve; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit( - (isImportedLastWriter ? StoreReason.StoreImported : StoreReason.StoreUsedByLaterPass), - userPassID, - (isImportedLastWriter ? StoreReason.StoreImported : StoreReason.StoreUsedByLaterPass), - msaaUserPassID); -#endif - } - else if (needsResolvedData) - { - storeAction = RenderBufferStoreAction.Resolve; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit( - (isImportedLastWriter ? StoreReason.StoreImported : StoreReason.StoreUsedByLaterPass), - userPassID, - StoreReason.DiscardUnused); -#endif - } - else if (needsMSAASamples) - { - storeAction = RenderBufferStoreAction.Store; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit( - (resourceData.bindMS ? StoreReason.DiscardBindMs : StoreReason.DiscardUnused), - -1, - (isImportedLastWriter ? StoreReason.StoreImported : StoreReason.StoreUsedByLaterPass), - msaaUserPassID); -#endif - } - else - { - Debug.Assert(false, "Resource was not destroyed but nobody seems to be using it?!"); - } - } - else if (isImportedLastWriter) - { - //It's an imported texture and we're the last ones writing it make sure to store the results - - // Used as a multisample-texture, we need the msaa samples only - if (resourceData.bindMS) - { - if (resourceData.discard) - { - storeAction = RenderBufferStoreAction.DontCare; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit(StoreReason.DiscardImported); -#endif - } - else - { - storeAction = RenderBufferStoreAction.Store; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit( - StoreReason.DiscardBindMs, -1, StoreReason.StoreImported); -#endif - } - } - // Used as a regular non-multisample texture, we need samples as resolved data - // we have no idea which one of them will be needed by the external users - else - { - if (resourceData.discard) - { - // Depth attachment always comes first if existing - bool isDepthAttachment = (nativePass.hasDepth && nativePass.attachments.size == 0); - - // For color attachment, we only discard the MSAA buffers and keep the resolve texture - // This is a design decision due to the restrictive ImportResourceParams API, it could be revised later - storeAction = isDepthAttachment - ? RenderBufferStoreAction.DontCare - : RenderBufferStoreAction.Resolve; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit( - StoreReason.DiscardImported, -1, StoreReason.DiscardImported); -#endif - } - else - { - storeAction = RenderBufferStoreAction.StoreAndResolve; -#if UNITY_EDITOR || DEVELOPMENT_BUILD - if (generateAudits) - currStoreAudit = new StoreAudit( - StoreReason.StoreImported, -1, StoreReason.StoreImported); -#endif - } - } - } - } - } - - if (resourceData.memoryLess) - { - memoryless = true; - -#if UNITY_EDITOR || DEVELOPMENT_BUILD - // Ensure load/store actions are actually valid for memory less - if (loadAction == RenderBufferLoadAction.Load) - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_LoadingMemorylessResource); - if (storeAction != RenderBufferStoreAction.DontCare) - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_ResolvignMemorylessResource); -#endif - } - - var newAttachment = new NativePassAttachment( - handle, - loadAction, - storeAction, - memoryless, - mipLevel, - depthSlice - ); - - nativePass.attachments.Add(newAttachment); - } - } - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - private void ValidateNativePass(in NativePassData nativePass, int width, int height, int depth, int samples, int attachmentCount) - { - if (RenderGraph.enableValidityChecks) - { - if (nativePass.attachments.size == 0 || nativePass.numNativeSubPasses == 0) - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_RenderPassIsEmpty); - - if (width == 0 || height == 0 || depth == 0 || samples == 0 || nativePass.numNativeSubPasses == 0 || attachmentCount == 0) - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_RenderPassHasInvalidProperties); - } - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - private void ValidateAttachment(in RenderTargetInfo attRenderTargetInfo, RenderGraphResourceRegistry resources, int nativePassWidth, int nativePassHeight, int nativePassMSAASamples, - bool isVrs, bool isShaderResolve) - { - if (RenderGraph.enableValidityChecks) - { - if (isVrs) - { - var tileSize = ShadingRateImage.GetAllocTileSize(nativePassWidth, nativePassHeight); - - if (attRenderTargetInfo.width != tileSize.x || attRenderTargetInfo.height != tileSize.y || attRenderTargetInfo.msaaSamples != 1) - { - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_ShadingRateImageAttachmentDoesNotMatch); - } - } - else - { - if (attRenderTargetInfo.width != nativePassWidth || attRenderTargetInfo.height != nativePassHeight || (attRenderTargetInfo.msaaSamples != nativePassMSAASamples && !isShaderResolve)) - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_AttachmentsDoNotMatch); - } - } - } - - internal unsafe void ExecuteBeginRenderPass(InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, ref NativePassData nativePass) - { - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_ExecuteBeginRenderpassCommand))) - { - ref var attachments = ref nativePass.attachments; - var attachmentCount = attachments.size; - - var width = nativePass.width; - var height = nativePass.height; - var volumeDepth = nativePass.volumeDepth; - var samples = nativePass.samples; - var isShaderResolve = nativePass.extendedFeatureFlags.HasFlag(ExtendedFeatureFlags.MultisampledShaderResolve); - ValidateNativePass(nativePass, width, height, volumeDepth, samples, attachmentCount); - - ref var nativeSubPasses = ref contextData.nativeSubPassData; - NativeArray nativeSubPassArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(nativeSubPasses.GetUnsafeReadOnlyPtr() + nativePass.firstNativeSubPass, nativePass.numNativeSubPasses, Allocator.None); - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - var safetyHandle = AtomicSafetyHandle.Create(); - AtomicSafetyHandle.SetAllowReadOrWriteAccess(safetyHandle, true); - NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref nativeSubPassArray, safetyHandle); -#endif - - if (nativePass.hasFoveatedRasterization) - { - rgContext.cmd.SetFoveatedRenderingMode(FoveatedRenderingMode.Enabled); - } - - if (nativePass.hasShadingRateStates) - { - rgContext.cmd.SetShadingRateFragmentSize(nativePass.shadingRateFragmentSize); - rgContext.cmd.SetShadingRateCombiner(ShadingRateCombinerStage.Primitive, nativePass.primitiveShadingRateCombiner); - rgContext.cmd.SetShadingRateCombiner(ShadingRateCombinerStage.Fragment, nativePass.fragmentShadingRateCombiner); - } - - // Filling the attachments array to be sent to the rendering command buffer - if(!m_BeginRenderPassAttachments.IsCreated) - m_BeginRenderPassAttachments = new NativeList(FixedAttachmentArray.MaxAttachments, Allocator.Persistent); - - m_BeginRenderPassAttachments.Resize(attachmentCount, NativeArrayOptions.UninitializedMemory); - for (var i = 0; i < attachmentCount; ++i) - { - ref readonly var currAttachmentHandle = ref attachments[i].handle; - - resources.GetRenderTargetInfo(currAttachmentHandle, out var renderTargetInfo); - - bool isVrs = (i == nativePass.shadingRateImageIndex); - ValidateAttachment(renderTargetInfo, resources, width, height, samples, isVrs, isShaderResolve); - - ref var currBeginAttachment = ref m_BeginRenderPassAttachments.ElementAt(i); - currBeginAttachment = new AttachmentDescriptor(renderTargetInfo.format); - - // Set up the RT pointers - var rtHandle = resources.GetTexture(currAttachmentHandle.index); - - //HACK: Always set the loadstore target even if StoreAction == DontCare or Resolve - //and LoadAction == Clear or DontCare - //in these cases you could argue setting the loadStoreTarget to NULL and only set the resolveTarget - //but this confuses the backend (on vulkan) and in general is not how the lower level APIs tend to work. - //because of the RenderTexture duality where we always bundle store+resolve targets as one RTex - //it does become impossible to have a memoryless loadStore texture with a memoryfull resolve - //but that is why we mark this as a hack and future work to fix. - //The proper (and planned) solution would be to move away from the render texture duality. - RenderTargetIdentifier rtidAllSlices = rtHandle; - currBeginAttachment.loadStoreTarget = new RenderTargetIdentifier(rtidAllSlices, attachments[i].mipLevel, CubemapFace.Unknown, attachments[i].depthSlice); - - if (attachments[i].storeAction == RenderBufferStoreAction.Resolve || - attachments[i].storeAction == RenderBufferStoreAction.StoreAndResolve) - { - currBeginAttachment.resolveTarget = rtHandle; - } - - currBeginAttachment.loadAction = attachments[i].loadAction; - currBeginAttachment.storeAction = attachments[i].storeAction; - - // Set up clear colors if we have a clear load action - if (attachments[i].loadAction == RenderBufferLoadAction.Clear) - { - currBeginAttachment.clearColor = Color.red; - currBeginAttachment.clearDepth = 1.0f; - currBeginAttachment.clearStencil = 0; - ref readonly var desc = ref resources.GetTextureResourceDesc(currAttachmentHandle, true); - if (i == 0 && nativePass.hasDepth) - { - // TODO: There seems to be no clear depth specified ?!?! - currBeginAttachment.clearDepth = 1.0f; // desc.clearDepth; - } - else - { - currBeginAttachment.clearColor = desc.clearColor; - } - } - } - - if (nativePass.extendedFeatureFlags.HasFlag(ExtendedFeatureFlags.MultisampledShaderResolve)) - { - var lastSubpass = nativeSubPassArray[^1]; - - // All input attachments must be memoryless for the shader resolve enabled subpass. - for (int i = 0; i < lastSubpass.inputs.Length; i++) - { - int inputIndex = lastSubpass.inputs[i]; - ref var inputAttachment = ref m_BeginRenderPassAttachments.ElementAt(inputIndex); - if (inputAttachment.storeAction != RenderBufferStoreAction.DontCare) - { - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_MultisampledShaderResolveInputAttachmentNotMemoryless); - } - } - - // The last subpass in a native pass with shader resolve is required to be the subpass that handles the resolve, and this subpass can only have 1 color attachment. - if (lastSubpass.colorOutputs.Length != 1) - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_MultisampledShaderResolveInvalidAttachmentSetup); - - if (SystemInfo.supportsMultisampledShaderResolve) - { - int attachmentIndex = lastSubpass.colorOutputs[0]; - - ref var currBeginAttachment = ref m_BeginRenderPassAttachments.ElementAt(attachmentIndex); - currBeginAttachment.resolveTarget = currBeginAttachment.loadStoreTarget; - currBeginAttachment.loadStoreTarget = new RenderTargetIdentifier(BuiltinRenderTextureType.None); - currBeginAttachment.storeAction = RenderBufferStoreAction.Store; - } - } - - NativeArray attachmentDescArray = m_BeginRenderPassAttachments.AsArray(); - - var depthAttachmentIndex = nativePass.hasDepth ? 0 : -1; - - var graphPassNamesForDebugSpan = ReadOnlySpan.Empty; -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (RenderGraph.enableValidityChecks) - { - graphPassNamesForDebug.Clear(); - - nativePass.GetGraphPassNames(contextData, graphPassNamesForDebug); - - int utf8CStrDebugNameLength = 0; - foreach (ref readonly Name graphPassName in graphPassNamesForDebug) - { - utf8CStrDebugNameLength += graphPassName.utf8ByteCount + 1; // +1 to add '/' between passes or the null terminator at the end - } - - var nameBytes = stackalloc byte[utf8CStrDebugNameLength]; - if (utf8CStrDebugNameLength > 0) - { - int startStr = 0; - foreach (ref readonly var graphPassName in graphPassNamesForDebug) - { - int strByteCount = graphPassName.utf8ByteCount; - System.Text.Encoding.UTF8.GetBytes(graphPassName.name.AsSpan(), new Span(nameBytes + startStr, strByteCount)); - startStr += strByteCount; - // Adding '/' in UTF8 - nameBytes[startStr++] = (byte)(0x2F); - } - - // Rewriting last '/' to be the null terminator - nameBytes[utf8CStrDebugNameLength - 1] = (byte)0; - } - - graphPassNamesForDebugSpan = new ReadOnlySpan(nameBytes, utf8CStrDebugNameLength); - } -#endif - - rgContext.cmd.BeginRenderPass(width, height, volumeDepth, samples, attachmentDescArray, depthAttachmentIndex, nativePass.shadingRateImageIndex, nativeSubPassArray, graphPassNamesForDebugSpan); - -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AtomicSafetyHandle.Release(safetyHandle); -#endif - CommandBuffer.ThrowOnSetRenderTarget = true; - } - } - - const int ArbitraryMaxNbMergedPasses = 16; - DynamicArray graphPassNamesForDebug = new DynamicArray(ArbitraryMaxNbMergedPasses); - - private void ExecuteDestroyResource(InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, ref PassData pass) - { - using (new ProfilingScope(ProfilingSampler.Get(NativeCompilerProfileId.NRPRGComp_ExecuteDestroyResources))) - { - // Unsafe pass might soon use temporary render targets, - // users can also use temporary data in their render graph execute nodes using public RenderGraphObjectPool API - // In both cases, we need to release these resources after the node execution - rgContext.renderGraphPool.ReleaseAllTempAlloc(); - - if (pass.type == RenderGraphPassType.Raster && pass.nativePassIndex >= 0) - { - // For raster passes we need to destroy resources after all the subpasses at the end of the native renderpass - if (pass.mergeState == PassMergeState.End || pass.mergeState == PassMergeState.None) - { - ref var nativePass = ref contextData.nativePassData.ElementAt(pass.nativePassIndex); - foreach (ref readonly var subPass in nativePass.GraphPasses(contextData)) - { - foreach (ref readonly var res in subPass.LastUsedResources(contextData)) - { - ref readonly var resInfo = ref contextData.UnversionedResourceData(res); - if (resInfo.isImported == false) - { - resources.ReleasePooledResource(rgContext, res.iType, res.index); - } - } - } - } - } - else - { - foreach (ref readonly var destroy in pass.LastUsedResources(contextData)) - { - ref readonly var pointTo = ref contextData.UnversionedResourceData(destroy); - if (pointTo.isImported == false) - { - resources.ReleasePooledResource(rgContext, destroy.iType, destroy.index); - } - } - } - } - } - - private void ExecuteSetRenderTargets(RenderGraphPass pass, InternalRenderGraphContext rgContext) - { - var depthBufferIsValid = pass.depthAccess.textureHandle.IsValid(); - if (depthBufferIsValid || pass.colorBufferMaxIndex != -1) - { - var resources = graph.m_ResourcesForDebugOnly; - var colorBufferAccess = pass.colorBufferAccess; - if (pass.colorBufferMaxIndex > 0) - { - var mrtArray = m_TempMRTArrays[pass.colorBufferMaxIndex]; - - for (int i = 0; i <= pass.colorBufferMaxIndex; ++i) - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (!colorBufferAccess[i].textureHandle.IsValid()) - throw new InvalidOperationException($"In pass {pass.name}, when trying to use {colorBufferAccess[i].textureHandle.handle.type} attachment at index {colorBufferAccess[i].textureHandle.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_InvalidMRTSetup); -#endif - mrtArray[i] = resources.GetTexture(colorBufferAccess[i].textureHandle); - } - - if (depthBufferIsValid) - { - CoreUtils.SetRenderTarget(rgContext.cmd, mrtArray, resources.GetTexture(pass.depthAccess.textureHandle)); - } - else - { - throw new InvalidOperationException($"In pass {pass.name} - " + RenderGraph.RenderGraphExceptionMessages.k_NoDepthBufferMRT); - } - } - else - { - if (depthBufferIsValid) - { - if (pass.colorBufferMaxIndex > -1) - { - CoreUtils.SetRenderTarget(rgContext.cmd, resources.GetTexture(pass.colorBufferAccess[0].textureHandle), - resources.GetTexture(pass.depthAccess.textureHandle)); - } - else - { - CoreUtils.SetRenderTarget(rgContext.cmd, resources.GetTexture(pass.depthAccess.textureHandle)); - } - } - else - { - if (pass.colorBufferAccess[0].textureHandle.IsValid()) - { - CoreUtils.SetRenderTarget(rgContext.cmd, resources.GetTexture(pass.colorBufferAccess[0].textureHandle)); - } - else - throw new InvalidOperationException($"In pass {pass.name} - " + RenderGraph.RenderGraphExceptionMessages.k_InvalidDepthAndColorTargets); - } - } - } - } - - internal unsafe void ExecuteSetRandomWriteTarget(in CommandBuffer cmd, RenderGraphResourceRegistry resources, int index, in ResourceHandle resource, bool preserveCounterValue = true) - { - if (resource.type == RenderGraphResourceType.Texture) - { - var tex = resources.GetTexture(resource.index); - cmd.SetRandomWriteTarget(index, tex); - } - else if (resource.type == RenderGraphResourceType.Buffer) - { - var buff = resources.GetBuffer(resource.index); - // Default is to preserve the value - if (preserveCounterValue) - { - cmd.SetRandomWriteTarget(index, buff); - } - else - { - cmd.SetRandomWriteTarget(index, buff, false); - } - } - else - { - var name = resources.GetRenderGraphResourceName(resource); - throw new Exception($"When trying to use resource '{name}' of type {resource.type} - " + RenderGraph.RenderGraphExceptionMessages.k_InvalidResourceType); - } - } - - internal void ExecuteRenderGraphPass(ref InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, RenderGraphPass pass) - { - - rgContext.executingPass = pass; - - if (!pass.HasRenderFunc()) - { - throw new InvalidOperationException($"In pass {pass.name} - " + - RenderGraph.RenderGraphExceptionMessages.k_NoRenderFunction); - } - - using (new ProfilingScope(rgContext.cmd, pass.customSampler)) - { - pass.Execute(rgContext); - - foreach (var tex in pass.setGlobalsList) - { - rgContext.cmd.SetGlobalTexture(tex.Item2, tex.Item1); - } - } - } - - public void ExecuteGraph(InternalRenderGraphContext rgContext, RenderGraphResourceRegistry resources, in List passes) - { - bool inRenderPass = false; - previousCommandBuffer = rgContext.cmd; - - // Having random access targets bound leads to all sorts of weird behavior so we clear them before executing the graph. - rgContext.cmd.ClearRandomWriteTargets(); - - for (int passIndex = 0; passIndex < contextData.passData.Length; passIndex++) - { - ref var passData = ref contextData.passData.ElementAt(passIndex); - if (passData.culled) - continue; - - bool nrpBegan = false; - bool haveGfxCommandsBeenAddedToCmdDuringResInit = ExecuteInitializeResource(rgContext, resources, passData); - - if (passData.type == RenderGraphPassType.Compute && passData.asyncCompute) - { - GraphicsFence previousFence = new GraphicsFence(); - // We add a fence to the gfx cmd if the async compute cmd needs to wait for some resources to be cleared - if (haveGfxCommandsBeenAddedToCmdDuringResInit) - { - previousFence = rgContext.cmd.CreateGraphicsFence(GraphicsFenceType.AsyncQueueSynchronisation, SynchronisationStageFlags.AllGPUOperations); - } - - if (!rgContext.contextlessTesting) - rgContext.renderContext.ExecuteCommandBuffer(rgContext.cmd); - rgContext.cmd.Clear(); - - var asyncCmd = CommandBufferPool.Get("async cmd"); - asyncCmd.SetExecutionFlags(CommandBufferExecutionFlags.AsyncCompute); - rgContext.cmd = asyncCmd; - - if (haveGfxCommandsBeenAddedToCmdDuringResInit) - { - rgContext.cmd.WaitOnAsyncGraphicsFence(previousFence, SynchronisationStageFlags.PixelProcessing); - } - } - - // also make sure to insert fence=waits for multiple queue syncs - if (passData.waitOnGraphicsFencePassId != -1) - { - rgContext.cmd.WaitOnAsyncGraphicsFence(contextData.fences[passData.waitOnGraphicsFencePassId], SynchronisationStageFlags.PixelProcessing); - } - - if (passData.type == RenderGraphPassType.Raster && passData.mergeState <= PassMergeState.Begin) - { - if (passData.nativePassIndex >= 0) - { - ref var nativePass = ref contextData.nativePassData.ElementAt(passData.nativePassIndex); - if (nativePass.fragments.size > 0) - { - ExecuteBeginRenderPass(rgContext, resources, ref nativePass); - nrpBegan = true; - inRenderPass = true; - } - } - } - else if (passData.type == RenderGraphPassType.Unsafe) - { - ExecuteSetRenderTargets(passes[passIndex], rgContext); - } - - if (passData.mergeState >= PassMergeState.SubPass) - { - if (passData.beginNativeSubpass) - { - if (!inRenderPass) - { - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_BeginNoActivePass); - } - - rgContext.cmd.NextSubPass(); - } - } - - if (passData.numRandomAccessResources > 0) - { - foreach (ref readonly var randomWriteAttachment in passData.RandomWriteTextures(contextData)) - { - ExecuteSetRandomWriteTarget(rgContext.cmd, resources, randomWriteAttachment.index, - randomWriteAttachment.resource); - } - } - - ExecuteRenderGraphPass(ref rgContext, resources, passes[passData.passId]); - EndRenderGraphPass(ref rgContext, ref passData, ref inRenderPass, resources, nrpBegan); - } - } - - void EndRenderGraphPass(ref InternalRenderGraphContext rgContext, ref PassData passData, - ref bool inRenderPass, RenderGraphResourceRegistry resources, bool nrpBegan) - { - // If we set any uavs clear them again so they are local to the pass - if (passData.numRandomAccessResources > 0) - { - rgContext.cmd.ClearRandomWriteTargets(); - } - - // should we insert a fence to sync between difference queues? - if (passData.insertGraphicsFence) - { - var fence = rgContext.cmd.CreateAsyncGraphicsFence(); - contextData.fences[passData.passId] = fence; - } - - if (passData.type == RenderGraphPassType.Raster) - { - var hasRenderPassEnded = (passData.mergeState == PassMergeState.None && nrpBegan) - || passData.mergeState == PassMergeState.End; - - if (hasRenderPassEnded) - { - if (passData.nativePassIndex >= 0) - { - ref var nativePass = ref contextData.nativePassData.ElementAt(passData.nativePassIndex); - if (nativePass.fragments.size > 0) - { - if (!inRenderPass) - { - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_NoActivePassForSubpass); - } - - if (nativePass.hasFoveatedRasterization) - { - rgContext.cmd.SetFoveatedRenderingMode(FoveatedRenderingMode.Disabled); - } - - rgContext.cmd.EndRenderPass(); - CommandBuffer.ThrowOnSetRenderTarget = false; - inRenderPass = false; - - // VRS ShadingRate(Image) cannot be set inside a render pass (cmdBuf). - // ShadingRate is set before BeginRenderPass and here we ResetShadingRate after EndRenderPass. - if (nativePass.hasShadingRateStates || nativePass.hasShadingRateImage) - { - rgContext.cmd.ResetShadingRate(); - } - } - } - } - } - else if (passData.type == RenderGraphPassType.Compute && passData.asyncCompute) - { - rgContext.renderContext.ExecuteCommandBufferAsync(rgContext.cmd, ComputeQueueType.Background); - CommandBufferPool.Release(rgContext.cmd); - rgContext.cmd = previousCommandBuffer; - } - - ExecuteDestroyResource(rgContext, resources, ref passData); - } - } -} - -#endif \ No newline at end of file diff --git a/Ghost.RenderGraph.Concept/Unity/RenderGraph.cs b/Ghost.RenderGraph.Concept/Unity/RenderGraph.cs deleted file mode 100644 index 6c76de0..0000000 --- a/Ghost.RenderGraph.Concept/Unity/RenderGraph.cs +++ /dev/null @@ -1,1692 +0,0 @@ -#if false - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using UnityEngine.Experimental.Rendering; -using UnityEngine.Scripting.APIUpdating; -// Typedef for the in-engine RendererList API (to avoid conflicts with the experimental version) -using CoreRendererListDesc = UnityEngine.Rendering.RendererUtils.RendererListDesc; - -namespace UnityEngine.Rendering.RenderGraphModule -{ - /// - /// Sets the read and write access for the depth buffer. - /// - [Flags][MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public enum DepthAccess - { - ///Read Access. - Read = 1 << 0, - ///Write Access. - Write = 1 << 1, - ///Read and Write Access. - ReadWrite = Read | Write, - } - - /// - /// Express the operations the rendergraph pass will do on a resource. - /// - [Flags][MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public enum AccessFlags - { - ///The pass does not access the resource at all. Calling Use* functions with none has no effect. - None = 0, - - ///This pass will read data the resource. Data in the resource should never be written unless one of the write flags is also present. Writing to a read-only resource may lead to undefined results, significant performance penaties, and GPU crashes. - Read = 1 << 0, - - ///This pass will at least write some data to the resource. Data in the resource should never be read unless one of the read flags is also present. Reading from a write-only resource may lead to undefined results, significant performance penaties, and GPU crashes. - Write = 1 << 1, - - ///Previous data in the resource is not preserved. The resource will contain undefined data at the beginning of the pass. - Discard = 1 << 2, - - ///All data in the resource will be written by this pass. Data in the resource should never be read. - WriteAll = Write | Discard, - - /// Shortcut for Read | Write - ReadWrite = Read | Write - } - - /// - /// Expresses additional pass properties that can be used to perform optimizations on some platforms. - /// - [Flags] - public enum ExtendedFeatureFlags - { - ///Default state with no extended features enabled. - None = 0, - ///On Meta XR, this flag can be set for the pass that performs the most 3D rendering to achieve better performance. - TileProperties = 1 << 0, - ///On XR, this flag can be set for passes that are compatible with Multiview Render Regions - MultiviewRenderRegionsCompatible = 1 << 1, - ///On Meta XR, this flag can be set to use MSAA shader resolve in the last subpass of a render pass. - MultisampledShaderResolve = 1 << 2, - } - - [Flags] - internal enum RenderGraphState - { - /// - /// Render Graph is not doing anything. - /// - Idle = 0, - - /// - /// Render Graph is recording the graph. - /// - RecordingGraph = 1 << 0, - - /// - /// Render Graph is recording a low level pass. - /// - RecordingPass = 1 << 1, - - /// - /// Render Graph is executing the graph. - /// - Executing = 1 << 2, - - /// - /// Utility flag to check if the graph is active. - /// - Active = RecordingGraph | RecordingPass | Executing - } - - /// - /// The strategy that the render pipeline should use to determine the UV origin of RenderTextures who have an Unknown TextureUVOrigin when rendering. - /// - public enum RenderTextureUVOriginStrategy - { - /// RenderTextures are always treated as bottom left orientation. - BottomLeft, - /// RenderTextures may inherit the backbuffer attachment orientation if they are only used via attachment reads. - PropagateAttachmentOrientation - } - - /// - /// An object representing the internal context of a rendergraph pass execution. - /// This object is public for technical reasons only and should not be used. - /// - [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public class InternalRenderGraphContext - { - internal ScriptableRenderContext renderContext; - internal CommandBuffer cmd; - internal RenderGraphObjectPool renderGraphPool; - internal RenderGraphDefaultResources defaultResources; - internal RenderGraphPass executingPass; - internal NativeRenderPassCompiler.CompilerContextData compilerContext; - internal bool contextlessTesting; - internal bool forceResourceCreation; - } - - // InternalRenderGraphContext is public (but all members are internal) - // only because the C# standard says that all interface member function implementations must be public. - // So below in for example the RasterGraphContext we can't implement the (internal) interface as - // internal void FromInternalContext(InternalRenderGraphContext context) { ... } - // So we have to make FromInternalContext public so InternalRenderGraphContext also becomes public. - // This seems an oversight in c# where Interfaces used as Generic constraints could very well be useful - // with internal only functions. - - /// - /// Interface implemented by the different render graph contexts provided at execution timeline (via SetRenderFunc()) - /// - internal interface IDerivedRendergraphContext - { - /// - /// This function is only public for techical resons of the c# language and should not be called outside the package. - /// - /// The context to convert - public void FromInternalContext(InternalRenderGraphContext context); - - /// - /// Retrieves the TextureUVOrigin of the Render Graph texture from its handle. - /// - /// - /// This function can only be called when using the Native Render Pass Compiler (enabled by default). - /// - /// The texture handle to query. - /// The TextureUVOrigin of the texture. - public TextureUVOrigin GetTextureUVOrigin(in TextureHandle textureHandle); - } - - /// - /// This class declares the context object passed to the execute function of a raster render pass. - /// - /// - [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public struct RasterGraphContext : IDerivedRendergraphContext - { - private InternalRenderGraphContext wrappedContext; - - ///Command Buffer used for rendering. - public RasterCommandBuffer cmd; - - ///Render Graph default resources. - public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; } - - ///Render Graph pool used for temporary data. - public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; } - - static internal RasterCommandBuffer rastercmd = new RasterCommandBuffer(null, null, false); - - /// - public void FromInternalContext(InternalRenderGraphContext context) - { - wrappedContext = context; - rastercmd.m_WrappedCommandBuffer = wrappedContext.cmd; - rastercmd.m_ExecutingPass = context.executingPass; - cmd = rastercmd; - } - - /// - public readonly TextureUVOrigin GetTextureUVOrigin(in TextureHandle textureHandle) - { - if (!SystemInfo.graphicsUVStartsAtTop) - return TextureUVOrigin.BottomLeft; - - if (wrappedContext.compilerContext != null) - { - return wrappedContext.compilerContext.GetTextureUVOrigin(textureHandle); - } - return TextureUVOrigin.BottomLeft; - } - } - - /// - /// This class declares the context object passed to the execute function of a compute render pass. - /// - /// - [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public class ComputeGraphContext : IDerivedRendergraphContext - { - private InternalRenderGraphContext wrappedContext; - - ///Command Buffer used for rendering. - public ComputeCommandBuffer cmd; - - ///Render Graph default resources. - public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; } - - ///Render Graph pool used for temporary data. - public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; } - - static internal ComputeCommandBuffer computecmd = new ComputeCommandBuffer(null, null, false); - - /// - public void FromInternalContext(InternalRenderGraphContext context) - { - wrappedContext = context; - computecmd.m_WrappedCommandBuffer = wrappedContext.cmd; - computecmd.m_ExecutingPass = context.executingPass; - cmd = computecmd; - } - - /// - public TextureUVOrigin GetTextureUVOrigin(in TextureHandle textureHandle) - { - if (!SystemInfo.graphicsUVStartsAtTop) - return TextureUVOrigin.BottomLeft; - - if (wrappedContext.compilerContext != null) - { - return wrappedContext.compilerContext.GetTextureUVOrigin(textureHandle); - } - return TextureUVOrigin.BottomLeft; - } - } - - /// - /// This class declares the context object passed to the execute function of an unsafe render pass. - /// - /// - [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public class UnsafeGraphContext : IDerivedRendergraphContext - { - private InternalRenderGraphContext wrappedContext; - - ///Unsafe Command Buffer used for rendering. - public UnsafeCommandBuffer cmd; - - ///Render Graph default resources. - public RenderGraphDefaultResources defaultResources { get => wrappedContext.defaultResources; } - - ///Render Graph pool used for temporary data. - public RenderGraphObjectPool renderGraphPool { get => wrappedContext.renderGraphPool; } - - internal static UnsafeCommandBuffer unsCmd = new UnsafeCommandBuffer(null, null, false); - /// - public void FromInternalContext(InternalRenderGraphContext context) - { - wrappedContext = context; - unsCmd.m_WrappedCommandBuffer = wrappedContext.cmd; - unsCmd.m_ExecutingPass = context.executingPass; - cmd = unsCmd; - } - - /// - public TextureUVOrigin GetTextureUVOrigin(in TextureHandle textureHandle) - { - if (!SystemInfo.graphicsUVStartsAtTop) - return TextureUVOrigin.BottomLeft; - - if (wrappedContext.compilerContext != null) - { - return wrappedContext.compilerContext.GetTextureUVOrigin(textureHandle); - } - return TextureUVOrigin.BottomLeft; - } - } - - /// - /// This struct contains properties which control the execution of the Render Graph. - /// - [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public struct RenderGraphParameters - { - ///Identifier for this render graph execution. - [Obsolete("Not used anymore. The debugging tools use the name of the object identified by executionId. #from(6000.3)")] - public string executionName; - ///Identifier for this render graph execution (i.e. EntityId of the Camera rendering). Used for debugging tools. - public EntityId executionId; - ///Whether the execution should generate debug data and be visible in Render Graph Viewer. - public bool generateDebugData; - ///Index of the current frame being rendered. - public int currentFrameIndex; - /// Controls whether to enable Renderer List culling or not. - [Obsolete("Not supported anymore. Syncing with culling system brings performance regressions in most cases. #from(6000.5)")] - public bool rendererListCulling; - ///Scriptable Render Context used by the render pipeline. - public ScriptableRenderContext scriptableRenderContext; - ///Command Buffer used to execute graphic commands. - public CommandBuffer commandBuffer; - ///When running tests indicate the context is intentionally invalid and all calls on it should just do nothing. - ///This allows you to run tests that rely on code execution the way to the pass render functions - ///This also changes some behaviours with exception handling and error logging so the test framework can act on exceptions to validate behaviour better. - internal bool invalidContextForTesting; - ///The strategy that the rendergraph should use to determine the texture uv origin if Unknown of RenderTextures when rendering. - public RenderTextureUVOriginStrategy renderTextureUVOriginStrategy; - } - - /// - /// The Render Pass rendering delegate to use with typed contexts. - /// - /// The type of the class used to provide data to the Render Pass. - /// The type of the context that will be passed to the render function. - /// Render Pass specific data. - /// Global Render Graph context. - [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public delegate void BaseRenderFunc(PassData data, ContextType renderGraphContext) where PassData : class, new(); - - /// - /// This class is the main entry point of the Render Graph system. - /// - [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public partial class RenderGraph - { - ///Maximum number of MRTs supported by Render Graph. - public static readonly int kMaxMRTCount = 8; - - /// - /// Enable the use of the render pass API instead of the traditional SetRenderTarget workflow for AddRasterRenderPass() API. Enabled by default since 6000.3. No alternative since 6000.5. - /// - /// - /// When enabled, the render graph try to use render passes and supasses instead of relying on SetRendertarget. It - /// will try to aggressively optimize the number of BeginRenderPass+EndRenderPass calls as well as calls to NextSubPass. - /// This with the aim to maximize the time spent "on chip" on tile based renderers. - /// - /// The Graph will automatically determine when to break render passes as well as the load and store actions to apply to these render passes. - /// To do this, the graph will analyze the use of textures. E.g. when a texture is used twice in a row as a active render target, the two - /// render graph passes will be merged in a single render pass with two surpasses. On the other hand if a render target is sampled as a texture in - /// a later pass this render target will be stored (and possibly resolved) and the render pass will be broken up. - /// - /// When setting this setting to true some existing render graph API is no longer valid as it can't express detailed frame information needed to emit - /// native render pases. In particular: - /// - The ImportBackbuffer overload without a RenderTargetInfo argument. - /// - Any AddRenderPass overloads. The more specific AddRasterRenderPass/AddComputePass/AddUnsafePass functions should be used to register passes. - /// - /// In addition to this, additional validation will be done on the correctness of arguments of existing API that was not previously done. This could lead - /// to new errors when using existing render graph code with nativeRenderPassesEnabled. - /// - /// Note: that CommandBuffer.BeginRenderPass/EndRenderPass calls are different by design from SetRenderTarget so this could also have - /// effects outside of render graph (e.g. for code relying on the currently active render target as this will not be updated when using render passes). - /// - [Obsolete("RenderGraph always enables native render pass support. #from(6000.5)")] - public bool nativeRenderPassesEnabled { get; set; } = true; - - internal/*for tests*/ RenderGraphResourceRegistry m_Resources; - internal/*for tests*/ RenderGraphObjectPool m_RenderGraphPool = new RenderGraphObjectPool(); - RenderGraphBuilders m_builderInstance = new RenderGraphBuilders(); - internal/*for tests*/ List m_RenderPasses = new List(64); - List m_RendererLists = new List(32); - RenderGraphDebugParams m_DebugParameters = new RenderGraphDebugParams(); - RenderGraphDefaultResources m_DefaultResources = new RenderGraphDefaultResources(); - Dictionary m_DefaultProfilingSamplers = new Dictionary(); - InternalRenderGraphContext m_RenderGraphContext = new InternalRenderGraphContext(); - CommandBuffer m_PreviousCommandBuffer; - RenderGraphCompilationCache m_CompilationCache; - - EntityId m_CurrentExecutionId; - bool m_CurrentExecutionCanGenerateDebugData; - int m_ExecutionCount; - int m_CurrentFrameIndex; - bool m_ExecutionExceptionWasRaised; - bool m_EnableCompilationCaching; - internal/*for tests*/ static bool? s_EnableCompilationCachingForTests; - RenderGraphState m_RenderGraphState; - RenderTextureUVOriginStrategy m_renderTextureUVOriginStrategy; - - // Global container of registered render graphs, associated with the list of executions that have been registered for them. - // When a RenderGraph is created, an entry is added to this dictionary. When that RenderGraph renders something, - // and a debug session is active, an entry is added to the list of executions for that RenderGraph using the executionId - // as the key. So when you render multiple times with the same executionId, only one DebugExecutionItem is created. - static Dictionary> s_RegisteredExecutions = new (); - - #region Public Interface - /// Name of the Render Graph. - public string name { get; private set; } = "RenderGraph"; - - internal RenderGraphState RenderGraphState - { - get { return m_RenderGraphState; } - set { m_RenderGraphState = value; } - } - - /// The strategy the Render Graph will take for the uv origin of RenderTextures in the graph. - public RenderTextureUVOriginStrategy renderTextureUVOriginStrategy - { - get { return m_renderTextureUVOriginStrategy; } - internal set { m_renderTextureUVOriginStrategy = value; } - } - - /// If true, the Render Graph Viewer is active. - public static bool isRenderGraphViewerActive => RenderGraphDebugSession.hasActiveDebugSession; - - /// If true, the Render Graph will run its various validity checks while processing (not considered in release mode). - internal static bool enableValidityChecks { get; private set; } - - /// - /// Set of default resources usable in a pass rendering code. - /// - public RenderGraphDefaultResources defaultResources - { - get - { - return m_DefaultResources; - } - } - - /// - /// Render Graph constructor. - /// - /// Optional name used to identify the render graph instance. - public RenderGraph(string name = "RenderGraph") - { - this.name = name; - if (GraphicsSettings.TryGetRenderPipelineSettings(out var renderGraphGlobalSettings)) - { - m_EnableCompilationCaching = renderGraphGlobalSettings.enableCompilationCaching; - enableValidityChecks = renderGraphGlobalSettings.enableValidityChecks; - } - else // No SRP pipeline is present/active, it can happen with unit tests - { - enableValidityChecks = true; - } - - if (s_EnableCompilationCachingForTests.HasValue) - m_EnableCompilationCaching = s_EnableCompilationCachingForTests.Value; - - if (m_EnableCompilationCaching) - m_CompilationCache = new RenderGraphCompilationCache(); - - m_Resources = new RenderGraphResourceRegistry(m_DebugParameters); - RegisterGraph(); - - m_RenderGraphState = RenderGraphState.Idle; - - RenderGraph.RenderGraphExceptionMessages.enableCaller = true; - -#if !UNITY_EDITOR && DEVELOPMENT_BUILD - if (RenderGraphDebugSession.currentDebugSession == null) - RenderGraphDebugSession.Create(); -#endif - } - - // Internal, only for testing - // Useful when we need to clean when calling - // internal functions in tests even if Render Graph is active - // This API shouldn't be called when the render graph is active! - internal void CleanupResourcesAndGraph() - { - // Usually done at the end of Execute step - // Also doing it here in case RG stopped before it - ClearCurrentCompiledGraph(); - - m_Resources.Cleanup(); - m_DefaultResources.Cleanup(); - m_RenderGraphPool.Cleanup(); - nativeCompiler?.Cleanup(); - m_CompilationCache?.Clear(); - - DelegateHashCodeUtils.ClearCache(); - } - - /// - /// Free up all resources used internally by the Render Graph instance, and unregister it so it won't be visible in the Render Graph Viewer. - /// - public void Cleanup() - { - CheckNotUsedWhenActive(); - - // Dispose of the compiled graphs left over in the cache - m_CompilationCache?.Cleanup(); - - CleanupResourcesAndGraph(); - UnregisterGraph(); - } - - internal RenderGraphDebugParams debugParams => m_DebugParameters; - - internal List GetWidgetList() - { - return m_DebugParameters.GetWidgetList(name); - } - - internal bool areAnySettingsActive => m_DebugParameters.AreAnySettingsActive; - - /// - /// Register the render graph to the debug window. - /// - /// - /// This API cannot be called when Render Graph is active, please call it outside of RecordRenderGraph(). - /// - /// Optional debug panel to which the render graph debug parameters will be registered. - public void RegisterDebug(DebugUI.Panel panel = null) - { - CheckNotUsedWhenActive(); - - m_DebugParameters.RegisterDebug(name, panel); - } - - /// - /// Unregister render graph from the debug window. - /// - /// - /// This API cannot be called when Render Graph is active, please call it outside of RecordRenderGraph(). - /// - public void UnRegisterDebug() - { - CheckNotUsedWhenActive(); - - m_DebugParameters.UnRegisterDebug(this.name); - } - - /// - /// Get the list of all registered render graphs. - /// - /// The list of all registered render graphs. - public static List GetRegisteredRenderGraphs() - { - return new List(s_RegisteredExecutions.Keys); - } - - internal static Dictionary> GetRegisteredExecutions() => s_RegisteredExecutions; - - /// - /// End frame processing. Purge resources that have been used since last frame and resets internal states. - /// This need to be called once per frame. - /// - /// - /// This API cannot be called when Render Graph is active, please call it outside of RecordRenderGraph(). - /// - public void EndFrame() - { - CheckNotUsedWhenActive(); - - m_Resources.PurgeUnusedGraphicsResources(); - } - - /// - /// Import an external texture to the Render Graph. - /// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// External RTHandle that needs to be imported. - /// A new TextureHandle that represents the imported texture in the context of this rendergraph. - public TextureHandle ImportTexture(RTHandle rt) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.ImportTexture(rt); - } - - /// - /// Import an external Variable Rate Shading (VRS) textures to the RenderGraph. - /// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled. - /// - /// External shading rate image RTHandle that needs to be imported. - /// New TextureHandle that represents the imported shading rate images in the context of this rendergraph. - public TextureHandle ImportShadingRateImageTexture(RTHandle rt) - { - if (ShadingRateInfo.supportsPerImageTile) - return m_Resources.ImportTexture(rt); - - return TextureHandle.nullHandle; - } - - /// - /// Import an external texture to the Render Graph. - /// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled. - /// - /// Note: RTHandles that wrap RenderTargetIdentifier will fail to import using this overload as render graph can't derive the render texture's properties. - /// In that case the overload taking a RenderTargetInfo argument should be used instead. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// External RTHandle that needs to be imported. - /// Info describing the clear behavior of imported textures. Clearing textures using importParams may be more efficient than manually clearing the texture using `cmd.Clear` on some hardware. - /// A new TextureHandle that represents the imported texture in the context of this rendergraph. - public TextureHandle ImportTexture(RTHandle rt, ImportResourceParams importParams) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.ImportTexture(rt, importParams); - } - - /// - /// Import an external texture to the Render Graph. This overload should be used for RTHandles wrapping a RenderTargetIdentifier. - /// If the RTHandle is wrapping a RenderTargetIdentifer, Rendergraph can't derive the render texture's properties so the user has to provide this info to the graph through RenderTargetInfo. - /// - /// Any pass writing to an imported texture will be considered having side effects and can't be automatically culled. - /// - /// Note: To avoid inconsistencies between the passed in RenderTargetInfo and render texture this overload can only be used when the RTHandle is wrapping a RenderTargetIdentifier. - /// If this is not the case, the overload of ImportTexture without a RenderTargetInfo argument should be used instead. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// External RTHandle that needs to be imported. - /// The properties of the passed in RTHandle. - /// Info describing the clear behavior of imported textures. Clearing textures using importParams may be more efficient than manually clearing the texture using `cmd.Clear` on some hardware. - /// A new TextureHandle that represents the imported texture in the context of this rendergraph. - public TextureHandle ImportTexture(RTHandle rt, RenderTargetInfo info, ImportResourceParams importParams = new ImportResourceParams()) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.ImportTexture(rt, info, importParams); - } - - /// - /// Import an external texture to the Render Graph and set the handle as builtin handle. This can only happen from within the graph module - /// so it is internal. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// External RTHandle that needs to be imported. - /// The handle is a builtin handle managed by RenderGraph internally. - /// A new TextureHandle that represents the imported texture in the context of this rendergraph. - internal TextureHandle ImportTexture(RTHandle rt, bool isBuiltin) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.ImportTexture(rt, isBuiltin); - } - - /// - /// Import the final backbuffer to render graph. The rendergraph can't derive the properties of a RenderTargetIdentifier as it is an opaque handle so the user has to pass them in through the info argument. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// Backbuffer render target identifier. - /// The properties of the passed in RTHandle. - /// Info describing the clear behavior of imported textures. Clearing textures using importParams may be more efficient than manually clearing the texture using `cmd.Clear` on some hardware. - /// A new TextureHandle that represents the imported texture in the context of this rendergraph. - public TextureHandle ImportBackbuffer(RenderTargetIdentifier rt, RenderTargetInfo info, ImportResourceParams importParams = new ImportResourceParams()) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.ImportBackbuffer(rt, info, importParams); - } - - /// - /// Create a new Render Graph Texture resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// Texture descriptor. - /// A new TextureHandle. - public TextureHandle CreateTexture(in TextureDesc desc) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateTexture(desc); - } - - /// - /// Create a new Render Graph Texture resource using the descriptor from another texture. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// Texture from which the descriptor should be used. - /// A new TextureHandle. - public TextureHandle CreateTexture(TextureHandle texture) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateTexture(in m_Resources.GetTextureResourceDesc(texture.handle)); - } - - /// - /// Create a new Render Graph Texture resource using the descriptor from another texture. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// Texture from which the descriptor should be used. - /// The destination texture name. - /// Texture needs to be cleared on first use. - /// A new TextureHandle. - public TextureHandle CreateTexture(TextureHandle texture, string name, bool clear = false) - { - CheckNotUsedWhenExecuting(); - - var destinationDesc = GetTextureDesc(texture); - destinationDesc.name = name; - destinationDesc.clearBuffer = clear; - - return m_Resources.CreateTexture(destinationDesc); - } - - /// - /// Create a new Render Graph Texture if the passed handle is invalid and use said handle as output. - /// If the passed handle is valid, no texture is created. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// Desc used to create the texture. - /// Texture from which the descriptor should be used. - public void CreateTextureIfInvalid(in TextureDesc desc, ref TextureHandle texture) - { - CheckNotUsedWhenExecuting(); - - if (!texture.IsValid()) - texture = m_Resources.CreateTexture(desc); - } - - /// - /// Gets the descriptor of the specified Texture resource. - /// - /// Texture resource from which the descriptor is requested. - /// The input texture descriptor. - public TextureDesc GetTextureDesc(in TextureHandle texture) - { - return m_Resources.GetTextureResourceDesc(texture.handle); - } - - /// - /// Gets the descriptor of the specified Texture resource. - /// - /// Texture resource from which the descriptor is requested. - /// The input texture descriptor. - public RenderTargetInfo GetRenderTargetInfo(TextureHandle texture) - { - RenderTargetInfo info; - m_Resources.GetRenderTargetInfo(texture.handle, out info); - return info; - } - - /// - /// Creates a new Renderer List Render Graph resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// Renderer List descriptor. - /// A new RendererListHandle. - public RendererListHandle CreateRendererList(in CoreRendererListDesc desc) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateRendererList(desc); - } - - /// - /// Creates a new Renderer List Render Graph resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// Renderer List descriptor. - /// A new RendererListHandle. - public RendererListHandle CreateRendererList(in RendererListParams desc) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateRendererList(desc); - } - - /// - /// Creates a new Shadow Renderer List Render Graph resource. - /// - /// DrawSettings that describe the shadow drawcall. - /// A new RendererListHandle. - public RendererListHandle CreateShadowRendererList(ref ShadowDrawingSettings shadowDrawingSettings) - { - return m_Resources.CreateShadowRendererList(m_RenderGraphContext.renderContext, ref shadowDrawingSettings); - } - - /// - /// Creates a new Gizmo Renderer List Render Graph resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// The camera that is used for rendering the Gizmo. - /// GizmoSubset that specifies whether gizmos render before or after postprocessing for a camera render. - /// A new RendererListHandle. - public RendererListHandle CreateGizmoRendererList(in Camera camera, in GizmoSubset gizmoSubset) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateGizmoRendererList(m_RenderGraphContext.renderContext, camera, gizmoSubset); - } - - /// - /// Creates a new UIOverlay Renderer List Render Graph resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// The camera that is used for rendering the full UIOverlay. - /// A new RendererListHandle. - public RendererListHandle CreateUIOverlayRendererList(in Camera camera) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateUIOverlayRendererList(m_RenderGraphContext.renderContext, camera, UISubset.All); - } - - /// - /// Creates a new UIOverlay Renderer List Render Graph resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// The camera that is used for rendering some subset of the UIOverlay. - /// Enum flag that specifies which subset to render. - /// A new RendererListHandle. - public RendererListHandle CreateUIOverlayRendererList(in Camera camera, in UISubset uiSubset) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateUIOverlayRendererList(m_RenderGraphContext.renderContext, camera, uiSubset); - } - - /// - /// Creates a new WireOverlay Renderer List Render Graph resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// The camera that is used for rendering the WireOverlay. - /// A new RendererListHandle. - public RendererListHandle CreateWireOverlayRendererList(in Camera camera) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateWireOverlayRendererList(m_RenderGraphContext.renderContext, camera); - } - - /// - /// Creates a new Skybox Renderer List Render Graph resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// The camera that is used for rendering the Skybox. - /// A new RendererListHandle. - public RendererListHandle CreateSkyboxRendererList(in Camera camera) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera); - } - - /// - /// Creates a new Skybox Renderer List Render Graph resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// The camera that is used for rendering the Skybox. - /// The projection matrix used during XR rendering of the skybox. - /// The view matrix used during XR rendering of the skybox. - /// A new RendererListHandle. - public RendererListHandle CreateSkyboxRendererList(in Camera camera, Matrix4x4 projectionMatrix, Matrix4x4 viewMatrix) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera, projectionMatrix, viewMatrix); - } - - /// - /// Creates a new Skybox Renderer List Render Graph resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// The camera that is used for rendering the Skybox. - /// The left eye projection matrix used during Legacy single pass XR rendering of the skybox. - /// The left eye view matrix used during Legacy single pass XR rendering of the skybox. - /// The right eye projection matrix used during Legacy single pass XR rendering of the skybox. - /// The right eye view matrix used during Legacy single pass XR rendering of the skybox. - /// A new RendererListHandle. - public RendererListHandle CreateSkyboxRendererList(in Camera camera, Matrix4x4 projectionMatrixL, Matrix4x4 viewMatrixL, Matrix4x4 projectionMatrixR, Matrix4x4 viewMatrixR) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateSkyboxRendererList(m_RenderGraphContext.renderContext, camera, projectionMatrixL, viewMatrixL, projectionMatrixR, viewMatrixR); - } - - /// - /// Import an external Graphics Buffer to the Render Graph. - /// Any pass writing to an imported graphics buffer will be considered having side effects and can't be automatically culled. - /// - /// External Graphics Buffer that needs to be imported. - /// A new GraphicsBufferHandle. - public BufferHandle ImportBuffer(GraphicsBuffer graphicsBuffer) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.ImportBuffer(graphicsBuffer); - } - - /// - /// Create a new Render Graph Graphics Buffer resource. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// Graphics Buffer descriptor. - /// A new GraphicsBufferHandle. - public BufferHandle CreateBuffer(in BufferDesc desc) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateBuffer(desc); - } - - /// - /// Create a new Render Graph Graphics Buffer resource using the descriptor from another graphics buffer. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// Graphics Buffer from which the descriptor should be used. - /// A new GraphicsBufferHandle. - public BufferHandle CreateBuffer(in BufferHandle graphicsBuffer) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.CreateBuffer(in m_Resources.GetBufferResourceDesc(graphicsBuffer.handle)); - } - - /// - /// Gets the descriptor of the specified Graphics Buffer resource. - /// - /// Graphics Buffer resource from which the descriptor is requested. - /// The input graphics buffer descriptor. - public BufferDesc GetBufferDesc(in BufferHandle graphicsBuffer) - { - return m_Resources.GetBufferResourceDesc(graphicsBuffer.handle); - } - - /// - /// Import an external RayTracingAccelerationStructure to the Render Graph. - /// Any pass writing to (building) an imported RayTracingAccelerationStructure will be considered having side effects and can't be automatically culled. - /// - /// - /// This API cannot be called during the Render Graph execution, please call it outside of SetRenderFunc(). - /// - /// External RayTracingAccelerationStructure that needs to be imported. - /// Optional name for identifying the RayTracingAccelerationStructure in the Render Graph. - /// A new RayTracingAccelerationStructureHandle. - public RayTracingAccelerationStructureHandle ImportRayTracingAccelerationStructure(in RayTracingAccelerationStructure accelStruct, string name = null) - { - CheckNotUsedWhenExecuting(); - - return m_Resources.ImportRayTracingAccelerationStructure(accelStruct, name); - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - void CheckNotUsedWhenExecuting() - { - if (enableValidityChecks && m_RenderGraphState == RenderGraphState.Executing) - throw new InvalidOperationException(RenderGraphExceptionMessages.GetExceptionMessage(RenderGraphState.Executing)); - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - void CheckNotUsedWhenRecordingGraph() - { - if (enableValidityChecks && m_RenderGraphState == RenderGraphState.RecordingGraph) - throw new InvalidOperationException(RenderGraphExceptionMessages.GetExceptionMessage(RenderGraphState.RecordingGraph)); - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - void CheckNotUsedWhenRecordPassOrExecute() - { - if (enableValidityChecks && (m_RenderGraphState == RenderGraphState.RecordingPass || m_RenderGraphState == RenderGraphState.Executing)) - throw new InvalidOperationException(RenderGraphExceptionMessages.GetExceptionMessage(RenderGraphState.RecordingPass | RenderGraphState.Executing)); - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - void CheckNotUsedWhenRecordingPass() - { - if (enableValidityChecks && m_RenderGraphState == RenderGraphState.RecordingPass) - throw new InvalidOperationException(RenderGraphExceptionMessages.GetExceptionMessage(RenderGraphState.RecordingPass)); - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - void CheckNotUsedWhenActive() - { - if (enableValidityChecks && (m_RenderGraphState & RenderGraphState.Active) != RenderGraphState.Idle) - throw new InvalidOperationException(RenderGraphExceptionMessages.GetExceptionMessage(RenderGraphState.Active)); - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - void CheckNotUsedWhenIdle() - { - if (enableValidityChecks && m_RenderGraphState == RenderGraphState.Idle) - throw new InvalidOperationException(RenderGraphExceptionMessages.GetExceptionMessage(RenderGraphState.Active)); - } - - /// - /// Add a new Raster Render Pass to the Render Graph. Raster passes can execute rasterization workloads but cannot do other GPU work like copies or compute. - /// - /// - /// This API cannot be called when Render Graph records a pass, please call it within SetRenderFunc() or outside of AddUnsafePass()/AddComputePass()/AddRasterRenderPass(). - /// - /// Type of the class to use to provide data to the Render Pass. - /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). - /// Instance of PassData that is passed to the render function and you must fill. - /// File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass. - public IRasterRenderGraphBuilder AddRasterRenderPass(string passName, out PassData passData -#if !CORE_PACKAGE_DOCTOOLS - , [CallerFilePath] string file = "", - [CallerLineNumber] int line = 0) where PassData : class, new() -#endif - { - return AddRasterRenderPass(passName, out passData, GetDefaultProfilingSampler(passName), file, line); - } - - /// - /// Add a new Raster Render Pass to the Render Graph. Raster passes can execute rasterization workloads but cannot do other GPU work like copies or compute. - /// - /// Type of the class to use to provide data to the Render Pass. - /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). - /// Instance of PassData that is passed to the render function and you must fill. - /// Profiling sampler used around the pass. - /// File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass. - public IRasterRenderGraphBuilder AddRasterRenderPass(string passName, out PassData passData, ProfilingSampler sampler -#if !CORE_PACKAGE_DOCTOOLS - ,[CallerFilePath] string file = "", - [CallerLineNumber] int line = 0) where PassData : class, new() -#endif - { - CheckNotUsedWhenRecordingPass(); - - m_RenderGraphState = RenderGraphState.RecordingPass; - - var renderPass = m_RenderGraphPool.Get>(); - renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get(), passName, RenderGraphPassType.Raster, sampler); - - AddPassDebugMetadata(renderPass, file, line); - - passData = renderPass.data; - - m_RenderPasses.Add(renderPass); - - m_builderInstance.Setup(renderPass, m_Resources, this); - - return m_builderInstance; - } - - /// - /// Add a new Compute Render Pass to the Render Graph. Raster passes can execute rasterization workloads but cannot do other GPU work like copies or compute. - /// - /// - /// This API cannot be called when Render Graph records a pass, please call it within SetRenderFunc() or outside of AddUnsafePass()/AddComputePass()/AddRasterRenderPass(). - /// - /// Type of the class to use to provide data to the Render Pass. - /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). - /// Instance of PassData that is passed to the render function and you must fill. - /// File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// A new instance of a IRasterRenderGraphBuilder used to setup the new Rasterization Render Pass. - public IComputeRenderGraphBuilder AddComputePass(string passName, out PassData passData -#if !CORE_PACKAGE_DOCTOOLS - , [CallerFilePath] string file = "", - [CallerLineNumber] int line = 0) where PassData : class, new() -#endif - { - return AddComputePass(passName, out passData, GetDefaultProfilingSampler(passName), file, line); - } - - /// - /// Add a new Compute Render Pass to the Render Graph. Compute passes can execute compute workloads but cannot do rasterization. - /// - /// Type of the class to use to provide data to the Render Pass. - /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). - /// Instance of PassData that is passed to the render function and you must fill. - /// Profiling sampler used around the pass. - /// File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// A new instance of a IComputeRenderGraphBuilder used to setup the new Compute Render Pass. - public IComputeRenderGraphBuilder AddComputePass(string passName, out PassData passData, ProfilingSampler sampler -#if !CORE_PACKAGE_DOCTOOLS - ,[CallerFilePath] string file = "", - [CallerLineNumber] int line = 0) where PassData : class, new() -#endif - { - CheckNotUsedWhenRecordingPass(); - - m_RenderGraphState = RenderGraphState.RecordingPass; - - var renderPass = m_RenderGraphPool.Get>(); - renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get(), passName, RenderGraphPassType.Compute, sampler); - - AddPassDebugMetadata(renderPass, file, line); - - passData = renderPass.data; - - m_RenderPasses.Add(renderPass); - - m_builderInstance.Setup(renderPass, m_Resources, this); - - return m_builderInstance; - } - - /// - /// Add a new Unsafe Render Pass to the Render Graph. Unsafe passes can do certain operations compute/raster render passes cannot do and have - /// access to the full command buffer API. The unsafe API should be used sparingly as it has the following downsides: - /// - Limited automatic validation of the commands and resource dependencies. The user is responsible to ensure that all dependencies are correctly declared. - /// - All native render passes will be serialized out. - /// - In the future the render graph compiler may generate a sub-optimal command stream for unsafe passes. - /// When using a unsafe pass the graph will also not automatically set up graphics state like rendertargets. The pass should do this itself - /// using cmd.SetRenderTarget and related commands. - /// - /// - /// This API cannot be called when Render Graph records a pass, please call it within SetRenderFunc() or outside of AddUnsafePass()/AddComputePass()/AddRasterRenderPass(). - /// - /// Type of the class to use to provide data to the Render Pass. - /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). - /// Instance of PassData that is passed to the render function and you must fill. - /// File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// A new instance of a IUnsafeRenderGraphBuilder used to setup the new Unsafe Render Pass. - public IUnsafeRenderGraphBuilder AddUnsafePass(string passName, out PassData passData -#if !CORE_PACKAGE_DOCTOOLS - , [CallerFilePath] string file = "", - [CallerLineNumber] int line = 0) where PassData : class, new() -#endif - { - return AddUnsafePass(passName, out passData, GetDefaultProfilingSampler(passName), file, line); - } - - /// - /// Add a new unsafe Render Pass to the Render Graph. Unsafe passes can do certain operations compute/raster render passes cannot do and have - /// access to the full command buffer API. The unsafe API should be used sparingly as it has the following downsides: - /// - Limited automatic validation of the commands and resource dependencies. The user is responsible to ensure that all dependencies are correctly declared. - /// - All native render passes will be serialized out. - /// - In the future the render graph compiler may generate a sub-optimal command stream for unsafe passes. - /// When using an unsafe pass the graph will also not automatically set up graphics state like rendertargets. The pass should do this itself - /// using cmd.SetRenderTarget and related commands. - /// - /// Type of the class to use to provide data to the Render Pass. - /// Name of the new Render Pass (this is also be used to generate a GPU profiling marker). - /// Instance of PassData that is passed to the render function and you must fill. - /// Profiling sampler used around the pass. - /// File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// A new instance of a IUnsafeRenderGraphBuilder used to setup the new unsafe Render Pass. - public IUnsafeRenderGraphBuilder AddUnsafePass(string passName, out PassData passData, ProfilingSampler sampler -#if !CORE_PACKAGE_DOCTOOLS - , [CallerFilePath] string file = "", - [CallerLineNumber] int line = 0) where PassData : class, new() -#endif - { - CheckNotUsedWhenRecordingPass(); - - m_RenderGraphState = RenderGraphState.RecordingPass; - - var renderPass = m_RenderGraphPool.Get>(); - renderPass.Initialize(m_RenderPasses.Count, m_RenderGraphPool.Get(), passName, RenderGraphPassType.Unsafe, sampler); - renderPass.AllowGlobalState(true); - - AddPassDebugMetadata(renderPass, file, line); - - passData = renderPass.data; - - m_RenderPasses.Add(renderPass); - - m_builderInstance.Setup(renderPass, m_Resources, this); - - return m_builderInstance; - } - - /// - /// Starts the recording of the render graph. - /// This must be called before adding any pass to the render graph. - /// - /// - /// This API cannot be called when Render Graph is active, please call it outside of RecordRenderGraph(). - /// - /// Parameters necessary for the render graph execution. - /// - /// Begin recording the Render Graph. - /// - /// renderGraph.BeginRecording(parameters) - /// // Add your render graph passes here. - /// renderGraph.EndRecordingAndExecute() - /// - /// - public void BeginRecording(in RenderGraphParameters parameters) - { - CheckNotUsedWhenActive(); - - m_ExecutionExceptionWasRaised = false; - m_RenderGraphState = RenderGraphState.RecordingGraph; - - m_CurrentFrameIndex = parameters.currentFrameIndex; - m_CurrentExecutionId = parameters.executionId; - - // Ignore preview cameras and render requests for debug data generation. They would cause the same camera to - // be rendered twice with different render graphs, confusing users and causing the RG Viewer to constantly update. - m_CurrentExecutionCanGenerateDebugData = parameters.generateDebugData && parameters.executionId != EntityId.None; - - m_renderTextureUVOriginStrategy = parameters.renderTextureUVOriginStrategy; - - m_Resources.BeginRenderGraph(m_ExecutionCount++); - - m_DefaultResources.InitializeForRendering(this); - - m_RenderGraphContext.cmd = parameters.commandBuffer; - m_RenderGraphContext.renderContext = parameters.scriptableRenderContext; - m_RenderGraphContext.contextlessTesting = parameters.invalidContextForTesting; - m_RenderGraphContext.renderGraphPool = m_RenderGraphPool; - m_RenderGraphContext.defaultResources = m_DefaultResources; - - // With the actual implementation of the Frame Debugger, we cannot re-use resources during the same frame - // or it breaks the rendering of the pass preview, since the FD copies the texture after the execution of the RG. - m_RenderGraphContext.forceResourceCreation = -#if UNITY_EDITOR || DEVELOPMENT_BUILD - FrameDebugger.enabled; -#else - false; -#endif - } - - /// - /// Ends the recording and executes the render graph. - /// This must be called once all passes have been added to the render graph. - /// - public void EndRecordingAndExecute() - { - CheckNotUsedWhenRecordPassOrExecute(); - - Execute(); - - ClearCompiledGraph(); - - m_Resources.EndExecute(); - - InvalidateContext(); - - m_RenderGraphState = RenderGraphState.Idle; - } - - /// - /// Catches and logs exceptions that could happen during the graph recording or execution. - /// - /// The exception thrown by the graph. - /// True if contexless testing is enabled, false otherwise. - public bool ResetGraphAndLogException(Exception e) - { - m_RenderGraphState = RenderGraphState.Idle; - - if (!m_RenderGraphContext.contextlessTesting) - { - // If we're not testing log the exception and swallow it. - // TODO: Do we really want to swallow exceptions here? Not a very c# thing to do. - Debug.LogError(RenderGraphExceptionMessages.k_RenderGraphExecutionError); - if (!m_ExecutionExceptionWasRaised) // Already logged. TODO: There is probably a better way in C# to handle that. - { - Debug.LogException(e); - } - - m_ExecutionExceptionWasRaised = true; - } - - CommandBuffer.ThrowOnSetRenderTarget = false; - - CleanupResourcesAndGraph(); - - // If there has been an error, we have to flush the command being built along the RG data structure, - // because otherwise the command might try to use resources we just deleted. - m_RenderGraphContext.cmd.Clear(); - return m_RenderGraphContext.contextlessTesting; - } - - /// - /// Execute the Render Graph in its current state. - /// - internal void Execute() - { - m_ExecutionExceptionWasRaised = false; - m_RenderGraphState = RenderGraphState.Executing; - -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (m_RenderGraphContext.cmd == null) - throw new InvalidOperationException("RenderGraph.BeginRecording was not called before executing the render graph."); - - ClearCacheIfNewActiveDebugSession(); -#endif - - int graphHash = m_EnableCompilationCaching ? ComputeGraphHash() : 0; - - CompileNativeRenderGraph(graphHash); - - // Must be set after compilation when the compiler has been initialized - m_RenderGraphContext.compilerContext = nativeCompiler?.contextData; - - m_Resources.BeginExecute(m_CurrentFrameIndex); - -#if UNITY_EDITOR || DEVELOPMENT_BUILD - // Feeding Render Graph Viewer before resource deallocation at pass execution - GenerateDebugData(graphHash); -#endif - ExecuteNativeRenderGraph(); - - // Clear the shader bindings for all global textures to make sure bindings don't leak outside the graph - ClearGlobalBindings(); - } - - class ProfilingScopePassData - { - public ProfilingSampler sampler; - } - - const string k_BeginProfilingSamplerPassName = "BeginProfile"; - const string k_EndProfilingSamplerPassName = "EndProfile"; - - /// - /// Begin a profiling scope. - /// - /// Sampler used for profiling. - /// File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - public void BeginProfilingSampler(ProfilingSampler sampler, - [CallerFilePath] string file = "", - [CallerLineNumber] int line = 0) - { - if (sampler == null) - return; - - using (var builder = AddUnsafePass(k_BeginProfilingSamplerPassName, out var passData, (ProfilingSampler)null, file, line)) - { - passData.sampler = sampler; - builder.AllowPassCulling(false); - builder.GenerateDebugData(false); - builder.SetRenderFunc(static (ProfilingScopePassData data, UnsafeGraphContext ctx) => - { - data.sampler.Begin(CommandBufferHelpers.GetNativeCommandBuffer(ctx.cmd)); - }); - } - } - - /// - /// End a profiling scope. - /// - /// Sampler used for profiling. - /// File name of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - /// File line of the source file this function is called from. Used for debugging. This parameter is automatically generated by the compiler. Users do not need to pass it. - public void EndProfilingSampler(ProfilingSampler sampler, - [CallerFilePath] string file = "", - [CallerLineNumber] int line = 0) - { - if (sampler == null) - return; - - using (var builder = AddUnsafePass(k_EndProfilingSamplerPassName, out var passData, (ProfilingSampler)null, file, line)) - { - passData.sampler = sampler; - builder.AllowPassCulling(false); - builder.GenerateDebugData(false); - builder.SetRenderFunc(static (ProfilingScopePassData data, UnsafeGraphContext ctx) => - { - data.sampler.End(CommandBufferHelpers.GetNativeCommandBuffer(ctx.cmd)); - }); - } - } - - #endregion - - #region Internal Interface - - // Internal for testing purpose only - internal void ClearCurrentCompiledGraph() - { - ClearCompiledGraph(); - } - - void ClearCompiledGraph() - { - ClearRenderPasses(); - m_Resources.Clear(m_ExecutionExceptionWasRaised); - m_RendererLists.Clear(); - registeredGlobals.Clear(); - } - - void InvalidateContext() - { - m_RenderGraphContext.cmd = null; - m_RenderGraphContext.renderGraphPool = null; - m_RenderGraphContext.defaultResources = null; - m_RenderGraphContext.compilerContext = null; - } - - internal delegate void OnGraphRegisteredDelegate(string graphName); - internal static event OnGraphRegisteredDelegate onGraphRegistered; - internal static event OnGraphRegisteredDelegate onGraphUnregistered; - internal delegate void OnExecutionRegisteredDelegate(string graphName, EntityId executionId, string executionName); - internal static event OnExecutionRegisteredDelegate onExecutionRegistered; - #endregion - - #region Private Interface - - // Internal for testing purpose only. - internal int ComputeGraphHash() - { - using (new ProfilingScope(ProfilingSampler.Get(RenderGraphProfileId.ComputeHashRenderGraph))) - { - var hash128 = HashFNV1A32.Create(); - for (int i = 0; i < m_RenderPasses.Count; ++i) - m_RenderPasses[i].ComputeHash(ref hash128, m_Resources); - - return hash128.value; - } - } - - internal bool GetImportedFallback(TextureDesc desc, out TextureHandle fallback) - { - fallback = TextureHandle.nullHandle; - - // We don't have any fallback texture with MSAA - if (!desc.bindTextureMS) - { - if (desc.depthBufferBits != DepthBits.None) - { - fallback = defaultResources.whiteTexture; - } - else if (desc.clearColor == Color.black || desc.clearColor == default) - { - if (desc.dimension == TextureXR.dimension) - fallback = defaultResources.blackTextureXR; - else if (desc.dimension == TextureDimension.Tex3D) - fallback = defaultResources.blackTexture3DXR; - else if (desc.dimension == TextureDimension.Tex2D) - fallback = defaultResources.blackTexture; - } - else if (desc.clearColor == Color.white) - { - if (desc.dimension == TextureXR.dimension) - fallback = defaultResources.whiteTextureXR; - else if (desc.dimension == TextureDimension.Tex2D) - fallback = defaultResources.whiteTexture; - } - } - - return fallback.IsValid(); - } - - void ClearRenderPasses() - { - foreach (var pass in m_RenderPasses) - pass.Release(m_RenderGraphPool); - - m_RenderPasses.Clear(); - } - - ProfilingSampler GetDefaultProfilingSampler(string name) - { - // In non-dev builds, ProfilingSampler.Get returns null, so we'd always end up executing this. - // To avoid that we also ifdef the code out here. -#if DEVELOPMENT_BUILD || UNITY_EDITOR - int hash = name.GetHashCode(); - if (!m_DefaultProfilingSamplers.TryGetValue(hash, out var sampler)) - { - sampler = new ProfilingSampler(name); - m_DefaultProfilingSamplers.Add(hash, sampler); - } - - return sampler; -#else - return null; -#endif - } - - // Register the graph in the RenderGraph Viewer. - // Registered once on graph creation, indicates to the viewer that this render graph exists. - void RegisterGraph() - { - s_RegisteredExecutions.Add(this, new List()); - onGraphRegistered?.Invoke(this.name); - } - - // Unregister the graph from the render graph viewer. - // Should only be unregistered when it is destroyed, not before. - // Should not be called when an error happens because the existence of the graph doesn't depend on its state - // or whether it's been cleaned. - void UnregisterGraph() - { - s_RegisteredExecutions.Remove(this); - onGraphUnregistered?.Invoke(name); - } - - // Note: obj.name allocates so make sure you only call this when debug tools / options are active - static string GetExecutionNameAllocates(EntityId entityId) - { - var obj = Resources.EntityIdToObject(entityId); - return obj != null ? obj.name : $"RenderGraphExecution ({entityId})"; - } - - static bool s_DebugSessionWasActive; - - void ClearCacheIfNewActiveDebugSession() - { - if (RenderGraphDebugSession.hasActiveDebugSession && !s_DebugSessionWasActive) - { - // Invalidate cache whenever a debug session becomes active. This is because while the DebugSession is - // inactive, certain debug data (store/load/pass break audits) stored inside the compilation cache is - // not getting generated. Therefore we clear compilation cache to regenerate this debug data. - // This needs to be done before the compilation step. - m_CompilationCache?.Clear(); - } - s_DebugSessionWasActive = RenderGraphDebugSession.hasActiveDebugSession; - } - - void GenerateDebugData(int graphHash) - { - if (!RenderGraphDebugSession.hasActiveDebugSession || !m_CurrentExecutionCanGenerateDebugData || m_ExecutionExceptionWasRaised) - return; - - var registeredExecutions = s_RegisteredExecutions[this]; - - // The reverse loop prunes deleted cameras and checks if the current camera needs to be registered. - bool alreadyRegistered = false; - using var deletedExecutionIdsDisposable = ListPool.Get(out var deletedExecutionIds); - for (int i = registeredExecutions.Count - 1; i >= 0; i--) - { - DebugExecutionItem executionItem = registeredExecutions[i]; - if (Resources.EntityIdToObject(executionItem.id) == null) - { - registeredExecutions.RemoveAt(i); - deletedExecutionIds.Add(executionItem.id); - continue; - } - if (executionItem.id == m_CurrentExecutionId) - alreadyRegistered = true; - } - - var currentExecutionName = GetExecutionNameAllocates(m_CurrentExecutionId); - if (!alreadyRegistered) - { - registeredExecutions.Add(new DebugExecutionItem(m_CurrentExecutionId, currentExecutionName)); - } - - if (deletedExecutionIds.Count > 0) - RenderGraphDebugSession.DeleteExecutionIds(name, deletedExecutionIds); // Clear stale debug data entries for deleted cameras - - if (!alreadyRegistered) - { - onExecutionRegistered?.Invoke(name, m_CurrentExecutionId, currentExecutionName); - } - - // When compilation caching is disabled, we don't hash the graph. In order to avoid UI needing to update every - // frame, we still want to use the hash to know if debug data should be updated, so compute the hash now. - if (!m_EnableCompilationCaching) - graphHash = ComputeGraphHash(); - - var debugData = RenderGraphDebugSession.GetDebugData(name, m_CurrentExecutionId); - bool cameraWasRenamed = debugData.executionName != currentExecutionName; - if (debugData.valid && debugData.graphHash == graphHash && !cameraWasRenamed) - return; // No need to update - - debugData.Clear(); - debugData.executionName = currentExecutionName; - debugData.graphHash = graphHash; - - nativeCompiler.GenerateNativeCompilerDebugData(ref debugData); - - debugData.valid = true; - RenderGraphDebugSession.SetDebugData(name, m_CurrentExecutionId, debugData); - } - - #endregion - - Dictionary registeredGlobals = new Dictionary(); - - internal void SetGlobal(in TextureHandle h, int globalPropertyId) - { - if (!h.IsValid()) - throw new ArgumentException("Attempting to register an invalid texture handle as a global"); - - registeredGlobals[globalPropertyId] = h; - } - - internal bool IsGlobal(int globalPropertyId) - { - return registeredGlobals.ContainsKey(globalPropertyId); - } - - internal Dictionary.ValueCollection AllGlobals() - { - return registeredGlobals.Values; - } - - internal TextureHandle GetGlobal(int globalPropertyId) - { - TextureHandle h; - registeredGlobals.TryGetValue(globalPropertyId, out h); - return h; - } - - /// - /// Clears the shader bindings associated with the registered globals in the graph - /// - /// This prevents later rendering logic from accidentally relying on stale shader bindings that were set - /// earlier during graph execution. - /// - internal void ClearGlobalBindings() - { - // Set all the global texture shader bindings to the default black texture. - // This doesn't technically "clear" the shader bindings, but it's the closest we can do. - foreach (var globalTex in registeredGlobals) - { - m_RenderGraphContext.cmd.SetGlobalTexture(globalTex.Key, defaultResources.blackTexture); - } - } - } - - /// - /// Render Graph Scoped Profiling markers - /// - [MovedFrom(true, "UnityEngine.Experimental.Rendering.RenderGraphModule", "UnityEngine.Rendering.RenderGraphModule")] - public struct RenderGraphProfilingScope : IDisposable - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - ProfilingSampler m_Sampler; - RenderGraph m_RenderGraph; - bool m_Disposed; -#endif - - /// - /// Profiling Scope constructor - /// - /// Render Graph used for this scope. - /// Profiling Sampler to be used for this scope. - public RenderGraphProfilingScope(RenderGraph renderGraph, ProfilingSampler sampler) - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - m_RenderGraph = renderGraph; - m_Sampler = sampler; - m_Disposed = false; - renderGraph.BeginProfilingSampler(sampler); -#endif - } - - /// - /// Dispose pattern implementation - /// - public void Dispose() - { - Dispose(true); - } - - // Protected implementation of Dispose pattern. - void Dispose(bool disposing) - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (m_Disposed) - return; - - // As this is a struct, it could have been initialized using an empty constructor so we - // need to make sure `cmd` isn't null to avoid a crash. Switching to a class would fix - // this but will generate garbage on every frame (and this struct is used quite a lot). - if (disposing) - { - m_RenderGraph.EndProfilingSampler(m_Sampler); - } - - m_Disposed = true; -#endif - } - } -} - -#endif \ No newline at end of file diff --git a/Ghost.RenderGraph.Concept/Unity/RenderGraphBuilders.cs b/Ghost.RenderGraph.Concept/Unity/RenderGraphBuilders.cs deleted file mode 100644 index a4f9fd9..0000000 --- a/Ghost.RenderGraph.Concept/Unity/RenderGraphBuilders.cs +++ /dev/null @@ -1,598 +0,0 @@ -#if false - -using System; -using System.Diagnostics; -using UnityEngine.Experimental.Rendering; -using static UnityEngine.Rendering.RenderGraphModule.RenderGraph; - -namespace UnityEngine.Rendering.RenderGraphModule -{ - // This is a class making it a struct wouldn't help as we pas it around as an interface which means it would be boxed/unboxed anyway - // Publicly this class has different faces to help the users with different pass types through type safety but internally - // we just have a single implementation for all builders - internal class RenderGraphBuilders : IBaseRenderGraphBuilder, IComputeRenderGraphBuilder, IRasterRenderGraphBuilder, IUnsafeRenderGraphBuilder - { - RenderGraphPass m_RenderPass; - RenderGraphResourceRegistry m_Resources; - RenderGraph m_RenderGraph; - bool m_Disposed; - - public RenderGraphBuilders() - { - m_RenderPass = null; - m_Resources = null; - m_RenderGraph = null; - m_Disposed = true; - } - - public void Setup(RenderGraphPass renderPass, RenderGraphResourceRegistry resources, RenderGraph renderGraph) - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - // If the object is not disposed yet this is an error as the pass is not finished (only in the dispose we register it with the rendergraph) - // This is likely cause by a user not doing a clean using and then forgetting to manually dispose the object. - if (m_Disposed != true) - { - throw new Exception(RenderGraph.RenderGraphExceptionMessages.k_UndisposedBuilderPreviousPass); - } -#endif - m_RenderPass = renderPass; - m_Resources = resources; - m_RenderGraph = renderGraph; - m_Disposed = false; - - renderPass.useAllGlobalTextures = false; - - if (renderPass.type == RenderGraphPassType.Raster) - { - CommandBuffer.ThrowOnSetRenderTarget = true; - } - } - - - public void EnableAsyncCompute(bool value) - { - m_RenderPass.EnableAsyncCompute(value); - } - - public void AllowPassCulling(bool value) - { - // This pass cannot be culled if it allows global state modifications - if (value && m_RenderPass.allowGlobalState) - return; - - m_RenderPass.AllowPassCulling(value); - } - - public void AllowGlobalStateModification(bool value) - { - m_RenderPass.AllowGlobalState(value); - - // This pass cannot be culled if it allows global state modifications - if (value) - { - AllowPassCulling(false); - } - } - - /// - /// Enable foveated rendering for this pass. - /// - /// True to enable foveated rendering. - public void EnableFoveatedRasterization(bool value) - { - m_RenderPass.EnableFoveatedRasterization(value); - } - - public BufferHandle CreateTransientBuffer(in BufferDesc desc) - { - var result = m_Resources.CreateBuffer(desc, m_RenderPass.index); - UseTransientResource(result.handle); - return result; - } - - public BufferHandle CreateTransientBuffer(in BufferHandle computebuffer) - { - ref readonly var desc = ref m_Resources.GetBufferResourceDesc(computebuffer.handle); - return CreateTransientBuffer(desc); - } - - public TextureHandle CreateTransientTexture(in TextureDesc desc) - { - var result = m_Resources.CreateTexture(desc, m_RenderPass.index); - UseTransientResource(result.handle); - return result; - } - - public TextureHandle CreateTransientTexture(in TextureHandle texture) - { - ref readonly var desc = ref m_Resources.GetTextureResourceDesc(texture.handle); - return CreateTransientTexture(desc); - } - - public void GenerateDebugData(bool value) - { - m_RenderPass.GenerateDebugData(value); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (m_Disposed) - return; - - try - { - if (disposing) - { - m_RenderGraph.RenderGraphState = RenderGraphState.RecordingGraph; - - // Use all globals simply means this... we do a UseTexture on all globals so the pass has the correct dependencies. - // This of course goes to show how bad an idea shader-system wide globals really are dependency/lifetime tracking wise :-) - if (m_RenderPass.useAllGlobalTextures) - { - foreach (var texture in m_RenderGraph.AllGlobals()) - { - if (texture.IsValid()) - this.UseTexture(texture, AccessFlags.Read); - } - } - - // Set globals on the graph fronted side so subsequent passes can have pass dependencies on these global texture handles - foreach (var t in m_RenderPass.setGlobalsList) - { - m_RenderGraph.SetGlobal(t.Item1, t.Item2); - } - } - } - finally - { - if (m_RenderPass.type == RenderGraphPassType.Raster) - { - CommandBuffer.ThrowOnSetRenderTarget = false; - } - - m_RenderPass = null; - m_Resources = null; - m_RenderGraph = null; - m_Disposed = true; - } - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - private void CheckWriteTo(in ResourceHandle handle) - { - if (RenderGraph.enableValidityChecks) - { - // Write by design generates a new version of the resource. However - // you could in theory write to v2 of the resource while there is already - // a v3 so this write would then introduce a new v4 of the resource. - // This would mean a divergence in the versioning history subsequent versions based on v2 and other subsequent versions based on v3 - // this would be very confusing as they are all still refered to by the same texture just different versions - // so we decide to disallow this. It can always be (at zero cost) handled by using a "Move" pass to move the divergent version - // so it's own texture resource which again has it's own single history of versions. - if (handle.IsVersioned) - { - var name = m_Resources.GetRenderGraphResourceName(handle); - throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {handle.type} at index {handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_WriteToVersionedResource); - } - - if (m_RenderPass.IsWritten(handle)) - { - // We bump the version and write count if you call USeResource with writing flags. So calling this several times - // would lead to the side effect of new versions for every call to UseResource leading to incorrect versions. - // In theory we could detect and ignore the second UseResource but we decided to just disallow is as it might also - // Stem from user confusion. - // It seems the most likely cause of such a situation would be something like: - // TextureHandle b = a; - // ... - // much much code in between, user lost track that a=b - // ... - // builder.WriteTexture(a) - // builder.WriteTexture(b) - // > Get this error they were probably thinking they were writing two separate outputs... but they are just two versions of resource 'a' - // where they can only differ between by careful management of versioned resources. - var name = m_Resources.GetRenderGraphResourceName(handle); - throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {handle.type} at index {handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_WriteToResourceTwice); - } - } - } - - // Lifetime of a transient resource is one render graph pass - private ResourceHandle UseTransientResource(in ResourceHandle inputHandle) - { - CheckResource(inputHandle); - - ResourceHandle versionedHandle = inputHandle.IsVersioned ? inputHandle : m_Resources.GetLatestVersionHandle(inputHandle); - - // Transient resources are always considered written and read in the render graph pass where they are used - // Compiler will take it into account later, no need to add them to read and write lists - m_RenderPass.AddTransientResource(versionedHandle); - - return versionedHandle; - } - - private ResourceHandle UseResource(in ResourceHandle inputHandle, AccessFlags flags) - { - CheckResource(inputHandle); - - bool discard = (flags & AccessFlags.Discard) != 0; - bool read = (flags & AccessFlags.Read) != 0; - bool write = (flags & AccessFlags.Write) != 0; - - ResourceHandle versionedHandle = inputHandle.IsVersioned ? inputHandle : m_Resources.GetLatestVersionHandle(inputHandle); - - // If we are not discarding the current version and its data, add a "read" dependency on it - // this is a bit of a misnomer it really means more like "Preserve existing content or read" - if (!discard) - { - m_Resources.IncrementReadCount(versionedHandle); - m_RenderPass.AddResourceRead(versionedHandle); - - // Implicit read - not user-specified - if (!read) - { - m_RenderPass.implicitReadsList.Add(versionedHandle); - } - } - else - { - // We are discarding it but we still read it, so we add a dependency on version "0" of this resource - if (read) - { - var zeroVersionHandle = m_Resources.GetZeroVersionHandle(versionedHandle); - m_Resources.IncrementReadCount(zeroVersionHandle); - m_RenderPass.AddResourceRead(zeroVersionHandle); - } - } - - if (write) - { - CheckWriteTo(inputHandle); - // New versioned written by this render graph pass - versionedHandle = m_Resources.IncrementWriteCount(inputHandle); - m_RenderPass.AddResourceWrite(versionedHandle); - } - - return versionedHandle; - } - - public BufferHandle UseBuffer(in BufferHandle input, AccessFlags flags) - { - UseResource(input.handle, flags); - return input; - } - - // UseTexture and SetRenderAttachment are currently forced to be mutually exclusive in the same pass - // check this. - // We currently ignore the version. In theory there might be some cases that are actually allowed with versioning - // for ample UseTexture(myTexV1, read) UseFragment(myTexV2, ReadWrite) as they are different versions - // but for now we don't allow any of that. - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - private void CheckNotUseFragment(in TextureHandle tex) - { - if (RenderGraph.enableValidityChecks) - { - bool usedAsFragment = (m_RenderPass.depthAccess.textureHandle.IsValid() && m_RenderPass.depthAccess.textureHandle.handle.index == tex.handle.index); - if (!usedAsFragment) - { - for (int i = 0; i <= m_RenderPass.colorBufferMaxIndex; i++) - { - if (m_RenderPass.colorBufferAccess[i].textureHandle.IsValid() && m_RenderPass.colorBufferAccess[i].textureHandle.handle.index == tex.handle.index) - { - usedAsFragment = true; - break; - } - } - } - - if (usedAsFragment) - { - var name = m_Resources.GetRenderGraphResourceName(tex.handle); - throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_TextureAlreadyBeingUsedThroughSetAttachment); - } - } - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - private void CheckTextureUVOriginIsValid(in ResourceHandle handle, TextureResource texRes) - { - if (texRes.textureUVOrigin == TextureUVOriginSelection.TopLeft) - { - var name = m_Resources.GetRenderGraphResourceName(handle); - throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type `{handle.type}` at index `{handle.index}` - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOriginUseTexture(texRes.textureUVOrigin)); - } - } - - public void UseTexture(in TextureHandle input, AccessFlags flags) - { - CheckNotUseFragment(input); - UseResource(input.handle, flags); - - if ((flags & AccessFlags.Read) == AccessFlags.Read) - { - if (m_RenderGraph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation) - { - TextureResource texRes = m_Resources.GetTextureResource(input.handle); - CheckTextureUVOriginIsValid(input.handle, texRes); - texRes.textureUVOrigin = TextureUVOriginSelection.BottomLeft; - } - } - } - - public void UseGlobalTexture(int propertyId, AccessFlags flags) - { - var h = m_RenderGraph.GetGlobal(propertyId); - if (h.IsValid()) - { - UseTexture(h, flags); - } - else - { - // rose test this path - var name = m_Resources.GetRenderGraphResourceName(h.handle); - throw new ArgumentException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {h.handle.type} at index {h.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.NoGlobalTextureAtPropertyID(propertyId)); - } - } - - public void UseAllGlobalTextures(bool enable) - { - m_RenderPass.useAllGlobalTextures = enable; - } - - public void SetGlobalTextureAfterPass(in TextureHandle input, int propertyId) - { - m_RenderPass.setGlobalsList.Add(ValueTuple.Create(input, propertyId)); - } - - // Shared validation between SetRenderAttachment/SetRenderAttachmentDepth - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - private void CheckUseFragment(in TextureHandle tex, bool isDepth) - { - if (RenderGraph.enableValidityChecks) - { - // We ignore the version as we don't allow mixing UseTexture/UseFragment between different versions - // even though it should theoretically work (and we might do so in the future) for now we're overly strict. - bool alreadyUsed = false; - - //TODO: Check grab textures here and allow if it's grabbed. For now - // SetRenderAttachment() - // UseTexture(grab) - // will work but not the other way around - for (int i = 0; i < m_RenderPass.resourceReadLists[tex.handle.iType].Count; i++) - { - if (m_RenderPass.resourceReadLists[tex.handle.iType][i].index == tex.handle.index) - { - alreadyUsed = true; - break; - } - } - - for (int i = 0; i < m_RenderPass.resourceWriteLists[tex.handle.iType].Count; i++) - { - if (m_RenderPass.resourceWriteLists[tex.handle.iType][i].index == tex.handle.index) - { - alreadyUsed = true; - break; - } - } - - if (alreadyUsed) - { - var name = m_Resources.GetRenderGraphResourceName(tex.handle); - throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentTextureAlreadyUsed); - } - - m_Resources.GetRenderTargetInfo(tex.handle, out var info); - - bool isDepthFormat = GraphicsFormatUtility.IsDepthFormat(info.format); - bool formatMismatch = isDepth != isDepthFormat; - - if (formatMismatch) - { - var name = m_Resources.GetRenderGraphResourceName(tex.handle); - var errorMsg = isDepth - ? RenderGraph.RenderGraphExceptionMessages.UseDepthWithColorFormat(info.format) - : RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentOnDepthTexture; - - throw new InvalidOperationException( - $"In pass '{m_RenderPass.name}' when trying to use resource '{name}' " + - $"of type {tex.handle.type} at index {tex.handle.index} - {errorMsg}"); - } - - if (m_RenderGraph.renderTextureUVOriginStrategy == RenderTextureUVOriginStrategy.PropagateAttachmentOrientation) - { - var texResource = m_Resources.GetTextureResource(tex.handle); - TextureResource checkTexResource = null; - for (int i = 0; i < m_RenderPass.fragmentInputMaxIndex + 1; ++i) - { - if (m_RenderPass.fragmentInputAccess[i].textureHandle.IsValid()) - { - ref readonly TextureHandle texCheck = ref m_RenderPass.fragmentInputAccess[i].textureHandle; - checkTexResource = m_Resources.GetTextureResource(texCheck.handle); - if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin) - { - var name = m_Resources.GetRenderGraphResourceName(tex.handle); - var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle); - throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "input", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin)); - } - } - } - - for (int i = 0; i < m_RenderPass.colorBufferMaxIndex + 1; ++i) - { - if (m_RenderPass.colorBufferAccess[i].textureHandle.IsValid()) - { - ref readonly TextureHandle texCheck = ref m_RenderPass.colorBufferAccess[i].textureHandle; - checkTexResource = m_Resources.GetTextureResource(texCheck.handle); - if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin) - { - var name = m_Resources.GetRenderGraphResourceName(tex.handle); - var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle); - throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "render", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin)); - } - } - } - - if (!isDepth && m_RenderPass.depthAccess.textureHandle.IsValid()) - { - TextureHandle texCheck = m_RenderPass.depthAccess.textureHandle; - checkTexResource = m_Resources.GetTextureResource(texCheck.handle); - if (texResource.textureUVOrigin != TextureUVOriginSelection.Unknown && checkTexResource.textureUVOrigin != TextureUVOriginSelection.Unknown && texResource.textureUVOrigin != checkTexResource.textureUVOrigin) - { - var name = m_Resources.GetRenderGraphResourceName(tex.handle); - var checkName = m_Resources.GetRenderGraphResourceName(texCheck.handle); - throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.IncompatibleTextureUVOrigin(texResource.textureUVOrigin, "depth", checkName, texCheck.handle.type, texCheck.handle.index, checkTexResource.textureUVOrigin)); - } - } - } - - foreach (var globalTex in m_RenderPass.setGlobalsList) - { - if (globalTex.Item1.handle.index == tex.handle.index) - { - var name = m_Resources.GetRenderGraphResourceName(tex.handle); - throw new InvalidOperationException($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {tex.handle.type} at index {tex.handle.index} - " + RenderGraph.RenderGraphExceptionMessages.k_SetRenderAttachmentOnGlobalTexture); - } - } - } - } - - public void SetRenderAttachment(TextureHandle tex, int index, AccessFlags flags, int mipLevel, int depthSlice) - { - CheckUseFragment(tex, false); - var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, flags)); - m_RenderPass.SetColorBufferRaw(versionedTextureHandle, index, flags, mipLevel, depthSlice); - } - - public void SetInputAttachment(TextureHandle tex, int index, AccessFlags flags, int mipLevel, int depthSlice) - { - CheckFrameBufferFetchEmulationIsSupported(tex); - - CheckUseFragment(tex, false); - var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, flags)); - m_RenderPass.SetFragmentInputRaw(versionedTextureHandle, index, flags, mipLevel, depthSlice); - } - - public void SetRenderAttachmentDepth(TextureHandle tex, AccessFlags flags, int mipLevel, int depthSlice) - { - CheckUseFragment(tex, true); - var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, flags)); - m_RenderPass.SetDepthBufferRaw(versionedTextureHandle, flags, mipLevel, depthSlice); - } - - public TextureHandle SetRandomAccessAttachment(TextureHandle input, int index, AccessFlags flags = AccessFlags.Read) - { - CheckNotUseFragment(input); - ResourceHandle result = UseResource(input.handle, flags); - m_RenderPass.SetRandomWriteResourceRaw(result, index, false, flags); - return input; - } - - public void SetShadingRateImageAttachment(in TextureHandle tex) - { - CheckNotUseFragment(tex); - var versionedTextureHandle = new TextureHandle(UseResource(tex.handle, AccessFlags.Read)); - m_RenderPass.SetShadingRateImageRaw(versionedTextureHandle); - } - - public BufferHandle UseBufferRandomAccess(BufferHandle input, int index, AccessFlags flags = AccessFlags.Read) - { - var h = UseBuffer(input, flags); - m_RenderPass.SetRandomWriteResourceRaw(h.handle, index, true, flags); - return input; - } - - public BufferHandle UseBufferRandomAccess(BufferHandle input, int index, bool preserveCounterValue, AccessFlags flags = AccessFlags.Read) - { - var h = UseBuffer(input, flags); - m_RenderPass.SetRandomWriteResourceRaw(h.handle, index, preserveCounterValue, flags); - return input; - } - - public void SetRenderFunc(BaseRenderFunc renderFunc) where PassData : class, new() - { - ((ComputeRenderGraphPass)m_RenderPass).renderFunc = renderFunc; - } - - public void SetRenderFunc(BaseRenderFunc renderFunc) where PassData : class, new() - { - ((RasterRenderGraphPass)m_RenderPass).renderFunc = renderFunc; - } - - public void SetRenderFunc(BaseRenderFunc renderFunc) where PassData : class, new() - { - ((UnsafeRenderGraphPass)m_RenderPass).renderFunc = renderFunc; - } - - public void UseRendererList(in RendererListHandle input) - { - m_RenderPass.UseRendererList(input); - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - void CheckResource(in ResourceHandle res, bool checkTransientReadWrite = false) - { - if (RenderGraph.enableValidityChecks) - { - if (res.IsValid()) - { - int transientIndex = m_Resources.GetRenderGraphResourceTransientIndex(res); - // We have dontCheckTransientReadWrite here because users may want to use UseColorBuffer/UseDepthBuffer API to benefit from render target auto binding. In this case we don't want to raise the error. - if (transientIndex == m_RenderPass.index && checkTransientReadWrite) - { - var name = m_Resources.GetRenderGraphResourceName(res); - Debug.LogError($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {res.type} at index {res.index} - " + RenderGraph.RenderGraphExceptionMessages.k_ReadWriteTransient); - } - - if (transientIndex != -1 && transientIndex != m_RenderPass.index) - { - var name = m_Resources.GetRenderGraphResourceName(res); - throw new ArgumentException( - $"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {res.type} at index {res.index} - " + - RenderGraph.RenderGraphExceptionMessages.UseTransientTextureInWrongPass(transientIndex)); - } - } - else - { - var name = m_Resources.GetRenderGraphResourceName(res); - throw new Exception($"In pass '{m_RenderPass.name}' when trying to use resource '{name}' of type {res.type} at index {res.index} - " + RenderGraph.RenderGraphExceptionMessages.k_InvalidResource); - } - } - } - - [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")] - void CheckFrameBufferFetchEmulationIsSupported(in TextureHandle tex) - { - if (enableValidityChecks) - { - if (!Util.RenderGraphUtils.IsFramebufferFetchEmulationMSAASupportedOnCurrentPlatform()) - { - var sourceInfo = m_RenderGraph.GetRenderTargetInfo(tex); - if (sourceInfo.bindMS) - throw new InvalidOperationException($"This API is not supported with MSAA attachments on the current platform: {SystemInfo.graphicsDeviceType}"); - } - } - } - - public void SetShadingRateFragmentSize(ShadingRateFragmentSize shadingRateFragmentSize) - { - m_RenderPass.SetShadingRateFragmentSize(shadingRateFragmentSize); - } - - public void SetShadingRateCombiner(ShadingRateCombinerStage stage, ShadingRateCombiner combiner) - { - m_RenderPass.SetShadingRateCombiner(stage, combiner); - } - - public void SetExtendedFeatureFlags(ExtendedFeatureFlags extendedFeatureFlags) - { - m_RenderPass.SetExtendedFeatureFlags(extendedFeatureFlags); - } - } -} - -#endif \ No newline at end of file diff --git a/Ghost.RenderGraph.Concept/Unity/RenderGraphPass.cs b/Ghost.RenderGraph.Concept/Unity/RenderGraphPass.cs deleted file mode 100644 index 723ff89..0000000 --- a/Ghost.RenderGraph.Concept/Unity/RenderGraphPass.cs +++ /dev/null @@ -1,752 +0,0 @@ -#if false - -using System; -using System.Diagnostics; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace UnityEngine.Rendering.RenderGraphModule -{ - [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")] - abstract class RenderGraphPass - { - public abstract void Execute(InternalRenderGraphContext renderGraphContext); - public abstract void Release(RenderGraphObjectPool pool); - public abstract bool HasRenderFunc(); - public abstract int GetRenderFuncHash(); - - public string name { get; protected set; } - public int index { get; protected set; } - public RenderGraphPassType type { get; internal set; } - public ProfilingSampler customSampler { get; protected set; } - public bool enableAsyncCompute { get; protected set; } - public bool allowPassCulling { get; protected set; } - public bool allowGlobalState { get; protected set; } - public bool enableFoveatedRasterization { get; protected set; } - public ExtendedFeatureFlags extendedFeatureFlags { get; protected set; } - - // Before using the AccessFlags use resourceHandle.isValid() - // to make sure that the data in the colorBuffer/fragmentInput/randomAccessResource buffers are up to date - public TextureAccess depthAccess { get; protected set; } - - public TextureAccess[] colorBufferAccess { get; protected set; } = new TextureAccess[RenderGraph.kMaxMRTCount]; - public int colorBufferMaxIndex { get; protected set; } = -1; - - public bool hasShadingRateImage { get; protected set; } - public TextureAccess shadingRateAccess { get; protected set; } - - public bool hasShadingRateStates { get; protected set; } - public ShadingRateFragmentSize shadingRateFragmentSize { get; protected set; } - public ShadingRateCombiner primitiveShadingRateCombiner { get; protected set; } - public ShadingRateCombiner fragmentShadingRateCombiner { get; protected set; } - - // Used by native pass compiler only - public TextureAccess[] fragmentInputAccess { get; protected set; } = new TextureAccess[RenderGraph.kMaxMRTCount]; - public int fragmentInputMaxIndex { get; protected set; } = -1; - - public struct RandomWriteResourceInfo - { - public ResourceHandle h; - public bool preserveCounterValue; - } - - // This list can contain both texture and buffer resources based on their binding index. - public RandomWriteResourceInfo[] randomAccessResource { get; protected set; } = new RandomWriteResourceInfo[RenderGraph.kMaxMRTCount]; - public int randomAccessResourceMaxIndex { get; protected set; } = -1; - - public bool generateDebugData { get; protected set; } - - public bool allowRendererListCulling { get; protected set; } - - public List[] resourceReadLists = new List[(int)RenderGraphResourceType.Count]; - public List[] resourceWriteLists = new List[(int)RenderGraphResourceType.Count]; - public List[] transientResourceList = new List[(int)RenderGraphResourceType.Count]; - - public List usedRendererListList = new List(); - - public List> setGlobalsList = new List>(); - public bool useAllGlobalTextures; - - public List implicitReadsList = new List(); - -#if UNITY_EDITOR || DEVELOPMENT_BUILD - public RenderGraph.DebugData.PassScriptInfo debugScriptInfo { get; set; } -#endif - - public RenderGraphPass() - { - for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) - { - resourceReadLists[i] = new List(); - resourceWriteLists[i] = new List(); - transientResourceList[i] = new List(); - } - } - - public void Clear() - { - name = ""; - index = -1; - customSampler = null; - for (int i = 0; i < (int)RenderGraphResourceType.Count; ++i) - { - resourceReadLists[i].Clear(); - resourceWriteLists[i].Clear(); - transientResourceList[i].Clear(); - } - - usedRendererListList.Clear(); - setGlobalsList.Clear(); - useAllGlobalTextures = false; - implicitReadsList.Clear(); - enableAsyncCompute = false; - allowPassCulling = true; - allowRendererListCulling = true; - allowGlobalState = false; - enableFoveatedRasterization = false; - generateDebugData = true; - - // Invalidate the buffers without clearing them, as it is too costly - // Use IsValid() to make sure that the data in the colorBuffer/fragmentInput/randomAccessResource buffers are up to date - colorBufferMaxIndex = -1; - fragmentInputMaxIndex = -1; - randomAccessResourceMaxIndex = -1; - - // We do not need to clear colorBufferAccess and fragmentInputAccess as we have the colorBufferMaxIndex and fragmentInputMaxIndex - // which are reset above so we only clear depthAccess here. - depthAccess = default(TextureAccess); - - hasShadingRateImage = false; - hasShadingRateStates = false; - shadingRateFragmentSize = ShadingRateFragmentSize.FragmentSize1x1; - primitiveShadingRateCombiner = ShadingRateCombiner.Keep; - fragmentShadingRateCombiner = ShadingRateCombiner.Keep; - - // Invalidate ExtendedFeatureFlags - extendedFeatureFlags = ExtendedFeatureFlags.None; - } - - // Check if the pass has any render targets set-up - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool HasRenderAttachments() - { - return depthAccess.textureHandle.IsValid() || colorBufferAccess[0].textureHandle.IsValid() || colorBufferMaxIndex > 0; - } - - // Checks if the resource is involved in this pass - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsTransient(in ResourceHandle res) - { - // Versioning doesn't matter much for transient resources as they are only used within a single pass - for (int i = 0; i < transientResourceList[res.iType].Count; i++) - { - if (transientResourceList[res.iType][i].index == res.index) - { - return true; - } - } - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsWritten(in ResourceHandle res) - { - // You can only ever write to the latest version so we ignore it when looking in the list - for (int i = 0; i < resourceWriteLists[res.iType].Count; i++) - { - if (resourceWriteLists[res.iType][i].index == res.index) - { - return true; - } - } - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsRead(in ResourceHandle res) - { - if (res.IsVersioned) - { - return resourceReadLists[res.iType].Contains(res); - } - else - { - // Just look if we are reading any version of this texture. - // Note that in theory this pass could read from several versions of the same texture - // e.g. ColorBuffer,v3 and ColorBuffer,v5 so this check is always conservative - for (int i = 0; i < resourceReadLists[res.iType].Count; i++) - { - if (resourceReadLists[res.iType][i].index == res.index) - { - return true; - } - } - return false; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsAttachment(in TextureHandle res) - { - // We ignore the version when checking, if any version is used it is considered a match - - if (depthAccess.textureHandle.IsValid() && depthAccess.textureHandle.handle.index == res.handle.index) return true; - for (int i = 0; i < colorBufferAccess.Length; i++) - { - if (colorBufferAccess[i].textureHandle.IsValid() && colorBufferAccess[i].textureHandle.handle.index == res.handle.index) return true; - } - - return false; - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddResourceWrite(in ResourceHandle res) - { - resourceWriteLists[res.iType].Add(res); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddResourceRead(in ResourceHandle res) - { - resourceReadLists[res.iType].Add(res); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddTransientResource(in ResourceHandle res) - { - transientResourceList[res.iType].Add(res); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UseRendererList(in RendererListHandle rendererList) - { - usedRendererListList.Add(rendererList); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EnableAsyncCompute(bool value) - { - enableAsyncCompute = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AllowPassCulling(bool value) - { - allowPassCulling = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void EnableFoveatedRasterization(bool value) - { - enableFoveatedRasterization = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AllowRendererListCulling(bool value) - { - allowRendererListCulling = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AllowGlobalState(bool value) - { - allowGlobalState = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void GenerateDebugData(bool value) - { - generateDebugData = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetColorBuffer(in TextureHandle resource, int index) - { - Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0); - colorBufferMaxIndex = Math.Max(colorBufferMaxIndex, index); - colorBufferAccess[index] = new TextureAccess(colorBufferAccess[index], resource); - AddResourceWrite(resource.handle); - } - - // Sets up the color buffer for this pass but not any resource Read/Writes for it - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetColorBufferRaw(in TextureHandle resource, int index, AccessFlags accessFlags, int mipLevel, int depthSlice) - { - Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0); - if (colorBufferAccess[index].textureHandle.handle.Equals(resource.handle) || !colorBufferAccess[index].textureHandle.IsValid()) - { - colorBufferMaxIndex = Math.Max(colorBufferMaxIndex, index); - colorBufferAccess[index] = new TextureAccess(resource, accessFlags, mipLevel, depthSlice); - } - else - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - // You tried to do SetRenderAttachment(tex1, 1, ..); SetRenderAttachment(tex2, 1, ..); that is not valid for different textures on the same index - throw new InvalidOperationException( - $"In pass '{name}' when trying to call SetRenderAttachment with resource of type {resource.handle.type} at index {index} - " + - RenderGraph.RenderGraphExceptionMessages.k_MoreThanOneResourceForMRTIndex); -#endif - } - } - - // Sets up the color buffer for this pass but not any resource Read/Writes for it - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetFragmentInputRaw(in TextureHandle resource, int index, AccessFlags accessFlags, int mipLevel, int depthSlice) - { - Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0); - if (fragmentInputAccess[index].textureHandle.handle.Equals(resource.handle) || !fragmentInputAccess[index].textureHandle.IsValid()) - { - fragmentInputMaxIndex = Math.Max(fragmentInputMaxIndex, index); - fragmentInputAccess[index] = new TextureAccess(resource, accessFlags, mipLevel, depthSlice); - } - else - { -#if DEVELOPMENT_BUILD || UNITY_EDITOR - // You tried to do SetRenderAttachment(tex1, 1, ..); SetRenderAttachment(tex2, 1, ..); that is not valid for different textures on the same index - throw new InvalidOperationException( - $"In pass '{name}' when trying to call SetInputAttachment with resource of type {resource.handle.type} at index {index} - " + - RenderGraph.RenderGraphExceptionMessages.k_MoreThanOneTextureForFragInputIndex); -#endif - } - } - - // Sets up the color buffer for this pass but not any resource Read/Writes for it - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetRandomWriteResourceRaw(in ResourceHandle resource, int index, bool preserveCounterValue, AccessFlags accessFlags) - { - Debug.Assert(index < RenderGraph.kMaxMRTCount && index >= 0); - if (randomAccessResource[index].h.Equals(resource) || !randomAccessResource[index].h.IsValid()) - { - randomAccessResourceMaxIndex = Math.Max(randomAccessResourceMaxIndex, index); - ref var info = ref randomAccessResource[index]; - info.h = resource; - info.preserveCounterValue = preserveCounterValue; - } - else - { - // You tried to do SetRenderAttachment(tex1, 1, ..); SetRenderAttachment(tex2, 1, ..); that is not valid for different textures on the same index - throw new InvalidOperationException( - $"In pass '{name}' when trying to call SetRandomAccessAttachment/UseBufferRandomAccess with resource of type {resource.type} at index {index} - " + - RenderGraph.RenderGraphExceptionMessages.k_MoreThanOneTextureRandomWriteInputIndex); - } - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetDepthBuffer(in TextureHandle resource, DepthAccess flags) - { - depthAccess = new TextureAccess(resource, (AccessFlags)flags, 0, 0); - if ((flags & DepthAccess.Read) != 0) - AddResourceRead(resource.handle); - if ((flags & DepthAccess.Write) != 0) - AddResourceWrite(resource.handle); - } - - // Sets up the depth buffer for this pass but not any resource Read/Writes for it - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetDepthBufferRaw(in TextureHandle resource, AccessFlags accessFlags, int mipLevel, int depthSlice) - { - // If no depth buffer yet or if it is the same one as the previous one, allow the call otherwise log an error. - if (depthAccess.textureHandle.handle.Equals(resource.handle) || !depthAccess.textureHandle.IsValid()) - { - depthAccess = new TextureAccess(resource, accessFlags, mipLevel, depthSlice); - } -#if DEVELOPMENT_BUILD || UNITY_EDITOR - else - { - throw new InvalidOperationException( - $"In pass '{name}' when trying to call SetRenderAttachmentDepth with resource of type {resource.handle.type} at index {index} - " + - RenderGraph.RenderGraphExceptionMessages.k_MultipleDepthTextures); - } -#endif - } - - // Here we want to keep computation to a minimum and only hash what will influence NRP compiler: Pass merging, load/store actions etc. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void ComputeTextureHash(ref HashFNV1A32 generator, in ResourceHandle handle, RenderGraphResourceRegistry resources) - { - if (handle.index == 0) - return; - - if (resources.IsRenderGraphResourceImported(handle)) - { - var res = resources.GetTextureResource(handle); - var graphicsResource = res.graphicsResource; - ref readonly var desc = ref res.desc; - - var externalTexture = graphicsResource.externalTexture; - if (externalTexture != null) // External texture - { - generator.Append((int) externalTexture.graphicsFormat); - generator.Append((int) externalTexture.dimension); - generator.Append(externalTexture.width); - generator.Append(externalTexture.height); - if (externalTexture is RenderTexture externalRT) - generator.Append(externalRT.antiAliasing); - } - else if (graphicsResource.rt != null) // Regular RTHandle - { - var rt = graphicsResource.rt; - generator.Append((int) rt.graphicsFormat); - generator.Append((int) rt.dimension); - generator.Append(rt.antiAliasing); - if (graphicsResource.useScaling) - if (graphicsResource.scaleFunc != null) - generator.Append(DelegateHashCodeUtils.GetFuncHashCode(graphicsResource.scaleFunc)); - else - generator.Append(graphicsResource.scaleFactor); - else - { - generator.Append(rt.width); - generator.Append(rt.height); - } - } - else if (graphicsResource.nameID != default) // External RTI - { - // The only info we have is from the provided desc upon importing. - generator.Append((int) desc.format); - generator.Append((int) desc.dimension); - generator.Append((int) desc.msaaSamples); - generator.Append(desc.width); - generator.Append(desc.height); - } - - // Add the clear/discard buffer flags to the hash (used in all the cases above) - generator.Append(desc.clearBuffer); - generator.Append(desc.discardBuffer); - } - else - { - ref readonly var desc = ref resources.GetTextureResourceDesc(handle); - generator.Append((int) desc.format); - generator.Append((int) desc.dimension); - generator.Append((int) desc.msaaSamples); - generator.Append(desc.clearBuffer); - generator.Append(desc.discardBuffer); - switch (desc.sizeMode) - { - case TextureSizeMode.Explicit: - generator.Append(desc.width); - generator.Append(desc.height); - break; - case TextureSizeMode.Scale: - generator.Append(desc.scale); - break; - case TextureSizeMode.Functor: - generator.Append(DelegateHashCodeUtils.GetFuncHashCode(desc.func)); - break; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void ComputeHashForTextureAccess(ref HashFNV1A32 generator, in ResourceHandle handle, in TextureAccess textureAccess) - { - generator.Append(handle.index); - generator.Append((int) textureAccess.flags); - generator.Append(textureAccess.mipLevel); - generator.Append(textureAccess.depthSlice); - } - - // This function is performance sensitive. - // Avoid mass function calls to get the hashCode and compute locally instead. - public void ComputeHash(ref HashFNV1A32 generator, RenderGraphResourceRegistry resources) - { - generator.Append((int) type); - generator.Append(enableAsyncCompute); - generator.Append(allowPassCulling); - generator.Append(allowGlobalState); - generator.Append(enableFoveatedRasterization); - generator.Append(extendedFeatureFlags); - - var depthHandle = depthAccess.textureHandle.handle; - if (depthHandle.IsValid()) - { - ComputeTextureHash(ref generator, depthHandle, resources); - ComputeHashForTextureAccess(ref generator, depthHandle, depthAccess); - } - - for (int i = 0; i < colorBufferMaxIndex + 1; ++i) - { - var colorBufferAccessElement = colorBufferAccess[i]; - var handle = colorBufferAccessElement.textureHandle.handle; - if (!handle.IsValid()) - continue; - - ComputeTextureHash(ref generator, handle, resources); - ComputeHashForTextureAccess(ref generator, handle, colorBufferAccessElement); - } - - generator.Append(colorBufferMaxIndex); - - generator.Append(hasShadingRateImage); - if (hasShadingRateImage) - { - var handle = shadingRateAccess.textureHandle.handle; - if (handle.IsValid()) - { - ComputeTextureHash(ref generator, handle, resources); - ComputeHashForTextureAccess(ref generator, handle, shadingRateAccess); - } - } - - generator.Append(hasShadingRateStates); - generator.Append((int)shadingRateFragmentSize); - generator.Append((int)primitiveShadingRateCombiner); - generator.Append((int)fragmentShadingRateCombiner); - - for (int i = 0; i < fragmentInputMaxIndex + 1; ++i) - { - var fragmentInputAccessElement = fragmentInputAccess[i]; - var handle = fragmentInputAccessElement.textureHandle.handle; - if (!handle.IsValid()) - continue; - - ComputeTextureHash(ref generator, handle, resources); - ComputeHashForTextureAccess(ref generator, handle, fragmentInputAccessElement); - } - - for (int i = 0; i < randomAccessResourceMaxIndex + 1; ++i) - { - var rar = randomAccessResource[i]; - if (!rar.h.IsValid()) - continue; - - generator.Append(rar.h.index); - generator.Append(rar.preserveCounterValue); - } - generator.Append(randomAccessResourceMaxIndex); - generator.Append(fragmentInputMaxIndex); - generator.Append(generateDebugData); - generator.Append(allowRendererListCulling); - - for (int resType = 0; resType < (int)RenderGraphResourceType.Count; resType++) - { - var resourceReads = resourceReadLists[resType]; - var resourceReadsCount = resourceReads.Count; - for (int i = 0; i < resourceReadsCount; ++i) - generator.Append(resourceReads[i].index); - - var resourceWrites = resourceWriteLists[resType]; - var resourceWritesCount = resourceWrites.Count; - for (int i = 0; i < resourceWritesCount; ++i) - generator.Append(resourceWrites[i].index); - - var resourceTransient = transientResourceList[resType]; - var resourceTransientCount = resourceTransient.Count; - for (int i = 0; i < resourceTransientCount; ++i) - generator.Append(resourceTransient[i].index); - } - - var usedRendererListListCount = usedRendererListList.Count; - for (int i = 0; i < usedRendererListListCount; ++i) - generator.Append(usedRendererListList[i].handle); - - var setGlobalsListCount = setGlobalsList.Count; - for (int i = 0; i < setGlobalsListCount; ++i) - { - var global = setGlobalsList[i]; - generator.Append(global.Item1.handle.index); - generator.Append(global.Item2); - } - generator.Append(useAllGlobalTextures); - - var implicitReadsListCount = implicitReadsList.Count; - for (int i = 0; i < implicitReadsListCount; ++i) - generator.Append(implicitReadsList[i].index); - - generator.Append(GetRenderFuncHash()); - } - - public void SetShadingRateImageRaw(in TextureHandle shadingRateImage) - { - if (ShadingRateInfo.supportsPerImageTile) - { - hasShadingRateImage = true; - // shading rate image access flag is always read, only 1 mip and 1 slice - shadingRateAccess = new TextureAccess(shadingRateImage, AccessFlags.Read, 0, 0); - } - } - - public void SetShadingRateImage(in TextureHandle shadingRateImage, AccessFlags accessFlags, int mipLevel, int depthSlice) - { - if (ShadingRateInfo.supportsPerImageTile) - { - hasShadingRateImage = true; - shadingRateAccess = new TextureAccess(shadingRateImage, accessFlags, mipLevel, depthSlice); - AddResourceRead(shadingRateAccess.textureHandle.handle); - } - } - - public void SetShadingRateFragmentSize(ShadingRateFragmentSize shadingRateFragmentSize) - { - if (ShadingRateInfo.supportsPerDrawCall) - { - hasShadingRateStates = true; - this.shadingRateFragmentSize = shadingRateFragmentSize; - } - } - - public void SetShadingRateCombiner(ShadingRateCombinerStage stage, ShadingRateCombiner combiner) - { - if (ShadingRateInfo.supportsPerImageTile) - { - switch (stage) - { - case ShadingRateCombinerStage.Primitive: - hasShadingRateStates = true; - primitiveShadingRateCombiner = combiner; - break; - - case ShadingRateCombinerStage.Fragment: - hasShadingRateStates = true; - fragmentShadingRateCombiner = combiner; - break; - } - } - } - - public void SetExtendedFeatureFlags(ExtendedFeatureFlags value) - { - extendedFeatureFlags |= value; - } - } - - // This used to have an extra generic argument 'RenderGraphContext' abstracting the context and avoiding - // the RenderGraphPass/ComputeRenderGraphPass/RasterRenderGraphPass/UnsafeRenderGraphPass classes below - // but this confuses IL2CPP and causes garbage when boxing the context created (even though they are structs) - [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")] - internal abstract class BaseRenderGraphPass : RenderGraphPass - where PassData : class, new() - { - internal PassData data; - internal BaseRenderFunc renderFunc; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Initialize(int passIndex, PassData passData, string passName, RenderGraphPassType passType, ProfilingSampler sampler) - { - Clear(); - index = passIndex; - data = passData; - name = passName; - type = passType; - customSampler = sampler; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Release(RenderGraphObjectPool pool) - { - pool.Release(data); - data = null; - renderFunc = null; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool HasRenderFunc() - { - return renderFunc != null; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetRenderFuncHash() - { - return renderFunc != null ? DelegateHashCodeUtils.GetFuncHashCode(renderFunc) : 0; - } - } - - [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")] - [Obsolete("RenderGraphPass is deprecated, use RasterRenderGraphPass/ComputeRenderGraphPass/UnsafeRenderGraphPass instead.")] - internal sealed class RenderGraphPass : BaseRenderGraphPass - where PassData : class, new() - { - internal static RenderGraphContext c = new RenderGraphContext(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Execute(InternalRenderGraphContext renderGraphContext) - { - c.FromInternalContext(renderGraphContext); - renderFunc(data, c); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Release(RenderGraphObjectPool pool) - { - base.Release(pool); - - // We need to do the release from here because we need the final type. - pool.Release(this); - } - } - - [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")] - internal sealed class ComputeRenderGraphPass : BaseRenderGraphPass - where PassData : class, new() - { - internal static ComputeGraphContext c = new ComputeGraphContext(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Execute(InternalRenderGraphContext renderGraphContext) - { - c.FromInternalContext(renderGraphContext); - renderFunc(data, c); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Release(RenderGraphObjectPool pool) - { - base.Release(pool); - - // We need to do the release from here because we need the final type. - pool.Release(this); - } - } - - [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")] - internal sealed class RasterRenderGraphPass : BaseRenderGraphPass - where PassData : class, new() - { - internal static RasterGraphContext c = new RasterGraphContext(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Execute(InternalRenderGraphContext renderGraphContext) - { - c.FromInternalContext(renderGraphContext); - renderFunc(data, c); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Release(RenderGraphObjectPool pool) - { - base.Release(pool); - - // We need to do the release from here because we need the final type. - pool.Release(this); - } - } - - [DebuggerDisplay("RenderPass: {name} (Index:{index} Async:{enableAsyncCompute})")] - internal sealed class UnsafeRenderGraphPass : BaseRenderGraphPass - where PassData : class, new() - { - internal static UnsafeGraphContext c = new UnsafeGraphContext(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Execute(InternalRenderGraphContext renderGraphContext) - { - c.FromInternalContext(renderGraphContext); - renderFunc(data, c); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Release(RenderGraphObjectPool pool) - { - base.Release(pool); - - // We need to do the release from here because we need the final type. - pool.Release(this); - } - } -} - -#endif \ No newline at end of file