Major refactor of render pipeline and shader system: - Replaced legacy shader properties with source generator and attribute-based HLSL struct generation. - Introduced ShaderPropertiesRegistry for runtime property layout/code registration. - Added modular IRenderPipeline, IRenderPipelineSettings, and IRenderPayload interfaces. - Implemented GhostRenderPipeline and ECS-driven GPUScene management. - Added experimental DirectX 12 Work Graph support. - Refactored shader compilation, variant hashing, and caching. - Updated APIs for consistency and improved codegen for registration. These changes modernize the rendering infrastructure for advanced features like work graphs and dynamic pipelines. BREAKING CHANGE: Shader DSL, pipeline, and property APIs have changed. Existing shaders and pipeline integrations must be updated.
1028 lines
35 KiB
C#
1028 lines
35 KiB
C#
using Ghost.Core;
|
|
using Ghost.Graphics.D3D12.Utilities;
|
|
using Ghost.Graphics.RHI;
|
|
using Misaki.HighPerformance.LowLevel;
|
|
using System.Diagnostics;
|
|
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 : D3D12Object<ID3D12GraphicsCommandList10>, ICommandBuffer
|
|
{
|
|
private readonly D3D12PipelineLibrary _pipelineLibrary;
|
|
private readonly D3D12ResourceDatabase _resourceDatabase;
|
|
private readonly D3D12ResourceAllocator _resourceAllocator;
|
|
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
|
private readonly CommandBufferType _type;
|
|
|
|
private CommandError _lastError;
|
|
private ushort _commandCount;
|
|
private bool _isRecording;
|
|
|
|
public CommandBufferType Type => _type;
|
|
public bool IsEmpty => _commandCount == 0;
|
|
|
|
private static ID3D12GraphicsCommandList10* CreateCommandList(ID3D12Device14* device, D3D12_COMMAND_LIST_TYPE type)
|
|
{
|
|
ID3D12GraphicsCommandList10* pCommandList = default;
|
|
ThrowIfFailed(device->CreateCommandList1(0u, type, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof(pCommandList), (void**)&pCommandList));
|
|
return pCommandList;
|
|
}
|
|
|
|
public D3D12CommandBuffer(
|
|
D3D12RenderDevice device,
|
|
D3D12PipelineLibrary pipelineLibrary,
|
|
D3D12ResourceDatabase resourceDatabase,
|
|
D3D12ResourceAllocator resourceAllocator,
|
|
D3D12DescriptorAllocator descriptorAllocator,
|
|
CommandBufferType type)
|
|
: base(CreateCommandList(device.NativeObject, D3D12Utility.ToCommandListType(type)))
|
|
{
|
|
_type = type;
|
|
|
|
_pipelineLibrary = pipelineLibrary;
|
|
_resourceDatabase = resourceDatabase;
|
|
_resourceAllocator = resourceAllocator;
|
|
_descriptorAllocator = descriptorAllocator;
|
|
|
|
_isRecording = false;
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void ThrowIfRecording()
|
|
{
|
|
if (_isRecording)
|
|
{
|
|
throw new InvalidOperationException("Command buffer is already recording");
|
|
}
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
[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)]
|
|
private void RecordError(string cmdName, Error status)
|
|
{
|
|
Debug.Fail($"Command '{cmdName}' failed with error: {status}");
|
|
|
|
_lastError = new CommandError
|
|
{
|
|
CommandName = cmdName,
|
|
CommandIndex = _commandCount,
|
|
Status = status
|
|
};
|
|
}
|
|
|
|
public void Begin(ICommandAllocator allocator)
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfRecording();
|
|
|
|
if (allocator is not D3D12CommandAllocator d3d12Allocator)
|
|
{
|
|
throw new ArgumentException("Invalid command allocator type", nameof(allocator));
|
|
}
|
|
|
|
ThrowIfFailed(pNativeObject->Reset(d3d12Allocator.NativeObject, 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
|
|
pNativeObject->SetDescriptorHeaps(2, heaps);
|
|
}
|
|
|
|
_commandCount = 0;
|
|
_isRecording = true;
|
|
}
|
|
|
|
public Result End()
|
|
{
|
|
ThrowIfDisposed();
|
|
ThrowIfNotRecording();
|
|
|
|
pNativeObject->Close();
|
|
_isRecording = false;
|
|
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return Result.Failure($"Command buffer ended with errors at {_lastError.CommandIndex}, command '{_lastError.CommandName}': {_lastError.Status}");
|
|
}
|
|
#endif
|
|
|
|
return Result.Success();
|
|
}
|
|
|
|
public void SetScissorRect(ScissorRectDesc rect)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var d3d12Rect = new RECT((int)rect.Left, (int)rect.Top, (int)rect.Right, (int)rect.Bottom);
|
|
pNativeObject->RSSetScissorRects(1, &d3d12Rect);
|
|
}
|
|
|
|
public void Barrier(params ReadOnlySpan<BarrierDesc> barrierDescs)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
if (barrierDescs.IsEmpty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var globalCount = 0;
|
|
var bufferCount = 0;
|
|
var textureCount = 0;
|
|
|
|
for (var i = 0; i < barrierDescs.Length; i++)
|
|
{
|
|
switch (barrierDescs[i].Type)
|
|
{
|
|
case BarrierType.Global: globalCount++; break;
|
|
case BarrierType.Buffer: bufferCount++; break;
|
|
case BarrierType.Texture: textureCount++; break;
|
|
}
|
|
}
|
|
|
|
var pGlobalBarriers = stackalloc D3D12_GLOBAL_BARRIER[globalCount];
|
|
var pBufferBarriers = stackalloc D3D12_BUFFER_BARRIER[bufferCount];
|
|
var pTextureBarriers = stackalloc D3D12_TEXTURE_BARRIER[textureCount];
|
|
|
|
var globalIndex = 0;
|
|
var bufferIndex = 0;
|
|
var textureIndex = 0;
|
|
|
|
for (var i = 0; i < barrierDescs.Length; i++)
|
|
{
|
|
var desc = barrierDescs[i];
|
|
switch (desc.Type)
|
|
{
|
|
case BarrierType.Global:
|
|
if (desc.SyncAfter == _resourceDatabase.globalBarrier.sync && desc.AccessAfter == _resourceDatabase.globalBarrier.access)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pGlobalBarriers[globalIndex++] = new D3D12_GLOBAL_BARRIER
|
|
{
|
|
SyncBefore = (D3D12_BARRIER_SYNC)_resourceDatabase.globalBarrier.sync,
|
|
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
|
AccessBefore = (D3D12_BARRIER_ACCESS)_resourceDatabase.globalBarrier.access,
|
|
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter
|
|
};
|
|
|
|
_resourceDatabase.globalBarrier = new ResourceBarrierData(BarrierLayout.Undefined, desc.AccessAfter, desc.SyncAfter);
|
|
break;
|
|
case BarrierType.Buffer:
|
|
{
|
|
var r = _resourceDatabase.GetResourceRecord(desc.Resource);
|
|
if (r.IsFailure)
|
|
{
|
|
RecordError(nameof(Barrier), r.Error);
|
|
continue;
|
|
}
|
|
|
|
ref var record = ref r.Value;
|
|
var accessBefore = desc.IsAliasing ? BarrierAccess.NoAccess : record.barrierData.access;
|
|
|
|
if (record.barrierData.sync == desc.SyncAfter && accessBefore == desc.AccessAfter)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var resource = record.ResourcePtr;
|
|
pBufferBarriers[bufferIndex++] = new D3D12_BUFFER_BARRIER
|
|
{
|
|
SyncBefore = (D3D12_BARRIER_SYNC)record.barrierData.sync,
|
|
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
|
AccessBefore = (D3D12_BARRIER_ACCESS)accessBefore,
|
|
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
|
|
pResource = resource,
|
|
Offset = 0,
|
|
Size = ulong.MaxValue
|
|
};
|
|
|
|
record.barrierData = new ResourceBarrierData(BarrierLayout.Undefined, desc.AccessAfter, desc.SyncAfter);
|
|
}
|
|
break;
|
|
case BarrierType.Texture:
|
|
{
|
|
var r = _resourceDatabase.GetResourceRecord(desc.Resource);
|
|
if (r.IsFailure)
|
|
{
|
|
RecordError(nameof(Barrier), r.Error);
|
|
continue;
|
|
}
|
|
|
|
ref var record = ref r.Value;
|
|
var accessBefore = desc.IsAliasing ? BarrierAccess.NoAccess : record.barrierData.access;
|
|
var layoutBefore = desc.IsAliasing ? BarrierLayout.Undefined : record.barrierData.layout;
|
|
|
|
if (record.barrierData.sync == desc.SyncAfter && accessBefore == desc.AccessAfter && layoutBefore == desc.LayoutAfter)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var resource = record.ResourcePtr;
|
|
pTextureBarriers[textureIndex++] = new D3D12_TEXTURE_BARRIER
|
|
{
|
|
SyncBefore = (D3D12_BARRIER_SYNC)record.barrierData.sync,
|
|
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
|
|
AccessBefore = (D3D12_BARRIER_ACCESS)accessBefore,
|
|
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
|
|
LayoutBefore = (D3D12_BARRIER_LAYOUT)layoutBefore,
|
|
LayoutAfter = (D3D12_BARRIER_LAYOUT)desc.LayoutAfter,
|
|
pResource = resource,
|
|
Subresources = new D3D12_BARRIER_SUBRESOURCE_RANGE
|
|
{
|
|
IndexOrFirstMipLevel = desc.Subresources.IndexOrFirstMipLevel,
|
|
NumMipLevels = desc.Subresources.NumMipLevels,
|
|
FirstArraySlice = desc.Subresources.FirstArraySlice,
|
|
NumArraySlices = desc.Subresources.NumArraySlices
|
|
},
|
|
Flags = desc.Discard ? D3D12_TEXTURE_BARRIER_FLAGS.D3D12_TEXTURE_BARRIER_FLAG_DISCARD : D3D12_TEXTURE_BARRIER_FLAGS.D3D12_TEXTURE_BARRIER_FLAG_NONE
|
|
};
|
|
|
|
record.barrierData = new ResourceBarrierData(desc.LayoutAfter, desc.AccessAfter, desc.SyncAfter);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
var groups = stackalloc D3D12_BARRIER_GROUP[3];
|
|
var groupCount = 0u;
|
|
|
|
if (globalIndex > 0)
|
|
{
|
|
groups[groupCount] = new D3D12_BARRIER_GROUP
|
|
{
|
|
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_GLOBAL,
|
|
NumBarriers = (uint)globalIndex,
|
|
};
|
|
groups[groupCount].Anonymous.pGlobalBarriers = pGlobalBarriers;
|
|
groupCount++;
|
|
}
|
|
|
|
if (bufferIndex > 0)
|
|
{
|
|
groups[groupCount] = new D3D12_BARRIER_GROUP
|
|
{
|
|
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_BUFFER,
|
|
NumBarriers = (uint)bufferIndex,
|
|
};
|
|
groups[groupCount].Anonymous.pBufferBarriers = pBufferBarriers;
|
|
groupCount++;
|
|
}
|
|
|
|
if (textureIndex > 0)
|
|
{
|
|
groups[groupCount] = new D3D12_BARRIER_GROUP
|
|
{
|
|
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_TEXTURE,
|
|
NumBarriers = (uint)textureIndex,
|
|
};
|
|
groups[groupCount].Anonymous.pTextureBarriers = pTextureBarriers;
|
|
groupCount++;
|
|
}
|
|
|
|
if (groupCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pNativeObject->Barrier(groupCount, groups);
|
|
}
|
|
|
|
public void SetRenderTargets(ReadOnlySpan<Handle<GPUTexture>> renderTargets, Handle<GPUTexture> depthTarget)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.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), Error.InvalidArgument);
|
|
continue;
|
|
}
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(handle.AsResource());
|
|
if (recordResult.Error != Error.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 != Error.None)
|
|
{
|
|
RecordError(nameof(SetRenderTargets), recordResult.Error);
|
|
return;
|
|
}
|
|
|
|
var viewGroup = recordResult.Value.viewGroup;
|
|
pDsvHandle[0] = _descriptorAllocator.GetCpuHandle(viewGroup.dsv);
|
|
}
|
|
|
|
pNativeObject->OMSetRenderTargets(rtvCount, pRtvHandles, FALSE, pDsvHandle);
|
|
}
|
|
|
|
public void ClearRenderTargetView(Handle<GPUTexture> renderTarget, Color128 clearColor)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(renderTarget.AsResource());
|
|
if (recordResult.Error != Error.None)
|
|
{
|
|
RecordError(nameof(ClearRenderTargetView), recordResult.Error);
|
|
return;
|
|
}
|
|
|
|
ref var record = ref recordResult.Value;
|
|
var cpuHandle = _descriptorAllocator.GetCpuHandle(record.viewGroup.rtv);
|
|
|
|
pNativeObject->ClearRenderTargetView(cpuHandle, (float*)&clearColor, 0, null);
|
|
}
|
|
|
|
public void ClearDepthStencilView(Handle<GPUTexture> depthStencil, bool inlcudeDepth, bool includeStencil, float clearDepth = 1.0f, byte clearStencil = 0)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(depthStencil.AsResource());
|
|
if (recordResult.Error != Error.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);
|
|
|
|
pNativeObject->ClearDepthStencilView(cpuHandle,
|
|
flag,
|
|
clearDepth,
|
|
clearStencil,
|
|
0,
|
|
null);
|
|
}
|
|
|
|
public void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, ref readonly PassDepthStencilDesc depthDesc, bool allowUAVWrites = false)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.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), Error.InvalidArgument);
|
|
continue;
|
|
}
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(rtDesc.Texture.AsResource());
|
|
if (recordResult.Error != Error.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 != Error.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,
|
|
AttachmentLoadOp.NoAccess => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS,
|
|
_ => 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,
|
|
AttachmentStoreOp.NoAccess => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
|
|
_ => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS
|
|
};
|
|
|
|
// 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,
|
|
AttachmentLoadOp.NoAccess => D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS,
|
|
_ => 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,
|
|
AttachmentStoreOp.NoAccess => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
|
|
_ => D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
pNativeObject->BeginRenderPass((uint)rtDescs.Length, pRtvDescs, pDsvDesc,
|
|
allowUAVWrites ? D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES : D3D12_RENDER_PASS_FLAG_NONE);
|
|
}
|
|
|
|
public void EndRenderPass()
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
pNativeObject->EndRenderPass();
|
|
}
|
|
|
|
public void SetViewport(ViewportDesc viewport)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var d3d12Viewport = new D3D12_VIEWPORT(viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth);
|
|
pNativeObject->RSSetViewports(1, &d3d12Viewport);
|
|
}
|
|
|
|
public void SetPipelineState(Key128<GraphicsPipeline> pipelineKey)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var psor = _pipelineLibrary.GetGraphicsPSO(pipelineKey);
|
|
if (psor.Error != Error.None)
|
|
{
|
|
RecordError(nameof(SetPipelineState), psor.Error);
|
|
return;
|
|
}
|
|
|
|
pNativeObject->SetGraphicsRootSignature(_pipelineLibrary.DefaultRootSignature);
|
|
pNativeObject->SetPipelineState(psor.Value);
|
|
}
|
|
|
|
public void SetConstantBufferView(uint slot, Handle<GPUBuffer> buffer)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var resource = _resourceDatabase.GetResource(buffer.AsResource());
|
|
pNativeObject->SetGraphicsRootConstantBufferView(slot, resource.Get()->GetGPUVirtualAddress());
|
|
}
|
|
|
|
public void SetVertexBuffer(uint slot, Handle<GPUBuffer> buffer, ulong offset = 0)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
var recordResult = _resourceDatabase.GetResourceRecord(buffer.AsResource());
|
|
if (recordResult.Error != Error.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
|
|
};
|
|
|
|
pNativeObject->IASetVertexBuffers(slot, 1, &vbView);
|
|
}
|
|
|
|
public void SetIndexBuffer(Handle<GPUBuffer> buffer, IndexType type, ulong offset = 0)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.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
|
|
};
|
|
|
|
pNativeObject->IASetIndexBuffer(&ibView);
|
|
}
|
|
|
|
public void SetPrimitiveTopology(PrimitiveTopology topology)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.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
|
|
};
|
|
|
|
pNativeObject->IASetPrimitiveTopology(d3d12Topology);
|
|
}
|
|
|
|
public void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan<uint> constantBuffer, uint offsetIn32Bits = 0)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
fixed (uint* pConstants = constantBuffer)
|
|
{
|
|
pNativeObject->SetGraphicsRoot32BitConstants(rootIndex, (uint)constantBuffer.Length, pConstants, offsetIn32Bits);
|
|
}
|
|
}
|
|
|
|
public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
pNativeObject->DrawInstanced(vertexCount, instanceCount, startVertex, startInstance);
|
|
}
|
|
|
|
public void SetProgram(ref readonly SetProgramDesc desc)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
pNativeObject->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance);
|
|
}
|
|
|
|
public void DispatchCompute(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
pNativeObject->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
|
|
}
|
|
|
|
public void DispatchMesh(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
IncrementCommandCount();
|
|
|
|
pNativeObject->DispatchMesh(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
|
|
}
|
|
|
|
public void DispatchRay()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void DispatchGraph(ref readonly DispatchGraphDesc desc)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void ExecuteIndirect(ICommandSignature commandSignature, Handle<GPUBuffer> argumentBuffer, ulong argumentOffset, Handle<GPUBuffer> countBuffer, ulong countBufferOffset)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
IncrementCommandCount();
|
|
|
|
Debug.Assert(commandSignature is D3D12CommandSignature);
|
|
|
|
var resource = _resourceDatabase.GetResource(argumentBuffer.AsResource());
|
|
var countResource = _resourceDatabase.GetResource(countBuffer.AsResource());
|
|
|
|
pNativeObject->ExecuteIndirect((ID3D12CommandSignature*)commandSignature.NativePointer, 0,
|
|
resource, argumentOffset, countResource, countBufferOffset);
|
|
}
|
|
|
|
public void CopyBuffer(Handle<GPUBuffer> dst, Handle<GPUBuffer> src, ulong dstOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (dst == src)
|
|
{
|
|
return;
|
|
}
|
|
|
|
IncrementCommandCount();
|
|
|
|
var pDstResource = _resourceDatabase.GetResource(dst.AsResource());
|
|
var pSrcResource = _resourceDatabase.GetResource(src.AsResource());
|
|
if (pSrcResource == null || pDstResource == null)
|
|
{
|
|
RecordError(nameof(CopyBuffer), Error.InvalidArgument);
|
|
return;
|
|
}
|
|
|
|
if (numBytes == 0)
|
|
{
|
|
pNativeObject->CopyResource(pDstResource, pSrcResource);
|
|
}
|
|
else
|
|
{
|
|
pNativeObject->CopyBufferRegion(pDstResource, dstOffset, pSrcResource, srcOffset, numBytes);
|
|
}
|
|
}
|
|
|
|
public void UpdateSubResources(Handle<GPUResource> resource, Handle<GPUResource> intermediate, params ReadOnlySpan<SubResourceData> subResources)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
IncrementCommandCount();
|
|
|
|
var d3d12Resource = _resourceDatabase.GetResource(resource);
|
|
var d3d12Intermediate = _resourceDatabase.GetResource(intermediate);
|
|
if (d3d12Intermediate == null || d3d12Resource == null)
|
|
{
|
|
RecordError(nameof(UpdateSubResources), Error.InvalidArgument);
|
|
return;
|
|
}
|
|
|
|
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*)pNativeObject,
|
|
d3d12Resource,
|
|
d3d12Intermediate,
|
|
0,
|
|
0,
|
|
(uint)subResources.Length,
|
|
d3d12Subresources);
|
|
}
|
|
|
|
private D3D12_TEXTURE_COPY_LOCATION GetTextureCopyLocation(SharedPtr<ID3D12Resource> texture, TextureSubresource subres)
|
|
{
|
|
var flatIndex = subres.MipLevel + subres.ArrayLayer * texture.Get()->GetDesc().MipLevels;
|
|
|
|
return new D3D12_TEXTURE_COPY_LOCATION
|
|
{
|
|
pResource = texture,
|
|
Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
|
SubresourceIndex = flatIndex
|
|
};
|
|
}
|
|
|
|
private bool AreTexturesIdentical(SharedPtr<ID3D12Resource> tex1, SharedPtr<ID3D12Resource> tex2)
|
|
{
|
|
var desc1 = tex1.Get()->GetDesc();
|
|
var desc2 = tex2.Get()->GetDesc();
|
|
return desc1.Width == desc2.Width
|
|
&& desc1.Height == desc2.Height
|
|
&& desc1.DepthOrArraySize == desc2.DepthOrArraySize
|
|
&& desc1.MipLevels == desc2.MipLevels
|
|
&& desc1.Format == desc2.Format
|
|
&& desc1.SampleDesc.Count == desc2.SampleDesc.Count;
|
|
}
|
|
|
|
public void CopyTexture(Handle<GPUTexture> dst, TextureRegion? dstRegion, Handle<GPUTexture> src, TextureRegion? srcRegion)
|
|
{
|
|
AssertNotDisposed();
|
|
ThrowIfNotRecording();
|
|
#if !DEBUG
|
|
if (_lastError.Status != Error.None)
|
|
{
|
|
return;
|
|
}
|
|
#endif
|
|
if (dst == src)
|
|
{
|
|
return;
|
|
}
|
|
|
|
IncrementCommandCount();
|
|
|
|
var pDstResource = _resourceDatabase.GetResource(dst.AsResource());
|
|
var pSrcResource = _resourceDatabase.GetResource(src.AsResource());
|
|
if (pSrcResource == null || pDstResource == null)
|
|
{
|
|
RecordError(nameof(CopyTexture), Error.InvalidArgument);
|
|
return;
|
|
}
|
|
|
|
if (dstRegion == null || srcRegion == null)
|
|
{
|
|
if (!AreTexturesIdentical(pDstResource, pSrcResource))
|
|
{
|
|
RecordError(nameof(CopyTexture), Error.InvalidArgument);
|
|
return;
|
|
}
|
|
|
|
pNativeObject->CopyResource(pDstResource, pSrcResource);
|
|
return;
|
|
}
|
|
|
|
var dstRegionV = dstRegion.Value;
|
|
var srcRegionV = srcRegion.Value;
|
|
|
|
var dstLocation = GetTextureCopyLocation(pDstResource, dstRegionV.Subresource);
|
|
var srcLocation = GetTextureCopyLocation(pSrcResource, srcRegionV.Subresource);
|
|
var srcBoc = new D3D12_BOX
|
|
{
|
|
left = srcRegionV.X,
|
|
top = srcRegionV.Y,
|
|
front = srcRegionV.Z,
|
|
right = srcRegionV.X + srcRegionV.Width,
|
|
bottom = srcRegionV.Y + srcRegionV.Height,
|
|
back = srcRegionV.Z + srcRegionV.Depth
|
|
};
|
|
|
|
pNativeObject->CopyTextureRegion(&dstLocation, dstRegionV.X, dstRegionV.Y, dstRegionV.Z, &srcLocation, &srcBoc);
|
|
}
|
|
}
|