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

17
Ghost.Core/ConstPtr.cs Normal file
View File

@@ -0,0 +1,17 @@
namespace Ghost.Core;
public unsafe readonly struct ConstPtr<T>
where T : unmanaged
{
private readonly T* _ptr;
public ConstPtr(T* ptr)
{
_ptr = ptr;
}
public readonly T* Ptr => _ptr;
public static implicit operator T*(ConstPtr<T> constPtr) => constPtr._ptr;
public static implicit operator ConstPtr<T>(T* ptr) => new(ptr);
}

View File

@@ -4,6 +4,7 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@@ -12,17 +12,17 @@ public readonly struct Result
this.message = message; this.message = message;
} }
public static Result OK() public static Result Success()
{ {
return new Result(true); return new Result(true);
} }
public static Result Error(string? message) public static Result Failure(string? message)
{ {
return new Result(false, message); return new Result(false, message);
} }
public void CheckSuccess() public void EnsureSuccess()
{ {
if (!success) if (!success)
{ {
@@ -47,17 +47,17 @@ public readonly struct Result<T>
this.message = message; this.message = message;
} }
public static Result<T> OK(T data) public static Result<T> Success(T data)
{ {
return new Result<T>(true, data); return new Result<T>(true, data);
} }
public static Result<T> Error(string? message) public static Result<T> Failure(string? message)
{ {
return new Result<T>(false, default!, message); return new Result<T>(false, default!, message);
} }
public void CheckSuccess() public void EnsureSuccess()
{ {
if (!success) if (!success)
{ {

View File

@@ -75,29 +75,29 @@ internal partial class ProjectService
{ {
if (string.IsNullOrWhiteSpace(projectDirectory) || !Directory.Exists(projectDirectory)) if (string.IsNullOrWhiteSpace(projectDirectory) || !Directory.Exists(projectDirectory))
{ {
return Result<ProjectMetadataInfo>.Error("Project directory is invalid or does not exist."); return Result<ProjectMetadataInfo>.Failure("Project directory is invalid or does not exist.");
} }
var projectAssetsPath = Path.Combine(projectDirectory, ASSETS_FOLDER); var projectAssetsPath = Path.Combine(projectDirectory, ASSETS_FOLDER);
var projectConfigPath = Path.Combine(projectDirectory, CONFIG_FOLDER); var projectConfigPath = Path.Combine(projectDirectory, CONFIG_FOLDER);
if (!Directory.Exists(projectAssetsPath) || !Directory.Exists(projectConfigPath)) if (!Directory.Exists(projectAssetsPath) || !Directory.Exists(projectConfigPath))
{ {
return Result<ProjectMetadataInfo>.Error("Project folder structure is invalid."); return Result<ProjectMetadataInfo>.Failure("Project folder structure is invalid.");
} }
var metadataPath = Directory.GetFiles(projectDirectory, $"*.{ProjectMetadata.PROJECT_EXTENSION}", SearchOption.TopDirectoryOnly).FirstOrDefault(); var metadataPath = Directory.GetFiles(projectDirectory, $"*.{ProjectMetadata.PROJECT_EXTENSION}", SearchOption.TopDirectoryOnly).FirstOrDefault();
if (string.IsNullOrWhiteSpace(metadataPath) || !File.Exists(metadataPath)) if (string.IsNullOrWhiteSpace(metadataPath) || !File.Exists(metadataPath))
{ {
return Result<ProjectMetadataInfo>.Error("Project metadata file not found."); return Result<ProjectMetadataInfo>.Failure("Project metadata file not found.");
} }
var metadata = await LoadMetadataAsync(metadataPath); var metadata = await LoadMetadataAsync(metadataPath);
if (metadata == null) if (metadata == null)
{ {
return Result<ProjectMetadataInfo>.Error("Project metadata file is corrupted or invalid."); return Result<ProjectMetadataInfo>.Failure("Project metadata file is corrupted or invalid.");
} }
return Result<ProjectMetadataInfo>.OK(new(metadataPath, metadata)); return Result<ProjectMetadataInfo>.Success(new(metadataPath, metadata));
} }
private static async ValueTask SetupRequestFolderAsync(string projectDirectory, string templateDirectory) private static async ValueTask SetupRequestFolderAsync(string projectDirectory, string templateDirectory)
@@ -186,7 +186,7 @@ internal partial class ProjectService
} }
catch (Exception e) catch (Exception e)
{ {
return Result<ProjectMetadataInfo>.Error($"Failed to create project: {e.Message}"); return Result<ProjectMetadataInfo>.Failure($"Failed to create project: {e.Message}");
} }
} }
@@ -200,7 +200,7 @@ internal partial class ProjectService
if (await HasProjectAsync(result.value.Path)) if (await HasProjectAsync(result.value.Path))
{ {
return Result<ProjectMetadataInfo>.Error("Project already exists."); return Result<ProjectMetadataInfo>.Failure("Project already exists.");
} }
await AddProjectAsync(result.value.Metadata.Name, result.value.Path); await AddProjectAsync(result.value.Metadata.Name, result.value.Path);

View File

@@ -35,15 +35,15 @@ public static partial class AssetDatabase
{ {
if (Directory.Exists(assetPath)) if (Directory.Exists(assetPath))
{ {
return Result<string>.Error("Folder does not have meta data"); return Result<string>.Failure("Folder does not have meta data");
} }
if (Path.GetExtension(assetPath).Equals(".meta", StringComparison.OrdinalIgnoreCase)) if (Path.GetExtension(assetPath).Equals(".meta", StringComparison.OrdinalIgnoreCase))
{ {
return Result<string>.Error("Asset path cannot be a meta file"); return Result<string>.Failure("Asset path cannot be a meta file");
} }
return Result<string>.OK(assetPath + ".meta"); return Result<string>.Success(assetPath + ".meta");
} }
private static ImporterSettings? GetDefaultSettingsForAsset(string assetPath) private static ImporterSettings? GetDefaultSettingsForAsset(string assetPath)

View File

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

View File

@@ -2,15 +2,14 @@ using Ghost.Editor.Controls.Internal;
using Ghost.Graphics; using Ghost.Graphics;
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Vortice.WinUI; using Microsoft.UI.Xaml.Controls;
using WinRT;
namespace Ghost.Editor.View.Pages.EngineEditor; namespace Ghost.Editor.View.Pages.EngineEditor;
internal sealed partial class ScenePage : NavigationTabPage internal sealed partial class ScenePage : NavigationTabPage
{ {
private IRenderView? _renderer; private IRenderer? _renderView;
private ISwapChainPanelNative2? _swapChainPanelNative; private ISwapChainPanelNative _swapChainPanelNative;
public ScenePage() public ScenePage()
{ {
@@ -23,16 +22,16 @@ internal sealed partial class ScenePage : NavigationTabPage
private void OnRendering(object? sender, object e) private void OnRendering(object? sender, object e)
{ {
_renderer?.Render();
} }
private void SwapChainPanel_Loaded(object sender, RoutedEventArgs e) private void SwapChainPanel_Loaded(object sender, RoutedEventArgs e)
{ {
var guid = typeof(ISwapChainPanelNative2).GUID; //var guid = typeof(ISwapChainPanelNative2).GUID;
((IWinRTObject)SwapChainPanel).NativeObject.TryAs(guid, out var swapChainPanelNativeHandle); //((IWinRTObject)SwapChainPanel).NativeObject.TryAs(guid, out var swapChainPanelNativeHandle);
_swapChainPanelNative = ISwapChainPanelNative.FromSwapChainPanel(SwapChainPanel);
_swapChainPanelNative = new ISwapChainPanelNative2(swapChainPanelNativeHandle); //_swapChainPanelNative = new ISwapChainPanelNative2(swapChainPanelNativeHandle);
_renderer = GraphicsPipeline.GraphicsDevice.CreateRenderView(new(_swapChainPanelNative, (uint)SwapChainPanel.ActualWidth, (uint)SwapChainPanel.ActualHeight)); _renderView = GraphicsPipeline.GraphicsDevice.CreateRenderer(new(_swapChainPanelNative, (uint)SwapChainPanel.ActualWidth, (uint)SwapChainPanel.ActualHeight));
//CompositionTarget.Rendering += OnRendering; //CompositionTarget.Rendering += OnRendering;
} }
@@ -40,15 +39,15 @@ internal sealed partial class ScenePage : NavigationTabPage
private void SwapChainPanel_Unloaded(object sender, RoutedEventArgs e) private void SwapChainPanel_Unloaded(object sender, RoutedEventArgs e)
{ {
//CompositionTarget.Rendering -= OnRendering; //CompositionTarget.Rendering -= OnRendering;
_swapChainPanelNative?.Dispose(); _swapChainPanelNative.Dispose();
_renderer?.Dispose(); _renderView?.Dispose();
} }
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 (e.NewSize.Width > 8.0 && e.NewSize.Height > 8.0)
{ {
_renderer?.RequestResize((uint)e.NewSize.Width, (uint)e.NewSize.Height); _renderView?.RequestResize((uint)e.NewSize.Width, (uint)e.NewSize.Height);
} }
} }
} }

View File

@@ -2,4 +2,5 @@
public interface ICommandBuffer public interface ICommandBuffer
{ {
public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size);
} }

View File

@@ -2,10 +2,17 @@
namespace Ghost.Graphics.Contracts; namespace Ghost.Graphics.Contracts;
public interface IGraphicsDevice : IDisposable internal interface IGraphicsDevice : IDisposable
{ {
public static abstract IGraphicsDevice Create(); public static abstract GraphicsAPI TargetAPI
{
public IRenderView CreateRenderView(in SwapChainPresenter swapChainSurface); get;
public void OnRender(); }
public ReadOnlySpan<IRenderer> Renderers
{
get;
}
public IRenderer CreateRenderer(in SwapChainPresenter swapChainSurface);
} }

View File

@@ -0,0 +1,7 @@
namespace Ghost.Graphics.Contracts;
internal interface IRenderPass : IDisposable
{
void Initialize(ICommandBuffer cmb);
void Execute(ICommandBuffer cmb);
}

View File

@@ -3,8 +3,13 @@
/// <summary> /// <summary>
/// Defines the contract for a render view in the graphics pipeline. /// Defines the contract for a render view in the graphics pipeline.
/// </summary> /// </summary>
internal interface IRenderView : IDisposable internal interface IRenderer : IDisposable
{ {
public ReadOnlySpan<IRenderPass> RenderPasses
{
get;
}
/// <summary> /// <summary>
/// Requests a resize of the render view. /// Requests a resize of the render view.
/// </summary> /// </summary>
@@ -17,26 +22,17 @@ internal interface IRenderView : IDisposable
/// </summary> /// </summary>
public void ExecutePendingResize(); public void ExecutePendingResize();
/// <summary>
/// Begins a render operation.
/// </summary>
/// <returns>An ICommandBuffer instance to manage render commands.</returns>
public ICommandBuffer BeginRender();
/// <summary> /// <summary>
/// Renders the current content to the output target. /// Renders the current content to the output target.
/// </summary> /// </summary>
public void Render(); public void Render();
/// <summary>
/// Ends the current rendering operation and finalizes any pending rendering tasks.
/// </summary>
public void EndRender();
/// <summary> /// <summary>
/// Waits for the next frame to be ready for rendering. /// Waits for the next frame to be ready for rendering.
/// </summary> /// </summary>
public void WaitNextFrame(); public void WaitNextFrame();
/// <summary> /// <summary>
/// Waits for the rendering operations to complete and the GPU to be idle. /// Waits for the render view to become idle, ensuring all previous commands have been executed and resources are ready for the next frame.
/// </summary> /// </summary>
public void WaitIdle(); public void WaitIdle();
} }

View File

@@ -0,0 +1,18 @@
namespace Ghost.Graphics.Contracts;
public interface IResource : IDisposable
{
public ulong GPUAddress
{
get;
}
public string Name
{
get;
set;
}
public void SetData<T>(Span<T> data)
where T : unmanaged;
}

View File

@@ -0,0 +1,11 @@
using Vortice.Direct3D12;
namespace Ghost.Graphics.Contracts;
internal unsafe interface IResourceAllocator : IDisposable
{
public abstract static IResourceAllocator Create();
public IResource CreateUploadBuffer(uint sizeInBytes, ResourceFlags flags = ResourceFlags.None);
public IResource CreateCopyDestinationBuffer(uint sizeInBytes, ResourceFlags flags = ResourceFlags.None);
}

View File

@@ -0,0 +1,61 @@
using System.Runtime.InteropServices;
namespace Ghost.Graphics.Contracts;
[ComImport]
[Guid("63aad0b8-7c24-40ff-85a8-640d944cc325")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ISwapChainPanelNativeRaw
{
// IUnknown: QueryInterface, AddRef, Release
void QueryInterface(in Guid riid, out IntPtr ppvObject);
uint AddRef();
uint Release();
// SetSwapChain is the 4th slot in the vtable (0-based index 3)
int SetSwapChain(IntPtr swapChainPtr);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
public unsafe readonly struct ISwapChainPanelNative
{
private readonly IntPtr _nativePtr;
public readonly IntPtr NativePointer => _nativePtr;
public ISwapChainPanelNative(IntPtr nativePtr)
{
_nativePtr = nativePtr;
}
public static ISwapChainPanelNative FromSwapChainPanel(object panel)
{
// Get the IUnknown/IInspectable pointer
var unknown = Marshal.GetIUnknownForObject(panel);
try
{
// Query for ISwapChainPanelNative
var iid = typeof(ISwapChainPanelNativeRaw).GUID;
var result = Marshal.QueryInterface(unknown, in iid, out var nativePtr);
if (result < 0)
{
Marshal.ThrowExceptionForHR(result);
}
return new ISwapChainPanelNative(nativePtr);
}
finally
{
Marshal.Release(unknown);
}
}
public int SetSwapChain(IntPtr swapChainPtr)
{
var raw = (ISwapChainPanelNativeRaw)Marshal.GetObjectForIUnknown(_nativePtr);
var hr = raw.SetSwapChain(swapChainPtr);
Marshal.ReleaseComObject(raw);
return hr;
}
public void Dispose() => Marshal.Release(_nativePtr);
}

View File

@@ -1,4 +1,6 @@
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Ghost.Graphics.Data;
using System.Runtime.CompilerServices;
using Vortice.Direct3D12; using Vortice.Direct3D12;
namespace Ghost.Graphics.DX12; namespace Ghost.Graphics.DX12;
@@ -11,4 +13,14 @@ internal class DX12CommandBuffer : ICommandBuffer
{ {
_commandList = commandList; _commandList = commandList;
} }
public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size)
{
GraphicsPipeline.CheckAPI(GraphicsAPI.DX12).EnsureSuccess();
var dstDXResource = Unsafe.As<DX12Resource>(dstResource);
var srcDXResource = Unsafe.As<DX12Resource>(srcResource);
_commandList.CopyBufferRegion(dstDXResource.NativeResource, dstOffset, srcDXResource.NativeResource, srcOffset, size);
}
} }

