Add new interfaces and refactor rendering logic

Added a new `ConstPtr<T>` struct for type-safe pointers.
Added a new `ICommandBuffer` interface for resource copying.
Added a new `IRenderPass` interface to define render passes.
Added a new `IResource` interface for GPU resources.
Added a new `IResourceAllocator` interface for resource management.
Added a new `ISwapChainPanelNative` struct for native interactions.
Added a new `D3D12Utility` class for Direct3D 12 utilities.
Added a new package reference for `Vortice.Win32.Graphics.D3D12MemoryAllocator`.

Changed project file to allow unsafe code blocks.
Changed `Result` struct methods to improve clarity.
Changed error handling in `ProjectService` and `AssetDatabase` to use `Result.Failure()`.
Changed `launchSettings.json` to enable native debugging.
Changed rendering logic in `ScenePage.xaml.cs` to use `IRenderer`.
Changed `IGraphicsDevice` interface to include renderer properties.
Changed `IRenderView` to `IRenderer` and updated its methods.
Changed `Mesh` class to use the new `IResource` interface for buffers.
Changed `GraphicsAPI` enum to include a `None` value.
Changed various aspects of the `GraphicsPipeline` class for new architecture.

Removed the old `DX12RenderView` class and replaced it with `DX12Renderer`.
Removed unnecessary code in the `ResourceView` class.
This commit is contained in:
2025-06-30 13:50:06 +09:00
parent 8fd1222780
commit 300ae7251b
27 changed files with 765 additions and 486 deletions

View File

