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.
This commit is contained in:
2025-12-23 00:35:34 +09:00
parent d23e701f0a
commit aa3d9c749b
24 changed files with 456 additions and 242 deletions

View File

@@ -2,6 +2,7 @@
csharp_new_line_before_open_brace = all csharp_new_line_before_open_brace = all
csharp_preserve_single_line_statements = true csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true csharp_preserve_single_line_blocks = true
csharp_style_prefer_primary_constructors = false
dotnet_sort_system_directives_first = false dotnet_sort_system_directives_first = false
dotnet_separate_import_directive_groups = false dotnet_separate_import_directive_groups = false
max_line_length = 400 max_line_length = 400

View File

@@ -1,5 +1,4 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
namespace Ghost.Core; namespace Ghost.Core;
@@ -10,7 +9,7 @@ public enum LogLevel
Error Error
} }
internal readonly struct LogMessage public readonly struct LogMessage
{ {
public LogLevel Level public LogLevel Level
{ {
@@ -51,67 +50,68 @@ internal readonly struct LogMessage
} }
} }
internal interface ILogger public interface ILogger
{ {
public ReadOnlyObservableCollection<LogMessage> Logs ReadOnlyObservableCollection<LogMessage> Logs
{ {
get; get;
} }
public void Log(string message, LogLevel level); void Log(string message, LogLevel level);
public void Log(Exception exception); void Log(Exception exception);
public void Assert(bool condition, string message); void Assert(bool condition, string message);
public void Clear(); void Clear();
}
// TODO: Add file logging.
internal class LoggerImplementation : ILogger
{
private readonly ObservableCollection<LogMessage> _logs = new();
private readonly Lock _lock = new();
public ReadOnlyObservableCollection<LogMessage> 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();
}
}
} }
public static class Logger public static class Logger
{ {
private static readonly ILogger s_logger = new LoggerImplementation(); // TODO: Add file logging.
internal static ReadOnlyObservableCollection<LogMessage> Logs => s_logger.Logs; private class LoggerImpl : ILogger
{
private readonly ObservableCollection<LogMessage> _logs = new();
private readonly Lock _lock = new();
public ReadOnlyObservableCollection<LogMessage> 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<LogMessage> Logs => s_logger.Logs;
public static void Log(LogLevel level, object? message) public static void Log(LogLevel level, object? message)
{ {

View File

@@ -2,7 +2,7 @@
"profiles": { "profiles": {
"Ghost.Graphics.Test (Package)": { "Ghost.Graphics.Test (Package)": {
"commandName": "MsixPackage", "commandName": "MsixPackage",
"nativeDebugging": true "nativeDebugging": false
}, },
"Ghost.Graphics.Test (Unpackaged)": { "Ghost.Graphics.Test (Unpackaged)": {
"commandName": "Project" "commandName": "Project"

View File

@@ -1,21 +1,19 @@
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Text.Json;
namespace Ghost.Graphics.Test.Windows; namespace Ghost.Graphics.Test.Windows;
public sealed partial class GraphicsTestWindow : Window public sealed partial class GraphicsTestWindow : Window
{ {
private DispatcherTimer _resizeTimer;
private IRenderSystem? _renderSystem; private IRenderSystem? _renderSystem;
private IRenderer? _renderer; private IRenderer? _renderer;
private ISwapChain? _swapChain; private ISwapChain? _swapChain;
private bool _isFirstActivationHandled; private bool _isFirstActivationHandled;
private bool _isResizing;
public GraphicsTestWindow() public GraphicsTestWindow()
{ {
@@ -24,11 +22,8 @@ public sealed partial class GraphicsTestWindow : Window
Activated += GraphicsTestWindow_Activated; Activated += GraphicsTestWindow_Activated;
Closed += GraphicsTestWindow_Closed; Closed += GraphicsTestWindow_Closed;
_resizeTimer = new DispatcherTimer();
_resizeTimer.Interval = TimeSpan.FromMilliseconds(200);
_resizeTimer.Tick += OnResizeTimerTick;
Panel.SizeChanged += SwapChainPanel_SizeChanged; Panel.SizeChanged += SwapChainPanel_SizeChanged;
Panel.CompositionScaleChanged += SwapChainPanel_CompositionScaleChanged;
} }
private void GraphicsTestWindow_Activated(object sender, WindowActivatedEventArgs e) private void GraphicsTestWindow_Activated(object sender, WindowActivatedEventArgs e)
@@ -39,7 +34,7 @@ public sealed partial class GraphicsTestWindow : Window
} }
#if DEBUG #if DEBUG
AllocationManager.EnableDebugLayer(); Misaki.HighPerformance.LowLevel.Buffer.AllocationManager.EnableDebugLayer();
#endif #endif
_renderSystem = new RenderSystem(new RenderingConfig() _renderSystem = new RenderSystem(new RenderingConfig()
@@ -52,11 +47,13 @@ public sealed partial class GraphicsTestWindow : Window
{ {
Width = (uint)AppWindow.Size.Width, Width = (uint)AppWindow.Size.Width,
Height = (uint)AppWindow.Size.Height, Height = (uint)AppWindow.Size.Height,
ScaleX = Panel.CompositionScaleX,
ScaleY = Panel.CompositionScaleY,
Format = TextureFormat.B8G8R8A8_UNorm, Format = TextureFormat.B8G8R8A8_UNorm,
Target = SwapChainTarget.FromCompositionSurface(Panel) Target = SwapChainTarget.FromCompositionSurface(Panel)
}); });
_renderer.SetRenderTarget(_swapChain.GetCurrentBackBuffer()); _renderer.RenderTargetStrategy = new SwapChainTargetStrategy(_swapChain);
_renderSystem.Start(); _renderSystem.Start();
CompositionTarget.Rendering += OnRendering; CompositionTarget.Rendering += OnRendering;
@@ -75,27 +72,16 @@ public sealed partial class GraphicsTestWindow : Window
_renderSystem?.Dispose(); _renderSystem?.Dispose();
#if DEBUG #if DEBUG
AllocationManager.Dispose(); Misaki.HighPerformance.LowLevel.Buffer.AllocationManager.Dispose();
#endif #endif
} }
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e) private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{ {
//if (e.NewSize.Width > 8.0 && e.NewSize.Height > 8.0) if (_renderSystem == null || _swapChain == null)
//{ {
// _renderer?.RequestResize(new((uint)e.NewSize.Width, (uint)e.NewSize.Height)); return;
//} }
_resizeTimer.Stop();
_resizeTimer.Start();
_isResizing = true;
}
private void OnResizeTimerTick(object? sender, object e)
{
_resizeTimer.Stop();
_isResizing = false;
var newWidth = (uint)(Panel.ActualWidth * Panel.CompositionScaleX); var newWidth = (uint)(Panel.ActualWidth * Panel.CompositionScaleX);
var newHeight = (uint)(Panel.ActualHeight * Panel.CompositionScaleY); var newHeight = (uint)(Panel.ActualHeight * Panel.CompositionScaleY);
@@ -105,8 +91,12 @@ public sealed partial class GraphicsTestWindow : Window
return; return;
} }
_renderSystem?.WaitIdle(); _renderSystem.RequestSwapChainResize(_swapChain, new uint2(newWidth, newHeight));
_swapChain?.Resize(newWidth, newHeight); }
private void SwapChainPanel_CompositionScaleChanged(SwapChainPanel sender, object args)
{
_swapChain?.SetScale(sender.CompositionScaleX, sender.CompositionScaleY);
} }
private void OnRendering(object? sender, object e) private void OnRendering(object? sender, object e)

View File

@@ -5,7 +5,7 @@ namespace Ghost.Graphics.Contracts;
public interface IRenderPass public interface IRenderPass
{ {
public void Initialize(ref readonly RenderingContext ctx); void Initialize(ref readonly RenderingContext ctx);
public void Execute(ref readonly RenderingContext ctx); void Execute(ref readonly RenderingContext ctx);
public void Cleanup(IResourceDatabase resourceDatabase); void Cleanup(IResourceDatabase resourceDatabase);
} }

View File

@@ -0,0 +1,30 @@
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.Contracts;
public interface IRenderTargetStrategy
{
/// <summary>
/// Gets a handle to the current render target texture.
/// </summary>
/// <returns>A handle to the texture that is currently set as the render target.</returns>
Handle<Texture> GetRenderTarget();
/// <summary>
/// Begins a rendering operation using the specified command buffer. Typically this will include resource barriers,
/// </summary>
/// <param name="cmd">The command buffer that records rendering commands.</param>
void BeginRender(ICommandBuffer cmd);
/// <summary>
/// Finalizes the rendering process using the specified command buffer.
/// </summary>
/// <param name="cmd">The command buffer that contains the rendering commands to be finalized.</param>
void EndRender(ICommandBuffer cmd);
/// <summary>
/// Displays the current frame to the output device or screen.
/// </summary>
/// <remarks>Call this method after rendering operations to present the rendered content. The exact
/// behavior may depend on the underlying graphics implementation or device.</remarks>
void Present();
}

View File

@@ -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<Texture> 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> _texture;
public TextureTargetStrategy(Handle<Texture> texture)
{
_texture = texture;
}
public Handle<Texture> GetRenderTarget()
{
return _texture;
}
public void BeginRender(ICommandBuffer cmd)
{
}
public void EndRender(ICommandBuffer cmd)
{
}
public void Present()
{
}
}

View File

@@ -12,28 +12,18 @@ public readonly unsafe ref struct RenderingContext
{ {
private readonly IGraphicsEngine _engine; private readonly IGraphicsEngine _engine;
private readonly ICommandBuffer _directCmd; private readonly ICommandBuffer _directCmd;
private readonly ICommandBuffer _copyCmd;
private readonly ICommandBuffer _computeCmd;
public ICommandBuffer DirectCommandBuffer => _directCmd; public ICommandBuffer DirectCommandBuffer => _directCmd;
public ICommandBuffer CopyCommandBuffer => _copyCmd;
public ICommandBuffer ComputeCommandBuffer => _computeCmd;
public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler; public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler;
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator; public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase; public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary; public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
internal RenderingContext( internal RenderingContext(IGraphicsEngine engine, ICommandBuffer directCmd)
IGraphicsEngine engine,
ICommandBuffer directCmd,
ICommandBuffer copyCmd,
ICommandBuffer computeCmd)
{ {
_engine = engine; _engine = engine;
_directCmd = directCmd; _directCmd = directCmd;
_copyCmd = copyCmd;
_computeCmd = computeCmd;
} }
public ICommandBuffer CrearteCommandBuffer(CommandBufferType type) public ICommandBuffer CrearteCommandBuffer(CommandBufferType type)

View File

@@ -1,5 +1,4 @@
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Ghost.Graphics.Core;
namespace Ghost.Graphics.Core; namespace Ghost.Graphics.Core;

View File

@@ -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<ID3D12CommandAllocator> _allocator;
public SharedPtr<ID3D12CommandAllocator> 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();
}
}

View File

@@ -19,7 +19,6 @@ namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12CommandBuffer : ICommandBuffer internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
private UniquePtr<ID3D12GraphicsCommandList10> _commandList; private UniquePtr<ID3D12GraphicsCommandList10> _commandList;
private UniquePtr<ID3D12CommandAllocator> _allocator;
private readonly D3D12PipelineLibrary _pipelineLibrary; private readonly D3D12PipelineLibrary _pipelineLibrary;
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
@@ -62,14 +61,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{ {
_type = type; _type = type;
ID3D12CommandAllocator* pAllocator = default;
ID3D12GraphicsCommandList10* pCommandList = 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); device.NativeDevice.Get()->CreateCommandList1(0u, commandListType, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof(pCommandList), (void**)&pCommandList);
_allocator.Attach(pAllocator);
_commandList.Attach(pCommandList); _commandList.Attach(pCommandList);
_pipelineLibrary = stateController; _pipelineLibrary = stateController;
@@ -85,17 +81,6 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
Dispose(); 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfDisposed() private void ThrowIfDisposed()
{ {
@@ -145,32 +130,28 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
#endif #endif
} }
public void Begin() public void Begin(ICommandAllocator allocator)
{ {
void ResetCommandList() ThrowIfDisposed();
ThrowIfRecording();
if (allocator is not D3D12CommandAllocator d3d12Allocator)
{ {
ThrowIfFailed(_allocator.Get()->Reset()); throw new ArgumentException("Invalid command allocator type", nameof(allocator));
ThrowIfFailed(_commandList.Get()->Reset(_allocator.Get(), null));
} }
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]; var heaps = stackalloc ID3D12DescriptorHeap*[2];
heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Bindless resource heap heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Bindless resource heap
heaps[1] = _descriptorAllocator.GetSamplerHeap(); // Bindless sampler heap heaps[1] = _descriptorAllocator.GetSamplerHeap(); // Bindless sampler heap
_commandList.Get()->SetDescriptorHeaps(2, heaps); _commandList.Get()->SetDescriptorHeaps(2, heaps);
} }
ThrowIfDisposed();
ThrowIfRecording();
ResetCommandList();
if (Type == CommandBufferType.Graphics || Type == CommandBufferType.Compute)
{
SetBindlessHeap();
}
_commandCount = 0; _commandCount = 0;
_isRecording = true; _isRecording = true;
} }
@@ -684,7 +665,6 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
} }
_commandList.Dispose(); _commandList.Dispose();
_allocator.Dispose();
_commandCount = 0; _commandCount = 0;
_disposed = true; _disposed = true;