View File

@@ -1,82 +1,93 @@
using Ghost.Graphics.Contracts; using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Vortice.Direct3D; using System.Collections.Immutable;
using Vortice.Direct3D12; using Win32;
using Vortice.DXGI; using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
using static Win32.Apis;
using static Win32.Graphics.Direct3D12.Apis;
using static Win32.Graphics.Dxgi.Apis;
namespace Ghost.Graphics.DX12; namespace Ghost.Graphics.DX12;
internal class DX12GraphicsDevice : IGraphicsDevice internal unsafe class DX12GraphicsDevice : IGraphicsDevice
{ {
private readonly IDXGIFactory7 _dxgiFactory;
private readonly ID3D12Device14 _device;
private readonly ID3D12CommandQueue _commandQueue;
private readonly List<IRenderView> _renderViews = new();
private readonly Lock _lock = new();
#if DEBUG #if DEBUG
private readonly DX12DebugLayer _debugLayer; private readonly DX12DebugLayer _debugLayer;
#endif #endif
private readonly ComPtr<IDXGIFactory7> _dxgiFactory;
private readonly ComPtr<ID3D12Device14> _device;
private readonly ComPtr<ID3D12CommandQueue> _commandQueue;
private ImmutableArray<IRenderer> _renderers;
private bool _disposed; private bool _disposed;
public ID3D12Device14 Device => _device; public static GraphicsAPI TargetAPI => GraphicsAPI.DX12;
public IDXGIFactory7 DXGIFactory => _dxgiFactory; public ReadOnlySpan<IRenderer> Renderers => _renderers.AsSpan();
public ID3D12CommandQueue CommandQueue => _commandQueue;
public static IGraphicsDevice Create() => new DX12GraphicsDevice(); public ConstPtr<ID3D12Device14> NativeDevice => new(_device.Get());
public ConstPtr<IDXGIFactory7> DXGIFactory => new(_dxgiFactory.Get());
public ConstPtr<ID3D12CommandQueue> CommandQueue => new(_commandQueue.Get());
private DX12GraphicsDevice() public DX12GraphicsDevice()
{ {
#if DEBUG #if DEBUG
_debugLayer = new DX12DebugLayer(); _debugLayer = new DX12DebugLayer();
#endif #endif
InitializeDevice(out _dxgiFactory, out _device); InitializeDevice();
InitializeCommandQueue(out _commandQueue); InitializeCommandQueue();
_renderers = ImmutableArray<IRenderer>.Empty;
} }
private void InitializeDevice(out IDXGIFactory7 factory, out ID3D12Device14 device) private void InitializeDevice()
{
fixed (void* factoryPtr = &_dxgiFactory)
{ {
#if DEBUG #if DEBUG
factory = DXGI.CreateDXGIFactory2<IDXGIFactory7>(true); CreateDXGIFactory2(true, __uuidof<IDXGIFactory2>(), &factoryPtr);
//factory = DXGI.CreateDXGIFactory2<IDXGIFactory7>(true);
#else #else
factory = DXGI.CreateDXGIFactory2<IDXGIFactory7>(false); //factory = DXGI.CreateDXGIFactory2<IDXGIFactory7>(false);
CreateDXGIFactory2(false, __uuidof<IDXGIFactory2>(), &factoryPtr);
#endif #endif
}
using ComPtr<IDXGIAdapter1> adapter = default;
ID3D12Device14? d3d12Device = default;
for (uint adapterIndex = 0; for (uint adapterIndex = 0;
factory.EnumAdapters1(adapterIndex, out var adapter).Success; _dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, GpuPreference.HighPerformance, __uuidof<IDXGIAdapter1>(), (void**)adapter.ReleaseAndGetAddressOf()).Success;
adapterIndex++) adapterIndex++)
{ {
var desc = adapter.Description1; AdapterDescription1 desc = default;
adapter.Get()->GetDesc1(&desc);
// Don't select the Basic Render Driver adapter. // Don't select the Basic Render Driver adapter.
if ((desc.Flags & AdapterFlags.Software) != AdapterFlags.None) if ((desc.Flags & AdapterFlags.Software) != AdapterFlags.None)
{ {
adapter.Dispose();
continue; continue;
} }
if (D3D12.D3D12CreateDevice(adapter, FeatureLevel.Level_11_0, out d3d12Device).Success) fixed (void* devicePtr = &_device)
{
if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_11_0, __uuidof<ID3D12Device>(), (void**)devicePtr).Success)
{ {
adapter.Dispose();
break; break;
} }
}
adapter.Dispose();
} }
if (d3d12Device == null) if (_device.Get() == null)
{ {
throw new PlatformNotSupportedException("Cannot create ID3D12Device"); throw new PlatformNotSupportedException("Cannot create ID3D12Device");
} }
device = d3d12Device;
} }
private void InitializeCommandQueue(out ID3D12CommandQueue queue) private void InitializeCommandQueue()
{ {
var queueDesc = new CommandQueueDescription var queueDesc = new CommandQueueDescription
{ {
@@ -85,32 +96,26 @@ internal class DX12GraphicsDevice : IGraphicsDevice
Flags = CommandQueueFlags.None, Flags = CommandQueueFlags.None,
}; };
queue = _device.CreateCommandQueue(queueDesc); fixed (void* queuePtr = &_commandQueue)
{
_device.Get()->CreateCommandQueue(&queueDesc, __uuidof<ID3D12CommandQueue>(), &queuePtr);
}
} }
public IRenderView CreateRenderView(in SwapChainPresenter swapChainSurface) public IRenderer CreateRenderer(in SwapChainPresenter presenter)
{ {
var renderView = new DX12RenderView(this, swapChainSurface); var renderView = new DX12Renderer(this, in presenter);
lock (_lock) ImmutableInterlocked.Update(ref _renderers, old => old.Add(renderView));
{
_renderViews.Add(renderView);
}
return renderView; return renderView;
} }
public void OnRender() public void RemoveRenderer(IRenderer renderer)
{ {
lock (_lock) if (renderer is DX12Renderer dx12RenderView)
{ {
foreach (var renderView in _renderViews) dx12RenderView.Dispose();
{ ImmutableInterlocked.Update(ref _renderers, old => old.Remove(dx12RenderView));
renderView.ExecutePendingResize();
renderView.BeginRender();
renderView.Render();
renderView.EndRender();
}
} }
} }
@@ -121,15 +126,19 @@ internal class DX12GraphicsDevice : IGraphicsDevice
return; return;
} }
foreach (var renderView in _renderViews) foreach (var renderer in _renderers)
{
renderer.Dispose();
}
foreach (var renderView in _renderers)
{ {
renderView.Dispose(); renderView.Dispose();
} }
_renderViews.Clear();
_commandQueue.Release(); _commandQueue.Dispose();
_device.Release(); _device.Dispose();
_dxgiFactory.Release(); _dxgiFactory.Dispose();
#if DEBUG #if DEBUG
_debugLayer.Dispose(); _debugLayer.Dispose();

