Refactor rendering projects

This commit is contained in:
2026-02-24 20:08:26 +09:00
parent 93c58fa7fb
commit 30090f84ab
88 changed files with 1350 additions and 1136 deletions

View File

@@ -0,0 +1,8 @@
global using static TerraFX.Interop.DirectX.D3D12;
global using static TerraFX.Interop.DirectX.DirectX;
global using static TerraFX.Interop.DirectX.DXGI;
global using static TerraFX.Interop.Windows.Windows;
using System.Runtime.Versioning;
[assembly: SupportedOSPlatform("windows10.0.19041.0")]

View File

@@ -0,0 +1,34 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12CommandAllocator : ICommandAllocator
{
private UniquePtr<ID3D12CommandAllocator> _allocator;
public SharedPtr<ID3D12CommandAllocator> NativeAllocator => _allocator.Share();
public D3D12CommandAllocator(D3D12RenderDevice device, CommandBufferType type)
{
ID3D12CommandAllocator* pAllocator = default;
var commandListType = D3D12Utility.ToCommandListType(type);
device.NativeDevice.Get()->CreateCommandAllocator(commandListType, __uuidof(pAllocator), (void**)&pAllocator);
_allocator.Attach(pAllocator);
}
public void Reset()
{
_allocator.Get()->Reset();
}
public void Dispose()
{
_allocator.Dispose();
}
}

View File

@@ -0,0 +1,998 @@
using Ghost.Core;
using Ghost.Core.Utilities;
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, Error status)
#else
private void RecordError(string cmdName, Error 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 != 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(RectDesc rect)
{
ThrowIfDisposed();
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);
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
}
public void ResourceBarrier(params ReadOnlySpan<BarrierDesc> barrierDescs)
{
ThrowIfDisposed();
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:
pGlobalBarriers[globalIndex++] = new D3D12_GLOBAL_BARRIER
{
SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore,
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore,
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter
};
break;
case BarrierType.Buffer:
{
var r = _resourceDatabase.GetResourceRecord(desc.Resource);
if (r.IsFailure)
{
RecordError(nameof(ResourceBarrier), r.Error);
continue;
}
ref var record = ref r.Value;
var resource = record.ResourcePtr;
pBufferBarriers[bufferIndex++] = new D3D12_BUFFER_BARRIER
{
SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore,
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
AccessBefore = (D3D12_BARRIER_ACCESS)desc.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(ResourceBarrier), r.Error);
continue;
}
ref var record = ref r.Value;
var resource = record.ResourcePtr;
pTextureBarriers[textureIndex++] = new D3D12_TEXTURE_BARRIER
{
SyncBefore = (D3D12_BARRIER_SYNC)desc.SyncBefore,
SyncAfter = (D3D12_BARRIER_SYNC)desc.SyncAfter,
AccessBefore = (D3D12_BARRIER_ACCESS)desc.AccessBefore,
AccessAfter = (D3D12_BARRIER_ACCESS)desc.AccessAfter,
LayoutBefore = (D3D12_BARRIER_LAYOUT)desc.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 (globalCount > 0)
{
groups[groupCount] = new D3D12_BARRIER_GROUP
{
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_GLOBAL,
NumBarriers = (uint)globalCount,
};
groups[groupCount].Anonymous.pGlobalBarriers = pGlobalBarriers;
groupCount++;
}
if (bufferCount > 0)
{
groups[groupCount] = new D3D12_BARRIER_GROUP
{
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_BUFFER,
NumBarriers = (uint)bufferCount,
};
groups[groupCount].Anonymous.pBufferBarriers = pBufferBarriers;
groupCount++;
}
if (textureCount > 0)
{
groups[groupCount] = new D3D12_BARRIER_GROUP
{
Type = D3D12_BARRIER_TYPE.D3D12_BARRIER_TYPE_TEXTURE,
NumBarriers = (uint)textureCount,
};
groups[groupCount].Anonymous.pTextureBarriers = pTextureBarriers;
groupCount++;
}
_commandList.Get()->Barrier(groupCount, groups);
}
public void SetRenderTargets(ReadOnlySpan<Handle<Texture>> renderTargets, Handle<Texture> depthTarget)
{
ThrowIfDisposed();
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);
}
_commandList.Get()->OMSetRenderTargets(rtvCount, pRtvHandles, FALSE, pDsvHandle);
}
public void ClearRenderTargetView(Handle<Texture> renderTarget, Color128 clearColor)
{
ThrowIfDisposed();
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);
_commandList.Get()->ClearRenderTargetView(cpuHandle, (float*)&clearColor, 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 != 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);
_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 != 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,
_ => 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 != Error.None)
{
return;
}
#endif
IncrementCommandCount();
_commandList.Get()->EndRenderPass();
}
public void SetViewport(ViewportDesc viewport)
{
ThrowIfDisposed();
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);
_commandList.Get()->RSSetViewports(1, &d3d12Viewport);
}
public void SetPipelineState(Key128<GraphicsPipeline> pipelineKey)
{
ThrowIfDisposed();
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;
}
_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 != Error.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 != 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
};
_commandList.Get()->IASetVertexBuffers(slot, 1, &vbView);
}
public void SetIndexBuffer(Handle<GraphicsBuffer> buffer, IndexType type, ulong offset = 0)
{
ThrowIfDisposed();
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
};
_commandList.Get()->IASetIndexBuffer(&ibView);
}
public void SetPrimitiveTopology(PrimitiveTopology topology)
{
ThrowIfDisposed();
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
};
_commandList.Get()->IASetPrimitiveTopology(d3d12Topology);
}
public void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan<uint> constantBuffer, uint offsetIn32Bits = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != Error.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 != Error.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 != Error.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 != Error.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 != Error.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 != Error.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, params ReadOnlySpan<T> data)
where T : unmanaged
{
ThrowIfDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != Error.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, params ReadOnlySpan<SubResourceData> subresources)
{
ThrowIfDisposed();
ThrowIfNotRecording();
#if !DEBUG
if (_lastError.Status != Error.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 != Error.None)
{
return;
}
#endif
IncrementCommandCount();
var pDestResource = _resourceDatabase.GetResource(dest.AsResource());
var pSrcResource = _resourceDatabase.GetResource(src.AsResource());
if (pSrcResource == null || pDestResource == null)
{
RecordError(nameof(CopyBuffer), Error.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);
}
}

View File

@@ -0,0 +1,182 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of command queue interface
/// </summary>
internal unsafe class D3D12CommandQueue : ICommandQueue
{
private UniquePtr<ID3D12CommandQueue> _commandQueue;
private UniquePtr<ID3D12Fence1> _fence;
private readonly AutoResetEvent _fenceEvent;
private ulong _fenceValue;
private bool _disposed;
public CommandQueueType Type
{
get;
}
public SharedPtr<ID3D12CommandQueue> NativeQueue => _commandQueue.Get();
public D3D12CommandQueue(ID3D12Device14* pDevice, CommandQueueType type)
{
Type = type;
_fenceEvent = new AutoResetEvent(false);
_fenceValue = 0;
var queueDesc = new D3D12_COMMAND_QUEUE_DESC
{
Type = ConvertCommandQueueType(type),
Priority = (int)D3D12_COMMAND_QUEUE_PRIORITY.D3D12_COMMAND_QUEUE_PRIORITY_NORMAL,
Flags = D3D12_COMMAND_QUEUE_FLAGS.D3D12_COMMAND_QUEUE_FLAG_NONE,
};
ID3D12CommandQueue* pQueue = default;
ID3D12Fence1* pFence = default;
ThrowIfFailed(pDevice->CreateCommandQueue(&queueDesc, __uuidof(pQueue), (void**)&pQueue));
ThrowIfFailed(pDevice->CreateFence(0, D3D12_FENCE_FLAGS.D3D12_FENCE_FLAG_NONE, __uuidof(pFence), (void**)&pFence));
_commandQueue.Attach(pQueue);
_fence.Attach(pFence);
}
~D3D12CommandQueue()
{
Dispose();
}
private static D3D12_COMMAND_LIST_TYPE ConvertCommandQueueType(CommandQueueType type)
{
return type switch
{
CommandQueueType.Graphics => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_DIRECT,
CommandQueueType.Compute => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COMPUTE,
CommandQueueType.Copy => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COPY,
_ => throw new ArgumentException($"Unknown command queue type: {type}")
};
}
public void Submit(ICommandBuffer commandBuffer)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (commandBuffer.IsEmpty)
{
return;
}
if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer)
{
var commandList = d3d12CommandBuffer.NativeCommandList;
var commandListPtr = (ID3D12CommandList*)commandList.Get();
_commandQueue.Get()->ExecuteCommandLists(1, &commandListPtr);
}
else
{
throw new ArgumentException("Command buffer must be a D3D12CommandBuffer", nameof(commandBuffer));
}
}
public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers)
{
ObjectDisposedException.ThrowIf(_disposed, this);
Span<int> executableIndices = stackalloc int[commandBuffers.Length];
executableIndices.Fill(-1);
var currentIndex = 0;
for (var i = 0; i < commandBuffers.Length; i++)
{
if (!commandBuffers[i].IsEmpty)
{
executableIndices[currentIndex] = i;
currentIndex++;
}
}
var ppCommandLists = stackalloc ID3D12CommandList*[commandBuffers.Length];
currentIndex = 0;
while (currentIndex < commandBuffers.Length)
{
var cmdIndex = executableIndices[currentIndex];
if (cmdIndex == -1)
{
break;
}
if (commandBuffers[cmdIndex] is D3D12CommandBuffer d3d12CommandBuffer)
{
ppCommandLists[currentIndex] = (ID3D12CommandList*)d3d12CommandBuffer.NativeCommandList.Get();
}
else
{
throw new ArgumentException("Command buffer must be a D3D12CommandBuffer", nameof(commandBuffers));
}
currentIndex++;
}
_commandQueue.Get()->ExecuteCommandLists((uint)currentIndex, ppCommandLists);
}
public ulong Signal(ulong value)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_fenceValue = value;
ThrowIfFailed(_commandQueue.Get()->Signal((ID3D12Fence*)_fence.Get(), _fenceValue));
return _fenceValue;
}
public void WaitForValue(ulong value)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_fence.Get()->GetCompletedValue() < value)
{
var handle = new HANDLE((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
if (_fence.Get()->SetEventOnCompletion(value, handle).SUCCEEDED)
{
_fenceEvent.WaitOne();
}
}
}
public ulong GetCompletedValue()
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _fence.Get()->GetCompletedValue();
}
public void WaitIdle()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var fenceValue = Signal(Interlocked.Increment(ref _fenceValue));
WaitForValue(fenceValue);
}
public void Dispose()
{
if (_disposed)
{
return;
}
_commandQueue.Dispose();
_fence.Dispose();
_fenceEvent?.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,45 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.DXGI_Alias;
namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12DebugLayer
{
private UniquePtr<ID3D12Debug6> _d3d12Debug;
private UniquePtr<IDXGIDebug1> _dxgiDebug;
private UniquePtr<IDXGIInfoQueue> _dxgiInfoQueue;
public D3D12DebugLayer()
{
ID3D12Debug6* pDebug = default;
ThrowIfFailed(D3D12GetDebugInterface(__uuidof(pDebug), (void**)&pDebug));
pDebug->EnableDebugLayer();
IDXGIDebug1* pDxgiDebug = default;
ThrowIfFailed(DXGIGetDebugInterface1(0u, __uuidof(pDxgiDebug), (void**)&pDxgiDebug));
pDxgiDebug->EnableLeakTrackingForThread();
IDXGIInfoQueue* pDxgiInfoQueue = default;
ThrowIfFailed(DXGIGetDebugInterface1(0u, __uuidof(pDxgiInfoQueue), (void**)&pDxgiInfoQueue));
ThrowIfFailed(pDxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true));
ThrowIfFailed(pDxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true));
_d3d12Debug.Attach(pDebug);
_dxgiDebug.Attach(pDxgiDebug);
_dxgiInfoQueue.Attach(pDxgiInfoQueue);
}
public void Dispose()
{
ThrowIfFailed(_dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL | DXGI_DEBUG_RLO_IGNORE_INTERNAL));
_d3d12Debug.Dispose();
_dxgiDebug.Dispose();
_dxgiInfoQueue.Dispose();
}
}

View File