View File

@@ -1,3 +1,7 @@
#if DEBUG
#define ENABLE_DEBUG
#endif
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
@@ -11,7 +15,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
{ {
private readonly IRenderSystem _renderSystem; private readonly IRenderSystem _renderSystem;
#if DEBUG #if ENABLE_DEBUG
private readonly D3D12DebugLayer _debugLayer; private readonly D3D12DebugLayer _debugLayer;
#endif #endif
@@ -21,7 +25,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12PipelineLibrary _pipelineLibrary; private readonly D3D12PipelineLibrary _pipelineLibrary;
private readonly D3D12ResourceAllocator _resourceAllocator; private readonly D3D12ResourceAllocator _resourceAllocator;
private readonly D3D12CommandBuffer _copyCommandBuffer;
private ImmutableArray<IRenderer> _renderers; private ImmutableArray<IRenderer> _renderers;
@@ -32,13 +35,12 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
public IPipelineLibrary PipelineLibrary => _pipelineLibrary; public IPipelineLibrary PipelineLibrary => _pipelineLibrary;
public IResourceDatabase ResourceDatabase => _resourceDatabase; public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceAllocator ResourceAllocator => _resourceAllocator; public IResourceAllocator ResourceAllocator => _resourceAllocator;
public ICommandBuffer CopyCommandBuffer => _copyCommandBuffer;
public D3D12GraphicsEngine(IRenderSystem renderSystem) public D3D12GraphicsEngine(IRenderSystem renderSystem)
{ {
_renderSystem = renderSystem; _renderSystem = renderSystem;
#if DEBUG #if ENABLE_DEBUG
_debugLayer = new D3D12DebugLayer(); _debugLayer = new D3D12DebugLayer();
#endif #endif
_device = new D3D12RenderDevice(); _device = new D3D12RenderDevice();
@@ -49,14 +51,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
_pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase); _pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase);
_resourceAllocator = new D3D12ResourceAllocator(renderSystem, _device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary); _resourceAllocator = new D3D12ResourceAllocator(renderSystem, _device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary);
_copyCommandBuffer = new D3D12CommandBuffer(
_device,
_pipelineLibrary,
_resourceDatabase,
_resourceAllocator,
_descriptorAllocator,
CommandBufferType.Copy);
_renderers = ImmutableArray<IRenderer>.Empty; _renderers = ImmutableArray<IRenderer>.Empty;
_pipelineLibrary.InitializeLibrary(null); _pipelineLibrary.InitializeLibrary(null);
@@ -94,6 +88,11 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Clear()); 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) public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
@@ -113,22 +112,27 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _renderSystem.MaxFrameLatency); return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _renderSystem.MaxFrameLatency);
} }
public void RenderFrame(ICommandBuffer commandBuffer) public Result RenderFrame(ICommandAllocator commandAllocator)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
_copyCommandBuffer.Begin(); var r = Result.Success();
foreach (var renderer in _renderers) foreach (var renderer in _renderers)
{ {
renderer.Render(commandBuffer); r = renderer.Render(commandAllocator);
if (r.IsFailure)
{
break;
}
} }
_copyCommandBuffer.End().ThrowIfFailed();
_resourceAllocator.ReleaseTempResources(); _resourceAllocator.ReleaseTempResources();
_descriptorAllocator.ResetCbvSrvUavDynamicHeap(); _descriptorAllocator.ResetCbvSrvUavDynamicHeap();
_descriptorAllocator.ResetDSVDynamicHeap(); _descriptorAllocator.ResetDSVDynamicHeap();
_descriptorAllocator.ResetRTVDynamicHeap(); _descriptorAllocator.ResetRTVDynamicHeap();
return r;
} }
public void Dispose() public void Dispose()
@@ -138,8 +142,6 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
return; return;
} }
_copyCommandBuffer.Dispose();
_resourceAllocator.Dispose(); _resourceAllocator.Dispose();
_pipelineLibrary.Dispose(); _pipelineLibrary.Dispose();
_resourceDatabase.Dispose(); _resourceDatabase.Dispose();
@@ -147,7 +149,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
_descriptorAllocator.Dispose(); _descriptorAllocator.Dispose();
_shaderCompiler.Dispose(); _shaderCompiler.Dispose();
_device.Dispose(); _device.Dispose();
#if DEBUG #if ENABLE_DEBUG
_debugLayer.Dispose(); _debugLayer.Dispose();
#endif #endif

