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:
17
Ghost.Core/ConstPtr.cs
Normal file
17
Ghost.Core/ConstPtr.cs
Normal 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);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
|
|
||||||
public interface ICommandBuffer
|
public interface ICommandBuffer
|
||||||
{
|
{
|
||||||
|
public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
7
Ghost.Graphics/Contracts/IRenderPass.cs
Normal file
7
Ghost.Graphics/Contracts/IRenderPass.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Ghost.Graphics.Contracts;
|
||||||
|
|
||||||
|
internal interface IRenderPass : IDisposable
|
||||||
|
{
|
||||||
|
void Initialize(ICommandBuffer cmb);
|
||||||
|
void Execute(ICommandBuffer cmb);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
18
Ghost.Graphics/Contracts/IResource.cs
Normal file
18
Ghost.Graphics/Contracts/IResource.cs
Normal 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;
|
||||||
|
}
|
||||||
11
Ghost.Graphics/Contracts/IResourceAllocator.cs
Normal file
11
Ghost.Graphics/Contracts/IResourceAllocator.cs
Normal 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);
|
||||||
|
}
|
||||||
61
Ghost.Graphics/Contracts/ISwapChainPanelNative.cs
Normal file
61
Ghost.Graphics/Contracts/ISwapChainPanelNative.cs
Normal 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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
324
Ghost.Graphics/DX12/DX12Renderer.cs
Normal file
324
Ghost.Graphics/DX12/DX12Renderer.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Ghost.Graphics/DX12/DX12Resource.cs
Normal file
37
Ghost.Graphics/DX12/DX12Resource.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
5
Ghost.Graphics/DX12/Utilities/D3D12Utility.cs
Normal file
5
Ghost.Graphics/DX12/Utilities/D3D12Utility.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace Ghost.Graphics.DX12.Utilities;
|
||||||
|
|
||||||
|
internal static class D3D12Utility
|
||||||
|
{\
|
||||||
|
}
|
||||||
@@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
public enum GraphicsAPI
|
public enum GraphicsAPI
|
||||||
{
|
{
|
||||||
|
None,
|
||||||
DX12
|
DX12
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user