Introduces a full-featured render graph system with pass culling, resource aliasing, and automatic barrier generation. Refactors resource and barrier APIs, improves error handling, and unifies result types. Renderer and render passes now use the new graph-based workflow. Updates shader includes, adds a blit shader, and improves HLSL parsing. Removes dynamic descriptor heaps in favor of persistent ones. Project file now includes the render graph module. Lays the foundation for advanced rendering features and improved memory efficiency.
1037 lines
33 KiB
C#
1037 lines
33 KiB
C#
using Ghost.Core;
|
|
using Ghost.Core.Utilities;
|
|
using Ghost.Graphics.Core;
|
|
using Ghost.Graphics.D3D12.Utilities;
|
|
using Ghost.Graphics.RHI;
|
|
using Misaki.HighPerformance.LowLevel;
|
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
|
using System.Runtime.CompilerServices;
|
|
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;
|
|
|
|
namespace Ghost.Graphics.D3D12;
|
|
|
|
internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
|
{
|
|
private UniquePtr<ID3D12GraphicsCommandList10> _commandList;
|
|
|
|
private readonly D3D12PipelineLibrary _pipelineLibrary;
|
|
private readonly D3D12ResourceDatabase _resourceDatabase;
|
|
private readonly D3D12ResourceAllocator _resourceAllocator;
|
|
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
|
private readonly CommandBufferType _type;
|
|
|
|
#if !DEBUG
|
|
private CommandError _lastError;
|
|
#endif
|
|
private ushort _commandCount;
|
|
private bool _isRecording;
|
|
private bool _disposed;
|
|
|
|
public SharedPtr<ID3D12GraphicsCommandList10> NativeCommandList => _commandList.Get();
|
|
|
|
public CommandBufferType Type => _type;
|
|
public bool IsEmpty => _commandCount == 0;
|
|
|
|
public string Name
|
|
{
|
|
get => field;
|
|
set
|
|
{
|
|
if (field == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
field = value;
|
|
_commandList.Get()->SetName(value);
|
|
}
|
|
} = string.Empty;
|
|
|
|
public D3D12CommandBuffer(
|
|
D3D12RenderDevice device,
|
|
D3D12PipelineLibrary stateController,
|
|
D3D12ResourceDatabase resourceDatabase,
|
|
D3D12ResourceAllocator resourceAllocator,
|
|
D3D12DescriptorAllocator descriptorAllocator,
|
|
CommandBufferType type)
|
|
{
|
|
_type = type;
|
|
|
|
ID3D12GraphicsCommandList10* pCommandList = default;
|
|
var commandListType = D3D12Utility.ToCommandListType(type);
|
|
|
|
device.NativeDevice.Get()->CreateCommandList1(0u, commandListType, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof(pCommandList), (void**)&pCommandList);
|
|
|
|
_commandList.Attach(pCommandList);
|
|
|
|
_pipelineLibrary = stateController;
|
|
_resourceDatabase = resourceDatabase;
|
|
_resourceAllocator = resourceAllocator;
|
|
_descriptorAllocator = descriptorAllocator;
|
|
|
|
_isRecording = false;
|
|
}
|
|
|
|
~D3D12CommandBuffer()
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void ThrowIfDisposed()
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void ThrowIfRecording()
|
|
{
|
|
if (_isRecording)
|
|
{
|
|
throw new InvalidOperationException("Command buffer is already recording");
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void ThrowIfNotRecording()
|
|
{
|
|
if (!_isRecording)
|
|
{
|
|
throw new InvalidOperationException("Command buffer is not recording");
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void IncrementCommandCount()
|
|
{
|
|
_commandCount++;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
#if DEBUG
|
|
[System.Diagnostics.CodeAnalysis.DoesNotReturn]
|
|
private static void RecordError(string cmdName, ErrorStatus status)
|
|
#else
|
|
private void RecordError(string cmdName, ErrorStatus status)
|
|
#endif
|
|
{
|
|
#if DEBUG
|
|
throw new InvalidOperationException($"Error at {cmdName} with {status}");
|
|
#else
|
|
|
|
_lastError = new CommandError
|
|
{
|
|
CommandName = cmdName,
|
|
CommandIndex = _commandCount,
|
|
Status = status
|
|
};
|
|
#endif
|
|
}
|
|
|
|
public void Begin(ICommandAllocator allocator)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfRecording();
|
|
|
|
if (allocator is not D3D12CommandAllocator d3d12Allocator)
|
|
{
|
|
throw new ArgumentException("Invalid command allocator type", nameof(allocator));
|
|
}
|
|
|
|
ThrowIfFailed(_commandList.Get()->Reset(d3d12Allocator.NativeAllocator, null));
|
|
|
|
if (Type == CommandBufferType.Graphics || Type == CommandBufferType.Compute)
|
|
{
|
|
// Set descriptor heaps for bindless resources and samplers
|
|
|
|
var heaps = stackalloc ID3D12DescriptorHeap*[2];
|
|
heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Bindless resource Heap
|
|
heaps[1] = _descriptorAllocator.GetSamplerHeap(); // Bindless sampler Heap
|
|
_commandList.Get()->SetDescriptorHeaps(2, heaps);
|
|
}
|
|
|
|
_commandCount = 0;
|
|
_isRecording = true;
|
|
}
|
|
|
|
public Result End()
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
|
|
_commandList.Get()->Close();
|
|
_isRecording = false;
|
|
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return Result.Failure($"Command buffer ended with errors at {_lastError.CommandIndex}, command '{_lastError.CommandName}': {_lastError.Status}");
|
|
}
|
|
#endif
|
|
|
|
return Result.Success();
|
|
}
|
|
|
|
public void SetScissorRect(RectDesc rect)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var d3d12Rect = new RECT((int)rect.Left, (int)rect.Top, (int)rect.Right, (int)rect.Bottom);
|
|
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
|
|
}
|
|
|
|
public void ResourceBarrier(ReadOnlySpan<BarrierDesc> barrierDescs)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
if (barrierDescs.IsEmpty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var count = 0u;
|
|
var pBarriers = stackalloc D3D12_RESOURCE_BARRIER[barrierDescs.Length];
|
|
|
|
for (var i = 0; i < barrierDescs.Length; i++)
|
|
{
|
|
var desc = barrierDescs[i];
|
|
D3D12_RESOURCE_BARRIER barrier = default;
|
|
|
|
switch (desc.type)
|
|
{
|
|
case BarrierType.Transition:
|
|
if (desc.transition.stateBefore == desc.transition.stateAfter)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(desc.transition.resource);
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(TransitionBarrier), recordResult.Error);
|
|
continue;
|
|
}
|
|
|
|
ref var record = ref recordResult.Value;
|
|
var stateBefore = desc.transition.stateBefore == ResourceState.Auto ? record.state : desc.transition.stateBefore;
|
|
|
|
barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr,
|
|
stateBefore.ToD3D12States(), desc.transition.stateAfter.ToD3D12States());
|
|
|
|
record.state = desc.transition.stateAfter;
|
|
break;
|
|
|
|
case BarrierType.Aliasing:
|
|
var recordBeforeResult = _resourceDatabase.GetResourceRecord(desc.aliasing.resourceBefore);
|
|
if (recordBeforeResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(TransitionBarrier), recordBeforeResult.Error);
|
|
continue;
|
|
}
|
|
|
|
var recordAfterResult = _resourceDatabase.GetResourceRecord(desc.aliasing.resourceAfter);
|
|
if (recordAfterResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(TransitionBarrier), recordAfterResult.Error);
|
|
continue;
|
|
}
|
|
|
|
barrier = D3D12_RESOURCE_BARRIER.InitAliasing(
|
|
recordBeforeResult.Value.ResourcePtr,
|
|
recordAfterResult.Value.ResourcePtr);
|
|
break;
|
|
case BarrierType.UAV:
|
|
var recordUavResult = _resourceDatabase.GetResourceRecord(desc.uav.resource);
|
|
if (recordUavResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(TransitionBarrier), recordUavResult.Error);
|
|
continue;
|
|
}
|
|
|
|
barrier = D3D12_RESOURCE_BARRIER.InitUAV(recordUavResult.Value.ResourcePtr);
|
|
break;
|
|
}
|
|
|
|
pBarriers[count] = barrier;
|
|
count++;
|
|
}
|
|
|
|
_commandList.Get()->ResourceBarrier(count, pBarriers);
|
|
}
|
|
|
|
public void TransitionBarrier(Handle<GPUResource> resource, ResourceState stateBefore, ResourceState stateAfter)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
if (stateBefore == stateAfter)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(resource);
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(TransitionBarrier), recordResult.Error);
|
|
return;
|
|
}
|
|
|
|
ref var record = ref recordResult.Value;
|
|
var barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr,
|
|
stateBefore.ToD3D12States(), stateAfter.ToD3D12States());
|
|
|
|
_commandList.Get()->ResourceBarrier(1, &barrier);
|
|
record.state = stateAfter;
|
|
}
|
|
|
|
public void TransitionBarrier(Handle<GPUResource> resource, ResourceState stateAfter)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(resource);
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(TransitionBarrier), recordResult.Error);
|
|
return;
|
|
}
|
|
|
|
ref var record = ref recordResult.Value;
|
|
if (record.state == stateAfter)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var barrier = D3D12_RESOURCE_BARRIER.InitTransition(record.ResourcePtr,
|
|
record.state.ToD3D12States(), stateAfter.ToD3D12States());
|
|
|
|
_commandList.Get()->ResourceBarrier(1, &barrier);
|
|
record.state = stateAfter;
|
|
}
|
|
|
|
public void AliasBarrier(Handle<GPUResource> resourceBefore, Handle<GPUResource> resourceAfter)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var recordBeforeResult = _resourceDatabase.GetResourceRecord(resourceBefore);
|
|
if (recordBeforeResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(AliasBarrier), recordBeforeResult.Error);
|
|
return;
|
|
}
|
|
|
|
var recordAfterResult = _resourceDatabase.GetResourceRecord(resourceAfter);
|
|
if (recordAfterResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(AliasBarrier), recordAfterResult.Error);
|
|
return;
|
|
}
|
|
|
|
var barrier = D3D12_RESOURCE_BARRIER.InitAliasing(
|
|
recordBeforeResult.Value.ResourcePtr,
|
|
recordAfterResult.Value.ResourcePtr);
|
|
_commandList.Get()->ResourceBarrier(1, &barrier);
|
|
}
|
|
|
|
public void SetRenderTargets(ReadOnlySpan<Handle<Texture>> renderTargets, Handle<Texture> depthTarget)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var pRtvHandles = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[renderTargets.Length];
|
|
var rtvCount = 0u;
|
|
for (var i = 0; i < renderTargets.Length; i++)
|
|
{
|
|
var handle = renderTargets[i];
|
|
if (!handle.IsValid)
|
|
{
|
|
RecordError(nameof(SetRenderTargets), ErrorStatus.InvalidArgument);
|
|
continue;
|
|
}
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(handle.AsResource());
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(SetRenderTargets), recordResult.Error);
|
|
continue;
|
|
}
|
|
|
|
var viewGroup = recordResult.Value.viewGroup;
|
|
pRtvHandles[i] = _descriptorAllocator.GetCpuHandle(viewGroup.rtv);
|
|
|
|
rtvCount++;
|
|
}
|
|
|
|
var pDsvHandle = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[depthTarget.IsValid ? 1 : 0];
|
|
if (pDsvHandle != null)
|
|
{
|
|
var recordResult = _resourceDatabase.GetResourceRecord(depthTarget.AsResource());
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(SetRenderTargets), recordResult.Error);
|
|
return;
|
|
}
|
|
|
|
var viewGroup = recordResult.Value.viewGroup;
|
|
pDsvHandle[0] = _descriptorAllocator.GetCpuHandle(viewGroup.dsv);
|
|
}
|
|
|
|
_commandList.Get()->OMSetRenderTargets(rtvCount, pRtvHandles, FALSE, pDsvHandle);
|
|
}
|
|
|
|
public void ClearRenderTargetView(Handle<Texture> renderTarget, Color128 clearColor)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(renderTarget.AsResource());
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(ClearRenderTargetView), recordResult.Error);
|
|
return;
|
|
}
|
|
|
|
ref var record = ref recordResult.Value;
|
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
|
|
var color = stackalloc float[4]
|
|
{
|
|
clearColor.r,
|
|
clearColor.g,
|
|
clearColor.b,
|
|
clearColor.a
|
|
};
|
|
|
|
_commandList.Get()->ClearRenderTargetView(cpuHandle, color, 0, null);
|
|
}
|
|
|
|
public void ClearDepthStencilView(Handle<Texture> depthStencil, bool inlcudeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(depthStencil.AsResource());
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(ClearDepthStencilView), recordResult.Error);
|
|
return;
|
|
}
|
|
|
|
ref var record = ref recordResult.Value;
|
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv);
|
|
var flag = (inlcudeDepth ? D3D12_CLEAR_FLAG_DEPTH : 0) | (includeStencil ? D3D12_CLEAR_FLAG_STENCIL : 0);
|
|
|
|
_commandList.Get()->ClearDepthStencilView(cpuHandle,
|
|
flag,
|
|
clearDepth,
|
|
clearStencil,
|
|
0,
|
|
null);
|
|
}
|
|
|
|
public void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var pRtvDescs = stackalloc D3D12_RENDER_PASS_RENDER_TARGET_DESC[rtDescs.Length];
|
|
for (var i = 0; i < rtDescs.Length; i++)
|
|
{
|
|
var rtDesc = rtDescs[i];
|
|
if (rtDesc.Texture.IsInvalid)
|
|
{
|
|
RecordError(nameof(BeginRenderPass), ErrorStatus.InvalidArgument);
|
|
continue;
|
|
}
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(rtDesc.Texture.AsResource());
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(BeginRenderPass), recordResult.Error);
|
|
continue;
|
|
}
|
|
|
|
ref var record = ref recordResult.Value;
|
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
|
|
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
|
|
var clearColor = rtDesc.ClearColor;
|
|
|
|
// Map load operation
|
|
var loadAccessType = rtDesc.LoadOp switch
|
|
{
|
|
AttachmentLoadOp.Load => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE,
|
|
AttachmentLoadOp.Clear => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR,
|
|
AttachmentLoadOp.DontCare => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD,
|
|
_ => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE
|
|
};
|
|
|
|
// Map store operation
|
|
var storeAccessType = rtDesc.StoreOp switch
|
|
{
|
|
AttachmentStoreOp.Store => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE,
|
|
AttachmentStoreOp.DontCare => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD,
|
|
_ => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE
|
|
};
|
|
|
|
var desc = new D3D12_RENDER_PASS_RENDER_TARGET_DESC
|
|
{
|
|
cpuDescriptor = cpuHandle,
|
|
BeginningAccess = new D3D12_RENDER_PASS_BEGINNING_ACCESS
|
|
{
|
|
Type = loadAccessType,
|
|
Clear = loadAccessType == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR
|
|
? new D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS
|
|
{
|
|
ClearValue = new D3D12_CLEAR_VALUE(format, (float*)&clearColor)
|
|
}
|
|
: default
|
|
},
|
|
EndingAccess = new D3D12_RENDER_PASS_ENDING_ACCESS
|
|
{
|
|
Type = storeAccessType
|
|
}
|
|
};
|
|
|
|
pRtvDescs[i] = desc;
|
|
}
|
|
|
|
var pDsvDesc = stackalloc D3D12_RENDER_PASS_DEPTH_STENCIL_DESC[depthDesc.Texture.IsValid ? 1 : 0];
|
|
if (pDsvDesc != null)
|
|
{
|
|
var recordResult = _resourceDatabase.GetResourceRecord(depthDesc.Texture.AsResource());
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(BeginRenderPass), recordResult.Error);
|
|
return;
|
|
}
|
|
|
|
ref var record = ref recordResult.Value;
|
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.dsv);
|
|
var format = record.desc.TextureDescription.Format.ToDXGIFormat();
|
|
|
|
// Map depth load operation
|
|
var depthLoadAccessType = depthDesc.DepthLoadOp switch
|
|
{
|
|
AttachmentLoadOp.Load => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE,
|
|
AttachmentLoadOp.Clear => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR,
|
|
AttachmentLoadOp.DontCare => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD,
|
|
_ => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE
|
|
};
|
|
|
|
// Map depth store operation
|
|
var depthStoreAccessType = depthDesc.DepthStoreOp switch
|
|
{
|
|
AttachmentStoreOp.Store => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE,
|
|
AttachmentStoreOp.DontCare => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD,
|
|
_ => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE
|
|
};
|
|
|
|
// Map stencil load operation
|
|
var stencilLoadAccessType = depthDesc.StencilLoadOp switch
|
|
{
|
|
AttachmentLoadOp.Load => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE,
|
|
AttachmentLoadOp.Clear => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR,
|
|
AttachmentLoadOp.DontCare => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD,
|
|
_ => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE
|
|
};
|
|
|
|
// Map stencil store operation
|
|
var stencilStoreAccessType = depthDesc.StencilStoreOp switch
|
|
{
|
|
AttachmentStoreOp.Store => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE,
|
|
AttachmentStoreOp.DontCare => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD,
|
|
_ => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE
|
|
};
|
|
|
|
var desc = new D3D12_RENDER_PASS_DEPTH_STENCIL_DESC
|
|
{
|
|
cpuDescriptor = cpuHandle,
|
|
DepthBeginningAccess = new D3D12_RENDER_PASS_BEGINNING_ACCESS
|
|
{
|
|
Type = depthLoadAccessType,
|
|
Clear = depthLoadAccessType == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR
|
|
? new D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS
|
|
{
|
|
ClearValue = new D3D12_CLEAR_VALUE(format, depthDesc.ClearDepth, depthDesc.ClearStencil)
|
|
}
|
|
: default
|
|
},
|
|
DepthEndingAccess = new D3D12_RENDER_PASS_ENDING_ACCESS
|
|
{
|
|
Type = depthStoreAccessType
|
|
},
|
|
StencilBeginningAccess = new D3D12_RENDER_PASS_BEGINNING_ACCESS
|
|
{
|
|
Type = stencilLoadAccessType,
|
|
Clear = stencilLoadAccessType == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR
|
|
? new D3D12_RENDER_PASS_BEGINNING_ACCESS_CLEAR_PARAMETERS
|
|
{
|
|
ClearValue = new D3D12_CLEAR_VALUE(format, depthDesc.ClearDepth, depthDesc.ClearStencil)
|
|
}
|
|
: default
|
|
},
|
|
StencilEndingAccess = new D3D12_RENDER_PASS_ENDING_ACCESS
|
|
{
|
|
Type = stencilStoreAccessType
|
|
}
|
|
};
|
|
|
|
pDsvDesc[0] = desc;
|
|
}
|
|
|
|
_commandList.Get()->BeginRenderPass((uint)rtDescs.Length, pRtvDescs, pDsvDesc,
|
|
allowUAVWrites ? D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES : D3D12_RENDER_PASS_FLAG_NONE);
|
|
}
|
|
|
|
public void EndRenderPass()
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
_commandList.Get()->EndRenderPass();
|
|
}
|
|
|
|
public void SetViewport(ViewportDesc viewport)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var d3d12Viewport = new D3D12_VIEWPORT(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth);
|
|
_commandList.Get()->RSSetViewports(1, &d3d12Viewport);
|
|
}
|
|
|
|
public void SetPipelineState(Key128<GraphicsPipeline> pipelineKey)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var psor = _pipelineLibrary.GetGraphicsPSO(pipelineKey);
|
|
if (psor.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(SetPipelineState), psor.Error);
|
|
return;
|
|
}
|
|
|
|
_commandList.Get()->SetGraphicsRootSignature(_pipelineLibrary.DefaultRootSignature);
|
|
_commandList.Get()->SetPipelineState(psor.Value);
|
|
}
|
|
|
|
public void SetConstantBufferView(uint slot, Handle<GraphicsBuffer> buffer)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var resource = _resourceDatabase.GetResource(buffer.AsResource());
|
|
_commandList.Get()->SetGraphicsRootConstantBufferView(slot, resource.Get()->GetGPUVirtualAddress());
|
|
}
|
|
|
|
public void SetVertexBuffer(uint slot, Handle<GraphicsBuffer> buffer, ulong offset = 0)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(buffer.AsResource());
|
|
if (recordResult.Error != ErrorStatus.None)
|
|
{
|
|
RecordError(nameof(BeginRenderPass), recordResult.Error);
|
|
return;
|
|
}
|
|
|
|
ref var record = ref recordResult.Value;
|
|
var vbView = new D3D12_VERTEX_BUFFER_VIEW
|
|
{
|
|
BufferLocation = record.ResourcePtr.Get()->GetGPUVirtualAddress() + offset,
|
|
SizeInBytes = (uint)(record.ResourcePtr.Get()->GetDesc().Width - offset),
|
|
StrideInBytes = record.desc.BufferDescription.Stride
|
|
};
|
|
|
|
_commandList.Get()->IASetVertexBuffers(slot, 1, &vbView);
|
|
}
|
|
|
|
public void SetIndexBuffer(Handle<GraphicsBuffer> buffer, IndexType type, ulong offset = 0)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var resource = _resourceDatabase.GetResource(buffer.AsResource());
|
|
var ibView = new D3D12_INDEX_BUFFER_VIEW
|
|
{
|
|
BufferLocation = resource.Get()->GetGPUVirtualAddress() + offset,
|
|
SizeInBytes = (uint)(resource.Get()->GetDesc().Width - offset),
|
|
Format = type == IndexType.UInt16 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT
|
|
};
|
|
|
|
_commandList.Get()->IASetIndexBuffer(&ibView);
|
|
}
|
|
|
|
public void SetPrimitiveTopology(PrimitiveTopology topology)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var d3d12Topology = topology switch
|
|
{
|
|
PrimitiveTopology.Point => D3D_PRIMITIVE_TOPOLOGY_POINTLIST,
|
|
PrimitiveTopology.Line => D3D_PRIMITIVE_TOPOLOGY_LINELIST,
|
|
PrimitiveTopology.Triangle => D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
|
|
_ => D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST
|
|
};
|
|
|
|
_commandList.Get()->IASetPrimitiveTopology(d3d12Topology);
|
|
}
|
|
|
|
public void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan<uint> constantBuffer, uint offsetIn32Bits = 0)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
fixed (uint* pConstants = constantBuffer)
|
|
{
|
|
_commandList.Get()->SetGraphicsRoot32BitConstants(rootIndex, (uint)constantBuffer.Length, pConstants, offsetIn32Bits);
|
|
}
|
|
}
|
|
|
|
public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
_commandList.Get()->DrawInstanced(vertexCount, instanceCount, startVertex, startInstance);
|
|
}
|
|
|
|
public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
_commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance);
|
|
}
|
|
|
|
public void DispatchCompute(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
_commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
|
|
}
|
|
|
|
public void DispatchMesh(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
_commandList.Get()->DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
|
|
}
|
|
|
|
public void DispatchRay()
|
|
{
|
|
throw new NotImplementedException();
|
|
|
|
// ThrowIfDisposed();
|
|
// ThrowIfNotRecording();
|
|
// IncrementCommandCount();
|
|
|
|
// _device.Get()->DispatchRays();
|
|
}
|
|
|
|
public void DispatchGraph()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void ExecuteIndirect(Handle<GraphicsBuffer> argumentBuffer, ulong argumentOffset, Handle<GraphicsBuffer> countBuffer, ulong countBufferOffset)
|
|
{
|
|
throw new NotImplementedException();
|
|
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
IncrementCommandCount();
|
|
var resource = _resourceDatabase.GetResource(argumentBuffer.AsResource());
|
|
var countResource = _resourceDatabase.GetResource(countBuffer.AsResource());
|
|
_commandList.Get()->ExecuteIndirect(null, 0,
|
|
resource, argumentOffset, countResource, countBufferOffset);
|
|
|
|
}
|
|
|
|
public void UploadBuffer<T>(Handle<GraphicsBuffer> buffer, ReadOnlySpan<T> data)
|
|
where T : unmanaged
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var sizeInBytes = (uint)(data.Length * sizeof(T));
|
|
|
|
var uploadHandle = _resourceAllocator.CreateTempUploadBuffer(sizeInBytes, out var offset);
|
|
var uploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
|
|
|
|
void* pMappedData;
|
|
uploadResource.Get()->Map(0, null, &pMappedData);
|
|
fixed (T* pData = data)
|
|
{
|
|
MemoryUtility.MemCpy((byte*)pMappedData + offset, pData, sizeInBytes);
|
|
}
|
|
uploadResource.Get()->Unmap(0, null);
|
|
|
|
var pResource = _resourceDatabase.GetResource(buffer.AsResource());
|
|
_commandList.Get()->CopyBufferRegion(pResource, 0, uploadResource, offset, sizeInBytes);
|
|
}
|
|
|
|
public void UploadTexture(Handle<Texture> texture, ReadOnlySpan<SubResourceData> subresources)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var resource = _resourceDatabase.GetResource(texture.AsResource());
|
|
|
|
var resourceDesc = resource.Get()->GetDesc();
|
|
var requiredSize = GetRequiredIntermediateSize(resource, 0, (uint)subresources.Length);
|
|
|
|
var uploadHandle = _resourceAllocator.CreateTempUploadBuffer(requiredSize, out var offset);
|
|
var pUploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
|
|
|
|
var d3d12Subresources = stackalloc D3D12_SUBRESOURCE_DATA[subresources.Length];
|
|
for (var i = 0; i < subresources.Length; i++)
|
|
{
|
|
d3d12Subresources[i] = new D3D12_SUBRESOURCE_DATA
|
|
{
|
|
pData = subresources[i].pData,
|
|
RowPitch = (nint)subresources[i].rowPitch,
|
|
SlicePitch = (nint)subresources[i].slicePitch
|
|
};
|
|
}
|
|
|
|
UpdateSubresources(
|
|
(ID3D12GraphicsCommandList*)_commandList.Get(),
|
|
resource,
|
|
pUploadResource,
|
|
offset,
|
|
0,
|
|
(uint)subresources.Length,
|
|
d3d12Subresources);
|
|
}
|
|
|
|
public void CopyBuffer(Handle<GraphicsBuffer> dest, Handle<GraphicsBuffer> src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != ErrorStatus.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var pDestResource = _resourceDatabase.GetResource(dest.AsResource());
|
|
var pSrcResource = _resourceDatabase.GetResource(src.AsResource());
|
|
if (pSrcResource == null || pDestResource == null)
|
|
{
|
|
RecordError(nameof(CopyBuffer), ErrorStatus.InvalidArgument);
|
|
return;
|
|
}
|
|
|
|
if (numBytes == 0)
|
|
{
|
|
_commandList.Get()->CopyResource(pDestResource, pSrcResource);
|
|
}
|
|
else
|
|
{
|
|
_commandList.Get()->CopyBufferRegion(pDestResource, destOffset, pSrcResource, srcOffset, numBytes);
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_isRecording)
|
|
{
|
|
throw new InvalidOperationException("Command buffer is still recording");
|
|
}
|
|
|
|
_commandList.Dispose();
|
|
_commandCount = 0;
|
|
|
|
_disposed = true;
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|