View File

@@ -28,9 +28,9 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
public ICommandQueue ComputeQueue => _computeQueue; public ICommandQueue ComputeQueue => _computeQueue;
public ICommandQueue CopyQueue => _copyQueue; public ICommandQueue CopyQueue => _copyQueue;
public SharedPtr<IDXGIFactory7> DXGIFactory => _dxgiFactory.Get(); public SharedPtr<IDXGIFactory7> DXGIFactory => _dxgiFactory.Share();
public SharedPtr<ID3D12Device14> NativeDevice => _device.Get(); public SharedPtr<ID3D12Device14> NativeDevice => _device.Share();
public SharedPtr<IDXGIAdapter1> Adapter => _adapter.Get(); public SharedPtr<IDXGIAdapter1> Adapter => _adapter.Share();
public SharedPtr<ID3D12CommandQueue> NativeGraphicsQueue => _graphicsQueue.NativeQueue; public SharedPtr<ID3D12CommandQueue> NativeGraphicsQueue => _graphicsQueue.NativeQueue;
public SharedPtr<ID3D12CommandQueue> NativeComputeQueue => _computeQueue.NativeQueue; public SharedPtr<ID3D12CommandQueue> NativeComputeQueue => _computeQueue.NativeQueue;
public SharedPtr<ID3D12CommandQueue> NativeCopyQueue => _copyQueue.NativeQueue; public SharedPtr<ID3D12CommandQueue> NativeCopyQueue => _copyQueue.NativeQueue;

