From a8d7cd8828df6123fe0765114b4edaf46a0ed91c Mon Sep 17 00:00:00 2001 From: Misaki Date: Sat, 1 Nov 2025 22:30:08 +0900 Subject: [PATCH] Refactor and enhance rendering pipeline - Added new C# formatting rules in .editorconfig. - Introduced `IKeyType`, `Key`, and `Ptr` structs. - Updated `Result` and `Result` for implicit conversions. - Added AOT compatibility to project files. - Introduced a `Camera` class and refactored namespaces. - Enhanced rendering with bindless support and pipeline state management. - Refactored `D3D12CommandBuffer` for new rendering features. - Improved `D3D12PipelineLibrary` with disk caching methods. - Added support for UAVs and raw buffers in `D3D12ResourceAllocator`. - Improved shader compilation and reflection in `D3D12ShaderCompiler`. - Refactored descriptor heap and swap chain initialization. - Added enums and structs for rendering configurations. - Expanded `ICommandBuffer` and `IPipelineLibrary` interfaces. - Updated `MeshRenderPass` to align with the new pipeline. - Consolidated namespaces and improved code maintainability. --- .editorconfig | 7 + Ghost.Core/Handle.cs | 48 ++- Ghost.Core/Ptr.cs | 27 ++ Ghost.Core/Result.cs | 5 +- Ghost.Core/Utilities/Win32Utility.cs | 69 +++- Ghost.Entities/Ghost.Entities.csproj | 8 + Ghost.FMOD/Ghost.FMOD.csproj | 8 + Ghost.Graphics/Core/Camera.cs | 5 + Ghost.Graphics/{Data => Core}/Color.cs | 4 +- Ghost.Graphics/{Data => Core}/Material.cs | 4 +- Ghost.Graphics/{Data => Core}/Mesh.cs | 4 +- .../{Data => Core}/RenderingContext.cs | 81 +++-- .../{Data => Core}/ResourceHandle.cs | 4 +- Ghost.Graphics/{Data => Core}/Shader.cs | 6 +- .../{Data => Core}/SwapChainPresenter.cs | 5 +- Ghost.Graphics/{Data => Core}/Vertex.cs | 5 +- Ghost.Graphics/D3D12/D3D12CommandBuffer.cs | 326 +++++++++++------- Ghost.Graphics/D3D12/D3D12CommandQueue.cs | 11 +- Ghost.Graphics/D3D12/D3D12DebugLayer.cs | 27 +- Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs | 259 +++++++------- Ghost.Graphics/D3D12/D3D12RenderDevice.cs | 16 +- Ghost.Graphics/D3D12/D3D12Renderer.cs | 2 +- .../D3D12/D3D12ResourceAllocator.cs | 125 +++++-- Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs | 6 +- Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs | 21 +- Ghost.Graphics/D3D12/D3D12SwapChain.cs | 17 +- .../D3D12/Utilities/D3D12DescriptorHeap.cs | 33 +- .../D3D12/Utilities/D3D12PipelineResource.cs | 14 +- .../D3D12/Utilities/D3D12Utility.cs | 28 +- Ghost.Graphics/Data/Camera.cs | 5 - Ghost.Graphics/RHI/Common.cs | 64 +++- Ghost.Graphics/RHI/ICommandBuffer.cs | 166 +++++---- Ghost.Graphics/RHI/IPipelineLibrary.cs | 11 +- Ghost.Graphics/RHI/IRenderTypes.cs | 15 - Ghost.Graphics/RHI/IRenderer.cs | 2 +- Ghost.Graphics/RHI/IResourceAllocator.cs | 2 +- Ghost.Graphics/RHI/IResourceDatabase.cs | 4 +- Ghost.Graphics/RHI/ISwapChain.cs | 2 +- Ghost.Graphics/RHI/RHIUtility.cs | 6 +- Ghost.Graphics/RenderPasses/MeshRenderPass.cs | 9 +- Ghost.Graphics/Utilities/MeshBuilder.cs | 4 +- 41 files changed, 974 insertions(+), 491 deletions(-) create mode 100644 .editorconfig create mode 100644 Ghost.Core/Ptr.cs create mode 100644 Ghost.Graphics/Core/Camera.cs rename Ghost.Graphics/{Data => Core}/Color.cs (98%) rename Ghost.Graphics/{Data => Core}/Material.cs (99%) rename Ghost.Graphics/{Data => Core}/Mesh.cs (99%) rename Ghost.Graphics/{Data => Core}/RenderingContext.cs (61%) rename Ghost.Graphics/{Data => Core}/ResourceHandle.cs (96%) rename Ghost.Graphics/{Data => Core}/Shader.cs (99%) rename Ghost.Graphics/{Data => Core}/SwapChainPresenter.cs (93%) rename Ghost.Graphics/{Data => Core}/Vertex.cs (93%) delete mode 100644 Ghost.Graphics/Data/Camera.cs delete mode 100644 Ghost.Graphics/RHI/IRenderTypes.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f89eba1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*.cs] +csharp_new_line_before_open_brace = all +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true +dotnet_sort_system_directives_first = false +dotnet_separate_import_directive_groups = false +max_line_length = 400 \ No newline at end of file diff --git a/Ghost.Core/Handle.cs b/Ghost.Core/Handle.cs index e7c3d6c..fa5d61c 100644 --- a/Ghost.Core/Handle.cs +++ b/Ghost.Core/Handle.cs @@ -2,6 +2,7 @@ public interface IHandleType; public interface IIdentifierType; +public interface IKeyType; public readonly struct Handle where T : IHandleType @@ -93,4 +94,49 @@ public readonly struct Identifier { return !a.Equals(b); } -} \ No newline at end of file +} + +public readonly struct Key + where T : IKeyType +{ + public readonly ulong value; + + public Key(ulong value) + { + this.value = value; + } + + public static Key Invalid => new(0); + + public bool IsValid => this != Invalid; + + public readonly override int GetHashCode() + { + return value.GetHashCode(); + } + + public readonly override bool Equals(object? obj) + { + return obj is Key id && Equals(id); + } + + public readonly bool Equals(Key other) + { + return value == other.value; + } + + public readonly int CompareTo(Key other) + { + return value.CompareTo(other.value); + } + + public static bool operator ==(Key a, Key b) + { + return a.Equals(b); + } + + public static bool operator !=(Key a, Key b) + { + return !a.Equals(b); + } +} diff --git a/Ghost.Core/Ptr.cs b/Ghost.Core/Ptr.cs new file mode 100644 index 0000000..37c6f6b --- /dev/null +++ b/Ghost.Core/Ptr.cs @@ -0,0 +1,27 @@ +using System.Runtime.CompilerServices; + +namespace Ghost.Core; + +public unsafe readonly struct Ptr + where T : unmanaged +{ + public readonly T* value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ptr(T* value) + { + this.value = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator T*(Ptr ptr) + { + return ptr.value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Ptr(T* value) + { + return new Ptr(value); + } +} diff --git a/Ghost.Core/Result.cs b/Ghost.Core/Result.cs index fa8e908..bd7867e 100644 --- a/Ghost.Core/Result.cs +++ b/Ghost.Core/Result.cs @@ -24,7 +24,7 @@ public readonly struct Result public override string ToString() => success ? "OK" : $"Error: {message}"; - public static implicit operator Result(bool success) => success ? Success() : Fail(null); + public static implicit operator bool(Result result) => result.success; } public readonly struct Result @@ -54,6 +54,7 @@ public readonly struct Result public override string ToString() => success ? $"OK: {value}" : $"Error: {message}"; public static implicit operator Result(T? data) => data is not null ? Success(data) : Fail(null); + public static implicit operator Result(Result result) => result.success ? Success(default!) : Fail(result.message); } public static class ResultExtensions @@ -75,4 +76,4 @@ public static class ResultExtensions return result.value; } -} \ No newline at end of file +} diff --git a/Ghost.Core/Utilities/Win32Utility.cs b/Ghost.Core/Utilities/Win32Utility.cs index 91af020..049cc00 100644 --- a/Ghost.Core/Utilities/Win32Utility.cs +++ b/Ghost.Core/Utilities/Win32Utility.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System.ComponentModel; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using TerraFX.Interop.Windows; @@ -7,33 +8,82 @@ namespace Ghost.Core.Utilities; #if PLATEFORME_WIN64 [SupportedOSPlatform("windows10.0.19041.0")] -internal unsafe static class Win32Utility +internal static unsafe class Win32Utility { - public static Guid* IID_NULL => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IID.IID_NULL)); - - [Conditional("DEBUG")] - public static void Assert(this HRESULT hr) + [EditorBrowsable(EditorBrowsableState.Never)] + public readonly ref struct IID_PPV { - Debug.Assert(hr.SUCCEEDED); + public readonly Guid* iid; + public readonly void** ppv; + + public IID_PPV(Guid* iid, void** ppv) + { + this.iid = iid; + this.ppv = ppv; + } + + public void Deconstruct(out Guid* iid, out void** ppv) + { + iid = this.iid; + ppv = this.ppv; + } } + public static Guid* IID_NULL + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in TerraFX.Interop.Windows.IID.IID_NULL)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IID_PPV IID_PPV_ARGS(ComPtr comPtr) + where T : unmanaged, IUnknown.Interface + { + return new IID_PPV(Windows.__uuidof(), comPtr.PPV()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IID_PPV IID_PPV_ARGS(T** ppv) + where T : unmanaged, IUnknown.Interface + { + return new IID_PPV(Windows.__uuidof(), (void**)ppv); + } + + [Conditional("DEBUG")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Assert(this HRESULT hr) + { + Debug.Assert(hr.SUCCEEDED, hr.ToString()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ThrowIfFailed(this HRESULT hr) { Windows.ThrowIfFailed(hr); } - public static void** GetVoidAddressOf(this ComPtr comPtr) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Guid* IID(this ComPtr comPtr) + where T : unmanaged, IUnknown.Interface + { + return Windows.__uuidof(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void** PPV(this ComPtr comPtr) where T : unmanaged, IUnknown.Interface { return (void**)comPtr.GetAddressOf(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void** ReleaseAndGetVoidAddressOf(this ComPtr comPtr) where T : unmanaged, IUnknown.Interface { return (void**)comPtr.ReleaseAndGetAddressOf(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ComPtr Move(ref this ComPtr comPtr) where T : unmanaged, IUnknown.Interface { @@ -42,10 +92,11 @@ internal unsafe static class Win32Utility return copy; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasFlag(this uint flags, T flag) where T : Enum { return (flags & Unsafe.As(ref flag)) != 0; } } -#endif \ No newline at end of file +#endif diff --git a/Ghost.Entities/Ghost.Entities.csproj b/Ghost.Entities/Ghost.Entities.csproj index f024e17..e6e74f1 100644 --- a/Ghost.Entities/Ghost.Entities.csproj +++ b/Ghost.Entities/Ghost.Entities.csproj @@ -7,6 +7,14 @@ True + + True + + + + True + + True diff --git a/Ghost.FMOD/Ghost.FMOD.csproj b/Ghost.FMOD/Ghost.FMOD.csproj index c24c596..0f6dd0b 100644 --- a/Ghost.FMOD/Ghost.FMOD.csproj +++ b/Ghost.FMOD/Ghost.FMOD.csproj @@ -6,6 +6,14 @@ enable + + True + + + + True + + diff --git a/Ghost.Graphics/Core/Camera.cs b/Ghost.Graphics/Core/Camera.cs new file mode 100644 index 0000000..e481d4d --- /dev/null +++ b/Ghost.Graphics/Core/Camera.cs @@ -0,0 +1,5 @@ +namespace Ghost.Graphics.Core; + +public class Camera +{ +} diff --git a/Ghost.Graphics/Data/Color.cs b/Ghost.Graphics/Core/Color.cs similarity index 98% rename from Ghost.Graphics/Data/Color.cs rename to Ghost.Graphics/Core/Color.cs index da38f72..c5831d2 100644 --- a/Ghost.Graphics/Data/Color.cs +++ b/Ghost.Graphics/Core/Color.cs @@ -1,7 +1,7 @@ using System.Drawing; using System.Runtime.InteropServices; -namespace Ghost.Graphics.Data; +namespace Ghost.Graphics.Core; /// /// Represents a color with 4 bytes components. @@ -111,4 +111,4 @@ public struct Color128 : IEquatable { return !(left == right); } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/Data/Material.cs b/Ghost.Graphics/Core/Material.cs similarity index 99% rename from Ghost.Graphics/Data/Material.cs rename to Ghost.Graphics/Core/Material.cs index 6fa9407..4894c4d 100644 --- a/Ghost.Graphics/Data/Material.cs +++ b/Ghost.Graphics/Core/Material.cs @@ -6,7 +6,7 @@ using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.Mathematics; using System.Runtime.CompilerServices; -namespace Ghost.Graphics.Data; +namespace Ghost.Graphics.Core; internal struct CBufferCache : IResourceReleasable { @@ -214,4 +214,4 @@ public ref struct MaterialAccessor cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan()); } } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/Data/Mesh.cs b/Ghost.Graphics/Core/Mesh.cs similarity index 99% rename from Ghost.Graphics/Data/Mesh.cs rename to Ghost.Graphics/Core/Mesh.cs index 7cc148c..89b4aa4 100644 --- a/Ghost.Graphics/Data/Mesh.cs +++ b/Ghost.Graphics/Core/Mesh.cs @@ -6,7 +6,7 @@ using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics.Geometry; -namespace Ghost.Graphics.Data; +namespace Ghost.Graphics.Core; public struct Mesh : IResourceReleasable, IHandleType { @@ -169,4 +169,4 @@ public static class MeshExtension mesh.vertices[i].tangent = new float4(t.x, t.y, t.z, w); } } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/Data/RenderingContext.cs b/Ghost.Graphics/Core/RenderingContext.cs similarity index 61% rename from Ghost.Graphics/Data/RenderingContext.cs rename to Ghost.Graphics/Core/RenderingContext.cs index b144991..8f61316 100644 --- a/Ghost.Graphics/Data/RenderingContext.cs +++ b/Ghost.Graphics/Core/RenderingContext.cs @@ -1,24 +1,25 @@ using Ghost.Core; +using Ghost.Core.Graphics; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; -namespace Ghost.Graphics.Data; +namespace Ghost.Graphics.Core; public unsafe readonly ref struct RenderingContext { private readonly IRenderDevice _device; - private readonly ICommandBuffer _cmd; - private readonly ICommandBuffer _copyCmd; - private readonly ICommandBuffer _computeCmd; + private readonly ICommandBuffer _directCmb; + private readonly ICommandBuffer _copyCmb; + private readonly ICommandBuffer _computeCmb; private readonly IResourceAllocator _resourceAllocator; private readonly IResourceDatabase _resourceDatabase; private readonly IPipelineLibrary _stateController; internal RenderingContext( IRenderDevice device, - ICommandBuffer cmd, + ICommandBuffer directCmd, ICommandBuffer copyCmd, ICommandBuffer computeCmd, IResourceAllocator resourceAllocator, @@ -26,9 +27,9 @@ public unsafe readonly ref struct RenderingContext IPipelineLibrary stateController) { _device = device; - _cmd = cmd; - _copyCmd = copyCmd; - _computeCmd = computeCmd; + _directCmb = directCmd; + _copyCmb = copyCmd; + _computeCmb = computeCmd; _resourceAllocator = resourceAllocator; _resourceDatabase = resourceDatabase; _stateController = stateController; @@ -51,6 +52,8 @@ public unsafe readonly ref struct RenderingContext return CreateMesh(vertexList, indexList); } + // TODO: Make one memory pool for upload. + /// /// Uploads the mesh data to the GPU. /// @@ -66,29 +69,29 @@ public unsafe readonly ref struct RenderingContext if (needVertexTransition) { - _copyCmd.ResourceBarrier(meshData.vertexBuffer.AsResource(), vertexState, ResourceState.CopyDest); + _copyCmb.ResourceBarrier(meshData.vertexBuffer.AsResource(), vertexState, ResourceState.CopyDest); _resourceDatabase.SetResourceState(meshData.vertexBuffer.AsResource(), ResourceState.CopyDest); } if (needIndexTransition) { - _copyCmd.ResourceBarrier(meshData.indexBuffer.AsResource(), indexState, ResourceState.CopyDest); + _copyCmb.ResourceBarrier(meshData.indexBuffer.AsResource(), indexState, ResourceState.CopyDest); _resourceDatabase.SetResourceState(meshData.indexBuffer.AsResource(), ResourceState.CopyDest); } - _copyCmd.Upload(meshData.vertexBuffer, meshData.vertices.AsSpan()); - _copyCmd.Upload(meshData.indexBuffer, meshData.indices.AsSpan()); + _copyCmb.Upload(meshData.vertexBuffer, meshData.vertices.AsSpan()); + _copyCmb.Upload(meshData.indexBuffer, meshData.indices.AsSpan()); if (needVertexTransition) { - _copyCmd.ResourceBarrier(meshData.vertexBuffer.AsResource(), ResourceState.CopyDest, vertexState); + _copyCmb.ResourceBarrier(meshData.vertexBuffer.AsResource(), ResourceState.CopyDest, vertexState); _resourceDatabase.SetResourceState(meshData.vertexBuffer.AsResource(), vertexState); } if (needIndexTransition) { _resourceDatabase.SetResourceState(meshData.indexBuffer.AsResource(), indexState); - _copyCmd.ResourceBarrier(meshData.indexBuffer.AsResource(), ResourceState.CopyDest, indexState); + _copyCmb.ResourceBarrier(meshData.indexBuffer.AsResource(), ResourceState.CopyDest, indexState); } if (markMeshStatic) @@ -119,27 +122,65 @@ public unsafe readonly ref struct RenderingContext if (needTransition) { - _copyCmd.ResourceBarrier(texture.AsResource(), sateBefore, ResourceState.CopyDest); + _copyCmb.ResourceBarrier(texture.AsResource(), sateBefore, ResourceState.CopyDest); _resourceDatabase.SetResourceState(texture.AsResource(), ResourceState.CopyDest); } - _copyCmd.Upload(texture, subresourceData); + _copyCmb.Upload(texture, subresourceData); if (needTransition) { - _copyCmd.ResourceBarrier(texture.AsResource(), ResourceState.CopyDest, sateBefore); + _copyCmb.ResourceBarrier(texture.AsResource(), ResourceState.CopyDest, sateBefore); _resourceDatabase.SetResourceState(texture.AsResource(), sateBefore); } } - public void RenderMesh(Handle mesh, Handle material) + public void RenderMesh(Handle mesh, Handle material, string passName) { //_cmd.DrawMesh(mesh, material); + ref var meshRef = ref _resourceDatabase.GetMeshReference(mesh); + ref var materialRef = ref _resourceDatabase.GetMaterialReference(material); + var shader = _resourceDatabase.GetShaderReference(materialRef.Shader); + + shader.TryGetPassKey(passName, out var passKey); + var hash = new GraphicsPipelineHash + { + id = passKey, + rtvCount = 1, + dsvFormat = TextureFormat.Unknown, + }; + + hash.rtvFormats[0] = TextureFormat.B8G8R8A8_UNorm; + var pipelineKey = hash.GetKey(); + _directCmb.SetPipelineState(pipelineKey); + + // FIX: Get valid root signature. In D3D12, we use fixed root signature layout for bindless rendering. + // However, our code should not assume that blindly. Each pipeline should have contained root signature info even if there are fixed. + // This ensures that future changes to root signature layout can be accommodated. + + // for (int i = 0; i < 4; i++) + { + ref var cache = ref materialRef.GetPassCache((int)passKey.value); + _directCmb.SetConstantBufferView(RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT, cache.GpuResource); + } + + // NOTE: Since we are using true bindless resources, we only need to set the descriptor heaps, not individual tables. + // TODO: Matbe handle the transitional bindless model? +#if false + var samplerGpuHandle = _descriptorAllocator.GetSamplerHeap()->GetGPUDescriptorHandleForHeapStart(); + _commandList.Get()->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle); +#endif + _directCmb.SetPrimitiveTopology(PrimitiveTopology.Triangle); + + // Draw without vertex/index buffers - use instanced drawing + // Each instance represents a triangle (3 vertices) + var triangleCount = (uint)meshRef.indices.Count / 3; + _directCmb.Draw(3, triangleCount, 0, 0); } public void ExecuteCopyCommands() { - _device.CopyQueue.Submit(_copyCmd); + _device.CopyQueue.Submit(_copyCmb); _device.CopyQueue.WaitIdle(); } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/Data/ResourceHandle.cs b/Ghost.Graphics/Core/ResourceHandle.cs similarity index 96% rename from Ghost.Graphics/Data/ResourceHandle.cs rename to Ghost.Graphics/Core/ResourceHandle.cs index e4747d7..ca06bb1 100644 --- a/Ghost.Graphics/Data/ResourceHandle.cs +++ b/Ghost.Graphics/Core/ResourceHandle.cs @@ -1,6 +1,6 @@ using Ghost.Core; -namespace Ghost.Graphics.Data; +namespace Ghost.Graphics.Core; public readonly struct GPUResource : IHandleType; public readonly struct Texture : IHandleType; @@ -27,4 +27,4 @@ public static class ResourceHandleExtensions { return new Handle(resource.id, resource.generation); } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/Data/Shader.cs b/Ghost.Graphics/Core/Shader.cs similarity index 99% rename from Ghost.Graphics/Data/Shader.cs rename to Ghost.Graphics/Core/Shader.cs index a0ae305..f4b937a 100644 --- a/Ghost.Graphics/Data/Shader.cs +++ b/Ghost.Graphics/Core/Shader.cs @@ -5,7 +5,7 @@ using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.InteropServices; -namespace Ghost.Graphics.Data; +namespace Ghost.Graphics.Core; public readonly struct TextureInfo { @@ -142,7 +142,7 @@ public class Shader : IResourceReleasable, IIdentifierType return _passIDs[index]; } - public bool TryGetPassKey(string passName, out ShaderPassKey? passID) + public bool TryGetPassKey(string passName, out ShaderPassKey passID) { var index = _passLookup.GetValueOrDefault(passName, -1); if (index == -1) @@ -169,4 +169,4 @@ public class Shader : IResourceReleasable, IIdentifierType { _passIDs.Dispose(); } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/Data/SwapChainPresenter.cs b/Ghost.Graphics/Core/SwapChainPresenter.cs similarity index 93% rename from Ghost.Graphics/Data/SwapChainPresenter.cs rename to Ghost.Graphics/Core/SwapChainPresenter.cs index 23b084f..5154c15 100644 --- a/Ghost.Graphics/Data/SwapChainPresenter.cs +++ b/Ghost.Graphics/Core/SwapChainPresenter.cs @@ -1,6 +1,7 @@ using Ghost.Graphics.Contracts; +using Ghost.Graphics.Core; -namespace Ghost.Graphics.Data; +namespace Ghost.Graphics.Core; internal readonly struct SwapChainPresenter { @@ -51,4 +52,4 @@ internal readonly struct SwapChainPresenter Width = width; Height = height; } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/Data/Vertex.cs b/Ghost.Graphics/Core/Vertex.cs similarity index 93% rename from Ghost.Graphics/Data/Vertex.cs rename to Ghost.Graphics/Core/Vertex.cs index 61fb949..c55ffce 100644 --- a/Ghost.Graphics/Data/Vertex.cs +++ b/Ghost.Graphics/Core/Vertex.cs @@ -2,8 +2,9 @@ using Misaki.HighPerformance.Mathematics; using System.Runtime.InteropServices; using TerraFX.Interop.DirectX; +using Ghost.Graphics.Core; -namespace Ghost.Graphics.Data; +namespace Ghost.Graphics.Core; [StructLayout(LayoutKind.Sequential)] public struct Vertex @@ -25,4 +26,4 @@ public struct Vertex public float4 tangent; public float4 uv; public Color128 color; -} \ No newline at end of file +} diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs index cf42bb8..5f77a76 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs @@ -1,15 +1,16 @@ using Ghost.Core; -using Ghost.Core.Utilities; using Ghost.Graphics.D3D12.Utilities; -using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Utilities; using System.Runtime.CompilerServices; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; -using static TerraFX.Aliases.D3D_Alias; + using static TerraFX.Aliases.D3D12_Alias; +using static TerraFX.Aliases.D3D_Alias; using static TerraFX.Aliases.DXGI_Alias; +using Ghost.Core.Graphics; +using Ghost.Graphics.Core; namespace Ghost.Graphics.D3D12; @@ -26,17 +27,26 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer private readonly D3D12ResourceAllocator _resourceAllocator; private readonly D3D12DescriptorAllocator _descriptorAllocator; + private string _name; private readonly CommandBufferType _type; private ushort _commandCount; private bool _isRecording; private bool _disposed; - public CommandBufferType Type => _type; - public ID3D12GraphicsCommandList10* NativeCommandList => _commandList.Get(); - + public CommandBufferType Type => _type; public bool IsEmpty => _commandCount == 0; + public string Name + { + get => _name; + set + { + _name = value; + _commandList.Get()->SetName(value); + } + } + public D3D12CommandBuffer( D3D12RenderDevice device, D3D12PipelineLibrary stateController, @@ -45,11 +55,18 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer D3D12DescriptorAllocator descriptorAllocator, CommandBufferType type) { + _name = string.Empty; _type = type; + + ID3D12CommandAllocator* pAllocator = default; + ID3D12GraphicsCommandList10* pCommandList = default; var commandListType = ConvertCommandBufferType(type); - device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof(), _allocator.GetVoidAddressOf()); - device.NativeDevice->CreateCommandList1(0u, commandListType, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof(), _commandList.GetVoidAddressOf()); + device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof(pAllocator), (void**)&pAllocator); + device.NativeDevice->CreateCommandList1(0u, commandListType, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof(pCommandList), (void**)&pCommandList); + + _allocator.Attach(pAllocator); + _commandList.Attach(pCommandList); _pipelineLibrary = stateController; _resourceDatabase = resourceDatabase; @@ -89,6 +106,20 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer public void Begin() { + void ResetCommandList() + { + ThrowIfFailed(_allocator.Get()->Reset()); + ThrowIfFailed(_commandList.Get()->Reset(_allocator.Get(), null)); + } + + void SetBindlessHeap() + { + var heaps = stackalloc ID3D12DescriptorHeap*[2]; + heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Bindless resource heap + heaps[1] = _descriptorAllocator.GetSamplerHeap(); // Bindless sampler heap + _commandList.Get()->SetDescriptorHeaps(2, heaps); + } + ThrowIfDisposed(); if (_isRecording) @@ -96,8 +127,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer throw new InvalidOperationException("Command buffer is already recording"); } - _allocator.Get()->Reset(); - _commandList.Get()->Reset(_allocator.Get(), null); + ResetCommandList(); + SetBindlessHeap(); + _commandCount = 0; _isRecording = true; } @@ -111,58 +143,6 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _isRecording = false; } - public void SetRenderTargets(ReadOnlySpan> renderTargets, Handle depthTarget) - { - ThrowIfDisposed(); - ThrowIfNotRecording(); - IncrementCommandCount(); - - var rtvHandles = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[renderTargets.Length]; - for (var i = 0; i < renderTargets.Length; i++) - { - var handle = renderTargets[i]; - if (!handle.IsValid) - { - throw new ArgumentException($"Render target at index {i} is not a valid texture handle"); - } - - var descriptor = _resourceDatabase.GetResourceInfo(handle.AsResource()).viewGroup; - rtvHandles[i] = _descriptorAllocator.GetCpuHandle(descriptor.rtv); - } - - var dsvHandle = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[depthTarget.IsValid ? 1 : 0]; - if (dsvHandle != null) - { - *dsvHandle = _descriptorAllocator.GetCpuHandle(_resourceDatabase.GetResourceInfo(depthTarget.AsResource()).viewGroup.dsv); - } - - _commandList.Get()->OMSetRenderTargets((uint)renderTargets.Length, rtvHandles, FALSE, dsvHandle); - } - - public void BeginRenderPass(Handle renderTarget, Handle depthTarget, Color128 clearColor) - { - // TODO: Implement render pass begin - } - - public void EndRenderPass() - { - ThrowIfDisposed(); - ThrowIfNotRecording(); - IncrementCommandCount(); - - _commandList.Get()->EndRenderPass(); - } - - public void SetViewport(ViewportDesc viewport) - { - ThrowIfDisposed(); - ThrowIfNotRecording(); - IncrementCommandCount(); - - var d3d12Viewport = new D3D12_VIEWPORT(viewport.width, viewport.height, viewport.x, viewport.y, viewport.minDepth, viewport.maxDepth); - _commandList.Get()->RSSetViewports(1, &d3d12Viewport); - } - public void SetScissorRect(RectDesc rect) { ThrowIfDisposed(); @@ -186,16 +166,141 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList.Get()->ResourceBarrier(1, &barrier); } - public void SetRootSignature(IRootSignature rootSignature) + public void SetRenderTargets(ReadOnlySpan> renderTargets, Handle depthTarget) { - // TODO: Implement root signature setting - throw new NotImplementedException(); + ThrowIfDisposed(); + ThrowIfNotRecording(); + IncrementCommandCount(); + + var pRtvHandles = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[renderTargets.Length]; + for (var i = 0; i < renderTargets.Length; i++) + { + var handle = renderTargets[i]; + if (!handle.IsValid) + { + throw new ArgumentException($"Render target at index {i} is not a valid texture handle"); + } + + var descriptor = _resourceDatabase.GetResourceInfo(handle.AsResource()).viewGroup; + pRtvHandles[i] = _descriptorAllocator.GetCpuHandle(descriptor.rtv); + } + + var pDsvHandle = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[depthTarget.IsValid ? 1 : 0]; + if (pDsvHandle != null) + { + pDsvHandle[0] = _descriptorAllocator.GetCpuHandle(_resourceDatabase.GetResourceInfo(depthTarget.AsResource()).viewGroup.dsv); + } + + _commandList.Get()->OMSetRenderTargets((uint)renderTargets.Length, pRtvHandles, FALSE, pDsvHandle); } - public void SetPipelineState(IShaderPipeline pipelineState) + public void BeginRenderPass(ReadOnlySpan rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false) { - // TODO: Implement pipeline state setting - throw new NotImplementedException(); + // TODO: Implement render pass begin + + var pRtvDescs = stackalloc D3D12_RENDER_PASS_RENDER_TARGET_DESC[rtDescs.Length]; + for (var i = 0; i < rtDescs.Length; i++) + { + var rtDesc = rtDescs[i]; + if (!rtDesc.texture.IsValid) + { + throw new ArgumentException($"Render target at index {i} is not a valid texture handle"); + } + + var resourceInfo = _resourceDatabase.GetResourceInfo(rtDesc.texture.AsResource()); + var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceInfo.viewGroup.rtv); + + var desc = new D3D12_RENDER_PASS_RENDER_TARGET_DESC + { + cpuDescriptor = cpuHandle, + BeginningAccess = new D3D12_RENDER_PASS_BEGINNING_ACCESS + { + Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, + Clear = new D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS + { + ClearValue = new D3D12_CLEAR_VALUE + { + Format = resourceInfo.desc.textureDescription.Format.ToDXGIFormat(), + } + } + } + }; + + desc.BeginningAccess.Clear.ClearValue.Color[0] = rtDesc.clearColor.r; + desc.BeginningAccess.Clear.ClearValue.Color[1] = rtDesc.clearColor.g; + desc.BeginningAccess.Clear.ClearValue.Color[2] = rtDesc.clearColor.b; + desc.BeginningAccess.Clear.ClearValue.Color[3] = rtDesc.clearColor.a; + + pRtvDescs[i] = desc; + } + + var pDsvDesc = stackalloc D3D12_RENDER_PASS_DEPTH_STENCIL_DESC[depthDesc.texture.IsValid ? 1 : 0]; + if (pDsvDesc != null) + { + var resourceInfo = _resourceDatabase.GetResourceInfo(depthDesc.texture.AsResource()); + var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceInfo.viewGroup.dsv); + + var desc = new D3D12_RENDER_PASS_DEPTH_STENCIL_DESC + { + cpuDescriptor = cpuHandle, + DepthBeginningAccess = new D3D12_RENDER_PASS_BEGINNING_ACCESS + { + Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, + Clear = new D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS + { + ClearValue = new D3D12_CLEAR_VALUE + { + Format = resourceInfo.desc.textureDescription.Format.ToDXGIFormat(), + DepthStencil = new D3D12_DEPTH_STENCIL_VALUE + { + Depth = depthDesc.clearDepth, + Stencil = depthDesc.clearStencil + } + } + } + } + }; + + pDsvDesc[0] = desc; + } + + _commandList.Get()->BeginRenderPass((uint)rtDescs.Length, pRtvDescs, pDsvDesc, + allowUAVWrites ? D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES : D3D12_RENDER_PASS_FLAG_NONE); + } + + public void EndRenderPass() + { + ThrowIfDisposed(); + ThrowIfNotRecording(); + IncrementCommandCount(); + + _commandList.Get()->EndRenderPass(); + } + + public void SetViewport(ViewportDesc viewport) + { + ThrowIfDisposed(); + ThrowIfNotRecording(); + IncrementCommandCount(); + + var d3d12Viewport = new D3D12_VIEWPORT(viewport.width, viewport.height, viewport.x, viewport.y, viewport.minDepth, viewport.maxDepth); + _commandList.Get()->RSSetViewports(1, &d3d12Viewport); + } + + public void SetPipelineState(GraphicsPipelineKey pipelineKey) + { + ThrowIfDisposed(); + ThrowIfNotRecording(); + IncrementCommandCount(); + + var shaderPipeline = _pipelineLibrary.LoadGraphicsPSO(pipelineKey).GetValueOrThrow(); + _commandList.Get()->SetPipelineState(shaderPipeline.value); + } + + public void SetConstantBufferView(uint slot, Handle buffer) + { + var resource = _resourceDatabase.GetResource(buffer.AsResource()); + _commandList.Get()->SetGraphicsRootConstantBufferView(RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT, resource->GetGPUVirtualAddress()); } public void SetVertexBuffer(uint slot, Handle buffer, ulong offset = 0) @@ -232,6 +337,19 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList.Get()->IASetIndexBuffer(&ibView); } + public void SetPrimitiveTopology(PrimitiveTopology topology) + { + var d3d12Topology = topology switch + { + PrimitiveTopology.Point => D3D_PRIMITIVE_TOPOLOGY_POINTLIST, + PrimitiveTopology.Line => D3D_PRIMITIVE_TOPOLOGY_LINELIST, + PrimitiveTopology.Triangle => D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, + _ => D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST + }; + + _commandList.Get()->IASetPrimitiveTopology(d3d12Topology); + } + public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0) { ThrowIfDisposed(); @@ -250,55 +368,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance); } - // TODO: Batch draw calls by material to minimize state changes - public void DrawMesh(Handle mesh, Handle material) - { - ThrowIfDisposed(); - ThrowIfNotRecording(); - IncrementCommandCount(); - - ref var meshRef = ref _resourceDatabase.GetMeshReference(mesh); - ref var materialRef = ref _resourceDatabase.GetMaterialReference(material); - ref var shaderRef = ref _resourceDatabase.GetShaderReference(materialRef.Shader); - - var shaderPipeline = _pipelineLibrary.GetShaderPipeline(materialRef.Shader); - if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline) - { - throw new InvalidOperationException("Shader pipeline is not compiled or invalid"); - } - - _commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get()); - _commandList.Get()->SetGraphicsRootSignature(_pipelineLibrary.DefaultRootSignature); - - // Set viewGroup heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6 - var heaps = stackalloc ID3D12DescriptorHeap*[2]; - heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Bindless resource heap - heaps[1] = _descriptorAllocator.GetSamplerHeap(); // Bindless sampler heap - _commandList.Get()->SetDescriptorHeaps(2, heaps); - - var rootParamIndex = 0u; - foreach (var cbufferInfo in shaderRef.PerMaterialBufferInfo) - { - ref var cache = ref materialRef._materialPropertiesCache[(int)cbufferInfo.RegisterSlot]; - var resource = _resourceDatabase.GetResource(cache.GpuResource.AsResource()); - _commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, resource->GetGPUVirtualAddress()); - } - - var samplerGpuHandle = _descriptorAllocator.GetSamplerHeap()->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(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - - // Draw without vertex/index buffers - use instanced drawing - // Each instance represents a triangle (3 vertices) - var triangleCount = (uint)meshRef.indices.Count / 3; - _commandList.Get()->DrawInstanced(3, triangleCount, 0, 0); - } - - public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1) + public void DispatchCompute(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ) { ThrowIfDisposed(); ThrowIfNotRecording(); @@ -307,6 +377,26 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer _commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ); } + public void DispatchMesh(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ) + { + ThrowIfDisposed(); + ThrowIfNotRecording(); + IncrementCommandCount(); + + _commandList.Get()->DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ); + } + + public void DispatchRay() + { + throw new NotImplementedException(); + + // ThrowIfDisposed(); + // ThrowIfNotRecording(); + // IncrementCommandCount(); + + // _commandList.Get()->DispatchRays(); + } + public void Upload(Handle buffer, ReadOnlySpan data) where T : unmanaged { @@ -323,7 +413,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer pUploadResource->Map(0, null, &pMappedData); fixed (T* pData = data) { - MemoryUtilities.MemCpy(pMappedData, pData, sizeInBytes); + MemoryUtility.MemCpy(pMappedData, pData, sizeInBytes); } pUploadResource->Unmap(0, null); diff --git a/Ghost.Graphics/D3D12/D3D12CommandQueue.cs b/Ghost.Graphics/D3D12/D3D12CommandQueue.cs index 6a3a10a..c3b378f 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandQueue.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandQueue.cs @@ -36,12 +36,13 @@ internal unsafe class D3D12CommandQueue : ICommandQueue Flags = D3D12_COMMAND_QUEUE_FLAGS.D3D12_COMMAND_QUEUE_FLAG_NONE, }; - fixed (void* queuePtr = &_queue) - { - pDevice->CreateCommandQueue(&queueDesc, __uuidof(), (void**)queuePtr); - } + ID3D12CommandQueue* pQueue = default; + ID3D12Fence1* pFence = default; + ThrowIfFailed(pDevice->CreateCommandQueue(&queueDesc, __uuidof(pQueue), (void**)&pQueue)); + ThrowIfFailed(pDevice->CreateFence(0, D3D12_FENCE_FLAGS.D3D12_FENCE_FLAG_NONE, __uuidof(pFence), (void**)&pFence)); - pDevice->CreateFence(0, D3D12_FENCE_FLAGS.D3D12_FENCE_FLAG_NONE, __uuidof(), _fence.GetVoidAddressOf()); + _queue.Attach(pQueue); + _fence.Attach(pFence); } ~D3D12CommandQueue() diff --git a/Ghost.Graphics/D3D12/D3D12DebugLayer.cs b/Ghost.Graphics/D3D12/D3D12DebugLayer.cs index f36871c..25b2740 100644 --- a/Ghost.Graphics/D3D12/D3D12DebugLayer.cs +++ b/Ghost.Graphics/D3D12/D3D12DebugLayer.cs @@ -1,6 +1,4 @@ -using Ghost.Core.Utilities; -using Ghost.Graphics.D3D12.Utilities; -using TerraFX.Interop.DirectX; +using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; using static TerraFX.Aliases.DXGI_Alias; @@ -15,20 +13,27 @@ internal unsafe class D3D12DebugLayer public D3D12DebugLayer() { - D3D12GetDebugInterface(__uuidof(), _d3d12Debug.GetVoidAddressOf()); - _d3d12Debug.Get()->EnableDebugLayer(); + ID3D12Debug6* pDebug = default; + ThrowIfFailed(D3D12GetDebugInterface(__uuidof(pDebug), (void**)&pDebug)); + pDebug->EnableDebugLayer(); - DXGIGetDebugInterface1(0u, __uuidof(), _dxgiDebug.GetVoidAddressOf()); - _dxgiDebug.Get()->EnableLeakTrackingForThread(); + IDXGIDebug1* pDxgiDebug = default; + ThrowIfFailed(DXGIGetDebugInterface1(0u, __uuidof(pDxgiDebug), (void**)&pDxgiDebug)); + pDxgiDebug->EnableLeakTrackingForThread(); - DXGIGetDebugInterface1(0u, __uuidof(), _dxgiInfoQueue.GetVoidAddressOf()); - _dxgiInfoQueue.Get()->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true); - _dxgiInfoQueue.Get()->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true); + IDXGIInfoQueue* pDxgiInfoQueue = default; + ThrowIfFailed(DXGIGetDebugInterface1(0u, __uuidof(pDxgiInfoQueue), (void**)&pDxgiInfoQueue)); + ThrowIfFailed(pDxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true)); + ThrowIfFailed(pDxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true)); + + _d3d12Debug.Attach(pDebug); + _dxgiDebug.Attach(pDxgiDebug); + _dxgiInfoQueue.Attach(pDxgiInfoQueue); } public void Dispose() { - _dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL | DXGI_DEBUG_RLO_IGNORE_INTERNAL); + ThrowIfFailed(_dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL | DXGI_DEBUG_RLO_IGNORE_INTERNAL)); _d3d12Debug.Dispose(); _dxgiDebug.Dispose(); diff --git a/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs b/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs index ad2e014..b6d8cab 100644 --- a/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs +++ b/Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs @@ -7,6 +7,7 @@ using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.Utilities; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -35,10 +36,12 @@ internal struct D3D12PipelineState : IDisposable // NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later. public D3D12GraphicsCompiledResult compileResult; public D3DX12_MESH_SHADER_PIPELINE_STATE_DESC psoDesc; + public ComPtr pso; public void Dispose() { compileResult.Dispose(); + pso.Dispose(); } } @@ -62,31 +65,16 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get(); - public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase, string? cachePath) + public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase) { _device = device; _resourceDatabase = resourceDatabase; _pipelineCache = new(); - InitializeLibrary(cachePath); CreateDefaultRootSignature(); } - private void InitializeLibrary(string? filePath) - { - if (!File.Exists(filePath)) - { - _device.NativeDevice->CreatePipelineLibrary(null, 0, __uuidof(), _library.GetVoidAddressOf()).ThrowIfFailed(); - } - - var fileBytes = File.ReadAllBytes(filePath!); - fixed (byte* pFileBytes = fileBytes) - { - _device.NativeDevice->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof(), _library.GetVoidAddressOf()).ThrowIfFailed(); - } - } - private void CreateDefaultRootSignature() { _defaultRootSignature = default; @@ -97,28 +85,28 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable { ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL, - Descriptor = new D3D12_ROOT_DESCRIPTOR1(0, 0), // b0 + Descriptor = new D3D12_ROOT_DESCRIPTOR1(RootSignatureLayout.GLOBAL_BUFFER_SLOT, 0), // b0 }; rootParameters[1] = new D3D12_ROOT_PARAMETER1 { ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL, - Descriptor = new D3D12_ROOT_DESCRIPTOR1(1, 0), // b1 + Descriptor = new D3D12_ROOT_DESCRIPTOR1(RootSignatureLayout.PER_VIEW_BUFFER_SLOT, 0), // b1 }; rootParameters[2] = new D3D12_ROOT_PARAMETER1 { ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL, - Descriptor = new D3D12_ROOT_DESCRIPTOR1(2, 0), // b2 + Descriptor = new D3D12_ROOT_DESCRIPTOR1(RootSignatureLayout.PER_OBJECT_BUFFER_SLOT, 0), // b2 }; rootParameters[3] = new D3D12_ROOT_PARAMETER1 { ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL, - Descriptor = new D3D12_ROOT_DESCRIPTOR1(3, 0), // b3 + Descriptor = new D3D12_ROOT_DESCRIPTOR1(RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT, 0), // b3 }; #if USE_TRADITIONAL_BINDLESS @@ -166,7 +154,6 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable #endif }; - var versionedDesc = new D3D12_VERSIONED_ROOT_SIGNATURE_DESC { Version = D3D_ROOT_SIGNATURE_VERSION_1_1, @@ -183,8 +170,48 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable throw new InvalidOperationException($"Failed to serialize default root signature: {errorMsg}"); } + ID3D12RootSignature* pRootSignature = default; ThrowIfFailed(_device.NativeDevice->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), - __uuidof(), _defaultRootSignature.GetVoidAddressOf())); + __uuidof(pRootSignature), (void**)&pRootSignature)); + + _defaultRootSignature.Attach(pRootSignature); + } + + public void LoadLibraryFromDisk(string? filePath) + { + ID3D12PipelineLibrary1* pLibrary = default; + + if (File.Exists(filePath)) + { + var fileBytes = File.ReadAllBytes(filePath!); + fixed (byte* pFileBytes = fileBytes) + { + ThrowIfFailed(_device.NativeDevice->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof(pLibrary), (void**)&pLibrary)); + } + } + else + { + ThrowIfFailed(_device.NativeDevice->CreatePipelineLibrary(null, 0, __uuidof(pLibrary), (void**)&pLibrary)); + } + + _library.Attach(pLibrary); + } + + public void SaveLibraryToDisk(string filePath) + { + var dir = Path.GetDirectoryName(filePath); + if (!Directory.Exists(dir)) + { + throw new InvalidOperationException($"Directory does not exist: {dir}"); + } + + var size = _library.Get()->GetSerializedSize(); + using var buffer = new UnsafeArray((int)size, Allocator.Persistent); // We use persistent heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries. + + ThrowIfFailed(_library.Get()->Serialize(buffer.GetUnsafePtr(), size)); + + using var fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None); + fs.Write(buffer.AsSpan()); } private static void ValidateReflectionData(ShaderReflectionData reflectionData) @@ -206,19 +233,30 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable { static CompileResult CompileAndValidate(ref CompilerConfig config) { - var reflectionBlob = default(IDxcBlob*); - var result = D3D12ShaderCompiler.Compile(ref config, Allocator.Persistent, &reflectionBlob).GetValueOrThrow(); + IDxcBlob* reflectionBlob = default; - if (reflectionBlob != null) + try { - var reflection = D3D12ShaderCompiler.PerformDXCReflection(reflectionBlob).GetValueOrThrow(); - ValidateReflectionData(reflection); - } + var result = D3D12ShaderCompiler.Compile(ref config, Allocator.Persistent, &reflectionBlob).GetValueOrThrow(); - return result; + if (reflectionBlob != null) + { + var reflection = D3D12ShaderCompiler.PerformDXCReflection(reflectionBlob).GetValueOrThrow(); + ValidateReflectionData(reflection); + } + + return result; + } + finally + { + if (reflectionBlob != null) + { + reflectionBlob->Release(); + } + } } - var tsResult = default(CompileResult); + CompileResult tsResult = default; var tsEntry = descriptor.taskShader; if (tsEntry.IsCreated) { @@ -304,17 +342,17 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable _ => D3D12_COMPARISON_FUNC_LESS_EQUAL }; - private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ref readonly PipelineDescriptor pipeline) + private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ZTestOptions ztest, ZWriteOptions zwrite) { - var depthEnabled = pipeline.zTest != ZTestOptions.Disabled; - var writeEnabled = pipeline.zWrite == ZWriteOptions.On; - var cmp = ToD3DCompare(pipeline.zTest); + var depthEnabled = ztest != ZTestOptions.Disabled; + var writeEnabled = zwrite == ZWriteOptions.On; + var cmp = ToD3DCompare(ztest); return D3D12_DEPTH_STENCIL_DESC.Create(depthEnabled, writeEnabled, cmp); } - private void StorePassState(ShaderPassKey id, ref readonly D3D12GraphicsCompiledResult compiled, ref readonly PipelineDescriptor pipelineDescriptor, ReadOnlySpan rtvs, TextureFormat dsv) + private GraphicsPipelineKey CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly D3D12GraphicsCompiledResult compiled) { - var rtvCount = (uint)Math.Min(rtvs.Length, D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT); + var rtvCount = (uint)Math.Min(descriptor.rtvFormats.Length, D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT); var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC { @@ -325,12 +363,12 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable SampleMask = UINT32_MAX, SampleDesc = new DXGI_SAMPLE_DESC(1, 0), NumRenderTargets = rtvCount, - DSVFormat = dsv.ToDXGIFormat(), - DepthStencilState = BuildDepthStencil(in pipelineDescriptor), + DSVFormat = descriptor.dsvFormat.ToDXGIFormat(), + DepthStencilState = BuildDepthStencil(descriptor.zTest, descriptor.zWrite), NodeMask = 0, Flags = D3D12_PIPELINE_STATE_FLAG_NONE, - BlendState = pipelineDescriptor.blend switch + BlendState = descriptor.blend switch { BlendOptions.Opaque => D3D12_BLEND_DESC.OPAQUE, BlendOptions.Alpha => D3D12_BLEND_DESC.ALPHA_BLEND, @@ -339,7 +377,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable BlendOptions.PremultipliedAlpha => D3D12_BLEND_DESC.PREMULTIPLIED, _ => D3D12_BLEND_DESC.OPAQUE }, - RasterizerState = pipelineDescriptor.cull switch + RasterizerState = descriptor.cull switch { CullOptions.Off => D3D12_RASTERIZER_DESC.CULL_NONE, CullOptions.Front => D3D12_RASTERIZER_DESC.CULL_CLOCKWISE, @@ -355,36 +393,76 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable var hash = new GraphicsPipelineHash { - id = id, - rtvCount = rtvCount, - dsvFormat = dsv, + id = descriptor.passId, + rtvCount = (uint)descriptor.rtvFormats.Length, + dsvFormat = descriptor.dsvFormat, }; - for (var i = 0; i < rtvCount && i < 6; i++) + for (var i = 0; i < rtvCount && i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) { - desc.RTVFormats[i] = rtvs[i].ToDXGIFormat(); - desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)(pipelineDescriptor.colorMask & 0x0F); - hash.rtvFormats[i] = rtvs[i]; + desc.RTVFormats[i] = descriptor.rtvFormats[i].ToDXGIFormat(); + desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)(descriptor.colorMask & 0x0F); + hash.rtvFormats[i] = descriptor.rtvFormats[i]; } var key = hash.GetKey(); - ref var existing = ref CollectionsMarshal.GetValueRefOrAddDefault(_pipelineCache, hash.GetKey(), out var exists); - if (exists) + ref var existing = ref CollectionsMarshal.GetValueRefOrAddDefault(_pipelineCache, key, out var exists); + if (!exists) { - throw new InvalidOperationException($"Pass code cache already contains an entry for key: {key}"); + existing.compileResult = compiled; + existing.psoDesc = desc; + + var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC + { + pPipelineStateSubobjectStream = &desc, + SizeInBytes = (nuint)sizeof(D3DX12_MESH_SHADER_PIPELINE_STATE_DESC) + }; + + ID3D12PipelineState* pPipelineState = default; + + char* pKeyStr = stackalloc char[GraphicsPipelineKey.KEY_STRING_LENGTH]; + var keySpan = new Span(pKeyStr, GraphicsPipelineKey.KEY_STRING_LENGTH); + key.GetString(keySpan).ThrowIfFailed(); + + var hr = _library.Get()->LoadPipeline(pKeyStr, &streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState); + if (hr == E.E_INVALIDARG) + { + // Pipeline not found in the library, create a new one. + ThrowIfFailed(_device.NativeDevice->CreatePipelineState(&streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState)); + ThrowIfFailed(_library.Get()->StorePipeline(pKeyStr, pPipelineState)); + } + else + { + ThrowIfFailed(hr); + } + + existing.pso.Attach(pPipelineState); } - existing.compileResult = compiled; - existing.psoDesc = desc; + return key; } - public void CompilePass(IPassDescriptor descriptor) + public GraphicsPipelineKey CompilePassPSO(IPassDescriptor descriptor, ReadOnlySpan rtvs, TextureFormat dsv) { + var key = default(GraphicsPipelineKey); switch (descriptor) { case FullPassDescriptor fullPass: var result = CompileAndValidateFullPass(fullPass).GetValueOrThrow(); - StorePassState(new(fullPass.Identifier), in result, in fullPass.localPipeline, [TextureFormat.B8G8R8A8_UNorm], TextureFormat.Unknown); + var psoDes = new GraphicsPSODescriptor + { + passId = new(fullPass.Identifier), + zTest = fullPass.localPipeline.zTest, + zWrite = fullPass.localPipeline.zWrite, + cull = fullPass.localPipeline.cull, + blend = fullPass.localPipeline.blend, + colorMask = fullPass.localPipeline.colorMask, + + rtvFormats = rtvs, + dsvFormat = dsv, + }; + + key = CompilePSO(in psoDes, in result); break; // Do we need to support other pass types? @@ -392,82 +470,31 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable default: break; } + + return key; } - public void CompileShader(ShaderDescriptor descriptor) + public Result> LoadGraphicsPSO(GraphicsPipelineKey key) { - foreach (var pass in descriptor.passes) + ref var cacheEntry = ref CollectionsMarshal.GetValueRefOrNullRef(_pipelineCache, key); + if (Unsafe.IsNullRef(ref cacheEntry)) { - CompilePass(pass); + return Result.Fail("Pipeline state not found in cache."); } - } - // TODO: Pipeline variants (keywords) - // TODO: Disk caching - // TODO: Async compilation - public void PreCookPipelineState() - { - foreach (var kvp in _pipelineCache) - { - var key = kvp.Key; - var state = kvp.Value; - - var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC - { - pPipelineStateSubobjectStream = &state.psoDesc, - SizeInBytes = (nuint)sizeof(D3DX12_MESH_SHADER_PIPELINE_STATE_DESC) - }; - - ComPtr pipelineState = default; - ThrowIfFailed(_device.NativeDevice->CreatePipelineState(&streamDesc, __uuidof(), pipelineState.GetVoidAddressOf())); - - var name = key.ToString(); - fixed (char* pName = name) - { - ThrowIfFailed(_library.Get()->StorePipeline(pName, pipelineState.Get())); - } - } - } - - public ID3D12PipelineState* LoadPipelineState(GraphicsPipelineKey key) - { - var name = key.ToString(); - var state = _pipelineCache[key]; - var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC - { - pPipelineStateSubobjectStream = &state.psoDesc, - SizeInBytes = (nuint)sizeof(D3DX12_MESH_SHADER_PIPELINE_STATE_DESC) - }; - - fixed (char* pName = name) - { - ID3D12PipelineState* pipelineState; - ThrowIfFailed(_library.Get()->LoadPipeline(pName, &streamDesc, __uuidof(), (void**)&pipelineState)); - - return pipelineState; - } - } - - public void SaveLibraryToDisk(string filePath) - { - var size = _library.Get()->GetSerializedSize(); - using var buffer = new UnsafeArray((int)size, Allocator.Persistent); // We use persistent heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries. - - ThrowIfFailed(_library.Get()->Serialize(buffer.GetUnsafePtr(), size)); - - var fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None); - fs.Write(buffer.AsSpan()); + return new Ptr(cacheEntry.pso.Get()); } public void Dispose() { - _defaultRootSignature.Dispose(); - foreach (var kvp in _pipelineCache) { kvp.Value.Dispose(); } + _pipelineCache.Clear(); + + _defaultRootSignature.Dispose(); _library.Dispose(); } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/D3D12/D3D12RenderDevice.cs b/Ghost.Graphics/D3D12/D3D12RenderDevice.cs index d116c6a..16e718c 100644 --- a/Ghost.Graphics/D3D12/D3D12RenderDevice.cs +++ b/Ghost.Graphics/D3D12/D3D12RenderDevice.cs @@ -47,16 +47,20 @@ internal unsafe class D3D12RenderDevice : IRenderDevice private void InitializeDevice() { + IDXGIFactory7* pFactory = default; #if DEBUG - CreateDXGIFactory2(TRUE, __uuidof(), _dxgiFactory.GetVoidAddressOf()); + CreateDXGIFactory2(TRUE, __uuidof(pFactory), (void**)&pFactory); #else - CreateDXGIFactory2(FALSE, __uuidof(), _dxgiFactory.GetVoidAddressOf()); + CreateDXGIFactory2(FALSE, __uuidof(pFactory), (void**)&pFactory); #endif + _dxgiFactory.Attach(pFactory); + + ID3D12Device14* pDevice = default; ComPtr adapter = default; for (uint adapterIndex = 0; - _dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof(), adapter.ReleaseAndGetVoidAddressOf()).SUCCEEDED; + _dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, adapter.IID(), adapter.ReleaseAndGetVoidAddressOf()).SUCCEEDED; adapterIndex++) { DXGI_ADAPTER_DESC1 desc = default; @@ -68,18 +72,20 @@ internal unsafe class D3D12RenderDevice : IRenderDevice continue; } - if (D3D12CreateDevice((IUnknown*)adapter.Get(), D3D_FEATURE_LEVEL_12_0, __uuidof(), _device.GetVoidAddressOf()).SUCCEEDED) + if (D3D12CreateDevice((IUnknown*)adapter.Get(), D3D_FEATURE_LEVEL_12_0, __uuidof(pDevice), (void**)&pDevice).SUCCEEDED) { _adapter = adapter.Move(); break; } } - if (_device.Get() == null) + if (pDevice == null) { adapter.Dispose(); // Dispose the last adapter we tried. If the operation succeeded, we would have moved it. throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0"); } + + _device.Attach(pDevice); } public void Dispose() diff --git a/Ghost.Graphics/D3D12/D3D12Renderer.cs b/Ghost.Graphics/D3D12/D3D12Renderer.cs index afb136f..6ddbee8 100644 --- a/Ghost.Graphics/D3D12/D3D12Renderer.cs +++ b/Ghost.Graphics/D3D12/D3D12Renderer.cs @@ -1,8 +1,8 @@ using Ghost.Core; using Ghost.Graphics.D3D12.Utilities; -using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Misaki.HighPerformance.Mathematics; +using Ghost.Graphics.Core; namespace Ghost.Graphics.D3D12; diff --git a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs index cce2840..f2ad7ce 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceAllocator.cs @@ -1,7 +1,6 @@ using Ghost.Core; using Ghost.Core.Graphics; using Ghost.Core.Utilities; -using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.CompilerServices; @@ -11,6 +10,7 @@ using static TerraFX.Aliases.D3D12_Alias; using static TerraFX.Aliases.D3D12MA_Alias; using static TerraFX.Aliases.DXGI_Alias; using static TerraFX.Interop.DirectX.D3D12MemAlloc; +using Ghost.Graphics.Core; namespace Ghost.Graphics.D3D12; @@ -22,8 +22,8 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable private ComPtr _allocator; - private readonly D3D12RenderDevice _device; private readonly RenderSystem _renderSystem; + private readonly D3D12RenderDevice _device; private readonly D3D12DescriptorAllocator _descriptorAllocator; private readonly D3D12ResourceDatabase _resourceDatabase; @@ -82,7 +82,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable return handle; } - private D3D12_SHADER_RESOURCE_VIEW_DESC CreateSrvDesc(ID3D12Resource* pResource, bool isCubeMap, uint mipLevels, uint arraySize) + private static D3D12_SHADER_RESOURCE_VIEW_DESC CreateSrvDesc(ID3D12Resource* pResource, bool isCubeMap, uint mipLevels, uint arraySize) { var resourceDesc = pResource->GetDesc(); var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC @@ -93,17 +93,6 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable switch (resourceDesc.Dimension) { - case D3D12_RESOURCE_DIMENSION_BUFFER: - srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; - srvDesc.Buffer = new D3D12_BUFFER_SRV - { - FirstElement = 0, - NumElements = (uint)(resourceDesc.Width / 4), - StructureByteStride = 0, - Flags = D3D12_BUFFER_SRV_FLAG_RAW, - }; - break; - case D3D12_RESOURCE_DIMENSION_TEXTURE1D: if (resourceDesc.DepthOrArraySize > 1) { @@ -180,7 +169,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable return srvDesc; } - private D3D12_RENDER_TARGET_VIEW_DESC CreateRtvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0) + private static D3D12_RENDER_TARGET_VIEW_DESC CreateRtvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0) { var resourceDesc = pResource->GetDesc(); var rtvDesc = new D3D12_RENDER_TARGET_VIEW_DESC(); @@ -276,7 +265,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable return rtvDesc; } - private D3D12_DEPTH_STENCIL_VIEW_DESC CreateDsvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, D3D12_DSV_FLAGS flags = D3D12_DSV_FLAG_NONE) + private static D3D12_DEPTH_STENCIL_VIEW_DESC CreateDsvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, D3D12_DSV_FLAGS flags = D3D12_DSV_FLAG_NONE) { var resourceDesc = pResource->GetDesc(); var dsvDesc = new D3D12_DEPTH_STENCIL_VIEW_DESC @@ -344,6 +333,88 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable return dsvDesc; } + private static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateUavDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0) + { + var resourceDesc = pResource->GetDesc(); + var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC + { + Format = resourceDesc.Format + }; + + switch (resourceDesc.Dimension) + { + case D3D12_RESOURCE_DIMENSION_TEXTURE1D: + if (resourceDesc.DepthOrArraySize > 1) + { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1DARRAY; + uavDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_UAV + { + MipSlice = mipSlice, + FirstArraySlice = firstArraySlice, + ArraySize = resourceDesc.ArraySize() - firstArraySlice + }; + } + else + { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D; + uavDesc.Texture1D = new D3D12_TEX1D_UAV + { + MipSlice = mipSlice + }; + } + break; + + case D3D12_RESOURCE_DIMENSION_TEXTURE2D: + if (resourceDesc.DepthOrArraySize > 1) + { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; + uavDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_UAV + { + MipSlice = mipSlice, + FirstArraySlice = firstArraySlice, + ArraySize = resourceDesc.ArraySize() - firstArraySlice, + PlaneSlice = planeSlice + }; + } + else + { + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + uavDesc.Texture2D = new D3D12_TEX2D_UAV + { + MipSlice = mipSlice, + PlaneSlice = planeSlice + }; + } + break; + + case D3D12_RESOURCE_DIMENSION_TEXTURE3D: + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D; + uavDesc.Texture3D = new D3D12_TEX3D_UAV + { + MipSlice = mipSlice, + FirstWSlice = firstArraySlice, + WSize = resourceDesc.Depth() - firstArraySlice + }; + break; + + case D3D12_RESOURCE_DIMENSION_BUFFER: + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + uavDesc.Buffer = new D3D12_BUFFER_UAV + { + FirstElement = 0, + NumElements = (uint)(resourceDesc.Width / 4), // Assuming R32_TYPELESS RAW + StructureByteStride = 0, + Flags = D3D12_BUFFER_UAV_FLAG_RAW + }; + break; + + default: + throw new ArgumentException($"Unsupported texture dimension for UAV: {resourceDesc.Dimension}"); + } + + return uavDesc; + } + public Handle CreateTexture(ref readonly TextureDesc desc, bool isTemp = false) { CheckTexture2DSize(desc.Width, desc.Height); @@ -434,17 +505,10 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess)) { resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp); - var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC - { - ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D, - Format = d3d12Format, - Texture2D = new D3D12_TEX2D_UAV - { - MipSlice = 0, - PlaneSlice = 0, - } - }; - _device.NativeDevice->CreateUnorderedAccessView(allocation.Get()->GetResource(), null, &uavDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav)); + var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav); + var uavDesc = CreateUavDesc(allocation.Get()->GetResource()); + + _device.NativeDevice->CreateUnorderedAccessView(allocation.Get()->GetResource(), null, &uavDesc, cpuHandle); } var handle = TrackResource(allocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), isTemp); @@ -458,11 +522,14 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable return CreateTexture(ref textureDesc, isTemp); } + // FIX: This is not correct! Fix it! public Handle CreateBuffer(ref readonly BufferDesc desc, bool isTemp = false) { CheckBufferSize(desc.Size); var resourceDescription = D3D12_RESOURCE_DESC.Buffer(desc.Size, ConvertBufferUsage(desc.Usage)); + resourceDescription.Format = desc.Usage.HasFlag(BufferUsage.Raw) ? DXGI_FORMAT_R32_TYPELESS : DXGI_FORMAT_UNKNOWN; + var allocationDesc = new D3D12MA_ALLOCATION_DESC { HeapType = ConvertMemoryType(desc.MemoryType), @@ -473,7 +540,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable ComPtr allocation = default; ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDescription, initialState, null, allocation.GetAddressOf(), Win32Utility.IID_NULL, null)); - + var resourceDescriptor = ResourceViewGroup.Invalid; if (desc.Usage.HasFlag(BufferUsage.ShaderResource)) { @@ -752,4 +819,4 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable GC.SuppressFinalize(this); } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs index b8774a6..39cced6 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs @@ -1,5 +1,4 @@ using Ghost.Core; -using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.LowLevel.Buffer; @@ -8,6 +7,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; +using Ghost.Graphics.Core; namespace Ghost.Graphics.D3D12; @@ -369,7 +369,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable var id = _shaders.Count; _shaders.Add(shader); - return new Identifier(id); + return new Identifier(id); } public bool HasShader(Identifier id) @@ -487,4 +487,4 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable GC.SuppressFinalize(this); } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs b/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs index fa017ec..861f821 100644 --- a/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs +++ b/Ghost.Graphics/D3D12/D3D12ShaderCompiler.cs @@ -198,6 +198,7 @@ internal static unsafe class D3D12ShaderCompiler public static Result Compile(ref readonly CompilerConfig config, Allocator allocator, IDxcBlob** ppReflectionBlob) { + // NOTE: Should we cache the compiler and utils instances for better performance? using ComPtr compiler = default; using ComPtr utils = default; using ComPtr includeHandler = default; @@ -206,8 +207,8 @@ internal static unsafe class D3D12ShaderCompiler var pDxcCompiler = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcCompiler); var pDxcUtils = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcUtils); - ThrowIfFailed(DxcCreateInstance(pDxcCompiler, __uuidof(), compiler.GetVoidAddressOf())); - ThrowIfFailed(DxcCreateInstance(pDxcUtils, __uuidof(), utils.GetVoidAddressOf())); + ThrowIfFailed(DxcCreateInstance(pDxcCompiler, compiler.IID(), compiler.PPV())); + ThrowIfFailed(DxcCreateInstance(pDxcUtils, utils.IID(), utils.PPV())); //includeHandler.Get()->LoadSource(); utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()); @@ -216,7 +217,7 @@ internal static unsafe class D3D12ShaderCompiler using ComPtr sourceBlob = default; if (utils.Get()->LoadFile(config.shaderPath.AsSpan().GetUnsafePtr(), null, sourceBlob.GetAddressOf()).FAILED) { - return Result.Fail($"Failed to load shader file: {config.shaderPath}"); + return Result.Fail($"Failed to load shader file: {config.shaderPath}"); } var argsArray = GetCompilerArguments(in config); @@ -237,7 +238,7 @@ internal static unsafe class D3D12ShaderCompiler Encoding = DXC.DXC_CP_UTF8 }; - ThrowIfFailed(compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler.Get(), __uuidof(), result.GetVoidAddressOf())); + ThrowIfFailed(compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler.Get(), result.IID(), result.PPV())); // Check compilation result HRESULT hrStatus; @@ -251,11 +252,11 @@ internal static unsafe class D3D12ShaderCompiler if (errorBlob.Get() != null) { var errorMessage = Marshal.PtrToStringUni((IntPtr)errorBlob.Get()->GetBufferPointer()); - return Result.Fail($"DXC shader compilation failed:\n{errorMessage}"); + return Result.Fail($"DXC shader compilation failed:\n{errorMessage}"); } else { - return Result.Fail("DXC shader compilation failed with unknown error."); + return Result.Fail("DXC shader compilation failed with unknown error."); } } @@ -300,7 +301,7 @@ internal static unsafe class D3D12ShaderCompiler // Create DXC utils to parse reflection data var pDxcUtils = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcUtils); using ComPtr utils = default; - ThrowIfFailed(DxcCreateInstance(pDxcUtils, __uuidof(), utils.GetVoidAddressOf())); + ThrowIfFailed(DxcCreateInstance(pDxcUtils, utils.IID(), utils.PPV())); // Create reflection interface from blob var reflectionBuffer = new DxcBuffer @@ -311,7 +312,7 @@ internal static unsafe class D3D12ShaderCompiler }; using ComPtr reflection = default; - ThrowIfFailed(utils.Get()->CreateReflection(&reflectionBuffer, __uuidof(), reflection.GetVoidAddressOf())); + ThrowIfFailed(utils.Get()->CreateReflection(&reflectionBuffer, reflection.IID(), reflection.PPV())); D3D12_SHADER_DESC shaderDesc; ThrowIfFailed(reflection.Get()->GetDesc(&shaderDesc)); @@ -326,7 +327,7 @@ internal static unsafe class D3D12ShaderCompiler var resourceName = Marshal.PtrToStringUTF8((IntPtr)bindDesc.Name); if (resourceName == null) { - return Result.Fail("Failed to get resource name from reflection data."); + return Result.Fail("Failed to get resource name from reflection data."); } switch (bindDesc.Type) @@ -391,4 +392,4 @@ internal static unsafe class D3D12ShaderCompiler return reflectionData; } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/D3D12/D3D12SwapChain.cs b/Ghost.Graphics/D3D12/D3D12SwapChain.cs index e16cc68..23816c4 100644 --- a/Ghost.Graphics/D3D12/D3D12SwapChain.cs +++ b/Ghost.Graphics/D3D12/D3D12SwapChain.cs @@ -2,7 +2,6 @@ using Ghost.Core; using Ghost.Core.Utilities; using Ghost.Graphics.Contracts; using Ghost.Graphics.D3D12.Utilities; -using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; @@ -12,6 +11,8 @@ using TerraFX.Interop.Windows; using static TerraFX.Aliases.DXGI_Alias; +using Ghost.Graphics.Core; + namespace Ghost.Graphics.D3D12; /// @@ -105,10 +106,10 @@ internal unsafe class D3D12SwapChain : ISwapChain throw new ArgumentException("Unsupported swap chain target type."); } - if (tempSwapChain.Get()->QueryInterface(__uuidof(), _swapChain.GetVoidAddressOf()).FAILED) - { - throw new InvalidOperationException("Failed to create IDXGISwapChain4 interface."); - } + IDXGISwapChain4* pSwapChain = default; + tempSwapChain.Get()->QueryInterface(__uuidof(pSwapChain), (void**)&pSwapChain); + + _swapChain.Attach(pSwapChain); } private void CreateBackBuffers() @@ -116,10 +117,10 @@ internal unsafe class D3D12SwapChain : ISwapChain for (uint i = 0; i < BufferCount; i++) { ComPtr backBuffer = default; - _swapChain.Get()->GetBuffer(i, __uuidof(), backBuffer.GetVoidAddressOf()); + _swapChain.Get()->GetBuffer(i, backBuffer.IID(), backBuffer.PPV()); backBuffer.Get()->SetName($"SwapChain_BackBuffer_{i}"); - _backBuffers[i] = _resourceDatabase.ImportExternalResource(backBuffer.Move(), ResourceState.Present).AsTexture(); + _backBuffers[i] = _resourceDatabase.ImportExternalResource(backBuffer, ResourceState.Present).AsTexture(); } } @@ -182,4 +183,4 @@ internal unsafe class D3D12SwapChain : ISwapChain _backBuffers.Dispose(); _disposed = true; } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12DescriptorHeap.cs b/Ghost.Graphics/D3D12/Utilities/D3D12DescriptorHeap.cs index 69576b2..5041a61 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12DescriptorHeap.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12DescriptorHeap.cs @@ -1,4 +1,5 @@ -using Misaki.HighPerformance.LowLevel.Collections; +using Ghost.Core.Utilities; +using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; using System.Diagnostics; using System.Numerics; @@ -277,33 +278,33 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable NodeMask = 0 }; - fixed (void* heapPtr = &_heap) + ID3D12DescriptorHeap* pHeap = default; + var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof(pHeap), (void**)&pHeap); + if (hr.FAILED) { - var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof(), (void**)heapPtr); - if (hr.FAILED) - { - return false; - } + return false; } + _heap.Attach(pHeap); + _startCpuHandle = _heap.Get()->GetCPUDescriptorHandleForHeapStart(); _allocatedDescriptors.Resize(numDescriptors); if (ShaderVisible) { - heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + ID3D12DescriptorHeap* pShaderVisibleHeap = default; - fixed (void* heapPtr = &_shaderVisibleHeap) + heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof(pShaderVisibleHeap), (void**)&pShaderVisibleHeap); + if (hr.FAILED) { - var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof(), (void**)heapPtr); - if (hr.FAILED) - { - return false; - } + return false; } - _startCpuHandleShaderVisible = _shaderVisibleHeap.Get()->GetCPUDescriptorHandleForHeapStart(); - _startGpuHandleShaderVisible = _shaderVisibleHeap.Get()->GetGPUDescriptorHandleForHeapStart(); + _startCpuHandleShaderVisible = pShaderVisibleHeap->GetCPUDescriptorHandleForHeapStart(); + _startGpuHandleShaderVisible = pShaderVisibleHeap->GetGPUDescriptorHandleForHeapStart(); + + _shaderVisibleHeap.Attach(pShaderVisibleHeap); } return true; diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs b/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs index 24b05f2..6f2ac8b 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12PipelineResource.cs @@ -1,6 +1,6 @@ -using Ghost.Graphics.Data; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using TerraFX.Interop.DirectX; +using Ghost.Graphics.Core; namespace Ghost.Graphics.D3D12.Utilities; @@ -9,11 +9,11 @@ internal unsafe static class D3D12PipelineResource public const int BACK_BUFFER_COUNT = 2; private readonly static D3D12_INPUT_ELEMENT_DESC[] s_inputElementDescs = [ - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.position.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, - new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.color.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Ghost.Graphics.Core.Semantic.position.GetUnsafePointer(), SemanticIndex = 0u, Format = Ghost.Graphics.Core.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Ghost.Graphics.Core.Semantic.normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Ghost.Graphics.Core.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Ghost.Graphics.Core.Semantic.tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Ghost.Graphics.Core.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Ghost.Graphics.Core.Semantic.uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Ghost.Graphics.Core.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, + new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Ghost.Graphics.Core.Semantic.color.GetUnsafePointer(), SemanticIndex = 0u, Format = Ghost.Graphics.Core.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 }, ]; public const DXGI_FORMAT SWAP_CHAIN_BACK_BUFFER_FORMAT = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM; diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs index 616860f..305e1d2 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs @@ -144,9 +144,13 @@ internal static class D3D12_DEPTH_STENCILOP_DESC_Extensions internal unsafe static class D3D12Utility { - public static void SetName(ref this ID3D12Resource resource, ReadOnlySpan name) + public static void SetName(ref this T obj, ReadOnlySpan name) + where T : unmanaged, ID3D12Object.Interface { - resource.SetName(name.GetUnsafePtr()); + fixed (char* pName = name) + { + obj.SetName(pName); + } } public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension) @@ -187,4 +191,22 @@ internal unsafe static class D3D12Utility _ => TextureFormat.Unknown, }; } -} \ No newline at end of file + + public static D3D12_RESOURCE_STATES ToD3D12States(this ResourceState state) + { + return state switch + { + ResourceState.Common or ResourceState.Present => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COMMON, + ResourceState.VertexAndConstantBuffer => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, + ResourceState.IndexBuffer => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_INDEX_BUFFER, + ResourceState.RenderTarget => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_RENDER_TARGET, + ResourceState.UnorderedAccess => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + ResourceState.DepthWrite => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_DEPTH_WRITE, + ResourceState.DepthRead => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_DEPTH_READ, + ResourceState.PixelShaderResource => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + ResourceState.CopyDest => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COPY_DEST, + ResourceState.CopySource => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COPY_SOURCE, + _ => throw new ArgumentException($"Unknown resource state: {state}") + }; + } +} diff --git a/Ghost.Graphics/Data/Camera.cs b/Ghost.Graphics/Data/Camera.cs deleted file mode 100644 index d4ce3ee..0000000 --- a/Ghost.Graphics/Data/Camera.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Ghost.Graphics.Data; - -public class Camera -{ -} \ No newline at end of file diff --git a/Ghost.Graphics/RHI/Common.cs b/Ghost.Graphics/RHI/Common.cs index c16d36e..800c208 100644 --- a/Ghost.Graphics/RHI/Common.cs +++ b/Ghost.Graphics/RHI/Common.cs @@ -1,9 +1,12 @@ -using Ghost.Graphics.D3D12.Utilities; +using Ghost.Core; +using Ghost.Core.Graphics; +using Ghost.Graphics.D3D12.Utilities; using Misaki.HighPerformance.Utilities; using System.IO.Hashing; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using TerraFX.Interop.DirectX; +using Ghost.Graphics.Core; namespace Ghost.Graphics.RHI; @@ -35,6 +38,8 @@ public readonly struct ShaderPassKey public readonly struct GraphicsPipelineKey { + public const int KEY_STRING_LENGTH = 17; // 16 chars + null terminator + public readonly ulong value; public GraphicsPipelineKey(ulong value) @@ -42,6 +47,17 @@ public readonly struct GraphicsPipelineKey this.value = value; } + public Result GetString(Span destination) + { + if (!value.TryFormat(destination, out _, "X16")) + { + return Result.Fail("Failed to format GraphicsPipelineKey to string."); + } + + destination[16] = '\0'; + return Result.Success(); + } + public override string ToString() { return value.ToString("X16"); @@ -55,14 +71,14 @@ public readonly struct GraphicsPipelineKey internal struct GraphicsPipelineHash { - [InlineArray(8)] + [InlineArray(D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)] public struct rtv_array { public TextureFormat rtvFormats; } - public rtv_array rtvFormats; public ShaderPassKey id; + public rtv_array rtvFormats; public uint rtvCount; public TextureFormat dsvFormat; // Do we need to store blend state? @@ -70,12 +86,12 @@ internal struct GraphicsPipelineHash public readonly GraphicsPipelineKey GetKey() { - Span data = stackalloc ulong[3 + 8]; + Span data = stackalloc ulong[3 + D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT]; data[0] = id.value; data[1] = rtvCount; data[2] = (ulong)dsvFormat; - for (var i = 0; i < 8; i++) + for (var i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) { data[3 + i] = (ulong)rtvFormats[i]; } @@ -85,6 +101,18 @@ internal struct GraphicsPipelineHash } } +public ref struct GraphicsPSODescriptor +{ + public ShaderPassKey passId; + public ZTestOptions zTest; + public ZWriteOptions zWrite; + public CullOptions cull; + public BlendOptions blend; + public uint colorMask; + + public ReadOnlySpan rtvFormats; + public TextureFormat dsvFormat; +} public struct ViewportDesc @@ -112,6 +140,20 @@ public struct SubResourceData public nint slicePitch; } +public struct PassRenderTargetDesc +{ + public Handle texture; + public Color128 clearColor; +} + +public struct PassDepthStencilDesc +{ + public Handle texture; + public float clearDepth; + public byte clearStencil; +} + + [StructLayout(LayoutKind.Explicit)] public struct ResourceDesc { @@ -501,9 +543,6 @@ public struct SwapChainTarget } } -/// -/// Swap chain target types -/// public enum SwapChainTargetType { WindowHandle, @@ -627,6 +666,13 @@ public enum IndexType UInt32 } +public enum PrimitiveTopology +{ + Point, + Line, + Triangle, +} + // SDL compiler internal ref struct CompilerConfig @@ -671,4 +717,4 @@ internal enum ShaderStage MeshShader, PixelShader, ComputeShader -} \ No newline at end of file +} diff --git a/Ghost.Graphics/RHI/ICommandBuffer.cs b/Ghost.Graphics/RHI/ICommandBuffer.cs index 3011cc3..19d04d6 100644 --- a/Ghost.Graphics/RHI/ICommandBuffer.cs +++ b/Ghost.Graphics/RHI/ICommandBuffer.cs @@ -1,6 +1,5 @@ using Ghost.Core; -using Ghost.Graphics.Data; -using TerraFX.Interop.DirectX; +using Ghost.Graphics.Core; namespace Ghost.Graphics.RHI; @@ -9,25 +8,52 @@ namespace Ghost.Graphics.RHI; /// public interface ICommandBuffer : IDisposable { - public CommandBufferType Type + /// + /// Gets the type of the command buffer. + /// + CommandBufferType Type { get; } - public bool IsEmpty + /// + /// Indicates whether the command buffer contains any recorded commands. + /// + bool IsEmpty { get; } + /// + /// Gets the name of the command buffer. + /// + string Name + { + get; + set; + } + /// /// Begins recording commands into this command buffer /// - public void Begin(); + void Begin(); /// /// Ends recording commands and prepares for submission /// - public void End(); + void End(); + + /// + /// Sets the viewport for rendering + /// + /// Viewport to set + void SetViewport(ViewportDesc viewport); + + /// + /// Sets the scissor rectangle + /// + /// Scissor rectangle to set + void SetScissorRect(RectDesc rect); /// /// Sets the optional render targets and optional depth target for subsequent rendering operations. @@ -39,32 +65,20 @@ public interface ICommandBuffer : IDisposable /// A read-only span of handles to textures that will be used as render targets. /// The order of handles determines the order in which render targets are bound. /// A handle to the texture to be used as the depth target. Specify a invalid handle if no depth target is required. - public void SetRenderTargets(ReadOnlySpan> renderTargets, Handle depthTarget); + void SetRenderTargets(ReadOnlySpan> renderTargets, Handle depthTarget); /// /// Begins a render pass with the specified render target /// - /// Render target to render into (can be invalid) - /// Depth target to use (can be invalid) - /// Color to clear the render target with - public void BeginRenderPass(Handle renderTarget, Handle depthTarget, Color128 clearColor); + /// Render target descriptions + /// Depth stencil description + /// Whether UAV writes are allowed during the render pass + void BeginRenderPass(ReadOnlySpan rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false); /// /// Ends the current render pass /// - public void EndRenderPass(); - - /// - /// Sets the viewport for rendering - /// - /// Viewport to set - public void SetViewport(ViewportDesc viewport); - - /// - /// Sets the scissor rectangle - /// - /// Scissor rectangle to set - public void SetScissorRect(RectDesc rect); + void EndRenderPass(); /// /// Inserts a resource barrier for state transitions @@ -72,25 +86,61 @@ public interface ICommandBuffer : IDisposable /// Resource to transition /// Current resource state /// Target resource state - public void ResourceBarrier(Handle resource, ResourceState before, ResourceState after); - - /// - /// Sets the graphics root signature - /// - /// Root signature to set - public void SetRootSignature(IRootSignature rootSignature); + void ResourceBarrier(Handle resource, ResourceState before, ResourceState after); /// /// Sets the pipeline state object /// - /// Pipeline state to set - public void SetPipelineState(IShaderPipeline pipelineState); + /// Pipeline state to set + void SetPipelineState(GraphicsPipelineKey pipelineKey); - public void SetVertexBuffer(uint slot, Handle buffer, ulong offset = 0); - public void SetIndexBuffer(Handle buffer, IndexType type, ulong offset = 0); + /// + /// Sets the constant buffer view for the specified slot in the graphics pipeline. + /// + /// The zero-based index of the slot to bind the constant buffer view to. + /// A graphics buffer to use as the constant buffer view. + void SetConstantBufferView(uint slot, Handle buffer); - public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0); - public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0); + /// + /// Binds a vertex buffer to the specified slot for subsequent draw calls. + /// + /// The vertex buffer slot to bind to. + /// The handle to the graphics buffer containing vertex data. + /// The offset in bytes from the start of the buffer. + void SetVertexBuffer(uint slot, Handle buffer, ulong offset = 0); + + /// + /// Binds an index buffer for indexed drawing. + /// + /// The handle to the graphics buffer containing index data. + /// The type of indices (e.g., 16-bit or 32-bit). + /// The offset in bytes from the start of the buffer. + void SetIndexBuffer(Handle buffer, IndexType type, ulong offset = 0); + + /// + /// Sets the primitive topology to be used for subsequent drawing operations. + /// + /// The primitive topology that determines how the input vertices are interpreted during rendering. + void SetPrimitiveTopology(PrimitiveTopology topology); + + /// + /// Issues a non-indexed draw call. + /// + /// Number of vertices to draw. + /// Number of instances to draw. + /// Index of the first vertex to draw. + /// Index of the first instance to draw. + void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0); + + /// + /// Issues an indexed draw call. + /// + /// Number of indices to draw. + /// Number of instances to draw. + /// Index of the first index to draw. + /// Value added to each index before indexing the vertex buffer. + /// Index of the first instance to draw. + void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0); /// /// Dispatches compute threads @@ -98,7 +148,20 @@ public interface ICommandBuffer : IDisposable /// Thread groups in X dimension /// Thread groups in Y dimension /// Thread groups in Z dimension - public void Dispatch(uint threadGroupCountX, uint threadGroupCountY = 1, uint threadGroupCountZ = 1); + void DispatchCompute(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ); + + /// + /// Dispatches mesh shader threads + /// + /// Thread groups in X dimension + /// Thread groups in Y dimension + /// Thread groups in Z dimension + void DispatchMesh(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ); + + /// + /// Dispatches ray tracing threads + /// + void DispatchRay(); /// /// Uploads the specified data to the buffer represented by the given handle. @@ -107,7 +170,7 @@ public interface ICommandBuffer : IDisposable /// A handle to the buffer that will receive the uploaded data. /// A read-only span containing the data to upload to the buffer. The span must contain elements of type /// . - public void Upload(Handle buffer, ReadOnlySpan data) + void Upload(Handle buffer, ReadOnlySpan data) where T : unmanaged; /// @@ -118,7 +181,7 @@ public interface ICommandBuffer : IDisposable /// A reference to the structure containing the subresource data to upload. The data must match the format and layout expected by the texture. /// The number of subresources to upload, starting from . /// Must be greater than zero and not exceed the remaining subresources in the texture. - public void Upload(Handle texture, params ReadOnlySpan subresources); + void Upload(Handle texture, params ReadOnlySpan subresources); /// /// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer. @@ -128,26 +191,5 @@ public interface ICommandBuffer : IDisposable /// The byte offset in the destination buffer at which to begin writing. Must be zero or greater. /// The byte offset in the source buffer at which to begin reading. Must be zero or greater. /// The number of bytes to copy. If zero, copies the remaining bytes from the source buffer starting at . - public void CopyBuffer(Handle dest, Handle src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0); -} - -internal static class ResourceStateExtensions -{ - public static D3D12_RESOURCE_STATES ToD3D12States(this ResourceState state) - { - return state switch - { - ResourceState.Common or ResourceState.Present => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COMMON, - ResourceState.VertexAndConstantBuffer => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, - ResourceState.IndexBuffer => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_INDEX_BUFFER, - ResourceState.RenderTarget => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_RENDER_TARGET, - ResourceState.UnorderedAccess => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_UNORDERED_ACCESS, - ResourceState.DepthWrite => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_DEPTH_WRITE, - ResourceState.DepthRead => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_DEPTH_READ, - ResourceState.PixelShaderResource => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, - ResourceState.CopyDest => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COPY_DEST, - ResourceState.CopySource => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COPY_SOURCE, - _ => throw new ArgumentException($"Unknown resource state: {state}") - }; - } + void CopyBuffer(Handle dest, Handle src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0); } diff --git a/Ghost.Graphics/RHI/IPipelineLibrary.cs b/Ghost.Graphics/RHI/IPipelineLibrary.cs index 5f63b00..2f5c0ed 100644 --- a/Ghost.Graphics/RHI/IPipelineLibrary.cs +++ b/Ghost.Graphics/RHI/IPipelineLibrary.cs @@ -15,8 +15,11 @@ public interface IShaderPipeline public interface IPipelineLibrary { - void CompilePass(IPassDescriptor descriptor); - void CompileShader(ShaderDescriptor descriptor); - void PreCookPipelineState(); + /// + /// Load pipeline library from disk. + /// + /// File path. If null, load default library. + void LoadLibraryFromDisk(string? filePath); void SaveLibraryToDisk(string filePath); -} \ No newline at end of file + GraphicsPipelineKey CompilePassPSO(IPassDescriptor descriptor, ReadOnlySpan rtvs, TextureFormat dsv); +} diff --git a/Ghost.Graphics/RHI/IRenderTypes.cs b/Ghost.Graphics/RHI/IRenderTypes.cs deleted file mode 100644 index 74a467f..0000000 --- a/Ghost.Graphics/RHI/IRenderTypes.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Ghost.Graphics.RHI; - -/// -/// Root signature interface -/// -public interface IRootSignature : IDisposable -{ - /// - /// Root signature name for debugging - /// - string Name - { - get; set; - } -} \ No newline at end of file diff --git a/Ghost.Graphics/RHI/IRenderer.cs b/Ghost.Graphics/RHI/IRenderer.cs index 29a8452..d2aae47 100644 --- a/Ghost.Graphics/RHI/IRenderer.cs +++ b/Ghost.Graphics/RHI/IRenderer.cs @@ -1,6 +1,6 @@ using Ghost.Core; -using Ghost.Graphics.Data; using Misaki.HighPerformance.Mathematics; +using Ghost.Graphics.Core; namespace Ghost.Graphics.RHI; diff --git a/Ghost.Graphics/RHI/IResourceAllocator.cs b/Ghost.Graphics/RHI/IResourceAllocator.cs index e0b1060..d293736 100644 --- a/Ghost.Graphics/RHI/IResourceAllocator.cs +++ b/Ghost.Graphics/RHI/IResourceAllocator.cs @@ -1,7 +1,7 @@ using Ghost.Core; using Ghost.Core.Graphics; -using Ghost.Graphics.Data; using Misaki.HighPerformance.LowLevel.Collections; +using Ghost.Graphics.Core; namespace Ghost.Graphics.RHI; diff --git a/Ghost.Graphics/RHI/IResourceDatabase.cs b/Ghost.Graphics/RHI/IResourceDatabase.cs index f80f5b9..15af3d3 100644 --- a/Ghost.Graphics/RHI/IResourceDatabase.cs +++ b/Ghost.Graphics/RHI/IResourceDatabase.cs @@ -1,5 +1,5 @@ using Ghost.Core; -using Ghost.Graphics.Data; +using Ghost.Graphics.Core; namespace Ghost.Graphics.RHI; @@ -150,8 +150,6 @@ public interface IResourceDatabase /// The identifier of the shader to release. Must refer to a valid, previously created shader. void ReleaseShader(Identifier id); - // TODO: Use xxhash3 to generate passKey from string id. - /// /// Adds a shader pass to the collection using the specified identifier. /// diff --git a/Ghost.Graphics/RHI/ISwapChain.cs b/Ghost.Graphics/RHI/ISwapChain.cs index 7868d72..2d1cb4a 100644 --- a/Ghost.Graphics/RHI/ISwapChain.cs +++ b/Ghost.Graphics/RHI/ISwapChain.cs @@ -1,5 +1,5 @@ using Ghost.Core; -using Ghost.Graphics.Data; +using Ghost.Graphics.Core; namespace Ghost.Graphics.RHI; diff --git a/Ghost.Graphics/RHI/RHIUtility.cs b/Ghost.Graphics/RHI/RHIUtility.cs index 997f9e0..565cb91 100644 --- a/Ghost.Graphics/RHI/RHIUtility.cs +++ b/Ghost.Graphics/RHI/RHIUtility.cs @@ -1,6 +1,4 @@ -using TerraFX.Interop.DirectX; - -namespace Ghost.Graphics.RHI; +namespace Ghost.Graphics.RHI; internal static class RHIUtility { @@ -122,4 +120,4 @@ internal static class RHIUtility slicePitch = rowPitch * height; } } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs index ac65412..646e0c9 100644 --- a/Ghost.Graphics/RenderPasses/MeshRenderPass.cs +++ b/Ghost.Graphics/RenderPasses/MeshRenderPass.cs @@ -1,10 +1,10 @@ using Ghost.Core; using Ghost.Graphics.Contracts; -using Ghost.Graphics.Data; using Ghost.Graphics.RHI; using Ghost.Graphics.Utilities; using Ghost.SDL.Compiler; using Misaki.HighPerformance.Image; +using Ghost.Graphics.Core; namespace Ghost.Graphics.RenderPasses; @@ -26,12 +26,11 @@ internal unsafe class MeshRenderPass : IRenderPass "C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg" ]; - public void Initialize(ref readonly RenderingContext ctx, IResourceAllocator resourceAllocator, IPipelineLibrary stateController) + public void Initialize(ref readonly RenderingContext ctx, IResourceAllocator resourceAllocator, IPipelineLibrary pipelineLibrary) { var shaderDescriptor = SDLCompiler.CompileShader("F:\\csharp\\GhostEngine\\Ghost.Graphics\\RenderPasses\\ShaderCode.hlsl").GetValueOrThrow(); - stateController.CompileShader(shaderDescriptor); - stateController.PreCookPipelineState(); + var key = pipelineLibrary.CompilePassPSO(shaderDescriptor.passes[0], [TextureFormat.B8G8R8A8_UNorm], TextureFormat.Unknown); MeshBuilder.CreateCube(0.75f, default, out var vertices, out var indices); @@ -85,4 +84,4 @@ internal unsafe class MeshRenderPass : IRenderPass } } } -} \ No newline at end of file +} diff --git a/Ghost.Graphics/Utilities/MeshBuilder.cs b/Ghost.Graphics/Utilities/MeshBuilder.cs index c4e8323..89228f9 100644 --- a/Ghost.Graphics/Utilities/MeshBuilder.cs +++ b/Ghost.Graphics/Utilities/MeshBuilder.cs @@ -1,7 +1,7 @@ -using Ghost.Graphics.Data; -using Misaki.HighPerformance.LowLevel.Buffer; +using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.Mathematics; +using Ghost.Graphics.Core; namespace Ghost.Graphics.Utilities;