View File

@@ -1,279 +0,0 @@
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Data;
using Ghost.Graphics.DX12.Utilities;
using Vortice.Direct3D12;
using Vortice.DXGI;
namespace Ghost.Graphics.DX12;
internal class DX12RenderView : IRenderView
{
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 IDXGISwapChain4 _swapChain;
private readonly ID3D12Resource[] _renderTargets;
private readonly uint[] _renderTargetDescriptorIndexes;
private uint _backBufferIndex;
private readonly ID3D12CommandAllocator[] _commandAllocators;
private readonly ID3D12GraphicsCommandList10 _commandList;
private readonly ID3D12Fence1 _fence;
private readonly AutoResetEvent _fenceEvent;
private readonly ulong[] _fenceValues;
private readonly D3D12DescriptorAllocator _rtvHeap;
private readonly ICommandBuffer _commandBuffer;
private readonly Lock _lock = new();
private uint _pendingWidth;
private uint _pendingHeight;
private bool _resizeRequested;
private bool _disposed;
public DX12RenderView(DX12GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface)
{
_graphicsDevice = graphicsDevice;
_swapChainPresenter = swapChainSurface;
_rtvHeap = new(_graphicsDevice.Device, DescriptorHeapType.RenderTargetView, _RENDER_TARGET_VIEW_HEAP_SIZE);
_fenceEvent = new AutoResetEvent(false);
_renderTargets = new ID3D12Resource[GraphicsPipeline.FRAME_COUNT];
_fenceValues = new ulong[GraphicsPipeline.FRAME_COUNT];
_renderTargetDescriptorIndexes = new uint[GraphicsPipeline.FRAME_COUNT];
InitializeSwapChain(out _swapChain);
InitializeCommandObjects(out _commandAllocators, out _commandList, out _fence);
CreateRenderTargets();
_commandBuffer = new DX12CommandBuffer(_commandList);
}
private void InitializeSwapChain(out IDXGISwapChain4 swapChain)
{
var swapChainDesc = new SwapChainDescription1
{
Width = _swapChainPresenter.Width,
Height = _swapChainPresenter.Height,
Format = Format.B8G8R8A8_UNorm,
Stereo = false,
SampleDescription = new SampleDescription(1, 0),
BufferUsage = Usage.Backbuffer | Usage.RenderTargetOutput,
BufferCount = GraphicsPipeline.FRAME_COUNT,
Scaling = Scaling.Stretch,
SwapEffect = SwapEffect.FlipDiscard,
AlphaMode = AlphaMode.Ignore,
Flags = SwapChainFlags.AllowTearing
};
switch (_swapChainPresenter.Type)
{
case SwapChainPresenter.TargetType.Composition:
var swapChain1 = _graphicsDevice.DXGIFactory.CreateSwapChainForComposition(_graphicsDevice.CommandQueue, swapChainDesc);
swapChain = swapChain1.QueryInterface<IDXGISwapChain4>();
swapChain1.Dispose();
_backBufferIndex = swapChain.CurrentBackBufferIndex;
_swapChainPresenter.SwapChainPanelNative!.SetSwapChain(swapChain);
break;
case SwapChainPresenter.TargetType.Hwnd:
var swapChainFullscreenDesc = new SwapChainFullscreenDescription
{
Windowed = true,
};
var swapChain2 = _graphicsDevice.DXGIFactory.CreateSwapChainForHwnd(
_graphicsDevice.CommandQueue,
_swapChainPresenter.Hwnd,
swapChainDesc,
swapChainFullscreenDesc,
null);
swapChain = swapChain2.QueryInterface<IDXGISwapChain4>();
swapChain2.Dispose();
break;
default:
throw new ArgumentException("Unsupported swap chain surface type.");
}
}
private void InitializeCommandObjects(out ID3D12CommandAllocator[] commandAllocator, out ID3D12GraphicsCommandList10 commandList, out ID3D12Fence1 fence)
{
commandAllocator = new ID3D12CommandAllocator[GraphicsPipeline.FRAME_COUNT];
for (var i = 0; i < GraphicsPipeline.FRAME_COUNT; i++)
{
commandAllocator[i] = _graphicsDevice.Device.CreateCommandAllocator(CommandListType.Direct);
}
commandList = _graphicsDevice.Device.CreateCommandList<ID3D12GraphicsCommandList10>(CommandListType.Direct, commandAllocator[0], null!);
commandList.Close();
fence = _graphicsDevice.Device.CreateFence<ID3D12Fence1>(_fenceValues[_backBufferIndex], FenceFlags.None);
_fenceValues[_backBufferIndex]++;
}
private void CreateRenderTargets()
{
for (var i = 0u; i < GraphicsPipeline.FRAME_COUNT; i++)
{
_renderTargets[i] = _swapChain.GetBuffer<ID3D12Resource>(i);
_renderTargets[i].Name = $"RenderTarget_{i}";
_renderTargetDescriptorIndexes[i] = _rtvHeap.AllocateDescriptor();
var rtvHandle = _rtvHeap.GetCpuHandle(_renderTargetDescriptorIndexes[i]);
_graphicsDevice.Device.CreateRenderTargetView(_renderTargets[i], null, rtvHandle);
}
}
public void RequestResize(uint width, uint height)
{
lock (_lock)
{
if (_pendingWidth == width && _pendingHeight == height)
{
return;
}
_resizeRequested = true;
_pendingWidth = width;
_pendingHeight = height;
}
}
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++)
{
if (_renderTargets[i] is not null)
{
_renderTargets[i].Dispose();
_rtvHeap.ReleaseDescriptor(_renderTargetDescriptorIndexes[i]);
}
_fenceValues[i] = _fenceValues[_backBufferIndex];
}
_swapChain.ResizeBuffers(GraphicsPipeline.FRAME_COUNT, newWidth, newHeight, Format.B8G8R8A8_UNorm, SwapChainFlags.AllowTearing).CheckError();
CreateRenderTargets();
_backBufferIndex = _swapChain.CurrentBackBufferIndex;
}
public ICommandBuffer BeginRender()
{
_backBufferIndex = _swapChain.CurrentBackBufferIndex;
var commandAllocator = _commandAllocators[_backBufferIndex];
commandAllocator.Reset();
_commandList.Reset(commandAllocator, null);
_commandList.ResourceBarrierTransition(_renderTargets[_backBufferIndex], ResourceStates.Present, ResourceStates.RenderTarget);
return _commandBuffer;
}
public void Render()
{
}
public void EndRender()
{
_commandList.ResourceBarrierTransition(_renderTargets[_backBufferIndex], ResourceStates.RenderTarget, ResourceStates.Present);
_commandList.Close();
_graphicsDevice.CommandQueue.ExecuteCommandLists(new[] { _commandList });
_swapChain.Present(1, PresentFlags.None).CheckError();
WaitNextFrame();
}
public void WaitNextFrame()
{
var fenceValue = _fenceValues[_backBufferIndex];
if (_graphicsDevice.CommandQueue.Signal(_fence, fenceValue).Failure)
{
return;
}
if (_fence.CompletedValue < _fenceValues[_backBufferIndex]
&& _fence.SetEventOnCompletion(_fenceValues[_backBufferIndex], _fenceEvent.SafeWaitHandle.DangerousGetHandle()).Success)
{
_fenceEvent.WaitOne();
}
_fenceValues[_backBufferIndex]++;
}
public void WaitIdle()
{
var fenceValue = _fenceValues[_backBufferIndex];
if (_graphicsDevice.CommandQueue.Signal(_fence, fenceValue).Success
&& _fence.SetEventOnCompletion(fenceValue, _fenceEvent.SafeWaitHandle.DangerousGetHandle()).Success)
{
_fenceEvent.WaitOne();
_fenceValues[_backBufferIndex]++;
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
WaitIdle();
_swapChainPresenter.SwapChainPanelNative?.SetSwapChain(null);
foreach (var commandAllocator in _commandAllocators)
{
commandAllocator.Dispose();
}
_commandAllocators.AsSpan().Clear();
foreach (var renderTarget in _renderTargets)
{
renderTarget.Dispose();
}
_renderTargets.AsSpan().Clear();
_swapChain.Dispose();
_commandList.Dispose();
_fence.Dispose();
_fenceEvent.Dispose();
_rtvHeap.Dispose();
_backBufferIndex = 0;
_fenceValues.AsSpan().Clear();
_disposed = true;
}
}

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;
}
}