View File

@@ -2,6 +2,7 @@ using Ghost.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Core; using Ghost.Graphics.Core;
using Ghost.Graphics.RenderPasses; using Ghost.Graphics.RenderPasses;
using Ghost.Graphics.Contracts;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -10,48 +11,30 @@ namespace Ghost.Graphics.D3D12;
/// </summary> /// </summary>
internal class D3D12Renderer : IRenderer 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 readonly D3D12GraphicsEngine _graphicsEngine;
private uint _frameIndex;
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
private Handle<Texture> _renderTarget; private readonly ICommandBuffer _commandBuffer;
private uint _frameIndex;
private bool _disposed; private bool _disposed;
// NOTE: Testing only. // NOTE: Testing only.
private readonly MeshRenderPass _pass; private readonly MeshRenderPass _pass;
public Handle<Texture> RenderTarget => _renderTarget; public IRenderTargetStrategy? RenderTargetStrategy
{
get; set;
}
// TODO: Add render passes support // TODO: Add render graph support
// private ImmutableArray<IRenderPass> _renderPasses;
public D3D12Renderer(D3D12GraphicsEngine graphicsEngine, D3D12ResourceDatabase resourceDatabase) public D3D12Renderer(D3D12GraphicsEngine graphicsEngine, D3D12ResourceDatabase resourceDatabase)
{ {
_graphicsEngine = graphicsEngine; _graphicsEngine = graphicsEngine;
_resourceDatabase = resourceDatabase; _resourceDatabase = resourceDatabase;
_renderTarget = Handle<Texture>.Invalid; _commandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
// NOTE: Testing only. // NOTE: Testing only.
_pass = new(); _pass = new();
@@ -62,36 +45,47 @@ internal class D3D12Renderer : IRenderer
Dispose(); Dispose();
} }
public void SetRenderTarget(Handle<Texture> renderTarget) public Result Render(ICommandAllocator commandAllocator)
{ {
_resourceDatabase.ReleaseResource(_renderTarget.AsResource()); if (RenderTargetStrategy is null)
_renderTarget = renderTarget;
}
public Result Render(ICommandBuffer commandBuffer)
{
if (!_renderTarget.IsValid)
{ {
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. // NOTE: Temperary solution: render directly to the swap chain back buffer if available.
// HACK: This is hard coded for testing purposes only. // HACK: This is hard coded for testing purposes only.
var error = RenderScene(_renderTarget, commandBuffer); var error = RenderScene(target);
if (error != ErrorStatus.None) if (error != ErrorStatus.None)
{ {
commandBuffer.End(); _commandBuffer.End();
return Result.Failure(error); 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. // TODO: A proper render graph integration.
private ErrorStatus RenderScene(Handle<Texture> target, ICommandBuffer cmd) private ErrorStatus RenderScene(Handle<Texture> target)
{ {
var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f }; 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. // NOTE: Testing only.
var ctx = new RenderingContext(_graphicsEngine, cmd, _graphicsEngine.CopyCommandBuffer, null!); var ctx = new RenderingContext(_graphicsEngine, _commandBuffer);
if (_frameIndex == 0) if (_frameIndex == 0)
{ {
_pass.Initialize(ref ctx); _pass.Initialize(ref ctx);
@@ -124,18 +118,20 @@ internal class D3D12Renderer : IRenderer
return result.Error; return result.Error;
} }
// TODO: Decouple viewport and scissor from the texture size.
var texDesc = result.Value.TextureDescription; var texDesc = result.Value.TextureDescription;
var viewport = new ViewportDesc { Width = texDesc.Width, Height = texDesc.Height, MinDepth = 0, MaxDepth = 1 }; var viewport = new ViewportDesc { Width = texDesc.Width, Height = texDesc.Height, MinDepth = 0, MaxDepth = 1 };
var scissor = new RectDesc { Right = texDesc.Width, Bottom = texDesc.Height }; var scissor = new RectDesc { Right = texDesc.Width, Bottom = texDesc.Height };
cmd.BeginRenderPass(rtDesc, depthDesc, false); _commandBuffer.BeginRenderPass(rtDesc, depthDesc, false);
cmd.SetViewport(viewport); _commandBuffer.SetViewport(viewport);
cmd.SetScissorRect(scissor); _commandBuffer.SetScissorRect(scissor);
// NOTE: Testing only. // NOTE: Testing only.
_pass.Execute(ref ctx); _pass.Execute(ref ctx);
cmd.EndRenderPass(); _commandBuffer.EndRenderPass();
_frameIndex++;
return ErrorStatus.None; return ErrorStatus.None;
} }
@@ -150,6 +146,8 @@ internal class D3D12Renderer : IRenderer
// NOTE: Testing only. // NOTE: Testing only.
_pass.Cleanup(_resourceDatabase); _pass.Cleanup(_resourceDatabase);
_commandBuffer.Dispose();
_disposed = true; _disposed = true;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);

View File

@@ -104,7 +104,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase
private readonly DynamicArray<Shader?> _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 DynamicArray<Shader?> _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<ShaderPassKey, ShaderPass> _shaderPasses; // NOTE: The reason we use Dictionary here is that ShaderPassKey is a presistence identifier across multiple application sessions. private readonly Dictionary<ShaderPassKey, ShaderPass> _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; private bool _disposed;
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator) public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
@@ -120,8 +119,6 @@ internal class D3D12ResourceDatabase : IResourceDatabase
_materials = new UnsafeSlotMap<Material>(16, Allocator.Persistent, AllocationOption.Clear); _materials = new UnsafeSlotMap<Material>(16, Allocator.Persistent, AllocationOption.Clear);
_shaders = new DynamicArray<Shader?>(16); _shaders = new DynamicArray<Shader?>(16);
_shaderPasses = new Dictionary<ShaderPassKey, ShaderPass>(16); _shaderPasses = new Dictionary<ShaderPassKey, ShaderPass>(16);
_lastSamplerId = -1;
} }
~D3D12ResourceDatabase() ~D3D12ResourceDatabase()

View File

@@ -42,6 +42,16 @@ internal unsafe class D3D12SwapChain : ISwapChain
get; private set; 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) public D3D12SwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount)
{ {
Debug.Assert(bufferCount >= 2); Debug.Assert(bufferCount >= 2);
@@ -55,8 +65,11 @@ internal unsafe class D3D12SwapChain : ISwapChain
Width = desc.Width; Width = desc.Width;
Height = desc.Height; Height = desc.Height;
CreateSwapChain(desc, bufferCount); var pSwapChian = CreateSwapChain(desc, bufferCount);
_swapChain.Attach(pSwapChian);
CreateBackBuffers(); CreateBackBuffers();
SetScale(desc.ScaleX, desc.ScaleY);
_compositionSurface = desc.Target.CompositionSurface; _compositionSurface = desc.Target.CompositionSurface;
} }
@@ -66,7 +79,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
Dispose(); Dispose();
} }
private void CreateSwapChain(SwapChainDesc desc, uint bufferCount) private IDXGISwapChain4* CreateSwapChain(SwapChainDesc desc, uint bufferCount)
{ {
var swapChainDesc = new DXGI_SWAP_CHAIN_DESC1 var swapChainDesc = new DXGI_SWAP_CHAIN_DESC1
{ {
@@ -124,7 +137,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
pTempSwapChain->QueryInterface(__uuidof(pSwapChain), (void**)&pSwapChain); pTempSwapChain->QueryInterface(__uuidof(pSwapChain), (void**)&pSwapChain);
pTempSwapChain->Release(); pTempSwapChain->Release();
_swapChain.Attach(pSwapChain); return pSwapChain;
} }
private void CreateBackBuffers() private void CreateBackBuffers()
@@ -155,6 +168,13 @@ internal unsafe class D3D12SwapChain : ISwapChain
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()]; return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()];
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<Handle<Texture>> GetBackBuffers()
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _backBuffers.AsSpan();
}
public void Present(bool vsync = true) public void Present(bool vsync = true)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
@@ -186,20 +206,27 @@ internal unsafe class D3D12SwapChain : ISwapChain
Height = height; Height = height;
CreateBackBuffers(); 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 DXGI_MATRIX_3X2_F inverseScaleMatrix = new DXGI_MATRIX_3X2_F
//{ {
// _11 = inverseScale, // Scale X _11 = inverseScaleX, // Scale X
// _22 = inverseScale, // Scale Y _22 = inverseScaleY, // Scale Y
// _12 = 0.0f, _12 = 0.0f,
// _21 = 0.0f, _21 = 0.0f,
// _31 = 0.0f, // Offset X _31 = 0.0f, // Offset X
// _32 = 0.0f // Offset Y _32 = 0.0f // Offset Y
//}; };
//_swapChain.Get()->SetMatrixTransform(&inverseScaleMatrix); _swapChain.Get()->SetMatrixTransform(&inverseScaleMatrix);
ScaleX = scaleX;
ScaleY = scaleY;
} }
public void Dispose() public void Dispose()