@@ -0,0 +1,379 @@
using Ghost.Core;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of viewGroup allocator that manages different types of viewGroup heaps.
/// </summary>
internal unsafe class D3D12DescriptorAllocator : IDisposable
{
private readonly D3D12DescriptorHeap _rtvHeap;
private readonly D3D12DescriptorHeap _dsvHeap;
private readonly D3D12DescriptorHeap _cbvSrvUavHeap;
private readonly D3D12DescriptorHeap _samplerHeap;
private bool _disposed;
public D3D12DescriptorAllocator(D3D12RenderDevice device, int initialRtvCount = 512, int initialDsvCount = 512, int initialSrvCount = 200_000, int initialSamplerCount = 256)
{
_rtvHeap = new D3D12DescriptorHeap("rtv", device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, initialRtvCount);
_dsvHeap = new D3D12DescriptorHeap("dsv", device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, initialDsvCount);
_cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, initialSrvCount);
_samplerHeap = new D3D12DescriptorHeap("sampler", device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, initialSamplerCount);
}
~D3D12DescriptorAllocator()
{
Dispose();
}
#region RTV Methods
public Identifier<RTVDescriptor> AllocateRTV()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _rtvHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate RTV descriptor");
}
return new Identifier<RTVDescriptor>(index);
}
public Identifier<RTVDescriptor>[] AllocateRTVs(int count)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _rtvHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors");
}
var descriptors = new Identifier<RTVDescriptor>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new Identifier<RTVDescriptor>(index);
}
return descriptors;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<RTVDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _rtvHeap.GetCpuHandle(descriptor.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(Identifier<RTVDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_rtvHeap.ReleaseDescriptor(descriptor.Value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(ReadOnlySpan<Identifier<RTVDescriptor>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
Release(descriptor);
}
}
#endregion
#region DSV Methods
public Identifier<DSVDescriptor> AllocateDSV()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _dsvHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate DSV descriptor");
}
return new Identifier<DSVDescriptor>(index);
}
public Identifier<DSVDescriptor>[] AllocateDSVs(int count)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _dsvHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors");
}
var descriptors = new Identifier<DSVDescriptor>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new Identifier<DSVDescriptor>(index);
}
return descriptors;
}
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<DSVDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _dsvHeap.GetCpuHandle(descriptor.Value);
}
public void Release(Identifier<DSVDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_dsvHeap.ReleaseDescriptor(descriptor.Value);
}
public void Release(ReadOnlySpan<Identifier<DSVDescriptor>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
Release(descriptor);
}
}
#endregion
#region CBV_SRV_UAV Methods
public Identifier<CbvSrvUavDescriptor> AllocateCbvSrvUav()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _cbvSrvUavHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate CBV/SRV/UAV descriptor");
}
return new Identifier<CbvSrvUavDescriptor>(index);
}
public Identifier<CbvSrvUavDescriptor>[] AllocateSRVs(int count)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _cbvSrvUavHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} CBV/SRV/UAV descriptors");
}
var descriptors = new Identifier<CbvSrvUavDescriptor>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new Identifier<CbvSrvUavDescriptor>(index);
}
return descriptors;
}
public void CopyToShaderVisible(Identifier<CbvSrvUavDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.CopyToShaderVisibleHeap(descriptor.Value);
}
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<CbvSrvUavDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _cbvSrvUavHeap.GetCpuHandle(descriptor.Value);
}
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandleShaderVisible(Identifier<CbvSrvUavDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _cbvSrvUavHeap.GetCpuHandleShaderVisible(descriptor.Value);
}
public D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(Identifier<CbvSrvUavDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _cbvSrvUavHeap.GetGpuHandle(descriptor.Value);
}
public void Release(Identifier<CbvSrvUavDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.ReleaseDescriptor(descriptor.Value);
}
public void Release(ReadOnlySpan<Identifier<CbvSrvUavDescriptor>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
Release(descriptor);
}
}
#endregion
#region Sampler Methods
public Identifier<SamplerDescriptor> AllocateSampler()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _samplerHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate Sampler descriptor");
}
return new Identifier<SamplerDescriptor>(index);
}
public Identifier<SamplerDescriptor>[] AllocateSamplers(int count)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _samplerHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} Sampler descriptors");
}
var descriptors = new Identifier<SamplerDescriptor>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new Identifier<SamplerDescriptor>(index);
}
return descriptors;
}
public void CopyToShaderVisible(Identifier<SamplerDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_samplerHeap.CopyToShaderVisibleHeap(descriptor.Value);
}
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<SamplerDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetCpuHandle(descriptor.Value);
}
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandleShaderVisible(Identifier<SamplerDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetCpuHandleShaderVisible(descriptor.Value);
}
public D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(Identifier<SamplerDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetGpuHandle(descriptor.Value);
}
public void Release(Identifier<SamplerDescriptor> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_samplerHeap.ReleaseDescriptor(descriptor.Value);
}
public void Release(ReadOnlySpan<Identifier<SamplerDescriptor>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
Release(descriptor);
}
}
#endregion
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(ResourceViewGroup descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
Release(descriptor.rtv);
Release(descriptor.dsv);
Release(descriptor.srv);
Release(descriptor.cbv);
Release(descriptor.uav);
Release(descriptor.sampler);
}
#region Utility Methods
/// <summary>
/// Gets the RTV Heap for binding to the command list.
/// </summary>
public ID3D12DescriptorHeap* GetRTVHeap() => _rtvHeap.Heap;
/// <summary>
/// Gets the DSV Heap for binding to the command list.
/// </summary>
public ID3D12DescriptorHeap* GetDSVHeap() => _dsvHeap.Heap;
/// <summary>
/// Gets the CBV/SRV/UAV Heap for binding to the command list.
/// </summary>
public ID3D12DescriptorHeap* GetCbvSrvUavHeap() => _cbvSrvUavHeap.ShaderVisibleHeap;
/// <summary>
/// Gets the sampler Heap for binding to the command list.
/// </summary>
public ID3D12DescriptorHeap* GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap;
/// <summary>
/// Gets the shader visible heaps that need to be bound to the command list.
/// </summary>
/// <param name="ppHeap">An array of two ID3D12DescriptorHeap pointers to receive the CBV/SRV/UAV and Sampler heaps.</param>
public void GetShaderVisibleHeaps(ID3D12DescriptorHeap** ppHeap)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (ppHeap == null)
{
throw new ArgumentNullException(nameof(ppHeap));
}
ppHeap[0] = _cbvSrvUavHeap.ShaderVisibleHeap;
ppHeap[1] = _samplerHeap.ShaderVisibleHeap;
}
#endregion
public void Dispose()
{
if (_disposed)
{
return;
}
_rtvHeap.Dispose();
_dsvHeap.Dispose();
_cbvSrvUavHeap.Dispose();
_samplerHeap.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,308 @@
using Ghost.Graphics.D3D12.Utilities;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
using System.Numerics;
using TerraFX.Interop.DirectX;
using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12DescriptorHeap : IDisposable
{
private const int _INVALID_DESCRIPTOR_INDEX = -1;
private readonly D3D12RenderDevice _device;
private UniquePtr<ID3D12DescriptorHeap> _heap;
private UniquePtr<ID3D12DescriptorHeap> _shaderVisibleHeap;
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandle;
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandleShaderVisible;
private D3D12_GPU_DESCRIPTOR_HANDLE _startGpuHandleShaderVisible;
private int _searchStart;
private UnsafeBitSet _allocatedDescriptors;
private readonly Lock _lock = new();
public D3D12_DESCRIPTOR_HEAP_TYPE HeapType
{
get;
}
public int NumDescriptors
{
get; private set;
}
public int NumAllocatedDescriptors
{
get; private set;
}
public bool ShaderVisible
{
get;
}
public uint Stride
{
get;
}
public ID3D12DescriptorHeap* Heap => _heap.Get();
public ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.Get();
public D3D12DescriptorHeap(string name, D3D12RenderDevice device, D3D12_DESCRIPTOR_HEAP_TYPE type, int numDescriptors)
{
numDescriptors = Math.Max(64, numDescriptors);
_device = device;
HeapType = type;
NumDescriptors = numDescriptors;
ShaderVisible = type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
Stride = device.NativeDevice.Get()->GetDescriptorHandleIncrementSize(type);
var success = AllocateResources(numDescriptors);
Debug.Assert(success);
_heap.Get()->SetName(name);
if (ShaderVisible)
{
_shaderVisibleHeap.Get()->SetName($"{name} Shader Visible");
}
}
public int AllocateDescriptor() => AllocateDescriptors(1);
public int AllocateDescriptors(int count)
{
lock (_lock)
{
var foundIndex = 0;
uint freeCount = 0;
var found = false;
// Find a contiguous range of 'count' indices for which _allocatedDescriptors[index] is false
for (var index = _searchStart; index < NumDescriptors; index++)
{
if (_allocatedDescriptors.IsSet(index))
{
freeCount = 0;
}
else
{
freeCount += 1;
}
if (freeCount >= count)
{
foundIndex = index > 0 ? index - count + 1 : 0;
found = true;
break;
}
}
if (!found)
{
foundIndex = NumDescriptors;
if (!Grow(NumDescriptors + count))
{
Debug.WriteLine("Error: Failed to grow descriptor heap.");
return _INVALID_DESCRIPTOR_INDEX;
}
}
for (var index = foundIndex; index < foundIndex + count; index++)
{
_allocatedDescriptors.SetBit(index);
}
NumAllocatedDescriptors += count;
_searchStart = foundIndex + count;
return foundIndex;
}
}
public void ReleaseDescriptor(int index) => ReleaseDescriptors(index, 1);
public void ReleaseDescriptors(int baseIndex, int count = 1)
{
if (baseIndex == _INVALID_DESCRIPTOR_INDEX)
{
return;
}
if (count == 0)
{
return;
}
lock (_lock)
{
for (var index = baseIndex; index < baseIndex + count; index++)
{
#if DEBUG || GHOST_EDITOR
if (!_allocatedDescriptors.IsSet(index))
{
Debug.WriteLine("Error: Attempted to release an un-allocated descriptor");
}
#endif
_allocatedDescriptors.ClearBit(index);
}
NumAllocatedDescriptors -= count;
if (_searchStart > baseIndex)
{
_searchStart = baseIndex;
}
}
}
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(int index)
{
if (index < 0 || index >= NumDescriptors)
{
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
}
var handle = _startCpuHandle;
return handle.Offset(index, Stride);
}
public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandleShaderVisible(int index)
{
if (index < 0 || index >= NumDescriptors)
{
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
}
if (!ShaderVisible)
{
throw new InvalidOperationException("Descriptor heap is not shader visible.");
}
var handle = _startCpuHandleShaderVisible;
return handle.Offset(index, Stride);
}
public D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(int index)
{
if (index < 0 || index >= NumDescriptors)
{
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
}
if (!ShaderVisible)
{
throw new InvalidOperationException("Descriptor heap is not shader visible.");
}
var handle = _startGpuHandleShaderVisible;
return handle.Offset(index, Stride);
}
public void CopyToShaderVisibleHeap(int index, int count = 1)
{
_device.NativeDevice.Get()->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
}
private bool AllocateResources(int numDescriptors)
{
NumDescriptors = numDescriptors;
_heap.Dispose();
_shaderVisibleHeap.Dispose();
D3D12_DESCRIPTOR_HEAP_DESC heapDesc = new()
{
Type = HeapType,
NumDescriptors = (uint)numDescriptors,
Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask = 0
};
ID3D12DescriptorHeap* pHeap = default;
var hr = _device.NativeDevice.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(pHeap), (void**)&pHeap);
if (hr.FAILED)
{
return false;
}
_heap.Attach(pHeap);
_startCpuHandle = _heap.Get()->GetCPUDescriptorHandleForHeapStart();
if (!_allocatedDescriptors.IsCreated)
{
_allocatedDescriptors = new UnsafeBitSet(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear);
}
else
{
_allocatedDescriptors.Resize(numDescriptors, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption.Clear);
}
if (ShaderVisible)
{
ID3D12DescriptorHeap* pShaderVisibleHeap = default;
heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
hr = _device.NativeDevice.Get()->CreateDescriptorHeap(&heapDesc, __uuidof(pShaderVisibleHeap), (void**)&pShaderVisibleHeap);
if (hr.FAILED)
{
return false;
}
_startCpuHandleShaderVisible = pShaderVisibleHeap->GetCPUDescriptorHandleForHeapStart();
_startGpuHandleShaderVisible = pShaderVisibleHeap->GetGPUDescriptorHandleForHeapStart();
_shaderVisibleHeap.Attach(pShaderVisibleHeap);
}
return true;
}
private bool Grow(int minRequiredSize)
{
var oldSize = NumDescriptors;
var newSize = (int)BitOperations.RoundUpToPowerOf2((uint)minRequiredSize);
var oldHeap = _heap.Detach();
try
{
if (!AllocateResources(newSize))
{
return false;
}
_device.NativeDevice.Get()->CopyDescriptorsSimple((uint)oldSize, _startCpuHandle, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType);
if (_shaderVisibleHeap.Get() != null)
{
_device.NativeDevice.Get()->CopyDescriptorsSimple((uint)oldSize, _startCpuHandleShaderVisible, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType);
}
}
finally
{
oldHeap->Release();
}
return true;
}
/// <inheritdoc />
public void Dispose()
{
Debug.Assert(NumAllocatedDescriptors == 0);
_heap.Dispose();
_shaderVisibleHeap.Dispose();
_allocatedDescriptors.Dispose();
}
}

View File