View File

@@ -0,0 +1,37 @@
using Ghost.Graphics.Contracts;
using System.Runtime.CompilerServices;
using Vortice.Direct3D12;
namespace Ghost.Graphics.DX12;
public unsafe class DX12Resource : IResource
{
private readonly ID3D12Resource _nativeResource;
internal ID3D12Resource NativeResource => _nativeResource;
public ulong GPUAddress => _nativeResource.GPUVirtualAddress;
public string Name
{
get => _nativeResource.Name;
set => _nativeResource.Name = value;
}
public DX12Resource(ID3D12Resource nativeResource)
{
_nativeResource = nativeResource;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetData<T>(Span<T> data)
where T : unmanaged
{
_nativeResource.WriteToSubresource(0, data, 0, 0);
}
public void Dispose()
{
_nativeResource.Dispose();
}
}

View File

@@ -1,16 +1,20 @@
using Ghost.Graphics.Utilities; using Ghost.Graphics.Contracts;
using Ghost.Graphics.DX12.Utilities;
using Ghost.Graphics.Utilities;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Vortice.Direct3D12; using Vortice.Direct3D12;
using Vortice.DXGI; using Vortice.DXGI;
namespace Ghost.Graphics.DX12.Utilities; namespace Ghost.Graphics.DX12;
internal unsafe class D3D12ResourceUtils internal unsafe class DX12ResourceAllocator : IResourceAllocator
{ {
private const ResourceStates _INITIALCOPYTARGETSTATE = ResourceStates.Common; private const ResourceStates _INITIALCOPYTARGETSTATE = ResourceStates.Common;
private const ResourceStates _INITIALREADTARGETSTATE = ResourceStates.Common; private const ResourceStates _INITIALREADTARGETSTATE = ResourceStates.Common;
private const ResourceStates _INITIALUAVTARGETSTATE = ResourceStates.Common; private const ResourceStates _INITIALUAVTARGETSTATE = ResourceStates.Common;
private const uint _MAX_BYTES = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u;
public static ID3D12Resource CreateStaticBuffer<T>( public static ID3D12Resource CreateStaticBuffer<T>(
ID3D12Device device, ID3D12Device device,
D3D12ResourceUploadBatch resourceUpload, D3D12ResourceUploadBatch resourceUpload,
@@ -31,10 +35,7 @@ internal unsafe class D3D12ResourceUtils
where T : unmanaged where T : unmanaged
{ {
var sizeInBytes = (uint)(sizeof(T) * data.Length); var sizeInBytes = (uint)(sizeof(T) * data.Length);
if (sizeInBytes > _MAX_BYTES)
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u;
if (sizeInBytes > c_maxBytes)
{ {
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})"); throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
} }
@@ -92,9 +93,7 @@ internal unsafe class D3D12ResourceUtils
void* data = default, void* data = default,
ResourceFlags flags = ResourceFlags.None) ResourceFlags flags = ResourceFlags.None)
{ {
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u; if (sizeInBytes > _MAX_BYTES)
if (sizeInBytes > c_maxBytes)
{ {
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})"); throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
} }
@@ -122,8 +121,7 @@ internal unsafe class D3D12ResourceUtils
uint sizeInBytes, uint sizeInBytes,
ResourceFlags flags = ResourceFlags.None) ResourceFlags flags = ResourceFlags.None)
{ {
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u; if (sizeInBytes > _MAX_BYTES)
if (sizeInBytes > c_maxBytes)
{ {
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})"); throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
} }
@@ -136,33 +134,11 @@ internal unsafe class D3D12ResourceUtils
return buffer; return buffer;
} }
public static ID3D12Resource CreateCPUDestinationBuffer(
ID3D12Device device,
uint sizeInBytes,
ResourceFlags flags = ResourceFlags.None)
{
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u;
if (sizeInBytes > c_maxBytes)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
var buffer = device.CreateCommittedResource(
HeapType.Default,
HeapFlags.None,
ResourceDescription.Buffer(sizeInBytes, flags),
ResourceStates.CopyDest
);
return buffer;
}
public static ID3D12Resource CreateUAVBuffer(ID3D12Device device, uint bufferSize, public static ID3D12Resource CreateUAVBuffer(ID3D12Device device, uint bufferSize,
ResourceStates initialState = ResourceStates.Common, ResourceStates initialState = ResourceStates.Common,
ResourceFlags flags = ResourceFlags.None) ResourceFlags flags = ResourceFlags.None)
{ {
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u; if (bufferSize > _MAX_BYTES)
if (bufferSize > c_maxBytes)
{ {
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {bufferSize})"); throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {bufferSize})");
} }
@@ -187,7 +163,7 @@ internal unsafe class D3D12ResourceUtils
ResourceFlags flags = ResourceFlags.None) ResourceFlags flags = ResourceFlags.None)
where T : unmanaged where T : unmanaged
{ {
if ((width > D3D12.RequestTexture2DUOrVDimension) || (height > D3D12.RequestTexture2DUOrVDimension)) if (width > D3D12.RequestTexture2DUOrVDimension || height > D3D12.RequestTexture2DUOrVDimension)
{ {
throw new InvalidOperationException($"ERROR: Resource dimensions too large for DirectX 12 (2D: size {width} by {height})"); throw new InvalidOperationException($"ERROR: Resource dimensions too large for DirectX 12 (2D: size {width} by {height})");
} }
@@ -231,4 +207,46 @@ internal unsafe class D3D12ResourceUtils
return texture; return texture;
} }
} }
public static IResourceAllocator Create() => new DX12ResourceAllocator();
public IResource CreateUploadBuffer(uint sizeInBytes, ResourceFlags flags = ResourceFlags.None)
{
if (sizeInBytes > _MAX_BYTES)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
var device = GraphicsPipeline.GetRenderer<DX12GraphicsDevice>();
var buffer = device.NativeDevice.CreateCommittedResource(
HeapType.Upload,
HeapFlags.None,
ResourceDescription.Buffer(sizeInBytes, flags),
ResourceStates.GenericRead
);
return new DX12Resource(buffer);
}
public IResource CreateCopyDestinationBuffer(uint sizeInBytes, ResourceFlags flags = ResourceFlags.None)
{
if (sizeInBytes > _MAX_BYTES)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
var device = GraphicsPipeline.GetRenderer<DX12GraphicsDevice>();
var buffer = device.NativeDevice.CreateCommittedResource(
HeapType.Default,
HeapFlags.None,
ResourceDescription.Buffer(sizeInBytes, flags),
ResourceStates.CopyDest
);
return new DX12Resource(buffer);
}
public void Dispose()
{
}
} }