View File

@@ -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( public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CREATE(
D3D12_FILL_MODE fillMode, D3D12_FILL_MODE fillMode,

View File

@@ -758,6 +758,15 @@ public struct SwapChainDesc
get; set; get; set;
} }
public float ScaleX
{
get; set;
}
public float ScaleY
{
get; set;
}
/// <summary> /// <summary>
/// Back buffer Format /// Back buffer Format

View File

@@ -0,0 +1,6 @@
namespace Ghost.Graphics.RHI;
public interface ICommandAllocator : IDisposable
{
void Reset();
}

View File

@@ -35,7 +35,7 @@ public interface ICommandBuffer : IDisposable
/// <summary> /// <summary>
/// Begins recording commands into this command buffer /// Begins recording commands into this command buffer
/// </summary> /// </summary>
void Begin(); void Begin(ICommandAllocator allocator);
/// <summary> /// <summary>
/// Ends recording commands and prepares for submission /// Ends recording commands and prepares for submission

View File

@@ -30,10 +30,32 @@ public interface IGraphicsEngine : IDisposable
get; get;
} }
/// <summary>
/// Creates a new instance of an object that implements the IRenderer interface.
/// </summary>
/// <returns>An object that provides rendering functionality through the IRenderer interface.</returns>
IRenderer CreateRenderer(); IRenderer CreateRenderer();
/// <summary>
/// Removes the specified renderer from the collection of active renderers.
/// </summary>
/// <param name="renderer">The renderer instance to remove. Cannot be null.</param>
void RemoveRenderer(IRenderer renderer); void RemoveRenderer(IRenderer renderer);
/// <summary>
/// Removes all registered renderers from the collection.
/// </summary>
/// <remarks>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.</remarks>
void ClearRenderers(); void ClearRenderers();
/// <summary>
/// Creates a new command allocator for the specified command buffer type.
/// </summary>
/// <param name="type">The type of command buffer for which to create the allocator. The default is CommandBufferType.Graphics.</param>
/// <returns>An <see cref="ICommandAllocator"/> instance configured for the specified command buffer type.</returns>
ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics);
/// <summary> /// <summary>
/// Creates a command buffer for recording rendering commands /// Creates a command buffer for recording rendering commands
/// </summary> /// </summary>
@@ -51,5 +73,7 @@ public interface IGraphicsEngine : IDisposable
/// <summary> /// <summary>
/// Renders the current frame. /// Renders the current frame.
/// </summary> /// </summary>
void RenderFrame(ICommandBuffer commandBuffer); /// <param name="commandAllocator">Command allocator to use for rendering</param>
/// <returns>Result of the rendering operation</returns>
Result RenderFrame(ICommandAllocator commandAllocator);
} }

