From d44ec0be3139b014dc7e233e7fd8762372179e97 Mon Sep 17 00:00:00 2001 From: Misaki Date: Mon, 23 Mar 2026 20:48:08 +0900 Subject: [PATCH] feat(d3d12): unify resource mgmt & add pooling system Refactored D3D12 resource and command management with a new D3D12Object base class for unified lifetime and naming of COM objects. Introduced pooled command buffer and resource management in D3D12GraphicsEngine and ResourceManager, using frame-based return queues for safe reuse. Updated RenderSystem to use pooled command buffers and render requests, and to properly dispose of per-frame resources. Changed frame synchronization and resource release logic to use ulong fence/frame values for improved robustness. Refactored swap chain to DXGISwapChain and improved error handling and code clarity. Removed renderer management from IGraphicsEngine. Changed ResourceDesc, TextureDesc, and BufferDesc to record structs with equality and hashing for pooling. BREAKING CHANGE: Renderer management APIs removed from IGraphicsEngine. Frame and resource synchronization now use ulong instead of uint. Resource pooling and command buffer pooling are now required for correct usage. --- .../Systems/RenderExtractionSystem.cs | 7 +- .../D3D12CommandAllocator.cs | 29 ++- .../D3D12CommandBuffer.cs | 174 +++++++----------- .../Ghost.Graphics.D3D12/D3D12CommandQueue.cs | 67 +++---- .../D3D12DescriptorHeap.cs | 14 +- .../D3D12GraphicsEngine.cs | 84 +++++---- .../Ghost.Graphics.D3D12/D3D12Object.cs | 75 ++++++++ .../D3D12PipelineLibrary.cs | 71 +++---- .../Ghost.Graphics.D3D12/D3D12RenderDevice.cs | 78 ++++---- .../D3D12ResourceAllocator.cs | 20 +- .../D3D12ResourceDatabase.cs | 22 +-- .../{D3D12SwapChain.cs => DXGISwapChain.cs} | 75 ++++---- .../Utilities/D3D12Utility.cs | 2 +- src/Runtime/Ghost.Graphics.RHI/Common.cs | 33 +++- .../Ghost.Graphics.RHI/IGraphicsEngine.cs | 42 ++--- .../Ghost.Graphics.RHI/IPipelineLibrary.cs | 5 - src/Runtime/Ghost.Graphics.RHI/IRHIObject.cs | 6 + src/Runtime/Ghost.Graphics.RHI/IRenderer.cs | 3 + .../Ghost.Graphics/Core/RenderRequest.cs | 45 +++-- .../GhostRenderPipeline.Test.cs | 3 +- src/Runtime/Ghost.Graphics/RenderSystem.cs | 86 +++++++-- src/Runtime/Ghost.Graphics/ResourceManager.cs | 122 ++++++++++-- 22 files changed, 623 insertions(+), 440 deletions(-) create mode 100644 src/Runtime/Ghost.Graphics.D3D12/D3D12Object.cs rename src/Runtime/Ghost.Graphics.D3D12/{D3D12SwapChain.cs => DXGISwapChain.cs} (89%) create mode 100644 src/Runtime/Ghost.Graphics.RHI/IRHIObject.cs diff --git a/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs b/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs index 210fe08..6941944 100644 --- a/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs +++ b/src/Runtime/Ghost.Engine/Systems/RenderExtractionSystem.cs @@ -5,7 +5,6 @@ using Ghost.Graphics; using Ghost.Graphics.Core; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; -using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics.Geometry; @@ -79,8 +78,6 @@ public class RenderExtractionSystem : ISystem ref var cameraQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_cameraQueryID); ref var meshQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_meshQueryID); - var renderRequests = new UnsafeList(cameraQuery.CalculateEntityCount(), Allocator.Temp); - // TODO: We should extract the render record for each camera because different cameras may have different culling results. foreach (var (cam, camLtw) in cameraQuery.GetComponentIterator()) { @@ -240,10 +237,8 @@ public class RenderExtractionSystem : ISystem }, }; - renderRequests.Add(request); + _renderSystem.AddRenderRequest(request); } - - _renderPipeline.Render(new RenderContext(), renderRequests.AsSpan()); } public void Cleanup(ref readonly SystemAPI systemAPI) diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandAllocator.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandAllocator.cs index 318e5ea..bf84c44 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandAllocator.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandAllocator.cs @@ -1,33 +1,26 @@ using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.RHI; -using Misaki.HighPerformance.LowLevel; using TerraFX.Interop.DirectX; namespace Ghost.Graphics.D3D12; -internal unsafe class D3D12CommandAllocator : ICommandAllocator +internal unsafe class D3D12CommandAllocator : D3D12Object, ICommandAllocator { - private UniquePtr _allocator; - - public SharedPtr NativeAllocator => _allocator.Share(); - - public D3D12CommandAllocator(D3D12RenderDevice device, CommandBufferType type) + private static ID3D12CommandAllocator* CreateCommandAllocator(ID3D12Device14* device, D3D12_COMMAND_LIST_TYPE type) { ID3D12CommandAllocator* pAllocator = default; - var commandListType = D3D12Utility.ToCommandListType(type); + ThrowIfFailed(device->CreateCommandAllocator(type, __uuidof(pAllocator), (void**)&pAllocator)); + return pAllocator; + } - device.NativeDevice.Get()->CreateCommandAllocator(commandListType, __uuidof(pAllocator), (void**)&pAllocator); - - _allocator.Attach(pAllocator); + public D3D12CommandAllocator(D3D12RenderDevice device, CommandBufferType type) + : base(CreateCommandAllocator(device.NativeObject, D3D12Utility.ToCommandListType(type))) + { } public void Reset() { - _allocator.Get()->Reset(); + AssertNotDisposed(); + pNativeObject->Reset(); } - - public void Dispose() - { - _allocator.Dispose(); - } -} \ No newline at end of file +} diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs index 777b4e4..1aac2d4 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandBuffer.cs @@ -14,10 +14,8 @@ using static TerraFX.Aliases.DXGI_Alias; namespace Ghost.Graphics.D3D12; -internal unsafe class D3D12CommandBuffer : ICommandBuffer +internal unsafe class D3D12CommandBuffer : D3D12Object, ICommandBuffer { - private UniquePtr _commandList; - private readonly D3D12PipelineLibrary _pipelineLibrary; private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceAllocator _resourceAllocator; @@ -27,46 +25,29 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer private CommandError _lastError; private ushort _commandCount; private bool _isRecording; - private bool _disposed; - - public SharedPtr NativeCommandList => _commandList.Get(); public CommandBufferType Type => _type; public bool IsEmpty => _commandCount == 0; - public string Name + private static ID3D12GraphicsCommandList10* CreateCommandList(ID3D12Device14* device, D3D12_COMMAND_LIST_TYPE type) { - get => field; - set - { - if (field == value) - { - return; - } - - field = value; - _commandList.Get()->SetName(value); - } - } = string.Empty; + ID3D12GraphicsCommandList10* pCommandList = default; + ThrowIfFailed(device->CreateCommandList1(0u, type, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof(pCommandList), (void**)&pCommandList)); + return pCommandList; + } public D3D12CommandBuffer( D3D12RenderDevice device, - D3D12PipelineLibrary stateController, + D3D12PipelineLibrary pipelineLibrary, D3D12ResourceDatabase resourceDatabase, D3D12ResourceAllocator resourceAllocator, D3D12DescriptorAllocator descriptorAllocator, CommandBufferType type) + :base (CreateCommandList(device.NativeObject, D3D12Utility.ToCommandListType(type))) { _type = type; - ID3D12GraphicsCommandList10* pCommandList = default; - var commandListType = D3D12Utility.ToCommandListType(type); - - device.NativeDevice.Get()->CreateCommandList1(0u, commandListType, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof(pCommandList), (void**)&pCommandList); - - _commandList.Attach(pCommandList); - - _pipelineLibrary = stateController; + _pipelineLibrary = pipelineLibrary; _resourceDatabase = resourceDatabase; _resourceAllocator = resourceAllocator; _descriptorAllocator = descriptorAllocator; @@ -74,18 +55,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _isRecording = false; } - ~D3D12CommandBuffer() - { - Dispose(); - } - [Conditional("DEBUG")] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ThrowIfDisposed() - { - ObjectDisposedException.ThrowIf(_disposed, this); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ThrowIfRecording() { @@ -95,6 +65,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer } } + [Conditional("DEBUG")] [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ThrowIfNotRecording() { @@ -133,7 +104,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer throw new ArgumentException("Invalid command allocator type", nameof(allocator)); } - ThrowIfFailed(_commandList.Get()->Reset(d3d12Allocator.NativeAllocator, null)); + ThrowIfFailed(pNativeObject->Reset(d3d12Allocator.NativeObject, null)); if (Type == CommandBufferType.Graphics || Type == CommandBufferType.Compute) { @@ -142,7 +113,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer var heaps = stackalloc ID3D12DescriptorHeap*[2]; heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Bindless resource Heap heaps[1] = _descriptorAllocator.GetSamplerHeap(); // Bindless sampler Heap - _commandList.Get()->SetDescriptorHeaps(2, heaps); + pNativeObject->SetDescriptorHeaps(2, heaps); } _commandCount = 0; @@ -154,7 +125,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer ThrowIfDisposed(); ThrowIfNotRecording(); - _commandList.Get()->Close(); + pNativeObject->Close(); _isRecording = false; #if !DEBUG @@ -169,7 +140,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void SetScissorRect(RectDesc rect) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -180,12 +151,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer IncrementCommandCount(); var d3d12Rect = new RECT((int)rect.Left, (int)rect.Top, (int)rect.Right, (int)rect.Bottom); - _commandList.Get()->RSSetScissorRects(1, &d3d12Rect); + pNativeObject->RSSetScissorRects(1, &d3d12Rect); } public void ResourceBarrier(params ReadOnlySpan barrierDescs) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -333,12 +304,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer groupCount++; } - _commandList.Get()->Barrier(groupCount, groups); + pNativeObject->Barrier(groupCount, groups); } public void SetRenderTargets(ReadOnlySpan> renderTargets, Handle depthTarget) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -386,12 +357,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer pDsvHandle[0] = _descriptorAllocator.GetCpuHandle(viewGroup.dsv); } - _commandList.Get()->OMSetRenderTargets(rtvCount, pRtvHandles, FALSE, pDsvHandle); + pNativeObject->OMSetRenderTargets(rtvCount, pRtvHandles, FALSE, pDsvHandle); } public void ClearRenderTargetView(Handle renderTarget, Color128 clearColor) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -411,12 +382,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer ref var record = ref recordResult.Value; var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv); - _commandList.Get()->ClearRenderTargetView(cpuHandle, (float*)&clearColor, 0, null); + pNativeObject->ClearRenderTargetView(cpuHandle, (float*)&clearColor, 0, null); } public void ClearDepthStencilView(Handle depthStencil, bool inlcudeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -437,7 +408,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv); var flag = (inlcudeDepth ? D3D12_CLEAR_FLAG_DEPTH : 0) | (includeStencil ? D3D12_CLEAR_FLAG_STENCIL : 0); - _commandList.Get()->ClearDepthStencilView(cpuHandle, + pNativeObject->ClearDepthStencilView(cpuHandle, flag, clearDepth, clearStencil, @@ -447,7 +418,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void BeginRenderPass(ReadOnlySpan rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -602,13 +573,13 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer pDsvDesc[0] = desc; } - _commandList.Get()->BeginRenderPass((uint)rtDescs.Length, pRtvDescs, pDsvDesc, + pNativeObject->BeginRenderPass((uint)rtDescs.Length, pRtvDescs, pDsvDesc, allowUAVWrites ? D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES : D3D12_RENDER_PASS_FLAG_NONE); } public void EndRenderPass() { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -618,12 +589,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer #endif IncrementCommandCount(); - _commandList.Get()->EndRenderPass(); + pNativeObject->EndRenderPass(); } public void SetViewport(ViewportDesc viewport) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -634,12 +605,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer IncrementCommandCount(); var d3d12Viewport = new D3D12_VIEWPORT(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth); - _commandList.Get()->RSSetViewports(1, &d3d12Viewport); + pNativeObject->RSSetViewports(1, &d3d12Viewport); } public void SetPipelineState(Key128 pipelineKey) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -656,13 +627,13 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer return; } - _commandList.Get()->SetGraphicsRootSignature(_pipelineLibrary.DefaultRootSignature); - _commandList.Get()->SetPipelineState(psor.Value); + pNativeObject->SetGraphicsRootSignature(_pipelineLibrary.DefaultRootSignature); + pNativeObject->SetPipelineState(psor.Value); } public void SetConstantBufferView(uint slot, Handle buffer) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -673,12 +644,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer IncrementCommandCount(); var resource = _resourceDatabase.GetResource(buffer.AsResource()); - _commandList.Get()->SetGraphicsRootConstantBufferView(slot, resource.Get()->GetGPUVirtualAddress()); + pNativeObject->SetGraphicsRootConstantBufferView(slot, resource.Get()->GetGPUVirtualAddress()); } public void SetVertexBuffer(uint slot, Handle buffer, ulong offset = 0) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -703,12 +674,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer StrideInBytes = record.desc.BufferDescription.Stride }; - _commandList.Get()->IASetVertexBuffers(slot, 1, &vbView); + pNativeObject->IASetVertexBuffers(slot, 1, &vbView); } public void SetIndexBuffer(Handle buffer, IndexType type, ulong offset = 0) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -726,12 +697,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer Format = type == IndexType.UInt16 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT }; - _commandList.Get()->IASetIndexBuffer(&ibView); + pNativeObject->IASetIndexBuffer(&ibView); } public void SetPrimitiveTopology(PrimitiveTopology topology) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -749,12 +720,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _ => D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST }; - _commandList.Get()->IASetPrimitiveTopology(d3d12Topology); + pNativeObject->IASetPrimitiveTopology(d3d12Topology); } public void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan constantBuffer, uint offsetIn32Bits = 0) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -766,13 +737,13 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer fixed (uint* pConstants = constantBuffer) { - _commandList.Get()->SetGraphicsRoot32BitConstants(rootIndex, (uint)constantBuffer.Length, pConstants, offsetIn32Bits); + pNativeObject->SetGraphicsRoot32BitConstants(rootIndex, (uint)constantBuffer.Length, pConstants, offsetIn32Bits); } } public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -782,12 +753,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer #endif IncrementCommandCount(); - _commandList.Get()->DrawInstanced(vertexCount, instanceCount, startVertex, startInstance); + pNativeObject->DrawInstanced(vertexCount, instanceCount, startVertex, startInstance); } public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -797,12 +768,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer #endif IncrementCommandCount(); - _commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance); + pNativeObject->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance); } public void DispatchCompute(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -812,12 +783,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer #endif IncrementCommandCount(); - _commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ); + pNativeObject->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ); } public void DispatchMesh(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -827,14 +798,14 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer #endif IncrementCommandCount(); - _commandList.Get()->DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ); + pNativeObject->DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ); } public void DispatchRay() { throw new NotImplementedException(); - // ThrowIfDisposed(); + // AssertNotDisposed(); // ThrowIfNotRecording(); // IncrementCommandCount(); @@ -850,7 +821,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer { throw new NotImplementedException(); - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -865,7 +836,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer var countResource = _resourceDatabase.GetResource(countBuffer.AsResource()); // TODO - _commandList.Get()->ExecuteIndirect(null, 0, + pNativeObject->ExecuteIndirect(null, 0, resource, argumentOffset, countResource, countBufferOffset); } @@ -873,7 +844,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void UploadBuffer(Handle buffer, params ReadOnlySpan data) where T : unmanaged { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -897,12 +868,12 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer uploadResource.Get()->Unmap(0, null); var pResource = _resourceDatabase.GetResource(buffer.AsResource()); - _commandList.Get()->CopyBufferRegion(pResource, 0, uploadResource, offset, sizeInBytes); + pNativeObject->CopyBufferRegion(pResource, 0, uploadResource, offset, sizeInBytes); } public void UploadTexture(Handle texture, params ReadOnlySpan subresources) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -932,7 +903,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer } UpdateSubresources( - (ID3D12GraphicsCommandList*)_commandList.Get(), + (ID3D12GraphicsCommandList*)pNativeObject, resource, pUploadResource, offset, @@ -943,7 +914,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void CopyBuffer(Handle dst, Handle src, ulong dstOffset = 0, ulong srcOffset = 0, ulong numBytes = 0) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -968,11 +939,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer if (numBytes == 0) { - _commandList.Get()->CopyResource(pDstResource, pSrcResource); + pNativeObject->CopyResource(pDstResource, pSrcResource); } else { - _commandList.Get()->CopyBufferRegion(pDstResource, dstOffset, pSrcResource, srcOffset, numBytes); + pNativeObject->CopyBufferRegion(pDstResource, dstOffset, pSrcResource, srcOffset, numBytes); } } @@ -1002,7 +973,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void CopyTexture(Handle dst, TextureRegion? dstRegion, Handle src, TextureRegion? srcRegion) { - ThrowIfDisposed(); + AssertNotDisposed(); ThrowIfNotRecording(); #if !DEBUG if (_lastError.Status != Error.None) @@ -1033,7 +1004,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer return; } - _commandList.Get()->CopyResource(pDstResource, pSrcResource); + pNativeObject->CopyResource(pDstResource, pSrcResource); return; } @@ -1052,25 +1023,6 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer back = srcRegionV.Z + srcRegionV.Depth }; - _commandList.Get()->CopyTextureRegion(&dstLocation, dstRegionV.X, dstRegionV.Y, dstRegionV.Z, &srcLocation, &srcBoc); - } - - public void Dispose() - { - if (_disposed) - { - return; - } - - if (_isRecording) - { - throw new InvalidOperationException("Command buffer is still recording"); - } - - _commandList.Dispose(); - _commandCount = 0; - - _disposed = true; - GC.SuppressFinalize(this); + pNativeObject->CopyTextureRegion(&dstLocation, dstRegionV.X, dstRegionV.Y, dstRegionV.Z, &srcLocation, &srcBoc); } } diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandQueue.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandQueue.cs index 13b35d5..e4ba11d 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandQueue.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12CommandQueue.cs @@ -10,28 +10,20 @@ namespace Ghost.Graphics.D3D12; /// /// D3D12 implementation of command queue interface /// -internal unsafe class D3D12CommandQueue : ICommandQueue +internal unsafe class D3D12CommandQueue : D3D12Object, ICommandQueue { - private UniquePtr _commandQueue; private UniquePtr _fence; private readonly AutoResetEvent _fenceEvent; private ulong _fenceValue; - private bool _disposed; public CommandQueueType Type { get; } - public SharedPtr NativeQueue => _commandQueue.Get(); - - public D3D12CommandQueue(ID3D12Device14* pDevice, CommandQueueType type) + private static ID3D12CommandQueue1* CreateCommandQueue(ID3D12Device14* device, CommandQueueType type) { - Type = type; - _fenceEvent = new AutoResetEvent(false); - _fenceValue = 0; - var queueDesc = new D3D12_COMMAND_QUEUE_DESC { Type = ConvertCommandQueueType(type), @@ -39,18 +31,22 @@ internal unsafe class D3D12CommandQueue : ICommandQueue Flags = D3D12_COMMAND_QUEUE_FLAGS.D3D12_COMMAND_QUEUE_FLAG_NONE, }; - ID3D12CommandQueue* pQueue = default; - ID3D12Fence1* pFence = default; - ThrowIfFailed(pDevice->CreateCommandQueue(&queueDesc, __uuidof(pQueue), (void**)&pQueue)); - ThrowIfFailed(pDevice->CreateFence(0, D3D12_FENCE_FLAGS.D3D12_FENCE_FLAG_NONE, __uuidof(pFence), (void**)&pFence)); - - _commandQueue.Attach(pQueue); - _fence.Attach(pFence); + ID3D12CommandQueue1* pQueue = default; + ThrowIfFailed(device->CreateCommandQueue(&queueDesc, __uuidof(pQueue), (void**)&pQueue)); + return pQueue; } - ~D3D12CommandQueue() + public D3D12CommandQueue(D3D12RenderDevice device, CommandQueueType type) + :base(CreateCommandQueue(device.NativeObject, type)) { - Dispose(); + Type = type; + _fenceEvent = new AutoResetEvent(false); + _fenceValue = 0; + + ID3D12Fence1* pFence = default; + ThrowIfFailed(device.NativeObject.Get()->CreateFence(0, D3D12_FENCE_FLAGS.D3D12_FENCE_FLAG_NONE, __uuidof(pFence), (void**)&pFence)); + + _fence.Attach(pFence); } private static D3D12_COMMAND_LIST_TYPE ConvertCommandQueueType(CommandQueueType type) @@ -66,7 +62,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public void Submit(ICommandBuffer commandBuffer) { - Debug.Assert(!_disposed); + AssertNotDisposed(); if (commandBuffer.IsEmpty) { @@ -75,9 +71,9 @@ internal unsafe class D3D12CommandQueue : ICommandQueue if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer) { - var commandList = d3d12CommandBuffer.NativeCommandList; + var commandList = d3d12CommandBuffer.NativeObject; var commandListPtr = (ID3D12CommandList*)commandList.Get(); - _commandQueue.Get()->ExecuteCommandLists(1, &commandListPtr); + pNativeObject->ExecuteCommandLists(1, &commandListPtr); } else { @@ -87,7 +83,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public void Submit(params ReadOnlySpan commandBuffers) { - Debug.Assert(!_disposed); + AssertNotDisposed(); Span executableIndices = stackalloc int[commandBuffers.Length]; executableIndices.Fill(-1); @@ -115,7 +111,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue if (commandBuffers[cmdIndex] is D3D12CommandBuffer d3d12CommandBuffer) { - ppCommandLists[currentIndex] = (ID3D12CommandList*)d3d12CommandBuffer.NativeCommandList.Get(); + ppCommandLists[currentIndex] = (ID3D12CommandList*)d3d12CommandBuffer.NativeObject.Get(); } else { @@ -125,21 +121,21 @@ internal unsafe class D3D12CommandQueue : ICommandQueue currentIndex++; } - _commandQueue.Get()->ExecuteCommandLists((uint)currentIndex, ppCommandLists); + pNativeObject->ExecuteCommandLists((uint)currentIndex, ppCommandLists); } public ulong Signal(ulong value) { - Debug.Assert(!_disposed); + AssertNotDisposed(); _fenceValue = value; - ThrowIfFailed(_commandQueue.Get()->Signal((ID3D12Fence*)_fence.Get(), _fenceValue)); + ThrowIfFailed(pNativeObject->Signal((ID3D12Fence*)_fence.Get(), _fenceValue)); return _fenceValue; } public void WaitForValue(ulong value) { - Debug.Assert(!_disposed); + AssertNotDisposed(); if (_fence.Get()->GetCompletedValue() < value) { @@ -153,30 +149,21 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public ulong GetCompletedValue() { - Debug.Assert(!_disposed); + AssertNotDisposed(); return _fence.Get()->GetCompletedValue(); } public void WaitIdle() { - Debug.Assert(!_disposed); + AssertNotDisposed(); var fenceValue = Signal(Interlocked.Increment(ref _fenceValue)); WaitForValue(fenceValue); } - public void Dispose() + protected override void Dispose(bool disposing) { - if (_disposed) - { - return; - } - - _commandQueue.Dispose(); _fence.Dispose(); _fenceEvent?.Dispose(); - - _disposed = true; - GC.SuppressFinalize(this); } } diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12DescriptorHeap.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12DescriptorHeap.cs index 129e493..0268131 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12DescriptorHeap.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12DescriptorHeap.cs @@ -64,7 +64,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable HeapType = type; NumDescriptors = numDescriptors; ShaderVisible = type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; - Stride = device.NativeDevice.Get()->GetDescriptorHandleIncrementSize(type); + Stride = device.NativeObject.Get()->GetDescriptorHandleIncrementSize(type); var success = AllocateResources(numDescriptors); Debug.Assert(success); @@ -210,7 +210,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable public void CopyToShaderVisibleHeap(int index, int count = 1) { - _device.NativeDevice.Get()->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType); + _device.NativeObject.Get()->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType); } private bool AllocateResources(int numDescriptors) @@ -228,7 +228,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable }; ID3D12DescriptorHeap* pHeap = default; - var hr = _device.NativeDevice.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(pHeap), (void**)&pHeap); + var hr = _device.NativeObject.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(pHeap), (void**)&pHeap); if (hr.FAILED) { return false; @@ -252,7 +252,7 @@ internal unsafe class D3D12DescriptorHeap : IDisposable ID3D12DescriptorHeap* pShaderVisibleHeap = default; heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; - hr = _device.NativeDevice.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(pShaderVisibleHeap), (void**)&pShaderVisibleHeap); + hr = _device.NativeObject.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(pShaderVisibleHeap), (void**)&pShaderVisibleHeap); if (hr.FAILED) { return false; @@ -281,11 +281,11 @@ internal unsafe class D3D12DescriptorHeap : IDisposable return false; } - _device.NativeDevice.Get()->CopyDescriptorsSimple((uint)oldSize, _startCpuHandle, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType); + _device.NativeObject.Get()->CopyDescriptorsSimple((uint)oldSize, _startCpuHandle, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType); if (_shaderVisibleHeap.Get() != null) { - _device.NativeDevice.Get()->CopyDescriptorsSimple((uint)oldSize, _startCpuHandleShaderVisible, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType); + _device.NativeObject.Get()->CopyDescriptorsSimple((uint)oldSize, _startCpuHandleShaderVisible, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType); } } finally @@ -305,4 +305,4 @@ internal unsafe class D3D12DescriptorHeap : IDisposable _shaderVisibleHeap.Dispose(); _allocatedDescriptors.Dispose(); } -} \ No newline at end of file +} diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs index 489063a..7ab0bde 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12GraphicsEngine.cs @@ -5,7 +5,6 @@ using Ghost.Core; using Ghost.Graphics.Core; using Ghost.Graphics.RHI; -using System.Collections.Immutable; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -22,6 +21,18 @@ public static class D3D12GraphicsEngineFactory internal class D3D12GraphicsEngine : IGraphicsEngine { + private readonly struct CommandBufferReturnEntry + { + public readonly ICommandBuffer commandBuffer; + public readonly ulong returnFrame; + + public CommandBufferReturnEntry(ICommandBuffer commandBuffer, ulong returnFrame) + { + this.commandBuffer = commandBuffer; + this.returnFrame = returnFrame; + } + } + private readonly GraphicsEngineDesc _desc; #if ENABLE_DEBUG_LAYER @@ -34,8 +45,10 @@ internal class D3D12GraphicsEngine : IGraphicsEngine private readonly D3D12PipelineLibrary _pipelineLibrary; private readonly D3D12ResourceAllocator _resourceAllocator; - private ImmutableArray _renderers; + private readonly Queue _commandBufferPool; + private readonly Queue _commandBufferReturnQueue; + private ulong _currentFrame; private bool _disposed; public IRenderDevice Device => _device; @@ -59,9 +72,8 @@ internal class D3D12GraphicsEngine : IGraphicsEngine _pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase); _resourceAllocator = new D3D12ResourceAllocator(_device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary); - _renderers = ImmutableArray.Empty; - - _pipelineLibrary.InitializeLibrary(null); + _commandBufferPool = new Queue(4); + _commandBufferReturnQueue = new Queue(4); } ~D3D12GraphicsEngine() @@ -76,27 +88,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine ObjectDisposedException.ThrowIf(_disposed, this); } - public IRenderer CreateRenderer() - { - ThrowIfDisposed(); - - var renderer = new D3D12Renderer(this); - ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer)); - return renderer; - } - - public void RemoveRenderer(IRenderer renderer) - { - ThrowIfDisposed(); - ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Remove(renderer)); - } - - public void ClearRenderers() - { - ThrowIfDisposed(); - ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Clear()); - } - public ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics) { return new D3D12CommandAllocator(_device, type); @@ -115,25 +106,51 @@ internal class D3D12GraphicsEngine : IGraphicsEngine type); } + public ICommandBuffer GetPooledCommandBuffer(CommandBufferType type = CommandBufferType.Graphics) + { + if (_commandBufferPool.TryDequeue(out var commandBuffer)) + { + return commandBuffer; + } + else + { + return CreateCommandBuffer(type); + } + } + + public void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer) + { + _commandBufferReturnQueue.Enqueue(new CommandBufferReturnEntry(commandBuffer, _currentFrame)); + } + + public ISwapChain CreateSwapChain(SwapChainDesc desc) { ThrowIfDisposed(); - return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount); + return new DXGISwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount); } - public Result BeginFrame(uint cpuFenceValue, uint gpuFenceValue) + public Result BeginFrame(ulong currentFrame) { ThrowIfDisposed(); - _resourceDatabase.BeginFrame(cpuFenceValue); + _currentFrame = currentFrame; + _resourceDatabase.BeginFrame(currentFrame); return Result.Success(); } - public Result EndFrame(uint cpuFenceValue, uint gpuFenceValue) + public Result EndFrame(ulong completedFrame) { ThrowIfDisposed(); - _resourceDatabase.EndFrame(gpuFenceValue); + _resourceDatabase.EndFrame(completedFrame); + + while (_commandBufferReturnQueue.TryPeek(out var entry) && entry.returnFrame <= completedFrame) + { + _commandBufferPool.Enqueue(entry.commandBuffer); + _commandBufferReturnQueue.Dequeue(); + } + return Result.Success(); } @@ -144,11 +161,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine return; } - foreach (var renderer in _renderers) - { - renderer.Dispose(); - } - _resourceDatabase.ReleaseAllResourcesImmediately(); _resourceAllocator.Dispose(); diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12Object.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12Object.cs new file mode 100644 index 0000000..90b3338 --- /dev/null +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12Object.cs @@ -0,0 +1,75 @@ +using Misaki.HighPerformance.LowLevel; +using TerraFX.Interop.DirectX; +using Ghost.Graphics.D3D12.Utilities; +using Ghost.Graphics.RHI; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ghost.Graphics.D3D12; + +public unsafe abstract class D3D12Object: IRHIObject + where T : unmanaged, ID3D12Object.Interface +{ + private UniquePtr _nativeObject; + private string _name = string.Empty; + + protected T* pNativeObject => _nativeObject.Get(); + public SharedPtr NativeObject => _nativeObject.Share(); + + public string Name + { + get => _name; + set + { + if (string.Equals(_name, value, StringComparison.Ordinal)) + { + return; + } + + _name = value; + _nativeObject.Get()->SetName(value); + } + } + + protected D3D12Object(T* nativeObject) + { + _nativeObject.Attach(nativeObject); + } + + ~D3D12Object() + { + Dispose(disposing: false); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void ThrowIfDisposed() + { + ObjectDisposedException.ThrowIf(_nativeObject.Get() == null, this); + } + + [Conditional("DEBUG")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void AssertNotDisposed() + { + Debug.Assert(_nativeObject.Get() != null, "Object has been disposed."); + } + + protected virtual void Dispose(bool disposing) + { + } + + public void Dispose() + { + if (_nativeObject.Get() == null) + { + return; + } + + Dispose(disposing: true); + + _nativeObject.Dispose(); + + GC.SuppressFinalize(this); + } +} + diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12PipelineLibrary.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12PipelineLibrary.cs index 290c590..87dded6 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12PipelineLibrary.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12PipelineLibrary.cs @@ -26,29 +26,48 @@ internal struct D3D12PipelineState : IDisposable } } -internal unsafe class D3D12PipelineLibrary : IPipelineLibrary +internal unsafe class D3D12PipelineLibrary : D3D12Object, IPipelineLibrary { private readonly D3D12RenderDevice _device; private readonly D3D12ResourceDatabase _resourceDatabase; - private UniquePtr _library; private UniquePtr _defaultRootSignature; - private readonly Dictionary, D3D12PipelineState> _pipelineCache; + private UnsafeHashMap, D3D12PipelineState> _pipelineCache; public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get(); + private static ID3D12PipelineLibrary1* CreateLibrary(D3D12RenderDevice device, string? filePath) + { + ID3D12PipelineLibrary1* pLibrary = default; + + if (File.Exists(filePath)) + { + var fileBytes = File.ReadAllBytes(filePath); + fixed (byte* pFileBytes = fileBytes) + { + ThrowIfFailed(device.NativeObject.Get()->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof(pLibrary), (void**)&pLibrary)); + } + } + else + { + ThrowIfFailed(device.NativeObject.Get()->CreatePipelineLibrary(null, 0, __uuidof(pLibrary), (void**)&pLibrary)); + } + + return pLibrary; + } + public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase) + :base(CreateLibrary(device, null)) // TODO: we need to path to load the existing library from disk. { _device = device; _resourceDatabase = resourceDatabase; - _pipelineCache = new Dictionary, D3D12PipelineState>(); + _pipelineCache = new UnsafeHashMap, D3D12PipelineState>(32, Allocator.Persistent); CreateDefaultRootSignature().ThrowIfFailed(); } - // TODO: Maybe we don't need 4 root signature. We can use bindless for global, per-view, and per-object buffers as well. private Result CreateDefaultRootSignature() { _defaultRootSignature = default; @@ -96,7 +115,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary } ID3D12RootSignature* pRootSignature = default; - ThrowIfFailed(_device.NativeDevice.Get()->CreateRootSignature(0, pSignature.Get()->GetBufferPointer(), pSignature.Get()->GetBufferSize(), + ThrowIfFailed(_device.NativeObject.Get()->CreateRootSignature(0, pSignature.Get()->GetBufferPointer(), pSignature.Get()->GetBufferSize(), __uuidof(pRootSignature), (void**)&pRootSignature)); _defaultRootSignature.Attach(pRootSignature); @@ -104,26 +123,6 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary return Result.Success(); } - public void InitializeLibrary(string? filePath) - { - ID3D12PipelineLibrary1* pLibrary = default; - - if (File.Exists(filePath)) - { - var fileBytes = File.ReadAllBytes(filePath); - fixed (byte* pFileBytes = fileBytes) - { - ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof(pLibrary), (void**)&pLibrary)); - } - } - else - { - ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineLibrary(null, 0, __uuidof(pLibrary), (void**)&pLibrary)); - } - - _library.Attach(pLibrary); - } - public void SaveLibraryToDisk(string filePath) { var dir = Path.GetDirectoryName(filePath); @@ -132,10 +131,10 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary throw new InvalidOperationException($"Directory does not exist: {dir}"); } - var size = _library.Get()->GetSerializedSize(); + var size = pNativeObject->GetSerializedSize(); using var buffer = new UnsafeArray((int)size, Allocator.Persistent); // We use persistent Heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries. - ThrowIfFailed(_library.Get()->Serialize(buffer.GetUnsafePtr(), size)); + ThrowIfFailed(pNativeObject->Serialize(buffer.GetUnsafePtr(), size)); using var fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None); fs.Write(buffer.AsSpan()); @@ -225,6 +224,8 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary return psr.Value; } + AssertNotDisposed(); + if (descriptor.RtvFormats.Length > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT) { return Result.Failure($"RTV format count exceeds the maximum supported render target count of {D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT}."); @@ -300,12 +301,12 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary return Result.Failure("Failed to convert pipeline key to string."); } - var hr = _library.Get()->LoadPipeline(pKeyStr, &streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState); + var hr = pNativeObject->LoadPipeline(pKeyStr, &streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState); if (hr == E.E_INVALIDARG) { // Pipeline not found in the library, create a new one. - ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineState(&streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState)); - ThrowIfFailed(_library.Get()->StorePipeline(pKeyStr, pPipelineState)); + ThrowIfFailed(_device.NativeObject.Get()->CreatePipelineState(&streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState)); + ThrowIfFailed(pNativeObject->StorePipeline(pKeyStr, pPipelineState)); } else { @@ -325,11 +326,13 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary public bool HasPipeline(Key128 key) { + AssertNotDisposed(); return _pipelineCache.ContainsKey(key); } public Result, Error> GetGraphicsPSO(Key128 key) { + AssertNotDisposed(); if (_pipelineCache.TryGetValue(key, out var cacheEntry)) { return cacheEntry.pso.Share(); @@ -338,16 +341,14 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary return Error.NotFound; } - public void Dispose() + protected override void Dispose(bool disposing) { foreach (var kvp in _pipelineCache) { kvp.Value.Dispose(); } - _pipelineCache.Clear(); - + _pipelineCache.Dispose(); _defaultRootSignature.Dispose(); - _library.Dispose(); } } diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12RenderDevice.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12RenderDevice.cs index 8a43785..f58780e 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12RenderDevice.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12RenderDevice.cs @@ -9,12 +9,8 @@ using static TerraFX.Aliases.DXGI_Alias; namespace Ghost.Graphics.D3D12; -/// -/// D3D12 implementation of the render device interface -/// -internal unsafe class D3D12RenderDevice : IRenderDevice +internal unsafe class D3D12RenderDevice : D3D12Object, IRenderDevice { - private UniquePtr _device; private UniquePtr _dxgiFactory; private UniquePtr _adapter; @@ -22,8 +18,6 @@ internal unsafe class D3D12RenderDevice : IRenderDevice private readonly D3D12CommandQueue _computeQueue; private readonly D3D12CommandQueue _copyQueue; - private bool _disposed; - public ICommandQueue GraphicsQueue => _graphicsQueue; public ICommandQueue ComputeQueue => _computeQueue; public ICommandQueue CopyQueue => _copyQueue; @@ -34,14 +28,28 @@ internal unsafe class D3D12RenderDevice : IRenderDevice } public SharedPtr DXGIFactory => _dxgiFactory.Share(); - public SharedPtr NativeDevice => _device.Share(); public SharedPtr Adapter => _adapter.Share(); - public SharedPtr NativeGraphicsQueue => _graphicsQueue.NativeQueue; - public SharedPtr NativeComputeQueue => _computeQueue.NativeQueue; - public SharedPtr NativeCopyQueue => _copyQueue.NativeQueue; + public SharedPtr NativeGraphicsQueue => _graphicsQueue.NativeObject; + public SharedPtr NativeComputeQueue => _computeQueue.NativeObject; + public SharedPtr NativeCopyQueue => _copyQueue.NativeObject; public D3D12RenderDevice() + :base(CreateDevice(out var dxgiFactory, out var adapter)) { + _dxgiFactory.Attach(dxgiFactory); + _adapter.Attach(adapter); + + _graphicsQueue = new D3D12CommandQueue(this, CommandQueueType.Graphics); + _computeQueue = new D3D12CommandQueue(this, CommandQueueType.Compute); + _copyQueue = new D3D12CommandQueue(this, CommandQueueType.Copy); + + FeatureSupport = GetFeatureSupport(); + } + + private static ID3D12Device14* CreateDevice(out IDXGIFactory7* dxgiFactory, out IDXGIAdapter1* adapter) + { + adapter = default; + IDXGIFactory7* pFactory = default; #if DEBUG ThrowIfFailed(CreateDXGIFactory2(TRUE, __uuidof(pFactory), (void**)&pFactory)); @@ -49,29 +57,13 @@ internal unsafe class D3D12RenderDevice : IRenderDevice ThrowIfFailed(CreateDXGIFactory2(FALSE, __uuidof(pFactory), (void**)&pFactory)); #endif - _dxgiFactory.Attach(pFactory); + dxgiFactory = pFactory; - InitializeDevice(); - - _graphicsQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Graphics); - _computeQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Compute); - _copyQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Copy); - - FeatureSupport = GetFeatureSupport(); - } - - ~D3D12RenderDevice() - { - Dispose(); - } - - private void InitializeDevice() - { ID3D12Device14* pDevice = default; IDXGIAdapter1* pAdapter = default; for (uint adapterIndex = 0; - _dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof(pAdapter), (void**)&pAdapter).SUCCEEDED; + pFactory->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof(pAdapter), (void**)&pAdapter).SUCCEEDED; adapterIndex++) { DXGI_ADAPTER_DESC1 desc = default; @@ -85,7 +77,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice if (D3D12CreateDevice((IUnknown*)pAdapter, D3D_FEATURE_LEVEL_12_0, __uuidof(pDevice), (void**)&pDevice).SUCCEEDED) { - _adapter.Attach(pAdapter); + adapter = pAdapter; break; } @@ -96,20 +88,21 @@ internal unsafe class D3D12RenderDevice : IRenderDevice if (pDevice == null) { pAdapter->Release(); // Dispose the last adapter we tried. + pFactory->Release(); // Dispose the factory before throwing. throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0"); } - _device.Attach(pDevice); + return pDevice; } private FeatureSupport GetFeatureSupport() { - ObjectDisposedException.ThrowIf(_disposed, this); + ThrowIfDisposed(); var support = FeatureSupport.None; D3D12_FEATURE_DATA_D3D12_OPTIONS options = default; - if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)).SUCCEEDED) + if (pNativeObject->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)).SUCCEEDED) { if (options.ResourceBindingTier == D3D12_RESOURCE_BINDING_TIER_3) { @@ -123,7 +116,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice } D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5 = default; - if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options5, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS5)).SUCCEEDED) + if (pNativeObject->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options5, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS5)).SUCCEEDED) { if (options5.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED) { @@ -132,7 +125,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice } D3D12_FEATURE_DATA_D3D12_OPTIONS6 options6 = default; - if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6, &options6, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS6)).SUCCEEDED) + if (pNativeObject->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6, &options6, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS6)).SUCCEEDED) { if (options6.VariableShadingRateTier != D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED) { @@ -141,7 +134,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice } D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7 = default; - if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &options7, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS7)).SUCCEEDED) + if (pNativeObject->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &options7, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS7)).SUCCEEDED) { if (options7.MeshShaderTier != D3D12_MESH_SHADER_TIER_NOT_SUPPORTED) { @@ -155,7 +148,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice } D3D12_FEATURE_DATA_D3D12_OPTIONS21 options9 = default; - if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS21, &options9, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS8)).SUCCEEDED) + if (pNativeObject->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS21, &options9, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS8)).SUCCEEDED) { if (options9.WorkGraphsTier != D3D12_WORK_GRAPHS_TIER.D3D12_WORK_GRAPHS_TIER_NOT_SUPPORTED) { @@ -166,22 +159,13 @@ internal unsafe class D3D12RenderDevice : IRenderDevice return support; } - public void Dispose() + protected override void Dispose(bool disposing) { - if (_disposed) - { - return; - } - _graphicsQueue.Dispose(); _computeQueue.Dispose(); _copyQueue.Dispose(); - _device.Dispose(); _dxgiFactory.Dispose(); _adapter.Dispose(); - - _disposed = true; - GC.SuppressFinalize(this); } } diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceAllocator.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceAllocator.cs index a212010..a21c322 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceAllocator.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceAllocator.cs @@ -468,7 +468,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator var desc = new D3D12MA_ALLOCATOR_DESC { pAdapter = (IDXGIAdapter*)device.Adapter.Get(), - pDevice = (ID3D12Device*)device.NativeDevice.Get(), + pDevice = (ID3D12Device*)device.NativeObject.Get(), Flags = D3D12MA_ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED | D3D12MA_ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED, }; @@ -546,7 +546,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator d3d12Desc = desc.BufferDescription.ToD3D12ResourceDesc(); } - var info = _device.NativeDevice.Get()->GetResourceAllocationInfo(0, 1, &d3d12Desc); + var info = _device.NativeObject.Get()->GetResourceAllocationInfo(0, 1, &d3d12Desc); return new ResourceSizeInfo { Size = info.SizeInBytes, @@ -646,7 +646,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray; var srvDesc = CreateTextureSrvDesc(pResource, resourceDesc.MipLevels, resourceDesc.DepthOrArraySize, isCubeMap); - _device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle); + _device.NativeObject.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle); _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv); } @@ -656,7 +656,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv); var rtvDesc = CreateRtvDesc(pResource); - _device.NativeDevice.Get()->CreateRenderTargetView(pResource, &rtvDesc, cpuHandle); + _device.NativeObject.Get()->CreateRenderTargetView(pResource, &rtvDesc, cpuHandle); } if (desc.Usage.HasFlag(TextureUsage.DepthStencil)) @@ -665,7 +665,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv); var dsvDesc = CreateDsvDesc(pResource); - _device.NativeDevice.Get()->CreateDepthStencilView(pResource, &dsvDesc, cpuHandle); + _device.NativeObject.Get()->CreateDepthStencilView(pResource, &dsvDesc, cpuHandle); } if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess)) @@ -674,7 +674,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav); var uavDesc = CreateTextureUavDesc(pResource); - _device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle); + _device.NativeObject.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle); _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav); } @@ -764,7 +764,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator SizeInBytes = (uint)resourceDesc.Width, }; - _device.NativeDevice.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle); + _device.NativeObject.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle); _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.cbv); } @@ -774,7 +774,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv); var srvDesc = CreateBufferSrvDesc(pResource, desc.Stride, isRaw); - _device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle); + _device.NativeObject.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle); _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv); } @@ -784,7 +784,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav); var uavDesc = CreateBufferUavDesc(pResource, desc.Stride, isRaw); - _device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle); + _device.NativeObject.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle); _descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav); } @@ -862,7 +862,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator var samplerDescriptor = _descriptorAllocator.AllocateSampler(); var cpuHandle = _descriptorAllocator.GetCpuHandle(samplerDescriptor); - _device.NativeDevice.Get()->CreateSampler(&samplerDesc, cpuHandle); + _device.NativeObject.Get()->CreateSampler(&samplerDesc, cpuHandle); _descriptorAllocator.CopyToShaderVisible(samplerDescriptor); return _resourceDatabase.AddSampler(in desc, samplerDescriptor.Value); diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceDatabase.cs b/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceDatabase.cs index 421a167..6aed453 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceDatabase.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/D3D12ResourceDatabase.cs @@ -88,9 +88,9 @@ internal class D3D12ResourceDatabase : IResourceDatabase private readonly struct ReleaseEntry { public readonly ResourceRecord record; - public readonly uint fenceValue; + public readonly ulong fenceValue; - public ReleaseEntry(ResourceRecord record, uint fenceValue) + public ReleaseEntry(ResourceRecord record, ulong fenceValue) { this.record = record; this.fenceValue = fenceValue; @@ -108,7 +108,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase private UnsafeQueue _releaseQueue; - private uint _currentFrameFenceValue; + private ulong _currentFrame; private bool _disposed; public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator) @@ -136,7 +136,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase if (pResource == null) { #if DEBUG - System.Diagnostics.Debugger.Break(); + Debugger.Break(); #endif return Handle.Invalid; } @@ -155,13 +155,13 @@ internal class D3D12ResourceDatabase : IResourceDatabase return handle; } - public unsafe Handle AddAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null) + internal unsafe Handle AddAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null) { Debug.Assert(!_disposed); if (allocation == null) { #if DEBUG - System.Diagnostics.Debugger.Break(); + Debugger.Break(); #endif return Handle.Invalid; } @@ -288,7 +288,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase return; } - var entry = new ReleaseEntry(record, _currentFrameFenceValue); + var entry = new ReleaseEntry(record, _currentFrame); _releaseQueue.Enqueue(entry); _resources.Remove(handle.ID, handle.Generation); @@ -359,20 +359,20 @@ internal class D3D12ResourceDatabase : IResourceDatabase return Error.None; } - public void BeginFrame(uint currentFrameFenceValue) + public void BeginFrame(ulong currentFrame) { Debug.Assert(!_disposed); - _currentFrameFenceValue = currentFrameFenceValue; + _currentFrame = currentFrame; } - public void EndFrame(uint completedFenceValue) + public void EndFrame(ulong completedFrame) { Debug.Assert(!_disposed); while (_releaseQueue.Count > 0) { var toRelease = _releaseQueue.Peek(); - if (toRelease.fenceValue > completedFenceValue) + if (toRelease.fenceValue > completedFrame) { break; } diff --git a/src/Runtime/Ghost.Graphics.D3D12/D3D12SwapChain.cs b/src/Runtime/Ghost.Graphics.D3D12/DXGISwapChain.cs similarity index 89% rename from src/Runtime/Ghost.Graphics.D3D12/D3D12SwapChain.cs rename to src/Runtime/Ghost.Graphics.D3D12/DXGISwapChain.cs index 4e84cae..03df9cb 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/D3D12SwapChain.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/DXGISwapChain.cs @@ -12,14 +12,11 @@ using static TerraFX.Aliases.DXGI_Alias; namespace Ghost.Graphics.D3D12; -/// -/// D3D12 implementation of swap chain interface -/// -internal unsafe class D3D12SwapChain : ISwapChain +internal unsafe class DXGISwapChain : ISwapChain { private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12DescriptorAllocator _descriptorAllocator; - private readonly D3D12RenderDevice _renderDevice; + private readonly D3D12RenderDevice _device; private UniquePtr _swapChain; private UnsafeArray> _backBuffers; @@ -48,37 +45,7 @@ internal unsafe class D3D12SwapChain : ISwapChain get; private set; } - public D3D12SwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount) - { - Debug.Assert(bufferCount >= 2); - - _resourceDatabase = resourceDatabase; - _descriptorAllocator = descriptorAllocator; - _renderDevice = device; - - _backBuffers = new UnsafeArray>((int)bufferCount, Allocator.Persistent); - - Width = desc.Width; - Height = desc.Height; - - var pSwapChian = CreateSwapChain(desc, bufferCount); - _swapChain.Attach(pSwapChian); - - CreateBackBuffers(); - SetScale(desc.ScaleX, desc.ScaleY); - - if (desc.Target.Type == SwapChainTargetType.Composition) - { - _compositionSurface = desc.Target.CompositionSurface; - } - } - - ~D3D12SwapChain() - { - Dispose(); - } - - private IDXGISwapChain4* CreateSwapChain(SwapChainDesc desc, uint bufferCount) + private static IDXGISwapChain4* CreateSwapChain(D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount) { var swapChainDesc = new DXGI_SWAP_CHAIN_DESC1 { @@ -97,8 +64,8 @@ internal unsafe class D3D12SwapChain : ISwapChain IDXGISwapChain1* pTempSwapChain = default; - var pFactory = _renderDevice.DXGIFactory.Get(); - var pCommandQueue = _renderDevice.NativeGraphicsQueue.Get(); + var pFactory = device.DXGIFactory.Get(); + var pCommandQueue = device.NativeGraphicsQueue.Get(); switch (desc.Target.Type) { @@ -139,6 +106,36 @@ internal unsafe class D3D12SwapChain : ISwapChain return pSwapChain; } + public DXGISwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount) + { + Debug.Assert(bufferCount >= 2); + + _resourceDatabase = resourceDatabase; + _descriptorAllocator = descriptorAllocator; + _device = device; + + var pSfwapChain = CreateSwapChain(device, desc, bufferCount); + _swapChain.Attach(pSfwapChain); + + _backBuffers = new UnsafeArray>((int)bufferCount, Allocator.Persistent); + + Width = desc.Width; + Height = desc.Height; + + CreateBackBuffers(); + SetScale(desc.ScaleX, desc.ScaleY); + + if (desc.Target.Type == SwapChainTargetType.Composition) + { + _compositionSurface = desc.Target.CompositionSurface; + } + } + + ~DXGISwapChain() + { + Dispose(); + } + private void CreateBackBuffers() { for (uint i = 0; i < _backBuffers.Count; i++) @@ -149,7 +146,7 @@ internal unsafe class D3D12SwapChain : ISwapChain var rtv = _descriptorAllocator.AllocateRTV(); var cpuHandle = _descriptorAllocator.GetCpuHandle(rtv); - _renderDevice.NativeDevice.Get()->CreateRenderTargetView(pBackBuffer, null, cpuHandle); + _device.NativeObject.Get()->CreateRenderTargetView(pBackBuffer, null, cpuHandle); var view = ResourceViewGroup.Invalid with { diff --git a/src/Runtime/Ghost.Graphics.D3D12/Utilities/D3D12Utility.cs b/src/Runtime/Ghost.Graphics.D3D12/Utilities/D3D12Utility.cs index 6e5052a..5672ecb 100644 --- a/src/Runtime/Ghost.Graphics.D3D12/Utilities/D3D12Utility.cs +++ b/src/Runtime/Ghost.Graphics.D3D12/Utilities/D3D12Utility.cs @@ -68,7 +68,7 @@ internal static unsafe class D3D12Utility if (ptr != null) { var refCount = ptr->Release(); - Debug.Assert((refCount != 0)); + Debug.Assert(refCount != 0); } } diff --git a/src/Runtime/Ghost.Graphics.RHI/Common.cs b/src/Runtime/Ghost.Graphics.RHI/Common.cs index 74c9289..2d06944 100644 --- a/src/Runtime/Ghost.Graphics.RHI/Common.cs +++ b/src/Runtime/Ghost.Graphics.RHI/Common.cs @@ -627,7 +627,7 @@ public struct BarrierDesc } } -public struct ResourceDesc +public record struct ResourceDesc { [StructLayout(LayoutKind.Explicit)] internal struct resource_union @@ -638,7 +638,7 @@ public struct ResourceDesc public BufferDesc bufferDescription; } - internal resource_union _desc; + private resource_union _desc; public ResourceType Type { @@ -682,6 +682,31 @@ public struct ResourceDesc TextureDescription = desc }; } + + public bool Equals(ResourceDesc other) + { + if (Type != other.Type) + { + return false; + } + + return Type switch + { + ResourceType.Texture => TextureDescription.Equals(other.TextureDescription), + ResourceType.Buffer => BufferDescription.Equals(other.BufferDescription), + _ => throw new InvalidOperationException($"Unknown resource type: {Type}") + }; + } + + public override int GetHashCode() + { + return Type switch + { + ResourceType.Texture => HashCode.Combine(Type, TextureDescription), + ResourceType.Buffer => HashCode.Combine(Type, BufferDescription), + _ => throw new InvalidOperationException($"Unknown resource type: {Type}") + }; + } } /// @@ -831,7 +856,7 @@ public struct RenderTargetDesc /// /// Texture description /// -public struct TextureDesc +public record struct TextureDesc { /// /// Width of the texture @@ -939,7 +964,7 @@ public record struct SamplerDesc } } -public struct BufferDesc +public record struct BufferDesc { public ulong Size { diff --git a/src/Runtime/Ghost.Graphics.RHI/IGraphicsEngine.cs b/src/Runtime/Ghost.Graphics.RHI/IGraphicsEngine.cs index eeb3e61..35e61d4 100644 --- a/src/Runtime/Ghost.Graphics.RHI/IGraphicsEngine.cs +++ b/src/Runtime/Ghost.Graphics.RHI/IGraphicsEngine.cs @@ -37,25 +37,6 @@ public interface IGraphicsEngine : IDisposable get; } - /// - /// Creates a new instance of a renderer for drawing graphical content. - /// - /// An object that implements the IRenderer interface, which can be used to render graphics. - IRenderer CreateRenderer(); - - /// - /// Removes the specified renderer from the collection of active renderers. - /// - /// The renderer instance to remove. - void RemoveRenderer(IRenderer renderer); - - /// - /// Removes all registered renderers from the collection. - /// - /// Call this method to reset the renderer collection to an empty state. After calling this - /// method, no renderers will be available until new ones are added. - void ClearRenderers(); - /// /// Creates a new command allocator for the specified command buffer space. /// @@ -70,6 +51,19 @@ public interface IGraphicsEngine : IDisposable /// A new command buffer instance ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics); + /// + /// Gets a command buffer from the pool for recording rendering commands. + /// + /// Type of command buffer to get from the pool + /// A command buffer instance from the pool + ICommandBuffer GetPooledCommandBuffer(CommandBufferType type = CommandBufferType.Graphics); + + /// + /// Returns a command buffer to the pool after use. + /// + /// The command buffer to return to the pool + void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer); + /// /// Creates a swap chain for presentation /// @@ -80,16 +74,14 @@ public interface IGraphicsEngine : IDisposable /// /// Begin the current frame. /// - /// CPU fence value for synchronization - /// GPU fence value for synchronization + /// CPU fence value for synchronization /// Result of the begin frame operation - Result BeginFrame(uint cpuFenceValue, uint gpuFenceValue); + Result BeginFrame(ulong currentFrame); /// /// End the current frame. /// - /// CPU fence value for synchronization - /// GPU fence value for synchronization + /// GPU fence value for synchronization /// Result of the end frame operation - Result EndFrame(uint cpuFenceValue, uint gpuFenceValue); + Result EndFrame(ulong completedFrame); } diff --git a/src/Runtime/Ghost.Graphics.RHI/IPipelineLibrary.cs b/src/Runtime/Ghost.Graphics.RHI/IPipelineLibrary.cs index 1daaf39..0932189 100644 --- a/src/Runtime/Ghost.Graphics.RHI/IPipelineLibrary.cs +++ b/src/Runtime/Ghost.Graphics.RHI/IPipelineLibrary.cs @@ -4,11 +4,6 @@ namespace Ghost.Graphics.RHI; public interface IPipelineLibrary : IDisposable { - /// - /// Load pipeline library from disk. - /// - /// File path. If null, load default library. - void InitializeLibrary(string? filePath); void SaveLibraryToDisk(string filePath); bool HasPipeline(Key128 key); Result> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled); diff --git a/src/Runtime/Ghost.Graphics.RHI/IRHIObject.cs b/src/Runtime/Ghost.Graphics.RHI/IRHIObject.cs new file mode 100644 index 0000000..4df1ad0 --- /dev/null +++ b/src/Runtime/Ghost.Graphics.RHI/IRHIObject.cs @@ -0,0 +1,6 @@ +namespace Ghost.Graphics.RHI; + +public interface IRHIObject: IDisposable +{ + string Name { get; set; } +} diff --git a/src/Runtime/Ghost.Graphics.RHI/IRenderer.cs b/src/Runtime/Ghost.Graphics.RHI/IRenderer.cs index 406dda3..0bee8a0 100644 --- a/src/Runtime/Ghost.Graphics.RHI/IRenderer.cs +++ b/src/Runtime/Ghost.Graphics.RHI/IRenderer.cs @@ -5,6 +5,9 @@ namespace Ghost.Graphics.RHI; public readonly struct RenderContext { public ICommandBuffer CommandBuffer { get; init; } + public ICommandQueue GraphicsQueue { get; init; } + public ICommandQueue ComputeQueue { get; init; } + public ICommandQueue CopyQueue { get; init; } } // TODO: We may don't need this anymore. We Use RenderExtractionSystem to extract render data from entities and pass them to IRenderPipeline to render. diff --git a/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs b/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs index c63b992..4bc568a 100644 --- a/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs +++ b/src/Runtime/Ghost.Graphics/Core/RenderRequest.cs @@ -34,12 +34,12 @@ public struct Frustum public static void CalculateFrustumPlanes(float4x4 finalMatrix, ref plane_array outPlanes) { - const int kPlaneFrustumLeft = 0; - const int kPlaneFrustumRight = 1; - const int kPlaneFrustumBottom = 2; - const int kPlaneFrustumTop = 3; - const int kPlaneFrustumNear = 4; - const int kPlaneFrustumFar = 5; + const int planeFrustumLeft = 0; + const int planeFrustumRight = 1; + const int planeFrustumBottom = 2; + const int planeFrustumTop = 3; + const int planeFrustumNear = 4; + const int planeFrustumFar = 5; float4 tmpVec = default; float4 otherVec = default; @@ -66,8 +66,8 @@ public struct Frustum leftNormalY *= leftInvMagnitude; leftNormalZ *= leftInvMagnitude; leftDistance *= leftInvMagnitude; - outPlanes[kPlaneFrustumLeft].xyz = new float3(leftNormalX, leftNormalY, leftNormalZ); - outPlanes[kPlaneFrustumLeft].w = leftDistance; + outPlanes[planeFrustumLeft].xyz = new float3(leftNormalX, leftNormalY, leftNormalZ); + outPlanes[planeFrustumLeft].w = leftDistance; var rightNormalX = -otherVec[0] + tmpVec[0]; var rightNormalY = -otherVec[1] + tmpVec[1]; @@ -80,8 +80,8 @@ public struct Frustum rightNormalY *= rightInvMagnitude; rightNormalZ *= rightInvMagnitude; rightDistance *= rightInvMagnitude; - outPlanes[kPlaneFrustumRight].xyz = new float3(rightNormalX, rightNormalY, rightNormalZ); - outPlanes[kPlaneFrustumRight].w = rightDistance; + outPlanes[planeFrustumRight].xyz = new float3(rightNormalX, rightNormalY, rightNormalZ); + outPlanes[planeFrustumRight].w = rightDistance; // bottom & top otherVec[0] = finalMatrix[0][1]; @@ -100,8 +100,8 @@ public struct Frustum bottomNormalY *= bottomInvMagnitude; bottomNormalZ *= bottomInvMagnitude; bottomDistance *= bottomInvMagnitude; - outPlanes[kPlaneFrustumBottom].xyz = new float3(bottomNormalX, bottomNormalY, bottomNormalZ); - outPlanes[kPlaneFrustumBottom].w = bottomDistance; + outPlanes[planeFrustumBottom].xyz = new float3(bottomNormalX, bottomNormalY, bottomNormalZ); + outPlanes[planeFrustumBottom].w = bottomDistance; var topNormalX = -otherVec[0] + tmpVec[0]; var topNormalY = -otherVec[1] + tmpVec[1]; @@ -114,8 +114,8 @@ public struct Frustum topNormalY *= topInvMagnitude; topNormalZ *= topInvMagnitude; topDistance *= topInvMagnitude; - outPlanes[kPlaneFrustumTop].xyz = new float3(topNormalX, topNormalY, topNormalZ); - outPlanes[kPlaneFrustumTop].w = topDistance; + outPlanes[planeFrustumTop].xyz = new float3(topNormalX, topNormalY, topNormalZ); + outPlanes[planeFrustumTop].w = topDistance; // near & far otherVec[0] = finalMatrix[0][2]; @@ -134,8 +134,8 @@ public struct Frustum nearNormalY *= nearInvMagnitude; nearNormalZ *= nearInvMagnitude; nearDistance *= nearInvMagnitude; - outPlanes[kPlaneFrustumNear].xyz = new float3(nearNormalX, nearNormalY, nearNormalZ); - outPlanes[kPlaneFrustumNear].w = nearDistance; + outPlanes[planeFrustumNear].xyz = new float3(nearNormalX, nearNormalY, nearNormalZ); + outPlanes[planeFrustumNear].w = nearDistance; var farNormalX = -otherVec[0] + tmpVec[0]; var farNormalY = -otherVec[1] + tmpVec[1]; @@ -148,8 +148,8 @@ public struct Frustum farNormalY *= farInvMagnitude; farNormalZ *= farInvMagnitude; farDistance *= farInvMagnitude; - outPlanes[kPlaneFrustumFar].xyz = new float3(farNormalX, farNormalY, farNormalZ); - outPlanes[kPlaneFrustumFar].w = farDistance; + outPlanes[planeFrustumFar].xyz = new float3(farNormalX, farNormalY, farNormalZ); + outPlanes[planeFrustumFar].w = farDistance; } } @@ -177,7 +177,7 @@ public struct RenderView public RenderingLayerMask renderingLayerMask; } -public unsafe struct RenderRequest +public unsafe struct RenderRequest: IDisposable { public RenderView view; @@ -189,4 +189,11 @@ public unsafe struct RenderRequest public RenderList shadowCasterRenderList; public delegate* renderFunc; + + public void Dispose() + { + opaqueRenderList.Dispose(); + transparentRenderList.Dispose(); + shadowCasterRenderList.Dispose(); + } } diff --git a/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.Test.cs b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.Test.cs index a2c489f..b5c5850 100644 --- a/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.Test.cs +++ b/src/Runtime/Ghost.Graphics/RenderPipeline/GhostRenderPipeline.Test.cs @@ -47,7 +47,7 @@ public partial class GhostRenderPipeline private readonly uint _padding1; private readonly uint _padding2; } - +#if flase private void RenderTest(RenderGraph graph, Identifier backbuffer) { Identifier renderTarget; @@ -107,4 +107,5 @@ public partial class GhostRenderPipeline }); } } +# endif } diff --git a/src/Runtime/Ghost.Graphics/RenderSystem.cs b/src/Runtime/Ghost.Graphics/RenderSystem.cs index a5fc82a..0c277d0 100644 --- a/src/Runtime/Ghost.Graphics/RenderSystem.cs +++ b/src/Runtime/Ghost.Graphics/RenderSystem.cs @@ -1,7 +1,10 @@ using Ghost.Core; +using Ghost.Graphics.Core; using Ghost.Graphics.D3D12; using Ghost.Graphics.RenderPipeline; using Ghost.Graphics.RHI; +using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.Mathematics; using System.Collections.Concurrent; using System.Diagnostics; @@ -70,14 +73,16 @@ public class RenderSystem : IDisposable private readonly FrameResource[] _frameResources; private readonly Thread _renderThread; private readonly AutoResetEvent _shutdownEvent; + + private UnsafeArray> _renderRequests; private readonly ConcurrentDictionary _resizeRequest; private IRenderPipelineSettings _renderPipelineSettings; private IRenderPipeline _renderPipeline; private uint _frameIndex; - private uint _cpuFenceValue; - private uint _gpuFenceValue; + private ulong _cpuFenceValue; + private ulong _gpuFenceValue; private bool _isRunning; private bool _disposed; @@ -86,8 +91,8 @@ public class RenderSystem : IDisposable public ResourceManager ResourceManager => _resourceManager; public bool IsRunning => _isRunning; - public uint CPUFenceValue => _cpuFenceValue; - public uint GPUFenceValue => _gpuFenceValue; + public ulong CPUFenceValue => _cpuFenceValue; + public ulong GPUFenceValue => _gpuFenceValue; public uint FrameIndex => _frameIndex; public uint MaxFrameLatency => _config.FrameBufferCount; @@ -160,6 +165,11 @@ public class RenderSystem : IDisposable _shutdownEvent = new AutoResetEvent(false); _resizeRequest = new ConcurrentDictionary(); + _renderRequests = new UnsafeArray>((int)desc.FrameBufferCount, Allocator.Persistent); + for (var i = 0; i < desc.FrameBufferCount; i++) + { + _renderRequests[i] = new UnsafeList(2, Allocator.Persistent); + } _renderPipelineSettings = new GhostRenderPipelineSettings(); _renderPipeline = _renderPipelineSettings.CreatePipeline(this); @@ -191,7 +201,7 @@ public class RenderSystem : IDisposable while (_isRunning) { - _frameIndex = _gpuFenceValue % _config.FrameBufferCount; + _frameIndex = (uint)(_gpuFenceValue % _config.FrameBufferCount); ref var frameResource = ref _frameResources[_frameIndex]; // Wait for either CPU ready signal or shutdown signal @@ -214,7 +224,6 @@ public class RenderSystem : IDisposable if (!_resizeRequest.IsEmpty) { - //WaitIdle(); _gpuFenceValue++; var flushFence = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue); _graphicsEngine.Device.GraphicsQueue.WaitForValue(flushFence); @@ -241,28 +250,65 @@ public class RenderSystem : IDisposable continue; // Skip rendering this frame since we just resized and may have invalid render targets } + // Begin rendering for this frame frameResource.CommandAllocator.Reset(); - var r = _graphicsEngine.BeginFrame(_cpuFenceValue, _gpuFenceValue); + _resourceManager.BeginFrame(_cpuFenceValue); + var r = _graphicsEngine.BeginFrame(_cpuFenceValue); + if (r.IsFailure) { StopRenderLoop(r); + break; } - var cmd = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics); + // Start recording commands + + // TODO: How can we support async compute and async copy? + var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics); + cmd.Begin(frameResource.CommandAllocator); + var renderCtx = new RenderContext { - CommandBuffer = cmd, + CommandBuffer = cmd }; - _renderPipeline.Render(renderCtx, default); + ref var renderRequests = ref _renderRequests[_frameIndex]; + _renderPipeline.Render(renderCtx, renderRequests.AsSpan()); + + // End recording commands and submit + r = cmd.End(); + if (r.IsFailure) + { + _graphicsEngine.ReturnPooledCommandBuffer(cmd); + StopRenderLoop(r); + break; + } + + _graphicsEngine.Device.GraphicsQueue.Submit(cmd); + _graphicsEngine.ReturnPooledCommandBuffer(cmd); + + // End the frame and present + _resourceManager.EndFrame(_cpuFenceValue); + r = _graphicsEngine.EndFrame(_gpuFenceValue); - r = _graphicsEngine.EndFrame(_cpuFenceValue, _gpuFenceValue); if (r.IsFailure) { StopRenderLoop(r); + break; } + // TODO: Present here. + + + // Prepare for the next frame. + for (var i = 0; i < renderRequests.Count; i++) + { + renderRequests[i].Dispose(); + } + + renderRequests.Clear(); + _gpuFenceValue++; frameResource.GpuReadyEvent.Set(); @@ -312,6 +358,14 @@ public class RenderSystem : IDisposable _resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize); } + public void AddRenderRequest(in RenderRequest request) + { + Debug.Assert(!_disposed, "Cannot add render request to a disposed RenderSystem."); + + var frameIndex = (int)(_cpuFenceValue % _config.FrameBufferCount); + _renderRequests[frameIndex].Add(request); + } + public bool WaitForGPUReady(int timeOut = -1) { Debug.Assert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem."); @@ -346,6 +400,16 @@ public class RenderSystem : IDisposable frameResource.Dispose(); } + foreach (ref var renderRequestList in _renderRequests) + { + foreach (ref var request in renderRequestList) + { + request.Dispose(); + } + + renderRequestList.Dispose(); + } + _graphicsEngine.Dispose(); _shutdownEvent.Dispose(); diff --git a/src/Runtime/Ghost.Graphics/ResourceManager.cs b/src/Runtime/Ghost.Graphics/ResourceManager.cs index eee6138..f3e36d6 100644 --- a/src/Runtime/Ghost.Graphics/ResourceManager.cs +++ b/src/Runtime/Ghost.Graphics/ResourceManager.cs @@ -4,11 +4,24 @@ using Ghost.Graphics.Core; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; +using System.Diagnostics; namespace Ghost.Graphics; public sealed class ResourceManager : IDisposable { + private readonly struct ResourceReturnEntry + { + public readonly Handle handle; + public readonly ulong returnFrame; + + public ResourceReturnEntry(Handle handle, ulong returnFrame) + { + this.handle = handle; + this.returnFrame = returnFrame; + } + } + private readonly IResourceAllocator _resourceAllocator; private readonly IResourceDatabase _resourceDatabase; @@ -18,6 +31,11 @@ public sealed class ResourceManager : IDisposable private readonly MaterialPaletteStore _materialPalettes; + private ulong _currentFrame; + + private UnsafeHashMap>> _resourceCache; + private UnsafeQueue _resourceReturnQueue; + private bool _disposed; public ResourceManager(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase) @@ -28,7 +46,11 @@ public sealed class ResourceManager : IDisposable _meshes = new UnsafeSlotMap(64, Allocator.Persistent); _materials = new UnsafeSlotMap(64, Allocator.Persistent); _shaders = new UnsafeList(16, Allocator.Persistent); + _materialPalettes = new MaterialPaletteStore(); + + _resourceCache = new UnsafeHashMap>>(32, Allocator.Persistent); + _resourceReturnQueue = new UnsafeQueue(32, Allocator.Persistent); } ~ResourceManager() @@ -36,6 +58,32 @@ public sealed class ResourceManager : IDisposable Dispose(); } + internal void BeginFrame(ulong currentFrame) + { + Debug.Assert(!_disposed); + _currentFrame = currentFrame; + } + + internal void EndFrame(ulong completedFrame) + { + Debug.Assert(!_disposed); + + while (_resourceReturnQueue.TryPeek(out var entry) && entry.returnFrame <= completedFrame) + { + _resourceReturnQueue.Dequeue(); + var result = _resourceDatabase.GetResourceDescription(entry.handle); + Debug.Assert(result.IsSuccess); + + ref var queue = ref _resourceCache.GetValueRefOrAddDefault(result.Value, out var exist); + if (!exist) + { + queue = new UnsafeQueue>(4, Allocator.Persistent); + } + + queue.Enqueue(entry.handle); + } + } + /// /// Creates a new mesh from the specified vertex and index data. /// @@ -44,7 +92,7 @@ public sealed class ResourceManager : IDisposable /// An representing the newly created mesh. public unsafe Handle CreateMesh(UnsafeList vertices, UnsafeList indices) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); var vertexBufferDesc = new BufferDesc { @@ -94,7 +142,7 @@ public sealed class ResourceManager : IDisposable /// An representing the newly created material. public Handle CreateMaterial(Identifier shader) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); var material = new Material(); if (material.SetShader(shader, this, _resourceDatabase, _resourceAllocator) != Error.None) @@ -113,7 +161,7 @@ public sealed class ResourceManager : IDisposable /// The viewGroup containing the shader's properties and passes. public Identifier CreateGraphicsShader(ShaderDescriptor descriptor) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); var shader = new Shader(descriptor); @@ -129,7 +177,7 @@ public sealed class ResourceManager : IDisposable /// true if a mesh with the specified Handle exists; otherwise, false. public bool HasMesh(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _meshes.Contains(handle.ID, handle.Generation); } @@ -155,7 +203,7 @@ public sealed class ResourceManager : IDisposable /// The handle of the mesh to release. Must refer to a mesh that was previously created and not already released. public void ReleaseMesh(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (!_meshes.TryGetElementAt(handle.ID, handle.Generation, out var mesh)) { @@ -173,7 +221,7 @@ public sealed class ResourceManager : IDisposable /// true if a material with the specified handle exists; otherwise, false. public bool HasMaterial(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _materials.Contains(handle.ID, handle.Generation); } @@ -199,7 +247,7 @@ public sealed class ResourceManager : IDisposable /// The handle of the material to release. Must refer to a material that has been previously acquired. public void ReleaseMaterial(Handle handle) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); var material = _materials.GetElementReferenceAt(handle.ID, handle.Generation, out var exist); if (!exist) @@ -218,7 +266,7 @@ public sealed class ResourceManager : IDisposable /// The palette index. Index 0 represents an empty palette. public int GetOrCreateMaterialPalette(ReadOnlySpan> materials) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); foreach (var material in materials) { @@ -237,7 +285,7 @@ public sealed class ResourceManager : IDisposable /// The palette index to validate. public bool HasMaterialPalette(Identifier paletteID) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _materialPalettes.IsValid(paletteID); } @@ -247,7 +295,7 @@ public sealed class ResourceManager : IDisposable /// The palette index to query. public MaterialPalette GetMaterialPaletteInfo(Identifier paletteID) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _materialPalettes.GetInfo(paletteID); } @@ -258,7 +306,7 @@ public sealed class ResourceManager : IDisposable /// The material slot inside the palette. public Handle GetMaterialPaletteMaterial(Identifier paletteID, int localMaterialIndex) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return _materialPalettes.GetMaterial(paletteID, localMaterialIndex); } @@ -268,7 +316,7 @@ public sealed class ResourceManager : IDisposable /// The palette index to release. public void ReleaseMaterialPalette(Identifier paletteID) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); _materialPalettes.Release(paletteID); } @@ -279,7 +327,7 @@ public sealed class ResourceManager : IDisposable /// true if a shader with the specified identifier exists; otherwise, false. public bool HasShader(Identifier id) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); return id.Value >= 0 && id.Value < _shaders.Count; } @@ -304,7 +352,7 @@ public sealed class ResourceManager : IDisposable /// The identifier of the shader to release. Must refer to a valid, previously created shader. public void ReleaseShader(Identifier id) { - ObjectDisposedException.ThrowIf(_disposed, this); + Debug.Assert(!_disposed); if (!HasShader(id)) { @@ -315,6 +363,38 @@ public sealed class ResourceManager : IDisposable shader.ReleaseResource(_resourceDatabase); } + public Handle GetPooledResource(in ResourceDesc desc) + { + Debug.Assert(!_disposed); + + ref var queue = ref _resourceCache.GetValueRef(desc, out var exist); + if (exist && queue.TryDequeue(out Handle handle)) + { + return handle; + } + + handle = desc.Type switch + { + ResourceType.Buffer => _resourceAllocator.CreateBuffer(in desc.BufferDescription, "PooledBuffer").AsResource(), + ResourceType.Texture => _resourceAllocator.CreateTexture(in desc.TextureDescription, "PooledTexture").AsResource(), + _ => throw new ArgumentException("Invalid resource type.", nameof(desc)), + }; + + return handle; + } + + public void ReturnPooledResource(Handle handle) + { + Debug.Assert(!_disposed); + + if (handle.IsInvalid) + { + return; + } + + _resourceReturnQueue.Enqueue(new ResourceReturnEntry(handle, _currentFrame)); + } + public void Dispose() { if (_disposed) @@ -342,6 +422,20 @@ public sealed class ResourceManager : IDisposable _shaders.Dispose(); _materialPalettes.Dispose(); + foreach (var kvp in _resourceCache) + { + var queue = kvp.Value; + while (queue.TryDequeue(out var handle)) + { + _resourceDatabase.ReleaseResource(handle); + } + + queue.Dispose(); + } + + _resourceCache.Dispose(); + _resourceReturnQueue.Dispose(); + _disposed = true; GC.SuppressFinalize(this); }