From aa3d9c749b15139f036dee5466c7048939818507 Mon Sep 17 00:00:00 2001 From: Misaki Date: Tue, 23 Dec 2025 00:35:34 +0900 Subject: [PATCH] Refactor: add command allocator & render target strategies Major refactor of graphics infrastructure: - Introduce ICommandAllocator and D3D12CommandAllocator for explicit command buffer management. - Change ICommandBuffer.Begin to require an allocator. - Add IRenderTargetStrategy abstraction with swap chain and texture implementations. - Update IRenderer to use RenderTargetStrategy instead of direct handle. - Add DPI scaling support to swap chains (ScaleX/ScaleY, SetScale). - RenderSystem now supports thread-safe swap chain resize requests. - Remove persistent copy command buffer; use per-frame allocators. - Make Logger public/static and clean up API visibility. - Update .editorconfig and debug layer enablement. These changes improve modularity, DPI-awareness, and future extensibility. --- .editorconfig | 1 + Ghost.Core/Logging.cs | 108 +++++++++--------- .../Properties/launchSettings.json | 2 +- .../Windows/GraphicsTestWindow.xaml.cs | 46 +++----- Ghost.Graphics/Contracts/IRenderPass.cs | 6 +- .../Contracts/IRenderTargetStrategy.cs | 30 +++++ Ghost.Graphics/Core/RenderTargetStrategy.cs | 62 ++++++++++ Ghost.Graphics/Core/RenderingContext.cs | 12 +- Ghost.Graphics/Core/SwapChainPresenter.cs | 1 - Ghost.Graphics/D3D12/D3D12CommandAllocator.cs | 34 ++++++ Ghost.Graphics/D3D12/D3D12CommandBuffer.cs | 44 ++----- Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs | 40 ++++--- Ghost.Graphics/D3D12/D3D12RenderDevice.cs | 6 +- Ghost.Graphics/D3D12/D3D12Renderer.cs | 88 +++++++------- Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs | 3 - Ghost.Graphics/D3D12/D3D12SwapChain.cs | 55 ++++++--- .../D3D12/Utilities/D3D12Utility.cs | 11 ++ Ghost.Graphics/RHI/Common.cs | 9 ++ Ghost.Graphics/RHI/ICommandAllocator.cs | 6 + Ghost.Graphics/RHI/ICommandBuffer.cs | 2 +- Ghost.Graphics/RHI/IGraphicsEngine.cs | 26 ++++- Ghost.Graphics/RHI/IRenderer.cs | 18 +-- Ghost.Graphics/RHI/ISwapChain.cs | 39 ++++++- Ghost.Graphics/RenderSystem.cs | 49 ++++++-- 24 files changed, 456 insertions(+), 242 deletions(-) create mode 100644 Ghost.Graphics/Contracts/IRenderTargetStrategy.cs create mode 100644 Ghost.Graphics/Core/RenderTargetStrategy.cs create mode 100644 Ghost.Graphics/D3D12/D3D12CommandAllocator.cs create mode 100644 Ghost.Graphics/RHI/ICommandAllocator.cs diff --git a/.editorconfig b/.editorconfig index f89eba1..533c8ff 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,6 +2,7 @@ csharp_new_line_before_open_brace = all csharp_preserve_single_line_statements = true csharp_preserve_single_line_blocks = true +csharp_style_prefer_primary_constructors = false 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/Logging.cs b/Ghost.Core/Logging.cs index 702749d..42b1337 100644 --- a/Ghost.Core/Logging.cs +++ b/Ghost.Core/Logging.cs @@ -1,5 +1,4 @@ using System.Collections.ObjectModel; -using System.Diagnostics; namespace Ghost.Core; @@ -10,7 +9,7 @@ public enum LogLevel Error } -internal readonly struct LogMessage +public readonly struct LogMessage { public LogLevel Level { @@ -51,67 +50,68 @@ internal readonly struct LogMessage } } -internal interface ILogger +public interface ILogger { - public ReadOnlyObservableCollection Logs + ReadOnlyObservableCollection Logs { get; } - public void Log(string message, LogLevel level); - public void Log(Exception exception); - public void Assert(bool condition, string message); - public void Clear(); -} - -// TODO: Add file logging. -internal class LoggerImplementation : ILogger -{ - private readonly ObservableCollection _logs = new(); - private readonly Lock _lock = new(); - - public ReadOnlyObservableCollection Logs => new(_logs); - - public void Log(string message, LogLevel level) - { - lock (_lock) - { - _logs.Add(new LogMessage(level, message)); - } - } - - public void Log(Exception exception) - { - lock (_lock) - { - _logs.Add(new LogMessage(LogLevel.Error, exception.Message, exception.StackTrace)); - } - } - - public void Assert(bool condition, string message) - { - lock (_lock) - { - if (!condition) - { - Log(message, LogLevel.Error); - } - } - } - - public void Clear() - { - lock (_lock) - { - _logs.Clear(); - } - } + void Log(string message, LogLevel level); + void Log(Exception exception); + void Assert(bool condition, string message); + void Clear(); } public static class Logger { - private static readonly ILogger s_logger = new LoggerImplementation(); - internal static ReadOnlyObservableCollection Logs => s_logger.Logs; + // TODO: Add file logging. + private class LoggerImpl : ILogger + { + private readonly ObservableCollection _logs = new(); + private readonly Lock _lock = new(); + + public ReadOnlyObservableCollection Logs => new(_logs); + + public void Log(string message, LogLevel level) + { + lock (_lock) + { + _logs.Add(new LogMessage(level, message)); + } + } + + public void Log(Exception exception) + { + lock (_lock) + { + _logs.Add(new LogMessage(LogLevel.Error, exception.Message, exception.StackTrace)); + } + } + + public void Assert(bool condition, string message) + { + lock (_lock) + { + if (!condition) + { + Log(message, LogLevel.Error); + } + } + } + + public void Clear() + { + lock (_lock) + { + _logs.Clear(); + } + } + } + + private static readonly ILogger s_logger = new LoggerImpl(); + + public static ReadOnlyObservableCollection Logs => s_logger.Logs; public static void Log(LogLevel level, object? message) { diff --git a/Ghost.Graphics.Test/Properties/launchSettings.json b/Ghost.Graphics.Test/Properties/launchSettings.json index a2a54f9..ce2e5c5 100644 --- a/Ghost.Graphics.Test/Properties/launchSettings.json +++ b/Ghost.Graphics.Test/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Ghost.Graphics.Test (Package)": { "commandName": "MsixPackage", - "nativeDebugging": true + "nativeDebugging": false }, "Ghost.Graphics.Test (Unpackaged)": { "commandName": "Project" diff --git a/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs b/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs index f1782c6..f5cc1aa 100644 --- a/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs +++ b/Ghost.Graphics.Test/Windows/GraphicsTestWindow.xaml.cs @@ -1,21 +1,19 @@ +using Ghost.Graphics.Core; using Ghost.Graphics.RHI; using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; -using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.Mathematics; -using System.Text.Json; namespace Ghost.Graphics.Test.Windows; public sealed partial class GraphicsTestWindow : Window { - private DispatcherTimer _resizeTimer; private IRenderSystem? _renderSystem; private IRenderer? _renderer; private ISwapChain? _swapChain; private bool _isFirstActivationHandled; - private bool _isResizing; public GraphicsTestWindow() { @@ -24,11 +22,8 @@ public sealed partial class GraphicsTestWindow : Window Activated += GraphicsTestWindow_Activated; Closed += GraphicsTestWindow_Closed; - _resizeTimer = new DispatcherTimer(); - _resizeTimer.Interval = TimeSpan.FromMilliseconds(200); - _resizeTimer.Tick += OnResizeTimerTick; - Panel.SizeChanged += SwapChainPanel_SizeChanged; + Panel.CompositionScaleChanged += SwapChainPanel_CompositionScaleChanged; } private void GraphicsTestWindow_Activated(object sender, WindowActivatedEventArgs e) @@ -39,7 +34,7 @@ public sealed partial class GraphicsTestWindow : Window } #if DEBUG - AllocationManager.EnableDebugLayer(); + Misaki.HighPerformance.LowLevel.Buffer.AllocationManager.EnableDebugLayer(); #endif _renderSystem = new RenderSystem(new RenderingConfig() @@ -52,11 +47,13 @@ public sealed partial class GraphicsTestWindow : Window { Width = (uint)AppWindow.Size.Width, Height = (uint)AppWindow.Size.Height, + ScaleX = Panel.CompositionScaleX, + ScaleY = Panel.CompositionScaleY, Format = TextureFormat.B8G8R8A8_UNorm, Target = SwapChainTarget.FromCompositionSurface(Panel) }); - _renderer.SetRenderTarget(_swapChain.GetCurrentBackBuffer()); + _renderer.RenderTargetStrategy = new SwapChainTargetStrategy(_swapChain); _renderSystem.Start(); CompositionTarget.Rendering += OnRendering; @@ -75,27 +72,16 @@ public sealed partial class GraphicsTestWindow : Window _renderSystem?.Dispose(); #if DEBUG - AllocationManager.Dispose(); + Misaki.HighPerformance.LowLevel.Buffer.AllocationManager.Dispose(); #endif } private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e) { - //if (e.NewSize.Width > 8.0 && e.NewSize.Height > 8.0) - //{ - // _renderer?.RequestResize(new((uint)e.NewSize.Width, (uint)e.NewSize.Height)); - //} - - _resizeTimer.Stop(); - _resizeTimer.Start(); - - _isResizing = true; - } - - private void OnResizeTimerTick(object? sender, object e) - { - _resizeTimer.Stop(); - _isResizing = false; + if (_renderSystem == null || _swapChain == null) + { + return; + } var newWidth = (uint)(Panel.ActualWidth * Panel.CompositionScaleX); var newHeight = (uint)(Panel.ActualHeight * Panel.CompositionScaleY); @@ -105,8 +91,12 @@ public sealed partial class GraphicsTestWindow : Window return; } - _renderSystem?.WaitIdle(); - _swapChain?.Resize(newWidth, newHeight); + _renderSystem.RequestSwapChainResize(_swapChain, new uint2(newWidth, newHeight)); + } + + private void SwapChainPanel_CompositionScaleChanged(SwapChainPanel sender, object args) + { + _swapChain?.SetScale(sender.CompositionScaleX, sender.CompositionScaleY); } private void OnRendering(object? sender, object e) diff --git a/Ghost.Graphics/Contracts/IRenderPass.cs b/Ghost.Graphics/Contracts/IRenderPass.cs index d705a48..a79b4ff 100644 --- a/Ghost.Graphics/Contracts/IRenderPass.cs +++ b/Ghost.Graphics/Contracts/IRenderPass.cs @@ -5,7 +5,7 @@ namespace Ghost.Graphics.Contracts; public interface IRenderPass { - public void Initialize(ref readonly RenderingContext ctx); - public void Execute(ref readonly RenderingContext ctx); - public void Cleanup(IResourceDatabase resourceDatabase); + void Initialize(ref readonly RenderingContext ctx); + void Execute(ref readonly RenderingContext ctx); + void Cleanup(IResourceDatabase resourceDatabase); } diff --git a/Ghost.Graphics/Contracts/IRenderTargetStrategy.cs b/Ghost.Graphics/Contracts/IRenderTargetStrategy.cs new file mode 100644 index 0000000..7e9bb13 --- /dev/null +++ b/Ghost.Graphics/Contracts/IRenderTargetStrategy.cs @@ -0,0 +1,30 @@ +using Ghost.Core; +using Ghost.Graphics.Core; +using Ghost.Graphics.RHI; + +namespace Ghost.Graphics.Contracts; + +public interface IRenderTargetStrategy +{ + /// + /// Gets a handle to the current render target texture. + /// + /// A handle to the texture that is currently set as the render target. + Handle GetRenderTarget(); + /// + /// Begins a rendering operation using the specified command buffer. Typically this will include resource barriers, + /// + /// The command buffer that records rendering commands. + void BeginRender(ICommandBuffer cmd); + /// + /// Finalizes the rendering process using the specified command buffer. + /// + /// The command buffer that contains the rendering commands to be finalized. + void EndRender(ICommandBuffer cmd); + /// + /// Displays the current frame to the output device or screen. + /// + /// Call this method after rendering operations to present the rendered content. The exact + /// behavior may depend on the underlying graphics implementation or device. + void Present(); +} diff --git a/Ghost.Graphics/Core/RenderTargetStrategy.cs b/Ghost.Graphics/Core/RenderTargetStrategy.cs new file mode 100644 index 0000000..e2a1d8e --- /dev/null +++ b/Ghost.Graphics/Core/RenderTargetStrategy.cs @@ -0,0 +1,62 @@ +using Ghost.Core; +using Ghost.Graphics.Contracts; +using Ghost.Graphics.RHI; + +namespace Ghost.Graphics.Core; + +internal class SwapChainTargetStrategy : IRenderTargetStrategy +{ + private readonly ISwapChain _swapChain; + + public SwapChainTargetStrategy(ISwapChain swapChain) + { + _swapChain = swapChain; + } + + public Handle GetRenderTarget() + { + return _swapChain.GetCurrentBackBuffer(); + } + + public void BeginRender(ICommandBuffer cmd) + { + cmd.ResourceBarrier(GetRenderTarget().AsResource(), ResourceState.Present, ResourceState.RenderTarget); + } + + public void EndRender(ICommandBuffer cmd) + { + cmd.ResourceBarrier(GetRenderTarget().AsResource(), ResourceState.RenderTarget, ResourceState.Present); + } + + public void Present() + { + _swapChain.Present(); + } +} + +internal class TextureTargetStrategy : IRenderTargetStrategy +{ + private readonly Handle _texture; + + public TextureTargetStrategy(Handle texture) + { + _texture = texture; + } + + public Handle GetRenderTarget() + { + return _texture; + } + + public void BeginRender(ICommandBuffer cmd) + { + } + + public void EndRender(ICommandBuffer cmd) + { + } + + public void Present() + { + } +} \ No newline at end of file diff --git a/Ghost.Graphics/Core/RenderingContext.cs b/Ghost.Graphics/Core/RenderingContext.cs index a87cba3..ad1159b 100644 --- a/Ghost.Graphics/Core/RenderingContext.cs +++ b/Ghost.Graphics/Core/RenderingContext.cs @@ -12,28 +12,18 @@ public readonly unsafe ref struct RenderingContext { private readonly IGraphicsEngine _engine; private readonly ICommandBuffer _directCmd; - private readonly ICommandBuffer _copyCmd; - private readonly ICommandBuffer _computeCmd; public ICommandBuffer DirectCommandBuffer => _directCmd; - public ICommandBuffer CopyCommandBuffer => _copyCmd; - public ICommandBuffer ComputeCommandBuffer => _computeCmd; public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler; public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator; public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase; public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary; - internal RenderingContext( - IGraphicsEngine engine, - ICommandBuffer directCmd, - ICommandBuffer copyCmd, - ICommandBuffer computeCmd) + internal RenderingContext(IGraphicsEngine engine, ICommandBuffer directCmd) { _engine = engine; _directCmd = directCmd; - _copyCmd = copyCmd; - _computeCmd = computeCmd; } public ICommandBuffer CrearteCommandBuffer(CommandBufferType type) diff --git a/Ghost.Graphics/Core/SwapChainPresenter.cs b/Ghost.Graphics/Core/SwapChainPresenter.cs index 3276c88..d393043 100644 --- a/Ghost.Graphics/Core/SwapChainPresenter.cs +++ b/Ghost.Graphics/Core/SwapChainPresenter.cs @@ -1,5 +1,4 @@ using Ghost.Graphics.Contracts; -using Ghost.Graphics.Core; namespace Ghost.Graphics.Core; diff --git a/Ghost.Graphics/D3D12/D3D12CommandAllocator.cs b/Ghost.Graphics/D3D12/D3D12CommandAllocator.cs new file mode 100644 index 0000000..b282d0c --- /dev/null +++ b/Ghost.Graphics/D3D12/D3D12CommandAllocator.cs @@ -0,0 +1,34 @@ +using Ghost.Core.Utilities; +using Ghost.Graphics.D3D12.Utilities; +using Ghost.Graphics.RHI; +using Misaki.HighPerformance.LowLevel; +using TerraFX.Interop.DirectX; + +namespace Ghost.Graphics.D3D12; + +internal unsafe class D3D12CommandAllocator : ICommandAllocator +{ + private UniquePtr _allocator; + + public SharedPtr NativeAllocator => _allocator.Share(); + + public D3D12CommandAllocator(D3D12RenderDevice device, CommandBufferType type) + { + ID3D12CommandAllocator* pAllocator = default; + var commandListType = D3D12Utility.ToCommandListType(type); + + device.NativeDevice.Get()->CreateCommandAllocator(commandListType, __uuidof(pAllocator), (void**)&pAllocator); + + _allocator.Attach(pAllocator); + } + + public void Reset() + { + _allocator.Get()->Reset(); + } + + public void Dispose() + { + _allocator.Dispose(); + } +} \ No newline at end of file diff --git a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs index a5a77ff..78bec70 100644 --- a/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs +++ b/Ghost.Graphics/D3D12/D3D12CommandBuffer.cs @@ -19,7 +19,6 @@ namespace Ghost.Graphics.D3D12; internal unsafe class D3D12CommandBuffer : ICommandBuffer { private UniquePtr _commandList; - private UniquePtr _allocator; private readonly D3D12PipelineLibrary _pipelineLibrary; private readonly D3D12ResourceDatabase _resourceDatabase; @@ -62,14 +61,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer { _type = type; - ID3D12CommandAllocator* pAllocator = default; ID3D12GraphicsCommandList10* pCommandList = default; - var commandListType = ConvertCommandBufferType(type); + var commandListType = D3D12Utility.ToCommandListType(type); - device.NativeDevice.Get()->CreateCommandAllocator(commandListType, __uuidof(pAllocator), (void**)&pAllocator); device.NativeDevice.Get()->CreateCommandList1(0u, commandListType, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof(pCommandList), (void**)&pCommandList); - _allocator.Attach(pAllocator); _commandList.Attach(pCommandList); _pipelineLibrary = stateController; @@ -85,17 +81,6 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer Dispose(); } - private static D3D12_COMMAND_LIST_TYPE ConvertCommandBufferType(CommandBufferType type) - { - return type switch - { - CommandBufferType.Graphics => D3D12_COMMAND_LIST_TYPE_DIRECT, - CommandBufferType.Compute => D3D12_COMMAND_LIST_TYPE_COMPUTE, - CommandBufferType.Copy => D3D12_COMMAND_LIST_TYPE_COPY, - _ => throw new ArgumentException($"Unknown command buffer type: {type}") - }; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ThrowIfDisposed() { @@ -145,32 +130,28 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer #endif } - public void Begin() + public void Begin(ICommandAllocator allocator) { - void ResetCommandList() + ThrowIfDisposed(); + ThrowIfRecording(); + + if (allocator is not D3D12CommandAllocator d3d12Allocator) { - ThrowIfFailed(_allocator.Get()->Reset()); - ThrowIfFailed(_commandList.Get()->Reset(_allocator.Get(), null)); + throw new ArgumentException("Invalid command allocator type", nameof(allocator)); } - void SetBindlessHeap() + ThrowIfFailed(_commandList.Get()->Reset(d3d12Allocator.NativeAllocator, null)); + + if (Type == CommandBufferType.Graphics || Type == CommandBufferType.Compute) { + // Set descriptor heaps for bindless resources and samplers + 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(); - ThrowIfRecording(); - - ResetCommandList(); - - if (Type == CommandBufferType.Graphics || Type == CommandBufferType.Compute) - { - SetBindlessHeap(); - } - _commandCount = 0; _isRecording = true; } @@ -684,7 +665,6 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer } _commandList.Dispose(); - _allocator.Dispose(); _commandCount = 0; _disposed = true; diff --git a/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs b/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs index aa50791..65714d8 100644 --- a/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs +++ b/Ghost.Graphics/D3D12/D3D12GraphicsEngine.cs @@ -1,3 +1,7 @@ +#if DEBUG +#define ENABLE_DEBUG +#endif + using Ghost.Core; using Ghost.Graphics.Contracts; using Ghost.Graphics.RHI; @@ -11,7 +15,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine { private readonly IRenderSystem _renderSystem; -#if DEBUG +#if ENABLE_DEBUG private readonly D3D12DebugLayer _debugLayer; #endif @@ -21,7 +25,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12PipelineLibrary _pipelineLibrary; private readonly D3D12ResourceAllocator _resourceAllocator; - private readonly D3D12CommandBuffer _copyCommandBuffer; private ImmutableArray _renderers; @@ -32,13 +35,12 @@ internal class D3D12GraphicsEngine : IGraphicsEngine public IPipelineLibrary PipelineLibrary => _pipelineLibrary; public IResourceDatabase ResourceDatabase => _resourceDatabase; public IResourceAllocator ResourceAllocator => _resourceAllocator; - public ICommandBuffer CopyCommandBuffer => _copyCommandBuffer; public D3D12GraphicsEngine(IRenderSystem renderSystem) { _renderSystem = renderSystem; -#if DEBUG +#if ENABLE_DEBUG _debugLayer = new D3D12DebugLayer(); #endif _device = new D3D12RenderDevice(); @@ -49,14 +51,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine _pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase); _resourceAllocator = new D3D12ResourceAllocator(renderSystem, _device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary); - _copyCommandBuffer = new D3D12CommandBuffer( - _device, - _pipelineLibrary, - _resourceDatabase, - _resourceAllocator, - _descriptorAllocator, - CommandBufferType.Copy); - _renderers = ImmutableArray.Empty; _pipelineLibrary.InitializeLibrary(null); @@ -94,6 +88,11 @@ internal class D3D12GraphicsEngine : IGraphicsEngine ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Clear()); } + public ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics) + { + return new D3D12CommandAllocator(_device, type); + } + public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics) { ThrowIfDisposed(); @@ -113,22 +112,27 @@ internal class D3D12GraphicsEngine : IGraphicsEngine return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _renderSystem.MaxFrameLatency); } - public void RenderFrame(ICommandBuffer commandBuffer) + public Result RenderFrame(ICommandAllocator commandAllocator) { ThrowIfDisposed(); - _copyCommandBuffer.Begin(); + var r = Result.Success(); foreach (var renderer in _renderers) { - renderer.Render(commandBuffer); + r = renderer.Render(commandAllocator); + if (r.IsFailure) + { + break; + } } - _copyCommandBuffer.End().ThrowIfFailed(); _resourceAllocator.ReleaseTempResources(); _descriptorAllocator.ResetCbvSrvUavDynamicHeap(); _descriptorAllocator.ResetDSVDynamicHeap(); _descriptorAllocator.ResetRTVDynamicHeap(); + + return r; } public void Dispose() @@ -138,8 +142,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine return; } - _copyCommandBuffer.Dispose(); - _resourceAllocator.Dispose(); _pipelineLibrary.Dispose(); _resourceDatabase.Dispose(); @@ -147,7 +149,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine _descriptorAllocator.Dispose(); _shaderCompiler.Dispose(); _device.Dispose(); -#if DEBUG +#if ENABLE_DEBUG _debugLayer.Dispose(); #endif diff --git a/Ghost.Graphics/D3D12/D3D12RenderDevice.cs b/Ghost.Graphics/D3D12/D3D12RenderDevice.cs index 7c5cdf2..e3f6656 100644 --- a/Ghost.Graphics/D3D12/D3D12RenderDevice.cs +++ b/Ghost.Graphics/D3D12/D3D12RenderDevice.cs @@ -28,9 +28,9 @@ internal unsafe class D3D12RenderDevice : IRenderDevice public ICommandQueue ComputeQueue => _computeQueue; public ICommandQueue CopyQueue => _copyQueue; - public SharedPtr DXGIFactory => _dxgiFactory.Get(); - public SharedPtr NativeDevice => _device.Get(); - public SharedPtr Adapter => _adapter.Get(); + public SharedPtr DXGIFactory => _dxgiFactory.Share(); + public SharedPtr NativeDevice => _device.Share(); + public SharedPtr Adapter => _adapter.Share(); public SharedPtr NativeGraphicsQueue => _graphicsQueue.NativeQueue; public SharedPtr NativeComputeQueue => _computeQueue.NativeQueue; public SharedPtr NativeCopyQueue => _copyQueue.NativeQueue; diff --git a/Ghost.Graphics/D3D12/D3D12Renderer.cs b/Ghost.Graphics/D3D12/D3D12Renderer.cs index e6ec425..e5898cb 100644 --- a/Ghost.Graphics/D3D12/D3D12Renderer.cs +++ b/Ghost.Graphics/D3D12/D3D12Renderer.cs @@ -2,6 +2,7 @@ using Ghost.Core; using Ghost.Graphics.RHI; using Ghost.Graphics.Core; using Ghost.Graphics.RenderPasses; +using Ghost.Graphics.Contracts; namespace Ghost.Graphics.D3D12; @@ -10,48 +11,30 @@ namespace Ghost.Graphics.D3D12; /// internal class D3D12Renderer : IRenderer { - private struct FrameResource : IDisposable - { - public ICommandBuffer commandBuffer; - public ulong fenceValue; - - public FrameResource(D3D12GraphicsEngine graphicsEngine, int index) - { - commandBuffer = graphicsEngine.CreateCommandBuffer(); - commandBuffer.Name = $"Frame Command Buffer {index}"; - fenceValue = 0; - } - - public readonly void Dispose() - { - commandBuffer?.Dispose(); - } - } - private readonly D3D12GraphicsEngine _graphicsEngine; - private uint _frameIndex; - private readonly D3D12ResourceDatabase _resourceDatabase; - private Handle _renderTarget; + private readonly ICommandBuffer _commandBuffer; + private uint _frameIndex; private bool _disposed; // NOTE: Testing only. private readonly MeshRenderPass _pass; - public Handle RenderTarget => _renderTarget; + public IRenderTargetStrategy? RenderTargetStrategy + { + get; set; + } - // TODO: Add render passes support - // private ImmutableArray _renderPasses; + // TODO: Add render graph support public D3D12Renderer(D3D12GraphicsEngine graphicsEngine, D3D12ResourceDatabase resourceDatabase) { _graphicsEngine = graphicsEngine; _resourceDatabase = resourceDatabase; - _renderTarget = Handle.Invalid; - + _commandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics); // NOTE: Testing only. _pass = new(); @@ -62,36 +45,47 @@ internal class D3D12Renderer : IRenderer Dispose(); } - public void SetRenderTarget(Handle renderTarget) + public Result Render(ICommandAllocator commandAllocator) { - _resourceDatabase.ReleaseResource(_renderTarget.AsResource()); - _renderTarget = renderTarget; - } - - public Result Render(ICommandBuffer commandBuffer) - { - if (!_renderTarget.IsValid) + if (RenderTargetStrategy is null) { - return Result.Failure("Render target is not set."); + return Result.Failure("Render target strategy is not set."); } - commandBuffer.Begin(); + var target = RenderTargetStrategy.GetRenderTarget(); + if (target.IsNotValid) + { + return Result.Failure("Render target is invalid."); + } + + _commandBuffer.Begin(commandAllocator); + RenderTargetStrategy.BeginRender(_commandBuffer); // NOTE: Temperary solution: render directly to the swap chain back buffer if available. // HACK: This is hard coded for testing purposes only. - var error = RenderScene(_renderTarget, commandBuffer); + var error = RenderScene(target); if (error != ErrorStatus.None) { - commandBuffer.End(); + _commandBuffer.End(); return Result.Failure(error); } - return commandBuffer.End(); + RenderTargetStrategy.EndRender(_commandBuffer); + var r = _commandBuffer.End(); + if (r.IsFailure) + { + return r; + } + + _graphicsEngine.Device.GraphicsQueue.Submit(_commandBuffer); + RenderTargetStrategy.Present(); + + return Result.Success(); } // TODO: A proper render graph integration. - private ErrorStatus RenderScene(Handle target, ICommandBuffer cmd) + private ErrorStatus RenderScene(Handle target) { var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f }; @@ -112,7 +106,7 @@ internal class D3D12Renderer : IRenderer }; // NOTE: Testing only. - var ctx = new RenderingContext(_graphicsEngine, cmd, _graphicsEngine.CopyCommandBuffer, null!); + var ctx = new RenderingContext(_graphicsEngine, _commandBuffer); if (_frameIndex == 0) { _pass.Initialize(ref ctx); @@ -124,18 +118,20 @@ internal class D3D12Renderer : IRenderer return result.Error; } + // TODO: Decouple viewport and scissor from the texture size. var texDesc = result.Value.TextureDescription; var viewport = new ViewportDesc { Width = texDesc.Width, Height = texDesc.Height, MinDepth = 0, MaxDepth = 1 }; var scissor = new RectDesc { Right = texDesc.Width, Bottom = texDesc.Height }; - cmd.BeginRenderPass(rtDesc, depthDesc, false); - cmd.SetViewport(viewport); - cmd.SetScissorRect(scissor); + _commandBuffer.BeginRenderPass(rtDesc, depthDesc, false); + _commandBuffer.SetViewport(viewport); + _commandBuffer.SetScissorRect(scissor); // NOTE: Testing only. _pass.Execute(ref ctx); - cmd.EndRenderPass(); + _commandBuffer.EndRenderPass(); + _frameIndex++; return ErrorStatus.None; } @@ -150,6 +146,8 @@ internal class D3D12Renderer : IRenderer // NOTE: Testing only. _pass.Cleanup(_resourceDatabase); + _commandBuffer.Dispose(); + _disposed = true; GC.SuppressFinalize(this); diff --git a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs index 35fa312..1f9ac2c 100644 --- a/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs +++ b/Ghost.Graphics/D3D12/D3D12ResourceDatabase.cs @@ -104,7 +104,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase private readonly DynamicArray _shaders; // NOTE: We use a simple list since shader is not frequently added/removed. This can save 4 bytes for each ecs component. private readonly Dictionary _shaderPasses; // NOTE: The reason we use Dictionary here is that ShaderPassKey is a presistence identifier across multiple application sessions. - private int _lastSamplerId; private bool _disposed; public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator) @@ -120,8 +119,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase _materials = new UnsafeSlotMap(16, Allocator.Persistent, AllocationOption.Clear); _shaders = new DynamicArray(16); _shaderPasses = new Dictionary(16); - - _lastSamplerId = -1; } ~D3D12ResourceDatabase() diff --git a/Ghost.Graphics/D3D12/D3D12SwapChain.cs b/Ghost.Graphics/D3D12/D3D12SwapChain.cs index 50562bc..cfadc27 100644 --- a/Ghost.Graphics/D3D12/D3D12SwapChain.cs +++ b/Ghost.Graphics/D3D12/D3D12SwapChain.cs @@ -42,6 +42,16 @@ internal unsafe class D3D12SwapChain : ISwapChain get; private set; } + public float ScaleX + { + get; private set; + } + + public float ScaleY + { + get; private set; + } + public D3D12SwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount) { Debug.Assert(bufferCount >= 2); @@ -55,8 +65,11 @@ internal unsafe class D3D12SwapChain : ISwapChain Width = desc.Width; Height = desc.Height; - CreateSwapChain(desc, bufferCount); + var pSwapChian = CreateSwapChain(desc, bufferCount); + _swapChain.Attach(pSwapChian); + CreateBackBuffers(); + SetScale(desc.ScaleX, desc.ScaleY); _compositionSurface = desc.Target.CompositionSurface; } @@ -66,7 +79,7 @@ internal unsafe class D3D12SwapChain : ISwapChain Dispose(); } - private void CreateSwapChain(SwapChainDesc desc, uint bufferCount) + private IDXGISwapChain4* CreateSwapChain(SwapChainDesc desc, uint bufferCount) { var swapChainDesc = new DXGI_SWAP_CHAIN_DESC1 { @@ -124,7 +137,7 @@ internal unsafe class D3D12SwapChain : ISwapChain pTempSwapChain->QueryInterface(__uuidof(pSwapChain), (void**)&pSwapChain); pTempSwapChain->Release(); - _swapChain.Attach(pSwapChain); + return pSwapChain; } private void CreateBackBuffers() @@ -155,6 +168,13 @@ internal unsafe class D3D12SwapChain : ISwapChain return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan> GetBackBuffers() + { + ObjectDisposedException.ThrowIf(_disposed, this); + return _backBuffers.AsSpan(); + } + public void Present(bool vsync = true) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -186,20 +206,27 @@ internal unsafe class D3D12SwapChain : ISwapChain Height = height; CreateBackBuffers(); + } - //float inverseScale = 1.0f / scale; + public void SetScale(float scaleX, float scaleY) + { + var inverseScaleX = 1.0f / scaleX; + var inverseScaleY = 1.0f / scaleY; - //DXGI_MATRIX_3X2_F inverseScaleMatrix = new DXGI_MATRIX_3X2_F - //{ - // _11 = inverseScale, // Scale X - // _22 = inverseScale, // Scale Y - // _12 = 0.0f, - // _21 = 0.0f, - // _31 = 0.0f, // Offset X - // _32 = 0.0f // Offset Y - //}; + DXGI_MATRIX_3X2_F inverseScaleMatrix = new DXGI_MATRIX_3X2_F + { + _11 = inverseScaleX, // Scale X + _22 = inverseScaleY, // Scale Y + _12 = 0.0f, + _21 = 0.0f, + _31 = 0.0f, // Offset X + _32 = 0.0f // Offset Y + }; - //_swapChain.Get()->SetMatrixTransform(&inverseScaleMatrix); + _swapChain.Get()->SetMatrixTransform(&inverseScaleMatrix); + + ScaleX = scaleX; + ScaleY = scaleY; } public void Dispose() diff --git a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs index 7140d48..0f7796b 100644 --- a/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs +++ b/Ghost.Graphics/D3D12/Utilities/D3D12Utility.cs @@ -155,6 +155,17 @@ internal unsafe static class D3D12Utility }; } + public static D3D12_COMMAND_LIST_TYPE ToCommandListType(CommandBufferType type) + { + return type switch + { + CommandBufferType.Graphics => D3D12_COMMAND_LIST_TYPE_DIRECT, + CommandBufferType.Compute => D3D12_COMMAND_LIST_TYPE_COMPUTE, + CommandBufferType.Copy => D3D12_COMMAND_LIST_TYPE_COPY, + _ => throw new ArgumentException($"Unknown command buffer type: {type}") + }; + } + public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CREATE( D3D12_FILL_MODE fillMode, diff --git a/Ghost.Graphics/RHI/Common.cs b/Ghost.Graphics/RHI/Common.cs index f53e85a..e15594b 100644 --- a/Ghost.Graphics/RHI/Common.cs +++ b/Ghost.Graphics/RHI/Common.cs @@ -758,6 +758,15 @@ public struct SwapChainDesc get; set; } + public float ScaleX + { + get; set; + } + + public float ScaleY + { + get; set; + } /// /// Back buffer Format diff --git a/Ghost.Graphics/RHI/ICommandAllocator.cs b/Ghost.Graphics/RHI/ICommandAllocator.cs new file mode 100644 index 0000000..2a57677 --- /dev/null +++ b/Ghost.Graphics/RHI/ICommandAllocator.cs @@ -0,0 +1,6 @@ +namespace Ghost.Graphics.RHI; + +public interface ICommandAllocator : IDisposable +{ + void Reset(); +} diff --git a/Ghost.Graphics/RHI/ICommandBuffer.cs b/Ghost.Graphics/RHI/ICommandBuffer.cs index 312596e..980bda6 100644 --- a/Ghost.Graphics/RHI/ICommandBuffer.cs +++ b/Ghost.Graphics/RHI/ICommandBuffer.cs @@ -35,7 +35,7 @@ public interface ICommandBuffer : IDisposable /// /// Begins recording commands into this command buffer /// - void Begin(); + void Begin(ICommandAllocator allocator); /// /// Ends recording commands and prepares for submission diff --git a/Ghost.Graphics/RHI/IGraphicsEngine.cs b/Ghost.Graphics/RHI/IGraphicsEngine.cs index f832a42..8e339bc 100644 --- a/Ghost.Graphics/RHI/IGraphicsEngine.cs +++ b/Ghost.Graphics/RHI/IGraphicsEngine.cs @@ -30,10 +30,32 @@ public interface IGraphicsEngine : IDisposable get; } + /// + /// Creates a new instance of an object that implements the IRenderer interface. + /// + /// An object that provides rendering functionality through the IRenderer interface. IRenderer CreateRenderer(); + + /// + /// Removes the specified renderer from the collection of active renderers. + /// + /// The renderer instance to remove. Cannot be null. void RemoveRenderer(IRenderer renderer); + + /// + /// Removes all registered renderers from the collection. + /// + /// Call this method to reset the renderer collection to an empty state. After calling this + /// method, no renderers will be available until new ones are added. void ClearRenderers(); + /// + /// Creates a new command allocator for the specified command buffer type. + /// + /// The type of command buffer for which to create the allocator. The default is CommandBufferType.Graphics. + /// An instance configured for the specified command buffer type. + ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics); + /// /// Creates a command buffer for recording rendering commands /// @@ -51,5 +73,7 @@ public interface IGraphicsEngine : IDisposable /// /// Renders the current frame. /// - void RenderFrame(ICommandBuffer commandBuffer); + /// Command allocator to use for rendering + /// Result of the rendering operation + Result RenderFrame(ICommandAllocator commandAllocator); } diff --git a/Ghost.Graphics/RHI/IRenderer.cs b/Ghost.Graphics/RHI/IRenderer.cs index 33ec586..ce21640 100644 --- a/Ghost.Graphics/RHI/IRenderer.cs +++ b/Ghost.Graphics/RHI/IRenderer.cs @@ -1,5 +1,5 @@ using Ghost.Core; -using Ghost.Graphics.Core; +using Ghost.Graphics.Contracts; namespace Ghost.Graphics.RHI; @@ -8,21 +8,15 @@ namespace Ghost.Graphics.RHI; /// public interface IRenderer : IDisposable { - Handle RenderTarget + IRenderTargetStrategy? RenderTargetStrategy { - get; + get; set; } - /// - /// Sets the render Target for this renderer - /// - /// Render Target to render into - public void SetRenderTarget(Handle renderTarget); - /// /// Renders a frame /// - /// Command buffer to record rendering commands into + /// Command allocator to use for rendering /// Result of the rendering operation - public Result Render(ICommandBuffer commandBuffer); -} + Result Render(ICommandAllocator commandAllocator); +} \ No newline at end of file diff --git a/Ghost.Graphics/RHI/ISwapChain.cs b/Ghost.Graphics/RHI/ISwapChain.cs index 417b3d4..ac31fe4 100644 --- a/Ghost.Graphics/RHI/ISwapChain.cs +++ b/Ghost.Graphics/RHI/ISwapChain.cs @@ -11,7 +11,7 @@ public interface ISwapChain : IDisposable /// /// Width of the swap chain back buffers /// - public uint Width + uint Width { get; } @@ -19,7 +19,23 @@ public interface ISwapChain : IDisposable /// /// Height of the swap chain back buffers /// - public uint Height + uint Height + { + get; + } + + /// + /// Gets the horizontal scaling factor applied to the object. This is used for DPI scaling. + /// + float ScaleX + { + get; + } + + /// + /// Gets the vertical scale factor applied to the object. This is used for DPI scaling. + /// + float ScaleY { get; } @@ -28,18 +44,31 @@ public interface ISwapChain : IDisposable /// Gets the current back buffer texture /// /// Current back buffer texture - public Handle GetCurrentBackBuffer(); + Handle GetCurrentBackBuffer(); + + /// + /// Gets all back buffer textures + /// + /// All back buffer textures + ReadOnlySpan> GetBackBuffers(); /// /// Presents the rendered frame /// /// Enable vertical synchronization - public void Present(bool vsync = true); + void Present(bool vsync = true); /// /// Resizes the swap chain back buffers /// /// New Width /// New Height - public void Resize(uint width, uint height); + void Resize(uint width, uint height); + + /// + /// Sets the horizontal and vertical scaling factors for the object. + /// + /// The factor by which to scale the object along the X-axis. + /// The factor by which to scale the object along the Y-axis. + void SetScale(float scaleX, float scaleY); } \ No newline at end of file diff --git a/Ghost.Graphics/RenderSystem.cs b/Ghost.Graphics/RenderSystem.cs index 9d3fdce..eaa459c 100644 --- a/Ghost.Graphics/RenderSystem.cs +++ b/Ghost.Graphics/RenderSystem.cs @@ -1,4 +1,8 @@ +using Ghost.Core; using Ghost.Graphics.RHI; +using Misaki.HighPerformance.Mathematics; +using System.Collections.Concurrent; +using System.Diagnostics; namespace Ghost.Graphics; @@ -61,6 +65,7 @@ public interface IRenderSystem : IFenceSynchronizer, IDisposable void Start(); void Stop(); + void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize); } /// @@ -82,7 +87,7 @@ internal class RenderSystem : IRenderSystem get; init; } - public required ICommandBuffer CommandBuffer + public required ICommandAllocator CommandAllocator { get; init; } @@ -96,7 +101,7 @@ internal class RenderSystem : IRenderSystem { CpuReadyEvent.Dispose(); GpuReadyEvent.Dispose(); - CommandBuffer.Dispose(); + CommandAllocator.Dispose(); } } @@ -106,6 +111,7 @@ internal class RenderSystem : IRenderSystem private readonly FrameResource[] _frameResources; private readonly Thread _renderThread; private readonly AutoResetEvent _shutdownEvent; + private readonly ConcurrentDictionary _resizeRequest; private uint _frameIndex; private uint _cpuFenceValue; @@ -131,8 +137,6 @@ internal class RenderSystem : IRenderSystem _ => throw new NotSupportedException($"Graphics API {config.GraphicsAPI} is not supported.") }; - _shutdownEvent = new AutoResetEvent(false); - // Create frame resources for synchronization _frameResources = new FrameResource[config.FrameBufferCount]; for (var i = 0; i < config.FrameBufferCount; i++) @@ -141,7 +145,7 @@ internal class RenderSystem : IRenderSystem { CpuReadyEvent = new AutoResetEvent(false), GpuReadyEvent = new AutoResetEvent(true), - CommandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics), + CommandAllocator = _graphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics) }; } @@ -152,6 +156,9 @@ internal class RenderSystem : IRenderSystem Priority = ThreadPriority.Normal }; + _shutdownEvent = new AutoResetEvent(false); + _resizeRequest = new ConcurrentDictionary(); + _isRunning = false; _disposed = false; } @@ -188,6 +195,12 @@ internal class RenderSystem : IRenderSystem _renderThread.Join(); } + public void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize) + { + ObjectDisposedException.ThrowIf(_disposed, this); + _resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize); + } + public bool WaitForGPUReady(int timeOut = -1) { ObjectDisposedException.ThrowIf(_disposed, this); @@ -243,13 +256,31 @@ internal class RenderSystem : IRenderSystem _graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue); } - _graphicsEngine.RenderFrame(frameResource.CommandBuffer); + if (!_resizeRequest.IsEmpty) + { + WaitIdle(); + foreach (var kvp in _resizeRequest) + { + var swapChain = kvp.Key; + var newSize = kvp.Value; + swapChain.Resize(newSize.x, newSize.y); + } + } + + var r = _graphicsEngine.RenderFrame(frameResource.CommandAllocator); + if (r.IsFailure) + { + _isRunning = false; +#if DEBUG + System.Diagnostics.Debugger.Break(); +#endif + Logger.LogError($"RenderFrame failed: {r.Message}"); + } _gpuFenceValue++; - frameResource.GpuReadyEvent.Set(); - frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_frameIndex); - _frameIndex++; + frameResource.GpuReadyEvent.Set(); + frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue); } } }