View File

@@ -1,5 +1,5 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.Core; using Ghost.Graphics.Contracts;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
@@ -8,21 +8,15 @@ namespace Ghost.Graphics.RHI;
/// </summary> /// </summary>
public interface IRenderer : IDisposable public interface IRenderer : IDisposable
{ {
Handle<Texture> RenderTarget IRenderTargetStrategy? RenderTargetStrategy
{ {
get; get; set;
} }
/// <summary>
/// Sets the render Target for this renderer
/// </summary>
/// <param name="renderTarget">Render Target to render into</param>
public void SetRenderTarget(Handle<Texture> renderTarget);
/// <summary> /// <summary>
/// Renders a frame /// Renders a frame
/// </summary> /// </summary>
/// <param name="commandBuffer">Command buffer to record rendering commands into</param> /// <param name="commandAllocator">Command allocator to use for rendering</param>
/// <returns>Result of the rendering operation</returns> /// <returns>Result of the rendering operation</returns>
public Result Render(ICommandBuffer commandBuffer); Result Render(ICommandAllocator commandAllocator);
} }

View File

@@ -11,7 +11,7 @@ public interface ISwapChain : IDisposable
/// <summary> /// <summary>
/// Width of the swap chain back buffers /// Width of the swap chain back buffers
/// </summary> /// </summary>
public uint Width uint Width
{ {
get; get;
} }
@@ -19,7 +19,23 @@ public interface ISwapChain : IDisposable
/// <summary> /// <summary>
/// Height of the swap chain back buffers /// Height of the swap chain back buffers
/// </summary> /// </summary>
public uint Height uint Height
{
get;
}
/// <summary>
/// Gets the horizontal scaling factor applied to the object. This is used for DPI scaling.
/// </summary>
float ScaleX
{
get;
}
/// <summary>
/// Gets the vertical scale factor applied to the object. This is used for DPI scaling.
/// </summary>
float ScaleY
{ {
get; get;
} }
@@ -28,18 +44,31 @@ public interface ISwapChain : IDisposable
/// Gets the current back buffer texture /// Gets the current back buffer texture
/// </summary> /// </summary>
/// <returns>Current back buffer texture</returns> /// <returns>Current back buffer texture</returns>
public Handle<Texture> GetCurrentBackBuffer(); Handle<Texture> GetCurrentBackBuffer();
/// <summary>
/// Gets all back buffer textures
/// </summary>
/// <returns>All back buffer textures</returns>
ReadOnlySpan<Handle<Texture>> GetBackBuffers();
/// <summary> /// <summary>
/// Presents the rendered frame /// Presents the rendered frame
/// </summary> /// </summary>
/// <param name="vsync">Enable vertical synchronization</param> /// <param name="vsync">Enable vertical synchronization</param>
public void Present(bool vsync = true); void Present(bool vsync = true);
/// <summary> /// <summary>
/// Resizes the swap chain back buffers /// Resizes the swap chain back buffers
/// </summary> /// </summary>
/// <param name="width">New Width</param> /// <param name="width">New Width</param>
/// <param name="height">New Height</param> /// <param name="height">New Height</param>
public void Resize(uint width, uint height); void Resize(uint width, uint height);
/// <summary>
/// Sets the horizontal and vertical scaling factors for the object.
/// </summary>
/// <param name="scaleX">The factor by which to scale the object along the X-axis.</param>
/// <param name="scaleY">The factor by which to scale the object along the Y-axis.</param>
void SetScale(float scaleX, float scaleY);
} }