@@ -0,0 +1,174 @@
#if DEBUG
#define ENABLE_DEBUG
#endif
using Ghost.Core;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.D3D12;
public static class D3D12GraphicsEngineFactory
{
public static IGraphicsEngine Create(IRenderSystem renderSystem)
{
return new D3D12GraphicsEngine(renderSystem);
}
}
internal class D3D12GraphicsEngine : IGraphicsEngine
{
private GCHandle _thisHandle;
private readonly IRenderSystem _renderSystem;
#if ENABLE_DEBUG
private readonly D3D12DebugLayer _debugLayer;
#endif
private readonly D3D12RenderDevice _device;
private readonly DxcShaderCompiler _shaderCompiler;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12PipelineLibrary _pipelineLibrary;
private readonly D3D12ResourceAllocator _resourceAllocator;
private ImmutableArray<IRenderer> _renderers;
private bool _disposed;
public IRenderDevice Device => _device;
public IShaderCompiler ShaderCompiler => _shaderCompiler;
public IPipelineLibrary PipelineLibrary => _pipelineLibrary;
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceAllocator ResourceAllocator => _resourceAllocator;
public D3D12GraphicsEngine(IRenderSystem renderSystem)
{
_renderSystem = renderSystem;
#if ENABLE_DEBUG
_debugLayer = new D3D12DebugLayer();
#endif
_device = new D3D12RenderDevice();
_shaderCompiler = new DxcShaderCompiler();
_descriptorAllocator = new D3D12DescriptorAllocator(_device);
_resourceDatabase = new D3D12ResourceDatabase(renderSystem, _descriptorAllocator);
_pipelineLibrary = new D3D12PipelineLibrary(_device, _resourceDatabase);
_resourceAllocator = new D3D12ResourceAllocator(_device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary);
_renderers = ImmutableArray<IRenderer>.Empty;
_pipelineLibrary.InitializeLibrary(null);
}
~D3D12GraphicsEngine()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
public IRenderer CreateRenderer()
{
ThrowIfDisposed();
var renderer = new D3D12Renderer(this);
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Add(renderer));
return renderer;
}
public void RemoveRenderer(IRenderer renderer)
{
ThrowIfDisposed();
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Remove(renderer));
}
public void ClearRenderers()
{
ThrowIfDisposed();
ImmutableInterlocked.Update(ref _renderers, renderers => renderers.Clear());
}
public ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics)
{
return new D3D12CommandAllocator(_device, type);
}
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
{
ThrowIfDisposed();
return new D3D12CommandBuffer(
_device,
_pipelineLibrary,
_resourceDatabase,
_resourceAllocator,
_descriptorAllocator,
type);
}
public ISwapChain CreateSwapChain(SwapChainDesc desc)
{
ThrowIfDisposed();
return new D3D12SwapChain(_resourceDatabase, _descriptorAllocator, _device, desc, _renderSystem.MaxFrameLatency);
}
public Result RenderFrame(ICommandAllocator commandAllocator)
{
ThrowIfDisposed();
var r = Result.Success();
foreach (var renderer in _renderers)
{
r = renderer.Render(commandAllocator);
if (r.IsFailure)
{
break;
}
}
_resourceDatabase.EndFrame();
return r;
}
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var renderer in _renderers)
{
renderer.Dispose();
}
_resourceDatabase.ReleaseAllResourcesImmediately();
_resourceAllocator.Dispose();
_pipelineLibrary.Dispose();
_resourceDatabase.Dispose();
_descriptorAllocator.Dispose();
_shaderCompiler.Dispose();
_device.Dispose();
#if ENABLE_DEBUG
_debugLayer.Dispose();
#endif
_thisHandle.Free();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,353 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D_Alias;
using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12;
internal struct D3D12PipelineState : IDisposable
{
public D3DX12_MESH_SHADER_PIPELINE_STATE_DESC psoDesc;
public UniquePtr<ID3D12PipelineState> pso;
public Key64<ShaderVariant> shaderVariant;
public void Dispose()
{
pso.Dispose();
}
}
internal unsafe class D3D12PipelineLibrary : IPipelineLibrary
{
private readonly D3D12RenderDevice _device;
private readonly D3D12ResourceDatabase _resourceDatabase;
private UniquePtr<ID3D12PipelineLibrary1> _library;
private UniquePtr<ID3D12RootSignature> _defaultRootSignature;
private readonly Dictionary<Key128<GraphicsPipeline>, D3D12PipelineState> _pipelineCache;
public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get();
public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase)
{
_device = device;
_resourceDatabase = resourceDatabase;
_pipelineCache = new Dictionary<Key128<GraphicsPipeline>, D3D12PipelineState>();
CreateDefaultRootSignature().ThrowIfFailed();
}
// TODO: Maybe we don't need 4 root signature. We can use bindless for global, per-view, and per-object buffers as well.
private Result CreateDefaultRootSignature()
{
_defaultRootSignature = default;
// NOTE: Since we are targeting SM 6.6, we can use ResourceDescriptorHeap and SamplerDescriptorHeap directly without needing to set up viewGroup tables.
var rootParameters = stackalloc D3D12_ROOT_PARAMETER1[RootSignatureLayout.ROOT_PARAMETER_COUNT];
rootParameters[0] = new D3D12_ROOT_PARAMETER1
{
ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
Constants = new D3D12_ROOT_CONSTANTS
{
ShaderRegister = 0, // b0
RegisterSpace = 0, // space0
Num32BitValues = 4 // Global, View, Object, Material indices
}
};
var rootSignatureDesc = new D3D12_ROOT_SIGNATURE_DESC1
{
NumParameters = RootSignatureLayout.ROOT_PARAMETER_COUNT,
pParameters = rootParameters,
NumStaticSamplers = 0,
pStaticSamplers = null,
Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
| D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
| D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED
};
var versionedDesc = new D3D12_VERSIONED_ROOT_SIGNATURE_DESC
{
Version = D3D_ROOT_SIGNATURE_VERSION_1_1,
Desc_1_1 = rootSignatureDesc
};
using ComPtr<ID3DBlob> pSignature = default;
using ComPtr<ID3DBlob> pError = default;
var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, pSignature.GetAddressOf(), pError.GetAddressOf());
if (serializeResult.FAILED)
{
var errorMsg = pError.Get() != null ? Marshal.PtrToStringUTF8((nint)pError.Get()->GetBufferPointer()) : "Unknown error";
return Result.Failure($"Failed to serialize default root signature: {errorMsg}");
}
ID3D12RootSignature* pRootSignature = default;
ThrowIfFailed(_device.NativeDevice.Get()->CreateRootSignature(0, pSignature.Get()->GetBufferPointer(), pSignature.Get()->GetBufferSize(),
__uuidof(pRootSignature), (void**)&pRootSignature));
_defaultRootSignature.Attach(pRootSignature);
return Result.Success();
}
public void InitializeLibrary(string? filePath)
{
ID3D12PipelineLibrary1* pLibrary = default;
if (File.Exists(filePath))
{
var fileBytes = File.ReadAllBytes(filePath);
fixed (byte* pFileBytes = fileBytes)
{
ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof(pLibrary), (void**)&pLibrary));
}
}
else
{
ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineLibrary(null, 0, __uuidof(pLibrary), (void**)&pLibrary));
}
_library.Attach(pLibrary);
}
public void SaveLibraryToDisk(string filePath)
{
var dir = Path.GetDirectoryName(filePath);
if (!Directory.Exists(dir))
{
throw new InvalidOperationException($"Directory does not exist: {dir}");
}
var size = _library.Get()->GetSerializedSize();
using var buffer = new UnsafeArray<byte>((int)size, Allocator.Persistent); // We use persistent Heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries.
ThrowIfFailed(_library.Get()->Serialize(buffer.GetUnsafePtr(), size));
using var fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
fs.Write(buffer.AsSpan());
}
private static Result<CBufferInfo> ValidateReflectionData(ShaderReflectionData reflectionData)
{
if (reflectionData.ResourcesBindings.Count > RootSignatureLayout.ROOT_PARAMETER_COUNT)
{
return Result.Failure($"Shader uses more root parameters than supported ({RootSignatureLayout.ROOT_PARAMETER_COUNT}).");
}
if (reflectionData.ResourcesBindings.Count == 0)
{
return Result.Success(default(CBufferInfo));
}
var rootConstant = reflectionData.ResourcesBindings[0];
if (rootConstant.Type != ShaderInputType.ConstantBuffer)
{
return Result.Failure($"Root constant parameter must be a constant buffer.");
}
if (rootConstant.Size != sizeof(PushConstantsData))
{
return Result.Failure($"Root constant buffer size must be {sizeof(PushConstantsData)} bytes.");
}
var cbufferInfo = new CBufferInfo
{
Name = rootConstant.Name,
RegisterSlot = rootConstant.BindPoint,
RegisterSpace = rootConstant.Space,
SizeInBytes = rootConstant.Size,
Properties = rootConstant.Properties
};
return Result.Success(cbufferInfo);
}
private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ZTest ztest, ZWrite zwrite)
{
var depthEnabled = ztest != ZTest.Disabled;
var writeEnabled = zwrite == ZWrite.On;
var cmp = ztest.ToD3DCompare();
return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp);
}
public Result<Key128<GraphicsPipeline>> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled)
{
static Result<CBufferInfo> ValidatePassReflectionData(ref readonly GraphicsCompiledResult compiled)
{
var msr = ValidateReflectionData(compiled.msResult.reflectionData);
if (msr.IsFailure)
{
return Result.Failure("Validation of mesh shader reflection data failed: " + msr.Message);
}
var psr = ValidateReflectionData(compiled.psResult.reflectionData);
if (psr.IsFailure)
{
return Result.Failure("Validation of pixel shader reflection data failed: " + psr.Message);
}
if (msr.Value.Properties != null
&& msr.Value.SizeInBytes != psr.Value.SizeInBytes)
{
return Result.Failure("Mesh shader and pixel shader constant buffer layouts do not match.");
}
if (compiled.tsResult.IsCreated)
{
var tsr = ValidateReflectionData(compiled.tsResult.reflectionData);
if (tsr.IsFailure)
{
return Result.Failure("Validation of task shader reflection data failed: " + tsr.Message);
}
if (tsr.Value.Properties != null
&& tsr.Value.SizeInBytes != psr.Value.SizeInBytes)
{
return Result.Failure("Task shader and pixel shader constant buffer layouts do not match.");
}
}
// ts and ms may not use per material cbuffer at all, so we return the psr value.
return psr.Value;
}
if (descriptor.RtvFormats.Length > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT)
{
return Result.Failure($"RTV format count exceeds the maximum supported render target count of {D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT}.");
}
var passPipelineKey = new PassPipelineHash(descriptor.RtvFormats, descriptor.DsvFormat);
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(descriptor.VariantKey, descriptor.PipelineOption, passPipelineKey);
if (!_pipelineCache.ContainsKey(pipelineKey))
{
//var result = ValidatePassReflectionData(in compiled);
//if (result.IsFailure)
//{
// return Result.Failure(result.Message);
//}
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
{
pRootSignature = _defaultRootSignature.Get(),
MS = new D3D12_SHADER_BYTECODE(compiled.msResult.bytecode.GetUnsafePtr(), (nuint)compiled.msResult.bytecode.Count),
PS = new D3D12_SHADER_BYTECODE(compiled.psResult.bytecode.GetUnsafePtr(), (nuint)compiled.psResult.bytecode.Count),
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
SampleMask = UINT32_MAX,
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
NumRenderTargets = (uint)descriptor.RtvFormats.Length,
DSVFormat = descriptor.DsvFormat.ToDXGIFormat(),
DepthStencilState = BuildDepthStencil(descriptor.PipelineOption.ZTest, descriptor.PipelineOption.ZWrite),
NodeMask = 0,
Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
BlendState = descriptor.PipelineOption.Blend switch
{
Blend.Opaque => D3D12Utility.D3D12_BLEND_DESC_OPAQUE,
Blend.Alpha => D3D12Utility.D3D12_BLEND_DESC_ALPHA_BLEND,
Blend.Additive => D3D12Utility.D3D12_BLEND_DESC_ADDITIVE,
Blend.Multiply => D3D12Utility.D3D12_BLEND_DESC_MULTIPLY,
Blend.PremultipliedAlpha => D3D12Utility.D3D12_BLEND_DESC_PREMULTIPLIED,
_ => D3D12Utility.D3D12_BLEND_DESC_OPAQUE
},
RasterizerState = descriptor.PipelineOption.Cull switch
{
Cull.Off => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE,
Cull.Front => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_CLOCKWISE,
Cull.Back => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_COUNTER_CLOCKWISE,
_ => D3D12Utility.D3D12_RASTERIZER_DESC_CULL_NONE
},
};
if (compiled.tsResult.IsCreated)
{
desc.AS = new D3D12_SHADER_BYTECODE(compiled.tsResult.bytecode.GetUnsafePtr(), (nuint)compiled.tsResult.bytecode.Count);
}
for (var i = 0; i < descriptor.RtvFormats.Length; i++)
{
desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat();
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)((int)descriptor.PipelineOption.ColorMask & 0x0F);
}
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc);
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{
pPipelineStateSubobjectStream = &meshStream,
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_MESH_STATE_STREAM)
};
ID3D12PipelineState* pPipelineState = default;
var pKeyStr = stackalloc char[33]; // 32 for 128 bits key + 1 for null terminator
var keySpan = new Span<char>(pKeyStr, 33);
if (!pipelineKey.TryGetString(keySpan))
{
return Result.Failure("Failed to convert pipeline key to string.");
}
var hr = _library.Get()->LoadPipeline(pKeyStr, &streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState);
if (hr == E.E_INVALIDARG)
{
// Pipeline not found in the library, create a new one.
ThrowIfFailed(_device.NativeDevice.Get()->CreatePipelineState(&streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState));
ThrowIfFailed(_library.Get()->StorePipeline(pKeyStr, pPipelineState));
}
else
{
ThrowIfFailed(hr);
}
D3D12PipelineState pso = default;
pso.shaderVariant = descriptor.VariantKey;
pso.psoDesc = desc;
pso.pso.Attach(pPipelineState);
_pipelineCache[pipelineKey] = pso;
}
return pipelineKey;
}
public bool HasPipeline(Key128<GraphicsPipeline> key)
{
return _pipelineCache.ContainsKey(key);
}
public Result<SharedPtr<ID3D12PipelineState>, Error> GetGraphicsPSO(Key128<GraphicsPipeline> key)
{
if (_pipelineCache.TryGetValue(key, out var cacheEntry))
{
return cacheEntry.pso.Share();
}
return Error.NotFound;
}
public void Dispose()
{
foreach (var kvp in _pipelineCache)
{
kvp.Value.Dispose();
}
_pipelineCache.Clear();
_defaultRootSignature.Dispose();
_library.Dispose();
}
}

View File