@@ -0,0 +1,324 @@
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Data;
using Ghost.Graphics.DX12.Utilities;
using System.Collections.Immutable;
using Win32;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
using Win32.Graphics.Dxgi.Common;
using static Win32.Apis;
namespace Ghost.Graphics.DX12;
internal unsafe class DX12Renderer : IRenderer
{
private class FrameResource : IDisposable
{
public readonly ID3D12CommandAllocator commandAllocator;
public readonly ID3D12GraphicsCommandList10 commandList;
public readonly ICommandBuffer commandBuffer;
public ID3D12Resource backBuffer;
public uint backBufferDescriptorIndexes;
public ulong fenceValue;
public FrameResource(DX12Renderer renderer, uint index)
{
commandAllocator = renderer._graphicsDevice.NativeDevice.CreateCommandAllocator(CommandListType.Direct);
commandList = renderer._graphicsDevice.NativeDevice.CreateCommandList<ID3D12GraphicsCommandList10>(CommandListType.Direct, commandAllocator);
commandBuffer = new DX12CommandBuffer(commandList);
renderer.CreateBackBufferResource(index, out backBuffer, out backBufferDescriptorIndexes);
commandList.Close();
}
public void ResetCommandList()
{
commandAllocator.Reset();
commandList.Reset(commandAllocator, null);
}
public void IncrementFenceValue()
{
fenceValue++;
}
public void Dispose()
{
commandAllocator.Dispose();
commandList.Dispose();
backBuffer?.Dispose();
}
}
private const int _RENDER_TARGET_VIEW_HEAP_SIZE = 1024;
private const int _DEPTH_STENCIL_VIEW_HEAP_SIZE = 256;
private readonly DX12GraphicsDevice _graphicsDevice;
private readonly SwapChainPresenter _swapChainPresenter;
private readonly ComPtr<IDXGISwapChain4> _swapChain;
private readonly FrameResource[] _frameResources;
private uint _backBufferIndex;
private readonly ComPtr<ID3D12Fence1> _fence;
private readonly AutoResetEvent _fenceEvent;
private readonly D3D12DescriptorAllocator _rtvHeap;
private ImmutableArray<IRenderPass> _renderPasses;
private readonly Lock _lock = new();
private uint _pendingWidth;
private uint _pendingHeight;
private bool _resizeRequested;
private bool _disposed;
public ReadOnlySpan<IRenderPass> RenderPasses => _renderPasses.AsSpan();
public DX12Renderer(DX12GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface)
{
_graphicsDevice = graphicsDevice;
_swapChainPresenter = swapChainSurface;
_rtvHeap = new D3D12DescriptorAllocator(_graphicsDevice.NativeDevice, DescriptorHeapType.Rtv, _RENDER_TARGET_VIEW_HEAP_SIZE);
_fenceEvent = new(false);
_renderPasses = ImmutableArray<IRenderPass>.Empty;
InitializeSwapChain();
InitializeCommandObjects(out _frameResources, out _fence);
}
private void InitializeSwapChain()
{
var swapChainDesc = new SwapChainDescription1
{
Width = _swapChainPresenter.Width,
Height = _swapChainPresenter.Height,
Format = Format.B8G8R8A8Unorm,
Stereo = false,
SampleDesc = new(1, 0),
BufferUsage = Usage.BackBuffer | Usage.RenderTargetOutput,
BufferCount = GraphicsPipeline._FRAME_COUNT,
Scaling = Scaling.Stretch,
SwapEffect = SwapEffect.FlipDiscard,
AlphaMode = AlphaMode.Ignore,
Flags = SwapChainFlags.AllowTearing
};
using ComPtr<IDXGISwapChain1> tempSwapChain = default;
switch (_swapChainPresenter.Type)
{
case SwapChainPresenter.TargetType.Composition:
{
_graphicsDevice.DXGIFactory.Ptr->
CreateSwapChainForComposition(
(IUnknown*)_graphicsDevice.CommandQueue.Ptr,
&swapChainDesc, null,
(IDXGISwapChain1**)&tempSwapChain);
fixed (void* swapChainPtr = &_swapChain)
{
tempSwapChain.Get()->QueryInterface(__uuidof<IDXGISwapChain4>(), &swapChainPtr);
}
_swapChainPresenter.SwapChainPanelNative.SetSwapChain((IntPtr)_swapChain.Get());
break;
}
case SwapChainPresenter.TargetType.Hwnd:
{
var swapChainFullscreenDesc = new SwapChainFullscreenDescription
{
Windowed = true,
};
_graphicsDevice.DXGIFactory.Ptr->
CreateSwapChainForHwnd(
(IUnknown*)_graphicsDevice.CommandQueue.Ptr,
_swapChainPresenter.Hwnd,
&swapChainDesc,
&swapChainFullscreenDesc,
null,
(IDXGISwapChain1**)&tempSwapChain);
fixed (void* swapChainPtr = &_swapChain)
{
tempSwapChain.Get()->QueryInterface(__uuidof<IDXGISwapChain4>(), &swapChainPtr);
}
break;
}
default:
throw new ArgumentException("Unsupported swap chain surface type.");
}
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
}
private void InitializeCommandObjects(out FrameResource[] frameResources)
{
frameResources = new FrameResource[GraphicsPipeline._FRAME_COUNT];
for (var i = 0u; i < GraphicsPipeline._FRAME_COUNT; i++)
{
frameResources[i] = new FrameResource(this, i);
}
fixed (void* fencePtr = &_fence)
{
_graphicsDevice.NativeDevice.Ptr->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), &fencePtr);
}
frameResources[0].IncrementFenceValue();
}
public void RequestResize(uint width, uint height)
{
lock (_lock)
{
if (_pendingWidth == width && _pendingHeight == height)
{
return;
}
_resizeRequested = true;
_pendingWidth = width;
_pendingHeight = height;
}
}
private void CreateBackBufferResource(uint i, out ID3D12Resource backBuffer, out uint index)
{
backBuffer = _swapChain.GetBuffer<ID3D12Resource>(i);
backBuffer.Name = $"BackBuffer_{i}";
index = _rtvHeap.AllocateDescriptor();
var rtvHandle = _rtvHeap.GetCpuHandle(index);
_graphicsDevice.NativeDevice.CreateRenderTargetView(backBuffer, null, rtvHandle);
}
public void ExecutePendingResize()
{
if (!_resizeRequested)
{
return;
}
uint newWidth;
uint newHeight;
lock (_lock)
{
newWidth = _pendingWidth;
newHeight = _pendingHeight;
_resizeRequested = false;
}
WaitIdle();
for (var i = 0; i < GraphicsPipeline._FRAME_COUNT; i++)
{
var backBuffer = _frameResources[i].backBuffer;
if (backBuffer is not null)
{
backBuffer.Dispose();
_rtvHeap.ReleaseDescriptor(_frameResources[i].backBufferDescriptorIndexes);
}
_frameResources[i].fenceValue = _frameResources[_backBufferIndex].fenceValue;
}
_swapChain.ResizeBuffers(GraphicsPipeline._FRAME_COUNT, newWidth, newHeight, Format.B8G8R8A8_UNorm, SwapChainFlags.AllowTearing).CheckError();
for (var i = 0u; i < GraphicsPipeline._FRAME_COUNT; i++)
{
CreateBackBufferResource(i, out var backBuffer, out var index);
_frameResources[i].backBuffer = backBuffer;
_frameResources[i].backBufferDescriptorIndexes = index;
}
}
public void Render()
{
_backBufferIndex = _swapChain.CurrentBackBufferIndex;
var frameResource = _frameResources[_backBufferIndex];
frameResource.ResetCommandList();
frameResource.commandList.ResourceBarrierTransition(_frameResources[_backBufferIndex].backBuffer!, ResourceStates.Present, ResourceStates.RenderTarget);
foreach (var pass in _renderPasses)
{
pass.Execute(frameResource.commandBuffer);
}
frameResource.commandList.ResourceBarrierTransition(_frameResources[_backBufferIndex].backBuffer!, ResourceStates.RenderTarget, ResourceStates.Present);
frameResource.commandList.Close();
_graphicsDevice.CommandQueue.ExecuteCommandList(frameResource.commandList);
_swapChain.Present(1, PresentFlags.None).CheckError();
WaitNextFrame();
}
public void WaitNextFrame()
{
var resource = _frameResources[_backBufferIndex];
if (_graphicsDevice.CommandQueue.Signal(_fence, resource.fenceValue).Failure)
{
return;
}
if (_fence.CompletedValue < resource.fenceValue
&& _fence.SetEventOnCompletion(resource.fenceValue, _fenceEvent.SafeWaitHandle.DangerousGetHandle()).Success)
{
_fenceEvent.WaitOne();
}
resource.IncrementFenceValue();
}
public void WaitIdle()
{
var resource = _frameResources[_backBufferIndex];
if (_graphicsDevice.CommandQueue.Signal(_fence, resource.fenceValue).Success
&& _fence.SetEventOnCompletion(resource.fenceValue, _fenceEvent.SafeWaitHandle.DangerousGetHandle()).Success)
{
_fenceEvent.WaitOne();
resource.IncrementFenceValue();
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
WaitIdle();
_swapChainPresenter.SwapChainPanelNative?.SetSwapChain(null);
foreach (var pass in _renderPasses)
{
pass.Dispose();
}
foreach (var frameResource in _frameResources)
{
frameResource.Dispose();
}
_swapChain.Dispose();
_fence.Dispose();
_fenceEvent.Dispose();
_rtvHeap.Dispose();
_backBufferIndex = 0;
_disposed = true;
}
}