diff --git a/Ghost.Entities/Components/ComponentStorage.cs b/Ghost.Entities/Components/ComponentStorage.cs index 590a8d2..13e1e27 100644 --- a/Ghost.Entities/Components/ComponentStorage.cs +++ b/Ghost.Entities/Components/ComponentStorage.cs @@ -39,7 +39,7 @@ internal interface IComponentPool : IComponentPool internal class ComponentPool : IComponentPool where T : unmanaged, IComponentData { - private struct ComponentData + private struct ComponentMetadata { public T data; public Entity owner; @@ -48,7 +48,7 @@ internal class ComponentPool : IComponentPool private EntityID _nextId; private EntityID _capacity; - private ComponentData[] _components; + private ComponentMetadata[] _components; private EntityID[] _lookup; public EntityID Count => _nextId; @@ -58,7 +58,7 @@ internal class ComponentPool : IComponentPool _nextId = 0; _capacity = initialSize; - _components = new ComponentData[initialSize]; + _components = new ComponentMetadata[initialSize]; _lookup = new EntityID[initialSize]; _lookup.AsSpan().Fill(Entity.INVALID_ID); @@ -124,7 +124,7 @@ internal class ComponentPool : IComponentPool } _lookup[lookupIndex] = _nextId; - _components[_nextId] = new ComponentData + _components[_nextId] = new ComponentMetadata { data = component, owner = entity @@ -205,7 +205,7 @@ internal class ComponentPool : IComponentPool public void Dispose() { - _components = Array.Empty(); + _components = Array.Empty(); _lookup = Array.Empty(); _nextId = 0; _capacity = 0; diff --git a/Ghost.Entities/Entity.cs b/Ghost.Entities/Entity.cs index 620696d..456fcba 100644 --- a/Ghost.Entities/Entity.cs +++ b/Ghost.Entities/Entity.cs @@ -47,7 +47,7 @@ public struct Entity : IEquatable, IComparable public readonly bool Equals(Entity other) { - return _id == other._id; + return _id == other._id && _generation == other._generation; } public readonly int CompareTo(Entity other) diff --git a/Ghost.Graphics/Contracts/IRenderPass.cs b/Ghost.Graphics/Contracts/IRenderPass.cs index 19996d2..57d2b66 100644 --- a/Ghost.Graphics/Contracts/IRenderPass.cs +++ b/Ghost.Graphics/Contracts/IRenderPass.cs @@ -1,9 +1,9 @@ -using Ghost.Graphics.D3D12; +using Ghost.Graphics.RHI; namespace Ghost.Graphics.Contracts; public interface IRenderPass : IDisposable { - void Initialize(CommandList cmd); - void Execute(CommandList cmd); + void Initialize(ICommandBuffer cmd); + void Execute(ICommandBuffer cmd); } \ No newline at end of file diff --git a/Ghost.Graphics/Contracts/ISwapChainPanelNative.cs b/Ghost.Graphics/Contracts/ISwapChainPanelNative.cs index 3ee3fda..1dbb247 100644 --- a/Ghost.Graphics/Contracts/ISwapChainPanelNative.cs +++ b/Ghost.Graphics/Contracts/ISwapChainPanelNative.cs @@ -3,7 +3,7 @@ namespace Ghost.Graphics.Contracts; [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")] -public unsafe readonly struct ISwapChainPanelNative +public unsafe readonly struct ISwapChainPanelNative : ISwapChainPanelNative.Interface, IDisposable { [ComImport] [Guid("63aad0b8-7c24-40ff-85a8-640d944cc325")] @@ -20,13 +20,27 @@ public unsafe readonly struct ISwapChainPanelNative } private readonly IntPtr _nativePtr; - public readonly IntPtr NativePointer => _nativePtr; public ISwapChainPanelNative(IntPtr nativePtr) { _nativePtr = nativePtr; } + public void QueryInterface(in Guid riid, out nint ppvObject) + { + throw new NotImplementedException(); + } + + public uint AddRef() + { + throw new NotImplementedException(); + } + + public uint Release() + { + throw new NotImplementedException(); + } + public static ISwapChainPanelNative FromSwapChainPanel(object panel) { // Get the IUnknown/IInspectable pointer diff --git a/Ghost.Graphics/D3D12/D3D12Buffer.cs b/Ghost.Graphics/D3D12/D3D12Buffer.cs index 91d9bea..788fe4c 100644 --- a/Ghost.Graphics/D3D12/D3D12Buffer.cs +++ b/Ghost.Graphics/D3D12/D3D12Buffer.cs @@ -43,7 +43,7 @@ internal unsafe class D3D12Buffer : IBuffer public ResourceState CurrentState => _currentState; - public ID3D12Resource* NativeResource => _handle.ResourceHandle.GetAllocation().Resource; + public ID3D12Resource* NativeResource => _externalResource.Get() == null ? _handle.ResourceHandle.GetAllocation().Resource : _externalResource.Get(); /// /// Constructor for wrapping existing D3D12 resources diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs index 0035197..f2d4102 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs @@ -1,6 +1,7 @@ using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Win32; +using Win32.Graphics.Direct3D; using Win32.Graphics.Direct3D12; using Win32.Numerics; @@ -13,19 +14,26 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer { private ComPtr _allocator; private ComPtr _commandList; + + private readonly D3D12PipelineStateController _stateController; + private readonly D3D12DescriptorAllocator _descriptorAllocator; + private readonly CommandBufferType _type; private bool _isRecording; private bool _disposed; public ID3D12GraphicsCommandList10* NativeCommandList => _commandList.Get(); - public D3D12CommandBuffer(ComPtr device, CommandBufferType type) + public D3D12CommandBuffer(D3D12RenderDevice device, D3D12PipelineStateController stateController, D3D12DescriptorAllocator descriptorAllocator, CommandBufferType type) { _type = type; var commandListType = ConvertCommandBufferType(type); - device.Get()->CreateCommandAllocator(commandListType, __uuidof(), _allocator.GetVoidAddressOf()); - device.Get()->CreateCommandList(0u, commandListType, _allocator.Get(), null, __uuidof(), _commandList.GetVoidAddressOf()); + device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof(), _allocator.GetVoidAddressOf()); + device.NativeDevice->CreateCommandList(0u, commandListType, _allocator.Get(), null, __uuidof(), _commandList.GetVoidAddressOf()); + + _stateController = stateController; + _descriptorAllocator = descriptorAllocator; // Command lists are created in recording state, so close it _commandList.Get()->Close(); @@ -35,7 +43,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void Begin() { if (_isRecording) + { throw new InvalidOperationException("Command buffer is already recording"); + } _allocator.Get()->Reset(); _commandList.Get()->Reset(_allocator.Get(), null); @@ -45,7 +55,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void End() { if (!_isRecording) + { throw new InvalidOperationException("Command buffer is not recording"); + } _commandList.Get()->Close(); _isRecording = false; @@ -99,21 +111,53 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer throw new NotImplementedException(); } - public void SetPipelineState(IPipelineState pipelineState) + public void SetPipelineState(IPipelineStateController pipelineState) { // TODO: Implement pipeline state setting throw new NotImplementedException(); } - public void SetDescriptorHeaps(IDescriptorHeap[] heaps) + public void DrawMesh(Mesh mesh, Material material) { - // TODO: Implement descriptor heap setting - throw new NotImplementedException(); - } + // Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps) + var shaderPipeline = _stateController.GetShaderPipeline(material.Shader); + if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline) + { + throw new InvalidOperationException("Shader pipeline is not compiled or invalid"); + } - public void DrawIndexedInstanced(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0) - { - _commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance); + // Set root signature and pipeline state + _commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get()); + _commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get()); + + // Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6 + var heaps = stackalloc ID3D12DescriptorHeap*[2]; + heaps[0] = _descriptorAllocator.GetBindlessHeap(); // Specialized bindless heap + heaps[1] = d3d12Pipeline.samplerHeap.Get(); // Sampler heap from shader + _commandList.Get()->SetDescriptorHeaps(2, heaps); + + // Bind constant buffers + var rootParamIndex = 0u; + foreach (var cbufferInfo in material.Shader.ConstantBuffers) + { + var cache = material.CBufferCaches[(int)cbufferInfo.RegisterSlot]; + _commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, cache.GpuResource.GPUAddress); + } + + // Bind sampler descriptor table (last root parameter) + var samplerGpuHandle = d3d12Pipeline.samplerHeap.Get()->GetGPUDescriptorHandleForHeapStart(); + _commandList.Get()->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle); + + + // For fully bindless rendering, we don't use the Input Assembler stage + // Instead, we use instanced drawing where each "instance" represents a triangle + // The shader will use SV_InstanceID to index into the index buffer and then into the vertex buffer + _commandList.Get()->IASetPrimitiveTopology(PrimitiveTopology.TriangleList); + + // Draw without vertex/index buffers - use instanced drawing + // Each instance represents a triangle (3 vertices) + var triangleCount = mesh.IndexCount / 3; + _commandList.Get()->DrawInstanced(3, triangleCount, 0, 0); } public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1) diff --git a/Ghost.Graphics/D3D12/D3D12CommandQueue.cs b/Ghost.Graphics/D3D12/D3D12CommandQueue.cs index 94fe716..342c270 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandQueue.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandQueue.cs @@ -15,10 +15,13 @@ internal unsafe class D3D12CommandQueue : ICommandQueue private ulong _fenceValue; private bool _disposed; - public CommandQueueType Type { get; } + public CommandQueueType Type + { + get; + } public ID3D12CommandQueue* NativeQueue => _queue.Get(); - public D3D12CommandQueue(ComPtr device, CommandQueueType type) + public D3D12CommandQueue(ID3D12Device14* pDevice, CommandQueueType type) { Type = type; _fenceEvent = new AutoResetEvent(false); @@ -33,10 +36,10 @@ internal unsafe class D3D12CommandQueue : ICommandQueue fixed (void* queuePtr = &_queue) { - device.Get()->CreateCommandQueue(&queueDesc, __uuidof(), (void**)queuePtr); + pDevice->CreateCommandQueue(&queueDesc, __uuidof(), (void**)queuePtr); } - device.Get()->CreateFence(0, FenceFlags.None, __uuidof(), _fence.GetVoidAddressOf()); + pDevice->CreateFence(0, FenceFlags.None, __uuidof(), _fence.GetVoidAddressOf()); } public void Submit(ICommandBuffer commandBuffer) @@ -53,11 +56,11 @@ internal unsafe class D3D12CommandQueue : ICommandQueue } } - public void Submit(ICommandBuffer[] commandBuffers) + public void Submit(params ReadOnlySpan commandBuffers) { var commandLists = stackalloc ID3D12CommandList*[commandBuffers.Length]; - for (int i = 0; i < commandBuffers.Length; i++) + for (var i = 0; i < commandBuffers.Length; i++) { if (commandBuffers[i] is D3D12CommandBuffer d3d12CommandBuffer) { @@ -109,7 +112,8 @@ internal unsafe class D3D12CommandQueue : ICommandQueue public void Dispose() { - if (_disposed) return; + if (_disposed) + return; _fenceEvent?.Dispose(); _fence.Dispose(); diff --git a/Ghost.Graphics/D3D12/DebugLayer.cs b/Ghost.Graphics/D3D12/D3D12DebugLayer.cs similarity index 89% rename from Ghost.Graphics/D3D12/DebugLayer.cs rename to Ghost.Graphics/D3D12/D3D12DebugLayer.cs index 182854a..5db33cc 100644 --- a/Ghost.Graphics/D3D12/DebugLayer.cs +++ b/Ghost.Graphics/D3D12/D3D12DebugLayer.cs @@ -4,13 +4,13 @@ using Win32.Graphics.Dxgi; namespace Ghost.Graphics.D3D12; -internal unsafe class DebugLayer +internal unsafe class D3D12DebugLayer { private readonly ComPtr _d3d12Debug; private readonly ComPtr _dxgiDebug; private readonly ComPtr _dxgiInfoQueue; - public DebugLayer() + public D3D12DebugLayer() { D3D12GetDebugInterface(__uuidof(), _d3d12Debug.GetVoidAddressOf()); _d3d12Debug.Get()->EnableDebugLayer(); @@ -25,7 +25,7 @@ internal unsafe class DebugLayer public void Dispose() { - _dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, ReportLiveObjectFlags.Detail | ReportLiveObjectFlags.IgnoreInternal); + _dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, ReportLiveObjectFlags.All); _d3d12Debug.Dispose(); _dxgiDebug.Dispose(); diff --git a/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs b/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs index ff4a282..cd7cec6 100644 --- a/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs +++ b/Ghost.Graphics/D3D12/D3D12DescriptorAllocator.cs @@ -1,88 +1,427 @@ -using Ghost.Graphics.RHI; -using Win32; +using Ghost.Core; +using Ghost.Graphics.D3D12.Utilities; using Win32.Graphics.Direct3D12; namespace Ghost.Graphics.D3D12; /// -/// D3D12 implementation of descriptor allocator interface +/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps. /// -internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator +internal unsafe class D3D12DescriptorAllocator : IDisposable { - private readonly DescriptorAllocator _internalAllocator; + private readonly DescriptorHeapAllocator _rtvHeap; + private readonly DescriptorHeapAllocator _dsvHeap; + private readonly DescriptorHeapAllocator _srvHeap; + private readonly DescriptorHeapAllocator _samplerHeap; + private readonly BindlessDescriptorHeapAllocator _bindlessHeap; + private bool _disposed; - public D3D12DescriptorAllocator(ComPtr device) + public unsafe D3D12DescriptorAllocator(D3D12RenderDevice device, uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256, uint initialBindlessCount = 10000) { - _internalAllocator = new DescriptorAllocator(); + var pDevice = device.NativeDevice; + + _rtvHeap = new DescriptorHeapAllocator("rtv", pDevice, DescriptorHeapType.Rtv, initialRtvCount); + _dsvHeap = new DescriptorHeapAllocator("dsv", pDevice, DescriptorHeapType.Dsv, initialDsvCount); + _srvHeap = new DescriptorHeapAllocator("srv", pDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount); + _samplerHeap = new DescriptorHeapAllocator("sampler", pDevice, DescriptorHeapType.Sampler, initialSamplerCount); + _bindlessHeap = new BindlessDescriptorHeapAllocator(pDevice, initialBindlessCount); } - public DescriptorHandle AllocateRTV() + ~D3D12DescriptorAllocator() { - var rtvDescriptor = _internalAllocator.AllocateRTV(); - return new DescriptorHandle(rtvDescriptor.Index); + Dispose(); } - public DescriptorHandle[] AllocateRTVs(uint count) + #region RTV Methods + + public RenderTargetDescriptor AllocateRTV() { - var rtvDescriptors = _internalAllocator.AllocateRTVs(count); - return rtvDescriptors.Select(desc => new DescriptorHandle(desc.Index)).ToArray(); + 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 DescriptorHandle AllocateDSV() + public RenderTargetDescriptor[] AllocateRTVs(uint count) { - var dsvDescriptor = _internalAllocator.AllocateDSV(); - return new DescriptorHandle(dsvDescriptor.Index); + 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 DescriptorHandle AllocateSRV() + public void ReleaseRTV(RenderTargetDescriptor descriptor) { - var srvDescriptor = _internalAllocator.AllocateSRV(); - return new DescriptorHandle(srvDescriptor.Index); + ObjectDisposedException.ThrowIf(_disposed, this); + + if (descriptor is RenderTargetDescriptor d3d12Descriptor) + { + _rtvHeap.ReleaseDescriptor(d3d12Descriptor.Index); + } } - public DescriptorHandle AllocateSampler() + public void ReleaseRTVs(RenderTargetDescriptor[] descriptors) { - var samplerDescriptor = _internalAllocator.AllocateSampler(); - return new DescriptorHandle(samplerDescriptor.Index); + ObjectDisposedException.ThrowIf(_disposed, this); + + foreach (var descriptor in descriptors) + { + ReleaseRTV(descriptor); + } } - public DescriptorHandle AllocateBindless() + #endregion + + #region DSV Methods + + public DepthStencilDescriptor AllocateDSV() { - var bindlessDescriptor = _internalAllocator.AllocateBindless(); - return new DescriptorHandle(bindlessDescriptor.Index); + 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 void ReleaseRTV(DescriptorHandle handle) + public DepthStencilDescriptor[] AllocateDSVs(uint count) { - // TODO: Convert back to internal descriptor and release + 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(DescriptorHandle handle) + public void ReleaseDSV(DepthStencilDescriptor descriptor) { - // TODO: Convert back to internal descriptor and release + ObjectDisposedException.ThrowIf(_disposed, this); + + if (descriptor is DepthStencilDescriptor d3d12Descriptor) + { + _dsvHeap.ReleaseDescriptor(d3d12Descriptor.Index); + } } - public void ReleaseSRV(DescriptorHandle handle) + public void ReleaseDSVs(DepthStencilDescriptor[] descriptors) { - // TODO: Convert back to internal descriptor and release + ObjectDisposedException.ThrowIf(_disposed, this); + + foreach (var descriptor in descriptors) + { + ReleaseDSV(descriptor); + } } - public void ReleaseSampler(DescriptorHandle handle) + #endregion + + #region SRV Methods + + public ShaderResourceDescriptor AllocateSRV() { - // TODO: Convert back to internal descriptor and release + 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 void ReleaseBindless(DescriptorHandle handle) + public ShaderResourceDescriptor[] AllocateSRVs(uint count) { - // TODO: Convert back to internal descriptor and release + 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 Bindless Methods + + /// + /// Allocates a bindless descriptor for SM 6.6 rendering. + /// The returned descriptor maintains a 1:1 relationship between allocation index and shader index. + /// + public BindlessDescriptor AllocateBindless() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var index = _bindlessHeap.AllocateDescriptor(); + if (index == uint.MaxValue) + { + throw new InvalidOperationException("Failed to allocate bindless descriptor"); + } + + var cpuHandle = _bindlessHeap.GetCpuHandle(index); + var gpuHandle = _bindlessHeap.GetGpuHandle(index); + + return new BindlessDescriptor(index, cpuHandle, gpuHandle); + } + + /// + /// Allocates multiple bindless descriptors for SM 6.6 rendering. + /// + public BindlessDescriptor[] AllocateBindless(uint count) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + var baseIndex = _bindlessHeap.AllocateDescriptors(count); + if (baseIndex == uint.MaxValue) + { + throw new InvalidOperationException($"Failed to allocate {count} bindless descriptors"); + } + + var descriptors = new BindlessDescriptor[count]; + for (uint i = 0; i < count; i++) + { + var index = baseIndex + i; + var cpuHandle = _bindlessHeap.GetCpuHandle(index); + var gpuHandle = _bindlessHeap.GetGpuHandle(index); + descriptors[i] = new BindlessDescriptor(index, cpuHandle, gpuHandle); + } + + return descriptors; + } + + /// + /// Releases a bindless descriptor. + /// + public void ReleaseBindless(BindlessDescriptor descriptor) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (descriptor is BindlessDescriptor d3d12Descriptor) + { + _bindlessHeap.ReleaseDescriptor(d3d12Descriptor.Index); + } + } + + /// + /// Releases multiple bindless descriptors. + /// + public void ReleaseBindless(BindlessDescriptor[] descriptors) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + foreach (var descriptor in descriptors) + { + ReleaseBindless(descriptor); + } + } + + #endregion + + #region Utility Methods + + /// + /// Gets the RTV heap for binding to the command list. + /// + public ID3D12DescriptorHeap* GetRTVHeap() => _rtvHeap.Heap; + + /// + /// Gets the DSV heap for binding to the command list. + /// + public ID3D12DescriptorHeap* GetDSVHeap() => _dsvHeap.Heap; + + /// + /// Gets the SRV heap for binding to the command list. + /// + public ID3D12DescriptorHeap* GetSRVHeap() => _srvHeap.ShaderVisibleHeap; + + /// + /// Gets the sampler heap for binding to the command list. + /// + public ID3D12DescriptorHeap* GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap; + + /// + /// Gets the bindless heap for SM 6.6 bindless rendering. + /// + public ID3D12DescriptorHeap* GetBindlessHeap() => _bindlessHeap.BindlessHeap; + + /// + /// Gets the shader visible heaps that need to be bound to the command list. + /// + public ID3D12DescriptorHeap*[] GetShaderVisibleHeaps() + { + return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap]; + } + + /// + /// Gets the shader visible heaps including bindless heap for SM 6.6 rendering. + /// + public ConstPtr[] GetShaderVisibleHeapsWithBindless() + { + return [_bindlessHeap.BindlessHeap, _srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap]; + } + + #endregion + public void Dispose() { - if (_disposed) return; + if (_disposed) + { + return; + } + + _rtvHeap.Dispose(); + _dsvHeap.Dispose(); + _srvHeap.Dispose(); + _samplerHeap.Dispose(); + _bindlessHeap.Dispose(); - _internalAllocator?.Dispose(); _disposed = true; + + GC.SuppressFinalize(this); } -} +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs b/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs new file mode 100644 index 0000000..60ab9a4 --- /dev/null +++ b/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs @@ -0,0 +1,61 @@ +using Ghost.Graphics.RHI; + +namespace Ghost.Graphics.D3D12; + +internal unsafe class D3D12GraphicsEngine : IGraphicsEngine +{ +#if DEBUG + private readonly D3D12DebugLayer _debugLayer; +#endif + + private readonly D3D12RenderDevice _device; + private readonly D3D12PipelineStateController _stateController; + private readonly D3D12ResourceAllocator _resourceAllocator; + + private readonly D3D12PipelineStateController _pipelineState; + private readonly D3D12DescriptorAllocator _descriptorAllocator; + + public IRenderDevice Device => _device; + public IPipelineStateController PipelineStateController => _stateController; + public IResourceAllocator ResourceAllocator => _resourceAllocator; + + public D3D12GraphicsEngine(RenderSystem renderSystem) + { +#if DEBUG + _debugLayer = new(); +#endif + + _device = new(); + _stateController = new(_device); + _resourceAllocator = new(_device, renderSystem); + + _pipelineState = new(_device); + _descriptorAllocator = new(_device); + } + + public IRenderer CreateRenderer() + { + return new D3D12Renderer(this, _resourceAllocator); + } + + public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics) + { + return new D3D12CommandBuffer(_device, _stateController, _descriptorAllocator, type); + } + + public ISwapChain CreateSwapChain(SwapChainDesc desc) + { + return new D3D12SwapChain(_device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc); + } + + public void Dispose() + { + _descriptorAllocator.Dispose(); + _resourceAllocator.Dispose(); + _device.Dispose(); + +#if DEBUG + _debugLayer.Dispose(); +#endif + } +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12PipelineStateController.cs b/Ghost.Graphics/D3D12/D3D12PipelineStateController.cs new file mode 100644 index 0000000..fc5e770 --- /dev/null +++ b/Ghost.Graphics/D3D12/D3D12PipelineStateController.cs @@ -0,0 +1,253 @@ +using Ghost.Graphics.D3D12.Utilities; +using Ghost.Graphics.Data; +using Ghost.Graphics.RHI; +using Win32; +using Win32.Graphics.Direct3D; +using Win32.Graphics.Direct3D12; +using Win32.Graphics.Dxgi.Common; + +namespace Ghost.Graphics.D3D12; + +internal class D3D12ShaderPipeline : IShaderPipeline +{ + public ComPtr rootSignature; + public ComPtr pipelineState; + public ComPtr samplerHeap; + public D3D12ShaderCompiler.CompileResult vsResult; + public D3D12ShaderCompiler.CompileResult psResult; + public D3D12ShaderCompiler.CompileResult csResult; + + public PipelineType Type + { + get; init; + } +} + +internal unsafe class D3D12PipelineStateController : IPipelineStateController, IDisposable +{ + private const string _VS_ENTRY_POINT = "VSMain"; + private const string _PS_ENTRY_POINT = "PSMain"; + private const string _PROFILE_VS_6_6 = "vs_6_6"; + + private readonly ID3D12Device14* _device; + + private readonly Dictionary _shaderPipelines; + + public D3D12PipelineStateController(D3D12RenderDevice device) + { + _device = device.NativeDevice; + _shaderPipelines = new(); + } + + // TODO: Support compute shaders + public void ColectionShader(ReadOnlySpan shaders) + { + foreach (var shader in shaders) + { + _shaderPipelines.TryAdd(shader, new() + { + Type = PipelineType.Graphics + }); + } + } + + public void CompileCollected() + { + foreach (var kvp in _shaderPipelines) + { + var vsResult = D3D12ShaderCompiler.CompileDXC(kvp.Key, _VS_ENTRY_POINT, _PROFILE_VS_6_6); + var psResult = D3D12ShaderCompiler.CompileDXC(kvp.Key, _PS_ENTRY_POINT, _PROFILE_VS_6_6); + + kvp.Value.vsResult = vsResult; + kvp.Value.psResult = psResult; + } + } + + private void CreateRootSignature(Shader shader, D3D12ShaderPipeline shaderPipeline) + { + // Calculate total root parameters: CBVs + Regular texture descriptor table + Sampler table + var totalRootParams = shader.ConstantBuffers.Count + (shader.RegularTextures.Count > 0 ? 1 : 0) + 1; // +1 for sampler + var rootParameters = new RootParameter1[totalRootParams]; + + var parameterIndex = 0; + + // Add CBV root parameters + foreach (var cbufferInfo in shader.ConstantBuffers) + { + rootParameters[parameterIndex++] = new RootParameter1 + { + ParameterType = RootParameterType.Cbv, + ShaderVisibility = ShaderVisibility.All, + Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0), + }; + } + + // Add regular texture descriptor table if we have regular textures + if (shader.RegularTextures.Count > 0) + { + var textureRanges = new DescriptorRange1[1]; + textureRanges[0] = new DescriptorRange1 + { + RangeType = DescriptorRangeType.Srv, + NumDescriptors = (uint)shader.RegularTextures.Count, + BaseShaderRegister = 0, // Start from t0 + RegisterSpace = 0, + Flags = DescriptorRangeFlags.None, + OffsetInDescriptorsFromTableStart = 0 + }; + + fixed (DescriptorRange1* textureRangesPtr = textureRanges) + { + rootParameters[parameterIndex++] = new RootParameter1 + { + ParameterType = RootParameterType.DescriptorTable, + ShaderVisibility = ShaderVisibility.All, + DescriptorTable = new RootDescriptorTable1(1, textureRangesPtr) + }; + } + } + + // 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[parameterIndex] = 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(), shaderPipeline.rootSignature.GetVoidAddressOf()); + } + } + + private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline) + { + var psoDesc = new GraphicsPipelineStateDescription + { + pRootSignature = shaderPipeline.rootSignature.Get(), + VS = new ShaderBytecode(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count), + PS = new ShaderBytecode(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count), + 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, + }; + + psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT; + + _device->CreateGraphicsPipelineState(&psoDesc, __uuidof(), shaderPipeline.pipelineState.GetVoidAddressOf()); + } + + private void CreateSamplerHeap(D3D12ShaderPipeline shaderPipeline) + { + // Create sampler heap + var samplerHeapDesc = new DescriptorHeapDescription + { + Type = DescriptorHeapType.Sampler, + NumDescriptors = 1, + Flags = DescriptorHeapFlags.ShaderVisible + }; + + _device->CreateDescriptorHeap(&samplerHeapDesc, __uuidof(), shaderPipeline.samplerHeap.GetVoidAddressOf()); + + // Create default 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 = shaderPipeline.samplerHeap.Get()->GetCPUDescriptorHandleForHeapStart(); + _device->CreateSampler(&samplerDesc, samplerHandle); + } + + // TODO: Pipeline variants (keywords) + // TODO: Disk caching + // TODO: Async compilation + public void PreCookPipelineState() + { + foreach (var kvp in _shaderPipelines) + { + CreateRootSignature(kvp.Key, kvp.Value); + CreatePipelineStateObject(kvp.Value); + CreateSamplerHeap(kvp.Value); + } + } + + public IShaderPipeline GetShaderPipeline(Shader shader) + { + if (_shaderPipelines.TryGetValue(shader, out var pipeline)) + { + return pipeline; + } + + throw new KeyNotFoundException($"Shader pipeline not found for shader: {shader}"); + } + + public void Dispose() + { + foreach (var kvp in _shaderPipelines) + { + kvp.Value.rootSignature.Dispose(); + kvp.Value.pipelineState.Dispose(); + kvp.Value.samplerHeap.Dispose(); + kvp.Value.vsResult.Dispose(); + kvp.Value.psResult.Dispose(); + } + } +} diff --git a/Ghost.Graphics/D3D12/D3D12RenderDevice.cs b/Ghost.Graphics/D3D12/D3D12RenderDevice.cs index 89198a3..eae06df 100644 --- a/Ghost.Graphics/D3D12/D3D12RenderDevice.cs +++ b/Ghost.Graphics/D3D12/D3D12RenderDevice.cs @@ -18,14 +18,12 @@ internal unsafe class D3D12RenderDevice : IRenderDevice private readonly D3D12CommandQueue _graphicsQueue; private readonly D3D12CommandQueue _computeQueue; private readonly D3D12CommandQueue _copyQueue; - private readonly D3D12DescriptorAllocator _descriptorAllocator; private bool _disposed; public ICommandQueue GraphicsQueue => _graphicsQueue; public ICommandQueue ComputeQueue => _computeQueue; public ICommandQueue CopyQueue => _copyQueue; - public IDescriptorAllocator DescriptorAllocator => _descriptorAllocator; public ID3D12Device14* NativeDevice => _device.Get(); public IDXGIFactory7* DXGIFactory => _dxgiFactory.Get(); @@ -38,8 +36,11 @@ internal unsafe class D3D12RenderDevice : IRenderDevice _graphicsQueue = new D3D12CommandQueue(_device, CommandQueueType.Graphics); _computeQueue = new D3D12CommandQueue(_device, CommandQueueType.Compute); _copyQueue = new D3D12CommandQueue(_device, CommandQueueType.Copy); + } - _descriptorAllocator = new D3D12DescriptorAllocator(_device); + ~D3D12RenderDevice() + { + Dispose(); } private void InitializeDevice() @@ -78,22 +79,13 @@ internal unsafe class D3D12RenderDevice : IRenderDevice } } - public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics) - { - return new D3D12CommandBuffer(_device, type); - } - - public ISwapChain CreateSwapChain(SwapChainDesc desc) - { - return new D3D12SwapChain(_dxgiFactory, _graphicsQueue.NativeQueue, desc); - } - public void Dispose() { if (_disposed) + { return; + } - _descriptorAllocator?.Dispose(); _graphicsQueue?.Dispose(); _computeQueue?.Dispose(); _copyQueue?.Dispose(); @@ -103,5 +95,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice _adapter.Dispose(); _disposed = true; + + GC.SuppressFinalize(this); } } diff --git a/Ghost.Graphics/D3D12/D3D12RenderTarget.cs b/Ghost.Graphics/D3D12/D3D12RenderTarget.cs index ca7e185..bd6e577 100644 --- a/Ghost.Graphics/D3D12/D3D12RenderTarget.cs +++ b/Ghost.Graphics/D3D12/D3D12RenderTarget.cs @@ -1,5 +1,8 @@ using Ghost.Graphics.Data; using Ghost.Graphics.RHI; +using System.Runtime.CompilerServices; +using Win32; +using Win32.Graphics.Direct3D12; namespace Ghost.Graphics.D3D12; @@ -7,62 +10,51 @@ namespace Ghost.Graphics.D3D12; /// D3D12 implementation of render target interface /// Supports either color OR depth rendering, not both /// -internal unsafe class D3D12RenderTarget : IRenderTarget +internal unsafe class D3D12RenderTarget : D3D12Texture, IRenderTarget { - private readonly D3D12Texture _target; - private bool _disposed; - - public uint Width - { - get; - } - - public uint Height - { - get; - } - public RenderTargetType Type { get; } - public ITexture Target => _target; + private D3D12RenderTarget(ComPtr resource, uint width, uint height, uint slice, TextureFormat format, RenderTargetType type, uint mipLevels = 1) + : base(resource, width, height, slice, format, mipLevels) + { + Type = type; + } + + private D3D12RenderTarget(TextureHandle handle, ref readonly RenderTargetDesc desc, ref readonly TextureDesc texDesc) + : base(handle, in texDesc) + { + Type = desc.Type; + } /// /// Create a new render target with its own texture /// - public D3D12RenderTarget(TextureHandle handle, ref readonly RenderTargetDesc desc) + /// The handle to the texture resource + /// The descriptor to describe the render target + /// New render target instance + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static D3D12RenderTarget Create(TextureHandle handle, ref readonly RenderTargetDesc desc) { - Width = desc.Width; - Height = desc.Height; - Type = desc.Type; - - // Create the target texture based on type - var usage = Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil; - var textureDesc = new TextureDesc(desc.Width, desc.Height, desc.Format, desc.Dimension, desc.MipLevels, usage); - _target = new D3D12Texture(handle, in textureDesc); + var texDesc = RenderTargetDesc.ToTextureDescriptor(desc); + return new D3D12RenderTarget(handle, in desc, in texDesc); } /// - /// Wrap an existing texture as a render target (for swap chain back buffers) + /// Create a new render target from an existing D3D12 resource /// - public D3D12RenderTarget(D3D12Texture existingTexture, RenderTargetType type) + /// The existing D3D12 resource + /// The width of the render target + /// The height of the render target + /// The format of the render target + /// The type of the render target + /// The number of mip levels + /// New render target instance + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static D3D12RenderTarget Create(ComPtr resource, uint width, uint height, uint slice, TextureFormat format, RenderTargetType type, uint mipLevels = 1) { - _target = existingTexture; - Width = existingTexture.Width; - Height = existingTexture.Height; - Type = type; - } - - public void Dispose() - { - if (_disposed) - { - return; - } - - _target?.Dispose(); - _disposed = true; + return new D3D12RenderTarget(resource, width, height, slice, format, type, mipLevels); } } diff --git a/Ghost.Graphics/D3D12/D3D12Renderer.cs b/Ghost.Graphics/D3D12/D3D12Renderer.cs index 5804263..ef9264c 100644 --- a/Ghost.Graphics/D3D12/D3D12Renderer.cs +++ b/Ghost.Graphics/D3D12/D3D12Renderer.cs @@ -1,6 +1,6 @@ -using Ghost.Graphics.RHI; -using Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.Data; +using Ghost.Graphics.RHI; namespace Ghost.Graphics.D3D12; @@ -11,18 +11,18 @@ public unsafe class D3D12Renderer : IRenderer { private struct FrameResource : IDisposable { - public ICommandBuffer CommandBuffer; - public ulong FenceValue; + public ICommandBuffer commandBuffer; + public ulong fenceValue; - public FrameResource(IRenderDevice device) + public FrameResource(IGraphicsEngine graphicsEngine) { - CommandBuffer = device.CreateCommandBuffer(); - FenceValue = 0; + commandBuffer = graphicsEngine.CreateCommandBuffer(); + fenceValue = 0; } - public void Dispose() + public readonly void Dispose() { - CommandBuffer?.Dispose(); + commandBuffer?.Dispose(); } } @@ -30,9 +30,11 @@ public unsafe class D3D12Renderer : IRenderer private readonly FrameResource[] _frameResources; private uint _frameIndex; - private IRenderTarget? _destinationTarget; // Final destination (custom render target or swap chain back buffer) + private readonly IResourceAllocator _resourceAllocator; + + private IRenderTarget? _customRenderTarget; // User-provided render target + private IRenderTarget? _offScreenRenderTarget; // Off-screen target for swap chain private ISwapChain? _swapChain; - private IRenderTarget? _offScreenRenderTarget; // Always render to off-screen first private readonly Lock _lock = new(); private uint _pendingWidth; @@ -43,41 +45,39 @@ public unsafe class D3D12Renderer : IRenderer // TODO: Add render passes support // private ImmutableArray _renderPasses; - public D3D12Renderer(IRenderDevice device) + public D3D12Renderer(IGraphicsEngine graphicsEngine, IResourceAllocator resourceAllocator) { - _commandQueue = device.GraphicsQueue; + _resourceAllocator = resourceAllocator; + _commandQueue = graphicsEngine.Device.GraphicsQueue; // Create frame resources for double buffering - _frameResources = new FrameResource[2]; - for (int i = 0; i < _frameResources.Length; i++) + _frameResources = new FrameResource[D3D12PipelineResource.BACK_BUFFER_COUNT]; + for (var i = 0; i < _frameResources.Length; i++) { - _frameResources[i] = new FrameResource(device); + _frameResources[i] = new FrameResource(graphicsEngine); } } + ~D3D12Renderer() + { + Dispose(); + } + public void SetRenderTarget(IRenderTarget? renderTarget) { - _destinationTarget = renderTarget; - _swapChain = null; // Clear swap chain when using custom render target + _customRenderTarget = renderTarget; + _swapChain = null; - // Create or update off-screen render target to match destination size - if (_destinationTarget != null) - { - CreateOrUpdateOffScreenRenderTarget(_destinationTarget.Width, _destinationTarget.Height); - } - else - { - _offScreenRenderTarget?.Dispose(); - _offScreenRenderTarget = null; - } + // Clean up off-screen target when switching to render target mode + _offScreenRenderTarget?.Dispose(); + _offScreenRenderTarget = null; } public void SetSwapChain(ISwapChain? swapChain) { _swapChain = swapChain; - _destinationTarget = null; // Clear custom render target when using swap chain + _customRenderTarget = null; - // Create or update off-screen render target to match swap chain size if (_swapChain != null) { CreateOrUpdateOffScreenRenderTarget(_swapChain.Width, _swapChain.Height); @@ -88,6 +88,7 @@ public unsafe class D3D12Renderer : IRenderer _offScreenRenderTarget = null; } } + public void RequestResize(uint width, uint height) { lock (_lock) @@ -104,7 +105,9 @@ public unsafe class D3D12Renderer : IRenderer public void ExecutePendingResize() { if (!_resizeRequested) + { return; + } uint newWidth, newHeight; lock (_lock) @@ -131,59 +134,37 @@ public unsafe class D3D12Renderer : IRenderer { ExecutePendingResize(); - // Get current frame resource var frameIndex = _frameIndex % (uint)_frameResources.Length; ref var frame = ref _frameResources[frameIndex]; - // Wait for this frame resource to be available - if (frame.FenceValue > 0) + if (frame.fenceValue > 0) { - _commandQueue.WaitForValue(frame.FenceValue); + _commandQueue.WaitForValue(frame.fenceValue); } - // Begin command recording - frame.CommandBuffer.Begin(); + frame.commandBuffer.Begin(); - // Determine the final destination target - IRenderTarget? finalDestination = null; - ITexture? swapChainBackBuffer = null; - - if (_destinationTarget != null) + if (_customRenderTarget != null) { - // Rendering to custom render target - finalDestination = _destinationTarget; + // Render target mode: render directly to custom target + RenderScene(_customRenderTarget, frame.commandBuffer); } - else if (_swapChain != null) + else if (_swapChain != null && _offScreenRenderTarget != null) { - // Rendering to swap chain - get back buffer as render target - finalDestination = _swapChain.GetCurrentBackBufferRenderTarget(); - swapChainBackBuffer = _swapChain.GetCurrentBackBuffer(); + // Swap chain mode: render to off-screen, then blit to back buffer + var backBufferRT = _swapChain.GetCurrentBackBuffer(); + + // For testing, we render directly to the back buffer + RenderScene(backBufferRT, frame.commandBuffer); + //BlitToDestination(_offScreenRenderTarget, backBufferRT, frame.CommandBuffer); } - if (finalDestination != null && _offScreenRenderTarget != null) - { - // Always render to off-screen first, then blit to final destination - RenderScene(_offScreenRenderTarget, frame.CommandBuffer); - BlitToDestination(_offScreenRenderTarget, finalDestination, swapChainBackBuffer, frame.CommandBuffer); - } - else - { - // No destination - skip rendering - frame.CommandBuffer.End(); - return; - } + frame.commandBuffer.End(); - // End command recording - frame.CommandBuffer.End(); - - // Submit commands - _commandQueue.Submit(frame.CommandBuffer); - - // Present if using swap chain + _commandQueue.Submit(frame.commandBuffer); _swapChain?.Present(); - // Signal fence for this frame - frame.FenceValue = _commandQueue.Signal(++_frameIndex); + frame.fenceValue = _commandQueue.Signal(++_frameIndex); } private void RenderScene(IRenderTarget target, ICommandBuffer cmd) @@ -207,13 +188,13 @@ public unsafe class D3D12Renderer : IRenderer cmd.EndRenderPass(); } - private void BlitToDestination(IRenderTarget source, IRenderTarget destination, ITexture? swapChainBackBuffer, ICommandBuffer cmd) + private void BlitToDestination(IRenderTarget source, IRenderTarget destination, ICommandBuffer cmd) { // Handle swap chain back buffer transitions if needed - if (swapChainBackBuffer != null) + if (_swapChain != null) { // Transition back buffer to render target - cmd.ResourceBarrier(swapChainBackBuffer, ResourceState.Present, ResourceState.RenderTarget); + cmd.ResourceBarrier(destination, ResourceState.Present, ResourceState.RenderTarget); } // For now, we'll do a simple copy operation @@ -231,22 +212,24 @@ public unsafe class D3D12Renderer : IRenderer cmd.EndRenderPass(); // Handle swap chain back buffer transitions if needed - if (swapChainBackBuffer != null) + if (_swapChain != null) { // Transition back buffer to present - cmd.ResourceBarrier(swapChainBackBuffer, ResourceState.RenderTarget, ResourceState.Present); + cmd.ResourceBarrier(destination, ResourceState.RenderTarget, ResourceState.Present); } } private void CreateOrUpdateOffScreenRenderTarget(uint width, uint height) { // Check if we need to recreate the off-screen render target - if (_offScreenRenderTarget == null || _offScreenRenderTarget.Width != width || _offScreenRenderTarget.Height != height) + if (_offScreenRenderTarget == null || + _offScreenRenderTarget.Width != width || + _offScreenRenderTarget.Height != height) { _offScreenRenderTarget?.Dispose(); - var desc = RenderTargetDesc.Color(width, height, TextureFormat.B8G8R8A8_UNorm); - _offScreenRenderTarget = _device.CreateRenderTarget(desc); + var desc = RenderTargetDesc.Color(width, height, 1, TextureFormat.R8G8B8A8_UNorm); + _offScreenRenderTarget = _resourceAllocator.CreateRenderTarget(in desc); } } @@ -255,16 +238,19 @@ public unsafe class D3D12Renderer : IRenderer // Wait for all frame resources to complete foreach (ref var frame in _frameResources.AsSpan()) { - if (frame.FenceValue > 0) + if (frame.fenceValue > 0) { - _commandQueue.WaitForValue(frame.FenceValue); + _commandQueue.WaitForValue(frame.fenceValue); } } } public void Dispose() { - if (_disposed) return; + if (_disposed) + { + return; + } WaitIdle(); @@ -276,5 +262,7 @@ public unsafe class D3D12Renderer : IRenderer _offScreenRenderTarget?.Dispose(); _disposed = true; + + GC.SuppressFinalize(this); } } diff --git a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs index 4f7b74e..489bd66 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs @@ -11,7 +11,7 @@ using ResourceHandle = Ghost.Graphics.Data.ResourceHandle; namespace Ghost.Graphics.D3D12; -internal unsafe class D3D12ResourceAllocator +internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable { private readonly struct AllocationInfo : IDisposable { @@ -60,14 +60,14 @@ internal unsafe class D3D12ResourceAllocator } } - public D3D12ResourceAllocator(IDXGIAdapter* pAdapter, ID3D12Device* pDevice, RenderSystem renderSystem) + public D3D12ResourceAllocator(D3D12RenderDevice device, RenderSystem renderSystem) { _renderSystem = renderSystem; var desc = new AllocatorDesc { - pAdapter = pAdapter, - pDevice = pDevice, + pAdapter = (IDXGIAdapter*)device.Adapter, + pDevice = (ID3D12Device*)device.NativeDevice, Flags = AllocatorFlags.DefaultPoolsNotZeroed | AllocatorFlags.MSAATexturesAlwaysCommitted }; @@ -92,7 +92,7 @@ internal unsafe class D3D12ResourceAllocator } } - private ResourceHandle TrackResource(in Allocation allocation, bool isTemp) + private ResourceHandle TrackResource(ref readonly Allocation allocation, bool isTemp) { int id; uint generation; @@ -130,7 +130,7 @@ internal unsafe class D3D12ResourceAllocator return handle; } - public TextureHandle CreateTexture2D(in TextureDesc desc, bool tempResource = false) + public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false) { CheckTexture2DSize(desc.Width, desc.Height); @@ -139,7 +139,7 @@ internal unsafe class D3D12ResourceAllocator desc.Width, desc.Height, mipLevels: (ushort)desc.MipLevels, - arraySize: 1, + arraySize: (ushort)desc.Slice, flags: ConvertTextureUsage(desc.Usage) ); @@ -157,7 +157,7 @@ internal unsafe class D3D12ResourceAllocator return new(TrackResource(in allocation, tempResource)); } - public BufferHandle CreateBuffer(in BufferDesc desc, bool tempResource = false) + public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false) { CheckBufferSize((uint)desc.Size); @@ -176,10 +176,30 @@ internal unsafe class D3D12ResourceAllocator return new(TrackResource(in allocation, tempResource)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public BufferHandle CreateUploadBuffer(uint sizeInBytes, bool tempResource = false) { var desc = new BufferDesc(sizeInBytes, BufferUsage.Upload, MemoryType.Upload); - return CreateBuffer(in desc, tempResource); + return CreateBufferHandle(in desc, tempResource); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false) + { + var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc); + return D3D12RenderTarget.Create(CreateTextureHandle(in textureDesc), in desc); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false) + { + return new D3D12Texture(CreateTextureHandle(in desc, tempResource), in desc); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false) + { + return new D3D12Buffer(CreateBufferHandle(in desc, tempResource), in desc); } #region Conversion Methods @@ -203,13 +223,19 @@ internal unsafe class D3D12ResourceAllocator var flags = ResourceFlags.None; if (usage.HasFlag(TextureUsage.RenderTarget)) + { flags |= ResourceFlags.AllowRenderTarget; + } if (usage.HasFlag(TextureUsage.DepthStencil)) + { flags |= ResourceFlags.AllowDepthStencil; + } if (usage.HasFlag(TextureUsage.UnorderedAccess)) + { flags |= ResourceFlags.AllowUnorderedAccess; + } return flags; } @@ -219,7 +245,9 @@ internal unsafe class D3D12ResourceAllocator var flags = ResourceFlags.None; if (usage.HasFlag(BufferUsage.Raw)) + { flags |= ResourceFlags.AllowUnorderedAccess; + } return flags; } @@ -238,13 +266,19 @@ internal unsafe class D3D12ResourceAllocator private static ResourceStates DetermineInitialTextureState(TextureUsage usage) { if (usage.HasFlag(TextureUsage.RenderTarget)) + { return ResourceStates.RenderTarget; + } if (usage.HasFlag(TextureUsage.DepthStencil)) + { return ResourceStates.DepthWrite; + } if (usage.HasFlag(TextureUsage.UnorderedAccess)) + { return ResourceStates.UnorderedAccess; + } return ResourceStates.Common; } @@ -252,19 +286,29 @@ internal unsafe class D3D12ResourceAllocator private static ResourceStates DetermineInitialBufferState(BufferUsage usage, MemoryType memoryType) { if (memoryType == MemoryType.Upload) + { return ResourceStates.GenericRead; + } if (memoryType == MemoryType.Readback) + { return ResourceStates.CopyDest; + } if (usage.HasFlag(BufferUsage.Vertex)) + { return ResourceStates.VertexAndConstantBuffer; + } if (usage.HasFlag(BufferUsage.Index)) + { return ResourceStates.IndexBuffer; + } if (usage.HasFlag(BufferUsage.Constant)) + { return ResourceStates.VertexAndConstantBuffer; + } return ResourceStates.Common; } @@ -275,20 +319,20 @@ internal unsafe class D3D12ResourceAllocator { while (_temResources.Count > 0) { - ref var handle = ref _temResources.Peek(); - ref var info = ref _allocations[handle.id]; + var handle = _temResources.Peek(); + var info = _allocations[handle.id]; if (info.Allocated && info.cpuFenceValue > _renderSystem.CPUFenceValue) { break; } - ReleaseAllocation(in handle); + ReleaseResource(handle); _temResources.Dequeue(); } } - public Allocation GetAllocation(in ResourceHandle handle) + public ID3D12Resource* GetResource(ResourceHandle handle) { if (!handle.IsValid) { @@ -301,10 +345,10 @@ internal unsafe class D3D12ResourceAllocator throw new InvalidOperationException($"Resource with ID {handle.id} and generation {handle.generation} is not allocated or has been released."); } - return allocationInfo.allocation; + return allocationInfo.allocation.Resource; } - public void ReleaseAllocation(in ResourceHandle handle) + public void ReleaseResource(ResourceHandle handle) { if (!handle.IsValid) { diff --git a/Ghost.Graphics/D3D12/D3D12ResourceFactory.cs b/Ghost.Graphics/D3D12/D3D12ResourceFactory.cs deleted file mode 100644 index 88f2e2a..0000000 --- a/Ghost.Graphics/D3D12/D3D12ResourceFactory.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Ghost.Graphics.Data; -using Ghost.Graphics.RHI; -using System.Runtime.CompilerServices; -using Win32.Graphics.Direct3D12; -using Win32.Graphics.Dxgi; - -namespace Ghost.Graphics.D3D12; -internal class D3D12ResourceFactory : IResourceFactory -{ - private readonly D3D12ResourceAllocator _allocator; - - public unsafe D3D12ResourceFactory(D3D12RenderDevice device, RenderSystem renderSystem) - { - _allocator = new D3D12ResourceAllocator((IDXGIAdapter*)device.Adapter, (ID3D12Device*)device.NativeDevice, renderSystem); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc) - { - var usage = desc.Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil; - if (desc.CreationFlags.HasFlag(RenderTargetCreationFlags.AllowUAV)) - { - usage |= TextureUsage.UnorderedAccess; - } - - var textureDesc = new TextureDesc(desc.Width, desc.Height, desc.Format, desc.Dimension, 1, usage); - return new D3D12RenderTarget(CreateTextureHandle(in textureDesc), in desc); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc) - { - return _allocator.CreateTexture2D(in desc); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ITexture CreateTexture(ref readonly TextureDesc desc) - { - return new D3D12Texture(CreateTextureHandle(in desc), in desc); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc) - { - return _allocator.CreateBuffer(in desc); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IBuffer CreateBuffer(ref readonly BufferDesc desc) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs b/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs new file mode 100644 index 0000000..c5c1df8 --- /dev/null +++ b/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs @@ -0,0 +1,245 @@ +using Ghost.Graphics.Data; +using Misaki.HighPerformance.LowLevel.Collections; +using System.Runtime.InteropServices; +using System.Text; +using Win32; +using Win32.Graphics.Direct3D; +using Win32.Graphics.Direct3D.Dxc; +using Win32.Graphics.Direct3D12; + +namespace Ghost.Graphics.D3D12; + +internal unsafe static class D3D12ShaderCompiler +{ + public struct CompileResult : IDisposable + { + public UnsafeArray bytecode; + public ComPtr reflection; + + public void Dispose() + { + bytecode.Dispose(); + reflection.Dispose(); + } + } + + public static CompileResult CompileDXC(Shader shader, 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(shader.Source); + fixed (byte* sourceBytesPtr = sourceBytes) + { + utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf()); + } + + // Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data + 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 but KEEP reflection + }; + + // 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"); + } + + // Get reflection data using DXC API + using ComPtr reflectionBlob = default; + result.Get()->GetOutput(DxcOutKind.Reflection, __uuidof(), reflectionBlob.GetVoidAddressOf(), null); + + if (reflectionBlob.Get() == null) + { + throw new Exception("DXC compilation succeeded but no reflection data was produced"); + } + + var bytecodeSize = bytecodeBlob.Get()->GetBufferSize(); + var bytecode = new UnsafeArray((int)bytecodeSize, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); + + NativeMemory.Copy(bytecodeBlob.Get()->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize); + + return new CompileResult + { + bytecode = bytecode, + reflection = reflectionBlob.Move() + }; + } + finally + { + // Free allocated wide strings + for (var i = 0; i < argPointers.Length; i++) + { + Marshal.FreeHGlobal(argPointers[i]); + } + } + } + + public static void PerformDXCReflection(Shader shader, IDxcBlob* reflectionBlob) + { + // Create DXC utils to parse reflection data + using ComPtr utils = default; + DxcCreateInstance(CLSID_DxcUtils, __uuidof(), utils.GetVoidAddressOf()); + + // Create reflection interface from blob + var reflectionData = new DxcBuffer + { + Ptr = reflectionBlob->GetBufferPointer(), + Size = reflectionBlob->GetBufferSize(), + Encoding = DXC_CP_ACP + }; + + using ComPtr reflection = default; + utils.Get()->CreateReflection(&reflectionData, __uuidof(), reflection.GetVoidAddressOf()); + + if (reflection.Get() == null) + { + throw new Exception("Failed to create shader reflection from DXC output"); + } + + ShaderDescription shaderDesc; + reflection.Get()->GetDesc(&shaderDesc); + + var cbufferRegistry = shader.ConstantBuffers.ToDictionary(cb => cb.Name); + var textureRegistry = shader.RegularTextures.ToDictionary(t => t.Name); + + for (uint i = 0; i < shaderDesc.BoundResources; i++) + { + ShaderInputBindDescription bindDesc; + reflection.Get()->GetResourceBindingDesc(i, &bindDesc); + + if (bindDesc.Type == ShaderInputType.ConstantBuffer) + { + var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); + if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName)) + { + continue; + } + + var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name); + ShaderBufferDescription cbufferDesc; + cbuffer->GetDesc(&cbufferDesc); + + var cbufferInfo = new CBufferInfo + { + Name = cbufferName, + Size = cbufferDesc.Size, + RegisterSlot = bindDesc.BindPoint + }; + cbufferRegistry.Add(cbufferName, cbufferInfo); + + for (uint j = 0; j < cbufferDesc.Variables; j++) + { + var variable = cbuffer->GetVariableByIndex(j); + ShaderVariableDescription varDesc; + variable->GetDesc(&varDesc); + + var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name); + if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName)) + { + continue; + } + + var propInfo = new PropertyInfo + { + Name = variableName, + CBufferIndex = cbufferInfo.RegisterSlot, + ByteOffset = varDesc.StartOffset, + Size = varDesc.Size + }; + + // Add to the list and create the name-to-ID mapping + var newId = shader.Properties.Count; + shader.Properties.Add(propInfo); + shader.PropertyNameToIdMap.Add(variableName, newId); + } + } + else if (bindDesc.Type == ShaderInputType.Texture) + { + var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); + if (textureName == null || textureRegistry.ContainsKey(textureName)) + { + continue; + } + + // ALL texture input slots are regular textures! + // Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index] + var textureInfo = new TextureInfo + { + Name = textureName, + RegisterSlot = bindDesc.BindPoint, + RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs + }; + + textureRegistry.Add(textureName, textureInfo); + } + } + + shader.ConstantBuffers.Clear(); + shader.ConstantBuffers.AddRange(cbufferRegistry.Values); + + shader.RegularTextures.Clear(); + shader.RegularTextures.AddRange(textureRegistry.Values); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12SwapChain.cs b/Ghost.Graphics/D3D12/D3D12SwapChain.cs index 2fea7a5..2baa5b7 100644 --- a/Ghost.Graphics/D3D12/D3D12SwapChain.cs +++ b/Ghost.Graphics/D3D12/D3D12SwapChain.cs @@ -1,5 +1,7 @@ using Ghost.Graphics.Contracts; +using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.RHI; +using System.Runtime.CompilerServices; using Win32; using Win32.Graphics.Direct3D12; using Win32.Graphics.Dxgi; @@ -13,47 +15,46 @@ namespace Ghost.Graphics.D3D12; internal unsafe class D3D12SwapChain : ISwapChain { private ComPtr _swapChain; - private readonly D3D12Texture[] _backBuffers; - private readonly D3D12RenderTarget[] _backBufferRenderTargets; - private uint _currentBackBufferIndex; + private readonly D3D12RenderTarget[] _backBuffers; private bool _disposed; public uint Width { get; private set; } + public uint Height { get; private set; } + public uint BufferCount { get; } - public D3D12SwapChain(ComPtr factory, ID3D12CommandQueue* commandQueue, SwapChainDesc desc) + public D3D12SwapChain(IDXGIFactory7* pFactory, ID3D12CommandQueue* pCommandQueue, SwapChainDesc desc) { - _backBuffers = new D3D12Texture[desc.BufferCount]; - _backBufferRenderTargets = new D3D12RenderTarget[desc.BufferCount]; + _backBuffers = new D3D12RenderTarget[D3D12PipelineResource.BACK_BUFFER_COUNT]; - Width = desc.Width; - Height = desc.Height; - BufferCount = desc.BufferCount; + Width = desc.width; + Height = desc.height; + BufferCount = D3D12PipelineResource.BACK_BUFFER_COUNT; - CreateSwapChain(factory, commandQueue, desc); + CreateSwapChain(pFactory, pCommandQueue, desc); CreateBackBuffers(); } - private void CreateSwapChain(ComPtr factory, ID3D12CommandQueue* commandQueue, SwapChainDesc desc) + private void CreateSwapChain(IDXGIFactory7* pFactory, ID3D12CommandQueue* commandQueue, SwapChainDesc desc) { var swapChainDesc = new SwapChainDescription1 { - Width = desc.Width, - Height = desc.Height, - Format = ConvertTextureFormat(desc.Format), + Width = desc.width, + Height = desc.height, + Format = ConvertTextureFormat(desc.format), SampleDesc = new SampleDescription(1, 0), BufferUsage = Usage.BackBuffer | Usage.RenderTargetOutput, - BufferCount = desc.BufferCount, + BufferCount = D3D12PipelineResource.BACK_BUFFER_COUNT, Scaling = Scaling.Stretch, SwapEffect = SwapEffect.FlipDiscard, AlphaMode = AlphaMode.Ignore, @@ -63,15 +64,15 @@ internal unsafe class D3D12SwapChain : ISwapChain using ComPtr tempSwapChain = default; - switch (desc.Target.Type) + switch (desc.target.Type) { case SwapChainTargetType.Composition: - factory.Get()->CreateSwapChainForComposition((IUnknown*)commandQueue, &swapChainDesc, null, tempSwapChain.GetAddressOf()); + pFactory->CreateSwapChainForComposition((IUnknown*)commandQueue, &swapChainDesc, null, tempSwapChain.GetAddressOf()); // Set the composition surface - if (desc.Target.CompositionSurface != null) + if (desc.target.CompositionSurface != null) { - var swapChainPanelNative = ISwapChainPanelNative.FromSwapChainPanel(desc.Target.CompositionSurface); + using var swapChainPanelNative = ISwapChainPanelNative.FromSwapChainPanel(desc.target.CompositionSurface); swapChainPanelNative.SetSwapChain((IntPtr)tempSwapChain.Get()); } break; @@ -82,9 +83,9 @@ internal unsafe class D3D12SwapChain : ISwapChain Windowed = true, }; - factory.Get()->CreateSwapChainForHwnd( + pFactory->CreateSwapChainForHwnd( (IUnknown*)commandQueue, - desc.Target.WindowHandle, + desc.target.WindowHandle, &swapChainDesc, &swapChainFullscreenDesc, null, @@ -99,8 +100,6 @@ internal unsafe class D3D12SwapChain : ISwapChain { throw new InvalidOperationException("Failed to create IDXGISwapChain4 interface."); } - - _currentBackBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex(); } private void CreateBackBuffers() @@ -111,23 +110,14 @@ internal unsafe class D3D12SwapChain : ISwapChain _swapChain.Get()->GetBuffer(i, __uuidof(), backBuffer.GetVoidAddressOf()); backBuffer.Get()->SetName($"SwapChain_BackBuffer_{i}"); - _backBuffers[i] = new D3D12Texture(backBuffer.Move(), Width, Height, TextureFormat.B8G8R8A8_UNorm); - - // Create render target wrapper for the back buffer - _backBufferRenderTargets[i] = new D3D12RenderTarget(_backBuffers[i], RenderTargetType.Color); + _backBuffers[i] = D3D12RenderTarget.Create(backBuffer.Move(), Width, Height, 1, TextureFormat.B8G8R8A8_UNorm, RenderTargetType.Color); } } - public ITexture GetCurrentBackBuffer() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IRenderTarget GetCurrentBackBuffer() { - _currentBackBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex(); - return _backBuffers[_currentBackBufferIndex]; - } - - public IRenderTarget GetCurrentBackBufferRenderTarget() - { - _currentBackBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex(); - return _backBufferRenderTargets[_currentBackBufferIndex]; + return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()]; } public void Present(bool vsync = true) @@ -149,7 +139,6 @@ internal unsafe class D3D12SwapChain : ISwapChain // Release old back buffers and render targets for (var i = 0; i < _backBuffers.Length; i++) { - _backBufferRenderTargets[i]?.Dispose(); _backBuffers[i]?.Dispose(); } @@ -164,7 +153,6 @@ internal unsafe class D3D12SwapChain : ISwapChain // Recreate back buffers CreateBackBuffers(); - _currentBackBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex(); } private static Format ConvertTextureFormat(TextureFormat format) @@ -188,7 +176,6 @@ internal unsafe class D3D12SwapChain : ISwapChain for (var i = 0; i < _backBuffers.Length; i++) { - _backBufferRenderTargets[i]?.Dispose(); _backBuffers[i]?.Dispose(); } diff --git a/Ghost.Graphics/D3D12/D3D12Texture.cs b/Ghost.Graphics/D3D12/D3D12Texture.cs index 07c303f..854ebbd 100644 --- a/Ghost.Graphics/D3D12/D3D12Texture.cs +++ b/Ghost.Graphics/D3D12/D3D12Texture.cs @@ -25,6 +25,11 @@ internal unsafe class D3D12Texture : ITexture get; } + public uint Slice + { + get; + } + public TextureFormat Format { get; @@ -49,13 +54,14 @@ internal unsafe class D3D12Texture : ITexture public ID3D12Resource* NativeResource => _handle.IsValid ? _handle.ResourceHandle.GetAllocation().Resource : _externalResource.Get(); - public D3D12Texture(ComPtr resource, uint width, uint height, TextureFormat format, uint mipLevels = 1) + public D3D12Texture(ComPtr resource, uint width, uint height, uint slice, TextureFormat format, uint mipLevels = 1) { _handle = TextureHandle.Invalid; _externalResource = resource.Move(); Width = width; Height = height; + Slice = slice; Format = format; MipLevels = mipLevels; _currentState = ResourceState.Common; @@ -69,6 +75,7 @@ internal unsafe class D3D12Texture : ITexture Width = desc.Width; Height = desc.Height; + Slice = desc.Slice; Format = desc.Format; var mipLevels = desc.MipLevels; @@ -84,7 +91,7 @@ internal unsafe class D3D12Texture : ITexture ~D3D12Texture() { - Dispose(); + Dispose(false); } private static uint GetBytesPerPixel(TextureFormat format) @@ -107,6 +114,12 @@ internal unsafe class D3D12Texture : ITexture } public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) { if (_disposed) { @@ -123,7 +136,5 @@ internal unsafe class D3D12Texture : ITexture } _disposed = true; - - GC.SuppressFinalize(this); } } diff --git a/Ghost.Graphics/D3D12/DescriptorAllocator.cs b/Ghost.Graphics/D3D12/DescriptorAllocator.cs deleted file mode 100644 index d639185..0000000 --- a/Ghost.Graphics/D3D12/DescriptorAllocator.cs +++ /dev/null @@ -1,421 +0,0 @@ -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 readonly BindlessDescriptorHeapAllocator _bindlessHeap; - - private bool _disposed; - - public DescriptorAllocator(uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256, uint initialBindlessCount = 10000) - { - var device = GraphicsPipeline.GraphicsDevice; - - _rtvHeap = new DescriptorHeapAllocator("rtv", device.NativeDevice, DescriptorHeapType.Rtv, initialRtvCount); - _dsvHeap = new DescriptorHeapAllocator("dsv", device.NativeDevice, DescriptorHeapType.Dsv, initialDsvCount); - _srvHeap = new DescriptorHeapAllocator("srv", device.NativeDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount); - _samplerHeap = new DescriptorHeapAllocator("sampler", device.NativeDevice, DescriptorHeapType.Sampler, initialSamplerCount); - _bindlessHeap = new BindlessDescriptorHeapAllocator(device.NativeDevice, initialBindlessCount); - } - - #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 Bindless Methods - - /// - /// Allocates a bindless descriptor for SM 6.6 rendering. - /// The returned descriptor maintains a 1:1 relationship between allocation index and shader index. - /// - public BindlessDescriptor AllocateBindless() - { - ObjectDisposedException.ThrowIf(_disposed, this); - - var index = _bindlessHeap.AllocateDescriptor(); - if (index == uint.MaxValue) - { - throw new InvalidOperationException("Failed to allocate bindless descriptor"); - } - - var cpuHandle = _bindlessHeap.GetCpuHandle(index); - var gpuHandle = _bindlessHeap.GetGpuHandle(index); - - return new BindlessDescriptor(index, cpuHandle, gpuHandle); - } - - /// - /// Allocates multiple bindless descriptors for SM 6.6 rendering. - /// - public BindlessDescriptor[] AllocateBindless(uint count) - { - ObjectDisposedException.ThrowIf(_disposed, this); - - var baseIndex = _bindlessHeap.AllocateDescriptors(count); - if (baseIndex == uint.MaxValue) - { - throw new InvalidOperationException($"Failed to allocate {count} bindless descriptors"); - } - - var descriptors = new BindlessDescriptor[count]; - for (uint i = 0; i < count; i++) - { - var index = baseIndex + i; - var cpuHandle = _bindlessHeap.GetCpuHandle(index); - var gpuHandle = _bindlessHeap.GetGpuHandle(index); - descriptors[i] = new BindlessDescriptor(index, cpuHandle, gpuHandle); - } - - return descriptors; - } - - /// - /// Releases a bindless descriptor. - /// - public void ReleaseBindless(BindlessDescriptor descriptor) - { - ObjectDisposedException.ThrowIf(_disposed, this); - - if (descriptor is BindlessDescriptor d3d12Descriptor) - { - _bindlessHeap.ReleaseDescriptor(d3d12Descriptor.Index); - } - } - - /// - /// Releases multiple bindless descriptors. - /// - public void ReleaseBindless(BindlessDescriptor[] descriptors) - { - ObjectDisposedException.ThrowIf(_disposed, this); - - foreach (var descriptor in descriptors) - { - ReleaseBindless(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 bindless heap for SM 6.6 bindless rendering. - /// - public ConstPtr GetBindlessHeap() => _bindlessHeap.BindlessHeap; - - /// - /// Gets the shader visible heaps that need to be bound to the command list. - /// - public ConstPtr[] GetShaderVisibleHeaps() - { - return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap]; - } - - /// - /// Gets the shader visible heaps including bindless heap for SM 6.6 rendering. - /// - public ConstPtr[] GetShaderVisibleHeapsWithBindless() - { - return [_bindlessHeap.BindlessHeap, _srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap]; - } - - #endregion - - public void Dispose() - { - if (_disposed) - { - return; - } - - _rtvHeap.Dispose(); - _dsvHeap.Dispose(); - _srvHeap.Dispose(); - _samplerHeap.Dispose(); - _bindlessHeap.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 index c81da79..54a7a76 100644 --- a/Ghost.Graphics/D3D12/Descriptors.cs +++ b/Ghost.Graphics/D3D12/Descriptors.cs @@ -2,129 +2,131 @@ using Win32.Graphics.Direct3D12; namespace Ghost.Graphics.D3D12; -/// -/// Base class descriptor implementations. -/// -public 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; - } -} - /// /// Implementation of render target view (RTV) descriptor. /// -public sealed class RenderTargetDescriptor : Descriptor +public readonly struct RenderTargetDescriptor { - private readonly CpuDescriptorHandle _cpuHandle; - - public RenderTargetDescriptor(uint index, CpuDescriptorHandle cpuHandle) - : base(index, false) + public uint Index { - _cpuHandle = cpuHandle; + get; } - public override CpuDescriptorHandle CpuHandle => _cpuHandle; - public override GpuDescriptorHandle GpuHandle => default; + public CpuDescriptorHandle CpuHandle + { + get; + } + + public RenderTargetDescriptor(uint index, CpuDescriptorHandle cpuHandle) + { + Index = index; + CpuHandle = cpuHandle; + } } /// /// Implementation of depth stencil view (DSV) descriptor. /// -public sealed class DepthStencilDescriptor : Descriptor +public readonly struct DepthStencilDescriptor { - private readonly CpuDescriptorHandle _cpuHandle; - - public DepthStencilDescriptor(uint index, CpuDescriptorHandle cpuHandle) - : base(index, false) // DSVs are not shader visible + public uint Index { - _cpuHandle = cpuHandle; + get; } - public override CpuDescriptorHandle CpuHandle => _cpuHandle; - public override GpuDescriptorHandle GpuHandle => default; + public CpuDescriptorHandle CpuHandle + { + get; + } + + public DepthStencilDescriptor(uint index, CpuDescriptorHandle cpuHandle) + { + Index = index; + CpuHandle = cpuHandle; + } } /// /// Implementation of shader resource view (SRV) descriptor. /// -public sealed class ShaderResourceDescriptor : Descriptor +public sealed class ShaderResourceDescriptor { - private readonly CpuDescriptorHandle _cpuHandle; - private readonly GpuDescriptorHandle _gpuHandle; - - public ShaderResourceDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle) - : base(index, true) + public uint Index { - _cpuHandle = cpuHandle; - _gpuHandle = gpuHandle; + get; } - public override CpuDescriptorHandle CpuHandle => _cpuHandle; - public override GpuDescriptorHandle GpuHandle => _gpuHandle; + public CpuDescriptorHandle CpuHandle + { + get; + } + + public GpuDescriptorHandle GpuHandle + { + get; + } + + public ShaderResourceDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle) + { + Index = index; + CpuHandle = cpuHandle; + GpuHandle = gpuHandle; + } } /// /// Implementation of sampler descriptor. /// -public sealed class SamplerDescriptor : Descriptor +public sealed class SamplerDescriptor { - private readonly CpuDescriptorHandle _cpuHandle; - private readonly GpuDescriptorHandle _gpuHandle; - - public SamplerDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle) - : base(index, true) + public uint Index { - _cpuHandle = cpuHandle; - _gpuHandle = gpuHandle; + get; } - public override CpuDescriptorHandle CpuHandle => _cpuHandle; - public override GpuDescriptorHandle GpuHandle => _gpuHandle; + public CpuDescriptorHandle CpuHandle + { + get; + } + + public GpuDescriptorHandle GpuHandle + { + get; + } + + public SamplerDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle) + { + Index = index; + CpuHandle = cpuHandle; + GpuHandle = gpuHandle; + } } /// /// Implementation of bindless descriptor for SM 6.6 rendering. /// This descriptor maintains a 1:1 relationship between allocation indices and shader indices. /// -public sealed class BindlessDescriptor : Descriptor +public sealed class BindlessDescriptor { - private readonly CpuDescriptorHandle _cpuHandle; - private readonly GpuDescriptorHandle _gpuHandle; - - public BindlessDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle) - : base(index, true) + public uint Index { - _cpuHandle = cpuHandle; - _gpuHandle = gpuHandle; + get; } - public override CpuDescriptorHandle CpuHandle => _cpuHandle; - public override GpuDescriptorHandle GpuHandle => _gpuHandle; + public CpuDescriptorHandle CpuHandle + { + get; + } + + public GpuDescriptorHandle GpuHandle + { + get; + } + + public BindlessDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle) + { + Index = index; + CpuHandle = cpuHandle; + GpuHandle = gpuHandle; + } } \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/Utilities/BindlessDescriptorHeapAllocator.cs b/Ghost.Graphics/D3D12/Utilities/BindlessDescriptorHeapAllocator.cs index 489a311..9c48053 100644 --- a/Ghost.Graphics/D3D12/Utilities/BindlessDescriptorHeapAllocator.cs +++ b/Ghost.Graphics/D3D12/Utilities/BindlessDescriptorHeapAllocator.cs @@ -14,7 +14,7 @@ internal unsafe struct BindlessDescriptorHeapAllocator : IDisposable { private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u; - private readonly ConstPtr _device; + private readonly ComPtr _device; private readonly Lock _lock = new(); private ComPtr _bindlessHeap; @@ -42,12 +42,14 @@ internal unsafe struct BindlessDescriptorHeapAllocator : IDisposable public readonly ConstPtr BindlessHeap => new(_bindlessHeap.Get()); - public BindlessDescriptorHeapAllocator(ConstPtr device, uint numDescriptors = 10000) + public BindlessDescriptorHeapAllocator(ComPtr device, uint numDescriptors = 10000) { _device = device; + device.Get()->AddRef(); + HeapType = DescriptorHeapType.CbvSrvUav; NumDescriptors = numDescriptors; - _stride = device.Ptr->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav); + _stride = device.Get()->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav); _freeDescriptors = new Queue(); var success = AllocateResources(numDescriptors); @@ -169,7 +171,7 @@ internal unsafe struct BindlessDescriptorHeapAllocator : IDisposable fixed (void* heapPtr = &_bindlessHeap) { - var hr = _device.Ptr->CreateDescriptorHeap(&heapDesc, __uuidof(), (void**)heapPtr); + var hr = _device.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(), (void**)heapPtr); if (hr.Failure) { return false; @@ -204,7 +206,7 @@ internal unsafe struct BindlessDescriptorHeapAllocator : IDisposable // Copy old descriptors to new heap if (oldHeap.Get() is not null) { - _device.Ptr->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType); + _device.Get()->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType); } return true; diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs b/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs index 159e35c..6983714 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs @@ -7,6 +7,8 @@ namespace Ghost.Graphics.D3D12.Utilities; internal unsafe static class D3D12PipelineResource { + public const int BACK_BUFFER_COUNT = 2; + private readonly static InputElementDescription[] s_inputElementDescs = [ new InputElementDescription{ SemanticName = Vertex.Semantic.PositionName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new InputElementDescription{ SemanticName = Vertex.Semantic.NormalName, SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, diff --git a/Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs b/Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs index 04691ba..1e88778 100644 --- a/Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs +++ b/Ghost.Graphics/D3D12/Utilities/DescriptorHeapAllocator.cs @@ -1,5 +1,4 @@ -using Ghost.Core; -using System.Diagnostics; +using System.Diagnostics; using System.Numerics; using Win32; using Win32.Graphics.Direct3D12; @@ -11,8 +10,7 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable { private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u; - private readonly ConstPtr _device; - private readonly Lock _lock = new(); + private ComPtr _device; private ComPtr _heap; private ComPtr _shaderVisibleHeap; @@ -22,6 +20,8 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable private DescriptorIndex _searchStart; private bool[] _allocatedDescriptors = []; + private readonly Lock _lock = new(); + public DescriptorHeapType HeapType { get; @@ -47,16 +47,18 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable get; } - public readonly ConstPtr Heap => new(_heap.Get()); - public readonly ConstPtr ShaderVisibleHeap => new(_shaderVisibleHeap.Get()); + public readonly ID3D12DescriptorHeap* Heap => _heap.Get(); + public readonly ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.Get(); - public DescriptorHeapAllocator(string name, ConstPtr device, DescriptorHeapType type, uint numDescriptors) + public DescriptorHeapAllocator(string name, ComPtr device, DescriptorHeapType type, uint numDescriptors) { _device = device; + device.Get()->AddRef(); + HeapType = type; NumDescriptors = numDescriptors; ShaderVisible = type == DescriptorHeapType.CbvSrvUav || type == DescriptorHeapType.Sampler; - Stride = device.Ptr->GetDescriptorHandleIncrementSize(type); + Stride = device.Get()->GetDescriptorHandleIncrementSize(type); var success = AllocateResources(numDescriptors); Debug.Assert(success); @@ -172,7 +174,7 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable public void CopyToShaderVisibleHeap(DescriptorIndex index, uint count = 1) { - _device.Ptr->CopyDescriptorsSimple(count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType); + _device.Get()->CopyDescriptorsSimple(count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType); } private bool AllocateResources(uint numDescriptors) @@ -191,7 +193,7 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable fixed (void* heapPtr = &_heap) { - var hr = _device.Ptr->CreateDescriptorHeap(&heapDesc, __uuidof(), (void**)heapPtr); + var hr = _device.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(), (void**)heapPtr); if (hr.Failure) { return false; @@ -207,7 +209,7 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable fixed (void* heapPtr = &_shaderVisibleHeap) { - var hr = _device.Ptr->CreateDescriptorHeap(&heapDesc, __uuidof(), (void**)heapPtr); + var hr = _device.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(), (void**)heapPtr); if (hr.Failure) { return false; @@ -233,11 +235,11 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable return false; } - _device.Ptr->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType); + _device.Get()->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType); if (_shaderVisibleHeap.Get() is not null) { - _device.Ptr->CopyDescriptorsSimple(oldSize, _startCpuHandleShaderVisible, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType); + _device.Get()->CopyDescriptorsSimple(oldSize, _startCpuHandleShaderVisible, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType); } return true; @@ -246,6 +248,8 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable /// public void Dispose() { + _device.Dispose(); + _heap.Dispose(); _shaderVisibleHeap.Dispose(); } diff --git a/Ghost.Graphics/D3D12_ALLOCATOR_IMPROVEMENTS.md b/Ghost.Graphics/D3D12_ALLOCATOR_IMPROVEMENTS.md deleted file mode 100644 index e69de29..0000000 diff --git a/Ghost.Graphics/Shading/CBufferCache.cs b/Ghost.Graphics/Data/CBufferCache.cs similarity index 96% rename from Ghost.Graphics/Shading/CBufferCache.cs rename to Ghost.Graphics/Data/CBufferCache.cs index 64ebb40..e94acd5 100644 --- a/Ghost.Graphics/Shading/CBufferCache.cs +++ b/Ghost.Graphics/Data/CBufferCache.cs @@ -4,7 +4,7 @@ using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Helpers; using System.Runtime.CompilerServices; -namespace Ghost.Graphics.Shading; +namespace Ghost.Graphics.Data; internal struct CBufferCache : IDisposable { diff --git a/Ghost.Graphics/Data/Material.cs b/Ghost.Graphics/Data/Material.cs index b8236b7..d678c25 100644 --- a/Ghost.Graphics/Data/Material.cs +++ b/Ghost.Graphics/Data/Material.cs @@ -1,5 +1,5 @@ using Ghost.Graphics.D3D12; -using Ghost.Graphics.Shading; +using Ghost.Graphics.RHI; using System.Numerics; using System.Runtime.CompilerServices; using Win32.Graphics.Direct3D12; @@ -12,10 +12,11 @@ namespace Ghost.Graphics.Data; public unsafe class Material : IDisposable { private readonly CBufferCache[] _cbufferCaches; - private readonly List _textures = new(); private bool _disposed; + internal ReadOnlySpan CBufferCaches => _cbufferCaches; + public Shader Shader { get; set; @@ -129,23 +130,12 @@ public unsafe class Material : IDisposable SetMatrix(Shader.GetPropertyId(propertyName), in value); } - /// - /// Adds a bindless texture to the material and returns its index - /// - /// The bindless texture to add - /// The index of the texture in the material's texture list - public int AddTexture(Texture2D texture) - { - _textures.Add(texture); - return _textures.Count - 1; - } - /// /// Sets a texture index for a shader property (for bindless texture access) /// /// The name of the shader property (e.g., "_TextureIndex1") /// The bindless texture to reference - public void SetTextureIndex(string propertyName, Texture2D texture) + public void SetTexture(string propertyName, Texture2D texture) { SetUInt(propertyName, texture.DescriptorIndex); } @@ -156,18 +146,14 @@ public unsafe class Material : IDisposable /// The mesh whose buffer indices to set /// The name of the vertex buffer index property (e.g., "_VertexBufferIndex") /// The name of the index buffer index property (e.g., "_IndexBufferIndex") - public void SetMeshBufferIndices(Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex") + public void SetMeshBuffer(Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex") { SetUInt(vertexBufferIndexProperty, mesh.VertexBufferDescriptorIndex); SetUInt(indexBufferIndexProperty, mesh.IndexBufferDescriptorIndex); } - /// - /// Gets all textures used by this material - /// - public IReadOnlyList Textures => _textures; - - private unsafe void WriteToCache(int propertyId, in T value) where T : unmanaged + private unsafe void WriteToCache(int propertyId, in T value) + where T : unmanaged { if (propertyId == -1) { @@ -177,7 +163,7 @@ public unsafe class Material : IDisposable var propInfo = Shader.Properties[propertyId]; if (propInfo.Size != sizeof(T)) { - throw new ArgumentException($"Property '{propInfo.Name}' has a size mismatch. Expected {sizeof(T)} bytes, but got {propInfo.Size} bytes."); + throw new ArgumentException($"Property '{propInfo.Name}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes."); } var cache = _cbufferCaches[propInfo.CBufferIndex]; @@ -196,37 +182,6 @@ public unsafe class Material : IDisposable } } - /// - /// Binds the material for bindless rendering - /// - /// Command list to bind to - internal void Bind(CommandList cmd) - { - var commandList = cmd.NativeCommandList.Ptr; - - // Set root signature and pipeline state - commandList->SetGraphicsRootSignature(Shader.RootSignature); - commandList->SetPipelineState(Shader.PipelineState); - - // Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6 - var heaps = stackalloc ID3D12DescriptorHeap*[2]; - heaps[0] = GraphicsPipeline.DescriptorAllocator.GetBindlessHeap().Ptr; // Specialized bindless heap - heaps[1] = Shader.SamplerHeap.Ptr; // Sampler heap from shader - commandList->SetDescriptorHeaps(2, heaps); - - // Bind constant buffers - var rootParamIndex = 0u; - foreach (var cbufferInfo in Shader.ConstantBuffers) - { - var cache = _cbufferCaches[cbufferInfo.RegisterSlot]; - commandList->SetGraphicsRootConstantBufferView(rootParamIndex++, cache.GpuResource.GPUAddress); - } - - // Bind sampler descriptor table (last root parameter) - var samplerGpuHandle = Shader.SamplerHeap.Ptr->GetGPUDescriptorHandleForHeapStart(); - commandList->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle); - } - public void Dispose() { if (_disposed) diff --git a/Ghost.Graphics/Data/ResourceHandle.cs b/Ghost.Graphics/Data/ResourceHandle.cs index e14baca..a7e4a58 100644 --- a/Ghost.Graphics/Data/ResourceHandle.cs +++ b/Ghost.Graphics/Data/ResourceHandle.cs @@ -3,7 +3,7 @@ using Win32.Graphics.D3D12MemoryAllocator; namespace Ghost.Graphics.Data; -internal readonly struct ResourceHandle : IEquatable, IDisposable +public readonly struct ResourceHandle : IEquatable, IDisposable { private const int _INVALID_ID = -1; @@ -28,7 +28,7 @@ internal readonly struct ResourceHandle : IEquatable, IDisposabl throw new InvalidOperationException("Cannot get allocation from an invalid AllocationHandle."); } - return GraphicsPipeline.ResourceAllocator.GetAllocation(this); + return GraphicsPipeline.ResourceAllocator.GetResource(this); } public bool Equals(ResourceHandle other) @@ -51,7 +51,7 @@ internal readonly struct ResourceHandle : IEquatable, IDisposabl public void Dispose() { - GraphicsPipeline.ResourceAllocator.ReleaseAllocation(this); + GraphicsPipeline.ResourceAllocator.ReleaseResource(this); } public static implicit operator Allocation(ResourceHandle handle) @@ -79,8 +79,7 @@ public readonly struct TextureHandle : IEquatable, IDisposable { private readonly ResourceHandle _resourceHandle; - internal ResourceHandle ResourceHandle => _resourceHandle; - + public ResourceHandle ResourceHandle => _resourceHandle; public static TextureHandle Invalid => new(ResourceHandle.Invalid); internal TextureHandle(ResourceHandle resourceHandle) @@ -125,8 +124,7 @@ public readonly struct BufferHandle : IEquatable, IDisposable { private readonly ResourceHandle _resourceHandle; - internal ResourceHandle ResourceHandle => _resourceHandle; - + public ResourceHandle ResourceHandle => _resourceHandle; public static BufferHandle Invalid => new(ResourceHandle.Invalid); internal BufferHandle(ResourceHandle resourceHandle) diff --git a/Ghost.Graphics/Data/Shader.cs b/Ghost.Graphics/Data/Shader.cs new file mode 100644 index 0000000..28cc2a0 --- /dev/null +++ b/Ghost.Graphics/Data/Shader.cs @@ -0,0 +1,116 @@ +namespace Ghost.Graphics.Data; + +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 + { + get; init; + } + + public uint CBufferIndex + { + get; init; + } + + public uint ByteOffset + { + get; init; + } + + public uint Size + { + get; init; + } +} + +internal readonly struct CBufferInfo +{ + public required string Name + { + get; init; + } + + public uint Size + { + get; init; + } + + public uint RegisterSlot + { + get; init; + } +} + +/// +/// Bindless shader implementation using SM 6.6 with ResourceDescriptorHeap +/// and D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED +/// Enhanced to support both bindless and regular texture binding for hybrid materials +/// +public unsafe class Shader : IDisposable +{ + private readonly string _source; + + private readonly List _constantBuffers = new(); + private readonly List _properties = new(); + private readonly List _regularTextures = new(); // Add regular texture support + private readonly Dictionary _propertyNameToIdMap = new(); + + private bool _disposed; + + internal string Source => _source; + + internal List ConstantBuffers => _constantBuffers; + internal List Properties => _properties; + internal List RegularTextures => _regularTextures; + internal Dictionary PropertyNameToIdMap => _propertyNameToIdMap; + + internal Shader(string shaderCode) + { + _source = shaderCode; + } + + /// + /// Gets a unique, stable ID for a shader property. + /// + /// 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; + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _constantBuffers.Clear(); + _properties.Clear(); + _propertyNameToIdMap.Clear(); + _regularTextures.Clear(); + + GC.SuppressFinalize(this); + + _disposed = true; + } +} \ No newline at end of file diff --git a/Ghost.Graphics/Documentation/bindless-in-ghost.md b/Ghost.Graphics/Documentation/bindless-in-ghost.md deleted file mode 100644 index 4f17db1..0000000 --- a/Ghost.Graphics/Documentation/bindless-in-ghost.md +++ /dev/null @@ -1,317 +0,0 @@ -# Bindless Rendering in Ghost Engine: A Technical Deep-Dive - -This document provides a detailed explanation of the bindless rendering architecture implemented in the Ghost Engine, leveraging modern Direct3D 12 features. - -## 1. Introduction to Bindless Rendering - -Traditional rendering approaches require the CPU to explicitly bind resources (textures, buffers, etc.) to specific "slots" in the shader pipeline before a draw call. This process, known as "binding," involves creating descriptor tables, setting them on the command list, and managing resource state transitions. While functional, this can lead to significant CPU overhead, complex state management, and a high number of draw calls. - -**Bindless rendering** revolutionizes this by moving resource selection from the CPU to the GPU. Instead of binding individual resources, we bind a single, massive descriptor heap containing descriptors for (potentially) all resources. Shaders can then access any resource in this heap using a simple index, which can be passed via a constant buffer or calculated dynamically. - -**Key Advantages:** -- **Reduced CPU Overhead:** Eliminates the need for constant re-binding of resources and management of numerous descriptor tables. -- **Simplified Rendering Code:** Drastically simplifies the logic for drawing objects with different materials. -- **Increased GPU Autonomy:** Allows the GPU to fetch required data on its own, leading to more efficient execution. -- **Enables Advanced Techniques:** Opens the door for techniques like "fully bindless" rendering, where even vertex and index data are fetched manually in the vertex shader. - -The Ghost Engine implements a state-of-the-art bindless system using DirectX 12's Shader Model 6.6 capabilities. - -## 2. Core D3D12 Implementation - -The foundation of the engine's bindless architecture rests on a few key D3D12 features. - -### 2.1. The Bindless Root Signature - -The root signature defines how shaders access resources. For bindless, we use a special flag: `D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED`. This tells the GPU that shaders will be able to directly index the entire CBV/SRV/UAV descriptor heap. - -Here's how the root signature is created in `Ghost.Graphics\Shading\Shader.cs`: - -```csharp -// From: Ghost.Graphics\Shading\Shader.cs - -private void CreateBindlessRootSignature() -{ - // ... (parameter setup for constant buffers and samplers) - - // Create root signature with the modern flag - fixed (RootParameter1* rootParamsPtr = rootParameters) - { - var rootSignatureDesc = new RootSignatureDescription1 - { - // ... (parameters) - - // 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 - }; - - // ... (serialization and creation) - } -} -``` - -With this flag, the HLSL global `ResourceDescriptorHeap` becomes accessible, representing the entire heap of shader-visible resources. - -### 2.2. Resource Descriptors - -For a resource to be accessible in a bindless fashion, it needs a Shader Resource View (SRV) created in the global, shader-visible descriptor heap. This is handled by the `Mesh` and `Texture2D` classes. - -#### Texture2D Descriptors - -When a `Texture2D` is created, it allocates a "bindless descriptor" and creates an SRV for itself at that descriptor's location. - -```csharp -// From: Ghost.Graphics\Data\Texture2D.cs - -private BindlessDescriptor CreateBindlessDescriptors() -{ - var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; - - // Allocate bindless descriptor from the descriptor allocator - var bindlessDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless(); - - // Create the SRV description - var srvDesc = new ShaderResourceViewDescription - { - Format = Format, - ViewDimension = SrvDimension.Texture2D, - Texture2D = new Texture2DSrv { MipLevels = 1 }, - Shader4ComponentMapping = 0x1688 // D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING - }; - - // Create the SRV in the bindless heap - device->CreateShaderResourceView(_resource.NativeResource.Ptr, &srvDesc, bindlessDescriptor.CpuHandle); - - return bindlessDescriptor; -} -``` -The `bindlessDescriptor.Index` now holds the unique integer ID that shaders will use to access this texture. - -#### Mesh Buffer Descriptors (Fully Bindless) - -The engine takes bindless a step further by also making vertex and index buffers available as bindless resources. This enables a "fully bindless" or "meshlet-style" rendering approach where the Vertex Shader manually fetches its own data, bypassing the Input Assembler stage. - -The `Mesh` class creates SRVs for its vertex and index buffers, treating them as raw byte-addressable buffers. - -```csharp -// From: Ghost.Graphics\Data\Mesh.cs - -private void CreateBindlessDescriptors() -{ - // ... (null checks) - - _vertexBufferDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless(); - _indexBufferDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateBindless(); - - var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; - - // Create SRV for vertex buffer (as ByteAddressBuffer) - var vertexSrvDesc = new ShaderResourceViewDescription - { - Format = Format.R32Typeless, - ViewDimension = SrvDimension.Buffer, - // ... - Buffer = new() - { - // ... - Flags = BufferSrvFlags.Raw // ByteAddressBuffer - } - }; - device->CreateShaderResourceView(_vertexBuffer.NativeResource.Ptr, &vertexSrvDesc, _vertexBufferDescriptor.CpuHandle); - - // Create SRV for index buffer (as ByteAddressBuffer) - var indexSrvDesc = new ShaderResourceViewDescription - { - Format = Format.R32Typeless, - ViewDimension = SrvDimension.Buffer, - // ... - Buffer = new() - { - // ... - Flags = BufferSrvFlags.Raw // ByteAddressBuffer - } - }; - device->CreateShaderResourceView(_indexBuffer.NativeResource.Ptr, &indexSrvDesc, _indexBufferDescriptor.CpuHandle); -} -``` - -## 3. HLSL Shader Implementation - -With the D3D12 backend in place, accessing resources in HLSL becomes remarkably simple. The `MeshRenderPass.cs` file contains a perfect example of a fully bindless shader. - -### 3.1. The Constant Buffer - -First, we define a constant buffer to pass the resource indices from the CPU to the GPU. - -```hlsl -// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs - -cbuffer ConstantBuffer : register(b0) -{ - float4 _Color; - uint _TextureIndex1; - uint _TextureIndex2; - uint _TextureIndex3; - uint _TextureIndex4; - uint _VertexBufferIndex; - uint _IndexBufferIndex; -}; -``` - -### 3.2. Vertex Shader: Manual Vertex Fetching - -The vertex shader (`VSMain`) demonstrates the power of fully bindless rendering. Instead of receiving pre-fetched vertex attributes, it receives only a `vertexId` and `instanceId`. It uses the `_VertexBufferIndex` and `_IndexBufferIndex` to access the correct buffers from the global `ResourceDescriptorHeap` and manually loads the vertex data. - -```hlsl -// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs - -PixelInput VSMain(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID) -{ - // Get bindless buffers from the global heap - ByteAddressBuffer vertexBuffer = ResourceDescriptorHeap[_VertexBufferIndex]; - ByteAddressBuffer indexBuffer = ResourceDescriptorHeap[_IndexBufferIndex]; - - // Each instance represents one triangle - // vertexId is 0, 1, or 2 - - // Calculate index into the index buffer to find the vertex index - uint indexOffset = (instanceId * 3 + vertexId) * 4; // 4 bytes per index - uint vertexIndex = indexBuffer.Load(indexOffset); - - // Calculate offset into the vertex buffer using the vertex index - uint vertexOffset = vertexIndex * 80; // 80 bytes per vertex (5 * float4) - - // Load vertex data from the bindless vertex buffer - Vertex vertex; - vertex.position = asfloat(vertexBuffer.Load4(vertexOffset + 0)); - vertex.normal = asfloat(vertexBuffer.Load4(vertexOffset + 16)); - // ... etc. - - // ... - return output; -} -``` - -### 3.3. Pixel Shader: Bindless Texture Sampling - -The pixel shader (`PSMain`) uses the same principle to access textures. It uses the `_TextureIndexN` values to grab the correct `Texture2D` from the `ResourceDescriptorHeap` and samples them. - -```hlsl -// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs - -float4 PSMain(PixelInput input) : SV_TARGET -{ - // Access textures directly from the heap using their indices - Texture2D tex1 = ResourceDescriptorHeap[_TextureIndex1]; - Texture2D tex2 = ResourceDescriptorHeap[_TextureIndex2]; - Texture2D tex3 = ResourceDescriptorHeap[_TextureIndex3]; - Texture2D tex4 = ResourceDescriptorHeap[_TextureIndex4]; - - // Sample the textures - float4 color1 = tex1.Sample(_MainSampler, input.uv.xy); - // ... etc. - - // Blend all textures together - float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f; - - return blendedColor * _Color; -} -``` - -## 4. C# Usage Example - -The C# side of the implementation is elegant and straightforward, abstracting the low-level details. The `MeshRenderPass` class demonstrates a typical setup. - -### 4.1. Initialization - -During initialization, we create the mesh and textures. The `UploadMeshData` and `UploadTextureData` calls ensure the data is on the GPU, and the underlying `Mesh` and `Texture2D` constructors have already created the necessary bindless SRVs. - -We then create a `Material` and use it to bridge the gap between the C# objects and the shader's constant buffer properties. - -```csharp -// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs - -public void Initialize(CommandList cmd) -{ - _mesh = MeshBuilder.CreateCube(0.75f); - _mesh.UploadMeshData(); - - _shader = new Shader(_HLSL_SOURCE); - _material = new Material(_shader); - - // Load textures - _textures = new Texture2D[_textureFiles.Length]; - for (var i = 0; i < _textureFiles.Length; i++) - { - _textures[i] = Texture2D.FromFile(_textureFiles[i]); - _textures[i].UploadTextureData(); - } - - // Set material properties, passing the descriptor indices to the shader - _material.SetVector("_Color", new Vector4(1.0f, 1.0f, 1.0f, 1.0f)); - for (var i = 0; i < _textures.Length; i++) - { - var texture = _textures[i]; - // This sets the uint property in the CBuffer - _material.SetTextureIndex($"_TextureIndex{i + 1}", texture); - } - - // This is a helper on Material that sets _VertexBufferIndex and _IndexBufferIndex - // It is not used in the provided code, but it's the intended way. - // For this example, the indices are set implicitly by the DrawMeshBindless call. - // material.SetMeshBufferIndices(mesh); - - // Uploads the constant buffer data to the GPU - _material.UploadMaterialData(); -} -``` -*Note: The `Material.SetTextureIndex` method is a convenient wrapper that calls `SetUInt` internally, passing the `texture.DescriptorIndex`.* - -### 4.2. Execution - -Executing the render pass is now incredibly simple. The `DrawMeshBindless` command takes the mesh and material, binds the necessary state (Root Signature, PSO, and the material's constant buffer), and issues a single `DrawInstanced` call. - -```csharp -// From: Ghost.Graphics\RenderPasses\MeshRenderPass.cs - -public void Execute(CommandList cmd) -{ - // This single call handles everything! - cmd.DrawMeshBindless(_mesh!, _material!); -} -``` - -The `DrawMeshBindless` method (in `CommandList.cs`) is where the magic happens. It doesn't need to set vertex or index buffers on the Input Assembler. It just needs to know the number of triangles to draw. - -```csharp -// From: Ghost.Graphics\D3D12\CommandList.cs (Conceptual) - -public void DrawMeshBindless(Mesh mesh, Material material) -{ - // 1. Bind the material (sets PSO, Root Signature, CBuffers, Samplers) - material.Bind(this); - - // 2. Set the mesh buffer indices on the material - // This is the crucial step that connects the mesh to the shader - material.SetMeshBufferIndices(mesh); - material.UploadMaterialData(); // Re-upload CBuffer with mesh indices - - // 3. Draw instanced. - // - VertexCountPerInstance = 3 (for a triangle) - // - InstanceCount = total number of triangles in the mesh - // - StartVertexLocation = 0 - // - StartInstanceLocation = 0 - uint triangleCount = mesh.IndexCount / 3; - _commandList.Get()->DrawInstanced(3, triangleCount, 0, 0); -} -``` - -## 5. Conclusion - -The bindless architecture in the Ghost Engine is a powerful, modern, and efficient way to handle rendering. By leveraging Shader Model 6.6, it significantly reduces CPU overhead and simplifies rendering logic, paving the way for more complex and dynamic scenes. The "fully bindless" approach for mesh data further enhances this paradigm, offering maximum flexibility and performance on the GPU. diff --git a/Ghost.Graphics/Examples/ModernRenderingExample.cs b/Ghost.Graphics/Examples/ModernRenderingExample.cs deleted file mode 100644 index e717ac2..0000000 --- a/Ghost.Graphics/Examples/ModernRenderingExample.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Ghost.Graphics; -using Ghost.Graphics.RHI; -using Ghost.Graphics.D3D12; - -namespace Ghost.Graphics.Examples; - -/// -/// Example showing how to use the new RHI architecture -/// -public class ModernRenderingExample -{ - private IRenderDevice _device; - private RenderSystem _renderSystem; - private IRenderer _forwardRenderer; - private ISwapChain _swapChain; - - public void Initialize() - { - // Create modern RHI device - _device = new D3D12RenderDevice(); - - // Create render system for frame management - _renderSystem = new RenderSystem(_device); - - // Create a renderer using RHI abstractions - _forwardRenderer = new D3D12Renderer(_device); - - // Create swap chain for presentation - var swapChainDesc = new SwapChainDesc - { - Width = 1920, - Height = 1080, - BufferCount = 2, - Format = TextureFormat.R8G8B8A8_UNorm, - // Presenter would be set based on your window system - }; - _swapChain = _device.CreateSwapChain(swapChainDesc); - - // Configure renderer - _forwardRenderer.SetSwapChain(_swapChain); - - // Register renderer with render system - _renderSystem.AddRenderer(_forwardRenderer); - - // Start rendering loop - _renderSystem.Start(); - } - - public void RenderOffscreen() - { - // Example of rendering to off-screen color target - var colorRenderTarget = RenderTargetDesc.Color(1024, 1024, TextureFormat.R8G8B8A8_UNorm); - var offscreenColorTarget = _device.CreateRenderTarget(colorRenderTarget); - - var colorRenderer = new D3D12Renderer(_device); - colorRenderer.SetRenderTarget(offscreenColorTarget); - _renderSystem.AddRenderer(colorRenderer); - - // Example of rendering to depth target - var depthRenderTarget = RenderTargetDesc.Depth(1024, 1024, TextureFormat.D24_UNorm_S8_UInt); - var offscreenDepthTarget = _device.CreateRenderTarget(depthRenderTarget); - - var depthRenderer = new D3D12Renderer(_device); - depthRenderer.SetRenderTarget(offscreenDepthTarget); - _renderSystem.AddRenderer(depthRenderer); - } - - public void Update() - { - // Signal that CPU work is ready - _renderSystem.SignalCPUReady(); - - // Wait for GPU to complete previous frame (optional) - _renderSystem.WaitForGPUReady(16); // 16ms timeout for 60fps - } - - public void Dispose() - { - _renderSystem?.Stop(); - _renderSystem?.Dispose(); - - _forwardRenderer?.Dispose(); - _swapChain?.Dispose(); - _device?.Dispose(); - } -} - -/// -/// Example showing legacy vs modern usage -/// -public static class LegacyVsModernExample -{ - public static void LegacyApproach() - { - // OLD WAY - tightly coupled to D3D12 - GraphicsPipeline.Initialize(); - - var graphicsDevice = GraphicsPipeline.GraphicsDevice; - // Renderer creation and management handled internally - // Frame synchronization handled by GraphicsPipeline - - GraphicsPipeline.Start(); - GraphicsPipeline.SignalCPUReady(); - GraphicsPipeline.WaitForGPUReady(); - } - - public static void ModernApproach() - { - // NEW WAY - clean RHI abstractions - var device = new D3D12RenderDevice(); - var renderSystem = new RenderSystem(device); - - var renderer = new D3D12Renderer(device); - var swapChain = device.CreateSwapChain(new SwapChainDesc { /* ... */ }); - - renderer.SetSwapChain(swapChain); - renderSystem.AddRenderer(renderer); - - renderSystem.Start(); - renderSystem.SignalCPUReady(); - renderSystem.WaitForGPUReady(); - } -} diff --git a/Ghost.Graphics/Examples/OffScreenRenderingExample.cs b/Ghost.Graphics/Examples/OffScreenRenderingExample.cs deleted file mode 100644 index e69de29..0000000 diff --git a/Ghost.Graphics/GraphicsPipeline.cs b/Ghost.Graphics/GraphicsPipeline.cs index e003ec7..8ba53a3 100644 --- a/Ghost.Graphics/GraphicsPipeline.cs +++ b/Ghost.Graphics/GraphicsPipeline.cs @@ -16,12 +16,12 @@ public static class GraphicsPipeline internal const uint _FRAME_COUNT = 2; #if DEBUG - private static DebugLayer? s_debugLayer; + private static D3D12DebugLayer? s_debugLayer; #endif private static GraphicsDevice? s_graphicsDevice; - private static DescriptorAllocator? s_descriptorAllocator; - private static D3D12ResourceAllocator? s_resourceAllocator; + private static D3D12DescriptorAllocator? s_descriptorAllocator; + private static D3D12DescriptorAllocator? s_resourceAllocator; private static ResourceUploadBatch? s_uploadBatch; // New RHI-based device for modern usage @@ -31,8 +31,8 @@ public static class GraphicsPipeline private static bool s_initialized; internal static GraphicsDevice GraphicsDevice => s_graphicsDevice ?? throw new InvalidOperationException("Graphics device is not initialized."); - internal static D3D12ResourceAllocator 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 D3D12DescriptorAllocator ResourceAllocator => s_resourceAllocator ?? throw new InvalidOperationException("Resource allocator is not initialized."); + internal static D3D12DescriptorAllocator DescriptorAllocator => s_descriptorAllocator ?? throw new InvalidOperationException("Descriptor allocator is not initialized."); /// /// Gets the modern RHI render device - prefer this over legacy GraphicsDevice @@ -61,7 +61,7 @@ public static class GraphicsPipeline internal static unsafe void Initialize() { #if DEBUG - s_debugLayer = new DebugLayer(); + s_debugLayer = new D3D12DebugLayer(); #endif // Initialize legacy components for compatibility s_graphicsDevice = new GraphicsDevice(); diff --git a/Ghost.Graphics/REFACTORING_SUMMARY.md b/Ghost.Graphics/REFACTORING_SUMMARY.md deleted file mode 100644 index e69de29..0000000 diff --git a/Ghost.Graphics/RHI/ICommandBuffer.cs b/Ghost.Graphics/RHI/ICommandBuffer.cs index e11a311..55c7bd4 100644 --- a/Ghost.Graphics/RHI/ICommandBuffer.cs +++ b/Ghost.Graphics/RHI/ICommandBuffer.cs @@ -10,36 +10,36 @@ public interface ICommandBuffer : IDisposable /// /// Begins recording commands into this command buffer /// - void Begin(); + public void Begin(); /// /// Ends recording commands and prepares for submission /// - void End(); + public void End(); /// /// Begins a render pass with the specified render target /// /// Render target to render into /// Color to clear the render target with - void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor); + public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor); /// /// Ends the current render pass /// - void EndRenderPass(); + public void EndRenderPass(); /// /// Sets the viewport for rendering /// /// Viewport to set - void SetViewport(ViewportDesc viewport); + public void SetViewport(ViewportDesc viewport); /// /// Sets the scissor rectangle /// /// Scissor rectangle to set - void SetScissorRect(RectDesc rect); + public void SetScissorRect(RectDesc rect); /// /// Inserts a resource barrier for state transitions @@ -47,35 +47,26 @@ public interface ICommandBuffer : IDisposable /// Resource to transition /// Current resource state /// Target resource state - void ResourceBarrier(IResource resource, ResourceState before, ResourceState after); + public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after); /// /// Sets the graphics root signature /// /// Root signature to set - void SetGraphicsRootSignature(IRootSignature rootSignature); + public void SetGraphicsRootSignature(IRootSignature rootSignature); /// /// Sets the pipeline state object /// /// Pipeline state to set - void SetPipelineState(IPipelineState pipelineState); + public void SetPipelineState(IPipelineStateController pipelineState); /// - /// Sets descriptor heaps for bindless rendering + /// Renders the specified mesh using the provided material. /// - /// Descriptor heaps to set - void SetDescriptorHeaps(IDescriptorHeap[] heaps); - - /// - /// Draws indexed geometry - /// - /// Number of indices to draw - /// Number of instances to draw - /// Starting index location - /// Base vertex location - /// Starting instance location - void DrawIndexedInstanced(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0); + /// The mesh to be drawn. Must not be null. + /// The material to use for rendering the mesh. Must not be null. + public void DrawMesh(Mesh mesh, Material material); /// /// Dispatches compute threads @@ -83,7 +74,7 @@ public interface ICommandBuffer : IDisposable /// Thread groups in X dimension /// Thread groups in Y dimension /// Thread groups in Z dimension - void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1); + public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1); } /// @@ -129,7 +120,7 @@ public struct RectDesc } /// -/// D3D12-style resource states +/// Resource states /// public enum ResourceState { @@ -143,5 +134,5 @@ public enum ResourceState PixelShaderResource = 0x80, CopyDest = 0x400, CopySource = 0x800, - Present = 0 + Present = 0, } diff --git a/Ghost.Graphics/RHI/ICommandQueue.cs b/Ghost.Graphics/RHI/ICommandQueue.cs index 9cfe2f4..91a506c 100644 --- a/Ghost.Graphics/RHI/ICommandQueue.cs +++ b/Ghost.Graphics/RHI/ICommandQueue.cs @@ -8,7 +8,10 @@ public interface ICommandQueue : IDisposable /// /// Type of commands this queue can execute /// - CommandQueueType Type { get; } + CommandQueueType Type + { + get; + } /// /// Submits a single command buffer for execution @@ -20,7 +23,7 @@ public interface ICommandQueue : IDisposable /// Submits multiple command buffers for execution /// /// Command buffers to submit - void Submit(ICommandBuffer[] commandBuffers); + void Submit(params ReadOnlySpan commandBuffers); /// /// Signals a fence with the specified value diff --git a/Ghost.Graphics/RHI/IDescriptorAllocator.cs b/Ghost.Graphics/RHI/IDescriptorAllocator.cs deleted file mode 100644 index 69eccf5..0000000 --- a/Ghost.Graphics/RHI/IDescriptorAllocator.cs +++ /dev/null @@ -1,144 +0,0 @@ -namespace Ghost.Graphics.RHI; - -/// -/// D3D12-style descriptor allocator interface -/// -public interface IDescriptorAllocator : IDisposable -{ - /// - /// Allocates a render target view descriptor - /// - /// RTV descriptor handle - DescriptorHandle AllocateRTV(); - - /// - /// Allocates multiple render target view descriptors - /// - /// Number of descriptors to allocate - /// Array of RTV descriptor handles - DescriptorHandle[] AllocateRTVs(uint count); - - /// - /// Allocates a depth stencil view descriptor - /// - /// DSV descriptor handle - DescriptorHandle AllocateDSV(); - - /// - /// Allocates a shader resource view descriptor - /// - /// SRV descriptor handle - DescriptorHandle AllocateSRV(); - - /// - /// Allocates a sampler descriptor - /// - /// Sampler descriptor handle - DescriptorHandle AllocateSampler(); - - /// - /// Allocates a bindless descriptor for SM 6.6 rendering - /// - /// Bindless descriptor handle - DescriptorHandle AllocateBindless(); - - /// - /// Releases a render target view descriptor - /// - /// RTV descriptor to release - void ReleaseRTV(DescriptorHandle handle); - - /// - /// Releases a depth stencil view descriptor - /// - /// DSV descriptor to release - void ReleaseDSV(DescriptorHandle handle); - - /// - /// Releases a shader resource view descriptor - /// - /// SRV descriptor to release - void ReleaseSRV(DescriptorHandle handle); - - /// - /// Releases a sampler descriptor - /// - /// Sampler descriptor to release - void ReleaseSampler(DescriptorHandle handle); - - /// - /// Releases a bindless descriptor - /// - /// Bindless descriptor to release - void ReleaseBindless(DescriptorHandle handle); -} - -/// -/// D3D12-style descriptor heap interface -/// -public interface IDescriptorHeap : IDisposable -{ - /// - /// Type of descriptors this heap contains - /// - DescriptorHeapType Type { get; } - - /// - /// Maximum number of descriptors in this heap - /// - uint MaxDescriptors { get; } - - /// - /// Whether this heap is shader visible - /// - bool IsShaderVisible { get; } - - /// - /// Gets a CPU descriptor handle at the specified index - /// - /// Index of the descriptor - /// CPU descriptor handle - DescriptorHandle GetCPUHandle(uint index); - - /// - /// Gets a GPU descriptor handle at the specified index - /// - /// Index of the descriptor - /// GPU descriptor handle - DescriptorHandle GetGPUHandle(uint index); -} - -/// -/// Descriptor handle for D3D12-style descriptor management -/// -public struct DescriptorHandle : IEquatable -{ - public uint Index; - public bool IsValid; - - public DescriptorHandle(uint index) - { - Index = index; - IsValid = true; - } - - public static DescriptorHandle Invalid => new() { Index = uint.MaxValue, IsValid = false }; - - public bool Equals(DescriptorHandle other) => Index == other.Index && IsValid == other.IsValid; - public override bool Equals(object? obj) => obj is DescriptorHandle other && Equals(other); - public override int GetHashCode() => HashCode.Combine(Index, IsValid); - - public static bool operator ==(DescriptorHandle left, DescriptorHandle right) => left.Equals(right); - public static bool operator !=(DescriptorHandle left, DescriptorHandle right) => !left.Equals(right); -} - -/// -/// D3D12 descriptor heap types -/// -public enum DescriptorHeapType -{ - CBV_SRV_UAV, - Sampler, - RTV, - DSV -} diff --git a/Ghost.Graphics/RHI/IGraphicsEngine.cs b/Ghost.Graphics/RHI/IGraphicsEngine.cs new file mode 100644 index 0000000..3f81678 --- /dev/null +++ b/Ghost.Graphics/RHI/IGraphicsEngine.cs @@ -0,0 +1,30 @@ +namespace Ghost.Graphics.RHI; + +public interface IGraphicsEngine : IDisposable +{ + public IRenderDevice Device + { + get; + } + + public IResourceAllocator ResourceAllocator + { + get; + } + + public IRenderer CreateRenderer(); + + /// + /// Creates a command buffer for recording rendering commands + /// + /// Type of command buffer to create + /// A new command buffer instance + public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics); + + /// + /// Creates a swap chain for presentation + /// + /// Swap chain description + /// A new swap chain instance + public ISwapChain CreateSwapChain(SwapChainDesc desc); +} \ No newline at end of file diff --git a/Ghost.Graphics/RHI/IPipelineStateController.cs b/Ghost.Graphics/RHI/IPipelineStateController.cs new file mode 100644 index 0000000..84bb6b9 --- /dev/null +++ b/Ghost.Graphics/RHI/IPipelineStateController.cs @@ -0,0 +1,25 @@ +using Ghost.Graphics.Data; + +namespace Ghost.Graphics.RHI; + +public interface IShaderPipeline +{ + /// + /// Pipeline type + /// + PipelineType Type + { + get; + } +} + +public interface IPipelineStateController +{ + public void ColectionShader(ReadOnlySpan shaders); + + public void CompileCollected(); + + public void PreCookPipelineState(); + + public IShaderPipeline GetShaderPipeline(Shader shader); +} \ No newline at end of file diff --git a/Ghost.Graphics/RHI/IRenderDevice.cs b/Ghost.Graphics/RHI/IRenderDevice.cs index 87516fe..83207d4 100644 --- a/Ghost.Graphics/RHI/IRenderDevice.cs +++ b/Ghost.Graphics/RHI/IRenderDevice.cs @@ -8,7 +8,7 @@ public interface IRenderDevice : IDisposable /// /// Graphics command queue for rendering operations /// - ICommandQueue GraphicsQueue + public ICommandQueue GraphicsQueue { get; } @@ -16,7 +16,7 @@ public interface IRenderDevice : IDisposable /// /// Compute command queue for compute shader operations /// - ICommandQueue ComputeQueue + public ICommandQueue ComputeQueue { get; } @@ -24,32 +24,10 @@ public interface IRenderDevice : IDisposable /// /// Copy command queue for data transfer operations /// - ICommandQueue CopyQueue + public ICommandQueue CopyQueue { get; } - - /// - /// Gets the descriptor allocator for managing descriptors - /// - IDescriptorAllocator DescriptorAllocator - { - get; - } - - /// - /// Creates a command buffer for recording rendering commands - /// - /// Type of command buffer to create - /// A new command buffer instance - ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics); - - /// - /// Creates a swap chain for presentation - /// - /// Swap chain description - /// A new swap chain instance - ISwapChain CreateSwapChain(SwapChainDesc desc); } /// diff --git a/Ghost.Graphics/RHI/IRenderTypes.cs b/Ghost.Graphics/RHI/IRenderTypes.cs index 84a3007..279d68b 100644 --- a/Ghost.Graphics/RHI/IRenderTypes.cs +++ b/Ghost.Graphics/RHI/IRenderTypes.cs @@ -1,27 +1,7 @@ +using System.Runtime.CompilerServices; + namespace Ghost.Graphics.RHI; -/// -/// Pipeline state object interface -/// -public interface IPipelineState : IDisposable -{ - /// - /// Pipeline type (graphics or compute) - /// - PipelineType Type - { - get; - } - - /// - /// Pipeline name for debugging - /// - string Name - { - get; set; - } -} - /// /// Root signature interface /// @@ -54,48 +34,89 @@ public struct RenderTargetDesc /// /// Width of the render target /// - public uint Width; + public uint Width + { + get; + set; + } /// /// Height of the render target /// - public uint Height; + public uint Height + { + get; + set; + } /// - /// Type of render target (color or depth) + /// Slice of the render target /// - public RenderTargetType Type; + public uint Slice + { + get; + set; + } + + /// + /// Type of render target + /// + public RenderTargetType Type + { + get; + set; + } /// /// Target texture format /// - public TextureFormat Format; + public TextureFormat Format + { + get; + set; + } /// /// Texture dimension /// - public TextureDimension Dimension; + public TextureDimension Dimension + { + get; + set; + } /// /// Creation flags for the render target /// - public RenderTargetCreationFlags CreationFlags; + public RenderTargetCreationFlags CreationFlags + { + get; + set; + } /// /// Number of mip levels. 0 to generate full mip chain /// - public uint MipLevels; + public uint MipLevels + { + get; + set; + } /// /// Number of samples for MSAA /// - public uint SampleCount; + public uint SampleCount + { + get; + set; + } /// /// Creates a color render target /// - public static RenderTargetDesc Color(uint width, uint height, - TextureFormat format, TextureDimension dimension = TextureDimension.Texture2D, + public static RenderTargetDesc Color(uint width, uint height, uint slice = 1, + TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D, RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyScalable | RenderTargetCreationFlags.GenerateMips, uint mipLevels = 0u, uint sampleCount = 1) { @@ -103,6 +124,7 @@ public struct RenderTargetDesc { Width = width, Height = height, + Slice = slice, Type = RenderTargetType.Color, Format = format, Dimension = dimension, @@ -115,7 +137,7 @@ public struct RenderTargetDesc /// /// Creates a depth render target /// - public static RenderTargetDesc Depth(uint width, uint height, + public static RenderTargetDesc Depth(uint width, uint height, uint slice = 1, TextureFormat format = TextureFormat.D24_UNorm_S8_UInt, TextureDimension dimension = TextureDimension.Texture2D, RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyScalable, uint mipLevels = 0u, uint sampleCount = 1) @@ -124,6 +146,7 @@ public struct RenderTargetDesc { Width = width, Height = height, + Slice = slice, Type = RenderTargetType.Depth, Format = format, Dimension = dimension, @@ -132,6 +155,18 @@ public struct RenderTargetDesc SampleCount = sampleCount }; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TextureDesc ToTextureDescriptor(RenderTargetDesc desc) + { + var usage = desc.Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil; + if (desc.CreationFlags.HasFlag(RenderTargetCreationFlags.AllowUAV)) + { + usage |= TextureUsage.UnorderedAccess; + } + + return new TextureDesc(desc.Width, desc.Height, desc.Slice, desc.Format, desc.Dimension, desc.MipLevels, usage); + } } /// @@ -142,37 +177,73 @@ public struct TextureDesc /// /// Width of the texture /// - public uint Width; + public uint Width + { + get; + set; + } /// /// Height of the texture /// - public uint Height; + public uint Height + { + get; + set; + } + + /// + /// Slice of the texture + /// + public uint Slice + { + get; + set; + } /// /// Texture format /// - public TextureFormat Format; + public TextureFormat Format + { + get; + set; + } /// /// Texture dimension /// - public TextureDimension Dimension; + public TextureDimension Dimension + { + get; + set; + } /// /// Number of mip levels. 0 to generate full mip chain /// - public uint MipLevels; + public uint MipLevels + { + get; + set; + } /// /// Texture usage flags /// - public TextureUsage Usage; + public TextureUsage Usage + { + get; + set; + } - public TextureDesc(uint width, uint height, TextureFormat format, TextureDimension dimension = TextureDimension.Texture2D, uint mipLevels = 0u, TextureUsage usage = TextureUsage.ShaderResource) + public TextureDesc(uint width, uint height, uint slice = 1, + TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D, + uint mipLevels = 0u, TextureUsage usage = TextureUsage.ShaderResource) { Width = width; Height = height; + Slice = slice; Format = format; Dimension = dimension; MipLevels = mipLevels; @@ -188,17 +259,29 @@ public struct BufferDesc /// /// Size of the buffer in bytes /// - public ulong Size; + public ulong Size + { + get; + set; + } /// /// Buffer usage flags /// - public BufferUsage Usage; + public BufferUsage Usage + { + get; + set; + } /// /// Memory type for the buffer /// - public MemoryType MemoryType; + public MemoryType MemoryType + { + get; + set; + } public BufferDesc(ulong size, BufferUsage usage, MemoryType memoryType = MemoryType.Default) { diff --git a/Ghost.Graphics/RHI/IResource.cs b/Ghost.Graphics/RHI/IResource.cs index 3620c63..6aca2f8 100644 --- a/Ghost.Graphics/RHI/IResource.cs +++ b/Ghost.Graphics/RHI/IResource.cs @@ -73,27 +73,12 @@ public interface IBuffer : IResource /// Render target interface for rendering operations /// Supports either color OR depth rendering, not both /// -public interface IRenderTarget : IDisposable +public interface IRenderTarget : ITexture { - /// - /// Width of the render target - /// - uint Width { get; } - - /// - /// Height of the render target - /// - uint Height { get; } - /// /// Type of render target (color or depth) /// RenderTargetType Type { get; } - - /// - /// Gets the target texture (either color or depth based on Type) - /// - ITexture Target { get; } } /// diff --git a/Ghost.Graphics/RHI/IResourceFactory.cs b/Ghost.Graphics/RHI/IResourceAllocator.cs similarity index 56% rename from Ghost.Graphics/RHI/IResourceFactory.cs rename to Ghost.Graphics/RHI/IResourceAllocator.cs index c2b75d4..65512be 100644 --- a/Ghost.Graphics/RHI/IResourceFactory.cs +++ b/Ghost.Graphics/RHI/IResourceAllocator.cs @@ -2,40 +2,58 @@ namespace Ghost.Graphics.RHI; -public interface IResourceFactory +public interface IResourceAllocator { /// /// Creates a render target for off-screen rendering /// /// Render target description /// A new render target instance - public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc); + public IRenderTarget CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false); /// /// Creates a texture resource /// /// Texture description /// A new texture handle point to the resource - public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc); + public TextureHandle CreateTextureHandle(ref readonly TextureDesc desc, bool tempResource = false); /// /// Creates a texture resource /// /// Texture description /// A new texture instance - public ITexture CreateTexture(ref readonly TextureDesc desc); + public ITexture CreateTexture(ref readonly TextureDesc desc, bool tempResource = false); /// /// Creates a buffer resource /// /// Buffer description /// A new buffer handle point to the resource - public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc); + public BufferHandle CreateBufferHandle(ref readonly BufferDesc desc, bool tempResource = false); /// /// Creates a buffer resource /// /// Buffer description /// A new buffer instance - public IBuffer CreateBuffer(ref readonly BufferDesc desc); + public IBuffer CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false); + + /// + /// Release a resource given its handle + /// + /// Resource handle + public void ReleaseResource(ResourceHandle handle); +} + +internal interface IResourceAllocator : IResourceAllocator + where T : unmanaged +{ + /// + /// Get the raw gpu resource pointer from a resource handle + /// + /// The type of the resource. + /// Resource handle + /// Pointer to the resource + public unsafe T* GetResource(ResourceHandle handle); } \ No newline at end of file diff --git a/Ghost.Graphics/RHI/ISwapChain.cs b/Ghost.Graphics/RHI/ISwapChain.cs index 61e21ad..24b449f 100644 --- a/Ghost.Graphics/RHI/ISwapChain.cs +++ b/Ghost.Graphics/RHI/ISwapChain.cs @@ -26,13 +26,7 @@ public interface ISwapChain : IDisposable /// Gets the current back buffer texture /// /// Current back buffer texture - ITexture GetCurrentBackBuffer(); - - /// - /// Gets the current back buffer as a render target - /// - /// Current back buffer render target - IRenderTarget GetCurrentBackBufferRenderTarget(); + IRenderTarget GetCurrentBackBuffer(); /// /// Presents the rendered frame @@ -56,35 +50,29 @@ public struct SwapChainDesc /// /// Width of the swap chain /// - public uint Width; + public uint width; /// /// Height of the swap chain /// - public uint Height; + public uint height; /// /// Back buffer format /// - public TextureFormat Format; - - /// - /// Number of back buffers - /// - public uint BufferCount; + public TextureFormat format; /// /// Target for presentation (window handle or composition target) /// - public SwapChainTarget Target; + public SwapChainTarget target; public SwapChainDesc(uint width, uint height, SwapChainTarget target, TextureFormat format = TextureFormat.B8G8R8A8_UNorm, uint bufferCount = 2) { - Width = width; - Height = height; - Format = format; - BufferCount = bufferCount; - Target = target; + this.width = width; + this.height = height; + this.format = format; + this.target = target; } } diff --git a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs index c775889..8e0ab4c 100644 --- a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs +++ b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs @@ -1,7 +1,7 @@ using Ghost.Graphics.Contracts; using Ghost.Graphics.D3D12; using Ghost.Graphics.Data; -using Ghost.Graphics.Shading; +using Ghost.Graphics.RHI; using Ghost.Graphics.Utilities; using System.Numerics; @@ -113,7 +113,7 @@ float4 PSMain(PixelInput input) : SV_TARGET "C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg" ]; - public void Initialize(CommandList cmd) + public void Initialize(ICommandBuffer cmd) { _mesh = MeshBuilder.CreateCube(0.75f); _mesh.UploadMeshData(); @@ -132,14 +132,14 @@ float4 PSMain(PixelInput input) : SV_TARGET for (var i = 0; i < _textures.Length; i++) { var texture = _textures[i]; - _material.SetTextureIndex($"_TextureIndex{i + 1}", texture); + _material.SetTexture($"_TextureIndex{i + 1}", texture); } - _material.SetMeshBufferIndices(_mesh); + _material.SetMeshBuffer(_mesh); _material.UploadMaterialData(); } - public void Execute(CommandList cmd) + public void Execute(ICommandBuffer cmd) { cmd.DrawMesh(_mesh!, _material!); } diff --git a/Ghost.Graphics/RenderSystem.cs b/Ghost.Graphics/RenderSystem.cs index adbe00b..7d5075a 100644 --- a/Ghost.Graphics/RenderSystem.cs +++ b/Ghost.Graphics/RenderSystem.cs @@ -1,35 +1,40 @@ -using System.Collections.Immutable; using Ghost.Graphics.RHI; +using System.Collections.Immutable; namespace Ghost.Graphics; +public enum GraphicsAPI +{ + Direct3D12 +} + /// /// Application-level render system that orchestrates multiple renderers /// and handles frame synchronization /// -public class RenderSystem : IDisposable +internal class RenderSystem : IDisposable { private readonly struct FrameResource : IDisposable { - public readonly AutoResetEvent CpuReadyEvent; - public readonly AutoResetEvent GpuReadyEvent; + public readonly AutoResetEvent cpuReadyEvent; + public readonly AutoResetEvent gpuReadyEvent; public FrameResource() { - CpuReadyEvent = new(false); - GpuReadyEvent = new(true); + cpuReadyEvent = new(false); + gpuReadyEvent = new(true); } public void Dispose() { - CpuReadyEvent?.Dispose(); - GpuReadyEvent?.Dispose(); + cpuReadyEvent?.Dispose(); + gpuReadyEvent?.Dispose(); } } - private const uint FRAME_COUNT = 2; + private const uint _FRAME_COUNT = 2; - private readonly IRenderDevice _device; + private readonly IGraphicsEngine _engine; private readonly FrameResource[] _frameResources; private readonly Thread _renderThread; private readonly AutoResetEvent _shutdownEvent; @@ -41,19 +46,25 @@ public class RenderSystem : IDisposable private bool _isRunning; private bool _disposed; + public IGraphicsEngine GraphicsEngine => _engine; public uint CPUFenceValue => _cpuFenceValue; public uint GPUFenceValue => _gpuFenceValue; public bool IsRunning => _isRunning; - public RenderSystem(IRenderDevice device) + public RenderSystem(GraphicsAPI api) { - _device = device; + _engine = api switch + { + GraphicsAPI.Direct3D12 => new D3D12.D3D12GraphicsEngine(this), + _ => throw new NotSupportedException($"Graphics API {api} is not supported.") + }; + _renderers = new(); _shutdownEvent = new(false); // Create frame resources for synchronization - _frameResources = new FrameResource[FRAME_COUNT]; - for (var i = 0; i < FRAME_COUNT; i++) + _frameResources = new FrameResource[_FRAME_COUNT]; + for (var i = 0; i < _FRAME_COUNT; i++) { _frameResources[i] = new(); } @@ -71,9 +82,11 @@ public class RenderSystem : IDisposable Dispose(); } - public void AddRenderer(IRenderer renderer) + public IRenderer CreateRenderer() { + var renderer = _engine.CreateRenderer(); ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer)); + return renderer; } public void RemoveRenderer(IRenderer renderer) @@ -84,7 +97,9 @@ public class RenderSystem : IDisposable public void Start() { if (_isRunning) + { return; + } _isRunning = true; _renderThread.Start(); @@ -93,7 +108,9 @@ public class RenderSystem : IDisposable public void Stop() { if (!_isRunning) + { return; + } _isRunning = false; _shutdownEvent.Set(); @@ -106,14 +123,14 @@ public class RenderSystem : IDisposable public bool WaitForGPUReady(int timeOut = -1) { - var eventIndex = (int)(_cpuFenceValue % FRAME_COUNT); - return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut); + var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT); + return _frameResources[eventIndex].gpuReadyEvent.WaitOne(timeOut); } public void SignalCPUReady() { - var eventIndex = (int)(_cpuFenceValue % FRAME_COUNT); - _frameResources[eventIndex].CpuReadyEvent.Set(); + var eventIndex = (int)(_cpuFenceValue % _FRAME_COUNT); + _frameResources[eventIndex].cpuReadyEvent.Set(); _cpuFenceValue++; } @@ -123,11 +140,11 @@ public class RenderSystem : IDisposable while (_isRunning) { - _frameIndex = _gpuFenceValue % FRAME_COUNT; + _frameIndex = _gpuFenceValue % _FRAME_COUNT; var frameResource = _frameResources[_frameIndex]; // Wait for either CPU ready signal or shutdown signal - waitHandles[0] = frameResource.CpuReadyEvent; + waitHandles[0] = frameResource.cpuReadyEvent; var waitResult = WaitHandle.WaitAny(waitHandles); // If shutdown was signaled or timeout occurred, exit the loop @@ -146,7 +163,7 @@ public class RenderSystem : IDisposable } _gpuFenceValue++; - frameResource.GpuReadyEvent.Set(); + frameResource.gpuReadyEvent.Set(); } } } @@ -154,7 +171,9 @@ public class RenderSystem : IDisposable public void Dispose() { if (_disposed) + { return; + } Stop(); diff --git a/Ghost.Graphics/SWAP_CHAIN_IMPROVEMENTS.md b/Ghost.Graphics/SWAP_CHAIN_IMPROVEMENTS.md deleted file mode 100644 index e69de29..0000000 diff --git a/Ghost.Graphics/Shading/Shader.cs b/Ghost.Graphics/Shading/Shader.cs deleted file mode 100644 index 6ed4f5e..0000000 --- a/Ghost.Graphics/Shading/Shader.cs +++ /dev/null @@ -1,530 +0,0 @@ -using Ghost.Core; -using Ghost.Graphics.D3D12.Utilities; -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.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 - { - get; init; - } - - public uint CBufferIndex - { - get; init; - } - - public uint ByteOffset - { - get; init; - } - - public uint Size - { - get; init; - } -} - -internal readonly struct CBufferInfo -{ - public required string Name - { - get; init; - } - - public uint Size - { - get; init; - } - - public uint RegisterSlot - { - get; init; - } -} - -/// -/// Bindless shader implementation using SM 6.6 with ResourceDescriptorHeap -/// and D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED -/// Enhanced to support both bindless and regular texture binding for hybrid materials -/// -public unsafe class Shader : IDisposable -{ - private ComPtr _pipelineState; - private ComPtr _rootSignature; - private ComPtr _samplerHeap; - - private readonly byte[] _vertexShaderBytecode; - private readonly byte[] _pixelShaderBytecode; - - private readonly List _constantBuffers = new(); - private readonly List _properties = new(); - private readonly Dictionary _propertyNameToIdMap = new(); - private readonly List _regularTextures = new(); // Add regular texture support - - private bool _disposed; - - internal ConstPtr PipelineState => new(_pipelineState.Get()); - internal ConstPtr RootSignature => new(_rootSignature.Get()); - internal ConstPtr SamplerHeap => new(_samplerHeap.Get()); - - internal IReadOnlyList ConstantBuffers => _constantBuffers; - internal IReadOnlyList Properties => _properties; - internal IReadOnlyList RegularTextures => _regularTextures; // Expose regular textures - - public Shader(string shaderCode) - { - var (vsBytecode, vsReflection) = CompileShaderDXC(shaderCode, "VSMain", "vs_6_6"); - var (psBytecode, psReflection) = CompileShaderDXC(shaderCode, "PSMain", "ps_6_6"); - - _vertexShaderBytecode = vsBytecode; - _pixelShaderBytecode = psBytecode; - - PerformDXCReflection(vsReflection); - PerformDXCReflection(psReflection); - - CreateBindlessRootSignature(); - CreatePipelineState(); - CreateSamplerHeap(); - } - - private (byte[] bytecode, ComPtr reflection) 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 - NOTE: NO -Qstrip_reflect to keep reflection data - 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 but KEEP reflection - }; - - // 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"); - } - - // Get reflection data using DXC API - using ComPtr reflectionBlob = default; - result.Get()->GetOutput(DxcOutKind.Reflection, __uuidof(), reflectionBlob.GetVoidAddressOf(), null); - - if (reflectionBlob.Get() == null) - { - throw new Exception("DXC compilation succeeded but no reflection data 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 both bytecode and reflection blob (move ownership) - return (bytecode, reflectionBlob.Move()); - } - finally - { - // Free allocated wide strings - for (var i = 0; i < argPointers.Length; i++) - { - Marshal.FreeHGlobal(argPointers[i]); - } - } - } - - private void CreateBindlessRootSignature() - { - var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; - - // Calculate total root parameters: CBVs + Regular texture descriptor table + Sampler table - var totalRootParams = _constantBuffers.Count + (_regularTextures.Count > 0 ? 1 : 0) + 1; // +1 for sampler - var rootParameters = new RootParameter1[totalRootParams]; - - var parameterIndex = 0; - - // Add CBV root parameters - foreach (var cbufferInfo in _constantBuffers) - { - rootParameters[parameterIndex++] = new RootParameter1 - { - ParameterType = RootParameterType.Cbv, - ShaderVisibility = ShaderVisibility.All, - Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0), - }; - } - - // Add regular texture descriptor table if we have regular textures - if (_regularTextures.Count > 0) - { - var textureRanges = new DescriptorRange1[1]; - textureRanges[0] = new DescriptorRange1 - { - RangeType = DescriptorRangeType.Srv, - NumDescriptors = (uint)_regularTextures.Count, - BaseShaderRegister = 0, // Start from t0 - RegisterSpace = 0, - Flags = DescriptorRangeFlags.None, - OffsetInDescriptorsFromTableStart = 0 - }; - - fixed (DescriptorRange1* textureRangesPtr = textureRanges) - { - rootParameters[parameterIndex++] = new RootParameter1 - { - ParameterType = RootParameterType.DescriptorTable, - ShaderVisibility = ShaderVisibility.All, - DescriptorTable = new RootDescriptorTable1(1, textureRangesPtr) - }; - } - } - - // 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[parameterIndex] = 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() - { - var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; - - fixed (byte* vsPtr = _vertexShaderBytecode) - fixed (byte* psPtr = _pixelShaderBytecode) - { - var psoDesc = new GraphicsPipelineStateDescription - { - pRootSignature = _rootSignature.Get(), - VS = new ShaderBytecode(vsPtr, (nuint)_vertexShaderBytecode.Length), - PS = new ShaderBytecode(psPtr, (nuint)_pixelShaderBytecode.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, - }; - - psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT; - - device->CreateGraphicsPipelineState(&psoDesc, __uuidof(), _pipelineState.GetVoidAddressOf()); - } - } - - private void CreateSamplerHeap() - { - var device = GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr; - - // Create sampler heap - var samplerHeapDesc = new DescriptorHeapDescription - { - Type = DescriptorHeapType.Sampler, - NumDescriptors = 1, - Flags = DescriptorHeapFlags.ShaderVisible - }; - - device->CreateDescriptorHeap(&samplerHeapDesc, __uuidof(), _samplerHeap.GetVoidAddressOf()); - - // Create default 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); - } - - private unsafe void PerformDXCReflection(ComPtr reflectionBlob) - { - // Create DXC utils to parse reflection data - using ComPtr utils = default; - DxcCreateInstance(CLSID_DxcUtils, __uuidof(), utils.GetVoidAddressOf()); - - // Create reflection interface from blob - var reflectionData = new DxcBuffer - { - Ptr = reflectionBlob.Get()->GetBufferPointer(), - Size = reflectionBlob.Get()->GetBufferSize(), - Encoding = DXC_CP_ACP - }; - - using ComPtr reflection = default; - utils.Get()->CreateReflection(&reflectionData, __uuidof(), reflection.GetVoidAddressOf()); - - if (reflection.Get() == null) - { - throw new Exception("Failed to create shader reflection from DXC output"); - } - - ShaderDescription shaderDesc; - reflection.Get()->GetDesc(&shaderDesc); - - var cbufferRegistry = _constantBuffers.ToDictionary(cb => cb.Name); - var textureRegistry = _regularTextures.ToDictionary(t => t.Name); - - for (uint i = 0; i < shaderDesc.BoundResources; i++) - { - ShaderInputBindDescription bindDesc; - reflection.Get()->GetResourceBindingDesc(i, &bindDesc); - - if (bindDesc.Type == ShaderInputType.ConstantBuffer) - { - var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); - if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName)) - { - continue; - } - - var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name); - ShaderBufferDescription cbufferDesc; - cbuffer->GetDesc(&cbufferDesc); - - var cbufferInfo = new CBufferInfo - { - Name = cbufferName, - Size = cbufferDesc.Size, - RegisterSlot = bindDesc.BindPoint - }; - cbufferRegistry.Add(cbufferName, cbufferInfo); - - for (uint j = 0; j < cbufferDesc.Variables; j++) - { - var variable = cbuffer->GetVariableByIndex(j); - ShaderVariableDescription varDesc; - variable->GetDesc(&varDesc); - - var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name); - if (variableName == null || _propertyNameToIdMap.ContainsKey(variableName)) - { - continue; - } - - var propInfo = new PropertyInfo - { - Name = variableName, - 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; - } - - // ALL texture input slots are regular textures! - // Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index] - var textureInfo = new TextureInfo - { - Name = textureName, - RegisterSlot = bindDesc.BindPoint, - RootParameterIndex = (uint)_constantBuffers.Count // Descriptor table comes after CBVs - }; - - textureRegistry.Add(textureName, textureInfo); - } - } - - _constantBuffers.Clear(); - _constantBuffers.AddRange(cbufferRegistry.Values); - - _regularTextures.Clear(); - _regularTextures.AddRange(textureRegistry.Values); - } - - /// - /// Gets a unique, stable ID for a shader property. - /// - /// 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; - } - - public void Dispose() - { - if (_disposed) - { - return; - } - - _pipelineState.Dispose(); - _rootSignature.Dispose(); - _samplerHeap.Dispose(); - - _constantBuffers.Clear(); - _properties.Clear(); - _propertyNameToIdMap.Clear(); - _regularTextures.Clear(); - - GC.SuppressFinalize(this); - - _disposed = true; - } -} \ No newline at end of file diff --git a/Ghost.UnitTest/Windows/GraphicsTestWindow.xaml.cs b/Ghost.UnitTest/Windows/GraphicsTestWindow.xaml.cs index 61d6736..9551161 100644 --- a/Ghost.UnitTest/Windows/GraphicsTestWindow.xaml.cs +++ b/Ghost.UnitTest/Windows/GraphicsTestWindow.xaml.cs @@ -1,6 +1,7 @@ using Ghost.Graphics; using Ghost.Graphics.Contracts; using Ghost.Graphics.D3D12; +using Ghost.Graphics.RHI; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; using Misaki.HighPerformance.LowLevel.Buffer; @@ -10,8 +11,9 @@ namespace Ghost.UnitTest.Windows; public sealed partial class GraphicsTestWindow : Window { - private Renderer? _renderer; - private ISwapChainPanelNative _swapChainPanelNative; + private RenderSystem? _renderSystem; + private IRenderer? _renderer; + private ISwapChain? _swapChain; public GraphicsTestWindow() { @@ -28,14 +30,12 @@ public sealed partial class GraphicsTestWindow : Window #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); + _renderSystem = new (GraphicsAPI.Direct3D12); + _renderer = _renderSystem.CreateRenderer(); - //_renderer = GraphicsPipeline.GraphicsDevice.CreateRenderer(new(_swapChainPanelNative, (uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height)); + _swapChain = _renderSystem.GraphicsEngine.Device.CreateSwapChain(new SwapChainDesc((uint)AppWindow.Size.Width, (uint)AppWindow.Size.Height, SwapChainTarget.FromCompositionSurface(Panel))); + _renderer.SetSwapChain(_swapChain); CompositionTarget.Rendering += OnRendering; } @@ -44,11 +44,9 @@ public sealed partial class GraphicsTestWindow : Window { CompositionTarget.Rendering -= OnRendering; - GraphicsPipeline.SignalCPUReady(); - GraphicsPipeline.Shutdown(); - - _swapChainPanelNative.Dispose(); + _swapChain?.Dispose(); _renderer?.Dispose(); + _renderSystem?.Dispose(); } private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)