feat(d3d12): add indirect command execution support
Added ICommandSignature and D3D12CommandSignature for indirect command execution in the D3D12 backend, with supporting types. Updated ICommandBuffer and IGraphicsEngine interfaces to support indirect execution and command signature creation. Refactored command buffer pooling in D3D12GraphicsEngine for more flexible reuse. Changed BeginFrame and EndFrame to void and clarified parameter names. Updated resource and frame data structures to use direct buffer indices. Added RenderingUtility for buffer and texture uploads. Removed IRenderOutput interface. Updated RenderSystem render loop and HLSL/C# code to match new buffer usage patterns. BREAKING CHANGE: ICommandBuffer, IGraphicsEngine, and related APIs have changed signatures and behaviors. Indirect command execution is now supported and required for some advanced features.
This commit is contained in:
@@ -834,12 +834,6 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
public void DispatchRay()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
// AssertNotDisposed();
|
||||
// ThrowIfNotRecording();
|
||||
// IncrementCommandCount();
|
||||
|
||||
// _device.Get()->DispatchRays();
|
||||
}
|
||||
|
||||
public void DispatchGraph()
|
||||
@@ -847,7 +841,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ExecuteIndirect(Handle<GPUBuffer> argumentBuffer, ulong argumentOffset, Handle<GPUBuffer> countBuffer, ulong countBufferOffset)
|
||||
public void ExecuteIndirect(ICommandSignature commandSignature, Handle<GPUBuffer> argumentBuffer, ulong argumentOffset, Handle<GPUBuffer> countBuffer, ulong countBufferOffset)
|
||||
{
|
||||
AssertNotDisposed();
|
||||
ThrowIfNotRecording();
|
||||
@@ -860,11 +854,12 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
|
||||
IncrementCommandCount();
|
||||
|
||||
Debug.Assert(commandSignature is D3D12CommandSignature);
|
||||
|
||||
var resource = _resourceDatabase.GetResource(argumentBuffer.AsResource());
|
||||
var countResource = _resourceDatabase.GetResource(countBuffer.AsResource());
|
||||
|
||||
// TODO
|
||||
pNativeObject->ExecuteIndirect(null, 0,
|
||||
pNativeObject->ExecuteIndirect(((D3D12CommandSignature)commandSignature).NativeObject, 0,
|
||||
resource, argumentOffset, countResource, countBufferOffset);
|
||||
}
|
||||
|
||||
|
||||
93
src/Runtime/Ghost.Graphics.D3D12/D3D12CommandSignature.cs
Normal file
93
src/Runtime/Ghost.Graphics.D3D12/D3D12CommandSignature.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12CommandSignature : D3D12Object<ID3D12CommandSignature>, ICommandSignature
|
||||
{
|
||||
private static ID3D12CommandSignature* CreateCommandSignature(D3D12RenderDevice device, D3D12PipelineLibrary pipelineLibrary, ref readonly CommandSignatureDesc desc)
|
||||
{
|
||||
var pDevice = device.NativeObject.Get();
|
||||
var pRootSignature = pipelineLibrary.DefaultRootSignature;
|
||||
|
||||
var pArgumentDescs = stackalloc D3D12_INDIRECT_ARGUMENT_DESC[desc.Arguments.Length];
|
||||
|
||||
for (var i = 0; i < desc.Arguments.Length; i++ )
|
||||
{
|
||||
var argument = desc.Arguments[i];
|
||||
var pArgumentDesc = &pArgumentDescs[i];
|
||||
|
||||
switch (argument.Type)
|
||||
{
|
||||
case IndirectArgumentType.Draw:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW;
|
||||
break;
|
||||
case IndirectArgumentType.DrawIndexed:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED;
|
||||
break;
|
||||
case IndirectArgumentType.Dispatch:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH;
|
||||
break;
|
||||
case IndirectArgumentType.VertexBufferView:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_VERTEX_BUFFER_VIEW;
|
||||
pArgumentDesc->VertexBuffer.Slot = argument.VertexBuffer.Slot;
|
||||
break;
|
||||
case IndirectArgumentType.IndexBufferView:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_INDEX_BUFFER_VIEW;
|
||||
break;
|
||||
case IndirectArgumentType.Constant:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT;
|
||||
pArgumentDesc->Constant.RootParameterIndex = argument.Constant.RootParameterIndex;
|
||||
pArgumentDesc->Constant.DestOffsetIn32BitValues = argument.Constant.DestOffsetIn32BitValues;
|
||||
pArgumentDesc->Constant.Num32BitValuesToSet = argument.Constant.Num32BitValuesToSet;
|
||||
break;
|
||||
case IndirectArgumentType.ConstantBufferView:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT_BUFFER_VIEW;
|
||||
pArgumentDesc->ConstantBufferView.RootParameterIndex = argument.ConstantBufferView.RootParameterIndex;
|
||||
break;
|
||||
case IndirectArgumentType.ShaderResourceView:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_SHADER_RESOURCE_VIEW;
|
||||
pArgumentDesc->ShaderResourceView.RootParameterIndex = argument.ShaderResourceView.RootParameterIndex;
|
||||
break;
|
||||
case IndirectArgumentType.UnorderedAccessView:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_UNORDERED_ACCESS_VIEW;
|
||||
pArgumentDesc->UnorderedAccessView.RootParameterIndex = argument.UnorderedAccessView.RootParameterIndex;
|
||||
break;
|
||||
case IndirectArgumentType.DispatchRays:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH_RAYS;
|
||||
break;
|
||||
case IndirectArgumentType.DispatchMesh:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH_MESH;
|
||||
break;
|
||||
case IndirectArgumentType.IncrementingConstant:
|
||||
pArgumentDesc->Type = D3D12_INDIRECT_ARGUMENT_TYPE_INCREMENTING_CONSTANT;
|
||||
pArgumentDesc->IncrementingConstant.RootParameterIndex = argument.IncrementingConstant.RootParameterIndex;
|
||||
pArgumentDesc->IncrementingConstant.DestOffsetIn32BitValues = argument.IncrementingConstant.DestOffsetIn32BitValues;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var d3d12Desc = new D3D12_COMMAND_SIGNATURE_DESC
|
||||
{
|
||||
ByteStride = desc.Stride,
|
||||
NumArgumentDescs = (uint)desc.Arguments.Length,
|
||||
pArgumentDescs = pArgumentDescs,
|
||||
NodeMask = 0
|
||||
};
|
||||
|
||||
ID3D12CommandSignature* pCommandSignature = default;
|
||||
ThrowIfFailed(pDevice->CreateCommandSignature(&d3d12Desc, pRootSignature, __uuidof(pCommandSignature), (void**)pCommandSignature));
|
||||
|
||||
return pCommandSignature;
|
||||
}
|
||||
|
||||
public D3D12CommandSignature(D3D12RenderDevice device, D3D12PipelineLibrary pipelineLibrary, ref readonly CommandSignatureDesc desc, Key128<GraphicsPipeline> pipelineKey)
|
||||
: base(CreateCommandSignature(device, pipelineLibrary, in desc))
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -72,7 +73,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
private readonly D3D12PipelineLibrary _pipelineLibrary;
|
||||
private readonly D3D12ResourceAllocator _resourceAllocator;
|
||||
|
||||
private readonly Queue<ICommandBuffer> _commandBufferPool;
|
||||
private readonly List<ICommandBuffer> _commandBufferPool;
|
||||
private readonly Queue<CommandBufferReturnEntry> _commandBufferReturnQueue;
|
||||
|
||||
private ulong _cpuFrame;
|
||||
@@ -99,7 +100,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
_pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase);
|
||||
_resourceAllocator = new D3D12ResourceAllocator(_device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary);
|
||||
|
||||
_commandBufferPool = new Queue<ICommandBuffer>(4);
|
||||
_commandBufferPool = new List<ICommandBuffer>(4);
|
||||
_commandBufferReturnQueue = new Queue<CommandBufferReturnEntry>(4);
|
||||
}
|
||||
|
||||
@@ -108,21 +109,15 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
Dispose();
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
}
|
||||
|
||||
public ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
return new D3D12CommandAllocator(_device, type);
|
||||
}
|
||||
|
||||
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Debug.Assert(!_disposed);
|
||||
|
||||
return new D3D12CommandBuffer(
|
||||
_device,
|
||||
@@ -135,49 +130,58 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
|
||||
public ICommandBuffer GetPooledCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
|
||||
{
|
||||
if (_commandBufferPool.TryDequeue(out var commandBuffer))
|
||||
Debug.Assert(!_disposed);
|
||||
|
||||
for (int i = 0; i < _commandBufferPool.Count; i++)
|
||||
{
|
||||
return commandBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CreateCommandBuffer(type);
|
||||
if (_commandBufferPool[i].Type == type)
|
||||
{
|
||||
var commandBuffer = _commandBufferPool[i];
|
||||
_commandBufferPool.RemoveAndSwapBack(i);
|
||||
return commandBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
return CreateCommandBuffer(type);
|
||||
}
|
||||
|
||||
public void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
_commandBufferReturnQueue.Enqueue(new CommandBufferReturnEntry(commandBuffer, _cpuFrame));
|
||||
}
|
||||
|
||||
public ISwapChain CreateSwapChain(SwapChainDesc desc)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Debug.Assert(!_disposed);
|
||||
return new DXGISwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _desc.FrameBufferCount);
|
||||
}
|
||||
|
||||
public Result BeginFrame(ulong cpuFrame)
|
||||
public ICommandSignature CreateCommandSignature(ref readonly CommandSignatureDesc desc, Key128<GraphicsPipeline> pipelineKey)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Debug.Assert(!_disposed);
|
||||
return new D3D12CommandSignature(_device, _pipelineLibrary, in desc, pipelineKey);
|
||||
}
|
||||
|
||||
public void BeginFrame(ulong cpuFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
|
||||
_cpuFrame = cpuFrame;
|
||||
_resourceDatabase.BeginFrame(cpuFrame);
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public Result EndFrame(ulong gpuFrame)
|
||||
public void EndFrame(ulong gpuFrame)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Debug.Assert(!_disposed);
|
||||
|
||||
_resourceDatabase.EndFrame(gpuFrame);
|
||||
|
||||
while (_commandBufferReturnQueue.TryPeek(out var entry) && entry.returnFrame < gpuFrame)
|
||||
{
|
||||
_commandBufferPool.Enqueue(entry.commandBuffer);
|
||||
_commandBufferPool.Add(entry.commandBuffer);
|
||||
_commandBufferReturnQueue.Dequeue();
|
||||
}
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -192,9 +196,9 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
entry.commandBuffer.Dispose();
|
||||
}
|
||||
|
||||
while (_commandBufferPool.TryDequeue(out var cmd))
|
||||
foreach (var commandBuffer in _commandBufferPool)
|
||||
{
|
||||
cmd.Dispose();
|
||||
commandBuffer.Dispose();
|
||||
}
|
||||
|
||||
_resourceDatabase.ReleaseAllResourcesImmediately();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Aliases.D3D_Alias;
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
using static TerraFX.Aliases.DXGI_Alias;
|
||||
@@ -18,15 +18,13 @@ internal unsafe class D3D12RenderDevice : D3D12Object<ID3D12Device14>, IRenderDe
|
||||
private readonly D3D12CommandQueue _graphicsQueue;
|
||||
private readonly D3D12CommandQueue _computeQueue;
|
||||
private readonly D3D12CommandQueue _copyQueue;
|
||||
private readonly FeatureSupport _featureSupport;
|
||||
|
||||
public ICommandQueue GraphicsQueue => _graphicsQueue;
|
||||
public ICommandQueue ComputeQueue => _computeQueue;
|
||||
public ICommandQueue CopyQueue => _copyQueue;
|
||||
|
||||
public FeatureSupport FeatureSupport
|
||||
{
|
||||
get;
|
||||
}
|
||||
public FeatureSupport FeatureSupport => _featureSupport;
|
||||
|
||||
public SharedPtr<IDXGIFactory7> DXGIFactory => _dxgiFactory.Share();
|
||||
public SharedPtr<IDXGIAdapter1> Adapter => _adapter.Share();
|
||||
@@ -35,7 +33,7 @@ internal unsafe class D3D12RenderDevice : D3D12Object<ID3D12Device14>, IRenderDe
|
||||
public SharedPtr<ID3D12CommandQueue> NativeCopyQueue => _copyQueue.NativeObject;
|
||||
|
||||
public D3D12RenderDevice()
|
||||
:base(CreateDevice(out var dxgiFactory, out var adapter))
|
||||
: base(CreateDevice(out var dxgiFactory, out var adapter))
|
||||
{
|
||||
_dxgiFactory.Attach(dxgiFactory);
|
||||
_adapter.Attach(adapter);
|
||||
@@ -44,7 +42,7 @@ internal unsafe class D3D12RenderDevice : D3D12Object<ID3D12Device14>, IRenderDe
|
||||
_computeQueue = new D3D12CommandQueue(this, CommandQueueType.Compute);
|
||||
_copyQueue = new D3D12CommandQueue(this, CommandQueueType.Copy);
|
||||
|
||||
FeatureSupport = GetFeatureSupport();
|
||||
_featureSupport = GetFeatureSupport();
|
||||
}
|
||||
|
||||
private static ID3D12Device14* CreateDevice(out IDXGIFactory7* dxgiFactory, out IDXGIAdapter1* adapter)
|
||||
|
||||
@@ -1017,6 +1017,124 @@ public struct CommandError
|
||||
}
|
||||
}
|
||||
|
||||
public struct IndirectArgumentDesc
|
||||
{
|
||||
public struct VertexBufferDesc
|
||||
{
|
||||
public uint Slot
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ConstantDesc
|
||||
{
|
||||
public uint RootParameterIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public uint DestOffsetIn32BitValues
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public uint Num32BitValuesToSet
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ConstantBufferViewDesc
|
||||
{
|
||||
public uint RootParameterIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ShaderResourceViewDesc
|
||||
{
|
||||
public uint RootParameterIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct UnorderedAccessViewDesc
|
||||
{
|
||||
public uint RootParameterIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct IncrementingConstantDesc
|
||||
{
|
||||
public uint RootParameterIndex
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public uint DestOffsetIn32BitValues
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct __union
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public VertexBufferDesc vertexBuffer;
|
||||
[FieldOffset(0)]
|
||||
public ConstantDesc constant;
|
||||
[FieldOffset(0)]
|
||||
public ConstantBufferViewDesc constantBufferView;
|
||||
[FieldOffset(0)]
|
||||
public ShaderResourceViewDesc shaderResourceView;
|
||||
[FieldOffset(0)]
|
||||
public UnorderedAccessViewDesc unorderedAccessView;
|
||||
[FieldOffset(0)]
|
||||
public IncrementingConstantDesc incrementingConstant;
|
||||
}
|
||||
|
||||
public IndirectArgumentType Type
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
private __union _data;
|
||||
|
||||
[UnscopedRef]
|
||||
public ref VertexBufferDesc VertexBuffer => ref _data.vertexBuffer;
|
||||
|
||||
[UnscopedRef]
|
||||
public ref ConstantDesc Constant => ref _data.constant;
|
||||
|
||||
[UnscopedRef]
|
||||
public ref ConstantBufferViewDesc ConstantBufferView => ref _data.constantBufferView;
|
||||
|
||||
[UnscopedRef]
|
||||
public ref ShaderResourceViewDesc ShaderResourceView => ref _data.shaderResourceView;
|
||||
|
||||
[UnscopedRef]
|
||||
public ref UnorderedAccessViewDesc UnorderedAccessView => ref _data.unorderedAccessView;
|
||||
|
||||
[UnscopedRef]
|
||||
public ref IncrementingConstantDesc IncrementingConstant => ref _data.incrementingConstant;
|
||||
}
|
||||
|
||||
public ref struct CommandSignatureDesc
|
||||
{
|
||||
public uint Stride
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<IndirectArgumentDesc> Arguments
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct SwapChainDesc
|
||||
{
|
||||
public uint Width
|
||||
@@ -1370,3 +1488,19 @@ public enum AttachmentStoreOp
|
||||
DontCare,
|
||||
NoAccess
|
||||
}
|
||||
|
||||
public enum IndirectArgumentType
|
||||
{
|
||||
Draw,
|
||||
DrawIndexed,
|
||||
Dispatch,
|
||||
VertexBufferView,
|
||||
IndexBufferView,
|
||||
Constant,
|
||||
ConstantBufferView,
|
||||
ShaderResourceView,
|
||||
UnorderedAccessView,
|
||||
DispatchRays,
|
||||
DispatchMesh,
|
||||
IncrementingConstant,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface ICommandAllocator : IDisposable
|
||||
public interface ICommandAllocator : IRHIObject
|
||||
{
|
||||
void Reset();
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ using Ghost.Core;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
// TODO: Add ICommandAllocator support for thread local command buffers. We often use one allocator for multiple command buffers in a single frame.
|
||||
|
||||
/// <summary>
|
||||
/// D3D12-style command buffer interface for recording rendering commands
|
||||
/// </summary>
|
||||
public interface ICommandBuffer : IDisposable
|
||||
public interface ICommandBuffer : IRHIObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the space of the command buffer.
|
||||
@@ -25,12 +23,6 @@ public interface ICommandBuffer : IDisposable
|
||||
get;
|
||||
}
|
||||
|
||||
string Name
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins recording commands into this command buffer
|
||||
/// </summary>
|
||||
@@ -182,9 +174,18 @@ public interface ICommandBuffer : IDisposable
|
||||
/// <summary>
|
||||
/// Dispatches ray tracing threads
|
||||
/// </summary>
|
||||
// TODO: This method is not supported yet.
|
||||
void DispatchRay();
|
||||
|
||||
/// <summary>
|
||||
/// Executes a sequence of GPU commands indirectly using the specified command signature and argument buffers.
|
||||
/// </summary>
|
||||
/// <param name="commandSignature">The command signature that defines the layout and type of commands to execute.</param>
|
||||
/// <param name="argumentBuffer">A handle to the GPU buffer containing the arguments for each command.</param>
|
||||
/// <param name="argumentOffset">The byte offset within the argument buffer at which to begin reading command arguments.</param>
|
||||
/// <param name="countBuffer">A handle to the GPU buffer that specifies the number of commands to execute.</param>
|
||||
/// <param name="countBufferOffset">The byte offset within the count buffer at which to read the command count.</param>
|
||||
void ExecuteIndirect(ICommandSignature commandSignature, Handle<GPUBuffer> argumentBuffer, ulong argumentOffset, Handle<GPUBuffer> countBuffer, ulong countBufferOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Copies a specified number of bytes from the source graphics buffer to the destination graphics buffer.
|
||||
/// </summary>
|
||||
@@ -204,5 +205,11 @@ public interface ICommandBuffer : IDisposable
|
||||
/// <param name="srcRegion">The region of the source texture to copy from. If null, the entire texture will be used.</param>
|
||||
void CopyTexture(Handle<GPUTexture> dst, TextureRegion? dstRegion, Handle<GPUTexture> src, TextureRegion? srcRegion);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the subresources of a GPU resource using data from the specified intermediate resource and subresource data spans.
|
||||
/// </summary>
|
||||
/// <param name="resource">A handle to the destination GPU resource whose subresources will be updated.</param>
|
||||
/// <param name="intermediate">A handle to an intermediate GPU resource used to stage the subresource data before copying to the destination resource.</param>
|
||||
/// <param name="subResources">A span containing the data for each subresource to update. Each element represents a subresource and its associated data.</param>
|
||||
void UpdateSubResources(Handle<GPUResource> resource, Handle<GPUResource> intermediate, params ReadOnlySpan<SubResourceData> subResources);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ namespace Ghost.Graphics.RHI;
|
||||
/// <summary>
|
||||
/// Command queue interface
|
||||
/// </summary>
|
||||
public interface ICommandQueue : IDisposable
|
||||
public interface ICommandQueue : IRHIObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of commands this queue can execute
|
||||
|
||||
3
src/Runtime/Ghost.Graphics.RHI/ICommandSignature.cs
Normal file
3
src/Runtime/Ghost.Graphics.RHI/ICommandSignature.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface ICommandSignature : IRHIObject;
|
||||
@@ -74,14 +74,14 @@ public interface IGraphicsEngine : IDisposable
|
||||
/// <summary>
|
||||
/// Begin the current frame.
|
||||
/// </summary>
|
||||
/// <param name="cpuFrame">CPU fence value for synchronization</param>
|
||||
/// <param name="submittedFrame">Submitted frame value for synchronization</param>
|
||||
/// <returns>Result of the begin frame operation</returns>
|
||||
Result BeginFrame(ulong cpuFrame);
|
||||
void BeginFrame(ulong submittedFrame);
|
||||
|
||||
/// <summary>
|
||||
/// End the current frame.
|
||||
/// </summary>
|
||||
/// <param name="gpuFrame">GPU fence value for synchronization</param>
|
||||
/// <param name="completedFrame">Completed frame value for synchronization</param>
|
||||
/// <returns>Result of the end frame operation</returns>
|
||||
Result EndFrame(ulong gpuFrame);
|
||||
void EndFrame(ulong completedFrame);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public enum FeatureSupport
|
||||
/// <summary>
|
||||
/// D3D12-native render device interface for creating graphics resources
|
||||
/// </summary>
|
||||
public interface IRenderDevice : IDisposable
|
||||
public interface IRenderDevice : IRHIObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Graphics command queue for rendering operations
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface IRenderOutput
|
||||
{
|
||||
ViewportDesc Viewport
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
ScissorRectDesc Scissor
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a handle to the current render target texture.
|
||||
/// </summary>
|
||||
/// <returns>A handle to the texture that is currently set as the render target.</returns>
|
||||
Handle<GPUTexture> GetRenderTarget();
|
||||
|
||||
/// <summary>
|
||||
/// Begins a rendering operation using the specified command buffer. Typically this will include resource barriers,
|
||||
/// </summary>
|
||||
/// <param name="cmd">The command buffer that records rendering commands.</param>
|
||||
///
|
||||
void BeginRender(ICommandBuffer cmd);
|
||||
/// <summary>
|
||||
/// Finalizes the rendering process using the specified command buffer.
|
||||
/// </summary>
|
||||
/// <param name="cmd">The command buffer that contains the rendering commands to be finalized.</param>
|
||||
void EndRender(ICommandBuffer cmd);
|
||||
|
||||
/// <summary>
|
||||
/// Displays the current frame to the output device or screen.
|
||||
/// </summary>
|
||||
/// <remarks>Call this method after rendering operations to present the rendered content. The exact
|
||||
/// behavior may depend on the underlying graphics implementation or device.</remarks>
|
||||
void Present();
|
||||
}
|
||||
@@ -24,17 +24,15 @@ public struct PushConstantsData
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FrameData
|
||||
{
|
||||
public uint viewBufferIndex;
|
||||
public uint instanceBufferIndex;
|
||||
public uint userBufferIndex;
|
||||
public uint userBuffer;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct InstanceData
|
||||
{
|
||||
public float4x4 localToWorld;
|
||||
public uint meshBufferIndex;
|
||||
public uint materialBufferIndex;
|
||||
public uint meshBuffer;
|
||||
public uint materialBuffer;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
|
||||
@@ -72,6 +72,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
|
||||
public int ActiveMeshIndexCount => _activeMeshIndexCount;
|
||||
|
||||
// TODO: Upload relative scale to gpu.
|
||||
public float2 RelativeScale
|
||||
{
|
||||
get; set;
|
||||
|
||||
@@ -210,10 +210,9 @@ public class RenderSystem : IDisposable
|
||||
{
|
||||
void StopRenderLoop(Result result)
|
||||
{
|
||||
Debug.Assert(result.IsFailure, "StopRenderLoop should only be called with a failure result.");
|
||||
|
||||
_isRunning = false;
|
||||
_shutdownEvent.Set();
|
||||
|
||||
#if DEBUG
|
||||
Debugger.Break();
|
||||
#endif
|
||||
@@ -224,113 +223,107 @@ public class RenderSystem : IDisposable
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
_frameIndex = (uint)(_submittedFenceValue % _config.FrameBufferCount);
|
||||
ref var frameResource = ref _frameResources[_frameIndex];
|
||||
|
||||
// Wait for either CPU ready signal or shutdown signal
|
||||
waitHandles[0] = frameResource.CpuReadyEvent;
|
||||
var waitResult = WaitHandle.WaitAny(waitHandles);
|
||||
|
||||
// If shutdown was signaled or timeout occurred, exit the loop
|
||||
if (!_isRunning || waitResult == 1 || waitResult == WaitHandle.WaitTimeout)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Only proceed if CPU ready event was signaled
|
||||
if (waitResult != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue);
|
||||
|
||||
if (!_resizeRequest.IsEmpty)
|
||||
{
|
||||
WaitIdle();
|
||||
|
||||
var keys = _resizeRequest.Keys.ToArray();
|
||||
foreach (var swapChain in keys)
|
||||
{
|
||||
if (_resizeRequest.TryRemove(swapChain, out var newSize))
|
||||
{
|
||||
swapChain.Resize(newSize.x, newSize.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var completedFenceValue = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
|
||||
if (_submittedFenceValue < completedFenceValue)
|
||||
{
|
||||
_submittedFenceValue = completedFenceValue;
|
||||
}
|
||||
|
||||
// Begin rendering for this frame
|
||||
frameResource.CommandAllocator.Reset();
|
||||
|
||||
_resourceManager.BeginFrame(_submittedFenceValue);
|
||||
var r = _graphicsEngine.BeginFrame(_submittedFenceValue);
|
||||
|
||||
if (r.IsFailure)
|
||||
{
|
||||
StopRenderLoop(r);
|
||||
break;
|
||||
}
|
||||
|
||||
// Start recording commands
|
||||
|
||||
// TODO: How can we support async compute and async copy?
|
||||
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
||||
ref var renderRequests = ref frameResource.RenderRequests;
|
||||
|
||||
try
|
||||
{
|
||||
cmd.Begin(frameResource.CommandAllocator);
|
||||
_frameIndex = (uint)(_submittedFenceValue % _config.FrameBufferCount);
|
||||
ref var frameResource = ref _frameResources[_frameIndex];
|
||||
|
||||
var renderCtx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
|
||||
// Wait for either CPU ready signal or shutdown signal
|
||||
waitHandles[0] = frameResource.CpuReadyEvent;
|
||||
var waitResult = WaitHandle.WaitAny(waitHandles);
|
||||
|
||||
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
|
||||
_swapChainManager.TransitionToPresent(cmd);
|
||||
|
||||
// End recording commands and submit
|
||||
r = cmd.End();
|
||||
if (r.IsFailure)
|
||||
// If shutdown was signaled or timeout occurred, exit the loop
|
||||
if (!_isRunning || waitResult == 1 || waitResult == WaitHandle.WaitTimeout)
|
||||
{
|
||||
StopRenderLoop(r);
|
||||
break;
|
||||
}
|
||||
|
||||
_graphicsEngine.Device.GraphicsQueue.Submit(cmd);
|
||||
_swapChainManager.PresentAll(cmd);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_graphicsEngine.ReturnPooledCommandBuffer(cmd);
|
||||
|
||||
for (var i = 0; i < renderRequests.Count; i++)
|
||||
// Only proceed if CPU ready event was signaled
|
||||
if (waitResult != 0)
|
||||
{
|
||||
renderRequests[i].Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
renderRequests.Clear();
|
||||
_graphicsEngine.Device.GraphicsQueue.WaitForValue(frameResource.FenceValue);
|
||||
|
||||
if (!_resizeRequest.IsEmpty)
|
||||
{
|
||||
WaitIdle();
|
||||
|
||||
var keys = _resizeRequest.Keys.ToArray();
|
||||
foreach (var swapChain in keys)
|
||||
{
|
||||
if (_resizeRequest.TryRemove(swapChain, out var newSize))
|
||||
{
|
||||
swapChain.Resize(newSize.x, newSize.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var completedFrame = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
|
||||
if (_submittedFenceValue < completedFrame)
|
||||
{
|
||||
_submittedFenceValue = completedFrame;
|
||||
}
|
||||
|
||||
// Begin rendering for this frame
|
||||
frameResource.CommandAllocator.Reset();
|
||||
|
||||
_resourceManager.BeginFrame(_submittedFenceValue);
|
||||
_graphicsEngine.BeginFrame(_submittedFenceValue);
|
||||
|
||||
// Start recording commands
|
||||
|
||||
// TODO: How can we support async compute and async copy?
|
||||
var cmd = _graphicsEngine.GetPooledCommandBuffer(CommandBufferType.Graphics);
|
||||
ref var renderRequests = ref frameResource.RenderRequests;
|
||||
|
||||
try
|
||||
{
|
||||
cmd.Begin(frameResource.CommandAllocator);
|
||||
|
||||
var renderCtx = new RenderContext(_graphicsEngine, _resourceManager, cmd);
|
||||
|
||||
_renderPipeline.Render(renderCtx, renderRequests.AsSpan());
|
||||
_swapChainManager.TransitionToPresent(cmd);
|
||||
|
||||
// End recording commands and submit
|
||||
var r = cmd.End();
|
||||
if (r.IsFailure)
|
||||
{
|
||||
StopRenderLoop(r);
|
||||
break;
|
||||
}
|
||||
|
||||
_graphicsEngine.Device.GraphicsQueue.Submit(cmd);
|
||||
_swapChainManager.PresentAll(cmd);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_graphicsEngine.ReturnPooledCommandBuffer(cmd);
|
||||
|
||||
for (var i = 0; i < renderRequests.Count; i++)
|
||||
{
|
||||
renderRequests[i].Dispose();
|
||||
}
|
||||
|
||||
renderRequests.Clear();
|
||||
}
|
||||
|
||||
_submittedFenceValue++;
|
||||
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_submittedFenceValue);
|
||||
frameResource.GpuReadyEvent.Set();
|
||||
|
||||
completedFrame = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
|
||||
|
||||
// End the frame and retire resources based on the freshest observed GPU progress.
|
||||
_resourceManager.EndFrame(completedFrame);
|
||||
_graphicsEngine.EndFrame(completedFrame);
|
||||
}
|
||||
|
||||
_submittedFenceValue++;
|
||||
frameResource.FenceValue = _graphicsEngine.Device.GraphicsQueue.Signal(_submittedFenceValue);
|
||||
frameResource.GpuReadyEvent.Set();
|
||||
|
||||
completedFenceValue = _graphicsEngine.Device.GraphicsQueue.GetCompletedValue();
|
||||
|
||||
// End the frame and retire resources based on the freshest observed GPU progress.
|
||||
_resourceManager.EndFrame(completedFenceValue);
|
||||
r = _graphicsEngine.EndFrame(completedFenceValue);
|
||||
|
||||
if (r.IsFailure)
|
||||
catch (Exception ex)
|
||||
{
|
||||
StopRenderLoop(r);
|
||||
break;
|
||||
StopRenderLoop(Result.Failure($"An exception occurred during rendering: {ex.Message}"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ public sealed partial class ResourceManager : IDisposable
|
||||
Dispose();
|
||||
}
|
||||
|
||||
internal void BeginFrame(ulong cpuFrame)
|
||||
internal void BeginFrame(ulong submittedFrame)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
_submittedFrame = cpuFrame;
|
||||
_submittedFrame = submittedFrame;
|
||||
}
|
||||
|
||||
internal void EndFrame(ulong completedFrame)
|
||||
|
||||
@@ -15,8 +15,6 @@ struct PushConstantData
|
||||
|
||||
struct FrameData
|
||||
{
|
||||
BYTE_ADDRESS_BUFFER viewBuffer;
|
||||
BYTE_ADDRESS_BUFFER instanceBuffer;
|
||||
BYTE_ADDRESS_BUFFER userBuffer;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Source: https://github.com/zeux/meshoptimizer/blob/master/demo/clusterlod.h
|
||||
// Translated from C++ to C#.
|
||||
|
||||
// TODO: This file should be moved to editor project since there is no reason we need to build meshlets and LOD at runtime.
|
||||
|
||||
using Ghost.MeshOptimizer;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
88
src/Runtime/Ghost.Graphics/Utilities/RenderingUtility.cs
Normal file
88
src/Runtime/Ghost.Graphics/Utilities/RenderingUtility.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics.Utilities;
|
||||
|
||||
public static unsafe class RenderingUtility
|
||||
{
|
||||
public static void UploadBuffer<T>(ResourceManager resourceManager, IResourceDatabase resourceDatabase, ICommandBuffer cmd, Handle<GPUBuffer> buffer, params ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var r = resourceDatabase.GetResourceDescription(buffer.AsResource());
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(r.Value.Type == ResourceType.Buffer);
|
||||
|
||||
var sizeInBytes = (nuint)(data.Length * sizeof(T));
|
||||
var memoryType = r.Value.BufferDescription.HeapType;
|
||||
|
||||
if (memoryType == HeapType.Upload)
|
||||
{
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
resourceDatabase.MapResource(buffer.AsResource(), 0, null, null, pData, sizeInBytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var uploadDesc = new BufferDesc
|
||||
{
|
||||
Size = sizeInBytes,
|
||||
Usage = BufferUsage.Upload,
|
||||
HeapType = HeapType.Upload,
|
||||
};
|
||||
|
||||
var uploadHandle = resourceManager.CreateTransientBuffer(in uploadDesc);
|
||||
if (uploadHandle.IsInvalid)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to create upload buffer for buffer data.");
|
||||
}
|
||||
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
resourceDatabase.MapResource(uploadHandle.AsResource(), 0, null, null, pData, sizeInBytes);
|
||||
}
|
||||
|
||||
cmd.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UploadTexture<T>(ResourceManager resourceManager, IResourceDatabase resourceDatabase, ICommandBuffer cmd, Handle<GPUTexture> texture, ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var desc = resourceDatabase.GetResourceDescription(texture.AsResource()).GetValueOrThrow();
|
||||
desc.TextureDescription.Format.GetSurfaceInfo(desc.TextureDescription.Width, desc.TextureDescription.Height, out var rowPitch, out var slicePitch, out _);
|
||||
|
||||
var requiredSize = resourceDatabase.GetIntermediateResourceSize(texture.AsResource(), 0, 1);
|
||||
var uploadDesc = new BufferDesc
|
||||
{
|
||||
Size = requiredSize,
|
||||
Usage = BufferUsage.Upload,
|
||||
HeapType = HeapType.Upload,
|
||||
};
|
||||
|
||||
var uploadHandle = resourceManager.CreateTransientBuffer(in uploadDesc);
|
||||
if (uploadHandle.IsInvalid)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to create upload buffer for texture data.");
|
||||
}
|
||||
|
||||
cmd.Barrier(BarrierDesc.Texture(texture.AsResource(), BarrierSync.Copy, BarrierAccess.CopyDest, BarrierLayout.CopyDest));
|
||||
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
var subresourceData = new SubResourceData
|
||||
{
|
||||
pData = pData,
|
||||
rowPitch = rowPitch,
|
||||
slicePitch = slicePitch
|
||||
};
|
||||
|
||||
cmd.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,6 @@ shader "MyShader/Standard"
|
||||
ByteAddressBuffer vertices = GET_BUFFER(meshData.vertexBuffer);
|
||||
Vertex v = vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
|
||||
|
||||
FrameData globalFrameData = LoadData<FrameData>(g_PushConstantData.frameBuffer, 0);
|
||||
ViewData viewData = LoadData<ViewData>(g_PushConstantData.viewBuffer, 0);
|
||||
|
||||
float4 worldPos = mul(instanceData.localToWorld, float4(v.position.xyz, 1.0f));
|
||||
|
||||
Reference in New Issue
Block a user