@@ -0,0 +1,187 @@
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;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of the render device interface
/// </summary>
internal unsafe class D3D12RenderDevice : IRenderDevice
{
private UniquePtr<ID3D12Device14> _device;
private UniquePtr<IDXGIFactory7> _dxgiFactory;
private UniquePtr<IDXGIAdapter1> _adapter;
private readonly D3D12CommandQueue _graphicsQueue;
private readonly D3D12CommandQueue _computeQueue;
private readonly D3D12CommandQueue _copyQueue;
private bool _disposed;
public ICommandQueue GraphicsQueue => _graphicsQueue;
public ICommandQueue ComputeQueue => _computeQueue;
public ICommandQueue CopyQueue => _copyQueue;
public FeatureSupport FeatureSupport
{
get;
}
public SharedPtr<IDXGIFactory7> DXGIFactory => _dxgiFactory.Share();
public SharedPtr<ID3D12Device14> NativeDevice => _device.Share();
public SharedPtr<IDXGIAdapter1> Adapter => _adapter.Share();
public SharedPtr<ID3D12CommandQueue> NativeGraphicsQueue => _graphicsQueue.NativeQueue;
public SharedPtr<ID3D12CommandQueue> NativeComputeQueue => _computeQueue.NativeQueue;
public SharedPtr<ID3D12CommandQueue> NativeCopyQueue => _copyQueue.NativeQueue;
public D3D12RenderDevice()
{
IDXGIFactory7* pFactory = default;
#if DEBUG
ThrowIfFailed(CreateDXGIFactory2(TRUE, __uuidof(pFactory), (void**)&pFactory));
#else
ThrowIfFailed(CreateDXGIFactory2(FALSE, __uuidof(pFactory), (void**)&pFactory));
#endif
_dxgiFactory.Attach(pFactory);
InitializeDevice();
_graphicsQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Graphics);
_computeQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Compute);
_copyQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Copy);
FeatureSupport = GetFeatureSupport();
}
~D3D12RenderDevice()
{
Dispose();
}
private void InitializeDevice()
{
ID3D12Device14* pDevice = default;
IDXGIAdapter1* pAdapter = default;
for (uint adapterIndex = 0;
_dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof(pAdapter), (void**)&pAdapter).SUCCEEDED;
adapterIndex++)
{
DXGI_ADAPTER_DESC1 desc = default;
pAdapter->GetDesc1(&desc);
// Don't select the Basic Render Driver adapter.
if ((desc.Flags & (uint)DXGI_ADAPTER_FLAG_SOFTWARE) != 0)
{
goto NEXT_ITERATION;
}
if (D3D12CreateDevice((IUnknown*)pAdapter, D3D_FEATURE_LEVEL_12_0, __uuidof(pDevice), (void**)&pDevice).SUCCEEDED)
{
_adapter.Attach(pAdapter);
break;
}
NEXT_ITERATION:
pAdapter->Release();
}
if (pDevice == null)
{
pAdapter->Release(); // Dispose the last adapter we tried.
throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0");
}
_device.Attach(pDevice);
}
private FeatureSupport GetFeatureSupport()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var support = FeatureSupport.None;
D3D12_FEATURE_DATA_D3D12_OPTIONS options = default;
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)).SUCCEEDED)
{
if (options.ResourceBindingTier == D3D12_RESOURCE_BINDING_TIER_3)
{
support |= FeatureSupport.BindlessResources;
}
if (options.ResourceHeapTier == D3D12_RESOURCE_HEAP_TIER.D3D12_RESOURCE_HEAP_TIER_2)
{
support |= FeatureSupport.AliasBuffersAndTextures;
}
}
D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5 = default;
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options5, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS5)).SUCCEEDED)
{
if (options5.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED)
{
support |= FeatureSupport.RayTracing;
}
}
D3D12_FEATURE_DATA_D3D12_OPTIONS6 options6 = default;
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6, &options6, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS6)).SUCCEEDED)
{
if (options6.VariableShadingRateTier != D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED)
{
support |= FeatureSupport.VariableRateShading;
}
}
D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7 = default;
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &options7, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS7)).SUCCEEDED)
{
if (options7.MeshShaderTier != D3D12_MESH_SHADER_TIER_NOT_SUPPORTED)
{
support |= FeatureSupport.MeshShaders;
}
if (options7.SamplerFeedbackTier != D3D12_SAMPLER_FEEDBACK_TIER_NOT_SUPPORTED)
{
support |= FeatureSupport.SamplerFeedback;
}
}
D3D12_FEATURE_DATA_D3D12_OPTIONS21 options9 = default;
if (_device.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS21, &options9, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS8)).SUCCEEDED)
{
if (options9.WorkGraphsTier != D3D12_WORK_GRAPHS_TIER.D3D12_WORK_GRAPHS_TIER_NOT_SUPPORTED)
{
support |= FeatureSupport.WorkGraphs;
}
}
return support;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_graphicsQueue.Dispose();
_computeQueue.Dispose();
_copyQueue.Dispose();
_device.Dispose();
_dxgiFactory.Dispose();
_adapter.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,124 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of the renderer interface using RHI abstractions
/// </summary>
internal class D3D12Renderer : IRenderer
{
private readonly D3D12GraphicsEngine _graphicsEngine;
private readonly ICommandBuffer _commandBuffer;
private bool _disposed;
public IRenderOutput? RenderOutput
{
get; set;
}
public Func<RenderContext, Error>? RenderFunc
{
get; set;
}
public D3D12Renderer(D3D12GraphicsEngine graphicsEngine)
{
_graphicsEngine = graphicsEngine;
_commandBuffer = _graphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
}
~D3D12Renderer()
{
Dispose();
}
public Result Render(ICommandAllocator commandAllocator)
{
if (RenderFunc is null)
{
return Result.Success(); // No render function set, skip rendering.
}
if (RenderOutput is null)
{
return Result.Failure("Render target strategy is not set.");
}
var target = RenderOutput.GetRenderTarget();
if (target.IsInvalid)
{
return Result.Failure("Render target is invalid.");
}
_commandBuffer.Begin(commandAllocator);
RenderOutput.BeginRender(_commandBuffer);
var ctx = new RenderContext();
var error = RenderFunc.Invoke(ctx);
if (error != Error.None)
{
_commandBuffer.End();
return Result.Failure(error);
}
RenderOutput.EndRender(_commandBuffer);
var r = _commandBuffer.End();
if (r.IsFailure)
{
return r;
}
_graphicsEngine.Device.GraphicsQueue.Submit(_commandBuffer);
RenderOutput.Present();
return Result.Success();
}
//// TODO: A proper render graph integration.
//private Error RenderScene(Handle<Texture> target, ViewportDesc viewport, RectDesc rect)
//{
// // NOTE: Testing only.
// var ctx = new RenderingContext(_graphicsEngine, _commandBuffer);
// if (_frameIndex == 0)
// {
// _pass.Initialize(ref ctx);
// }
// //_commandBuffer.BeginRenderPass(rtDesc, depthDesc, false);
// _commandBuffer.SetViewport(viewport);
// _commandBuffer.SetScissorRect(rect);
// _renderGraph.Reset();
// var backBuffer = _renderGraph.ImportTexture(target, "Back Buffer");
// _pass.Build(_renderGraph, backBuffer);
// // Create view state from viewport
// var viewState = new ViewState((uint)viewport.Width, (uint)viewport.Height);
// // Compile with view state
// _renderGraph.Compile(in viewState);
// _renderGraph.Execute(_commandBuffer);
// //_commandBuffer.EndRenderPass();
// _frameIndex++;
// return Error.None;
//}
public void Dispose()
{
if (_disposed)
{
return;
}
_commandBuffer.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,881 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.D3D12MA_Alias;
using static TerraFX.Aliases.DXGI_Alias;
using static TerraFX.Interop.DirectX.D3D12MemAlloc;
namespace Ghost.Graphics.D3D12;
internal sealed unsafe partial class D3D12ResourceAllocator
{
// NOTE: _MAX_BYTES may not be accurate, we need to verify it with feature level checks.
private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckBufferSize(ulong sizeInBytes)
{
if (sizeInBytes > _MAX_BYTES)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckTexture2DSize(uint width, uint height)
{
if (width > _MAX_TEXTURE2D_DIMENSION || height > _MAX_TEXTURE2D_DIMENSION)
{
throw new InvalidOperationException($"ERROR: Texture size too large for DirectX 12 (width {width}, height {height})");
}
}
private static D3D12_SHADER_RESOURCE_VIEW_DESC CreateTextureSrvDesc(ID3D12Resource* pResource, uint mipLevels, uint arraySize, bool isCubeMap)
{
var resourceDesc = pResource->GetDesc();
var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
{
Format = resourceDesc.Format,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
switch (resourceDesc.Dimension)
{
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
if (resourceDesc.DepthOrArraySize > 1)
{
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY;
srvDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_SRV
{
MipLevels = mipLevels,
ArraySize = arraySize,
};
}
else
{
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
srvDesc.Texture1D = new D3D12_TEX1D_SRV
{
MipLevels = mipLevels,
};
}
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
if (resourceDesc.DepthOrArraySize > 1)
{
if (isCubeMap)
{
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
srvDesc.TextureCubeArray = new D3D12_TEXCUBE_ARRAY_SRV
{
MipLevels = mipLevels,
NumCubes = arraySize / 6,
};
}
else
{
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY : D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
srvDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_SRV
{
MipLevels = mipLevels,
ArraySize = arraySize,
};
}
}
else
{
if (isCubeMap)
{
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
srvDesc.TextureCube = new D3D12_TEXCUBE_SRV
{
MipLevels = mipLevels,
};
}
else
{
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMS : D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D = new D3D12_TEX2D_SRV
{
MipLevels = mipLevels,
};
}
}
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
srvDesc.Texture3D = new D3D12_TEX3D_SRV
{
MipLevels = mipLevels,
};
break;
default:
throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
}
return srvDesc;
}
private static D3D12_SHADER_RESOURCE_VIEW_DESC CreateBufferSrvDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
{
var resourceDesc = pResource->GetDesc();
var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
{
ViewDimension = D3D12_SRV_DIMENSION_BUFFER,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
if (isRaw)
{
srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
srvDesc.Buffer.StructureByteStride = 0;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
}
else // Assumes Structured
{
srvDesc.Format = resourceDesc.Format;
srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
srvDesc.Buffer.StructureByteStride = stride;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
}
return srvDesc;
}
private static D3D12_RENDER_TARGET_VIEW_DESC CreateRtvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
{
var resourceDesc = pResource->GetDesc();
var rtvDesc = new D3D12_RENDER_TARGET_VIEW_DESC();
switch (resourceDesc.Dimension)
{
case D3D12_RESOURCE_DIMENSION_BUFFER:
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_BUFFER;
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE1DARRAY : D3D12_RTV_DIMENSION_TEXTURE1D;
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
if (resourceDesc.SampleDesc.Count > 1)
{
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY : D3D12_RTV_DIMENSION_TEXTURE2DMS;
}
else
{
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DARRAY : D3D12_RTV_DIMENSION_TEXTURE2D;
}
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
break;
default:
throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
}
rtvDesc.Format = resourceDesc.Format;
var isArray =
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DARRAY ||
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
var arraySize = 1u;
if (isArray)
{
arraySize = resourceDesc.ArraySize() - firstArraySlice;
}
switch (rtvDesc.ViewDimension)
{
case D3D12_RTV_DIMENSION_BUFFER:
rtvDesc.Buffer.FirstElement = firstArraySlice;
rtvDesc.Buffer.NumElements = arraySize;
break;
case D3D12_RTV_DIMENSION_TEXTURE1D:
rtvDesc.Texture1D.MipSlice = mipSlice;
break;
case D3D12_RTV_DIMENSION_TEXTURE1DARRAY:
rtvDesc.Texture1DArray.MipSlice = mipSlice;
rtvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
rtvDesc.Texture1DArray.ArraySize = arraySize;
break;
case D3D12_RTV_DIMENSION_TEXTURE2D:
rtvDesc.Texture2D.MipSlice = mipSlice;
rtvDesc.Texture2D.PlaneSlice = planeSlice;
break;
case D3D12_RTV_DIMENSION_TEXTURE2DARRAY:
rtvDesc.Texture2DArray.MipSlice = mipSlice;
rtvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
rtvDesc.Texture2DArray.ArraySize = arraySize;
rtvDesc.Texture2DArray.PlaneSlice = planeSlice;
break;
case D3D12_RTV_DIMENSION_TEXTURE2DMS:
break;
case D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY:
rtvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
rtvDesc.Texture2DMSArray.ArraySize = arraySize;
break;
case D3D12_RTV_DIMENSION_TEXTURE3D:
rtvDesc.Texture3D.MipSlice = mipSlice;
rtvDesc.Texture3D.FirstWSlice = firstArraySlice;
rtvDesc.Texture3D.WSize = arraySize;
break;
default:
throw new ArgumentException($"Unsupported RTV dimension: {rtvDesc.ViewDimension}");
}
return rtvDesc;
}
private static D3D12_DEPTH_STENCIL_VIEW_DESC CreateDsvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, D3D12_DSV_FLAGS flags = D3D12_DSV_FLAG_NONE)
{
var resourceDesc = pResource->GetDesc();
var dsvDesc = new D3D12_DEPTH_STENCIL_VIEW_DESC
{
Flags = flags,
};
switch (resourceDesc.Dimension)
{
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE1DARRAY : D3D12_DSV_DIMENSION_TEXTURE1D;
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
if (resourceDesc.SampleDesc.Count > 1)
{
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY : D3D12_DSV_DIMENSION_TEXTURE2DMS;
}
else
{
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DARRAY : D3D12_DSV_DIMENSION_TEXTURE2D;
}
break;
}
dsvDesc.Format = resourceDesc.Format;
var isArray =
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DARRAY ||
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;
var arraySize = 1u;
if (isArray)
{
arraySize = resourceDesc.ArraySize() - firstArraySlice;
}
switch (dsvDesc.ViewDimension)
{
case D3D12_DSV_DIMENSION_TEXTURE1D:
dsvDesc.Texture1D.MipSlice = mipSlice;
break;
case D3D12_DSV_DIMENSION_TEXTURE1DARRAY:
dsvDesc.Texture1DArray.MipSlice = mipSlice;
dsvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
dsvDesc.Texture1DArray.ArraySize = arraySize;
break;
case D3D12_DSV_DIMENSION_TEXTURE2D:
dsvDesc.Texture2D.MipSlice = mipSlice;
break;
case D3D12_DSV_DIMENSION_TEXTURE2DARRAY:
dsvDesc.Texture2DArray.MipSlice = mipSlice;
dsvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
dsvDesc.Texture2DArray.ArraySize = arraySize;
break;
case D3D12_DSV_DIMENSION_TEXTURE2DMS:
break;
case D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY:
dsvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
dsvDesc.Texture2DMSArray.ArraySize = arraySize;
break;
default:
break;
}
return dsvDesc;
}
private static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateTextureUavDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
{
var resourceDesc = pResource->GetDesc();
var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
{
Format = resourceDesc.Format
};
switch (resourceDesc.Dimension)
{
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
if (resourceDesc.DepthOrArraySize > 1)
{
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1DARRAY;
uavDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_UAV
{
MipSlice = mipSlice,
FirstArraySlice = firstArraySlice,
ArraySize = resourceDesc.ArraySize() - firstArraySlice
};
}
else
{
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D;
uavDesc.Texture1D = new D3D12_TEX1D_UAV
{
MipSlice = mipSlice
};
}
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
if (resourceDesc.DepthOrArraySize > 1)
{
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
uavDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_UAV
{
MipSlice = mipSlice,
FirstArraySlice = firstArraySlice,
ArraySize = resourceDesc.ArraySize() - firstArraySlice,
PlaneSlice = planeSlice
};
}
else
{
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
uavDesc.Texture2D = new D3D12_TEX2D_UAV
{
MipSlice = mipSlice,
PlaneSlice = planeSlice
};
}
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
uavDesc.Texture3D = new D3D12_TEX3D_UAV
{
MipSlice = mipSlice,
FirstWSlice = firstArraySlice,
WSize = resourceDesc.Depth() - firstArraySlice
};
break;
case D3D12_RESOURCE_DIMENSION_BUFFER:
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
uavDesc.Buffer = new D3D12_BUFFER_UAV
{
FirstElement = 0,
NumElements = (uint)(resourceDesc.Width / 4), // Assuming R32_TYPELESS RAW
StructureByteStride = 0,
Flags = D3D12_BUFFER_UAV_FLAG_RAW
};
break;
default:
throw new ArgumentException($"Unsupported texture dimension for UAV: {resourceDesc.Dimension}");
}
return uavDesc;
}
private static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateBufferUavDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
{
var resourceDesc = pResource->GetDesc();
var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
{
ViewDimension = D3D12_UAV_DIMENSION_BUFFER,
};
if (isRaw)
{
uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
uavDesc.Buffer.FirstElement = 0;
uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
uavDesc.Buffer.StructureByteStride = 0;
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW;
}
else // Assumes Structured
{
uavDesc.Format = resourceDesc.Format;
uavDesc.Buffer.FirstElement = 0;
uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
uavDesc.Buffer.StructureByteStride = stride;
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_NONE;
}
return uavDesc;
}
private static D3D12_HEAP_TYPE ConvertMemoryType(ResourceMemoryType memoryType)
{
return memoryType switch
{
ResourceMemoryType.Default => D3D12_HEAP_TYPE_DEFAULT,
ResourceMemoryType.Upload => D3D12_HEAP_TYPE_UPLOAD,
ResourceMemoryType.Readback => D3D12_HEAP_TYPE_READBACK,
_ => throw new ArgumentException($"Unsupported memory type: {memoryType}")
};
}
}
internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
{
private const uint _UPLOAD_BATCH_SIZE = 64 * 1024 * 1024; // 64 MB
private const uint _MAX_RESOURCE_SIZE_TO_FIT_IN_UPLOAD_BATCH = 16 * 1024 * 1024; // 16 MB
private UniquePtr<D3D12MA_Allocator> _d3d12MA;
private readonly D3D12RenderDevice _device;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12PipelineLibrary _pipelineLibrary;
// TODO: We should use ring buffer pool in d3d12ma for upload buffer.
private readonly Handle<GraphicsBuffer> _uploadBatch;
private ulong _uploadBatchOffset;
private bool _disposed;
public D3D12ResourceAllocator(
D3D12RenderDevice device,
D3D12DescriptorAllocator descriptorAllocator,
D3D12ResourceDatabase resourceDatabase,
D3D12PipelineLibrary pipelineLibrary)
{
var desc = new D3D12MA_ALLOCATOR_DESC
{
pAdapter = (IDXGIAdapter*)device.Adapter.Get(),
pDevice = (ID3D12Device*)device.NativeDevice.Get(),
Flags = D3D12MA_ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED | D3D12MA_ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED,
};
D3D12MA_Allocator* pAllocator = default;
ThrowIfFailed(D3D12MA_CreateAllocator(&desc, &pAllocator));
_d3d12MA.Attach(pAllocator);
_device = device;
_descriptorAllocator = descriptorAllocator;
_resourceDatabase = resourceDatabase;
_pipelineLibrary = pipelineLibrary;
// Create an upload batch
var uploadDesc = new BufferDesc
{
Size = _UPLOAD_BATCH_SIZE,
Usage = BufferUsage.Upload,
MemoryType = ResourceMemoryType.Upload,
};
_uploadBatch = CreateBuffer(in uploadDesc, "D3D12ResourceAllocator_UploadBatch");
_uploadBatchOffset = 0;
}
~D3D12ResourceAllocator()
{
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Handle<GPUResource> TrackAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData barrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string name, bool isTemp)
{
var handle = _resourceDatabase.AddAllocation(allocation, barrierData, resourceDescriptor, desc, name);
return handle;
}
private HRESULT CreateResource(D3D12MA_ALLOCATION_DESC* pAllocationDesc, D3D12_RESOURCE_DESC* pResourceDesc, D3D12_RESOURCE_STATES initialState, CreationOptions options, Guid* riid, void** ppv)
{
HRESULT hr;
if (options.AllocationType == ResourceAllocationType.Suballocation)
{
// pAllocation should be the render graph Heap. ppvResource should be the out resource.
var result = _resourceDatabase.GetResourceRecord(options.Heap);
if (result.IsFailure)
{
return E.E_NOTFOUND;
}
hr = _d3d12MA.Get()->CreateAliasingResource(result.Value.resource.allocation.Get(), options.Offset, pResourceDesc, initialState, null, riid, ppv);
}
else
{
var iid_null = IID.IID_NULL;
hr = _d3d12MA.Get()->CreateResource(pAllocationDesc, pResourceDesc, initialState, null, (D3D12MA_Allocation**)ppv, &iid_null, null);
}
return hr;
}
public ResourceSizeInfo GetSizeInfo(ResourceDesc desc)
{
D3D12_RESOURCE_DESC d3d12Desc;
if (desc.Type == ResourceType.Texture)
{
d3d12Desc = desc.TextureDescription.ToD3D12ResourceDesc();
}
else
{
d3d12Desc = desc.BufferDescription.ToD3D12ResourceDesc();
}
var info = _device.NativeDevice.Get()->GetResourceAllocationInfo(0, 1, &d3d12Desc);
return new ResourceSizeInfo
{
Size = info.SizeInBytes,
Alignment = info.Alignment
};
}
public Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string name)
{
var allocDesc = new D3D12MA_ALLOCATION_DESC
{
HeapType = desc.HeapType switch
{
HeapType.Default => D3D12_HEAP_TYPE_DEFAULT,
HeapType.Upload => D3D12_HEAP_TYPE_UPLOAD,
HeapType.Readback => D3D12_HEAP_TYPE_READBACK,
_ => D3D12_HEAP_TYPE_DEFAULT
},
Flags = D3D12MA_ALLOCATION_FLAG_COMMITTED,
ExtraHeapFlags = desc.HeapFlags switch
{
HeapFlags.None => D3D12_HEAP_FLAG_NONE,
HeapFlags.AllowBuffers => D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,
HeapFlags.AllowTextures => D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES,
HeapFlags.AllowRTAndDS => D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES,
HeapFlags.AlowBufferAndTexture => D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES,
_ => D3D12_HEAP_FLAG_NONE
}
};
// SizeInBytes must be aligned to 64KB for committed resources
var allocInfo = new D3D12_RESOURCE_ALLOCATION_INFO
{
SizeInBytes = desc.Size + 65535 & ~65535u,
Alignment = desc.Alignment
};
D3D12MA_Allocation* alloc = default;
if (_d3d12MA.Get()->AllocateMemory(&allocDesc, &allocInfo, &alloc).FAILED)
{
return Handle<GPUResource>.Invalid;
}
var barrierData = new ResourceBarrierData
{
access = BarrierAccess.NoAccess,
layout = BarrierLayout.Common,
sync = BarrierSync.None
};
return TrackAllocation(alloc, barrierData, ResourceViewGroup.Invalid, default, name, false);
}
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, string name, CreationOptions options = default)
{
ObjectDisposedException.ThrowIf(_disposed, this);
CheckTexture2DSize(desc.Width, desc.Height);
var resourceDesc = desc.ToD3D12ResourceDesc();
var allocationDesc = new D3D12MA_ALLOCATION_DESC
{
HeapType = D3D12_HEAP_TYPE_DEFAULT,
Flags = D3D12MA_ALLOCATION_FLAG_NONE
};
var isSubAllocation = options.AllocationType == ResourceAllocationType.Suballocation;
D3D12MA_Allocation* pAllocation = default;
ID3D12Resource* pResource = default;
HRESULT hr;
if (isSubAllocation)
{
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, __uuidof(pResource), (void**)&pResource);
}
else
{
hr = CreateResource(&allocationDesc, &resourceDesc, D3D12_RESOURCE_STATE_COMMON, options, null, (void**)&pAllocation);
pResource = pAllocation->GetResource();
}
if (hr.FAILED)
{
#if DEBUG
ThrowIfFailed(hr);
#endif
return Handle<Texture>.Invalid;
}
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
{
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav();
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray;
var srvDesc = CreateTextureSrvDesc(pResource, resourceDesc.MipLevels, resourceDesc.DepthOrArraySize, isCubeMap);
_device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
}
if (desc.Usage.HasFlag(TextureUsage.RenderTarget))
{
resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV();
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv);
var rtvDesc = CreateRtvDesc(pResource);
_device.NativeDevice.Get()->CreateRenderTargetView(pResource, &rtvDesc, cpuHandle);
}
if (desc.Usage.HasFlag(TextureUsage.DepthStencil))
{
resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV();
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv);
var dsvDesc = CreateDsvDesc(pResource);
_device.NativeDevice.Get()->CreateDepthStencilView(pResource, &dsvDesc, cpuHandle);
}
if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess))
{
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav();
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
var uavDesc = CreateTextureUavDesc(pResource);
_device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
}
var barrierData = new ResourceBarrierData
{
access = BarrierAccess.NoAccess,
layout = BarrierLayout.Common,
sync = BarrierSync.None
};
Handle<GPUResource> resource;
if (isSubAllocation)
{
resource = _resourceDatabase.ImportExternalResource(pResource, barrierData, resourceDescriptor, name);
}
else
{
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Texture(desc), name, isTemp);
}
return resource.AsTexture();
}
public Handle<Texture> CreateRenderTarget(ref readonly RenderTargetDesc desc, string name, CreationOptions options = default)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var textureDesc = desc.ToTextureDescription();
return CreateTexture(in textureDesc, name, options);
}
public Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, string name, CreationOptions options = default)
{
ObjectDisposedException.ThrowIf(_disposed, this);
CheckBufferSize(desc.Size);
var resourceDesc = desc.ToD3D12ResourceDesc();
var isRaw = desc.Usage.HasFlag(BufferUsage.Raw);
var allocationDesc = new D3D12MA_ALLOCATION_DESC
{
HeapType = ConvertMemoryType(desc.MemoryType),
Flags = D3D12MA_ALLOCATION_FLAG_NONE,
};
var isSubAllocation = options.Heap.IsValid;
D3D12MA_Allocation* pAllocation = default;
ID3D12Resource* pResource = default;
HRESULT hr;
var initialState = desc.MemoryType switch
{
ResourceMemoryType.Default => D3D12_RESOURCE_STATE_COMMON,
ResourceMemoryType.Upload => D3D12_RESOURCE_STATE_GENERIC_READ,
ResourceMemoryType.Readback => D3D12_RESOURCE_STATE_COPY_DEST,
_ => D3D12_RESOURCE_STATE_COMMON
};
if (isSubAllocation)
{
hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, __uuidof(pResource), (void**)&pResource);
}
else
{
hr = CreateResource(&allocationDesc, &resourceDesc, initialState, options, null, (void**)&pAllocation);
pResource = pAllocation->GetResource();
}
if (hr.FAILED)
{
#if DEBUG
ThrowIfFailed(hr);
#endif
return Handle<GraphicsBuffer>.Invalid;
}
var isTemp = options.AllocationType == ResourceAllocationType.Temporary;
var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(BufferUsage.Constant))
{
resourceDescriptor.cbv = _descriptorAllocator.AllocateCbvSrvUav();
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.cbv);
var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC
{
BufferLocation = pResource->GetGPUVirtualAddress(),
SizeInBytes = (uint)resourceDesc.Width,
};
_device.NativeDevice.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle);
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.cbv);
}
if (desc.Usage.HasFlag(BufferUsage.ShaderResource))
{
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav();
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
var srvDesc = CreateBufferSrvDesc(pResource, desc.Stride, isRaw);
_device.NativeDevice.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
}
if (desc.Usage.HasFlag(BufferUsage.UnorderedAccess))
{
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav();
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
var uavDesc = CreateBufferUavDesc(pResource, desc.Stride, isRaw);
_device.NativeDevice.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
}
var barrierData = new ResourceBarrierData
{
access = BarrierAccess.NoAccess,
layout = BarrierLayout.Undefined,
sync = BarrierSync.None
};
Handle<GPUResource> resource;
if (isSubAllocation)
{
resource = _resourceDatabase.ImportExternalResource(pResource, barrierData, resourceDescriptor, name);
}
else
{
resource = TrackAllocation(pAllocation, barrierData, resourceDescriptor, ResourceDesc.Buffer(desc), name, isTemp);
}
return resource.AsGraphicsBuffer();
}
public Handle<GraphicsBuffer> CreateTempUploadBuffer(ulong sizeInBytes, out ulong offset)
{
if (sizeInBytes <= _MAX_RESOURCE_SIZE_TO_FIT_IN_UPLOAD_BATCH && sizeInBytes + _uploadBatchOffset <= _UPLOAD_BATCH_SIZE)
{
offset = _uploadBatchOffset;
_uploadBatchOffset += sizeInBytes;
return _uploadBatch;
}
else
{
var bufferDesc = new BufferDesc
{
Size = (uint)sizeInBytes,
Usage = BufferUsage.Upload,
MemoryType = ResourceMemoryType.Upload,
};
var options = new CreationOptions
{
AllocationType = ResourceAllocationType.Temporary,
};
offset = 0;
var handle = CreateBuffer(in bufferDesc, "TempUploadBuffer", options);
_resourceDatabase.ScheduleReleaseResource(handle.AsResource());
return handle;
}
}
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_resourceDatabase.TryGetSampler(in desc, out var id))
{
return id;
}
var samplerDesc = new D3D12_SAMPLER_DESC
{
Filter = desc.FilterMode.ToD3D12Filter(),
AddressU = desc.AddressU.ToD3D12TextureAddressMode(),
AddressV = desc.AddressV.ToD3D12TextureAddressMode(),
AddressW = desc.AddressW.ToD3D12TextureAddressMode(),
MipLODBias = desc.MipLODBias,
MaxAnisotropy = desc.MaxAnisotropy,
ComparisonFunc = desc.ComparisonFunc.ToD3D12ComparisonFunc(),
MinLOD = desc.MinLOD,
MaxLOD = desc.MaxLOD,
};
var samplerDescriptor = _descriptorAllocator.AllocateSampler();
var cpuHandle = _descriptorAllocator.GetCpuHandle(samplerDescriptor);
_device.NativeDevice.Get()->CreateSampler(&samplerDesc, cpuHandle);
_descriptorAllocator.CopyToShaderVisible(samplerDescriptor);
return _resourceDatabase.CreateSampler(in desc, samplerDescriptor.Value);
}
public void Dispose()
{
if (_disposed)
{
return;
}
_resourceDatabase.ReleaseResourceImmediately(_uploadBatch.AsResource());
_d3d12MA.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,388 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.D3D12;
// TODO: Thread safety
internal class D3D12ResourceDatabase : IResourceDatabase
{
internal unsafe record struct ResourceRecord
{
[StructLayout(LayoutKind.Explicit)]
public struct ResourceUnion
{
[FieldOffset(0)]
public UniquePtr<D3D12MA_Allocation> allocation;
[FieldOffset(0)]
public UniquePtr<ID3D12Resource> resource;
public ResourceUnion(D3D12MA_Allocation* allocation)
{
this.allocation = allocation;
}
public ResourceUnion(ID3D12Resource* resource)
{
this.resource = resource;
}
}
public ResourceDesc desc;
public ResourceViewGroup viewGroup;
public ResourceUnion resource;
public ResourceBarrierData barrierData;
public readonly bool isExternal;
public readonly bool Allocated => isExternal ? resource.resource.Get() != null : resource.allocation.Get() != null;
public readonly SharedPtr<ID3D12Resource> ResourcePtr => isExternal ? resource.resource.Get() : resource.allocation.Get()->GetResource();
public ResourceRecord(D3D12MA_Allocation* allocation, ResourceBarrierData barrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
{
this.resource = new ResourceUnion(allocation);
this.isExternal = false;
this.viewGroup = resourceDescriptor;
this.barrierData = barrierData;
this.desc = desc;
}
public ResourceRecord(ID3D12Resource* resource, ResourceBarrierData barrierData, ResourceViewGroup viewGroup)
{
this.resource = new ResourceUnion(resource);
this.isExternal = true;
this.viewGroup = viewGroup;
this.barrierData = barrierData;
this.desc = resource->GetDesc().ToResourceDesc();
}
public readonly uint Release(D3D12DescriptorAllocator descriptorAllocator)
{
var refCount = 0u;
if (Allocated)
{
if (isExternal)
{
refCount = resource.resource.Get()->Release();
}
else
{
refCount = resource.allocation.Get()->Release();
}
}
descriptorAllocator.Release(viewGroup);
return refCount;
}
}
private readonly struct ReleaseEntry
{
public readonly ResourceRecord record;
public readonly uint fenceValue;
public ReleaseEntry(ResourceRecord record, uint fenceValue)
{
this.record = record;
this.fenceValue = fenceValue;
}
}
private readonly IFenceSynchronizer _fenceSynchronizer;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private UnsafeSlotMap<ResourceRecord> _resources;
private UnsafeHashMap<SamplerDesc, Identifier<Sampler>> _samplers;
#if DEBUG || GHOST_EDITOR
private readonly Dictionary<Handle<GPUResource>, string> _resourceName;
#endif
private UnsafeQueue<ReleaseEntry> _releaseQueue;
private bool _disposed;
public D3D12ResourceDatabase(IFenceSynchronizer fenceSynchronizer, D3D12DescriptorAllocator descriptorAllocator)
{
_fenceSynchronizer = fenceSynchronizer;
_descriptorAllocator = descriptorAllocator;
_resources = new UnsafeSlotMap<ResourceRecord>(64, Allocator.Persistent, AllocationOption.Clear);
_samplers = new UnsafeHashMap<SamplerDesc, Identifier<Sampler>>(32, Allocator.Persistent);
#if DEBUG || GHOST_EDITOR
_resourceName = new Dictionary<Handle<GPUResource>, string>(64);
#endif
_releaseQueue = new UnsafeQueue<ReleaseEntry>(32, Allocator.Persistent);
}
~D3D12ResourceDatabase()
{
Dispose();
}
internal unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceBarrierData initialBarrierData, ResourceViewGroup viewGroup, string? name = null)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (pResource == null)
{
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif
return Handle<GPUResource>.Invalid;
}
var id = _resources.Add(new ResourceRecord(pResource, initialBarrierData, viewGroup), out var generation);
var handle = new Handle<GPUResource>(id, generation);
#if DEBUG || GHOST_EDITOR
if (!string.IsNullOrEmpty(name))
{
pResource->SetName(name);
_resourceName[handle] = name;
}
#endif
return handle;
}
public unsafe Handle<GPUResource> AddAllocation(D3D12MA_Allocation* allocation, ResourceBarrierData initialBarrierData, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (allocation == null)
{
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif
return Handle<GPUResource>.Invalid;
}
var id = _resources.Add(new ResourceRecord(allocation, initialBarrierData, resourceDescriptor, desc), out var generation);
var handle = new Handle<GPUResource>(id, generation);
#if DEBUG || GHOST_EDITOR
if (!string.IsNullOrEmpty(name))
{
allocation->SetName(name);
var pResource = allocation->GetResource();
if (pResource != null)
{
pResource->SetName(name);
}
_resourceName[handle] = name;
}
#endif
return handle;
}
public bool HasResource(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _resources.Contains(handle.ID, handle.Generation);
}
public RefResult<ResourceRecord, Error> GetResourceRecord(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist)
{
return Error.NotFound;
}
return RefResult<ResourceRecord, Error>.Success(ref info);
}
public SharedPtr<ID3D12Resource> GetResource(Handle<GPUResource> handle)
{
var r = GetResourceRecord(handle);
if (r.IsFailure)
{
return null;
}
return r.Value.ResourcePtr;
}
public Result<ResourceBarrierData, Error> GetResourceBarrierData(Handle<GPUResource> handle)
{
var r = GetResourceRecord(handle);
if (r.IsFailure)
{
return r.Error;
}
return r.Value.barrierData;
}
public Error SetResourceBarrierData(Handle<GPUResource> handle, ResourceBarrierData data)
{
var r = GetResourceRecord(handle);
if (r.IsFailure)
{
return r.Error;
}
r.Value.barrierData = data;
return Error.None;
}
public Result<ResourceDesc, Error> GetResourceDescription(Handle<GPUResource> handle)
{
var r = GetResourceRecord(handle);
if (r.IsFailure)
{
return r.Error;
}
return r.Value.desc;
}
public uint GetBindlessIndex(Handle<GPUResource> handle, BindlessAccess access = BindlessAccess.ShaderResource)
{
var r = GetResourceRecord(handle);
if (r.IsFailure || !r.Value.Allocated)
{
return ~0u;
}
return access switch
{
BindlessAccess.ShaderResource => (uint)r.Value.viewGroup.srv.Value,
BindlessAccess.ConstantBuffer => (uint)r.Value.viewGroup.cbv.Value,
BindlessAccess.UnorderedAccess => (uint)r.Value.viewGroup.uav.Value,
_ => ~0u,
};
}
public string? GetResourceName(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
#if DEBUG || GHOST_EDITOR
if (_resourceName.TryGetValue(handle, out var name))
{
return name;
}
#endif
return null;
}
public void ScheduleReleaseResource(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_resources.TryGetElementAt(handle.ID, handle.Generation, out var record))
{
return;
}
var entry = new ReleaseEntry(record, _fenceSynchronizer.CPUFenceValue);
_releaseQueue.Enqueue(entry);
_resources.Remove(handle.ID, handle.Generation);
#if DEBUG || GHOST_EDITOR
_resourceName.Remove(handle, out var name);
#endif
}
public void ReleaseResourceImmediately(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.ID, handle.Generation, out var exist);
if (!exist || !info.Allocated)
{
return;
}
info.Release(_descriptorAllocator);
_resources.Remove(handle.ID, handle.Generation);
}
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc, int id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_samplers.ContainsKey(desc))
{
throw new InvalidOperationException("Sampler already exists.");
}
var identifier = new Identifier<Sampler>(id);
_samplers.Add(desc, identifier);
return identifier;
}
public bool TryGetSampler(ref readonly SamplerDesc desc, out Identifier<Sampler> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _samplers.TryGetValue(desc, out id);
}
public void ReleaseSampler(Identifier<Sampler> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
// NOTE: We almost never release samplers individually, because they are cheap and can be reused.
// Ideally we would release all samplers at once when disposing the ResourceDatabase.
_descriptorAllocator.Release(new Identifier<SamplerDescriptor>(id.Value));
}
public void EndFrame()
{
ObjectDisposedException.ThrowIf(_disposed, this);
while (_releaseQueue.Count > 0)
{
var toRelease = _releaseQueue.Peek();
if (toRelease.fenceValue > _fenceSynchronizer.GPUFenceValue)
{
break;
}
_releaseQueue.Dequeue();
toRelease.record.Release(_descriptorAllocator);
}
}
internal void ReleaseAllResourcesImmediately()
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (ref var record in _resources)
{
record.Release(_descriptorAllocator);
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_resources.Dispose();
_samplers.Dispose();
_releaseQueue.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,261 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.DXGI_Alias;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of swap chain interface
/// </summary>
internal unsafe class D3D12SwapChain : ISwapChain
{
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12RenderDevice _renderDevice;
private UniquePtr<IDXGISwapChain4> _swapChain;
private UnsafeArray<Handle<Texture>> _backBuffers;
private readonly object? _compositionSurface;
private bool _disposed;
public uint Width
{
get; private set;
}
public uint Height
{
get; private set;
}
public float ScaleX
{
get; private set;
}
public float ScaleY
{
get; private set;
}
public D3D12SwapChain(D3D12ResourceDatabase resourceDatabase, D3D12DescriptorAllocator descriptorAllocator, D3D12RenderDevice device, SwapChainDesc desc, uint bufferCount)
{
Debug.Assert(bufferCount >= 2);
_resourceDatabase = resourceDatabase;
_descriptorAllocator = descriptorAllocator;
_renderDevice = device;
_backBuffers = new UnsafeArray<Handle<Texture>>((int)bufferCount, Allocator.Persistent);
Width = desc.Width;
Height = desc.Height;
var pSwapChian = CreateSwapChain(desc, bufferCount);
_swapChain.Attach(pSwapChian);
CreateBackBuffers();
SetScale(desc.ScaleX, desc.ScaleY);
if (desc.Target.Type == SwapChainTargetType.Composition)
_compositionSurface = desc.Target.CompositionSurface;
}
~D3D12SwapChain()
{
Dispose();
}
private IDXGISwapChain4* CreateSwapChain(SwapChainDesc desc, uint bufferCount)
{
var swapChainDesc = new DXGI_SWAP_CHAIN_DESC1
{
Width = desc.Width,
Height = desc.Height,
Format = desc.Format.ToDXGIFormat(),
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount = bufferCount,
Scaling = DXGI_SCALING_STRETCH,
SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
AlphaMode = DXGI_ALPHA_MODE_IGNORE,
Flags = (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING,
Stereo = false,
};
IDXGISwapChain1* pTempSwapChain = default;
var pFactory = _renderDevice.DXGIFactory.Get();
var pCommandQueue = _renderDevice.NativeGraphicsQueue.Get();
switch (desc.Target.Type)
{
case SwapChainTargetType.Composition:
ThrowIfFailed(pFactory->CreateSwapChainForComposition((IUnknown*)pCommandQueue, &swapChainDesc, null, &pTempSwapChain));
if (desc.Target.CompositionSurface != null)
{
using var compositionSurface = ISwapChainPanelNative.FromSwapChainPanel(desc.Target.CompositionSurface);
compositionSurface.SetSwapChain((nint)pTempSwapChain);
}
break;
case SwapChainTargetType.WindowHandle:
var swapChainFullscreenDesc = new DXGI_SWAP_CHAIN_FULLSCREEN_DESC
{
Windowed = true,
};
pFactory->CreateSwapChainForHwnd(
(IUnknown*)pCommandQueue,
new HWND(desc.Target.WindowHandle.ToPointer()),
&swapChainDesc,
&swapChainFullscreenDesc,
null,
&pTempSwapChain);
break;
default:
throw new ArgumentException("Unsupported swap chain target type.");
}
IDXGISwapChain4* pSwapChain = default;
pTempSwapChain->QueryInterface(__uuidof(pSwapChain), (void**)&pSwapChain);
pTempSwapChain->Release();
return pSwapChain;
}
private void CreateBackBuffers()
{
for (uint i = 0; i < _backBuffers.Count; i++)
{
ID3D12Resource* pBackBuffer = default;
ThrowIfFailed(_swapChain.Get()->GetBuffer(i, __uuidof(pBackBuffer), (void**)&pBackBuffer));
pBackBuffer->SetName($"SwapChain_BackBuffer_{i}");
var rtv = _descriptorAllocator.AllocateRTV();
var cpuHandle = _descriptorAllocator.GetCpuHandle(rtv);
_renderDevice.NativeDevice.Get()->CreateRenderTargetView(pBackBuffer, null, cpuHandle);
var view = ResourceViewGroup.Invalid with
{
rtv = rtv
};
var barrierData = new ResourceBarrierData
{
access = BarrierAccess.NoAccess,
layout = BarrierLayout.Present,
sync = BarrierSync.None,
};
var handle = _resourceDatabase.ImportExternalResource(pBackBuffer, barrierData, view);
_backBuffers[i] = handle.AsTexture();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Handle<Texture> GetCurrentBackBuffer()
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<Handle<Texture>> GetBackBuffers()
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _backBuffers.AsSpan();
}
public void Present(bool vsync = true)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var presentFlags = 0u;
var syncInterval = vsync ? 1u : 0u;
ThrowIfFailed(_swapChain.Get()->Present(syncInterval, presentFlags));
}
public void Resize(uint width, uint height)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (Width == width && Height == height)
{
return;
}
// Release old back buffers and render targets
for (var i = 0; i < _backBuffers.Count; i++)
{
_resourceDatabase.ReleaseResourceImmediately(_backBuffers[i].AsResource());
}
ThrowIfFailed(_swapChain.Get()->ResizeBuffers((uint)_backBuffers.Count, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING));
Width = width;
Height = height;
CreateBackBuffers();
}
public void SetScale(float scaleX, float scaleY)
{
var inverseScaleX = 1.0f / scaleX;
var inverseScaleY = 1.0f / scaleY;
var inverseScaleMatrix = new DXGI_MATRIX_3X2_F
{
_11 = inverseScaleX, // Scale X
_22 = inverseScaleY, // Scale Y
_12 = 0.0f,
_21 = 0.0f,
_31 = 0.0f, // Offset X
_32 = 0.0f // Offset Y
};
_swapChain.Get()->SetMatrixTransform(&inverseScaleMatrix);
ScaleX = scaleX;
ScaleY = scaleY;
}
public void Dispose()
{
if (_disposed)
{
return;
}
if (_compositionSurface != null)
{
using var compositionSurface = ISwapChainPanelNative.FromSwapChainPanel(_compositionSurface);
compositionSurface.SetSwapChain(0);
}
for (var i = 0; i < _backBuffers.Count; i++)
{
_resourceDatabase.ScheduleReleaseResource(_backBuffers[i].AsResource());
}
_backBuffers.Dispose();
_swapChain.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,516 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Utilities;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Interop.DirectX.DXC;
namespace Ghost.Graphics.Core;
internal sealed partial class DxcShaderCompiler
{
private static string GetProfileString(ShaderStage stage, CompilerTier version)
{
return (stage, version) switch
{
(ShaderStage.TaskShader, CompilerTier.Tier0) => "as_6_6",
(ShaderStage.PixelShader, CompilerTier.Tier0) => "ps_6_6",
(ShaderStage.MeshShader, CompilerTier.Tier0) => "ms_6_6",
(ShaderStage.ComputeShader, CompilerTier.Tier0) => "cs_6_6",
(ShaderStage.TaskShader, CompilerTier.Tier1) => "as_6_7",
(ShaderStage.PixelShader, CompilerTier.Tier1) => "ps_6_7",
(ShaderStage.MeshShader, CompilerTier.Tier1) => "ms_6_7",
(ShaderStage.ComputeShader, CompilerTier.Tier1) => "cs_6_7",
(ShaderStage.TaskShader, CompilerTier.Tier2) => "as_6_8",
(ShaderStage.PixelShader, CompilerTier.Tier2) => "ps_6_8",
(ShaderStage.MeshShader, CompilerTier.Tier2) => "ms_6_8",
(ShaderStage.ComputeShader, CompilerTier.Tier2) => "cs_6_8",
_ => throw new ArgumentOutOfRangeException(nameof(stage), "Unsupported shader stage or compiler version")
};
}
private static string GetOptimizeLevelString(CompilerOptimizeLevel level)
{
return level switch
{
CompilerOptimizeLevel.O0 => DXC_ARG_OPTIMIZATION_LEVEL0,
CompilerOptimizeLevel.O1 => DXC_ARG_OPTIMIZATION_LEVEL1,
CompilerOptimizeLevel.O2 => DXC_ARG_OPTIMIZATION_LEVEL2,
CompilerOptimizeLevel.O3 => DXC_ARG_OPTIMIZATION_LEVEL3,
_ => throw new ArgumentOutOfRangeException(nameof(level), "Unsupported optimization level")
};
}
private static List<string> GetCompilerArguments(ref readonly ShaderCompilationConfig config)
{
var argsArray = new List<string>
{
"-T", GetProfileString(config.stage, config.tier), // Target profile (ms_6_6, ps_6_6)
"-E", config.entryPoint, // Entry point
"-HV", "2021", // HLSL version 2021
"-enable-16bit-types", // Enable 16-bit types
GetOptimizeLevelString(config.optimizeLevel), // Optimization level
};
foreach (var define in config.defines)
{
argsArray.Add("-D");
argsArray.Add(define);
}
if (!config.options.HasFlag(CompilerOption.KeepDebugInfo))
{
argsArray.Add("-Qstrip_debug");
}
if (!config.options.HasFlag(CompilerOption.KeepReflections))
{
argsArray.Add("-Qstrip_reflect");
}
if (config.options.HasFlag(CompilerOption.WarnAsError))
{
argsArray.Add(DXC_ARG_WARNINGS_ARE_ERRORS);
}
if (config.options.HasFlag(CompilerOption.SpirvCrossCompile))
{
argsArray.Add("-spirv");
}
return argsArray;
}
private static Result<string, Error> GetFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode)
{
string shaderCode;
if (shaderPath == "hlsl_block")
{
if (string.IsNullOrEmpty(injectedCode))
{
return Error.InvalidArgument;
}
shaderCode = string.Empty;
}
else
{
if (!File.Exists(shaderPath))
{
return Error.NotFound;
}
shaderCode = File.ReadAllText(shaderPath);
}
var sb = new System.Text.StringBuilder();
foreach (var includePath in includes)
{
sb.AppendLine($"#include \"{includePath}\"");
}
if (!string.IsNullOrEmpty(injectedCode))
{
sb.AppendLine($"#line 1 \"hlsl_block\"");
sb.AppendLine(injectedCode);
}
if (!string.IsNullOrEmpty(shaderCode))
{
sb.AppendLine($"#line 1 \"{shaderPath}\"");
sb.AppendLine(shaderCode);
}
return sb.ToString();
}
private static ShaderInputType ToInputType(D3D_SHADER_INPUT_TYPE type)
{
return type switch
{
D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER => ShaderInputType.ConstantBuffer,
D3D_SHADER_INPUT_TYPE.D3D_SIT_TBUFFER => ShaderInputType.Texture,
D3D_SHADER_INPUT_TYPE.D3D_SIT_TEXTURE => ShaderInputType.Texture,
D3D_SHADER_INPUT_TYPE.D3D_SIT_SAMPLER => ShaderInputType.Sampler,
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWTYPED => ShaderInputType.UAV,
D3D_SHADER_INPUT_TYPE.D3D_SIT_STRUCTURED => ShaderInputType.StructuredBuffer,
D3D_SHADER_INPUT_TYPE.D3D_SIT_BYTEADDRESS => ShaderInputType.ByteAddressBuffer,
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWSTRUCTURED => ShaderInputType.RWStructuredBuffer,
D3D_SHADER_INPUT_TYPE.D3D_SIT_UAV_RWBYTEADDRESS => ShaderInputType.RWByteAddressBuffer,
_ => throw new ArgumentOutOfRangeException(nameof(type), "Unsupported shader input type")
};
}
}
internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
{
private UniquePtr<IDxcCompiler3> _compiler;
private UniquePtr<IDxcUtils> _utils;
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later.
// TODO: This should be shader variant specific cache instead of pass specific.
private readonly Dictionary<Key64<ShaderVariant>, GraphicsCompiledResult> _compiledResults;
private bool _disposed;
public DxcShaderCompiler()
{
// Initialize DXC _compiler.Get() and _utils.Get()
var dxccID = CLSID.CLSID_DxcCompiler;
var dxcuID = CLSID.CLSID_DxcUtils;
IDxcCompiler3* pCompiler = default;
IDxcUtils* pUtils = default;
ThrowIfFailed(DxcCreateInstance(&dxccID, __uuidof(pCompiler), (void**)&pCompiler));
ThrowIfFailed(DxcCreateInstance(&dxcuID, __uuidof(pUtils), (void**)&pUtils));
_compiler.Attach(pCompiler);
_utils.Attach(pUtils);
_compiledResults = new Dictionary<Key64<ShaderVariant>, GraphicsCompiledResult>();
}
~DxcShaderCompiler()
{
Dispose();
}
private Result<ShaderReflectionData> PerformDXCReflection(IDxcBlob* pReflectionBlob)
{
ID3D12ShaderReflection* pReflection = default;
try
{
// Create DXC _utils.Get() to parse reflection data
var dxcuID = CLSID.CLSID_DxcUtils;
// Create reflection interface from blob
var reflectionBuffer = new DxcBuffer
{
Ptr = pReflectionBlob->GetBufferPointer(),
Size = pReflectionBlob->GetBufferSize(),
Encoding = DXC_CP_ACP
};
ThrowIfFailed(_utils.Get()->CreateReflection(&reflectionBuffer, __uuidof(pReflection), (void**)&pReflection));
D3D12_SHADER_DESC shaderDesc;
ThrowIfFailed(pReflection->GetDesc(&shaderDesc));
var reflectionData = new ShaderReflectionData();
for (uint i = 0; i < shaderDesc.BoundResources; i++)
{
D3D12_SHADER_INPUT_BIND_DESC bindDesc;
ThrowIfFailed(pReflection->GetResourceBindingDesc(i, &bindDesc));
var resourceName = Marshal.PtrToStringUTF8((IntPtr)bindDesc.Name);
if (resourceName == null)
{
return Result.Failure("Failed to get resource name from reflection data.");
}
var info = new ResourceBindingInfo
{
Name = resourceName,
Type = ToInputType(bindDesc.Type),
BindPoint = bindDesc.BindPoint,
BindCount = bindDesc.BindCount,
Space = bindDesc.Space
};
switch (bindDesc.Type)
{
case D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER:
{
var cbuffer = pReflection->GetConstantBufferByName(bindDesc.Name);
D3D12_SHADER_BUFFER_DESC cbufferDesc;
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
var variables = new List<CBufferPropertyInfo>((int)cbufferDesc.Variables);
// Now we iterate all variables for *every* cbuffer, not just b3
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
D3D12_SHADER_VARIABLE_DESC varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringUTF8((IntPtr)varDesc.Name);
if (variableName == null)
{
continue;
}
variables.Add(new CBufferPropertyInfo
{
Name = variableName,
StartOffset = varDesc.StartOffset,
Size = varDesc.Size
});
}
info.Size = cbufferDesc.Size;
info.Properties = variables;
break;
}
// NOTE: Currently we do not support resource bindings yet, everything access through bindless heaps.
}
reflectionData.ResourcesBindings.Add(info);
}
return reflectionData;
}
finally
{
pReflection->Release();
}
}
public Result<ShaderCompileResult> Compile(ref readonly ShaderCompilationConfig config, Allocator allocator)
{
ObjectDisposedException.ThrowIf(_disposed, this);
using ComPtr<IDxcIncludeHandler> includeHandler = default;
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()));
var finalShaderCodeResult = GetFinalShaderCode(config.shaderPath, config.includes, config.injectedCode);
if (finalShaderCodeResult.IsFailure)
{
return Result.Failure(finalShaderCodeResult.Error);
}
var finalShaderCode = finalShaderCodeResult.Value;
fixed (byte* pCode = System.Text.Encoding.UTF8.GetBytes(finalShaderCode))
{
var sizeInBytes = System.Text.Encoding.UTF8.GetByteCount(finalShaderCode);
ThrowIfFailed(_utils.Get()->CreateBlobFromPinned(pCode, (uint)sizeInBytes, DXC_CP_UTF8, sourceBlob.GetAddressOf()));
}
var argsArray = GetCompilerArguments(in config);
var argPtrs = stackalloc char*[argsArray.Count];
for (var i = 0; i < argsArray.Count; i++)
{
argPtrs[i] = (char*)Marshal.StringToHGlobalUni(argsArray[i]);
}
using ComPtr<IDxcResult> result = default;
try
{
// Compile shader
var buffer = new DxcBuffer
{
Ptr = sourceBlob.Get()->GetBufferPointer(),
Size = sourceBlob.Get()->GetBufferSize(),
Encoding = DXC_CP_UTF8
};
ThrowIfFailed(_compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler, __uuidof(result.Get()), (void**)&result));
// Check compilation result
HRESULT hrStatus;
result.Get()->GetStatus(&hrStatus);
if (hrStatus.FAILED)
{
// Get error messages
IDxcBlobEncoding* pErrorBlob = default;
result.Get()->GetErrorBuffer(&pErrorBlob);
if (pErrorBlob != null)
{
var errorMessage = Marshal.PtrToStringUTF8((IntPtr)pErrorBlob->GetBufferPointer());
pErrorBlob->Release();
return Result.Failure($"DXC shader compilation failed:\n{errorMessage}");
}
else
{
return Result.Failure("DXC shader compilation failed with unknown error.");
}
}
// Get compiled bytecode
using ComPtr<IDxcBlob> bytecodeBlob = default;
ThrowIfFailed(result.Get()->GetResult(bytecodeBlob.GetAddressOf()));
ShaderReflectionData reflectionData = default;
if (config.options.HasFlag(CompilerOption.KeepReflections))
{
using ComPtr<IDxcBlob> reflection = default;
if (result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof(reflection.Get()), (void**)&reflection, null).SUCCEEDED)
{
reflectionData = PerformDXCReflection(reflection).GetValueOrDefault();
}
}
var bytecodeSize = bytecodeBlob.Get()->GetBufferSize();
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, allocator);
NativeMemory.Copy(bytecodeBlob.Get()->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize);
return new ShaderCompileResult
{
bytecode = bytecode,
reflectionData = reflectionData,
};
}
finally
{
for (var i = 0; i < argsArray.Count; i++)
{
Marshal.FreeHGlobal((nint)argPtrs[i]);
}
}
}
// TODO: This should be shader variant specific compile instead of pass specific.
// TODO: Build final shader code in memory before compiling.
public Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var defineCountInDescriptor = descriptor.defines?.Length ?? 0;
var fullDefines = new string[defineCountInDescriptor + additionalConfig.defines.Length];
descriptor.defines?.CopyTo(fullDefines);
additionalConfig.defines.CopyTo(fullDefines.AsSpan(defineCountInDescriptor));
ShaderCompileResult tsResult = default;
var tsEntry = descriptor.taskShader;
if (tsEntry.IsCreated)
{
var config = new ShaderCompilationConfig
{
defines = fullDefines.AsSpan(),
includes = descriptor.includes.AsSpan(),
shaderPath = tsEntry.shader,
entryPoint = tsEntry.entry,
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
stage = ShaderStage.TaskShader,
tier = additionalConfig.tier,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
};
var result = Compile(ref config, Allocator.Persistent);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
tsResult = result.Value;
}
ShaderCompileResult msResult;
var msEntry = descriptor.meshShader;
if (msEntry.IsCreated)
{
var config = new ShaderCompilationConfig
{
defines = fullDefines.AsSpan(),
includes = descriptor.includes.AsSpan(),
shaderPath = msEntry.shader,
entryPoint = msEntry.entry,
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
stage = ShaderStage.MeshShader,
tier = additionalConfig.tier,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
};
var result = Compile(ref config, Allocator.Persistent);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
msResult = result.Value;
}
else
{
return Result.Failure("Mesh shader expected.");
}
ShaderCompileResult psResult;
var psEntry = descriptor.pixelShader;
if (psEntry.IsCreated)
{
var config = new ShaderCompilationConfig
{
defines = fullDefines.AsSpan(),
includes = descriptor.includes.AsSpan(),
shaderPath = psEntry.shader,
entryPoint = psEntry.entry,
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
stage = ShaderStage.PixelShader,
tier = additionalConfig.tier,
optimizeLevel = additionalConfig.optimizeLevel,
options = additionalConfig.options,
};
var result = Compile(ref config, Allocator.Persistent);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
psResult = result.Value;
}
else
{
return Result.Failure("Pixel shader expected.");
}
var compiled = new GraphicsCompiledResult
{
tsResult = tsResult,
msResult = msResult,
psResult = psResult,
};
_compiledResults[key] = compiled;
return compiled;
}
public Result<GraphicsCompiledResult, Error> LoadCompiledCache(Key64<ShaderVariant> key)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_compiledResults.TryGetValue(key, out var compiledResult))
{
return compiledResult;
}
return Error.NotFound;
}
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var kvp in _compiledResults)
{
kvp.Value.Dispose();
}
_compiler.Dispose();
_utils.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishAot>true</PublishAot>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<Content Include="runtimes\win-x64\native\dxcompiler.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="runtimes\win-x64\native\dxil.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
<ProjectReference Include="..\Ghost.Graphics.RHI\Ghost.Graphics.RHI.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,73 @@
using System.Runtime.InteropServices;
namespace Ghost.Graphics.D3D12;
public readonly unsafe struct ISwapChainPanelNative : ISwapChainPanelNative.Interface, IDisposable
{
[ComImport]
[Guid("63aad0b8-7c24-40ff-85a8-640d944cc325")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface Interface
{
// IUnknown: QueryInterface, AddRef, Release
void QueryInterface(in Guid riid, out IntPtr ppvObject);
uint AddRef();
uint Release();
// SetSwapChain is the 4th slot in the vtable (0-based index 3)
int SetSwapChain(IntPtr swapChainPtr);
}
private readonly IntPtr _nativePtr;
public ISwapChainPanelNative(IntPtr nativePtr)
{
_nativePtr = nativePtr;
}
public void QueryInterface(in Guid riid, out nint ppvObject)
{
throw new NotImplementedException();
}
public uint AddRef()
{
throw new NotImplementedException();
}
public uint Release()
{
throw new NotImplementedException();
}
public static ISwapChainPanelNative FromSwapChainPanel(object panel)
{
// Get the IUnknown/IInspectable pointer
var unknown = Marshal.GetIUnknownForObject(panel);
try
{
// Query for ISwapChainPanelNative
var iid = typeof(Interface).GUID;
var result = Marshal.QueryInterface(unknown, in iid, out var nativePtr);
if (result < 0)
{
Marshal.ThrowExceptionForHR(result);
}
return new ISwapChainPanelNative(nativePtr);
}
finally
{
Marshal.Release(unknown);
}
}
public int SetSwapChain(IntPtr swapChainPtr)
{
var vtbl = *(void***)_nativePtr;
var setSwapChainFn = (delegate* unmanaged<IntPtr, IntPtr, int>)vtbl[3];
return setSwapChainFn(_nativePtr, swapChainPtr);
}
public void Dispose() => Marshal.Release(_nativePtr);
}

View File

@@ -0,0 +1,28 @@
using Ghost.Core;
namespace Ghost.Graphics.D3D12;
internal readonly struct RTVDescriptor;
internal readonly struct DSVDescriptor;
internal readonly struct CbvSrvUavDescriptor;
internal readonly struct SamplerDescriptor;
internal struct ResourceViewGroup
{
public Identifier<RTVDescriptor> rtv;
public Identifier<DSVDescriptor> dsv;
public Identifier<CbvSrvUavDescriptor> srv;
public Identifier<CbvSrvUavDescriptor> cbv;
public Identifier<CbvSrvUavDescriptor> uav;
public Identifier<SamplerDescriptor> sampler;
public static ResourceViewGroup Invalid => new()
{
rtv = Identifier<RTVDescriptor>.Invalid,
dsv = Identifier<DSVDescriptor>.Invalid,
srv = Identifier<CbvSrvUavDescriptor>.Invalid,
cbv = Identifier<CbvSrvUavDescriptor>.Invalid,
uav = Identifier<CbvSrvUavDescriptor>.Invalid,
sampler = Identifier<SamplerDescriptor>.Invalid,
};
}

View File

@@ -0,0 +1,27 @@
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using static TerraFX.Interop.DirectX.D3D12_INPUT_CLASSIFICATION;
using static TerraFX.Interop.DirectX.DXGI_FORMAT;
namespace Ghost.Graphics.D3D12.Utilities;
internal static unsafe class D3D12PipelineResource
{
private static readonly D3D12_INPUT_ELEMENT_DESC[] s_inputElementDescs = [
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Position.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Normal.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Tangent.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Uv.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.Color.GetUnsafePtr(), SemanticIndex = 0u, Format = DXGI_FORMAT_R32G32B32A32_FLOAT, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
];
public const DXGI_FORMAT SWAP_CHAIN_BACK_BUFFER_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
public static D3D12_INPUT_LAYOUT_DESC InputLayoutDescription => new()
{
pInputElementDescs = (D3D12_INPUT_ELEMENT_DESC*)Unsafe.AsPointer(ref s_inputElementDescs[0]),
NumElements = (uint)s_inputElementDescs.Length
};
}

View File

@@ -0,0 +1,604 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.DXGI_Alias;
namespace Ghost.Graphics.D3D12.Utilities;
internal static unsafe class D3D12Utility
{
[EditorBrowsable(EditorBrowsableState.Never)]
public readonly ref struct IID_PPV
{
public readonly Guid* iid;
public readonly void** ppv;
public IID_PPV(Guid* iid, void** ppv)
{
this.iid = iid;
this.ppv = ppv;
}
public void Deconstruct(out Guid* iid, out void** ppv)
{
iid = this.iid;
ppv = this.ppv;
}
}
public static Guid* IID_NULL
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IID.IID_NULL));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IID_PPV IID_PPV_ARGS<T>(ComPtr<T>* comPtr)
where T : unmanaged, IUnknown.Interface
{
return new IID_PPV(__uuidof<T>(), (void**)comPtr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Attach<T>(ref this UniquePtr<T> uPtr, T* other)
where T : unmanaged, IUnknown.Interface
{
var ptr = uPtr.Get();
if (ptr != null)
{
var refCount = ptr->Release();
Debug.Assert((refCount != 0) || (ptr != other));
}
uPtr = new UniquePtr<T>(other);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Dispose<T>(ref this UniquePtr<T> uPtr)
where T : unmanaged, IUnknown.Interface
{
var ptr = uPtr.Detach();
if (ptr != null)
{
var refCount = ptr->Release();
Debug.Assert((refCount != 0));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Result ToResult(this HRESULT hr, [CallerArgumentExpression(nameof(hr))] string? op = null)
{
if (hr.SUCCEEDED)
{
return Result.Success();
}
return Result.Failure($"{op} failed with code {hr}");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void** ReleaseAndGetVoidAddressOf<T>(ref this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
return (void**)comPtr.ReleaseAndGetAddressOf();
}
public static void SetName<T>(ref this T obj, ReadOnlySpan<char> name)
where T : unmanaged, ID3D12Object.Interface
{
if (name.IsEmpty)
{
return;
}
fixed (char* pName = name)
{
obj.SetName(pName);
}
}
public static void SetName(ref this D3D12MA_Allocation obj, ReadOnlySpan<char> name)
{
if (name.IsEmpty)
{
return;
}
fixed (char* pName = name)
{
obj.SetName(pName);
}
}
public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension)
{
return dimension switch
{
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D,
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D,
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D,
_ => throw new NotSupportedException($"Resource dimension {dimension} is not supported."),
};
}
public static DXGI_FORMAT ToDXGIFormat(this TextureFormat format)
{
return format switch
{
TextureFormat.Unknown => DXGI_FORMAT_UNKNOWN,
TextureFormat.R8G8B8A8_UNorm => DXGI_FORMAT_R8G8B8A8_UNORM,
TextureFormat.B8G8R8A8_UNorm => DXGI_FORMAT_B8G8R8A8_UNORM,
TextureFormat.R16G16B16A16_Float => DXGI_FORMAT_R16G16B16A16_FLOAT,
TextureFormat.R32G32B32A32_Float => DXGI_FORMAT_R32G32B32A32_FLOAT,
TextureFormat.D24_UNorm_S8_UInt => DXGI_FORMAT_D24_UNORM_S8_UINT,
TextureFormat.D32_Float => DXGI_FORMAT_D32_FLOAT,
_ => throw new NotSupportedException($"Texture format {format} is not supported."),
};
}
public static TextureFormat ToTextureFormat(this DXGI_FORMAT format)
{
return format switch
{
DXGI_FORMAT_R8G8B8A8_UNORM => TextureFormat.R8G8B8A8_UNorm,
DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat.B8G8R8A8_UNorm,
DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat.R16G16B16A16_Float,
DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_Float,
DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat.D24_UNorm_S8_UInt,
DXGI_FORMAT_D32_FLOAT => TextureFormat.D32_Float,
_ => throw new NotSupportedException($"DXGI format {format} is not supported.")
};
}
public static D3D12_RESOURCE_STATES ToD3D12States(this ResourceState state)
{
var d3dStates = D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COMMON;
if (state.HasFlag(ResourceState.VertexAndConstantBuffer))
{
d3dStates |= D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
}
if (state.HasFlag(ResourceState.IndexBuffer))
{
d3dStates |= D3D12_RESOURCE_STATE_INDEX_BUFFER;
}
if (state.HasFlag(ResourceState.RenderTarget))
{
d3dStates |= D3D12_RESOURCE_STATE_RENDER_TARGET;
}
if (state.HasFlag(ResourceState.UnorderedAccess))
{
d3dStates |= D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
}
if (state.HasFlag(ResourceState.DepthWrite))
{
d3dStates |= D3D12_RESOURCE_STATE_DEPTH_WRITE;
}
if (state.HasFlag(ResourceState.DepthRead))
{
d3dStates |= D3D12_RESOURCE_STATE_DEPTH_READ;
}
if (state.HasFlag(ResourceState.PixelShaderResource))
{
d3dStates |= D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
}
if (state.HasFlag(ResourceState.CopyDest))
{
d3dStates |= D3D12_RESOURCE_STATE_COPY_DEST;
}
if (state.HasFlag(ResourceState.CopySource))
{
d3dStates |= D3D12_RESOURCE_STATE_COPY_SOURCE;
}
if (state.HasFlag(ResourceState.GenericRead))
{
d3dStates |= D3D12_RESOURCE_STATE_GENERIC_READ;
}
if (state.HasFlag(ResourceState.IndirectArgument))
{
d3dStates |= D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT;
}
if (state.HasFlag(ResourceState.NonPixelShaderResource))
{
d3dStates |= D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
}
return d3dStates;
}
public static ResourceState ToResourceState(this D3D12_RESOURCE_STATES states)
{
var resourceState = ResourceState.Common;
if (states.HasFlag(D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER))
{
resourceState |= ResourceState.VertexAndConstantBuffer;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_INDEX_BUFFER))
{
resourceState |= ResourceState.IndexBuffer;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_RENDER_TARGET))
{
resourceState |= ResourceState.RenderTarget;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_UNORDERED_ACCESS))
{
resourceState |= ResourceState.UnorderedAccess;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_DEPTH_WRITE))
{
resourceState |= ResourceState.DepthWrite;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_DEPTH_READ))
{
resourceState |= ResourceState.DepthRead;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
{
resourceState |= ResourceState.PixelShaderResource;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_COPY_DEST))
{
resourceState |= ResourceState.CopyDest;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_COPY_SOURCE))
{
resourceState |= ResourceState.CopySource;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_GENERIC_READ))
{
resourceState |= ResourceState.GenericRead;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT))
{
resourceState |= ResourceState.IndirectArgument;
}
if (states.HasFlag(D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE))
{
resourceState |= ResourceState.NonPixelShaderResource;
}
return resourceState;
}
public static D3D12_FILTER ToD3D12Filter(this TextureFilterMode filterMode)
{
return filterMode switch
{
TextureFilterMode.Point => D3D12_FILTER_MIN_MAG_MIP_POINT,
TextureFilterMode.Bilinear => D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT,
TextureFilterMode.Trilinear => D3D12_FILTER_MIN_MAG_MIP_LINEAR,
TextureFilterMode.Anisotropic => D3D12_FILTER_ANISOTROPIC,
_ => throw new ArgumentException($"Unknown texture filter mode: {filterMode}")
};
}
public static D3D12_TEXTURE_ADDRESS_MODE ToD3D12TextureAddressMode(this TextureAddressMode addressMode)
{
return addressMode switch
{
TextureAddressMode.Repeat => D3D12_TEXTURE_ADDRESS_MODE_WRAP,
TextureAddressMode.Mirror => D3D12_TEXTURE_ADDRESS_MODE_MIRROR,
TextureAddressMode.Clamp => D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
TextureAddressMode.Border => D3D12_TEXTURE_ADDRESS_MODE_BORDER,
TextureAddressMode.MirrorOnce => D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE,
_ => throw new ArgumentException($"Unknown texture address mode: {addressMode}")
};
}
public static D3D12_COMPARISON_FUNC ToD3D12ComparisonFunc(this ComparisonFunction func)
{
return func switch
{
ComparisonFunction.Never => D3D12_COMPARISON_FUNC_NEVER,
ComparisonFunction.Less => D3D12_COMPARISON_FUNC_LESS,
ComparisonFunction.Equal => D3D12_COMPARISON_FUNC_EQUAL,
ComparisonFunction.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
ComparisonFunction.Greater => D3D12_COMPARISON_FUNC_GREATER,
ComparisonFunction.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
ComparisonFunction.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
ComparisonFunction.Always => D3D12_COMPARISON_FUNC_ALWAYS,
_ => throw new ArgumentException($"Unknown comparison function: {func}")
};
}
public static D3D12_COMPARISON_FUNC ToD3DCompare(this ZTest z)
{
return z switch
{
ZTest.Disabled => D3D12_COMPARISON_FUNC_NEVER,
ZTest.Less => D3D12_COMPARISON_FUNC_LESS,
ZTest.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
ZTest.Equal => D3D12_COMPARISON_FUNC_EQUAL,
ZTest.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
ZTest.Greater => D3D12_COMPARISON_FUNC_GREATER,
ZTest.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
ZTest.Always => D3D12_COMPARISON_FUNC_ALWAYS,
_ => D3D12_COMPARISON_FUNC_LESS_EQUAL
};
}
public static D3D12_COMMAND_LIST_TYPE ToCommandListType(CommandBufferType type)
{
return type switch
{
CommandBufferType.Graphics => D3D12_COMMAND_LIST_TYPE_DIRECT,
CommandBufferType.Compute => D3D12_COMMAND_LIST_TYPE_COMPUTE,
CommandBufferType.Copy => D3D12_COMMAND_LIST_TYPE_COPY,
_ => throw new ArgumentException($"Unknown command buffer type: {type}")
};
}
public static D3D12_RESOURCE_FLAGS ToD3D12ResourceFlag(this TextureUsage usage)
{
var flags = D3D12_RESOURCE_FLAG_NONE;
if (usage.HasFlag(TextureUsage.RenderTarget))
{
flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
}
if (usage.HasFlag(TextureUsage.DepthStencil))
{
flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
}
if (usage.HasFlag(TextureUsage.UnorderedAccess))
{
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
}
return flags;
}
public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in TextureDesc desc)
{
var dxgiFormat = desc.Format.ToDXGIFormat();
var maxDimension = Math.Max(desc.Width, Math.Max(desc.Height, desc.Slice));
var mipLevels = desc.MipLevels == 0
? (ushort)(1 + Math.Floor(Math.Log2(maxDimension)))
: (ushort)desc.MipLevels;
var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
return desc.Dimension switch
{
TextureDimension.Texture2D => D3D12_RESOURCE_DESC.Tex2D(
dxgiFormat,
desc.Width,
desc.Height,
mipLevels: mipLevels,
flags: resourceFlags),
TextureDimension.Texture3D => D3D12_RESOURCE_DESC.Tex3D(
dxgiFormat,
desc.Width,
desc.Height,
(ushort)desc.Slice,
flags: resourceFlags),
TextureDimension.TextureCube => D3D12_RESOURCE_DESC.Tex2D(
dxgiFormat,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: 6,
flags: resourceFlags),
TextureDimension.Texture2DArray => D3D12_RESOURCE_DESC.Tex2D(
dxgiFormat,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: (ushort)desc.Slice,
flags: resourceFlags),
TextureDimension.TextureCubeArray => D3D12_RESOURCE_DESC.Tex2D(
dxgiFormat,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: (ushort)(desc.Slice * 6),
flags: resourceFlags),
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
};
}
public static D3D12_RESOURCE_FLAGS ToD3D12ResourceFlag(this BufferUsage usage)
{
var flags = D3D12_RESOURCE_FLAG_NONE;
if (usage.HasFlag(BufferUsage.Raw) || usage.HasFlag(BufferUsage.UnorderedAccess))
{
flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
}
return flags;
}
public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in BufferDesc desc)
{
var alignedSize = desc.Size;
if (desc.Usage.HasFlag(BufferUsage.Constant))
{
// D3D12 CBV size must be 256-byte aligned
alignedSize = (uint)(desc.Size + 255) & ~255u;
}
var resourceFlags = desc.Usage.ToD3D12ResourceFlag();
return D3D12_RESOURCE_DESC.Buffer(alignedSize, resourceFlags);
}
public static ResourceDesc ToResourceDesc(this D3D12_RESOURCE_DESC desc)
{
if (desc.Dimension == D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_BUFFER)
{
return ResourceDesc.Buffer(new BufferDesc
{
Size = (uint)desc.Width,
Stride = 0,
Usage = BufferUsage.None,
MemoryType = ResourceMemoryType.Default
});
}
else
{
return ResourceDesc.Texture(new TextureDesc
{
Width = (uint)desc.Width,
Height = desc.Height,
Slice = desc.DepthOrArraySize,
Format = desc.Format.ToTextureFormat(),
Dimension = desc.Dimension.ToTextureDimension(),
MipLevels = desc.MipLevels,
Usage = TextureUsage.None,
});
}
}
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CREATE(
D3D12_FILL_MODE fillMode,
D3D12_CULL_MODE cullMode,
bool frontCounterClockwise = false,
int depthBias = D3D12_DEFAULT_DEPTH_BIAS,
float depthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
float slopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
bool depthClipEnable = true,
bool multisampleEnable = true,
bool antialiasedLineEnable = false,
uint forcedSampleCount = 0,
D3D12_CONSERVATIVE_RASTERIZATION_MODE conservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF)
{
return new D3D12_RASTERIZER_DESC
{
FillMode = fillMode,
CullMode = cullMode,
FrontCounterClockwise = frontCounterClockwise ? TRUE : FALSE,
DepthBias = depthBias,
DepthBiasClamp = depthBiasClamp,
SlopeScaledDepthBias = slopeScaledDepthBias,
DepthClipEnable = depthClipEnable ? TRUE : FALSE,
MultisampleEnable = multisampleEnable ? TRUE : FALSE,
AntialiasedLineEnable = antialiasedLineEnable ? TRUE : FALSE,
ForcedSampleCount = forcedSampleCount,
ConservativeRaster = conservativeRaster
};
}
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CULL_NONE => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE);
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CULL_CLOCKWISE => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_FRONT);
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_CULL_COUNTER_CLOCKWISE => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_BACK);
public static D3D12_RASTERIZER_DESC D3D12_RASTERIZER_DESC_WIREFRAME => D3D12_RASTERIZER_DESC_CREATE(D3D12_FILL_MODE_WIREFRAME, D3D12_CULL_MODE_NONE);
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_CREATE(D3D12_BLEND srcBlend, D3D12_BLEND destBlend)
{
var blendDesc = new D3D12_BLEND_DESC
{
AlphaToCoverageEnable = false,
IndependentBlendEnable = false
};
for (var i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
{
blendDesc.RenderTarget[i].BlendEnable = srcBlend != D3D12_BLEND_ONE || destBlend != D3D12_BLEND_ZERO;
blendDesc.RenderTarget[i].LogicOp = D3D12_LOGIC_OP_NOOP;
blendDesc.RenderTarget[i].SrcBlend = srcBlend;
blendDesc.RenderTarget[i].DestBlend = destBlend;
blendDesc.RenderTarget[i].BlendOp = D3D12_BLEND_OP_ADD;
blendDesc.RenderTarget[i].SrcBlendAlpha = srcBlend;
blendDesc.RenderTarget[i].DestBlendAlpha = destBlend;
blendDesc.RenderTarget[i].BlendOpAlpha = D3D12_BLEND_OP_ADD;
blendDesc.RenderTarget[i].RenderTargetWriteMask = (byte)D3D12_COLOR_WRITE_ENABLE_ALL;
}
return blendDesc;
}
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_OPAQUE => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_ONE, D3D12_BLEND_ZERO);
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_ALPHA_BLEND => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_ADDITIVE => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_ONE);
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_MULTIPLY => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_DEST_COLOR, D3D12_BLEND_ZERO);
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_PREMULTIPLIED => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_BLEND_DESC D3D12_BLEND_DESC_NON_PREMULTIPLIED => D3D12_BLEND_DESC_CREATE(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_CREATE(
bool depthEnable,
bool depthWriteEnable,
D3D12_COMPARISON_FUNC depthFunc,
bool stencilEnable = false,
byte stencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK,
byte stencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK,
D3D12_STENCIL_OP frontStencilFailOp = D3D12_STENCIL_OP_KEEP,
D3D12_STENCIL_OP frontStencilDepthFailOp = D3D12_STENCIL_OP_KEEP,
D3D12_STENCIL_OP frontStencilPassOp = D3D12_STENCIL_OP_KEEP,
D3D12_COMPARISON_FUNC frontStencilFunc = D3D12_COMPARISON_FUNC_ALWAYS,
D3D12_STENCIL_OP backStencilFailOp = D3D12_STENCIL_OP_KEEP,
D3D12_STENCIL_OP backStencilDepthFailOp = D3D12_STENCIL_OP_KEEP,
D3D12_STENCIL_OP backStencilPassOp = D3D12_STENCIL_OP_KEEP,
D3D12_COMPARISON_FUNC backStencilFunc = D3D12_COMPARISON_FUNC_ALWAYS)
{
return new D3D12_DEPTH_STENCIL_DESC
{
DepthEnable = depthEnable,
DepthWriteMask = depthWriteEnable ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO,
DepthFunc = depthFunc,
StencilEnable = stencilEnable,
StencilReadMask = stencilReadMask,
StencilWriteMask = stencilWriteMask,
FrontFace = D3D12_DEPTH_STENCILOP_DESC_CREATE(frontStencilFailOp, frontStencilDepthFailOp, frontStencilPassOp, frontStencilFunc),
BackFace = D3D12_DEPTH_STENCILOP_DESC_CREATE(backStencilFailOp, backStencilDepthFailOp, backStencilPassOp, backStencilFunc)
};
}
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_NONE => D3D12_DEPTH_STENCIL_DESC_CREATE(false, false, D3D12_COMPARISON_FUNC_LESS_EQUAL);
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_READ => D3D12_DEPTH_STENCIL_DESC_CREATE(true, false, D3D12_COMPARISON_FUNC_LESS_EQUAL);
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_REVERSE_Z => D3D12_DEPTH_STENCIL_DESC_CREATE(true, true, D3D12_COMPARISON_FUNC_GREATER_EQUAL);
public static D3D12_DEPTH_STENCIL_DESC D3D12_DEPTH_STENCIL_DESC_READ_REVERSE_Z => D3D12_DEPTH_STENCIL_DESC_CREATE(true, false, D3D12_COMPARISON_FUNC_GREATER_EQUAL);
public static D3D12_DEPTH_STENCILOP_DESC D3D12_DEPTH_STENCILOP_DESC_CREATE(
D3D12_STENCIL_OP stencilFailOp,
D3D12_STENCIL_OP stencilDepthFailOp,
D3D12_STENCIL_OP stencilPassOp,
D3D12_COMPARISON_FUNC stencilFunc)
{
return new D3D12_DEPTH_STENCILOP_DESC
{
StencilFailOp = stencilFailOp,
StencilDepthFailOp = stencilDepthFailOp,
StencilPassOp = stencilPassOp,
StencilFunc = stencilFunc
};
}
public static D3D12_DEPTH_STENCILOP_DESC D3D12_DEPTH_STENCILOP_DESC_DEFAULT => D3D12_DEPTH_STENCILOP_DESC_CREATE(D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS);
}

File diff suppressed because it is too large Load Diff