View File

@@ -1,4 +1,8 @@
using Ghost.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
using System.Collections.Concurrent;
using System.Diagnostics;
namespace Ghost.Graphics; namespace Ghost.Graphics;
@@ -61,6 +65,7 @@ public interface IRenderSystem : IFenceSynchronizer, IDisposable
void Start(); void Start();
void Stop(); void Stop();
void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize);
} }
/// <summary> /// <summary>
@@ -82,7 +87,7 @@ internal class RenderSystem : IRenderSystem
get; init; get; init;
} }
public required ICommandBuffer CommandBuffer public required ICommandAllocator CommandAllocator
{ {
get; init; get; init;
} }
@@ -96,7 +101,7 @@ internal class RenderSystem : IRenderSystem
{ {
CpuReadyEvent.Dispose(); CpuReadyEvent.Dispose();
GpuReadyEvent.Dispose(); GpuReadyEvent.Dispose();
CommandBuffer.Dispose(); CommandAllocator.Dispose();
} }
} }
@@ -106,6 +111,7 @@ internal class RenderSystem : IRenderSystem
private readonly FrameResource[] _frameResources; private readonly FrameResource[] _frameResources;
private readonly Thread _renderThread; private readonly Thread _renderThread;
private readonly AutoResetEvent _shutdownEvent; private readonly AutoResetEvent _shutdownEvent;
private readonly ConcurrentDictionary<ISwapChain, uint2> _resizeRequest;
private uint _frameIndex; private uint _frameIndex;
private uint _cpuFenceValue; private uint _cpuFenceValue;
@@ -131,8 +137,6 @@ internal class RenderSystem : IRenderSystem
_ => throw new NotSupportedException($"Graphics API {config.GraphicsAPI} is not supported.") _ => throw new NotSupportedException($"Graphics API {config.GraphicsAPI} is not supported.")
}; };
_shutdownEvent = new AutoResetEvent(false);
// Create frame resources for synchronization // Create frame resources for synchronization
_frameResources = new FrameResource[config.FrameBufferCount]; _frameResources = new FrameResource[config.FrameBufferCount];
for (var i = 0; i < config.FrameBufferCount; i++) for (var i = 0; i < config.FrameBufferCount; i++)
@@ -141,7 +145,7 @@ internal class RenderSystem : IRenderSystem
{ {
CpuReadyEvent = new AutoResetEvent(false), CpuReadyEvent = new AutoResetEvent(false),
GpuReadyEvent = new AutoResetEvent(true), 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 Priority = ThreadPriority.Normal
}; };
_shutdownEvent = new AutoResetEvent(false);
_resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>();
_isRunning = false; _isRunning = false;
_disposed = false; _disposed = false;
} }
@@ -188,6 +195,12 @@ internal class RenderSystem : IRenderSystem
_renderThread.Join(); _renderThread.Join();
} }
public void RequestSwapChainResize(ISwapChain swapChain, uint2 newSize)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
}
public bool WaitForGPUReady(int timeOut = -1) public bool WaitForGPUReady(int timeOut = -1)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
@@ -243,13 +256,31 @@ internal class RenderSystem : IRenderSystem
_graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue); _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++; _gpuFenceValue++;
frameResource.GpuReadyEvent.Set();
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_frameIndex); frameResource.GpuReadyEvent.Set();
_frameIndex++; frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_gpuFenceValue);
} }
} }
} }