From 1284bb17de68afeebbfa655d1831388144bb11dc Mon Sep 17 00:00:00 2001 From: Misaki Date: Sat, 12 Jul 2025 01:20:04 +0900 Subject: [PATCH] Refactor graphics architecture and resource management Added DescriptorAllocator.cs to manage descriptor allocations for Direct3D 12. Added Texture2D.cs to handle 2D textures and GPU resource creation. Added DescriptorAllocatorExample.cs to demonstrate the new descriptor allocator interface. Changed project files to reference Misaki.HighPerformance.LowLevel instead of Misaki.HighPerformance.Unsafe. Changed _renderView type from IRenderer? to Renderer? in ScenePage.xaml.cs. Changed EngineCore.cs to remove explicit graphics API specification during initialization. Changed Logger.cs to enhance the Assert method with a DoesNotReturnIf attribute. Changed resource types in Mesh.cs from IResource to GraphicsResource. Removed multiple interfaces including ICommandBuffer, IDebugLayer, IGraphicsDevice, IPipelineResource, IRenderPass, IRenderer, IResource, and IResourceAllocator to simplify the graphics architecture. Removed D3D12DebugLayer class from DebugLayer.cs to streamline the debug layer implementation. Updated CommandList.cs and D3D12CommandBuffer.cs to implement a new command list structure for Direct3D 12. Updated Material.cs to improve handling of constant buffers and textures. Updated Shader.cs to include new structures for texture and property information. Updated GraphicsPipeline.cs to support the new graphics device and resource management system. Updated UnitTestAppWindow.xaml.cs to reflect changes in the renderer type and ensure proper resource management. Updated BindlessMeshRenderPass.cs and MeshRenderPass.cs to implement modern rendering techniques, including bindless textures and improved shader management. Updated CBufferCache.cs to align with the new resource management system and improve memory handling. --- Ghost.Core/Ghost.Core.csproj | 2 +- Ghost.Editor/Ghost.Editor.csproj | 2 +- .../View/Pages/EngineEditor/ScenePage.xaml.cs | 3 +- Ghost.Engine/EngineCore.cs | 3 +- Ghost.Engine/Services/Logger.cs | 3 +- Ghost.Graphics/Contracts/ICommandBuffer.cs | 14 - Ghost.Graphics/Contracts/IDebugLayer.cs | 5 - Ghost.Graphics/Contracts/IGraphicsDevice.cs | 20 - Ghost.Graphics/Contracts/IPipelineResource.cs | 5 - Ghost.Graphics/Contracts/IRenderPass.cs | 8 +- Ghost.Graphics/Contracts/IRenderer.cs | 39 - Ghost.Graphics/Contracts/IResource.cs | 38 - .../Contracts/IResourceAllocator.cs | 11 - Ghost.Graphics/D3D12/CommandList.cs | 59 ++ Ghost.Graphics/D3D12/D3D12CommandBuffer.cs | 52 -- .../{D3D12DebugLayer.cs => DebugLayer.cs} | 7 +- Ghost.Graphics/D3D12/DescriptorAllocator.cs | 330 +++++++++ Ghost.Graphics/D3D12/Descriptors.cs | 110 +++ ...D12GraphicsDevice.cs => GraphicsDevice.cs} | 41 +- .../{D3D12Resource.cs => GraphicsResource.cs} | 16 +- .../D3D12/{D3D12Renderer.cs => Renderer.cs} | 45 +- ...ourceAllocator.cs => ResourceAllocator.cs} | 27 +- Ghost.Graphics/D3D12/ResourceUploadBatch.cs | 339 +++++++++ ...llocator.cs => DescriptorHeapAllocator.cs} | 4 +- Ghost.Graphics/Data/GraphicsAPI.cs | 7 - Ghost.Graphics/Data/Material.cs | 210 +++++- Ghost.Graphics/Data/Mesh.cs | 27 +- Ghost.Graphics/Data/Texture2D.cs | 357 +++++++++ Ghost.Graphics/Data/Vertex.cs | 2 +- .../Examples/DescriptorAllocatorExample.cs | 178 +++++ Ghost.Graphics/Ghost.Graphics.csproj | 14 +- Ghost.Graphics/GraphicsPipeline.cs | 194 ++--- .../RenderPasses/BindlessMeshRenderPass.cs | 677 ++++++++++++++++++ Ghost.Graphics/RenderPasses/MeshRenderPass.cs | 233 +++++- Ghost.Graphics/Shading/CBufferCache.cs | 9 +- Ghost.Graphics/Shading/Shader.cs | 222 ++++-- Ghost.Graphics/Utilities/MeshBuilder.cs | 16 +- Ghost.UnitTest/UnitTestAppWindow.xaml.cs | 19 +- 38 files changed, 2831 insertions(+), 517 deletions(-) delete mode 100644 Ghost.Graphics/Contracts/ICommandBuffer.cs delete mode 100644 Ghost.Graphics/Contracts/IDebugLayer.cs delete mode 100644 Ghost.Graphics/Contracts/IGraphicsDevice.cs delete mode 100644 Ghost.Graphics/Contracts/IPipelineResource.cs delete mode 100644 Ghost.Graphics/Contracts/IRenderer.cs delete mode 100644 Ghost.Graphics/Contracts/IResource.cs delete mode 100644 Ghost.Graphics/Contracts/IResourceAllocator.cs create mode 100644 Ghost.Graphics/D3D12/CommandList.cs delete mode 100644 Ghost.Graphics/D3D12/D3D12CommandBuffer.cs rename Ghost.Graphics/D3D12/{D3D12DebugLayer.cs => DebugLayer.cs} (89%) create mode 100644 Ghost.Graphics/D3D12/DescriptorAllocator.cs create mode 100644 Ghost.Graphics/D3D12/Descriptors.cs rename Ghost.Graphics/D3D12/{D3D12GraphicsDevice.cs => GraphicsDevice.cs} (76%) rename Ghost.Graphics/D3D12/{D3D12Resource.cs => GraphicsResource.cs} (87%) rename Ghost.Graphics/D3D12/{D3D12Renderer.cs => Renderer.cs} (88%) rename Ghost.Graphics/D3D12/{D3D12ResourceAllocator.cs => ResourceAllocator.cs} (90%) create mode 100644 Ghost.Graphics/D3D12/ResourceUploadBatch.cs rename Ghost.Graphics/D3D12/Utilities/{D3D12DescriptorAllocator.cs => DescriptorHeapAllocator.cs} (97%) delete mode 100644 Ghost.Graphics/Data/GraphicsAPI.cs create mode 100644 Ghost.Graphics/Data/Texture2D.cs create mode 100644 Ghost.Graphics/Examples/DescriptorAllocatorExample.cs create mode 100644 Ghost.Graphics/RenderPasses/BindlessMeshRenderPass.cs diff --git a/Ghost.Core/Ghost.Core.csproj b/Ghost.Core/Ghost.Core.csproj index 65d5c8c..489a212 100644 --- a/Ghost.Core/Ghost.Core.csproj +++ b/Ghost.Core/Ghost.Core.csproj @@ -9,7 +9,7 @@ - ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll + ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll diff --git a/Ghost.Editor/Ghost.Editor.csproj b/Ghost.Editor/Ghost.Editor.csproj index a28fcaf..a825bb6 100644 --- a/Ghost.Editor/Ghost.Editor.csproj +++ b/Ghost.Editor/Ghost.Editor.csproj @@ -102,7 +102,7 @@ - ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll + ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll diff --git a/Ghost.Editor/View/Pages/EngineEditor/ScenePage.xaml.cs b/Ghost.Editor/View/Pages/EngineEditor/ScenePage.xaml.cs index a3d3fb7..e8cb3fb 100644 --- a/Ghost.Editor/View/Pages/EngineEditor/ScenePage.xaml.cs +++ b/Ghost.Editor/View/Pages/EngineEditor/ScenePage.xaml.cs @@ -1,6 +1,7 @@ using Ghost.Editor.Controls.Internal; using Ghost.Graphics; using Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using WinRT; @@ -9,7 +10,7 @@ namespace Ghost.Editor.View.Pages.EngineEditor; internal sealed partial class ScenePage : NavigationTabPage { - private IRenderer? _renderView; + private Renderer? _renderView; private ISwapChainPanelNative _swapChainPanelNative; public ScenePage() diff --git a/Ghost.Engine/EngineCore.cs b/Ghost.Engine/EngineCore.cs index 9bbaf75..fadca99 100644 --- a/Ghost.Engine/EngineCore.cs +++ b/Ghost.Engine/EngineCore.cs @@ -1,7 +1,6 @@ using Ghost.Engine.Models; using Ghost.Engine.Services; using Ghost.Graphics; -using Ghost.Graphics.Data; namespace Ghost.Engine; @@ -11,7 +10,7 @@ internal class EngineCore { ActivationHandler.Handle(args); - GraphicsPipeline.Initialize(GraphicsAPI.D3D12); + GraphicsPipeline.Initialize(); GraphicsPipeline.Start(); Logger.LogInfo("Engine started successfully."); diff --git a/Ghost.Engine/Services/Logger.cs b/Ghost.Engine/Services/Logger.cs index 61a3c83..bc93783 100644 --- a/Ghost.Engine/Services/Logger.cs +++ b/Ghost.Engine/Services/Logger.cs @@ -1,5 +1,6 @@ using Ghost.Engine.Models; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace Ghost.Engine.Services; @@ -89,7 +90,7 @@ public static class Logger LogExceptionInternal(ex); } - public static void Assert(bool condition, object? message = null) + public static void Assert([DoesNotReturnIf(false)] bool condition, object? message = null) { if (!condition) { diff --git a/Ghost.Graphics/Contracts/ICommandBuffer.cs b/Ghost.Graphics/Contracts/ICommandBuffer.cs deleted file mode 100644 index 6757fed..0000000 --- a/Ghost.Graphics/Contracts/ICommandBuffer.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Ghost.Graphics.Data; -using Win32.Graphics.Direct3D12; - -namespace Ghost.Graphics.Contracts; - -public interface ICommandBuffer -{ - // TODO: They should be internal, maybe an interface ICommandBufferInternal? - public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState); - public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress); - - public void DrawMesh(Mesh mesh, Material material); - public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size); -} \ No newline at end of file diff --git a/Ghost.Graphics/Contracts/IDebugLayer.cs b/Ghost.Graphics/Contracts/IDebugLayer.cs deleted file mode 100644 index d8ddafb..0000000 --- a/Ghost.Graphics/Contracts/IDebugLayer.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Ghost.Graphics.Contracts; - -internal interface IDebugLayer : IDisposable -{ -} \ No newline at end of file diff --git a/Ghost.Graphics/Contracts/IGraphicsDevice.cs b/Ghost.Graphics/Contracts/IGraphicsDevice.cs deleted file mode 100644 index 55fa65f..0000000 --- a/Ghost.Graphics/Contracts/IGraphicsDevice.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Ghost.Graphics.Data; - -namespace Ghost.Graphics.Contracts; - -internal interface IGraphicsDevice : IDisposable -{ - public static abstract GraphicsAPI TargetAPI - { - get; - } - - public ReadOnlySpan Renderers - { - get; - } - - public IRenderer CreateRenderer(in SwapChainPresenter swapChainSurface); - public void RemoveRenderer(IRenderer renderer); - public void InitializePendingRenderers(); -} \ No newline at end of file diff --git a/Ghost.Graphics/Contracts/IPipelineResource.cs b/Ghost.Graphics/Contracts/IPipelineResource.cs deleted file mode 100644 index 9068741..0000000 --- a/Ghost.Graphics/Contracts/IPipelineResource.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Ghost.Graphics.Contracts; - -internal interface IPipelineResource : IDisposable -{ -} \ No newline at end of file diff --git a/Ghost.Graphics/Contracts/IRenderPass.cs b/Ghost.Graphics/Contracts/IRenderPass.cs index 184e21e..d3b4fd5 100644 --- a/Ghost.Graphics/Contracts/IRenderPass.cs +++ b/Ghost.Graphics/Contracts/IRenderPass.cs @@ -1,7 +1,9 @@ -namespace Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12; + +namespace Ghost.Graphics.Contracts; internal interface IRenderPass : IDisposable { - void Initialize(ICommandBuffer cmb); - void Execute(ICommandBuffer cmb); + void Initialize(CommandList cmd); + void Execute(CommandList cmd); } \ No newline at end of file diff --git a/Ghost.Graphics/Contracts/IRenderer.cs b/Ghost.Graphics/Contracts/IRenderer.cs deleted file mode 100644 index 52a4dd7..0000000 --- a/Ghost.Graphics/Contracts/IRenderer.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Ghost.Graphics.Contracts; - -/// -/// Defines the contract for a render view in the graphics pipeline. -/// -internal interface IRenderer : IDisposable -{ - public ReadOnlySpan RenderPasses - { - get; - } - - /// - /// Requests a resize of the render view. - /// - /// The new width of the render view. - /// The new height of the render view. - /// This only submits a resize request without executing it. May overwrite last request if next request issued before next frame. - public void RequestResize(uint width, uint height); - /// - /// Executes any pending resize operations for the current context. - /// - public void ExecutePendingResize(); - - public void Initialize(); - /// - /// Renders the current content to the output target. - /// - public void Render(); - - /// - /// Waits for the next frame to be ready for rendering. - /// - public void WaitNextFrame(); - /// - /// Waits for the render view to become idle, ensuring all previous commands have been executed and resources are ready for the next frame. - /// - public void WaitIdle(); -} \ No newline at end of file diff --git a/Ghost.Graphics/Contracts/IResource.cs b/Ghost.Graphics/Contracts/IResource.cs deleted file mode 100644 index c062e34..0000000 --- a/Ghost.Graphics/Contracts/IResource.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Misaki.HighPerformance.LowLevel.Collections; - -namespace Ghost.Graphics.Contracts; - -public unsafe interface IResource : IDisposable -{ - public ulong GPUAddress - { - get; - } - - public string Name - { - get; - set; - } - - public bool TempResource - { - get; - } - - public void SetData(Span data) - where T : unmanaged; - - public void SetData(T* data, uint length) - where T : unmanaged; - - public void SetData(void* data, uint size); - - public UnsafeArray ReadData(Allocator allocator) - where T : unmanaged; - - public void ReadData(T* ppData, uint* size) - where T : unmanaged; - - public void ReadData(void* ppData, uint* size); -} \ No newline at end of file diff --git a/Ghost.Graphics/Contracts/IResourceAllocator.cs b/Ghost.Graphics/Contracts/IResourceAllocator.cs deleted file mode 100644 index 5df866c..0000000 --- a/Ghost.Graphics/Contracts/IResourceAllocator.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Win32.Graphics.Direct3D12; - -namespace Ghost.Graphics.Contracts; - -internal unsafe interface IResourceAllocator : IDisposable -{ - public IResource CreateUploadBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None); - public IResource CreateCopyDestinationBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None); - - public void ReleaseTempResource(); -} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/CommandList.cs b/Ghost.Graphics/D3D12/CommandList.cs new file mode 100644 index 0000000..3816838 --- /dev/null +++ b/Ghost.Graphics/D3D12/CommandList.cs @@ -0,0 +1,59 @@ +using Ghost.Core; +using Ghost.Graphics.Data; +using Win32.Graphics.Direct3D; +using Win32.Graphics.Direct3D12; + +namespace Ghost.Graphics.D3D12; + +public unsafe class CommandList +{ + private readonly ConstPtr _commandList; + + internal ConstPtr NativeCommandList => _commandList; + + public CommandList(ID3D12GraphicsCommandList10* commandList) + { + _commandList = commandList; + } + + public void BarrierTransition(GraphicsResource resource, ResourceStates beforeState, ResourceStates afterState) + { + _commandList.Ptr->ResourceBarrierTransition(resource.NativeResource.Ptr, beforeState, afterState); + } + + public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress) + { + _commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress); + } + + public void DrawMesh(Mesh mesh, Material material) + { + _commandList.Ptr->SetGraphicsRootSignature(material.Shader.RootSignature); + _commandList.Ptr->SetPipelineState(material.Shader.PipelineState); + + // Bind shader-visible descriptor heaps before setting descriptor tables + if (material.Shader.Textures.Count > 0) + { + var shaderVisibleHeaps = GraphicsPipeline.DescriptorAllocator.GetShaderVisibleHeaps(); + var heapPtrs = stackalloc ID3D12DescriptorHeap*[shaderVisibleHeaps.Length]; + for (var i = 0; i < shaderVisibleHeaps.Length; i++) + { + heapPtrs[i] = shaderVisibleHeaps[i].Ptr; + } + _commandList.Ptr->SetDescriptorHeaps((uint)shaderVisibleHeaps.Length, heapPtrs); + } + + material.Bind(this); + + _commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList); + _commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView); + _commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView); + + _commandList.Ptr->DrawIndexedInstanced(mesh.IndexCount, 1, 0, 0, 0); + } + + public void CopyResource(GraphicsResource dstResource, uint dstOffset, GraphicsResource srcResource, uint srcOffset, uint size) + { + _commandList.Ptr->CopyBufferRegion(dstResource.NativeResource, dstOffset, srcResource.NativeResource, srcOffset, size); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs deleted file mode 100644 index 4ee6a9c..0000000 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Ghost.Core; -using Ghost.Graphics.Contracts; -using Ghost.Graphics.Data; -using Win32.Graphics.Direct3D; -using Win32.Graphics.Direct3D12; - -namespace Ghost.Graphics.D3D12; - -internal unsafe class D3D12CommandBuffer : ICommandBuffer -{ - private readonly ConstPtr _commandList; - - internal ConstPtr CommandList => _commandList; - - public D3D12CommandBuffer(ID3D12GraphicsCommandList10* commandList) - { - _commandList = commandList; - } - - public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState) - { - var dxResource = (D3D12Resource)resource; - _commandList.Ptr->ResourceBarrierTransition(dxResource.NativeResource.Ptr, beforeState, afterState); - } - - public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress) - { - _commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress); - } - - public void DrawMesh(Mesh mesh, Material material) - { - _commandList.Ptr->SetGraphicsRootSignature(material.Shader.RootSignature); - _commandList.Ptr->SetPipelineState(material.Shader.PipelineState); - - material.UploadAndBind(this); - - _commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList); - _commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView); - _commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView); - - _commandList.Ptr->DrawIndexedInstanced(mesh.IndexCount, 1, 0, 0, 0); - } - - public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size) - { - var dstDXResource = (D3D12Resource)dstResource; - var srcDXResource = (D3D12Resource)srcResource; - - _commandList.Ptr->CopyBufferRegion(dstDXResource.NativeResource, dstOffset, srcDXResource.NativeResource, srcOffset, size); - } -} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12DebugLayer.cs b/Ghost.Graphics/D3D12/DebugLayer.cs similarity index 89% rename from Ghost.Graphics/D3D12/D3D12DebugLayer.cs rename to Ghost.Graphics/D3D12/DebugLayer.cs index 35c145e..182854a 100644 --- a/Ghost.Graphics/D3D12/D3D12DebugLayer.cs +++ b/Ghost.Graphics/D3D12/DebugLayer.cs @@ -1,17 +1,16 @@ -using Ghost.Graphics.Contracts; -using Win32; +using Win32; using Win32.Graphics.Direct3D12; using Win32.Graphics.Dxgi; namespace Ghost.Graphics.D3D12; -internal unsafe class D3D12DebugLayer : IDebugLayer +internal unsafe class DebugLayer { private readonly ComPtr _d3d12Debug; private readonly ComPtr _dxgiDebug; private readonly ComPtr _dxgiInfoQueue; - public D3D12DebugLayer() + public DebugLayer() { D3D12GetDebugInterface(__uuidof(), _d3d12Debug.GetVoidAddressOf()); _d3d12Debug.Get()->EnableDebugLayer(); diff --git a/Ghost.Graphics/D3D12/DescriptorAllocator.cs b/Ghost.Graphics/D3D12/DescriptorAllocator.cs new file mode 100644 index 0000000..5ee7396 --- /dev/null +++ b/Ghost.Graphics/D3D12/DescriptorAllocator.cs @@ -0,0 +1,330 @@ +using Ghost.Core; +using Ghost.Graphics.D3D12.Utilities; +using Win32.Graphics.Direct3D12; + +namespace Ghost.Graphics.D3D12; + +/// +/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps. +/// +internal class DescriptorAllocator : IDisposable +{ + private readonly DescriptorHeapAllocator _rtvHeap; + private readonly DescriptorHeapAllocator _dsvHeap; + private readonly DescriptorHeapAllocator _srvHeap; + private readonly DescriptorHeapAllocator _samplerHeap; + + private bool _disposed; + + public DescriptorAllocator(uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256) + { + var device = GraphicsPipeline.GraphicsDevice; + + _rtvHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.Rtv, initialRtvCount); + _dsvHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.Dsv, initialDsvCount); + _srvHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount); + _samplerHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.Sampler, initialSamplerCount); + } + + #region RTV Methods + + public RenderTargetDescriptor AllocateRTV() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var index = _rtvHeap.AllocateDescriptor(); + if (index == uint.MaxValue) + { + throw new InvalidOperationException("Failed to allocate RTV descriptor"); + } + + var cpuHandle = _rtvHeap.GetCpuHandle(index); + return new RenderTargetDescriptor(index, cpuHandle); + } + + public RenderTargetDescriptor[] AllocateRTVs(uint count) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var baseIndex = _rtvHeap.AllocateDescriptors(count); + if (baseIndex == uint.MaxValue) + { + throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors"); + } + + var descriptors = new RenderTargetDescriptor[count]; + for (uint i = 0; i < count; i++) + { + var index = baseIndex + i; + var cpuHandle = _rtvHeap.GetCpuHandle(index); + descriptors[i] = new RenderTargetDescriptor(index, cpuHandle); + } + + return descriptors; + } + + public void ReleaseRTV(RenderTargetDescriptor descriptor) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (descriptor is RenderTargetDescriptor d3d12Descriptor) + { + _rtvHeap.ReleaseDescriptor(d3d12Descriptor.Index); + } + } + + public void ReleaseRTVs(RenderTargetDescriptor[] descriptors) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + foreach (var descriptor in descriptors) + { + ReleaseRTV(descriptor); + } + } + + #endregion + + #region DSV Methods + + public DepthStencilDescriptor AllocateDSV() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var index = _dsvHeap.AllocateDescriptor(); + if (index == uint.MaxValue) + { + throw new InvalidOperationException("Failed to allocate DSV descriptor"); + } + + var cpuHandle = _dsvHeap.GetCpuHandle(index); + return new DepthStencilDescriptor(index, cpuHandle); + } + + public DepthStencilDescriptor[] AllocateDSVs(uint count) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var baseIndex = _dsvHeap.AllocateDescriptors(count); + if (baseIndex == uint.MaxValue) + { + throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors"); + } + + var descriptors = new DepthStencilDescriptor[count]; + for (uint i = 0; i < count; i++) + { + var index = baseIndex + i; + var cpuHandle = _dsvHeap.GetCpuHandle(index); + descriptors[i] = new DepthStencilDescriptor(index, cpuHandle); + } + + return descriptors; + } + + public void ReleaseDSV(DepthStencilDescriptor descriptor) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (descriptor is DepthStencilDescriptor d3d12Descriptor) + { + _dsvHeap.ReleaseDescriptor(d3d12Descriptor.Index); + } + } + + public void ReleaseDSVs(DepthStencilDescriptor[] descriptors) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + foreach (var descriptor in descriptors) + { + ReleaseDSV(descriptor); + } + } + + #endregion + + #region SRV Methods + + public ShaderResourceDescriptor AllocateSRV() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var index = _srvHeap.AllocateDescriptor(); + if (index == uint.MaxValue) + { + throw new InvalidOperationException("Failed to allocate SRV descriptor"); + } + + var cpuHandle = _srvHeap.GetCpuHandle(index); + var gpuHandle = _srvHeap.GetGpuHandle(index); + + // Copy to shader visible heap + _srvHeap.CopyToShaderVisibleHeap(index); + + return new ShaderResourceDescriptor(index, cpuHandle, gpuHandle); + } + + public ShaderResourceDescriptor[] AllocateSRVs(uint count) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var baseIndex = _srvHeap.AllocateDescriptors(count); + if (baseIndex == uint.MaxValue) + { + throw new InvalidOperationException($"Failed to allocate {count} SRV descriptors"); + } + + var descriptors = new ShaderResourceDescriptor[count]; + for (uint i = 0; i < count; i++) + { + var index = baseIndex + i; + var cpuHandle = _srvHeap.GetCpuHandle(index); + var gpuHandle = _srvHeap.GetGpuHandle(index); + descriptors[i] = new ShaderResourceDescriptor(index, cpuHandle, gpuHandle); + } + + // Copy all descriptors to shader visible heap + _srvHeap.CopyToShaderVisibleHeap(baseIndex, count); + + return descriptors; + } + + public void ReleaseSRV(ShaderResourceDescriptor descriptor) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (descriptor is ShaderResourceDescriptor d3d12Descriptor) + { + _srvHeap.ReleaseDescriptor(d3d12Descriptor.Index); + } + } + + public void ReleaseSRVs(ShaderResourceDescriptor[] descriptors) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + foreach (var descriptor in descriptors) + { + ReleaseSRV(descriptor); + } + } + + #endregion + + #region Sampler Methods + + public SamplerDescriptor AllocateSampler() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var index = _samplerHeap.AllocateDescriptor(); + if (index == uint.MaxValue) + { + throw new InvalidOperationException("Failed to allocate Sampler descriptor"); + } + + var cpuHandle = _samplerHeap.GetCpuHandle(index); + var gpuHandle = _samplerHeap.GetGpuHandle(index); + + // Copy to shader visible heap + _samplerHeap.CopyToShaderVisibleHeap(index); + + return new SamplerDescriptor(index, cpuHandle, gpuHandle); + } + + public SamplerDescriptor[] AllocateSamplers(uint count) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var baseIndex = _samplerHeap.AllocateDescriptors(count); + if (baseIndex == uint.MaxValue) + { + throw new InvalidOperationException($"Failed to allocate {count} Sampler descriptors"); + } + + var descriptors = new SamplerDescriptor[count]; + for (uint i = 0; i < count; i++) + { + var index = baseIndex + i; + var cpuHandle = _samplerHeap.GetCpuHandle(index); + var gpuHandle = _samplerHeap.GetGpuHandle(index); + descriptors[i] = new SamplerDescriptor(index, cpuHandle, gpuHandle); + } + + // Copy all descriptors to shader visible heap + _samplerHeap.CopyToShaderVisibleHeap(baseIndex, count); + + return descriptors; + } + + public void ReleaseSampler(SamplerDescriptor descriptor) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (descriptor is SamplerDescriptor d3d12Descriptor) + { + _samplerHeap.ReleaseDescriptor(d3d12Descriptor.Index); + } + } + + public void ReleaseSamplers(SamplerDescriptor[] descriptors) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + foreach (var descriptor in descriptors) + { + ReleaseSampler(descriptor); + } + } + + #endregion + + #region Utility Methods + + /// + /// Gets the RTV heap for binding to the command list. + /// + public ConstPtr GetRTVHeap() => _rtvHeap.Heap; + + /// + /// Gets the DSV heap for binding to the command list. + /// + public ConstPtr GetDSVHeap() => _dsvHeap.Heap; + + /// + /// Gets the SRV heap for binding to the command list. + /// + public ConstPtr GetSRVHeap() => _srvHeap.ShaderVisibleHeap; + + /// + /// Gets the sampler heap for binding to the command list. + /// + public ConstPtr GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap; + + /// + /// Gets the shader visible heaps that need to be bound to the command list. + /// + public ConstPtr[] GetShaderVisibleHeaps() + { + return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap]; + } + + #endregion + + public void Dispose() + { + if (_disposed) + { + return; + } + + _rtvHeap.Dispose(); + _dsvHeap.Dispose(); + _srvHeap.Dispose(); + _samplerHeap.Dispose(); + + _disposed = true; + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/Descriptors.cs b/Ghost.Graphics/D3D12/Descriptors.cs new file mode 100644 index 0000000..28e653a --- /dev/null +++ b/Ghost.Graphics/D3D12/Descriptors.cs @@ -0,0 +1,110 @@ +using Win32.Graphics.Direct3D12; + +namespace Ghost.Graphics.D3D12; + +/// +/// Base class for D3D12 descriptor implementations. +/// +internal abstract class Descriptor +{ + protected readonly uint index; + protected readonly bool isShaderVisible; + + protected Descriptor(uint index, bool isShaderVisible) + { + this.index = index; + this.isShaderVisible = isShaderVisible; + } + + public bool IsValid => index != uint.MaxValue; + public uint Index => index; + public bool IsShaderVisible => isShaderVisible; + + /// + /// Gets the CPU descriptor handle. + /// + public abstract CpuDescriptorHandle CpuHandle + { + get; + } + + /// + /// Gets the GPU descriptor handle (only valid for shader-visible descriptors). + /// + public abstract GpuDescriptorHandle GpuHandle + { + get; + } +} + +/// +/// D3D12 implementation of render target view (RTV) descriptor. +/// +internal sealed class RenderTargetDescriptor : Descriptor +{ + private readonly CpuDescriptorHandle _cpuHandle; + + public RenderTargetDescriptor(uint index, CpuDescriptorHandle cpuHandle) + : base(index, false) + { + _cpuHandle = cpuHandle; + } + + public override CpuDescriptorHandle CpuHandle => _cpuHandle; + public override GpuDescriptorHandle GpuHandle => default; +} + +/// +/// D3D12 implementation of depth stencil view (DSV) descriptor. +/// +internal sealed class DepthStencilDescriptor : Descriptor +{ + private readonly CpuDescriptorHandle _cpuHandle; + + public DepthStencilDescriptor(uint index, CpuDescriptorHandle cpuHandle) + : base(index, false) // DSVs are not shader visible + { + _cpuHandle = cpuHandle; + } + + public override CpuDescriptorHandle CpuHandle => _cpuHandle; + public override GpuDescriptorHandle GpuHandle => default; +} + +/// +/// D3D12 implementation of shader resource view (SRV) descriptor. +/// +internal sealed class ShaderResourceDescriptor : Descriptor +{ + private readonly CpuDescriptorHandle _cpuHandle; + private readonly GpuDescriptorHandle _gpuHandle; + + public ShaderResourceDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle) + : base(index, true) + { + _cpuHandle = cpuHandle; + _gpuHandle = gpuHandle; + } + + public override CpuDescriptorHandle CpuHandle => _cpuHandle; + public override GpuDescriptorHandle GpuHandle => _gpuHandle; +} + +/// +/// D3D12 implementation of sampler descriptor. +/// +internal sealed class SamplerDescriptor : Descriptor +{ + private readonly CpuDescriptorHandle _cpuHandle; + private readonly GpuDescriptorHandle _gpuHandle; + + public SamplerDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle) + : base(index, true) + { + _cpuHandle = cpuHandle; + _gpuHandle = gpuHandle; + } + + public override CpuDescriptorHandle CpuHandle => _cpuHandle; + public override GpuDescriptorHandle GpuHandle => _gpuHandle; +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12GraphicsDevice.cs b/Ghost.Graphics/D3D12/GraphicsDevice.cs similarity index 76% rename from Ghost.Graphics/D3D12/D3D12GraphicsDevice.cs rename to Ghost.Graphics/D3D12/GraphicsDevice.cs index 4935dee..98f8739 100644 --- a/Ghost.Graphics/D3D12/D3D12GraphicsDevice.cs +++ b/Ghost.Graphics/D3D12/GraphicsDevice.cs @@ -1,5 +1,4 @@ using Ghost.Core; -using Ghost.Graphics.Contracts; using Ghost.Graphics.Data; using System.Collections.Immutable; using Win32; @@ -9,39 +8,38 @@ using Win32.Graphics.Dxgi; namespace Ghost.Graphics.D3D12; -internal unsafe class D3D12GraphicsDevice : IGraphicsDevice +internal unsafe class GraphicsDevice { #if DEBUG - private readonly D3D12DebugLayer _debugLayer; + private readonly DebugLayer _debugLayer; #endif private ComPtr _dxgiFactory; private ComPtr _device; private ComPtr _commandQueue; - private ImmutableArray _initializeQueue; - private ImmutableArray _renderers; + private ImmutableArray _initializeQueue; + private ImmutableArray _renderers; private bool _disposed; - public static GraphicsAPI TargetAPI => GraphicsAPI.D3D12; - public ReadOnlySpan InitializeQueue => _initializeQueue.AsSpan(); - public ReadOnlySpan Renderers => _renderers.AsSpan(); + public ReadOnlySpan InitializeQueue => _initializeQueue.AsSpan(); + public ReadOnlySpan Renderers => _renderers.AsSpan(); public ConstPtr NativeDevice => new(_device.Get()); public ConstPtr DXGIFactory => new(_dxgiFactory.Get()); public ConstPtr CommandQueue => new(_commandQueue.Get()); - public D3D12GraphicsDevice() + public GraphicsDevice() { #if DEBUG - _debugLayer = new D3D12DebugLayer(); + _debugLayer = new DebugLayer(); #endif InitializeDevice(); InitializeCommandQueue(); - _initializeQueue = ImmutableArray.Empty; - _renderers = ImmutableArray.Empty; + _initializeQueue = ImmutableArray.Empty; + _renderers = ImmutableArray.Empty; } private void InitializeDevice() @@ -67,7 +65,7 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice continue; } - if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_11_0, __uuidof(), _device.GetVoidAddressOf()).Success) + if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_12_0, __uuidof(), _device.GetVoidAddressOf()).Success) { break; } @@ -75,7 +73,7 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice if (_device.Get() == null) { - throw new PlatformNotSupportedException("Cannot create ID3D12Device"); + throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0"); } } @@ -94,17 +92,17 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice } } - public IRenderer CreateRenderer(in SwapChainPresenter presenter) + public Renderer CreateRenderer(in SwapChainPresenter presenter) { - var renderView = new D3D12Renderer(this, in presenter); + var renderView = new Renderer(this, in presenter); ImmutableInterlocked.Update(ref _initializeQueue, old => old.Add(renderView)); return renderView; } - public void RemoveRenderer(IRenderer renderer) + public void RemoveRenderer(Renderer renderer) { - if (renderer is D3D12Renderer dx12RenderView) + if (renderer is Renderer dx12RenderView) { dx12RenderView.Dispose(); @@ -148,13 +146,8 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice renderer.Dispose(); } - foreach (var renderView in _renderers) - { - renderView.Dispose(); - } - _commandQueue.Dispose(); - _device.Dispose(); + _device.Reset(); _dxgiFactory.Dispose(); #if DEBUG diff --git a/Ghost.Graphics/D3D12/D3D12Resource.cs b/Ghost.Graphics/D3D12/GraphicsResource.cs similarity index 87% rename from Ghost.Graphics/D3D12/D3D12Resource.cs rename to Ghost.Graphics/D3D12/GraphicsResource.cs index 17e7f46..4aebf72 100644 --- a/Ghost.Graphics/D3D12/D3D12Resource.cs +++ b/Ghost.Graphics/D3D12/GraphicsResource.cs @@ -1,5 +1,4 @@ using Ghost.Core; -using Ghost.Graphics.Contracts; using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.CompilerServices; using Win32; @@ -7,7 +6,7 @@ using Win32.Graphics.Direct3D12; namespace Ghost.Graphics.D3D12; -public unsafe class D3D12Resource : IResource +public unsafe class GraphicsResource { private ComPtr _nativeResource; private string _name = string.Empty; @@ -33,13 +32,13 @@ public unsafe class D3D12Resource : IResource get; } - internal D3D12Resource(ComPtr nativeResource, bool temp) + internal GraphicsResource(ComPtr nativeResource, bool temp = false) { _nativeResource = nativeResource; TempResource = temp; } - ~D3D12Resource() + ~GraphicsResource() { DisposeInternal(); } @@ -68,12 +67,7 @@ public unsafe class D3D12Resource : IResource var range = new Win32.Graphics.Direct3D12.Range(0, size); void* mappedPtr; - var hr = _nativeResource.Get()->Map(0, &range, &mappedPtr); - if (hr.Failure) - { - var message = hr.ToString(); - throw new InvalidOperationException($"Failed to map resource: {message}"); - } + ThrowIfFailed(_nativeResource.Get()->Map(0, &range, &mappedPtr)); Unsafe.CopyBlock(mappedPtr, data, size); _nativeResource.Get()->Unmap(0, &range); @@ -99,7 +93,7 @@ public unsafe class D3D12Resource : IResource public void ReadData(T* pData, uint* size) where T : unmanaged { - ReadData(pData, size); + ReadData((void*)pData, size); } public void ReadData(void* pData, uint* size) diff --git a/Ghost.Graphics/D3D12/D3D12Renderer.cs b/Ghost.Graphics/D3D12/Renderer.cs similarity index 88% rename from Ghost.Graphics/D3D12/D3D12Renderer.cs rename to Ghost.Graphics/D3D12/Renderer.cs index c0c404b..822fb80 100644 --- a/Ghost.Graphics/D3D12/D3D12Renderer.cs +++ b/Ghost.Graphics/D3D12/Renderer.cs @@ -14,7 +14,7 @@ namespace Ghost.Graphics.D3D12; // TODO: We should split the renderer and swap chain into different classes to allow for more flexibility in rendering pipelines. // Each renderer can have a render target (swap chain or texture). // When render target is null, skip the render pass execution. -internal unsafe class D3D12Renderer : IRenderer +internal unsafe class Renderer { private struct FrameResource : IDisposable { @@ -22,17 +22,17 @@ internal unsafe class D3D12Renderer : IRenderer public ComPtr commandList; public ComPtr backBuffer; - public ICommandBuffer commandBuffer; - public uint backBufferDescriptorIndexes; + public CommandList cmd; + public RenderTargetDescriptor rtvDescriptor; public ulong fenceValue; - public FrameResource(D3D12Renderer renderer, uint index) + public FrameResource(Renderer renderer, uint index) { renderer._graphicsDevice.NativeDevice.Ptr->CreateCommandAllocator(CommandListType.Direct, __uuidof(), commandAllocator.GetVoidAddressOf()); renderer._graphicsDevice.NativeDevice.Ptr->CreateCommandList(0u, CommandListType.Direct, commandAllocator.Get(), null, __uuidof(), commandList.GetVoidAddressOf()); - commandBuffer = new D3D12CommandBuffer(commandList.Get()); - backBufferDescriptorIndexes = renderer.CreateBackBufferResource(index, backBuffer.GetAddressOf()); + cmd = new(commandList.Get()); + rtvDescriptor = renderer.CreateBackBufferResource(index, backBuffer.GetAddressOf()); } public readonly void ResetCommandBuffer() @@ -58,13 +58,11 @@ internal unsafe class D3D12Renderer : IRenderer commandAllocator.Dispose(); commandList.Dispose(); backBuffer.Dispose(); + GraphicsPipeline.DescriptorAllocator.ReleaseRTV(rtvDescriptor); } } - private const int _RENDER_TARGET_VIEW_HEAP_SIZE = 1024; - private const int _DEPTH_STENCIL_VIEW_HEAP_SIZE = 256; - - private readonly D3D12GraphicsDevice _graphicsDevice; + private readonly GraphicsDevice _graphicsDevice; private readonly SwapChainPresenter _swapChainPresenter; private ComPtr _swapChain = default; @@ -74,8 +72,6 @@ internal unsafe class D3D12Renderer : IRenderer private readonly FrameResource[] _frameResources; private readonly AutoResetEvent _fenceEvent; - private D3D12DescriptorAllocator _rtvHeap; - private ImmutableArray _renderPasses; private readonly Lock _lock = new(); @@ -89,16 +85,15 @@ internal unsafe class D3D12Renderer : IRenderer public ReadOnlySpan RenderPasses => _renderPasses.AsSpan(); - public D3D12Renderer(D3D12GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface) + public Renderer(GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface) { _graphicsDevice = graphicsDevice; _swapChainPresenter = swapChainSurface; _viewPortWidth = swapChainSurface.Width; _viewPortHeight = swapChainSurface.Height; - _rtvHeap = new(_graphicsDevice.NativeDevice, DescriptorHeapType.Rtv, _RENDER_TARGET_VIEW_HEAP_SIZE); _fenceEvent = new(false); - _renderPasses = [new MeshRenderPass()]; + _renderPasses = [new BindlessMeshRenderPass()]; InitializeSwapChain(); InitializeFrameResource(out _frameResources); @@ -190,14 +185,14 @@ internal unsafe class D3D12Renderer : IRenderer } } - private uint CreateBackBufferResource(uint i, ID3D12Resource** backBuffer) + private RenderTargetDescriptor CreateBackBufferResource(uint i, ID3D12Resource** backBuffer) { _swapChain.Get()->GetBuffer(i, __uuidof(), (void**)backBuffer); (*backBuffer)->SetName($"BackBuffer_{i}"); - var index = _rtvHeap.AllocateDescriptor(); - var rtvHandle = _rtvHeap.GetCpuHandle(index); + var rtvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateRTV(); + var rtvHandle = rtvDescriptor.CpuHandle; _graphicsDevice.NativeDevice.Ptr->CreateRenderTargetView(*backBuffer, null, rtvHandle); - return index; + return rtvDescriptor; } public void ExecutePendingResize() @@ -225,7 +220,7 @@ internal unsafe class D3D12Renderer : IRenderer if (frameResource.backBuffer.Get() is not null) { var c = frameResource.backBuffer.Reset(); - _rtvHeap.ReleaseDescriptor(frameResource.backBufferDescriptorIndexes); + GraphicsPipeline.DescriptorAllocator.ReleaseRTV(frameResource.rtvDescriptor); } frameResource.fenceValue = _frameResources[_backBufferIndex].fenceValue; @@ -239,7 +234,7 @@ internal unsafe class D3D12Renderer : IRenderer for (var i = 0u; i < GraphicsPipeline._FRAME_COUNT; i++) { var index = CreateBackBufferResource(i, _frameResources[i].backBuffer.GetAddressOf()); - _frameResources[i].backBufferDescriptorIndexes = index; + _frameResources[i].rtvDescriptor = index; } _backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex(); @@ -253,7 +248,7 @@ internal unsafe class D3D12Renderer : IRenderer foreach (var pass in _renderPasses) { - pass.Initialize(frameResource.commandBuffer); + pass.Initialize(frameResource.cmd); } frameResource.ExecuteCommandBuffer(_graphicsDevice.CommandQueue); @@ -265,7 +260,7 @@ internal unsafe class D3D12Renderer : IRenderer _backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex(); ref var frameResource = ref _frameResources[_backBufferIndex]; - var cpuHandle = _rtvHeap.GetCpuHandle(frameResource.backBufferDescriptorIndexes); + var cpuHandle = frameResource.rtvDescriptor.CpuHandle; frameResource.ResetCommandBuffer(); frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.Present, ResourceStates.RenderTarget); @@ -282,7 +277,7 @@ internal unsafe class D3D12Renderer : IRenderer foreach (var pass in _renderPasses) { - pass.Execute(frameResource.commandBuffer); + pass.Execute(frameResource.cmd); } frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.RenderTarget, ResourceStates.Present); @@ -353,8 +348,6 @@ internal unsafe class D3D12Renderer : IRenderer _fence.Dispose(); _fenceEvent.Dispose(); - _rtvHeap.Dispose(); - _backBufferIndex = 0; _disposed = true; diff --git a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs b/Ghost.Graphics/D3D12/ResourceAllocator.cs similarity index 90% rename from Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs rename to Ghost.Graphics/D3D12/ResourceAllocator.cs index c324fcd..ab5dc7a 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs +++ b/Ghost.Graphics/D3D12/ResourceAllocator.cs @@ -1,23 +1,22 @@ -using Ghost.Graphics.Contracts; -using Win32; +using Win32; using Win32.Graphics.Direct3D12; namespace Ghost.Graphics.D3D12; -internal unsafe class D3D12ResourceAllocator : IResourceAllocator +internal unsafe class ResourceAllocator { private readonly struct TempResourceAllocInfo { - public readonly D3D12Resource resource; + public readonly GraphicsResource resource; public readonly uint cpuFenceValue; - public TempResourceAllocInfo(D3D12Resource resource, uint cpuFenceValue) + public TempResourceAllocInfo(GraphicsResource resource, uint cpuFenceValue) { this.resource = resource; this.cpuFenceValue = cpuFenceValue; } - public TempResourceAllocInfo(D3D12Resource resource) + public TempResourceAllocInfo(GraphicsResource resource) : this(resource, GraphicsPipeline.CPUFenceValue + 1) { } @@ -224,29 +223,28 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator // } //} - public IResource CreateUploadBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None) + public GraphicsResource CreateUploadBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None) { if (sizeInBytes > _MAX_BYTES) { throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})"); } - var device = GraphicsPipeline.GetGraphicsDevice(); var heapProperties = new HeapProperties(HeapType.Upload); var resourceDescription = ResourceDescription.Buffer(sizeInBytes, flags); ComPtr buffer = default; - device.NativeDevice.Ptr->CreateCommittedResource( + GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource( &heapProperties, HeapFlags.None, &resourceDescription, - ResourceStates.Common, + ResourceStates.GenericRead, null, __uuidof(), buffer.GetVoidAddressOf() ); - var resource = new D3D12Resource(buffer.Move(), tempResource); + var resource = new GraphicsResource(buffer.Move(), tempResource); if (tempResource) { _temResources.Enqueue(new(resource)); @@ -255,19 +253,18 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator return resource; } - public IResource CreateCopyDestinationBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None) + public GraphicsResource CreateCopyDestinationBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None) { if (sizeInBytes > _MAX_BYTES) { throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})"); } - var device = GraphicsPipeline.GetGraphicsDevice(); var heapProperties = new HeapProperties(HeapType.Default); var resourceDescription = ResourceDescription.Buffer(sizeInBytes, flags); ComPtr buffer = default; - device.NativeDevice.Ptr->CreateCommittedResource( + GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource( &heapProperties, HeapFlags.None, &resourceDescription, @@ -277,7 +274,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator buffer.GetVoidAddressOf() ); - var resource = new D3D12Resource(buffer.Move(), tempResource); + var resource = new GraphicsResource(buffer.Move(), tempResource); if (tempResource) { _temResources.Enqueue(new(resource)); diff --git a/Ghost.Graphics/D3D12/ResourceUploadBatch.cs b/Ghost.Graphics/D3D12/ResourceUploadBatch.cs new file mode 100644 index 0000000..fdd774b --- /dev/null +++ b/Ghost.Graphics/D3D12/ResourceUploadBatch.cs @@ -0,0 +1,339 @@ +using System.Runtime.CompilerServices; +using Win32; +using Win32.Graphics.Direct3D12; + +namespace Ghost.Graphics.D3D12; + +/// +/// Resource upload batch for efficiently uploading resources to GPU memory +/// +internal unsafe class ResourceUploadBatch : IDisposable +{ + private struct TrackedResource + { + public GraphicsResource resource; + public ResourceStates state; + + public TrackedResource(GraphicsResource resource, ResourceStates state) + { + this.resource = resource; + this.state = state; + } + } + + private ComPtr _commandAllocator; + private ComPtr _commandList; + private ComPtr _fence; + + private readonly GraphicsDevice _device = GraphicsPipeline.GraphicsDevice; + private readonly AutoResetEvent _fenceEvent = new(false); + + private ulong _fenceValue; + private bool _isRecording; + private bool _disposed; + + /// + /// Gets whether the batch is currently recording commands + /// + public bool IsRecording => _isRecording; + + /// + /// Creates a new ResourceUploadBatch + /// + internal ResourceUploadBatch() + { + Initialize(); + } + + ~ResourceUploadBatch() + { + Dispose(); + } + + private void Initialize() + { + ThrowIfFailed(_device.NativeDevice.Ptr->CreateCommandAllocator( + CommandListType.Direct, + __uuidof(), + _commandAllocator.GetVoidAddressOf())); + + ThrowIfFailed(_device.NativeDevice.Ptr->CreateCommandList1( + 0, + CommandListType.Direct, + CommandListFlags.None, + __uuidof(), + _commandList.GetVoidAddressOf())); + + ThrowIfFailed(_device.NativeDevice.Ptr->CreateFence(0, FenceFlags.None, __uuidof(), _fence.GetVoidAddressOf())); + } + + /// + /// Begins recording upload commands + /// + public void Begin() + { + if (_isRecording) + { + throw new InvalidOperationException("Upload batch is already recording"); + } + + ThrowIfFailed(_commandAllocator.Get()->Reset()); + ThrowIfFailed(_commandList.Get()->Reset(_commandAllocator.Get(), null)); + + _isRecording = true; + } + + /// + /// Uploads buffer data to a resource + /// + /// Type of data to upload + /// Destination resource + /// Source data + public void Upload(GraphicsResource resource, ReadOnlySpan data) + where T : unmanaged + { + if (!_isRecording) + { + throw new InvalidOperationException("Upload batch is not recording"); + } + + var sizeInBytes = (uint)(data.Length * sizeof(T)); + + // Create upload buffer + var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(sizeInBytes, true); + + // Copy data to upload buffer + fixed (T* dataPtr = data) + { + uploadBuffer.SetData(dataPtr, (uint)data.Length); + } + + // Copy from upload buffer to destination + _commandList.Get()->CopyBufferRegion( + resource.NativeResource.Ptr, + 0, + uploadBuffer.NativeResource.Ptr, + 0, + sizeInBytes); + } + + /// + /// Uploads subresource data to a texture + /// + /// Destination texture resource + /// First subresource index + /// Subresource data array + /// Number of subresources + public void Upload(GraphicsResource resource, uint firstSubresource, SubresourceData* subresources, uint numSubresources) + { + if (!_isRecording) + { + throw new InvalidOperationException("Upload batch is not recording"); + } + + var resourceDesc = resource.NativeResource.Ptr->GetDesc(); + + var requiredSize = GetRequiredIntermediateSize(resource, firstSubresource, numSubresources); + + var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer((uint)requiredSize, true); + + UpdateSubresources( + resource.NativeResource.Ptr, + uploadBuffer.NativeResource.Ptr, + 0, + firstSubresource, + numSubresources, + subresources); + } + + /// + /// Adds a resource transition barrier + /// + /// Resource to transition + /// State before transition + /// State after transition + public void Transition(GraphicsResource resource, ResourceStates stateBefore, ResourceStates stateAfter) + { + if (!_isRecording) + { + throw new InvalidOperationException("Upload batch is not recording"); + } + + // Apply the transition immediately + var barrier = new ResourceBarrier + { + Type = ResourceBarrierType.Transition, + Flags = ResourceBarrierFlags.None, + Anonymous = new ResourceBarrier._Anonymous_e__Union + { + Transition = new ResourceTransitionBarrier + { + pResource = resource.NativeResource.Ptr, + Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + StateBefore = stateBefore, + StateAfter = stateAfter + } + } + }; + + _commandList.Get()->ResourceBarrier(1, &barrier); + } + + /// + /// Generates mipmaps for a texture (if supported) + /// + /// Texture resource + public void GenerateMips(GraphicsResource resource) + { + if (!_isRecording) + { + throw new InvalidOperationException("Upload batch is not recording"); + } + + // This would require compute shader implementation for mipmap generation + // For now, this is a placeholder - DirectXTK12 uses a compute shader approach + throw new NotImplementedException("Mipmap generation not yet implemented"); + } + + /// + /// Ends recording and submits the batch for execution + /// + /// Future that completes when upload is finished + public ulong End() + { + if (!_isRecording) + { + throw new InvalidOperationException("Upload batch is not recording"); + } + + ThrowIfFailed(_commandList.Get()->Close()); + _device.CommandQueue.Ptr->ExecuteCommandLists(1, (ID3D12CommandList**)_commandList.GetAddressOf()); + ThrowIfFailed(_device.CommandQueue.Ptr->Signal(_fence.Get(), ++_fenceValue)); + + _isRecording = false; + + return _fenceValue; + } + + /// + /// Waits for the upload batch to complete + /// + /// Fence value to wait for + public void WaitForCompletion(ulong fenceValue) + { + if (_fence.Get()->GetCompletedValue() < fenceValue) + { + var handle = new Handle((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle()); + ThrowIfFailed(_fence.Get()->SetEventOnCompletion(fenceValue, handle)); + _fenceEvent.WaitOne(); + } + } + + private ulong GetRequiredIntermediateSize(GraphicsResource destinationResource, uint firstSubresource, uint numSubresources) + { + var resourceDesc = destinationResource.NativeResource.Ptr->GetDesc(); + + ulong requiredSize = 0; + var numRows = stackalloc uint[(int)numSubresources]; + var rowSizeInBytes = stackalloc ulong[(int)numSubresources]; + + _device.NativeDevice.Ptr->GetCopyableFootprints( + &resourceDesc, + firstSubresource, + numSubresources, + 0, + null, + numRows, + rowSizeInBytes, + &requiredSize); + + return requiredSize; + } + + private void UpdateSubresources(ID3D12Resource* destinationResource, ID3D12Resource* intermediate, ulong intermediateOffset, uint firstSubresource, uint numSubresources, SubresourceData* pSubresourceData) + { + var destDesc = destinationResource->GetDesc(); + + var layouts = stackalloc PlacedSubresourceFootprint[(int)numSubresources]; + var numRows = stackalloc uint[(int)numSubresources]; + var rowSizeInBytes = stackalloc ulong[(int)numSubresources]; + ulong requiredSize = 0; + + _device.NativeDevice.Ptr->GetCopyableFootprints( + &destDesc, + firstSubresource, + numSubresources, + intermediateOffset, + layouts, + numRows, + rowSizeInBytes, + &requiredSize); + + void* pMappedData; + ThrowIfFailed(intermediate->Map(0, null, &pMappedData)); + for (uint i = 0; i < numSubresources; i++) + { + var srcData = pSubresourceData[i]; + var destLayout = layouts[i]; + var pDestSlice = (byte*)pMappedData + destLayout.Offset; + var pSrcSlice = (byte*)srcData.pData; + + for (uint y = 0; y < numRows[i]; y++) + { + var pDestRow = pDestSlice + (y * destLayout.Footprint.RowPitch); + var pSrcRow = pSrcSlice + (y * srcData.RowPitch); + + Unsafe.CopyBlockUnaligned(pDestRow, pSrcRow, (uint)rowSizeInBytes[i]); + } + } + + intermediate->Unmap(0, null); + + if (destDesc.Dimension == ResourceDimension.Buffer) + { + _commandList.Get()->CopyBufferRegion(destinationResource, 0, intermediate, intermediateOffset, requiredSize); + } + else + { + for (uint i = 0; i < numSubresources; i++) + { + var destLocation = new TextureCopyLocation(destinationResource, firstSubresource + i); + var srcLocation = new TextureCopyLocation(intermediate, in layouts[i]); + var box = new Box + { + left = 0, + top = 0, + front = 0, + right = (uint)destDesc.Width, + bottom = destDesc.Height, + back = destDesc.DepthOrArraySize + }; + + _commandList.Get()->CopyTextureRegion(&destLocation, 0, 0, 0, &srcLocation, &box); + } + } + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + if (_isRecording) + { + _commandList.Get()->Close(); + _isRecording = false; + } + + _fence.Dispose(); + _commandList.Dispose(); + _commandAllocator.Dispose(); + + _fenceEvent.Dispose(); + + _disposed = true; + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12DescriptorAllocator.cs b/Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs similarity index 97% rename from Ghost.Graphics/D3D12/Utilities/D3D12DescriptorAllocator.cs rename to Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs index 2cb4f80..88adf3a 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12DescriptorAllocator.cs +++ b/Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs @@ -7,7 +7,7 @@ using DescriptorIndex = System.UInt32; namespace Ghost.Graphics.D3D12.Utilities; -internal unsafe struct D3D12DescriptorAllocator : IDisposable +internal unsafe struct DescriptorHeapAllocator : IDisposable { private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u; @@ -50,7 +50,7 @@ internal unsafe struct D3D12DescriptorAllocator : IDisposable public readonly ConstPtr Heap => new(_heap.Get()); public readonly ConstPtr ShaderVisibleHeap => new(_shaderVisibleHeap.Get()); - public D3D12DescriptorAllocator(ConstPtr device, DescriptorHeapType type, uint numDescriptors) + public DescriptorHeapAllocator(ConstPtr device, DescriptorHeapType type, uint numDescriptors) { _device = device; HeapType = type; diff --git a/Ghost.Graphics/Data/GraphicsAPI.cs b/Ghost.Graphics/Data/GraphicsAPI.cs deleted file mode 100644 index d75072f..0000000 --- a/Ghost.Graphics/Data/GraphicsAPI.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Ghost.Graphics.Data; - -public enum GraphicsAPI -{ - None, - D3D12 -} \ No newline at end of file diff --git a/Ghost.Graphics/Data/Material.cs b/Ghost.Graphics/Data/Material.cs index 883f9f1..528e703 100644 --- a/Ghost.Graphics/Data/Material.cs +++ b/Ghost.Graphics/Data/Material.cs @@ -1,13 +1,15 @@ -using Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12; using Ghost.Graphics.Shading; using System.Numerics; using System.Runtime.CompilerServices; namespace Ghost.Graphics.Data; -public class Material : IDisposable +public unsafe class Material : IDisposable { - private readonly Dictionary _cbufferCaches; + private readonly CBufferCache[] _cbufferCaches; + private readonly Texture2D?[] _textures; + private readonly Dictionary _textureNameToSlotMap; private bool _disposed; @@ -21,10 +23,37 @@ public class Material : IDisposable { Shader = shader; - _cbufferCaches = new(); - foreach (var cbufferInfo in shader.ConstantBuffers.Values) + if (shader.ConstantBuffers.Count > 0) { - _cbufferCaches.Add(cbufferInfo.Name, new CBufferCache(cbufferInfo.Size)); + var maxSlot = shader.ConstantBuffers.Max(cb => cb.RegisterSlot); + _cbufferCaches = new CBufferCache[maxSlot + 1]; + + foreach (var cbufferInfo in shader.ConstantBuffers) + { + _cbufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(cbufferInfo.Size); + } + } + else + { + _cbufferCaches = Array.Empty(); + } + + // Initialize texture storage + if (shader.Textures.Count > 0) + { + var maxTextureSlot = shader.Textures.Max(t => t.RegisterSlot); + _textures = new Texture2D?[maxTextureSlot + 1]; + _textureNameToSlotMap = new Dictionary(); + + foreach (var textureInfo in shader.Textures) + { + _textureNameToSlotMap.Add(textureInfo.Name, (int)textureInfo.RegisterSlot); + } + } + else + { + _textures = Array.Empty(); + _textureNameToSlotMap = new Dictionary(); } } @@ -33,52 +62,191 @@ public class Material : IDisposable Dispose(); } + /// + /// Sets a float property in the material's constant buffer. + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetFloat(int propertyId, in float value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a float property in the material's constant buffer. + /// + /// The name of the property to set. + /// The value to set for the property. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetFloat(string propertyName, in float value) { - WriteToCache(propertyName, in value); + SetFloat(Shader.GetPropertyId(propertyName), in value); } + /// + /// Sets a Vector property in the material's constant buffer. + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetVector(int propertyId, in Vector4 value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a Vector property in the material's constant buffer. + /// + /// The name of the property to set. + /// The value to set for the property. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetVector(string propertyName, in Vector4 value) { - WriteToCache(propertyName, in value); + SetVector(Shader.GetPropertyId(propertyName), in value); } + /// + /// Sets a Matrix property in the material's constant buffer. + /// + /// The ID of the property to set. + /// The value to set for the property. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetMatrix(int propertyId, in Matrix4x4 value) + { + WriteToCache(propertyId, in value); + } + + /// + /// Sets a Matrix property in the material's constant buffer. + /// + /// The name of the property to set. + /// The value to set for the property. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetMatrix(string propertyName, in Matrix4x4 value) { - WriteToCache(propertyName, in value); + SetMatrix(Shader.GetPropertyId(propertyName), in value); } - private unsafe void WriteToCache(string propertyName, in T value) where T : unmanaged + /// + /// Sets a texture property in the material. + /// + /// The ID of the texture to set. + /// The texture to set. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetTexture(int textureId, Texture2D? texture) { - if (!Shader.Properties.TryGetValue(propertyName, out var propInfo)) + if (textureId == -1) { - throw new ArgumentException($"Property '{propertyName}' does not exist in the shader.", nameof(propertyName)); + throw new ArgumentException("Texture ID is invalid."); } + if (textureId >= _textures.Length) + { + throw new ArgumentException($"Texture ID {textureId} is out of range."); + } + + _textures[textureId] = texture; + } + + /// + /// Sets a texture property in the material. + /// + /// The name of the texture to set. + /// The texture to set. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetTexture(string textureName, Texture2D? texture) + { + if (!_textureNameToSlotMap.TryGetValue(textureName, out var slot)) + { + throw new ArgumentException($"Texture '{textureName}' not found in shader."); + } + + _textures[slot] = texture; + } + + /// + /// Gets a texture property from the material. + /// + /// The ID of the texture to get. + /// The texture, or null if not set. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Texture2D? GetTexture(int textureId) + { + if (textureId == -1 || textureId >= _textures.Length) + { + return null; + } + + return _textures[textureId]; + } + + /// + /// Gets a texture property from the material. + /// + /// The name of the texture to get. + /// The texture, or null if not set. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Texture2D? GetTexture(string textureName) + { + if (!_textureNameToSlotMap.TryGetValue(textureName, out var slot)) + { + return null; + } + + return _textures[slot]; + } + + private unsafe void WriteToCache(int propertyId, in T value) where T : unmanaged + { + if (propertyId == -1) + { + throw new ArgumentException("Property ID is invalid."); + } + + var propInfo = Shader.Properties[propertyId]; if (propInfo.Size != sizeof(T)) { - throw new ArgumentException($"Property '{propertyName}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes.", nameof(propertyName)); + throw new ArgumentException($"Property '{propInfo.Name}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes."); } - var cache = _cbufferCaches[propInfo.CBufferName]; + var cache = _cbufferCaches[propInfo.CBufferIndex]; Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value); } - public void UploadAndBind(ICommandBuffer cmb) + /// + /// Uploads the material data to the GPU. + /// + public void UploadMaterialData() { - foreach (var cache in _cbufferCaches.Values) + foreach (var cache in _cbufferCaches) { cache.UploadToGpu(); } + } - foreach (var (name, cache) in _cbufferCaches) + internal void Bind(CommandList cmd) + { + // Bind constant buffers + foreach (var cbufferInfo in Shader.ConstantBuffers) { - var cbufferInfo = Shader.ConstantBuffers[name]; - cmb.SetGraphicsRootConstantBufferView(cbufferInfo.RegisterSlot, cache.GpuResource.GPUAddress); + var cache = _cbufferCaches[cbufferInfo.RegisterSlot]; + cmd.SetGraphicsRootConstantBufferView(cbufferInfo.RegisterSlot, cache.GpuResource.GPUAddress); + } + + // Bind textures using descriptor table + if (Shader.Textures.Count > 0) + { + // Get the first texture info to determine the root parameter index + var textureInfo = Shader.Textures[0]; + var texture = _textures[0]; // Get the first texture + + if (texture != null) + { + // Set descriptor table for SRVs + cmd.NativeCommandList.Ptr->SetGraphicsRootDescriptorTable(textureInfo.RootParameterIndex, texture.SRVDescriptor.GpuHandle); + } } } @@ -89,13 +257,11 @@ public class Material : IDisposable return; } - foreach (var cache in _cbufferCaches.Values) + foreach (var cache in _cbufferCaches) { cache.Dispose(); } - _cbufferCaches.Clear(); - GC.SuppressFinalize(this); _disposed = true; diff --git a/Ghost.Graphics/Data/Mesh.cs b/Ghost.Graphics/Data/Mesh.cs index c08f4d1..30cd4a0 100644 --- a/Ghost.Graphics/Data/Mesh.cs +++ b/Ghost.Graphics/Data/Mesh.cs @@ -1,5 +1,5 @@ using Ghost.Core; -using Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Helpers; using System.Numerics; @@ -16,8 +16,8 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde private Bounds _boundingBox; - private IResource? _vertexBuffer; - private IResource? _indexBuffer; + private GraphicsResource? _vertexBuffer; + private GraphicsResource? _indexBuffer; private VertexBufferView _vertexBufferView; private IndexBufferView _indexBufferView; @@ -204,8 +204,7 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde /// /// Uploads the mesh data to GPU resources. /// - /// The command buffer to record the upload commands. - public unsafe void UploadMeshData(ICommandBuffer cmb) + public unsafe void UploadMeshData() { if (VertexCount == 0 || IndexCount == 0) { @@ -220,17 +219,13 @@ public unsafe sealed class Mesh(int initialVertexCapacity = 256, int initialInde _vertexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(vertexBufferSize); _indexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(indexBufferSize); - var vertexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(vertexBufferSize, false); - var indexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(indexBufferSize, false); - - vertexUploadBuffer.SetData(_vertices.AsSpan()); - indexUploadBuffer.SetData(_indices.AsSpan()); - - cmb.CopyResource(_vertexBuffer, 0, vertexUploadBuffer, 0, vertexBufferSize); - cmb.CopyResource(_indexBuffer, 0, indexUploadBuffer, 0, indexBufferSize); - - cmb.BarrierTransition(_vertexBuffer, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer); - cmb.BarrierTransition(_indexBuffer, ResourceStates.CopyDest, ResourceStates.IndexBuffer); + using var uploadBatch = new ResourceUploadBatch(); + uploadBatch.Begin(); + uploadBatch.Upload(_vertexBuffer, _vertices.AsSpan()); + uploadBatch.Upload(_indexBuffer, _indices.AsSpan()); + uploadBatch.Transition(_vertexBuffer, ResourceStates.CopyDest, ResourceStates.VertexAndConstantBuffer); + uploadBatch.Transition(_indexBuffer, ResourceStates.CopyDest, ResourceStates.IndexBuffer); + uploadBatch.WaitForCompletion(uploadBatch.End()); _vertexBufferView = new VertexBufferView { diff --git a/Ghost.Graphics/Data/Texture2D.cs b/Ghost.Graphics/Data/Texture2D.cs new file mode 100644 index 0000000..c77766b --- /dev/null +++ b/Ghost.Graphics/Data/Texture2D.cs @@ -0,0 +1,357 @@ +using Ghost.Graphics.D3D12; +using Misaki.HighPerformance.LowLevel.Collections; +using Misaki.HighPerformance.LowLevel.Helpers; +using System.Runtime.InteropServices; +using Win32; +using Win32.Graphics.Direct3D12; +using Win32.Graphics.Dxgi.Common; + +namespace Ghost.Graphics.Data; + +public unsafe class Texture2D : IDisposable +{ + private UnsafeArray _cpuData; + + private readonly GraphicsResource _resource; + private readonly ShaderResourceDescriptor _srvDescriptor; + + private bool _disposed; + + public uint Width + { + get; + } + + public uint Height + { + get; + } + + public Format Format + { + get; + } + + public uint BytesPerPixel + { + get; + } + + public uint Pitch + { + get; + } + + public uint DataSize + { + get; + } + + internal ShaderResourceDescriptor SRVDescriptor => _srvDescriptor; + + public GraphicsResource? Resource => _resource; + public ReadOnlySpan CpuData => _cpuData.AsSpan(); + + public Texture2D(uint width, uint height, Span data, Format format) + { + Width = width; + Height = height; + Format = format; + BytesPerPixel = GetBytesPerPixel(format); + Pitch = width * BytesPerPixel; + DataSize = Pitch * height; + + // Initialize CPU-side data + _cpuData = new((int)DataSize, Allocator.Persistent); + + if (!data.IsEmpty) + { + if (data.Length != DataSize) + { + throw new ArgumentException($"Data size mismatch. Expected {DataSize} bytes, got {data.Length} bytes."); + } + + data.CopyTo(_cpuData.AsSpan()); + } + + _resource = CreateGpuResource(); + _srvDescriptor = CreateShaderResourceView(); + } + private static uint GetBytesPerPixel(Format format) + { + return format switch + { + Format.R8G8B8A8Unorm => 4, + Format.R8G8B8A8UnormSrgb => 4, + Format.B8G8R8A8Unorm => 4, + Format.B8G8R8A8UnormSrgb => 4, + Format.R8G8B8A8Uint => 4, + Format.R8G8B8A8Sint => 4, + Format.R8G8B8A8Snorm => 4, + Format.R8G8Unorm => 2, + Format.R8G8Uint => 2, + Format.R8G8Sint => 2, + Format.R8G8Snorm => 2, + Format.R8Unorm => 1, + Format.R8Uint => 1, + Format.R8Sint => 1, + Format.R8Snorm => 1, + Format.A8Unorm => 1, + Format.R16G16B16A16Float => 8, + Format.R16G16B16A16Unorm => 8, + Format.R16G16B16A16Uint => 8, + Format.R16G16B16A16Sint => 8, + Format.R16G16B16A16Snorm => 8, + Format.R32G32B32A32Float => 16, + Format.R32G32B32A32Uint => 16, + Format.R32G32B32A32Sint => 16, + Format.R32G32B32Float => 12, + Format.R32G32B32Uint => 12, + Format.R32G32B32Sint => 12, + Format.R32G32Float => 8, + Format.R32G32Uint => 8, + Format.R32G32Sint => 8, + Format.R32Float => 4, + Format.R32Uint => 4, + Format.R32Sint => 4, + Format.R16G16Float => 4, + Format.R16G16Unorm => 4, + Format.R16G16Uint => 4, + Format.R16G16Sint => 4, + Format.R16G16Snorm => 4, + Format.R16Float => 2, + Format.R16Unorm => 2, + Format.R16Uint => 2, + Format.R16Sint => 2, + Format.R16Snorm => 2, + _ => throw new NotSupportedException($"Format {format} is not supported.") + }; + } + + private GraphicsResource CreateGpuResource() + { + var heapProperties = new HeapProperties(HeapType.Default); + var resourceDesc = ResourceDescription.Tex2D(Format, Width, Height); + + ComPtr textureResource = default; + GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource( + &heapProperties, + HeapFlags.None, + &resourceDesc, + ResourceStates.Common, + null, + __uuidof(), + textureResource.GetVoidAddressOf() + ); + + return new(textureResource.Move()); + } + + private ShaderResourceDescriptor CreateShaderResourceView() + { + var srvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateSRV(); + + // Create the actual SRV + var srvDesc = new ShaderResourceViewDescription + { + Format = Format, + ViewDimension = SrvDimension.Texture2D, + Texture2D = new Texture2DSrv { MipLevels = 1 }, + Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING + }; + + GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateShaderResourceView(_resource.NativeResource.Ptr, &srvDesc, srvDescriptor.CpuHandle); + + return srvDescriptor; + } + + private void CopyBufferToTexture(CommandList cmd, GraphicsResource srcBuffer, GraphicsResource dstTexture) + { + // Calculate the proper footprint for the placed subresource + var resourceDesc = dstTexture.NativeResource.Ptr->GetDesc(); + + PlacedSubresourceFootprint footprint = new() + { + Footprint = new SubresourceFootprint + { + Format = Format, + Width = Width, + Height = Height, + Depth = 1, + RowPitch = (uint)((Pitch + 255) & ~255) // Align to 256 bytes + } + }; + + var srcLocation = new TextureCopyLocation(srcBuffer.NativeResource.Ptr, footprint); + var dstLocation = new TextureCopyLocation(dstTexture.NativeResource.Ptr, 0); + + cmd.NativeCommandList.Ptr->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, null); + } + + /// + /// Sets the entire texture data on the CPU side. + /// + /// The texture data to set + public void SetData(ReadOnlySpan data) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (data.Length != DataSize) + { + throw new ArgumentException($"Data size mismatch. Expected {DataSize} bytes, got {data.Length} bytes."); + } + + data.CopyTo(_cpuData.AsSpan()); + } + + /// + /// Sets the texture data for a specific region on the CPU side. + /// + /// The texture data to set + /// Starting X coordinate + /// Starting Y coordinate + /// Width of the region + /// Height of the region + public void SetData(ReadOnlySpan data, uint x, uint y, uint width, uint height) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (x + width > Width || y + height > Height) + { + throw new ArgumentException("Region extends beyond texture bounds."); + } + + var expectedSize = width * height * BytesPerPixel; + if (data.Length != expectedSize) + { + throw new ArgumentException($"Data size mismatch. Expected {expectedSize} bytes, got {data.Length} bytes."); + } + + for (uint row = 0; row < height; row++) + { + var srcOffset = (int)(row * width * BytesPerPixel); + var dstOffset = (int)((y + row) * Pitch + x * BytesPerPixel); + var rowSize = (int)(width * BytesPerPixel); + + data.Slice(srcOffset, rowSize).CopyTo(_cpuData.AsSpan().Slice(dstOffset, rowSize)); + } + } + + /// + /// Sets a single pixel value on the CPU side. + /// + /// X coordinate of the pixel + /// Y coordinate of the pixel + /// The color data for the pixel + public void SetPixel(uint x, uint y, ReadOnlySpan color) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (x >= Width || y >= Height) + { + throw new ArgumentException("Pixel coordinates are outside texture bounds."); + } + + if (color.Length != BytesPerPixel) + { + throw new ArgumentException($"Color data size mismatch. Expected {BytesPerPixel} bytes, got {color.Length} bytes."); + } + + var offset = (int)(y * Pitch + x * BytesPerPixel); + color.CopyTo(_cpuData.AsSpan().Slice(offset, (int)BytesPerPixel)); + } + + /// + /// Sets a single pixel value using generic color types. + /// + /// The color type (e.g., uint for RGBA32) + /// X coordinate of the pixel + /// Y coordinate of the pixel + /// The color value + public void SetPixel(uint x, uint y, T color) where T : unmanaged + { + if (sizeof(T) != BytesPerPixel) + { + throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes."); + } + + var colorSpan = new ReadOnlySpan(&color, sizeof(T)); + SetPixel(x, y, colorSpan); + } + + /// + /// Gets a single pixel value from the CPU side data. + /// + /// X coordinate of the pixel + /// Y coordinate of the pixel + /// The pixel color data + public ReadOnlySpan GetPixel(uint x, uint y) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (x >= Width || y >= Height) + { + throw new ArgumentException("Pixel coordinates are outside texture bounds."); + } + + var offset = (int)(y * Pitch + x * BytesPerPixel); + return _cpuData.AsSpan().Slice(offset, (int)BytesPerPixel); + } + + /// + /// Gets a single pixel value as a generic color type. + /// + /// The color type (e.g., uint for RGBA32) + /// X coordinate of the pixel + /// Y coordinate of the pixel + /// The pixel color value + public T GetPixel(uint x, uint y) where T : unmanaged + { + if (sizeof(T) != BytesPerPixel) + { + throw new ArgumentException($"Color type size mismatch. Expected {BytesPerPixel} bytes, got {sizeof(T)} bytes."); + } + + var pixelData = GetPixel(x, y); + return MemoryMarshal.Read(pixelData); + } + + /// + /// Uploads the CPU-side texture data to the GPU resource. + /// + public void UploadTextureData() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + Format.GetSurfaceInfo((int)Width, (int)Height, out var rowPitch, out var slicePitch); + var initData = new SubresourceData() + { + pData = _cpuData.GetUnsafePtr(), + RowPitch = rowPitch, + SlicePitch = slicePitch + }; + + using var uploadBatch = new ResourceUploadBatch(); + uploadBatch.Begin(); + uploadBatch.Transition(_resource, ResourceStates.Common, ResourceStates.CopyDest); + uploadBatch.Upload(_resource, 0, &initData, 1); + uploadBatch.Transition(_resource, ResourceStates.CopyDest, ResourceStates.PixelShaderResource); + uploadBatch.WaitForCompletion(uploadBatch.End()); + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _cpuData.Dispose(); + _resource.Dispose(); + + GraphicsPipeline.DescriptorAllocator.ReleaseSRV(_srvDescriptor); + + _disposed = true; + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/Data/Vertex.cs b/Ghost.Graphics/Data/Vertex.cs index 77e31c9..66d6935 100644 --- a/Ghost.Graphics/Data/Vertex.cs +++ b/Ghost.Graphics/Data/Vertex.cs @@ -17,7 +17,7 @@ public struct Vertex(Vector4 position, Vector4 normal, Vector4 tangent, Color128 private static readonly byte[] s_normalBytes = Encoding.UTF8.GetBytes("NORMAL"); private static readonly byte[] s_tangentBytes = Encoding.UTF8.GetBytes("TANGENT"); private static readonly byte[] s_colorBytes = Encoding.UTF8.GetBytes("COLOR"); - private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("UV"); + private static readonly byte[] s_uvBytes = Encoding.UTF8.GetBytes("TEXCOORD"); public static byte* PositionName => (byte*)Unsafe.AsPointer(ref s_positionBytes[0]); public static byte* NormalName => (byte*)Unsafe.AsPointer(ref s_normalBytes[0]); diff --git a/Ghost.Graphics/Examples/DescriptorAllocatorExample.cs b/Ghost.Graphics/Examples/DescriptorAllocatorExample.cs new file mode 100644 index 0000000..a6e115b --- /dev/null +++ b/Ghost.Graphics/Examples/DescriptorAllocatorExample.cs @@ -0,0 +1,178 @@ +using Ghost.Graphics.D3D12; +using Win32.Graphics.Dxgi.Common; + +namespace Ghost.Graphics.Examples; + +/// +/// Example demonstrating how to use the IDescriptorAllocator interface. +/// +public static class DescriptorAllocatorExample +{ + /// + /// Example showing basic descriptor allocation and release. + /// + public static void BasicDescriptorUsage() + { + // Note: In a real application, you would get the descriptor allocator from the graphics device + // var device = GraphicsPipeline.GetGraphicsDevice(); + // var descriptorAllocator = device.DescriptorAllocator; + + // For demonstration purposes, we'll show the interface usage + DescriptorAllocator descriptorAllocator = null!; // Would be obtained from graphics device + + // Allocate a single RTV descriptor + var rtvDescriptor = descriptorAllocator.AllocateRTV(); + + // Allocate multiple SRV descriptors + var srvDescriptors = descriptorAllocator.AllocateSRVs(4); + + // Allocate a DSV descriptor + var dsvDescriptor = descriptorAllocator.AllocateDSV(); + + // Allocate sampler descriptors + var samplerDescriptors = descriptorAllocator.AllocateSamplers(2); + + // Use the descriptors... + ProcessDescriptors(rtvDescriptor, srvDescriptors, dsvDescriptor, samplerDescriptors); + + // Release descriptors when done + descriptorAllocator.ReleaseRTV(rtvDescriptor); + descriptorAllocator.ReleaseSRVs(srvDescriptors); + descriptorAllocator.ReleaseDSV(dsvDescriptor); + descriptorAllocator.ReleaseSamplers(samplerDescriptors); + } + + /// + /// Example showing how to work with D3D12-specific descriptor handles. + /// + public static void D3D12SpecificUsage() + { + // In D3D12-specific code, you can cast to get the underlying handles + DescriptorAllocator descriptorAllocator = null!; // Would be obtained from graphics device + + var srvDescriptor = descriptorAllocator.AllocateSRV(); + + // For D3D12, you can cast to get the specific handles + if (srvDescriptor is ShaderResourceDescriptor d3d12Srv) + { + var cpuHandle = d3d12Srv.CpuHandle; + var gpuHandle = d3d12Srv.GpuHandle; + + // Use the handles for D3D12 API calls + // device.CreateShaderResourceView(resource, &srvDesc, cpuHandle); + // commandList.SetGraphicsRootDescriptorTable(0, gpuHandle); + } + + descriptorAllocator.ReleaseSRV(srvDescriptor); + } + + /// + /// Example showing descriptor management in a texture class. + /// + public static void TextureDescriptorUsage() + { + // This shows how you might integrate descriptor allocation in a texture class + DescriptorAllocator descriptorAllocator = null!; // Would be obtained from graphics device + + // Create a texture with SRV + var texture = new ExampleTexture2D(512, 512, Format.R8G8B8A8Unorm, descriptorAllocator); + + // The texture automatically manages its SRV descriptor + var srvDescriptor = texture.GetShaderResourceView(); + + // Use the texture... + + // Dispose will automatically release the descriptor + texture.Dispose(); + } + + private static void ProcessDescriptors( + RenderTargetDescriptor rtv, + ShaderResourceDescriptor[] srvs, + DepthStencilDescriptor dsv, + SamplerDescriptor[] samplers) + { + // Process the descriptors - check validity, shader visibility, etc. + if (rtv.IsValid) + { + // Use RTV descriptor + } + + foreach (var srv in srvs) + { + if (srv.IsValid && srv.IsShaderVisible) + { + // Use SRV descriptor in shaders + } + } + + if (dsv.IsValid) + { + // Use DSV descriptor + } + + foreach (var sampler in samplers) + { + if (sampler.IsValid && sampler.IsShaderVisible) + { + // Use sampler descriptor in shaders + } + } + } +} + +/// +/// Example texture class showing descriptor integration. +/// +internal class ExampleTexture2D : IDisposable +{ + private readonly DescriptorAllocator _descriptorAllocator; + private readonly ShaderResourceDescriptor _srvDescriptor; + private readonly uint _width; + private readonly uint _height; + private readonly Format _format; + private bool _disposed; + + public ExampleTexture2D(uint width, uint height, Format format, DescriptorAllocator descriptorAllocator) + { + _width = width; + _height = height; + _format = format; + _descriptorAllocator = descriptorAllocator; + + // Allocate SRV descriptor for this texture + _srvDescriptor = _descriptorAllocator.AllocateSRV(); + + // Create the actual texture resource and SRV... + CreateTextureResource(); + } + + public ShaderResourceDescriptor GetShaderResourceView() => _srvDescriptor; + + private void CreateTextureResource() + { + // Create D3D12 texture resource + // Create SRV using the allocated descriptor + + // For D3D12: + if (_srvDescriptor is ShaderResourceDescriptor d3d12Srv) + { + var cpuHandle = d3d12Srv.CpuHandle; + // device.CreateShaderResourceView(textureResource, &srvDesc, cpuHandle); + } + } + + public void Dispose() + { + if (_disposed) + return; + + // Release the descriptor + _descriptorAllocator.ReleaseSRV(_srvDescriptor); + + // Dispose texture resource... + + _disposed = true; + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/Ghost.Graphics.csproj b/Ghost.Graphics/Ghost.Graphics.csproj index 33f5a54..dbf9d82 100644 --- a/Ghost.Graphics/Ghost.Graphics.csproj +++ b/Ghost.Graphics/Ghost.Graphics.csproj @@ -17,6 +17,8 @@ + + @@ -27,14 +29,20 @@ - - ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Image\bin\Release\net9.0\Misaki.HighPerformance.Image.dll - ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net9.0\Misaki.HighPerformance.LowLevel.dll + + True diff --git a/Ghost.Graphics/GraphicsPipeline.cs b/Ghost.Graphics/GraphicsPipeline.cs index da3ed0e..70c1720 100644 --- a/Ghost.Graphics/GraphicsPipeline.cs +++ b/Ghost.Graphics/GraphicsPipeline.cs @@ -1,196 +1,154 @@ -using Ghost.Core; -using Ghost.Graphics.Contracts; -using Ghost.Graphics.D3D12; -using Ghost.Graphics.Data; -using System.Runtime.CompilerServices; +using Ghost.Graphics.D3D12; namespace Ghost.Graphics; public static class GraphicsPipeline { - internal const int _FRAME_COUNT = 2; + internal const uint _FRAME_COUNT = 2; - private static IGraphicsDevice? _graphicsDevice; - private static IResourceAllocator? _resourceAllocator; - - private static Thread? _renderThread; - private static AutoResetEvent[]? _cpuReadyEvent; - private static AutoResetEvent[]? _gpuReadyEvent; - - private static uint _cpuFenceValue; - private static uint _gpuFenceValue; - - private static bool _initialized; - private static bool _isRunning; - - internal static uint CPUFenceValue => _cpuFenceValue; - internal static uint GPUFenceValue => _gpuFenceValue; - internal static bool IsRunning => _isRunning; - - internal static IGraphicsDevice GraphicsDevice + private readonly struct FrameResource : IDisposable { - get - { - if (_graphicsDevice == null) - { - throw new InvalidOperationException("Graphics pipeline is not initialized."); - } + public readonly AutoResetEvent cpuReadyEvent; + public readonly AutoResetEvent gpuReadyEvent; - return _graphicsDevice; + public FrameResource() + { + cpuReadyEvent = new(false); + gpuReadyEvent = new(true); + } + + public void Dispose() + { + cpuReadyEvent?.Dispose(); + gpuReadyEvent?.Dispose(); } } - internal static IResourceAllocator ResourceAllocator + private static GraphicsDevice? s_graphicsDevice; + private static DescriptorAllocator? s_descriptorAllocator; + private static ResourceAllocator? s_resourceAllocator; + + private static Thread? s_renderThread; + private static FrameResource[]? s_frameResources; + + private static uint s_frameIndex; + private static uint s_cpuFenceValue; + private static uint s_gpuFenceValue; + + private static bool s_initialized; + private static bool s_isRunning; + + internal static uint CPUFenceValue => s_cpuFenceValue; + internal static uint GPUFenceValue => s_gpuFenceValue; + internal static bool IsRunning => s_isRunning; + + internal static GraphicsDevice GraphicsDevice => s_graphicsDevice ?? throw new InvalidOperationException("Graphics device is not initialized."); + internal static ResourceAllocator ResourceAllocator => s_resourceAllocator ?? throw new InvalidOperationException("Resource allocator is not initialized."); + internal static DescriptorAllocator DescriptorAllocator => s_descriptorAllocator ?? throw new InvalidOperationException("Descriptor allocator is not initialized."); + + internal static void Initialize() { - get - { - if (_resourceAllocator == null) - { - throw new InvalidOperationException("Resource allocator is not initialized."); - } + s_graphicsDevice = new GraphicsDevice(); + s_descriptorAllocator = new DescriptorAllocator(); + s_resourceAllocator = new ResourceAllocator(); - return _resourceAllocator; - } - } - - public static GraphicsAPI CurrentAPI - { - get; - private set; - } - - internal static void Initialize(GraphicsAPI api) - { - switch (api) - { - case GraphicsAPI.D3D12: - _graphicsDevice = new D3D12GraphicsDevice(); - _resourceAllocator = new D3D12ResourceAllocator(); - break; - default: - throw new NotSupportedException($"Graphics API {api} is not supported."); - } - - _renderThread = new Thread(RenderLoop) + s_renderThread = new Thread(RenderLoop) { IsBackground = true, Name = "Graphics Render Thread", Priority = ThreadPriority.Normal }; - _cpuReadyEvent = new AutoResetEvent[_FRAME_COUNT]; - _gpuReadyEvent = new AutoResetEvent[_FRAME_COUNT]; + s_frameResources = new FrameResource[_FRAME_COUNT]; for (var i = 0; i < _FRAME_COUNT; i++) { - _cpuReadyEvent[i] = new(false); - _gpuReadyEvent[i] = new(true); + s_frameResources[i] = new FrameResource(); } - CurrentAPI = api; - - _initialized = true; + s_initialized = true; } private static void RenderLoop() { - while (_isRunning) + while (s_isRunning) { - if (_graphicsDevice == null) - { - throw new ArgumentException("Renderer has been disposed or is not initialized."); - } + s_frameIndex = s_gpuFenceValue % _FRAME_COUNT; + var frameResource = s_frameResources![s_frameIndex]; - var eventIndex = (int)(_gpuFenceValue % _FRAME_COUNT); - _cpuReadyEvent![eventIndex].WaitOne(); + frameResource.cpuReadyEvent.WaitOne(); - _graphicsDevice.InitializePendingRenderers(); + s_graphicsDevice!.InitializePendingRenderers(); - foreach (var renderer in _graphicsDevice.Renderers) + foreach (var renderer in s_graphicsDevice.Renderers) { renderer.ExecutePendingResize(); renderer.Render(); } - _gpuFenceValue++; - _gpuReadyEvent![eventIndex].Set(); + s_gpuFenceValue++; + frameResource.gpuReadyEvent.Set(); - _resourceAllocator!.ReleaseTempResource(); + s_resourceAllocator!.ReleaseTempResource(); } } - internal static bool WaitForGPUReady(int timeOut = -1) { - if (_gpuReadyEvent == null) + if (s_frameResources == null) { throw new InvalidOperationException("Graphics pipeline is not initialized."); } - var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT); - return _gpuReadyEvent[eventIndex].WaitOne(timeOut); + var eventIndex = (int)(s_cpuFenceValue % _FRAME_COUNT); + return s_frameResources[eventIndex].gpuReadyEvent.WaitOne(timeOut); } internal static void SignalCPUReady() { - if (_cpuReadyEvent == null) + if (s_frameResources == null) { throw new InvalidOperationException("Graphics pipeline is not initialized."); } - var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT); - _cpuFenceValue++; - _cpuReadyEvent[eventIndex].Set(); + var eventIndex = (int)(s_cpuFenceValue % _FRAME_COUNT); + s_cpuFenceValue++; + s_frameResources[eventIndex].cpuReadyEvent.Set(); } internal static void Start() { - if (_isRunning || !_initialized) + if (s_isRunning || !s_initialized) { return; } - _isRunning = true; - _renderThread!.Start(); + s_isRunning = true; + s_renderThread!.Start(); } internal static void Stop() { - _isRunning = false; - _renderThread?.Join(); + s_isRunning = false; + s_renderThread?.Join(); } internal static void Shutdown() { Stop(); - _graphicsDevice?.Dispose(); - _resourceAllocator?.Dispose(); + s_resourceAllocator?.Dispose(); + s_descriptorAllocator?.Dispose(); + s_graphicsDevice?.Dispose(); - _graphicsDevice = null; - _renderThread = null; - _initialized = false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static T GetGraphicsDevice() - where T : class, IGraphicsDevice - { - if (T.TargetAPI != CurrentAPI) + if (s_frameResources != null) { - throw new InvalidOperationException($"No graphics device of type {typeof(T)} available for the current API."); + foreach (var frameResource in s_frameResources) + { + frameResource.Dispose(); + } + s_frameResources = null; } - return Unsafe.As(GraphicsDevice); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Result CheckAPI(GraphicsAPI expectedAPI) - { - if (CurrentAPI != expectedAPI) - { - return Result.Failure($"Expected API {expectedAPI}, but got {CurrentAPI}."); - } - - return Result.Success(); + s_initialized = false; } } \ No newline at end of file diff --git a/Ghost.Graphics/RenderPasses/BindlessMeshRenderPass.cs b/Ghost.Graphics/RenderPasses/BindlessMeshRenderPass.cs new file mode 100644 index 0000000..6eb35fb --- /dev/null +++ b/Ghost.Graphics/RenderPasses/BindlessMeshRenderPass.cs @@ -0,0 +1,677 @@ +using Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12; +using Ghost.Graphics.D3D12.Utilities; +using Ghost.Graphics.Data; +using Ghost.Graphics.Utilities; +using StbImageSharp; +using System.Runtime.InteropServices; +using Win32; +using Win32.Graphics.Direct3D; +using Win32.Graphics.Direct3D.Dxc; +using Win32.Graphics.Direct3D12; +using Win32.Graphics.Dxgi.Common; + +namespace Ghost.Graphics.RenderPasses; + +/// +/// Modern bindless texture implementation using SM 6.6 with ResourceDescriptorHeap +/// and D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED +/// +internal unsafe class BindlessMeshRenderPass : IRenderPass +{ + private const string _HLSL_SOURCE = @" +cbuffer ConstantBuffer : register(b0) +{ + float4 _Color; + uint _TextureIndex1; + uint _TextureIndex2; + uint _TextureIndex3; + uint _TextureIndex4; +}; + +// SM 6.6 approach - no need for explicit texture arrays +// ResourceDescriptorHeap is a global descriptor heap access mechanism +// This allows direct indexing into any texture in the bound descriptor heap +SamplerState _MainSampler : register(s0); + +struct VertexInput +{ + float4 position : POSITION; + float4 color : COLOR; + float4 uv : TEXCOORD0; +}; + +struct PixelInput +{ + float4 position : SV_POSITION; + float4 color : COLOR; + float4 uv : TEXCOORD0; +}; + +PixelInput VSMain(VertexInput input) +{ + PixelInput output; + output.position = input.position; + output.color = input.color; + output.uv = input.uv; + return output; +} + +float4 PSMain(PixelInput input) : SV_TARGET +{ + // SM 6.6 Modern Bindless Approach: + // ResourceDescriptorHeap[index] directly accesses any texture in the heap + // No need for explicit texture arrays or descriptor table binding + // This is enabled by D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED + Texture2D tex1 = ResourceDescriptorHeap[_TextureIndex1]; + Texture2D tex2 = ResourceDescriptorHeap[_TextureIndex2]; + Texture2D tex3 = ResourceDescriptorHeap[_TextureIndex3]; + Texture2D tex4 = ResourceDescriptorHeap[_TextureIndex4]; + + // Sample the textures normally + float4 color1 = tex1.Sample(_MainSampler, input.uv.xy); + float4 color2 = tex2.Sample(_MainSampler, input.uv.xy); + float4 color3 = tex3.Sample(_MainSampler, input.uv.xy); + float4 color4 = tex4.Sample(_MainSampler, input.uv.xy); + + // Blend all textures together (simple average) + float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f; + + return blendedColor * _Color; +} +"; + + private Mesh? _mesh; + + // Direct D3D12 resources for modern bindless implementation + private ComPtr _pipelineState; + private ComPtr _rootSignature; + private ComPtr _constantBuffer; + private void* _constantBufferMappedData; + + // Bindless texture resources + private ComPtr[] _textureResources; + private ComPtr _bindlessHeap; + private ComPtr _samplerHeap; + private CpuDescriptorHandle _bindlessHeapStart; + private GpuDescriptorHandle _bindlessHeapGpuStart; + private uint _descriptorSize; + + // Texture upload resources + private ComPtr[] _uploadBuffers; + private uint[] _textureWidths; + private uint[] _textureHeights; + private uint[] _texturePitches; + private bool _texturesUploaded = false; + + // Texture file paths for this demo + private readonly string[] _textureFiles = [ + "C:/Users/Misaki/Downloads/Im/Icon.png", + "C:/Users/Misaki/Downloads/Im/Backdrop.jpg", + "C:/Users/Misaki/Downloads/Im/101167591_p0.png", + "C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg" + ]; + + // Constant buffer data structure + private struct ConstantBufferData + { + public System.Numerics.Vector4 Color; + public uint TextureIndex1; + public uint TextureIndex2; + public uint TextureIndex3; + public uint TextureIndex4; + } + + public void Initialize(CommandList cmd) + { + _mesh = MeshBuilder.CreateCube(0.75f); + _mesh.UploadMeshData(); + + CreateModernBindlessShader(); + CreateConstantBuffer(); + CreateBindlessTextures(); + UploadTextureData(cmd); + UpdateConstantBuffer(); + } + + private void CreateModernBindlessShader() + { + var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; + + var vsCode = CompileShaderDXC(_HLSL_SOURCE, "VSMain", "vs_6_6"); + var psCode = CompileShaderDXC(_HLSL_SOURCE, "PSMain", "ps_6_6"); + + CreateModernBindlessRootSignature(); + CreatePipelineState(vsCode, psCode); + } + + private byte[] CompileShaderDXC(string source, string entryPoint, string profile) + { + using ComPtr compiler = default; + using ComPtr utils = default; + + // Create DXC compiler and utils + DxcCreateInstance(CLSID_DxcCompiler, __uuidof(), compiler.GetVoidAddressOf()); + DxcCreateInstance(CLSID_DxcUtils, __uuidof(), utils.GetVoidAddressOf()); + + // Create source blob + using ComPtr sourceBlob = default; + var sourceBytes = System.Text.Encoding.UTF8.GetBytes(source); + fixed (byte* sourceBytesPtr = sourceBytes) + { + utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf()); + } + + // Prepare compilation arguments + var argsArray = new string[] + { + "-T", profile, // Target profile (vs_6_6, ps_6_6) + "-E", entryPoint, // Entry point + "-HV", "2021", // HLSL version 2021 (required for SM 6.6) + "-enable-16bit-types", // Enable 16-bit types + "-O3", // Optimization level + "-Qstrip_debug", // Strip debug info for release + "-Qstrip_reflect" // Strip reflection info for release + }; + + // Convert to wide strings (DXC expects LPCWSTR) + var wideArgs = new nuint[argsArray.Length]; + var argPointers = new IntPtr[argsArray.Length]; + + for (var i = 0; i < argsArray.Length; i++) + { + argPointers[i] = Marshal.StringToHGlobalUni(argsArray[i]); + wideArgs[i] = (nuint)argPointers[i]; + } + + try + { + // Compile shader + using ComPtr result = default; + fixed (nuint* argsPtr = wideArgs) + { + var buffer = new DxcBuffer + { + Ptr = sourceBlob.Get()->GetBufferPointer(), + Size = sourceBlob.Get()->GetBufferSize(), + Encoding = DXC_CP_UTF8 + }; + + compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, null, __uuidof(), result.GetVoidAddressOf()); + } + + // Check compilation result + HResult hrStatus; + result.Get()->GetStatus(&hrStatus); + if (hrStatus.Failure) + { + // Get error messages + using ComPtr errorBlob = default; + result.Get()->GetErrorBuffer(errorBlob.GetAddressOf()); + + if (errorBlob.Get() != null) + { + var errorMessage = Marshal.PtrToStringUni((IntPtr)errorBlob.Get()->GetBufferPointer()); + throw new Exception($"DXC shader compilation failed: {errorMessage}"); + } + else + { + throw new Exception("DXC shader compilation failed with unknown error"); + } + } + + // Get compiled bytecode + using ComPtr bytecodeBlob = default; + result.Get()->GetResult(bytecodeBlob.GetAddressOf()); + + if (bytecodeBlob.Get() == null) + { + throw new Exception("DXC compilation succeeded but no bytecode was produced"); + } + + // Copy bytecode to managed array + var bytecodeSize = (int)bytecodeBlob.Get()->GetBufferSize(); + var bytecode = new byte[bytecodeSize]; + + fixed (byte* bytecodePtr = bytecode) + { + Buffer.MemoryCopy(bytecodeBlob.Get()->GetBufferPointer(), bytecodePtr, bytecodeSize, bytecodeSize); + } + + return bytecode; + } + finally + { + // Free allocated wide strings + for (var i = 0; i < argPointers.Length; i++) + { + Marshal.FreeHGlobal(argPointers[i]); + } + } + } + + private void CreateModernBindlessRootSignature() + { + var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; + + // Modern approach: Only CBV + Sampler, no descriptor tables needed + // The CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED flag allows direct heap access + var rootParameters = new RootParameter1[2]; + + // CBV for constant buffer + rootParameters[0] = new RootParameter1 + { + ParameterType = RootParameterType.Cbv, + ShaderVisibility = ShaderVisibility.All, + Descriptor = new RootDescriptor1(0, 0), // b0 + }; + + // Sampler descriptor table (still needed for samplers) + var samplerRanges = new DescriptorRange1[1]; + samplerRanges[0] = new DescriptorRange1 + { + RangeType = DescriptorRangeType.Sampler, + NumDescriptors = 1, + BaseShaderRegister = 0, // s0 + RegisterSpace = 0, + Flags = DescriptorRangeFlags.None, + OffsetInDescriptorsFromTableStart = 0 + }; + + fixed (DescriptorRange1* samplerRangesPtr = samplerRanges) + { + rootParameters[1] = new RootParameter1 + { + ParameterType = RootParameterType.DescriptorTable, + ShaderVisibility = ShaderVisibility.All, + DescriptorTable = new RootDescriptorTable1(1, samplerRangesPtr) + }; + } + + // Create root signature with the modern flag + fixed (RootParameter1* rootParamsPtr = rootParameters) + { + var rootSignatureDesc = new RootSignatureDescription1 + { + NumParameters = (uint)rootParameters.Length, + pParameters = rootParamsPtr, + NumStaticSamplers = 0, + pStaticSamplers = null, + // Key difference: Use the modern flag for direct heap indexing + Flags = RootSignatureFlags.AllowInputAssemblerInputLayout | + RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed + }; + + var versionedDesc = new VersionedRootSignatureDescription + { + Version = RootSignatureVersion.V1_1, + Desc_1_1 = rootSignatureDesc + }; + + using ComPtr signature = default; + using ComPtr error = default; + + D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf()); + + device->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof(), _rootSignature.GetVoidAddressOf()); + } + } + + private void CreatePipelineState(byte[] vsCode, byte[] psCode) + { + var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; + + fixed (byte* vsPtr = vsCode) + fixed (byte* psPtr = psCode) + { + + var psoDesc = new GraphicsPipelineStateDescription + { + pRootSignature = _rootSignature.Get(), + VS = new ShaderBytecode(vsPtr, (nuint)vsCode.Length), + PS = new ShaderBytecode(psPtr, (nuint)psCode.Length), + InputLayout = D3D12PipelineResource.InputLayoutDescription, + RasterizerState = RasterizerDescription.CullNone, + BlendState = BlendDescription.Opaque, + DepthStencilState = DepthStencilDescription.Default, + SampleMask = uint.MaxValue, + PrimitiveTopologyType = PrimitiveTopologyType.Triangle, + NumRenderTargets = 1, + SampleDesc = new SampleDescription(1, 0), + DSVFormat = Format.Unknown, + }; + + // Fix: Use the correct swap chain back buffer format + psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT; + + device->CreateGraphicsPipelineState(&psoDesc, __uuidof(), _pipelineState.GetVoidAddressOf()); + } + } + + private void CreateConstantBuffer() + { + var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; + + // Create constant buffer + var cbSize = (uint)((sizeof(ConstantBufferData) + 255) & ~255); // Align to 256 bytes + var heapProps = new HeapProperties(HeapType.Upload); + var bufferDesc = ResourceDescription.Buffer(cbSize); + + device->CreateCommittedResource( + &heapProps, + HeapFlags.None, + &bufferDesc, + ResourceStates.GenericRead, + null, + __uuidof(), + _constantBuffer.GetVoidAddressOf() + ); + + // Map constant buffer + fixed (void** mappedDataPtr = &_constantBufferMappedData) + { + _constantBuffer.Get()->Map(0, null, mappedDataPtr); + } + } + + private void UpdateConstantBuffer() + { + var cbData = new ConstantBufferData + { + Color = new(1.0f, 1.0f, 1.0f, 1.0f), + TextureIndex1 = 0, + TextureIndex2 = 1, + TextureIndex3 = 2, + TextureIndex4 = 3 + }; + + *(ConstantBufferData*)_constantBufferMappedData = cbData; + } + + private void CreateBindlessTextures() + { + var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; + var textureCount = _textureFiles.Length; + + // Initialize arrays + _textureResources = new ComPtr[textureCount]; + _uploadBuffers = new ComPtr[textureCount]; + _textureWidths = new uint[textureCount]; + _textureHeights = new uint[textureCount]; + _texturePitches = new uint[textureCount]; + + // Create bindless descriptor heap + var bindlessHeapDesc = new DescriptorHeapDescription + { + Type = DescriptorHeapType.CbvSrvUav, + NumDescriptors = 1000, + Flags = DescriptorHeapFlags.ShaderVisible + }; + + device->CreateDescriptorHeap(&bindlessHeapDesc, __uuidof(), _bindlessHeap.GetVoidAddressOf()); + + // Create sampler heap + var samplerHeapDesc = new DescriptorHeapDescription + { + Type = DescriptorHeapType.Sampler, + NumDescriptors = 1, + Flags = DescriptorHeapFlags.ShaderVisible + }; + + device->CreateDescriptorHeap(&samplerHeapDesc, __uuidof(), _samplerHeap.GetVoidAddressOf()); + + // Get descriptor handles + _bindlessHeapStart = _bindlessHeap.Get()->GetCPUDescriptorHandleForHeapStart(); + _bindlessHeapGpuStart = _bindlessHeap.Get()->GetGPUDescriptorHandleForHeapStart(); + _descriptorSize = device->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav); + + // Create sampler + var samplerDesc = new SamplerDescription + { + Filter = Filter.MinMagMipLinear, + AddressU = TextureAddressMode.Wrap, + AddressV = TextureAddressMode.Wrap, + AddressW = TextureAddressMode.Wrap, + MipLODBias = 0, + MaxAnisotropy = 1, + MinLOD = 0, + MaxLOD = float.MaxValue + }; + + // Set border color manually + samplerDesc.BorderColor[0] = 0; + samplerDesc.BorderColor[1] = 0; + samplerDesc.BorderColor[2] = 0; + samplerDesc.BorderColor[3] = 0; + + var samplerHandle = _samplerHeap.Get()->GetCPUDescriptorHandleForHeapStart(); + device->CreateSampler(&samplerDesc, samplerHandle); + + // Load and create textures + for (var i = 0; i < textureCount; i++) + { + CreateTextureResource(i); + } + } + + private void CreateTextureResource(int index) + { + var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; + + // Load image data + using var stream = new FileStream(_textureFiles[index], FileMode.Open, FileAccess.Read); + var image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + + var width = (uint)image.Width; + var height = (uint)image.Height; + uint bytesPerPixel = 4; + var pitch = width * bytesPerPixel; + + // Store texture dimensions + _textureWidths[index] = width; + _textureHeights[index] = height; + _texturePitches[index] = (pitch + 255) & ~255u; + + // Create texture resource + var textureDesc = new ResourceDescription + { + Dimension = ResourceDimension.Texture2D, + Alignment = 0, + Width = width, + Height = height, + DepthOrArraySize = 1, + MipLevels = 1, + Format = Format.R8G8B8A8Unorm, + SampleDesc = new SampleDescription(1, 0), + Layout = TextureLayout.Unknown, + Flags = ResourceFlags.None + }; + + var heapProps = new HeapProperties(HeapType.Default); + device->CreateCommittedResource( + &heapProps, + HeapFlags.None, + &textureDesc, + ResourceStates.CopyDest, + null, + __uuidof(), + _textureResources[index].GetVoidAddressOf() + ); + + // Create upload buffer + var uploadBufferSize = GetRequiredIntermediateSize(_textureResources[index].Get(), 0, 1); + var uploadHeapProps = new HeapProperties(HeapType.Upload); + var uploadBufferDesc = ResourceDescription.Buffer(uploadBufferSize); + + device->CreateCommittedResource( + &uploadHeapProps, + HeapFlags.None, + &uploadBufferDesc, + ResourceStates.GenericRead, + null, + __uuidof(), + _uploadBuffers[index].GetVoidAddressOf() + ); + + // Map and copy texture data + void* mappedData = null; + _uploadBuffers[index].Get()->Map(0, null, &mappedData); + + var srcData = image.Data.AsSpan(); + var dstSpan = new Span(mappedData, (int)uploadBufferSize); + + // Copy row by row with proper alignment + for (var y = 0; y < height; y++) + { + var srcRow = srcData.Slice(y * (int)pitch, (int)pitch); + var dstRow = dstSpan.Slice(y * (int)_texturePitches[index], (int)pitch); + srcRow.CopyTo(dstRow); + } + + _uploadBuffers[index].Get()->Unmap(0, null); + + // Create SRV in the bindless heap + var srvDesc = new ShaderResourceViewDescription + { + Format = Format.R8G8B8A8Unorm, + ViewDimension = Win32.Graphics.Direct3D12.SrvDimension.Texture2D, + Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, + Texture2D = new Texture2DSrv + { + MostDetailedMip = 0, + MipLevels = 1, + PlaneSlice = 0, + ResourceMinLODClamp = 0.0f + } + }; + + // Calculate SRV handle for this texture + var srvHandle = new CpuDescriptorHandle + { + ptr = _bindlessHeapStart.ptr + (nuint)(index * _descriptorSize) + }; + + device->CreateShaderResourceView(_textureResources[index].Get(), &srvDesc, srvHandle); + } + + private void UploadTextureData(CommandList cmd) + { + var commandList = cmd.NativeCommandList.Ptr; + + for (var i = 0; i < _textureResources.Length; i++) + { + // Copy from upload buffer to texture + var srcLocation = new TextureCopyLocation(_uploadBuffers[i].Get(), new PlacedSubresourceFootprint + { + Offset = 0, + Footprint = new SubresourceFootprint + { + Format = Format.R8G8B8A8Unorm, + Width = _textureWidths[i], + Height = _textureHeights[i], + Depth = 1, + RowPitch = _texturePitches[i] + } + }); + + var dstLocation = new TextureCopyLocation(_textureResources[i].Get(), 0); + + commandList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, null); + + // Transition texture to shader resource + var barrier = new ResourceBarrier + { + Type = ResourceBarrierType.Transition, + Flags = ResourceBarrierFlags.None, + Transition = new ResourceTransitionBarrier + { + pResource = _textureResources[i].Get(), + StateBefore = ResourceStates.CopyDest, + StateAfter = ResourceStates.PixelShaderResource, + Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES + } + }; + commandList->ResourceBarrier(1, &barrier); + } + + _texturesUploaded = true; + } + + private static ulong GetRequiredIntermediateSize(ID3D12Resource* destinationResource, uint firstSubresource, uint numSubresources) + { + var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; + var resourceDesc = destinationResource->GetDesc(); + + ulong requiredSize = 0; + device->GetCopyableFootprints(&resourceDesc, firstSubresource, numSubresources, 0, null, null, null, &requiredSize); + + return requiredSize; + } + + public void Execute(CommandList cmd) + { + var commandList = cmd.NativeCommandList.Ptr; + + // Set root signature and pipeline state + commandList->SetGraphicsRootSignature(_rootSignature.Get()); + commandList->SetPipelineState(_pipelineState.Get()); + + // Set descriptor heaps - this is crucial for modern bindless + var heaps = stackalloc ID3D12DescriptorHeap*[2]; + heaps[0] = _bindlessHeap.Get(); // CBV_SRV_UAV heap for direct indexing + heaps[1] = _samplerHeap.Get(); // Sampler heap + commandList->SetDescriptorHeaps(2, heaps); + + // Bind constant buffer + commandList->SetGraphicsRootConstantBufferView(0, _constantBuffer.Get()->GetGPUVirtualAddress()); + + // Bind sampler descriptor table + var samplerGpuHandle = _samplerHeap.Get()->GetGPUDescriptorHandleForHeapStart(); + commandList->SetGraphicsRootDescriptorTable(1, samplerGpuHandle); + + // No need to explicitly bind texture descriptor table! + // The textures are accessed directly via ResourceDescriptorHeap[index] + + // Draw mesh + commandList->IASetPrimitiveTopology(PrimitiveTopology.TriangleList); + commandList->IASetVertexBuffers(0, 1, _mesh!.VertexBufferView); + commandList->IASetIndexBuffer(_mesh.IndexBufferView); + commandList->DrawIndexedInstanced(_mesh.IndexCount, 1, 0, 0, 0); + } + + public void Dispose() + { + _mesh?.Dispose(); + + // Unmap constant buffer + if (_constantBuffer.Get() != null) + { + _constantBuffer.Get()->Unmap(0, null); + } + + _pipelineState.Dispose(); + _rootSignature.Dispose(); + _constantBuffer.Dispose(); + + // Dispose texture resources + if (_textureResources != null) + { + for (var i = 0; i < _textureResources.Length; i++) + { + _textureResources[i].Dispose(); + } + } + + // Dispose upload buffers + if (_uploadBuffers != null) + { + for (var i = 0; i < _uploadBuffers.Length; i++) + { + _uploadBuffers[i].Dispose(); + } + } + + _bindlessHeap.Dispose(); + _samplerHeap.Dispose(); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs index dd2cc73..1d45f50 100644 --- a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs +++ b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs @@ -1,9 +1,13 @@ using Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12; using Ghost.Graphics.Data; using Ghost.Graphics.Shading; using Ghost.Graphics.Utilities; -using System.Drawing; -using System.Numerics; +using StbImageSharp; +using Win32; +using Win32.Graphics.Direct3D; +using Win32.Graphics.Direct3D12; +using Win32.Graphics.Dxgi.Common; namespace Ghost.Graphics.RenderPasses; @@ -15,29 +19,35 @@ cbuffer ConstantBuffer : register(b0) float4 _Color; }; +Texture2D _MainTex : register(t0); +SamplerState _MainSampler : register(s0); + struct VertexInput { - float3 position : POSITION; + float4 position : POSITION; float4 color : COLOR; + float4 uv : TEXCOORD0; }; struct PixelInput { float4 position : SV_POSITION; float4 color : COLOR; + float4 uv : TEXCOORD0; }; PixelInput VSMain(VertexInput input) { PixelInput output; - output.position = float4(input.position, 1.0f); + output.position = input.position; output.color = input.color; + output.uv = input.uv; return output; } float4 PSMain(PixelInput input) : SV_TARGET { - return float4(_Color.xyz, 1.0); + return _MainTex.Sample(_MainSampler, input.uv.xy); } "; @@ -45,21 +55,218 @@ float4 PSMain(PixelInput input) : SV_TARGET private Shader? _shader; private Material? _material; - public void Initialize(ICommandBuffer cmb) + // Direct D3D12 resources for texture + private ComPtr _textureResource; + private ComPtr _srvHeap; + private CpuDescriptorHandle _srvHandle; + private GpuDescriptorHandle _srvGpuHandle; + private uint _srvDescriptorSize; + + // Additional fields for deferred texture upload + private ComPtr _uploadBuffer; + private uint _textureWidth; + private uint _textureHeight; + private uint _texturePitch; + private bool _textureUploaded = false; + + public void Initialize(CommandList cmd) { _mesh = MeshBuilder.CreateCube(0.25f); - _mesh.UploadMeshData(cmb); + _mesh.UploadMeshData(); _shader = new(_HLSL_SOURCE); _material = new(_shader); - var color = new Vector4(Color.Brown.R / 255f, Color.Brown.G / 255f, Color.Brown.B / 255f, 1.0f); - _material.SetVector("_Color", ref color); + // Create direct D3D12 texture resources + CreateTextureDirectly(); + + _material.UploadMaterialData(); + + // Copy from upload buffer to texture + var srcLocation = new TextureCopyLocation(_uploadBuffer.Get(), new PlacedSubresourceFootprint + { + Offset = 0, + Footprint = new SubresourceFootprint + { + Format = Format.R8G8B8A8Unorm, + Width = _textureWidth, + Height = _textureHeight, + Depth = 1, + RowPitch = _texturePitch + } + }); + + var dstLocation = new TextureCopyLocation(_textureResource.Get(), 0); + + cmd.NativeCommandList.Ptr->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, null); + + // Transition texture to shader resource + var barrier = new ResourceBarrier + { + Type = ResourceBarrierType.Transition, + Flags = ResourceBarrierFlags.None, + Transition = new ResourceTransitionBarrier + { + pResource = _textureResource.Get(), + StateBefore = ResourceStates.CopyDest, + StateAfter = ResourceStates.PixelShaderResource, + Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES + } + }; + cmd.NativeCommandList.Ptr->ResourceBarrier(1, &barrier); } - public void Execute(ICommandBuffer cmb) + private void CreateTextureDirectly() { - cmb.DrawMesh(_mesh!, _material!); + var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; + + // Load image data + using var stream = new FileStream("C:/Users/Misaki/Downloads/Im/Icon.png", FileMode.Open, FileAccess.Read); + var image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha); + + var width = (uint)image.Width; + var height = (uint)image.Height; + uint bytesPerPixel = 4; // RGBA + var pitch = width * bytesPerPixel; + var textureSize = pitch * height; + + // Create the texture resource + var textureDesc = new ResourceDescription + { + Dimension = ResourceDimension.Texture2D, + Alignment = 0, + Width = width, + Height = height, + DepthOrArraySize = 1, + MipLevels = 1, + Format = Format.R8G8B8A8Unorm, + SampleDesc = new SampleDescription(1, 0), + Layout = TextureLayout.Unknown, + Flags = ResourceFlags.None + }; + + var heapProps = new HeapProperties(HeapType.Default); + device->CreateCommittedResource( + &heapProps, + HeapFlags.None, + &textureDesc, + ResourceStates.CopyDest, + null, + __uuidof(), + _textureResource.GetVoidAddressOf() + ); + + // Create upload buffer + var uploadBufferSize = GetRequiredIntermediateSize(_textureResource.Get(), 0, 1); + var uploadHeapProps = new HeapProperties(HeapType.Upload); + var uploadBufferDesc = ResourceDescription.Buffer(uploadBufferSize); + + ComPtr uploadBuffer = default; + device->CreateCommittedResource( + &uploadHeapProps, + HeapFlags.None, + &uploadBufferDesc, + ResourceStates.GenericRead, + null, + __uuidof(), + uploadBuffer.GetVoidAddressOf() + ); + + // Map and copy texture data + void* mappedData = null; + uploadBuffer.Get()->Map(0, null, &mappedData); + + // Copy image data to upload buffer + var srcData = image.Data.AsSpan(); + var dstSpan = new Span(mappedData, (int)uploadBufferSize); + + // Copy row by row with proper alignment + var alignedPitch = (pitch + 255) & ~255u; // Align to 256 bytes + for (var y = 0; y < height; y++) + { + var srcRow = srcData.Slice(y * (int)pitch, (int)pitch); + var dstRow = dstSpan.Slice(y * (int)alignedPitch, (int)pitch); + srcRow.CopyTo(dstRow); + } + + uploadBuffer.Get()->Unmap(0, null); + + // We'll copy the texture data during Execute phase when we have access to command list + // Store the upload buffer for later use + _uploadBuffer = uploadBuffer.Move(); + _textureWidth = width; + _textureHeight = height; + _texturePitch = alignedPitch; + + // Create SRV descriptor heap + var srvHeapDesc = new DescriptorHeapDescription + { + Type = DescriptorHeapType.CbvSrvUav, + NumDescriptors = 1, + Flags = DescriptorHeapFlags.ShaderVisible + }; + + device->CreateDescriptorHeap(&srvHeapDesc, __uuidof(), _srvHeap.GetVoidAddressOf()); + + // Get descriptor handles + _srvHandle = _srvHeap.Get()->GetCPUDescriptorHandleForHeapStart(); + _srvGpuHandle = _srvHeap.Get()->GetGPUDescriptorHandleForHeapStart(); + _srvDescriptorSize = device->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav); + + // Create SRV + var srvDesc = new ShaderResourceViewDescription + { + Format = Format.R8G8B8A8Unorm, + ViewDimension = Win32.Graphics.Direct3D12.SrvDimension.Texture2D, + Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, + Texture2D = new Texture2DSrv + { + MostDetailedMip = 0, + MipLevels = 1, + PlaneSlice = 0, + ResourceMinLODClamp = 0.0f + } + }; + + device->CreateShaderResourceView(_textureResource.Get(), &srvDesc, _srvHandle); + } + + private static ulong GetRequiredIntermediateSize(ID3D12Resource* destinationResource, uint firstSubresource, uint numSubresources) + { + var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; + var resourceDesc = destinationResource->GetDesc(); + + ulong requiredSize = 0; + device->GetCopyableFootprints(&resourceDesc, firstSubresource, numSubresources, 0, null, null, null, &requiredSize); + + return requiredSize; + } + + public void Execute(CommandList cmd) + { + var commandList = cmd.NativeCommandList.Ptr; + + // Set root signature and pipeline state + commandList->SetGraphicsRootSignature(_material!.Shader.RootSignature); + commandList->SetPipelineState(_material.Shader.PipelineState); + + // Set descriptor heap + var heaps = stackalloc ID3D12DescriptorHeap*[1]; + heaps[0] = _srvHeap.Get(); + commandList->SetDescriptorHeaps(1, heaps); + + // Bind texture descriptor table directly + if (_material.Shader.Textures.Count > 0) + { + var textureInfo = _material.Shader.Textures[0]; + commandList->SetGraphicsRootDescriptorTable(textureInfo.RootParameterIndex, _srvGpuHandle); + } + + // Draw mesh + commandList->IASetPrimitiveTopology(PrimitiveTopology.TriangleList); + commandList->IASetVertexBuffers(0, 1, _mesh!.VertexBufferView); + commandList->IASetIndexBuffer(_mesh.IndexBufferView); + commandList->DrawIndexedInstanced(_mesh.IndexCount, 1, 0, 0, 0); } public void Dispose() @@ -67,5 +274,9 @@ float4 PSMain(PixelInput input) : SV_TARGET _mesh?.Dispose(); _shader?.Dispose(); _material?.Dispose(); + + _textureResource.Dispose(); + _uploadBuffer.Dispose(); + _srvHeap.Dispose(); } } diff --git a/Ghost.Graphics/Shading/CBufferCache.cs b/Ghost.Graphics/Shading/CBufferCache.cs index 0d8c895..bd6f11d 100644 --- a/Ghost.Graphics/Shading/CBufferCache.cs +++ b/Ghost.Graphics/Shading/CBufferCache.cs @@ -1,4 +1,4 @@ -using Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Helpers; using System.Runtime.CompilerServices; @@ -10,9 +10,10 @@ internal struct CBufferCache : IDisposable public UnsafeArray CpuData { get; + set; } - public IResource GpuResource + public GraphicsResource GpuResource { get; } @@ -21,9 +22,9 @@ internal struct CBufferCache : IDisposable public unsafe CBufferCache(uint bufferSize) { - CpuData = new((int)bufferSize, Allocator.Persistent); - _alignedSize = (bufferSize + 255u) & ~255u; + + CpuData = new((int)_alignedSize, Allocator.Persistent); GpuResource = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(_alignedSize); GpuResource.Name = "Material_CBufferCache"; diff --git a/Ghost.Graphics/Shading/Shader.cs b/Ghost.Graphics/Shading/Shader.cs index 4b799bf..3262483 100644 --- a/Ghost.Graphics/Shading/Shader.cs +++ b/Ghost.Graphics/Shading/Shader.cs @@ -1,7 +1,7 @@ using Ghost.Core; -using Ghost.Graphics.D3D12; using Ghost.Graphics.D3D12.Utilities; using Misaki.HighPerformance.LowLevel.Helpers; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -13,6 +13,24 @@ using Win32.Graphics.Dxgi.Common; namespace Ghost.Graphics.Shading; +internal readonly struct TextureInfo +{ + public required string Name + { + get; init; + } + + public uint RegisterSlot + { + get; init; + } + + public uint RootParameterIndex + { + get; init; + } +} + internal readonly struct PropertyInfo { public required string Name @@ -20,7 +38,7 @@ internal readonly struct PropertyInfo get; init; } - public required string CBufferName + public uint CBufferIndex { get; init; } @@ -62,23 +80,28 @@ public unsafe class Shader : IDisposable private readonly byte[] _vertexShaderBytecode; private readonly byte[] _pixelShaderBytecode; - private readonly Dictionary _constantBuffers = new(); - private readonly Dictionary _properties = new(); + private readonly List _constantBuffers = new(); + private readonly List _properties = new(); + private readonly Dictionary _propertyNameToIdMap = new(); + + private readonly List _textures = new(); + private readonly Dictionary _textureNameToIdMap = new(); private bool _disposed; internal ConstPtr PipelineState => new(_pipelineState.Get()); internal ConstPtr RootSignature => new(_rootSignature.Get()); - internal IReadOnlyDictionary ConstantBuffers => _constantBuffers; - internal IReadOnlyDictionary Properties => _properties; + internal IReadOnlyList ConstantBuffers => _constantBuffers; + internal IReadOnlyList Properties => _properties; + internal IReadOnlyList Textures => _textures; //public Shader(string shaderPath) //{ //} - public Shader(string shaderCode) + internal Shader(string shaderCode) { _vertexShaderBytecode = CompileShader(Encoding.UTF8.GetBytes(shaderCode), "VSMain", "vs_5_0"); _pixelShaderBytecode = CompileShader(Encoding.UTF8.GetBytes(shaderCode), "PSMain", "ps_5_0"); @@ -103,7 +126,7 @@ public unsafe class Shader : IDisposable /// The shader model to target (e.g., "vs_5_0", "ps_5_0"). /// A byte array containing the compiled shader bytecode. /// Thrown if shader compilation fails. - public static unsafe byte[] CompileShader(byte[] sourceCodeBytes, string entryPoint, string shaderProfile) + public static unsafe byte[] CompileShader(ReadOnlySpan sourceCodeBytes, string entryPoint, string shaderProfile) { using ComPtr bytecodeBlob = default; using ComPtr errorBlob = default; @@ -111,29 +134,14 @@ public unsafe class Shader : IDisposable var entryPointBytes = Encoding.UTF8.GetBytes(entryPoint); var shaderProfileBytes = Encoding.UTF8.GetBytes(shaderProfile); - // Call the D3DCompile function - var hr = D3DCompile( - sourceCodeBytes.AsSpan(), + ThrowIfFailed(D3DCompile( + sourceCodeBytes, entryPointBytes.AsSpan(), shaderProfileBytes.AsSpan(), CompileFlags.EnableStrictness | CompileFlags.Debug, bytecodeBlob.GetAddressOf(), errorBlob.GetAddressOf() - ); - - if (hr.Failure) - { - var errorMessage = "Shader compilation failed."; - if (errorBlob.Get() is not null) - { - errorMessage += "\n" + Encoding.ASCII.GetString( - (byte*)errorBlob.Get()->GetBufferPointer(), - (int)errorBlob.Get()->GetBufferSize() - ); - } - - throw new Exception(errorMessage); - } + )); var bytecode = new byte[bytecodeBlob.Get()->GetBufferSize()]; Unsafe.CopyBlock(bytecode.AsSpan().GetPointer(), bytecodeBlob.Get()->GetBufferPointer(), (uint)bytecode.Length); @@ -143,11 +151,18 @@ public unsafe class Shader : IDisposable private void CreateRootSignature() { - var rootParameters = new RootParameter1[_constantBuffers.Values.Count]; + // Calculate total root parameters: CBVs + 1 descriptor table for SRVs (if any textures) + var totalRootParameters = _constantBuffers.Count + (_textures.Count > 0 ? 1 : 0); + var rootParameters = new RootParameter1[totalRootParameters]; - var i = 0; - foreach (var cbufferInfo in _constantBuffers.Values) + var parameterIndex = 0; + + // Add CBV root parameters + for (var i = 0; i < _constantBuffers.Count; i++) { + var cbufferInfo = _constantBuffers[i]; + Debug.Assert(i == cbufferInfo.RegisterSlot); + var rootParameter = new RootParameter1 { ParameterType = RootParameterType.Cbv, @@ -155,10 +170,68 @@ public unsafe class Shader : IDisposable Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0), }; - rootParameters[i++] = rootParameter; + rootParameters[parameterIndex++] = rootParameter; } - var rootSignatureDesc = new RootSignatureDescription((uint)rootParameters.Length, (RootParameter*)Unsafe.AsPointer(ref rootParameters[0])) + // Add descriptor table for SRVs if we have textures + if (_textures.Count > 0) + { + var ranges = new DescriptorRange1[1]; + ranges[0] = new DescriptorRange1 + { + RangeType = DescriptorRangeType.Srv, + NumDescriptors = (uint)_textures.Count, + BaseShaderRegister = 0, // Start from t0 + RegisterSpace = 0, + Flags = DescriptorRangeFlags.None, + OffsetInDescriptorsFromTableStart = 0 + }; + + fixed (DescriptorRange1* rangesPtr = ranges) + { + var rootParameter = new RootParameter1 + { + ParameterType = RootParameterType.DescriptorTable, + ShaderVisibility = ShaderVisibility.All, + DescriptorTable = new RootDescriptorTable1(1, rangesPtr) + }; + + rootParameters[parameterIndex++] = rootParameter; + } + } + + // Create static samplers for textures + var staticSamplers = new StaticSamplerDescription[_textures.Count]; + for (var i = 0; i < _textures.Count; i++) + { + staticSamplers[i] = new StaticSamplerDescription + { + Filter = Filter.MinMagMipLinear, + AddressU = TextureAddressMode.Wrap, + AddressV = TextureAddressMode.Wrap, + AddressW = TextureAddressMode.Wrap, + MipLODBias = 0, + MaxAnisotropy = 1, + BorderColor = StaticBorderColor.OpaqueWhite, + MinLOD = 0, + MaxLOD = 0, + ShaderRegister = (uint)i, // s0, s1, etc. + RegisterSpace = 0, + ShaderVisibility = ShaderVisibility.All + }; + } + + var parameterCount = (uint)rootParameters.Length; + var parameters = parameterCount > 0 + ? (RootParameter*)Unsafe.AsPointer(ref rootParameters[0]) + : null; + + var samplerCount = (uint)staticSamplers.Length; + var samplers = samplerCount > 0 + ? (StaticSamplerDescription*)Unsafe.AsPointer(ref staticSamplers[0]) + : null; + + var rootSignatureDesc = new RootSignatureDescription(parameterCount, parameters, samplerCount, samplers) { Flags = RootSignatureFlags.AllowInputAssemblerInputLayout }; @@ -166,14 +239,9 @@ public unsafe class Shader : IDisposable using ComPtr signature = default; using ComPtr error = default; - var hr = D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_0, signature.GetAddressOf(), error.GetAddressOf()); - if (hr.Failure) - { - var errorMessage = System.Text.Encoding.ASCII.GetString((byte*)error.Get()->GetBufferPointer(), (int)error.Get()->GetBufferSize()); - throw new InvalidOperationException($"Failed to serialize root signature: {errorMessage}"); - } + ThrowIfFailed(D3D12SerializeRootSignature(&rootSignatureDesc, RootSignatureVersion.V1_0, signature.GetAddressOf(), error.GetAddressOf())); - GraphicsPipeline.GetGraphicsDevice().NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof(), _rootSignature.GetVoidAddressOf()); + GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof(), _rootSignature.GetVoidAddressOf()); } private void CreatePipelineStateObject() @@ -202,7 +270,7 @@ public unsafe class Shader : IDisposable psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT; // Create the PSO - GraphicsPipeline.GetGraphicsDevice().NativeDevice.Ptr->CreateGraphicsPipelineState(&psoDesc, __uuidof(), _pipelineState.GetVoidAddressOf()); + GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateGraphicsPipelineState(&psoDesc, __uuidof(), _pipelineState.GetVoidAddressOf()); } } catch (Exception ex) @@ -222,6 +290,9 @@ public unsafe class Shader : IDisposable ShaderDescription shaderDesc; reflection.Get()->GetDesc(&shaderDesc); + var cbufferRegistry = _constantBuffers.ToDictionary(cb => cb.Name); + var textureRegistry = _textures.ToDictionary(t => t.Name); + for (uint i = 0; i < shaderDesc.BoundResources; i++) { ShaderInputBindDescription bindDesc; @@ -230,7 +301,7 @@ public unsafe class Shader : IDisposable if (bindDesc.Type == ShaderInputType.ConstantBuffer) { var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); - if (cbufferName == null || _constantBuffers.ContainsKey(cbufferName)) + if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName)) { continue; } @@ -239,12 +310,13 @@ public unsafe class Shader : IDisposable ShaderBufferDescription cbufferDesc; cbuffer->GetDesc(&cbufferDesc); - _constantBuffers.Add(cbufferName, new CBufferInfo + var cbufferInfo = new CBufferInfo { Name = cbufferName, Size = cbufferDesc.Size, RegisterSlot = bindDesc.BindPoint - }); + }; + cbufferRegistry.Add(cbufferName, cbufferInfo); for (uint j = 0; j < cbufferDesc.Variables; j++) { @@ -253,24 +325,80 @@ public unsafe class Shader : IDisposable variable->GetDesc(&varDesc); var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name); - if (variableName == null || _properties.ContainsKey(variableName)) + if (variableName == null || _propertyNameToIdMap.ContainsKey(variableName)) { continue; } - _properties.Add(variableName, new PropertyInfo + var propInfo = new PropertyInfo { Name = variableName, - CBufferName = cbufferName, + CBufferIndex = cbufferInfo.RegisterSlot, ByteOffset = varDesc.StartOffset, Size = varDesc.Size - }); + }; + + // Add to the list and create the name-to-ID mapping + var newId = _properties.Count; + _properties.Add(propInfo); + _propertyNameToIdMap.Add(variableName, newId); } } + else if (bindDesc.Type == ShaderInputType.Texture) + { + var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); + if (textureName == null || textureRegistry.ContainsKey(textureName)) + { + continue; + } + + // The root parameter index for textures is after all CBVs + var textureInfo = new TextureInfo + { + Name = textureName, + RegisterSlot = bindDesc.BindPoint, + RootParameterIndex = (uint)_constantBuffers.Count // Descriptor table comes after CBVs + }; + + textureRegistry.Add(textureName, textureInfo); + + // Add to the texture name-to-ID mapping + var newId = _textures.Count; + _textureNameToIdMap.Add(textureName, newId); + } } + + _constantBuffers.Clear(); + _constantBuffers.AddRange(cbufferRegistry.Values); + + _textures.Clear(); + _textures.AddRange(textureRegistry.Values); + reflection.Dispose(); } + /// + /// Gets a unique, stable ID for a shader property. + /// This should be called once and the ID cached for repeated use. + /// + /// The name of the property (e.g., "_Color"). + /// The integer ID of the property, or -1 if not found. + public int GetPropertyId(string propertyName) + { + return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1; + } + + /// + /// Gets a unique, stable ID for a texture property. + /// This should be called once and the ID cached for repeated use. + /// + /// The name of the texture (e.g., "_MainTex"). + /// The integer ID of the texture, or -1 if not found. + public int GetTextureId(string textureName) + { + return _textureNameToIdMap.TryGetValue(textureName, out var id) ? id : -1; + } + public void Dispose() { if (_disposed) @@ -283,6 +411,8 @@ public unsafe class Shader : IDisposable _constantBuffers.Clear(); _properties.Clear(); + _textures.Clear(); + _textureNameToIdMap.Clear(); GC.SuppressFinalize(this); diff --git a/Ghost.Graphics/Utilities/MeshBuilder.cs b/Ghost.Graphics/Utilities/MeshBuilder.cs index a3f3214..aeb0d62 100644 --- a/Ghost.Graphics/Utilities/MeshBuilder.cs +++ b/Ghost.Graphics/Utilities/MeshBuilder.cs @@ -8,17 +8,17 @@ public static class MeshBuilder /// /// Creates a unit cube centered at the origin with size 1. /// - public static Mesh CreateCube(float size = 1.0f, Color128 color = default, Vector3 offset = default) + public static Mesh CreateCube(float size = 1.0f, Color128 color = default) { var half = size * 0.5f; var mesh = new Mesh(24, 36); - var corners = new Vector3[] + var corners = new Vector4[] { - new Vector3(-half, -half, -half) + offset, new Vector3( half, -half, -half) + offset, - new Vector3( half, half, -half) + offset, new Vector3(-half, half, -half) + offset, - new Vector3(-half, -half, half) + offset, new Vector3( half, -half, half) + offset, - new Vector3( half, half, half) + offset, new Vector3(-half, half, half) + offset + new (-half, -half, -half, 1.0f), new (half, -half, -half, 1.0f), + new (half, half, -half, 1.0f), new (-half, half, -half, 1.0f), + new (-half, -half, half, 1.0f), new (half, -half, half, 1.0f), + new (half, half, half, 1.0f), new (-half, half, half, 1.0f) }; int[][] faces = @@ -40,13 +40,13 @@ public static class MeshBuilder { var vertex = new Vertex { - Position = corners[face[i]].AsVector4(), + Position = corners[face[i]], Normal = Vector4.Zero, Tangent = Vector4.Zero, Color = color, UV = uvs[i].AsVector4() }; - mesh.AddVertex(new(corners[face[i]].AsVector4(), Vector4.Zero, Vector4.Zero, color, uvs[i].AsVector4())); + mesh.AddVertex(vertex); } mesh.AddTriangle((int)baseIndex + 0, (int)baseIndex + 1, (int)baseIndex + 2); diff --git a/Ghost.UnitTest/UnitTestAppWindow.xaml.cs b/Ghost.UnitTest/UnitTestAppWindow.xaml.cs index cf3aa1f..8a3ff5a 100644 --- a/Ghost.UnitTest/UnitTestAppWindow.xaml.cs +++ b/Ghost.UnitTest/UnitTestAppWindow.xaml.cs @@ -1,14 +1,16 @@ using Ghost.Graphics; using Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; +using Misaki.HighPerformance.LowLevel.Buffer; using WinRT; namespace Ghost.UnitTest; public sealed partial class UnitTestAppWindow : Window { - private IRenderer? _renderView; + private Renderer? _renderer; private ISwapChainPanelNative _swapChainPanelNative; public UnitTestAppWindow() @@ -25,31 +27,36 @@ public sealed partial class UnitTestAppWindow : Window { if (e.NewSize.Width > 8.0 && e.NewSize.Height > 8.0) { - _renderView?.RequestResize((uint)e.NewSize.Width, (uint)e.NewSize.Height); + _renderer?.RequestResize((uint)e.NewSize.Width, (uint)e.NewSize.Height); } } private void UnitTestAppWindow_Activated(object sender, WindowActivatedEventArgs args) { - GraphicsPipeline.Initialize(Graphics.Data.GraphicsAPI.D3D12); +#if DEBUG + AllocationManager.EnableDebugLayer(); +#endif + GraphicsPipeline.Initialize(); GraphicsPipeline.Start(); var guid = typeof(ISwapChainPanelNative.Interface).GUID; ((IWinRTObject)Panel).NativeObject.TryAs(guid, out var swapChainPanelNativeHandle); _swapChainPanelNative = new ISwapChainPanelNative(swapChainPanelNativeHandle); - _renderView = GraphicsPipeline.GraphicsDevice.CreateRenderer(new(_swapChainPanelNative, (uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height)); + _renderer = GraphicsPipeline.GraphicsDevice.CreateRenderer(new(_swapChainPanelNative, (uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height)); CompositionTarget.Rendering += OnRendering; } private void UnitTestAppWindow_Closed(object sender, WindowEventArgs args) { + CompositionTarget.Rendering -= OnRendering; + GraphicsPipeline.SignalCPUReady(); GraphicsPipeline.Shutdown(); - CompositionTarget.Rendering -= OnRendering; + _swapChainPanelNative.Dispose(); - _renderView?.Dispose(); + _renderer?.Dispose(); } private void OnRendering(object? sender, object e)