View File

@@ -0,0 +1,5 @@
namespace Ghost.Graphics.DX12.Utilities;
internal static class D3D12Utility
{\
}

View File

@@ -2,5 +2,6 @@
public enum GraphicsAPI public enum GraphicsAPI
{ {
None,
DX12 DX12
} }

View File

@@ -1,4 +1,4 @@
using Ghost.Graphics.DX12.Utilities; using Ghost.Graphics.Contracts;
using Misaki.HighPerformance.Unsafe.Collections; using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.Unsafe.Helpers; using Misaki.HighPerformance.Unsafe.Helpers;
using System.Numerics; using System.Numerics;
@@ -16,8 +16,8 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
private BoundingBox _bounds; private BoundingBox _bounds;
private ID3D12Resource? _vertexBuffer; private IResource? _vertexBuffer;
private ID3D12Resource? _indexBuffer; private IResource? _indexBuffer;
private VertexBufferView _vertexBufferView; private VertexBufferView _vertexBufferView;
private IndexBufferView _indexBufferView; private IndexBufferView _indexBufferView;
@@ -35,7 +35,7 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
/// <summary> /// <summary>
/// Adds a vertex to the mesh with the specified attributes. /// Adds a vertex to the mesh with the specified attributes.
/// </summary> /// </summary>
/// <param name="vertex">The data to add</param> /// <param name="vertex">The vertex data to add</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddVertex(Vertex vertex) public void AddVertex(Vertex vertex)
{ {
@@ -198,11 +198,11 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
} }
/// <summary> /// <summary>
/// Uploads the mesh data to GPU resources immediately. /// Uploads the mesh data to GPU resources.
/// </summary> /// </summary>
/// <param name="device">The Direct3D 12 device.</param> /// <param name="device">The Direct3D 12 device.</param>
/// <param name="commandList">The Direct3D 12 command list to record the upload commands.</param> /// <param name="commandList">The Direct3D 12 command list to record the upload commands.</param>
public unsafe void UploadMeshData(ID3D12Device device, ID3D12GraphicsCommandList commandList) public unsafe void UploadMeshData(ICommandBuffer cmb)
{ {
if (VertexCount == 0 || IndexCount == 0) if (VertexCount == 0 || IndexCount == 0)
{ {
@@ -214,49 +214,33 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
var vertexBufferSize = (uint)(VertexCount * sizeof(Vertex)); var vertexBufferSize = (uint)(VertexCount * sizeof(Vertex));
var indexBufferSize = (uint)(IndexCount * sizeof(int)); var indexBufferSize = (uint)(IndexCount * sizeof(int));
_vertexBuffer = D3D12ResourceUtils.CreateCPUDestinationBuffer(device, vertexBufferSize); _vertexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(vertexBufferSize);
_indexBuffer = D3D12ResourceUtils.CreateCPUDestinationBuffer(device, indexBufferSize); _indexBuffer = GraphicsPipeline.ResourceAllocator.CreateCopyDestinationBuffer(indexBufferSize);
using var vertexUploadBuffer = D3D12ResourceUtils.CreateUploadBuffer(device, vertexBufferSize); using var vertexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(vertexBufferSize);
using var indexUploadBuffer = D3D12ResourceUtils.CreateUploadBuffer(device, indexBufferSize); using var indexUploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(indexBufferSize);
void* vertexData; vertexUploadBuffer.SetData(_vertices.AsSpan());
vertexUploadBuffer.Map(0, null, &vertexData); indexUploadBuffer.SetData(_indices.AsSpan());
Unsafe.CopyBlock(vertexData, _vertices.GetUnsafePtr(), vertexBufferSize);
vertexUploadBuffer.Unmap(0);
void* indexData; cmb.CopyResource(_vertexBuffer, 0, vertexUploadBuffer, 0, vertexBufferSize);
indexUploadBuffer.Map(0, null, &indexData); cmb.CopyResource(_indexBuffer, 0, indexUploadBuffer, 0, indexBufferSize);
Unsafe.CopyBlock(indexData, _indices.GetUnsafePtr(), indexBufferSize);
indexUploadBuffer.Unmap(0);
commandList.CopyBufferRegion(_vertexBuffer, 0, vertexUploadBuffer, 0, vertexBufferSize);
commandList.CopyBufferRegion(_indexBuffer, 0, indexUploadBuffer, 0, indexBufferSize);
_vertexBufferView = new VertexBufferView _vertexBufferView = new VertexBufferView
{ {
BufferLocation = _vertexBuffer.GPUVirtualAddress, BufferLocation = _vertexBuffer.GPUAddress,
SizeInBytes = vertexBufferSize, SizeInBytes = vertexBufferSize,
StrideInBytes = (uint)sizeof(Vertex) StrideInBytes = (uint)sizeof(Vertex)
}; };
_indexBufferView = new IndexBufferView _indexBufferView = new IndexBufferView
{ {
BufferLocation = _indexBuffer.GPUVirtualAddress, BufferLocation = _indexBuffer.GPUAddress,
SizeInBytes = indexBufferSize, SizeInBytes = indexBufferSize,
Format = Format.R32_UInt Format = Format.R32_SInt
}; };
} }
private void DisposeGpuResources()
{
_vertexBuffer?.Release();
_vertexBuffer = null;
_indexBuffer?.Release();
_indexBuffer = null;
}
/// <summary> /// <summary>
/// Clears all vertex and index data and releases associated GPU resources. /// Clears all vertex and index data and releases associated GPU resources.
/// </summar> /// </summar>
@@ -267,6 +251,15 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
DisposeGpuResources(); DisposeGpuResources();
} }
private void DisposeGpuResources()
{
_vertexBuffer?.Dispose();
_vertexBuffer = null;
_indexBuffer?.Dispose();
_indexBuffer = null;
}
public void Dispose() public void Dispose()
{ {
_vertices.Dispose(); _vertices.Dispose();

View File

@@ -1,25 +0,0 @@
namespace Ghost.Graphics.Data;
internal abstract unsafe class ResourceView
{
public GraphicsResource Resource
{
get;
}
internal void* CpuDescriptorHandle
{
get;
}
protected ResourceView(GraphicsResource resource, void* descriptor)
{
Resource = resource;
CpuDescriptorHandle = descriptor;
}
protected ResourceView(GraphicsResource resource, IntPtr descriptor) :
this(resource, (void*)descriptor)
{
}
}

View File

@@ -1,4 +1,6 @@
namespace Ghost.Graphics.Data; using Ghost.Graphics.Contracts;
namespace Ghost.Graphics.Data;
internal readonly struct SwapChainPresenter internal readonly struct SwapChainPresenter
{ {
@@ -13,7 +15,7 @@ internal readonly struct SwapChainPresenter
get; get;
} }
public readonly Vortice.WinUI.ISwapChainPanelNative? SwapChainPanelNative public readonly ISwapChainPanelNative SwapChainPanelNative
{ {
get; get;
} }
@@ -33,7 +35,7 @@ internal readonly struct SwapChainPresenter
get; get;
} }
public SwapChainPresenter(Vortice.WinUI.ISwapChainPanelNative swapChainPanelNative, uint width, uint height) public SwapChainPresenter(ISwapChainPanelNative swapChainPanelNative, uint width, uint height)
{ {
Type = TargetType.Composition; Type = TargetType.Composition;
SwapChainPanelNative = swapChainPanelNative; SwapChainPanelNative = swapChainPanelNative;
@@ -45,7 +47,6 @@ internal readonly struct SwapChainPresenter
public SwapChainPresenter(IntPtr hwnd, uint width, uint height) public SwapChainPresenter(IntPtr hwnd, uint width, uint height)
{ {
Type = TargetType.Hwnd; Type = TargetType.Hwnd;
SwapChainPanelNative = null;
Hwnd = hwnd; Hwnd = hwnd;
Width = width; Width = width;
Height = height; Height = height;

View File

@@ -18,6 +18,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Vortice.Direct3D12" Version="3.6.2" /> <PackageReference Include="Vortice.Direct3D12" Version="3.6.2" />
<PackageReference Include="Vortice.Win32.Graphics.D3D12MemoryAllocator" Version="2.2.7" />
<PackageReference Include="Vortice.WinUI" Version="3.6.2" /> <PackageReference Include="Vortice.WinUI" Version="3.6.2" />
</ItemGroup> </ItemGroup>

View File

@@ -1,18 +1,23 @@
using Ghost.Graphics.Contracts; using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Ghost.Graphics.DX12;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics; namespace Ghost.Graphics;
public static class GraphicsPipeline public static class GraphicsPipeline
{ {
internal const int FRAME_COUNT = 2; internal const int _FRAME_COUNT = 2;
private static IGraphicsDevice? _graphicsDevice; private static IGraphicsDevice? _graphicsDevice;
private static IResourceAllocator? _resourceAllocator;
private static Thread? _renderThread; private static Thread? _renderThread;
private static bool _isRunning; private static bool _isRunning;
public static IGraphicsDevice GraphicsDevice internal static IGraphicsDevice GraphicsDevice
{ {
get get
{ {
@@ -20,26 +25,61 @@ public static class GraphicsPipeline
{ {
throw new InvalidOperationException("Graphics pipeline is not initialized."); throw new InvalidOperationException("Graphics pipeline is not initialized.");
} }
return _graphicsDevice; return _graphicsDevice;
} }
} }
internal static IResourceAllocator ResourceAllocator
{
get
{
if (_resourceAllocator == null)
{
throw new InvalidOperationException("Resource allocator is not initialized.");
}
return _resourceAllocator;
}
}
public static GraphicsAPI CurrentAPI
{
get;
private set;
}
internal static void Initialize(GraphicsAPI api) internal static void Initialize(GraphicsAPI api)
{ {
_graphicsDevice = api switch switch (api)
{ {
GraphicsAPI.DX12 => DX12.DX12GraphicsDevice.Create(), case GraphicsAPI.DX12:
_ => throw new NotSupportedException($"Graphics API {api} is not supported.") _graphicsDevice = new DX12GraphicsDevice();
}; _resourceAllocator = new DX12ResourceAllocator();
break;
default:
throw new NotSupportedException($"Graphics API {api} is not supported.");
}
_renderThread = new Thread(RenderLoop); _renderThread = new Thread(RenderLoop);
CurrentAPI = api;
} }
private static void RenderLoop() private static void RenderLoop()
{ {
while (_isRunning) while (_isRunning)
{ {
GraphicsDevice.OnRender(); if (_graphicsDevice == null)
{
throw new ArgumentException("Renderer has been disposed or is not initialized.");
}
foreach (var renderer in _graphicsDevice.Renderers)
{
renderer.ExecutePendingResize();
renderer.Render();
}
} }
} }
@@ -70,8 +110,32 @@ public static class GraphicsPipeline
Stop(); Stop();
_graphicsDevice?.Dispose(); _graphicsDevice?.Dispose();
_resourceAllocator?.Dispose();
_graphicsDevice = null; _graphicsDevice = null;
_renderThread = null; _renderThread = null;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static T GetRenderer<T>()
where T : class, IGraphicsDevice
{
if (T.TargetAPI != CurrentAPI)
{
throw new InvalidOperationException($"No graphics device of type {typeof(T)} available for the current API.");
}
return Unsafe.As<T>(GraphicsDevice);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Result CheckAPI(GraphicsAPI expectedAPI)
{
if (CurrentAPI != expectedAPI)
{
return Result.Failure($"Expected API {expectedAPI}, but got {CurrentAPI}.");
}
return Result.Success();
}
} }