Refactoring Rendering backend

This commit is contained in:
2025-10-05 16:26:37 +09:00
parent a39f377533
commit 01a850ff94
99 changed files with 5056 additions and 5136 deletions

View File

@@ -1,79 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
public unsafe class CommandList
{
private readonly ConstPtr<ID3D12GraphicsCommandList10> _commandList;
internal ConstPtr<ID3D12GraphicsCommandList10> NativeCommandList => _commandList;
public CommandList(ID3D12GraphicsCommandList10* commandList)
{
_commandList = commandList;
}
internal void BarrierTransition(GraphicsResource resource, ResourceStates beforeState, ResourceStates afterState)
{
_commandList.Ptr->ResourceBarrierTransition(resource.NativeResource.Ptr, beforeState, afterState);
}
internal void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress)
{
_commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress);
}
/// <summary>
/// Draws a mesh using fully bindless rendering with SM 6.6 support.
/// This method does not use the Input Assembler stage and instead relies on
/// vertex and index buffer access through bindless descriptors in the shader.
/// </summary>
/// <param name="mesh">The mesh to draw</param>
/// <param name="material">The bindless material to use</param>
public void DrawMesh(MeshClass mesh, MaterialClass material)
{
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
material.Bind(this);
// For fully bindless rendering, we don't use the Input Assembler stage
// Instead, we use instanced drawing where each "instance" represents a triangle
// The shader will use SV_InstanceID to index into the index buffer and then into the vertex buffer
_commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
// Draw without vertex/index buffers - use instanced drawing
// Each instance represents a triangle (3 vertices)
var triangleCount = mesh.IndexCount / 3;
_commandList.Ptr->DrawInstanced(3, triangleCount, 0, 0);
}
public void SetRenderTarget(RenderTexture? color, RenderTexture? depth)
{
var rtvHandle = color?.RenderTargetView?.CpuHandle;
var rtvHandleValue = rtvHandle ?? default;
var pRtvHandle = rtvHandle.HasValue ? &rtvHandleValue : null;
var dsvHandle = depth?.RenderTargetView?.CpuHandle;
var dsvHandleValue = dsvHandle ?? default;
var pDsvHandle = dsvHandle.HasValue ? &dsvHandleValue : null;
_commandList.Ptr->OMSetRenderTargets(1, pRtvHandle, false, pDsvHandle);
}
public void ClearRenderTarget(RenderTexture renderTarget, Color128 color)
{
renderTarget.ClearColor(this, color);
}
public void ClearDepthStencil(RenderTexture depthStencil, ClearFlags flags, float depth = 1.0f, byte stencil = 0)
{
depthStencil.ClearDepthStencil(this, flags, depth, stencil);
}
public void CopyResource(GraphicsResource dstResource, uint dstOffset, GraphicsResource srcResource, uint srcOffset, uint size)
{
_commandList.Ptr->CopyBufferRegion(dstResource.NativeResource, dstOffset, srcResource.NativeResource, srcOffset, size);
}
}

View File

@@ -1,139 +0,0 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of buffer interface using resource handles
/// </summary>
internal unsafe class D3D12Buffer : IBuffer
{
private readonly BufferHandle _handle;
private readonly D3D12ResourceAllocator? _allocator;
private readonly ComPtr<ID3D12Resource> _externalResource; // For externally managed resources
private ResourceState _currentState;
private void* _mappedPtr;
private bool _disposed;
public BufferUsage Usage
{
get;
}
public MemoryType MemoryType
{
get;
}
public string Name
{
get => field;
set
{
field = value;
NativeResource->SetName(field);
}
} = string.Empty;
public ulong Size
{
get;
}
public BufferHandle Handle => _handle;
public ResourceState CurrentState => _currentState;
public ID3D12Resource* NativeResource => _externalResource.Get() == null ? _allocator!.GetResource(_handle.ResourceHandle) : _externalResource.Get();
/// <summary>
/// Constructor for wrapping existing D3D12 resources
/// </summary>
public D3D12Buffer(ComPtr<ID3D12Resource> resource, ulong size, BufferUsage usage, MemoryType memoryType)
{
_handle = BufferHandle.Invalid;
_allocator = null;
_externalResource = resource.Move();
Size = size;
Usage = usage;
MemoryType = memoryType;
_currentState = ResourceState.Common;
}
/// <summary>
/// Constructor for allocator-managed buffers
/// </summary>
public D3D12Buffer(BufferHandle handle, ref readonly BufferDesc desc, D3D12ResourceAllocator allocator)
{
_handle = handle;
_allocator = allocator;
_externalResource = default;
Size = desc.Size;
Usage = desc.Usage;
MemoryType = desc.MemoryType;
_currentState = ResourceState.Common;
}
public void* Map()
{
if (_mappedPtr != null)
{
return _mappedPtr;
}
if (MemoryType != MemoryType.Upload && MemoryType != MemoryType.Readback)
{
throw new InvalidOperationException("Only upload and readback buffers can be mapped");
}
var range = new Win32.Graphics.Direct3D12.Range { Begin = 0, End = 0 };
fixed (void** ptr = &_mappedPtr)
{
NativeResource->Map(0, &range, ptr);
}
return _mappedPtr;
}
public void Unmap()
{
if (_mappedPtr != null)
{
NativeResource->Unmap(0, null);
_mappedPtr = null;
}
}
public void SetCurrentState(ResourceState state)
{
_currentState = state;
}
public void Dispose()
{
if (_disposed)
{
return;
}
Unmap();
if (_handle.IsValid)
{
// Release resource via allocator
_allocator?.ReleaseResource(_handle.ResourceHandle);
}
else
{
// Release external resource
_externalResource.Dispose();
}
_disposed = true;
}
}

View File

@@ -1,9 +1,13 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
using Win32.Numerics;
namespace Ghost.Graphics.D3D12;
@@ -16,12 +20,13 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
private ComPtr<ID3D12CommandAllocator> _allocator;
private ComPtr<ID3D12GraphicsCommandList10> _commandList;
private readonly D3D12PipelineStateController _stateController;
private readonly D3D12PipelineLibrary _stateController;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12ResourceAllocator _resourceAllocator;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly CommandBufferType _type;
private ushort _commandCount;
private bool _isRecording;
private bool _disposed;
@@ -29,9 +34,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
public ID3D12GraphicsCommandList10* NativeCommandList => _commandList.Get();
public bool IsEmpty => _commandCount == 0;
public D3D12CommandBuffer(
D3D12RenderDevice device,
D3D12PipelineStateController stateController,
D3D12PipelineLibrary stateController,
D3D12ResourceDatabase resourceDatabase,
D3D12ResourceAllocator resourceAllocator,
D3D12DescriptorAllocator descriptorAllocator,
@@ -71,6 +78,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
}
}
private void IncrementCommandCount()
{
_commandCount++;
}
public void Begin()
{
ThrowIfDisposed();
@@ -82,6 +94,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_allocator.Get()->Reset();
_commandList.Get()->Reset(_allocator.Get(), null);
_commandCount = 0;
_isRecording = true;
}
@@ -94,22 +107,53 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_isRecording = false;
}
public void BeginRenderPass(IRenderTarget renderTarget, Color128 clearColor)
public void SetRenderTargets(ReadOnlySpan<Handle<Texture>> renderTargets, Handle<Texture> depthTarget)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var rtvHandles = stackalloc CpuDescriptorHandle[renderTargets.Length];
for (var i = 0; i < renderTargets.Length; i++)
{
var handle = renderTargets[i];
if (!handle.IsValid)
{
throw new ArgumentException($"Render target at index {i} is not a valid texture handle");
}
var descriptor = _resourceDatabase.GetResourceInfo(handle.AsResource()).descriptor;
rtvHandles[i] = _descriptorAllocator.GetCpuHandle(descriptor.rtv);
}
var dsvHandle = stackalloc CpuDescriptorHandle[depthTarget.IsValid ? 1 : 0];
if (dsvHandle != null)
{
*dsvHandle = _descriptorAllocator.GetCpuHandle(_resourceDatabase.GetResourceInfo(depthTarget.AsResource()).descriptor.dsv);
}
_commandList.Get()->OMSetRenderTargets((uint)renderTargets.Length, rtvHandles, Bool32.False, dsvHandle);
}
public void BeginRenderPass(Handle<Texture> renderTarget, Handle<Texture> depthTarget, Color128 clearColor)
{
// TODO: Implement render pass begin
throw new NotImplementedException();
}
public void EndRenderPass()
{
// TODO: Implement render pass end
throw new NotImplementedException();
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
_commandList.Get()->EndRenderPass();
}
public void SetViewport(ViewportDesc viewport)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var d3d12Viewport = new Viewport(viewport.width, viewport.height, viewport.x, viewport.y, viewport.minDepth, viewport.maxDepth);
_commandList.Get()->RSSetViewports(1, &d3d12Viewport);
@@ -119,81 +163,125 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var d3d12Rect = new Rect(rect.Left, rect.Top, rect.Right, rect.Bottom);
var d3d12Rect = new Rect((int)rect.left, (int)rect.top, (int)rect.right, (int)rect.bottom);
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
}
public void ResourceBarrier(IResource resource, ResourceState before, ResourceState after)
public void ResourceBarrier(Handle<GPUResource> resource, ResourceState before, ResourceState after)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
if (resource is D3D12Texture d3d12Texture)
{
_commandList.Get()->ResourceBarrierTransition(d3d12Texture.NativeResource,
ConvertResourceState(before), ConvertResourceState(after));
}
else if (resource is D3D12Buffer d3d12Buffer)
{
_commandList.Get()->ResourceBarrierTransition(d3d12Buffer.NativeResource,
ConvertResourceState(before), ConvertResourceState(after));
}
else
{
throw new ArgumentException("Resource must be a D3D12 resource", nameof(resource));
}
var d3d12Resource = _resourceDatabase.GetResource(resource);
_commandList.Get()->ResourceBarrierTransition(d3d12Resource,
before.ToD3D12States(), after.ToD3D12States());
}
public void SetGraphicsRootSignature(IRootSignature rootSignature)
public void SetRootSignature(IRootSignature rootSignature)
{
// TODO: Implement root signature setting
throw new NotImplementedException();
}
public void SetPipelineState(IPipelineStateController pipelineState)
public void SetPipelineState(IShaderPipeline pipelineState)
{
// TODO: Implement pipeline state setting
throw new NotImplementedException();
}
// TODO: Batch draw calls by material to minimize state changes
public void DrawMesh(MeshClass mesh, MaterialClass material)
public void SetVertexBuffer(uint slot, Handle<GraphicsBuffer> buffer, ulong offset = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
// Bind the bindless material (sets up root signature, pipeline state, and descriptor heaps)
var shaderPipeline = _stateController.GetShaderPipeline(material.Shader);
var pResource = _resourceDatabase.GetResource(buffer.AsResource());
var vbView = new VertexBufferView
{
BufferLocation = pResource->GetGPUVirtualAddress() + offset,
SizeInBytes = (uint)(pResource->GetDesc().Width - offset),
StrideInBytes = _resourceDatabase.GetResourceDescription(buffer.AsResource()).bufferDescription.Stride
};
_commandList.Get()->IASetVertexBuffers(slot, 1, &vbView);
}
public void SetIndexBuffer(Handle<GraphicsBuffer> buffer, IndexType type, ulong offset = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var pResource = _resourceDatabase.GetResource(buffer.AsResource());
var ibView = new IndexBufferView
{
BufferLocation = pResource->GetGPUVirtualAddress() + offset,
SizeInBytes = (uint)(pResource->GetDesc().Width - offset),
Format = type == IndexType.UInt16 ? Format.R16Uint : Format.R32Uint
};
_commandList.Get()->IASetIndexBuffer(&ibView);
}
public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0)
{
ThrowIfDisposed();
ThrowIfNotRecording();
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();
IncrementCommandCount();
_commandList.Get()->DrawIndexedInstanced(indexCount, instanceCount, startIndex, baseVertex, startInstance);
}
// TODO: Batch draw calls by material to minimize state changes
public void DrawMesh(Handle<Mesh> mesh, Handle<Material> material)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
ref var meshRef = ref _resourceDatabase.GetMeshReference(mesh);
ref var materialRef = ref _resourceDatabase.GetMaterialReference(material);
ref var shaderRef = ref _resourceDatabase.GetShaderReference(materialRef.Shader);
var shaderPipeline = _stateController.GetShaderPipeline(materialRef.Shader);
if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline)
{
throw new InvalidOperationException("Shader pipeline is not compiled or invalid");
}
// Set root signature and pipeline state
_commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get());
_commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get());
// Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6
var heaps = stackalloc ID3D12DescriptorHeap*[2];
heaps[0] = _descriptorAllocator.GetBindlessHeap(); // Specialized bindless heap
heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Specialized bindless heap
heaps[1] = d3d12Pipeline.samplerHeap.Get(); // Sampler heap from shader
_commandList.Get()->SetDescriptorHeaps(2, heaps);
// Bind constant buffers
var rootParamIndex = 0u;
foreach (var cbufferInfo in material.Shader.ConstantBuffers)
foreach (var cbufferInfo in shaderRef.ConstantBuffers)
{
var cache = material.CBufferCaches[(int)cbufferInfo.RegisterSlot];
var resource = _resourceDatabase.GetResource(cache.GpuResource.ResourceHandle);
ref var cache = ref materialRef._cBufferCaches[(int)cbufferInfo.RegisterSlot];
var resource = _resourceDatabase.GetResource(cache.GpuResource.AsResource());
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, resource->GetGPUVirtualAddress());
}
// Bind sampler descriptor table (last root parameter)
var samplerGpuHandle = d3d12Pipeline.samplerHeap.Get()->GetGPUDescriptorHandleForHeapStart();
_commandList.Get()->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle);
// For fully bindless rendering, we don't use the Input Assembler stage
// Instead, we use instanced drawing where each "instance" represents a triangle
// The shader will use SV_InstanceID to index into the index buffer and then into the vertex buffer
@@ -201,7 +289,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
// Draw without vertex/index buffers - use instanced drawing
// Each instance represents a triangle (3 vertices)
var triangleCount = mesh.IndexCount / 3;
var triangleCount = (uint)meshRef.indices.Count / 3;
_commandList.Get()->DrawInstanced(3, triangleCount, 0, 0);
}
@@ -209,63 +297,92 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
_commandList.Get()->Dispatch(threadGroupCountX, threadGroupCountY, threadGroupCountZ);
}
public void Upload<T>(BufferHandle buffer, ReadOnlySpan<T> data)
public void Upload<T>(Handle<GraphicsBuffer> buffer, ReadOnlySpan<T> data)
where T : unmanaged
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var sizeInBytes = (uint)(data.Length * sizeof(T));
var uploadHandle = _resourceAllocator.CreateUploadBuffer(sizeInBytes);
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
var pUploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
void* mappedData;
uploadResource->Map(0, null, &mappedData);
fixed (T* dataPtr = data)
void* pMappedData;
pUploadResource->Map(0, null, &pMappedData);
fixed (T* pData = data)
{
MemoryUtilities.MemCpy(mappedData, dataPtr, sizeInBytes);
MemoryUtilities.MemCpy(pMappedData, pData, sizeInBytes);
}
uploadResource->Unmap(0, null);
pUploadResource->Unmap(0, null);
var resource = _resourceDatabase.GetResource(buffer.ResourceHandle);
var pResource = _resourceDatabase.GetResource(buffer.AsResource());
// Copy from upload buffer to destination
_commandList.Get()->CopyBufferRegion(resource, 0, uploadResource, 0, sizeInBytes);
_commandList.Get()->CopyBufferRegion(pResource, 0, pUploadResource, 0, sizeInBytes);
}
public void Upload(TextureHandle texture, uint firstSubresource, ref SubResourceData subresources, uint numSubresources)
public void Upload(Handle<Texture> texture, params ReadOnlySpan<SubResourceData> subresources)
{
ThrowIfDisposed();
ThrowIfNotRecording();
IncrementCommandCount();
var textureResource = _resourceDatabase.GetResource(texture.ResourceHandle);
var pResource = _resourceDatabase.GetResource(texture.AsResource());
var resourceDesc = textureResource->GetDesc();
var requiredSize = GetRequiredIntermediateSize(textureResource, firstSubresource, numSubresources);
var resourceDesc = pResource->GetDesc();
var requiredSize = GetRequiredIntermediateSize(pResource, 0, (uint)subresources.Length);
var uploadHandle = _resourceAllocator.CreateUploadBuffer(requiredSize);
var uploadResource = _resourceDatabase.GetResource(uploadHandle.ResourceHandle);
var pUploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
var d3d12Subresources = new SubresourceData
var d3d12Subresources = stackalloc SubresourceData[subresources.Length];
for (var i = 0; i < subresources.Length; i++)
{
pData = subresources.pData,
RowPitch = subresources.rowPitch,
SlicePitch = subresources.slicePitch
};
d3d12Subresources[i] = new SubresourceData
{
pData = subresources[i].pData,
RowPitch = subresources[i].rowPitch,
SlicePitch = subresources[i].slicePitch
};
}
UpdateSubresources(
(ID3D12GraphicsCommandList*)_commandList.Get(),
textureResource,
uploadResource,
pResource,
pUploadResource,
0,
firstSubresource,
numSubresources,
&d3d12Subresources);
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();
IncrementCommandCount();
var pDestResource = _resourceDatabase.GetResource(dest.AsResource());
var pSrcResource = _resourceDatabase.GetResource(src.AsResource());
if (pSrcResource == null || pDestResource == null)
{
throw new ArgumentException("Source or destination buffer is not valid");
}
if (numBytes == 0)
{
_commandList.Get()->CopyResource(pDestResource, pSrcResource);
}
else
{
_commandList.Get()->CopyBufferRegion(pDestResource, destOffset, pSrcResource, srcOffset, numBytes);
}
}
private static CommandListType ConvertCommandBufferType(CommandBufferType type)
@@ -279,24 +396,6 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
};
}
private static ResourceStates ConvertResourceState(ResourceState state)
{
return state switch
{
ResourceState.Common or ResourceState.Present => ResourceStates.Common,
ResourceState.VertexAndConstantBuffer => ResourceStates.VertexAndConstantBuffer,
ResourceState.IndexBuffer => ResourceStates.IndexBuffer,
ResourceState.RenderTarget => ResourceStates.RenderTarget,
ResourceState.UnorderedAccess => ResourceStates.UnorderedAccess,
ResourceState.DepthWrite => ResourceStates.DepthWrite,
ResourceState.DepthRead => ResourceStates.DepthRead,
ResourceState.PixelShaderResource => ResourceStates.PixelShaderResource,
ResourceState.CopyDest => ResourceStates.CopyDest,
ResourceState.CopySource => ResourceStates.CopySource,
_ => throw new ArgumentException($"Unknown resource state: {state}")
};
}
public void Dispose()
{
if (_isRecording)
@@ -311,6 +410,8 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
_commandList.Dispose();
_allocator.Dispose();
_isRecording = false;
_commandCount = 0;
_disposed = true;
GC.SuppressFinalize(this);

View File

@@ -1,4 +1,6 @@
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Win32;
using Win32.Graphics.Direct3D12;
@@ -49,6 +51,11 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
public void Submit(ICommandBuffer commandBuffer)
{
if (commandBuffer.IsEmpty)
{
return;
}
if (commandBuffer is D3D12CommandBuffer d3d12CommandBuffer)
{
var commandList = d3d12CommandBuffer.NativeCommandList;
@@ -63,21 +70,43 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers)
{
var commandLists = stackalloc ID3D12CommandList*[commandBuffers.Length];
Span<int> executableIndices = stackalloc int[commandBuffers.Length];
executableIndices.Fill(-1);
var currentIndex = 0;
for (var i = 0; i < commandBuffers.Length; i++)
{
if (commandBuffers[i] is D3D12CommandBuffer d3d12CommandBuffer)
if (!commandBuffers[i].IsEmpty)
{
commandLists[i] = (ID3D12CommandList*)d3d12CommandBuffer.NativeCommandList;
}
else
{
throw new ArgumentException($"Command buffer at index {i} must be a D3D12CommandBuffer", nameof(commandBuffers));
executableIndices[currentIndex] = i;
currentIndex++;
}
}
_queue.Get()->ExecuteCommandLists((uint)commandBuffers.Length, commandLists);
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;
}
else
{
throw new ArgumentException("Command buffer must be a D3D12CommandBuffer", nameof(commandBuffers));
}
currentIndex++;
}
_queue.Get()->ExecuteCommandLists((uint)currentIndex, ppCommandLists);
}
public ulong Signal(ulong value)
@@ -104,6 +133,12 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
return _fence.Get()->GetCompletedValue();
}
public void WaitIdle()
{
var fenceValue = Signal(Interlocked.Increment(ref _fenceValue));
WaitForValue(fenceValue);
}
private static CommandListType ConvertCommandQueueType(CommandQueueType type)
{
return type switch

View File

@@ -1,7 +1,5 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
@@ -10,25 +8,21 @@ namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps.
/// </summary>
internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposable
internal unsafe class D3D12DescriptorAllocator : IDisposable
{
private readonly D3D12DescriptorHeap _rtvHeap;
private readonly D3D12DescriptorHeap _dsvHeap;
private readonly D3D12DescriptorHeap _srvHeap;
private readonly D3D12DescriptorHeap _cbvSrvUavHeap;
private readonly D3D12DescriptorHeap _samplerHeap;
private readonly BindlessDescriptorHeap _bindlessHeap;
private bool _disposed;
public unsafe D3D12DescriptorAllocator(D3D12RenderDevice device, uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256, uint initialBindlessCount = 10000)
public unsafe D3D12DescriptorAllocator(D3D12RenderDevice device, int initialRtvCount = 256, int initialDsvCount = 256, int initialSrvCount = 200_000, int initialSamplerCount = 256)
{
var pDevice = device.NativeDevice;
_rtvHeap = new D3D12DescriptorHeap("rtv", pDevice, DescriptorHeapType.Rtv, initialRtvCount);
_dsvHeap = new D3D12DescriptorHeap("dsv", pDevice, DescriptorHeapType.Dsv, initialDsvCount);
_srvHeap = new D3D12DescriptorHeap("srv", pDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount);
_samplerHeap = new D3D12DescriptorHeap("sampler", pDevice, DescriptorHeapType.Sampler, initialSamplerCount);
_bindlessHeap = new BindlessDescriptorHeap(pDevice, initialBindlessCount);
_rtvHeap = new D3D12DescriptorHeap("rtv", device, DescriptorHeapType.Rtv, initialRtvCount, initialRtvCount / 2);
_dsvHeap = new D3D12DescriptorHeap("dsv", device, DescriptorHeapType.Dsv, initialDsvCount, initialDsvCount / 2);
_cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, DescriptorHeapType.CbvSrvUav, initialSrvCount, initialSrvCount /2);
_samplerHeap = new D3D12DescriptorHeap("sampler", device, DescriptorHeapType.Sampler, initialSamplerCount, initialSamplerCount);
}
~D3D12DescriptorAllocator()
@@ -38,54 +32,55 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
#region RTV Methods
public RenderTargetDescriptor AllocateRTV()
public Identifier<RTVDesc> AllocateRTV(bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _rtvHeap.AllocateDescriptor();
if (index == uint.MaxValue)
var index = dynamic ? _rtvHeap.AllocateDescriptorDynamic() : _rtvHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate RTV descriptor");
}
return new RenderTargetDescriptor { Index = index };
return new Identifier<RTVDesc>(index);
}
public RenderTargetDescriptor[] AllocateRTVs(uint count)
public Identifier<RTVDesc>[] AllocateRTVs(int count, bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _rtvHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
var baseIndex = dynamic ? _rtvHeap.AllocateDescriptorsDynamic(count) : _rtvHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors");
}
var descriptors = new RenderTargetDescriptor[count];
for (uint i = 0; i < count; i++)
var descriptors = new Identifier<RTVDesc>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new RenderTargetDescriptor { Index = index };
descriptors[i] = new Identifier<RTVDesc>(index);
}
return descriptors;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CpuDescriptorHandle GetCpuHandle(RenderTargetDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandle(Identifier<RTVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _rtvHeap.GetCpuHandle(descriptor.Index);
return _rtvHeap.GetCpuHandle(descriptor.value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(RenderTargetDescriptor descriptor)
public void Release(Identifier<RTVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_rtvHeap.ReleaseDescriptor(descriptor.Index);
_rtvHeap.ReleaseDescriptor(descriptor.value);
}
public void Release(ReadOnlySpan<RenderTargetDescriptor> descriptors)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(ReadOnlySpan<Identifier<RTVDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -95,56 +90,80 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(Identifier<RTVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_rtvHeap.CopyToPersistentHeap(descriptor.value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(ReadOnlySpan<Identifier<RTVDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
MakePersistent(descriptor);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetRTVDynamicHeap()
{
ObjectDisposedException.ThrowIf(_disposed, this);
_rtvHeap.ResetDynamicHeap();
}
#endregion
#region DSV Methods
public DepthStencilDescriptor AllocateDSV()
public Identifier<DSVDesc> AllocateDSV(bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _dsvHeap.AllocateDescriptor();
if (index == uint.MaxValue)
var index = dynamic ? _dsvHeap.AllocateDescriptorDynamic() : _dsvHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate DSV descriptor");
}
return new DepthStencilDescriptor { Index = index };
return new Identifier<DSVDesc>(index);
}
public DepthStencilDescriptor[] AllocateDSVs(uint count)
public Identifier<DSVDesc>[] AllocateDSVs(int count, bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _dsvHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
var baseIndex = dynamic ? _dsvHeap.AllocateDescriptorsDynamic(count) : _dsvHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors");
}
var descriptors = new DepthStencilDescriptor[count];
for (uint i = 0; i < count; i++)
var descriptors = new Identifier<DSVDesc>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new DepthStencilDescriptor { Index = index };
descriptors[i] = new Identifier<DSVDesc>(index);
}
return descriptors;
}
public CpuDescriptorHandle GetCpuHandle(DepthStencilDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandle(Identifier<DSVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _dsvHeap.GetCpuHandle(descriptor.Index);
return _dsvHeap.GetCpuHandle(descriptor.value);
}
public void Release(DepthStencilDescriptor descriptor)
public void Release(Identifier<DSVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_dsvHeap.ReleaseDescriptor(descriptor.Index);
_dsvHeap.ReleaseDescriptor(descriptor.value);
}
public void Release(ReadOnlySpan<DepthStencilDescriptor> descriptors)
public void Release(ReadOnlySpan<Identifier<DSVDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -154,64 +173,94 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(Identifier<DSVDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_dsvHeap.CopyToPersistentHeap(descriptor.value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(ReadOnlySpan<Identifier<DSVDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
MakePersistent(descriptor);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetDSVDynamicHeap()
{
ObjectDisposedException.ThrowIf(_disposed, this);
_dsvHeap.ResetDynamicHeap();
}
#endregion
#region SRV Methods
#region CBV_SRV_UAV Methods
public ShaderResourceDescriptor AllocateSRV()
public Identifier<CbvSrvUavDesc> AllocateCbvSrvUav(bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _srvHeap.AllocateDescriptor();
if (index == uint.MaxValue)
var index = dynamic ? _cbvSrvUavHeap.AllocateDescriptorDynamic() : _cbvSrvUavHeap.AllocateDescriptor();
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate SRV descriptor");
throw new InvalidOperationException("Failed to allocate CBV/SRV/UAV descriptor");
}
_srvHeap.CopyToShaderVisibleHeap(index);
return new ShaderResourceDescriptor { Index = index };
_cbvSrvUavHeap.CopyToShaderVisibleHeap(index);
return new Identifier<CbvSrvUavDesc>(index);
}
public ShaderResourceDescriptor[] AllocateSRVs(uint count)
public Identifier<CbvSrvUavDesc>[] AllocateSRVs(int count, bool dynamic = false)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _srvHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
var baseIndex = dynamic ? _cbvSrvUavHeap.AllocateDescriptorsDynamic(count) : _cbvSrvUavHeap.AllocateDescriptors(count);
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} SRV descriptors");
throw new InvalidOperationException($"Failed to allocate {count} CBV/SRV/UAV descriptors");
}
var descriptors = new ShaderResourceDescriptor[count];
for (uint i = 0; i < count; i++)
var descriptors = new Identifier<CbvSrvUavDesc>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new ShaderResourceDescriptor { Index = index };
descriptors[i] = new Identifier<CbvSrvUavDesc>(index);
}
_srvHeap.CopyToShaderVisibleHeap(baseIndex, count);
_cbvSrvUavHeap.CopyToShaderVisibleHeap(baseIndex, count);
return descriptors;
}
public CpuDescriptorHandle GetCpuHandle(ShaderResourceDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandle(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _srvHeap.GetCpuHandle(descriptor.Index);
return _cbvSrvUavHeap.GetCpuHandle(descriptor.value);
}
public GpuDescriptorHandle GetGpuHandle(ShaderResourceDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandleShaderVisible(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _srvHeap.GetGpuHandle(descriptor.Index);
return _cbvSrvUavHeap.GetCpuHandleShaderVisible(descriptor.value);
}
public void Release(ShaderResourceDescriptor descriptor)
public GpuDescriptorHandle GetGpuHandle(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_srvHeap.ReleaseDescriptor(descriptor.Index);
return _cbvSrvUavHeap.GetGpuHandle(descriptor.value);
}
public void Release(ReadOnlySpan<ShaderResourceDescriptor> descriptors)
public void Release(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.ReleaseDescriptor(descriptor.value);
}
public void Release(ReadOnlySpan<Identifier<CbvSrvUavDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -221,64 +270,94 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(Identifier<CbvSrvUavDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.CopyToPersistentHeap(descriptor.value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MakePersistent(ReadOnlySpan<Identifier<CbvSrvUavDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
MakePersistent(descriptor);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetCbvSrvUavDynamicHeap()
{
ObjectDisposedException.ThrowIf(_disposed, this);
_cbvSrvUavHeap.ResetDynamicHeap();
}
#endregion
#region Sampler Methods
public SamplerDescriptor AllocateSampler()
public Identifier<SamplerDesc> AllocateSampler()
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _samplerHeap.AllocateDescriptor();
if (index == uint.MaxValue)
if (index == -1)
{
throw new InvalidOperationException("Failed to allocate Sampler descriptor");
}
_samplerHeap.CopyToShaderVisibleHeap(index);
return new SamplerDescriptor { Index = index };
return new Identifier<SamplerDesc>(index);
}
public SamplerDescriptor[] AllocateSamplers(uint count)
public Identifier<SamplerDesc>[] AllocateSamplers(int count)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _samplerHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
if (baseIndex == -1)
{
throw new InvalidOperationException($"Failed to allocate {count} Sampler descriptors");
}
var descriptors = new SamplerDescriptor[count];
for (uint i = 0; i < count; i++)
var descriptors = new Identifier<SamplerDesc>[count];
for (var i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new SamplerDescriptor { Index = index };
descriptors[i] = new Identifier<SamplerDesc>(index);
}
_samplerHeap.CopyToShaderVisibleHeap(baseIndex, count);
return descriptors;
}
public CpuDescriptorHandle GetCpuHandle(SamplerDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandle(Identifier<SamplerDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetCpuHandle(descriptor.Index);
return _samplerHeap.GetCpuHandle(descriptor.value);
}
public GpuDescriptorHandle GetGpuHandle(SamplerDescriptor descriptor)
public CpuDescriptorHandle GetCpuHandleShaderVisible(Identifier<SamplerDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetGpuHandle(descriptor.Index);
return _samplerHeap.GetCpuHandleShaderVisible(descriptor.value);
}
public void Release(SamplerDescriptor descriptor)
public GpuDescriptorHandle GetGpuHandle(Identifier<SamplerDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_samplerHeap.ReleaseDescriptor(descriptor.Index);
return _samplerHeap.GetGpuHandle(descriptor.value);
}
public void Release(ReadOnlySpan<SamplerDescriptor> descriptors)
public void Release(Identifier<SamplerDesc> descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_samplerHeap.ReleaseDescriptor(descriptor.value);
}
public void Release(ReadOnlySpan<Identifier<SamplerDesc>> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -290,84 +369,19 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
#endregion
#region Bindless Methods
/// <summary>
/// Allocates a bindless descriptor for SM 6.6 rendering.
/// The returned descriptor maintains a 1:1 relationship between allocation index and shader index.
/// </summary>
public BindlessDescriptor AllocateBindless()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(D3D12ResourceDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var index = _bindlessHeap.AllocateDescriptor();
if (index == uint.MaxValue)
{
throw new InvalidOperationException("Failed to allocate bindless descriptor");
}
return new BindlessDescriptor { Index = index };
Release(descriptor.rtv);
Release(descriptor.dsv);
Release(descriptor.srv);
Release(descriptor.cbv);
Release(descriptor.uav);
Release(descriptor.sampler);
}
/// <summary>
/// Allocates multiple bindless descriptors for SM 6.6 rendering.
/// </summary>
public BindlessDescriptor[] AllocateBindless(uint count)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var baseIndex = _bindlessHeap.AllocateDescriptors(count);
if (baseIndex == uint.MaxValue)
{
throw new InvalidOperationException($"Failed to allocate {count} bindless descriptors");
}
var descriptors = new BindlessDescriptor[count];
for (uint i = 0; i < count; i++)
{
var index = baseIndex + i;
descriptors[i] = new BindlessDescriptor { Index = index };
}
return descriptors;
}
public CpuDescriptorHandle GetCpuHandle(BindlessDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _bindlessHeap.GetCpuHandle(descriptor.Index);
}
public GpuDescriptorHandle GetGpuHandle(BindlessDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _bindlessHeap.GetGpuHandle(descriptor.Index);
}
/// <summary>
/// Releases a bindless descriptor.
/// </summary>
public void Release(BindlessDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_bindlessHeap.ReleaseDescriptor(descriptor.Index);
}
/// <summary>
/// Releases multiple bindless descriptors.
/// </summary>
public void Release(ReadOnlySpan<BindlessDescriptor> descriptors)
{
ObjectDisposedException.ThrowIf(_disposed, this);
foreach (var descriptor in descriptors)
{
Release(descriptor);
}
}
#endregion
#region Utility Methods
/// <summary>
@@ -381,34 +395,30 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
public ID3D12DescriptorHeap* GetDSVHeap() => _dsvHeap.Heap;
/// <summary>
/// Gets the SRV heap for binding to the command list.
/// Gets the CBV/SRV/UAV heap for binding to the command list.
/// </summary>
public ID3D12DescriptorHeap* GetSRVHeap() => _srvHeap.ShaderVisibleHeap;
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 bindless heap for SM 6.6 bindless rendering.
/// </summary>
public ID3D12DescriptorHeap* GetBindlessHeap() => _bindlessHeap.BindlessHeap;
/// <summary>
/// Gets the shader visible heaps that need to be bound to the command list.
/// </summary>
public ID3D12DescriptorHeap*[] GetShaderVisibleHeaps()
/// <param name="ppHeap">An array of two ID3D12DescriptorHeap pointers to receive the CBV/SRV/UAV and Sampler heaps.</param>
public void GetShaderVisibleHeaps(ID3D12DescriptorHeap** ppHeap)
{
return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
}
ObjectDisposedException.ThrowIf(_disposed, this);
/// <summary>
/// Gets the shader visible heaps including bindless heap for SM 6.6 rendering.
/// </summary>
public ConstPtr<ID3D12DescriptorHeap>[] GetShaderVisibleHeapsWithBindless()
{
return [_bindlessHeap.BindlessHeap, _srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
if (ppHeap == null)
{
throw new ArgumentNullException(nameof(ppHeap));
}
ppHeap[0] = _cbvSrvUavHeap.ShaderVisibleHeap;
ppHeap[1] = _samplerHeap.ShaderVisibleHeap;
}
#endregion
@@ -422,9 +432,8 @@ internal unsafe class D3D12DescriptorAllocator : IDescriptorAllocator, IDisposab
_rtvHeap.Dispose();
_dsvHeap.Dispose();
_srvHeap.Dispose();
_cbvSrvUavHeap.Dispose();
_samplerHeap.Dispose();
_bindlessHeap.Dispose();
_disposed = true;

View File

@@ -9,7 +9,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
#endif
private readonly D3D12RenderDevice _device;
private readonly D3D12PipelineStateController _stateController;
private readonly D3D12PipelineLibrary _stateController;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
@@ -22,7 +22,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceAllocator ResourceAllocator => _resourceAllocator;
public IPipelineStateController PipelineStateController => _stateController;
public IPipelineLibrary PipelineStateController => _stateController;
public D3D12GraphicsEngine(RenderSystem renderSystem)
{
@@ -52,7 +52,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
public IRenderer CreateRenderer()
{
return new D3D12Renderer(this, _resourceAllocator);
return new D3D12Renderer(this, _resourceAllocator, _resourceDatabase);
}
public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics)
@@ -68,7 +68,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
public ISwapChain CreateSwapChain(SwapChainDesc desc)
{
return new D3D12SwapChain(_device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc);
return new D3D12SwapChain(_resourceDatabase, _device.DXGIFactory, ((D3D12CommandQueue)_device.ComputeQueue).NativeQueue, desc);
}
public void BeginFrame()

View File

@@ -0,0 +1,324 @@
#undef USE_TRANDITIONAL_BINDLESS
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using System.Runtime.InteropServices;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.D3D12;
// TODO: Fixed root signature and use bindless samplers and textures.
// This can dramatically reduce the number of root parameters needed and improve performance.
internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
{
public ComPtr<ID3D12PipelineState> pipelineState;
public D3D12ShaderCompiler.CompileResult vsResult;
public D3D12ShaderCompiler.CompileResult psResult;
public D3D12ShaderCompiler.CompileResult csResult;
public PipelineType Type
{
get; init;
}
public void Dispose()
{
pipelineState.Dispose();
vsResult.Dispose();
psResult.Dispose();
csResult.Dispose();
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal unsafe struct D3D12GraphicsPipelineStream
{
public PipelineStateSubObjectType rootSignatureType;
public ID3D12RootSignature* pRootSignature;
public PipelineStateSubObjectType vsType;
public ShaderBytecode vs;
public PipelineStateSubObjectType psType;
public ShaderBytecode ps;
public PipelineStateSubObjectType rasterizerType;
public RasterizerDescription rasterizer;
public PipelineStateSubObjectType blendType;
public BlendDescription blend;
public PipelineStateSubObjectType depthStencilType;
public DepthStencilDescription depthStencil;
public PipelineStateSubObjectType topologyType;
public PrimitiveTopologyType primitiveTopology;
public PipelineStateSubObjectType rtvFormatType;
public RtFormatArray rtvFormats;
public PipelineStateSubObjectType dsvFormatType;
public Format dsvFormat;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal unsafe struct ComputePipelineStream
{
public PipelineStateSubObjectType rootSignatureType;
public ID3D12RootSignature* pRootSignature;
public PipelineStateSubObjectType csType;
public ShaderBytecode cs;
}
internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
{
private readonly D3D12RenderDevice _device;
private readonly D3D12ResourceDatabase _resourceDatabase;
private ComPtr<ID3D12PipelineLibrary1> _library;
private ComPtr<ID3D12RootSignature> _defaultRootSignature;
private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase, string? cachePath)
{
_device = device;
_resourceDatabase = resourceDatabase;
_shaderPipelines = new();
InitializePipelineLibrary(cachePath);
CreateDefaultRootSignature();
}
private void InitializePipelineLibrary(string? cachePath)
{
if (!File.Exists(cachePath))
{
_device.NativeDevice->CreatePipelineLibrary(null, 0, __uuidof<ID3D12PipelineLibrary1>(), _library.GetVoidAddressOf()).ThrowIfFailed();
}
var fileBytes = File.ReadAllBytes(cachePath!);
fixed (byte* pFileBytes = fileBytes)
{
_device.NativeDevice->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof<ID3D12PipelineLibrary1>(), _library.GetVoidAddressOf()).ThrowIfFailed();
}
}
private void CreateDefaultRootSignature()
{
const int rootParamCount =
#if USE_TRANDITIONAL_BINDLESS
6
#else
4
#endif
;
_defaultRootSignature = default;
// The layout of the root signature is:
// - Global buffer (b0)
// - Per-view buffer (b1)
// - Per-object buffer (b2)
// - Per-instance buffer (b3)
// - Descriptor table for bindless textures (t0)
// - Descriptor table for bindless samplers (s0)
// NOTE: Since we are targeting SM 6.6, we can use ResourceDescriptorHeap and SamplerDescriptorHeap directly without needing to set up descriptor tables.
var rootParameters = stackalloc RootParameter1[rootParamCount];
rootParameters[0] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(0, 0), // b0
};
rootParameters[1] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(1, 0), // b1
};
rootParameters[2] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(2, 0), // b2
};
rootParameters[3] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(3, 0), // b3
};
#if USE_TRANDITIONAL_BINDLESS
// Descriptor table for bindless textures
var srvRange = new DescriptorRange1(
DescriptorRangeType.Srv,
~0u,
0,
0,
DescriptorRangeFlags.DataVolatile);
rootParameters[4] = new RootParameter1
{
ParameterType = RootParameterType.DescriptorTable,
ShaderVisibility = ShaderVisibility.All,
DescriptorTable = new RootDescriptorTable1(1, &srvRange)
};
// Descriptor table for bindless samplers
var sampRange = new DescriptorRange1(
DescriptorRangeType.Sampler,
~0u,
0,
0,
DescriptorRangeFlags.DataVolatile);
rootParameters[5] = new RootParameter1
{
ParameterType = RootParameterType.DescriptorTable,
ShaderVisibility = ShaderVisibility.All,
DescriptorTable = new RootDescriptorTable1(1, &sampRange)
};
#endif
var rootSignatureDesc = new RootSignatureDescription1
{
NumParameters = rootParamCount,
pParameters = rootParameters,
NumStaticSamplers = 0,
pStaticSamplers = null,
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout
#if !USE_TRANDITIONAL_BINDLESS
| RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed
| RootSignatureFlags.SamplerHeapDirectlyIndexed
#endif
};
var versionedDesc = new VersionedRootSignatureDescription
{
Version = RootSignatureVersion.V1_1,
Desc_1_1 = rootSignatureDesc
};
using ComPtr<ID3DBlob> signature = default;
using ComPtr<ID3DBlob> error = default;
var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
if (serializeResult.Failure)
{
var errorMsg = error.Get() != null ? Marshal.PtrToStringAnsi((nint)error.Get()->GetBufferPointer()) : "Unknown error";
throw new InvalidOperationException($"Failed to serialize default root signature: {errorMsg}");
}
_device.NativeDevice->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(),
__uuidof<ID3D12RootSignature>(), _defaultRootSignature.GetVoidAddressOf()).ThrowIfFailed();
}
public void StorePipeline(string psoIdentifier, ID3D12PipelineState* pso)
{
_library.Get()->StorePipeline(psoIdentifier.AsSpan().GetPointer(), pso);
}
public void* LoadGraphicsPipeline(string psoIdentifier)
{
if (_library.Get()->LoadGraphicsPipeline(psoIdentifier.AsSpan().GetPointer(), __uuidof<ID3D12PipelineState>(), out var pso).Failure)
{
return null;
}
return pso;
}
public void CompileShader(Identifier<Shader> id, string shaderPath)
{
var vsResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
var psResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
ref var shader = ref _resourceDatabase.GetShaderReference(id);
D3D12ShaderCompiler.PerformDXCReflection(ref shader, vsResult.reflection.Get());
D3D12ShaderCompiler.PerformDXCReflection(ref shader, psResult.reflection.Get());
var shaderPipeline = new D3D12ShaderPipeline
{
Type = PipelineType.Graphics,
vsResult = vsResult,
psResult = psResult
};
_shaderPipelines[id] = shaderPipeline;
}
// Create PSO from SDL (Shader Definition Language) file
private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline)
{
var psoDesc = new GraphicsPipelineStateDescription
{
pRootSignature = _defaultRootSignature.Get(),
VS = new ShaderBytecode(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
PS = new ShaderBytecode(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = RasterizerDescription.CullNone,
BlendState = BlendDescription.Opaque,
DepthStencilState = DepthStencilDescription.Default,
SampleMask = uint.MaxValue,
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
NumRenderTargets = 1,
SampleDesc = new SampleDescription(1, 0),
DSVFormat = Format.Unknown,
};
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
_device.NativeDevice->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), shaderPipeline.pipelineState.GetVoidAddressOf());
}
// TODO: Pipeline variants (keywords)
// TODO: Disk caching
// TODO: Async compilation
public void PreCookPipelineState()
{
foreach (var kvp in _shaderPipelines)
{
ref var shader = ref _resourceDatabase.GetShaderReference(kvp.Key);
CreatePipelineStateObject(kvp.Value);
kvp.Value.vsResult.Dispose();
kvp.Value.psResult.Dispose();
kvp.Value.csResult.Dispose();
}
}
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id)
{
if (_shaderPipelines.TryGetValue(id, out var pipeline))
{
return pipeline;
}
throw new KeyNotFoundException($"Shader pipeline not found for shader ID: {id}");
}
public void Dispose()
{
foreach (var kvp in _shaderPipelines)
{
kvp.Value.Dispose();
}
}
}

View File

@@ -1,257 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.D3D12;
internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
{
public ComPtr<ID3D12RootSignature> rootSignature;
public ComPtr<ID3D12PipelineState> pipelineState;
public ComPtr<ID3D12DescriptorHeap> samplerHeap;
public D3D12ShaderCompiler.CompileResult vsResult;
public D3D12ShaderCompiler.CompileResult psResult;
public D3D12ShaderCompiler.CompileResult csResult;
public PipelineType Type
{
get; init;
}
public void Dispose()
{
rootSignature.Dispose();
pipelineState.Dispose();
samplerHeap.Dispose();
vsResult.Dispose();
psResult.Dispose();
csResult.Dispose();
}
}
internal unsafe class D3D12PipelineStateController : IPipelineStateController, IDisposable
{
private readonly ID3D12Device14* _device;
private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
public D3D12PipelineStateController(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase)
{
_device = device.NativeDevice;
_resourceDatabase = resourceDatabase;
_shaderPipelines = new();
}
public void CompileShader(Identifier<Shader> id, string shaderPath)
{
var vsResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
var psResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
var shader = _resourceDatabase.GetShader(id);
D3D12ShaderCompiler.PerformDXCReflection(shader, vsResult.reflection.Get());
D3D12ShaderCompiler.PerformDXCReflection(shader, psResult.reflection.Get());
var shaderPipeline = new D3D12ShaderPipeline
{
Type = PipelineType.Graphics,
vsResult = vsResult,
psResult = psResult
};
_shaderPipelines[id] = shaderPipeline;
}
private void CreateRootSignature(Shader shader, D3D12ShaderPipeline shaderPipeline)
{
// Calculate total root parameters: CBVs + Regular texture descriptor table + Sampler table
var totalRootParams = shader.ConstantBuffers.Count + (shader.RegularTextures.Count > 0 ? 1 : 0) + 1; // +1 for sampler
var rootParameters = new RootParameter1[totalRootParams];
var parameterIndex = 0;
// Add CBV root parameters
foreach (var cbufferInfo in shader.ConstantBuffers)
{
rootParameters[parameterIndex++] = new RootParameter1
{
ParameterType = RootParameterType.Cbv,
ShaderVisibility = ShaderVisibility.All,
Descriptor = new RootDescriptor1(cbufferInfo.RegisterSlot, 0),
};
}
// Add regular texture descriptor table if we have regular textures
if (shader.RegularTextures.Count > 0)
{
var textureRanges = new DescriptorRange1[1];
textureRanges[0] = new DescriptorRange1
{
RangeType = DescriptorRangeType.Srv,
NumDescriptors = (uint)shader.RegularTextures.Count,
BaseShaderRegister = 0, // Start from t0
RegisterSpace = 0,
Flags = DescriptorRangeFlags.None,
OffsetInDescriptorsFromTableStart = 0
};
fixed (DescriptorRange1* textureRangesPtr = textureRanges)
{
rootParameters[parameterIndex++] = new RootParameter1
{
ParameterType = RootParameterType.DescriptorTable,
ShaderVisibility = ShaderVisibility.All,
DescriptorTable = new RootDescriptorTable1(1, textureRangesPtr)
};
}
}
// Sampler descriptor table (still needed for samplers)
var samplerRanges = new DescriptorRange1[1];
samplerRanges[0] = new DescriptorRange1
{
RangeType = DescriptorRangeType.Sampler,
NumDescriptors = 1,
BaseShaderRegister = 0, // s0
RegisterSpace = 0,
Flags = DescriptorRangeFlags.None,
OffsetInDescriptorsFromTableStart = 0
};
fixed (DescriptorRange1* samplerRangesPtr = samplerRanges)
{
rootParameters[parameterIndex] = new RootParameter1
{
ParameterType = RootParameterType.DescriptorTable,
ShaderVisibility = ShaderVisibility.All,
DescriptorTable = new RootDescriptorTable1(1, samplerRangesPtr)
};
}
// Create root signature with the modern flag
fixed (RootParameter1* rootParamsPtr = rootParameters)
{
var rootSignatureDesc = new RootSignatureDescription1
{
NumParameters = (uint)rootParameters.Length,
pParameters = rootParamsPtr,
NumStaticSamplers = 0,
pStaticSamplers = null,
// Key difference: Use the modern flag for direct heap indexing
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout |
RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed
};
var versionedDesc = new VersionedRootSignatureDescription
{
Version = RootSignatureVersion.V1_1,
Desc_1_1 = rootSignatureDesc
};
using ComPtr<ID3DBlob> signature = default;
using ComPtr<ID3DBlob> error = default;
D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
_device->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), __uuidof<ID3D12RootSignature>(), shaderPipeline.rootSignature.GetVoidAddressOf());
}
}
private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline)
{
var psoDesc = new GraphicsPipelineStateDescription
{
pRootSignature = shaderPipeline.rootSignature.Get(),
VS = new ShaderBytecode(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
PS = new ShaderBytecode(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = RasterizerDescription.CullNone,
BlendState = BlendDescription.Opaque,
DepthStencilState = DepthStencilDescription.Default,
SampleMask = uint.MaxValue,
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
NumRenderTargets = 1,
SampleDesc = new SampleDescription(1, 0),
DSVFormat = Format.Unknown,
};
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
_device->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), shaderPipeline.pipelineState.GetVoidAddressOf());
}
private void CreateSamplerHeap(D3D12ShaderPipeline shaderPipeline)
{
// Create sampler heap
var samplerHeapDesc = new DescriptorHeapDescription
{
Type = DescriptorHeapType.Sampler,
NumDescriptors = 1,
Flags = DescriptorHeapFlags.ShaderVisible
};
_device->CreateDescriptorHeap(&samplerHeapDesc, __uuidof<ID3D12DescriptorHeap>(), shaderPipeline.samplerHeap.GetVoidAddressOf());
// Create default sampler
var samplerDesc = new SamplerDescription
{
Filter = Filter.MinMagMipLinear,
AddressU = TextureAddressMode.Wrap,
AddressV = TextureAddressMode.Wrap,
AddressW = TextureAddressMode.Wrap,
MipLODBias = 0,
MaxAnisotropy = 1,
MinLOD = 0,
MaxLOD = float.MaxValue
};
// Set border color manually
samplerDesc.BorderColor[0] = 0;
samplerDesc.BorderColor[1] = 0;
samplerDesc.BorderColor[2] = 0;
samplerDesc.BorderColor[3] = 0;
var samplerHandle = shaderPipeline.samplerHeap.Get()->GetCPUDescriptorHandleForHeapStart();
_device->CreateSampler(&samplerDesc, samplerHandle);
}
// TODO: Pipeline variants (keywords)
// TODO: Disk caching
// TODO: Async compilation
public void PreCookPipelineState()
{
foreach (var kvp in _shaderPipelines)
{
var shader = _resourceDatabase.GetShader(kvp.Key);
CreateRootSignature(shader, kvp.Value);
CreatePipelineStateObject(kvp.Value);
CreateSamplerHeap(kvp.Value);
}
}
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id)
{
if (_shaderPipelines.TryGetValue(id, out var pipeline))
{
return pipeline;
}
throw new KeyNotFoundException($"Shader pipeline not found for shader ID: {id}");
}
public void Dispose()
{
foreach (var kvp in _shaderPipelines)
{
kvp.Value.Dispose();
}
}
}

View File

@@ -1,4 +1,6 @@
using Ghost.Graphics.RHI;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;

View File

@@ -1,60 +0,0 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of render target interface
/// Supports either color OR depth rendering, not both
/// </summary>
internal unsafe class D3D12RenderTarget : D3D12Texture, IRenderTarget
{
public RenderTargetType Type
{
get;
}
private D3D12RenderTarget(ComPtr<ID3D12Resource> resource, uint width, uint height, uint slice, TextureFormat format, RenderTargetType type, uint mipLevels = 1)
: base(resource, width, height, slice, format, mipLevels)
{
Type = type;
}
private D3D12RenderTarget(TextureHandle handle, ref readonly RenderTargetDesc desc, ref readonly TextureDesc texDesc)
: base(handle, in texDesc)
{
Type = desc.Type;
}
/// <summary>
/// Create a new render target with its own texture
/// </summary>
/// <param name="handle">The handle to the texture resource</param>
/// <param name="desc">The descriptor to describe the render target</param>
/// <returns>New render target instance</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static D3D12RenderTarget Create(TextureHandle handle, ref readonly RenderTargetDesc desc)
{
var texDesc = RenderTargetDesc.ToTextureDescriptor(desc);
return new D3D12RenderTarget(handle, in desc, in texDesc);
}
/// <summary>
/// Create a new render target from an existing D3D12 resource
/// </summary>
/// <param name="resource">The existing D3D12 resource</param>
/// <param name="width">The width of the render target</param>
/// <param name="height">The height of the render target</param>
/// <param name="format">The format of the render target</param>
/// <param name="type">The type of the render target</param>
/// <param name="mipLevels">The number of mip levels</param>
/// <returns>New render target instance</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static D3D12RenderTarget Create(ComPtr<ID3D12Resource> resource, uint width, uint height, uint slice, TextureFormat format, RenderTargetType type, uint mipLevels = 1)
{
return new D3D12RenderTarget(resource, width, height, slice, format, type, mipLevels);
}
}

View File

@@ -1,13 +1,15 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of the renderer interface using RHI abstractions
/// </summary>
public unsafe class D3D12Renderer : IRenderer
internal unsafe class D3D12Renderer : IRenderer
{
private struct FrameResource : IDisposable
{
@@ -31,24 +33,30 @@ public unsafe class D3D12Renderer : IRenderer
private uint _frameIndex;
private readonly IResourceAllocator _resourceAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private IRenderTarget? _customRenderTarget; // User-provided render target
private IRenderTarget? _offScreenRenderTarget; // Off-screen target for swap chain
private Handle<Texture> _customRenderTarget; // User-provided render target
private Handle<Texture> _offScreenRenderTarget; // Off-screen target for swap chain
private ISwapChain? _swapChain;
private readonly Lock _lock = new();
private uint2 _currentSize;
private uint _pendingWidth;
private uint _pendingHeight;
private bool _resizeRequested;
private bool _disposed;
public uint2 Size => _currentSize;
// TODO: Add render passes support
// private ImmutableArray<IRenderPass> _renderPasses;
public D3D12Renderer(IGraphicsEngine graphicsEngine, IResourceAllocator resourceAllocator)
public D3D12Renderer(IGraphicsEngine graphicsEngine, IResourceAllocator resourceAllocator, D3D12ResourceDatabase resourceDatabase)
{
_resourceAllocator = resourceAllocator;
_commandQueue = graphicsEngine.Device.GraphicsQueue;
_resourceAllocator = resourceAllocator;
_resourceDatabase = resourceDatabase;
// Create frame resources for double buffering
_frameResources = new FrameResource[D3D12PipelineResource.BACK_BUFFER_COUNT];
@@ -56,6 +64,9 @@ public unsafe class D3D12Renderer : IRenderer
{
_frameResources[i] = new FrameResource(graphicsEngine);
}
_customRenderTarget = Handle<Texture>.Invalid;
_offScreenRenderTarget = Handle<Texture>.Invalid;
}
~D3D12Renderer()
@@ -63,20 +74,20 @@ public unsafe class D3D12Renderer : IRenderer
Dispose();
}
public void SetRenderTarget(IRenderTarget? renderTarget)
public void SetRenderTarget(Handle<Texture> renderTarget)
{
_customRenderTarget = renderTarget;
_swapChain = null;
// Clean up off-screen target when switching to render target mode
_offScreenRenderTarget?.Dispose();
_offScreenRenderTarget = null;
_resourceDatabase.ReleaseResource(_offScreenRenderTarget.AsResource());
_offScreenRenderTarget = Handle<Texture>.Invalid;
}
public void SetSwapChain(ISwapChain? swapChain)
{
_swapChain = swapChain;
_customRenderTarget = null;
_customRenderTarget = Handle<Texture>.Invalid;
if (_swapChain != null)
{
@@ -84,8 +95,8 @@ public unsafe class D3D12Renderer : IRenderer
}
else
{
_offScreenRenderTarget?.Dispose();
_offScreenRenderTarget = null;
_resourceDatabase.ReleaseResource(_offScreenRenderTarget.AsResource());
_offScreenRenderTarget = Handle<Texture>.Invalid;
}
}
@@ -122,6 +133,7 @@ public unsafe class D3D12Renderer : IRenderer
// Resize swap chain if present
_swapChain?.Resize(newWidth, newHeight);
_currentSize = new uint2(newWidth, newHeight);
// Update off-screen render target size
if (_swapChain != null)
@@ -144,12 +156,12 @@ public unsafe class D3D12Renderer : IRenderer
frame.commandBuffer.Begin();
if (_customRenderTarget != null)
if (_customRenderTarget.IsValid)
{
// Render target mode: render directly to custom target
RenderScene(_customRenderTarget, frame.commandBuffer);
}
else if (_swapChain != null && _offScreenRenderTarget != null)
else if (_swapChain != null && _offScreenRenderTarget.IsValid)
{
// Swap chain mode: render to off-screen, then blit to back buffer
var backBufferRT = _swapChain.GetCurrentBackBuffer();
@@ -164,17 +176,18 @@ public unsafe class D3D12Renderer : IRenderer
_commandQueue.Submit(frame.commandBuffer);
_swapChain?.Present();
frame.fenceValue = _commandQueue.Signal(++_frameIndex);
frame.fenceValue = _commandQueue.Signal(_frameIndex);
_frameIndex++;
}
private void RenderScene(IRenderTarget target, ICommandBuffer cmd)
private void RenderScene(Handle<Texture> target, ICommandBuffer cmd)
{
var clearColor = new Color128 { r = 1.0f, g = 0.0f, b = 1.0f, a = 1.0f };
cmd.BeginRenderPass(target, clearColor);
cmd.BeginRenderPass(target, Handle<Texture>.Invalid, clearColor);
var viewport = new ViewportDesc(target.Width, target.Height);
var scissor = new RectDesc(0, 0, (int)target.Width, (int)target.Height);
var viewport = new ViewportDesc { width = _currentSize.x, height = _currentSize.y, minDepth = 0, maxDepth = 1 };
var scissor = new RectDesc { right = _currentSize.x, bottom = _currentSize.y };
cmd.SetViewport(viewport);
cmd.SetScissorRect(scissor);
@@ -188,13 +201,13 @@ public unsafe class D3D12Renderer : IRenderer
cmd.EndRenderPass();
}
private void BlitToDestination(IRenderTarget source, IRenderTarget destination, ICommandBuffer cmd)
private void BlitToDestination(Handle<Texture> source, Handle<Texture> destination, ICommandBuffer cmd)
{
// Handle swap chain back buffer transitions if needed
if (_swapChain != null)
{
// Transition back buffer to render target
cmd.ResourceBarrier(destination, ResourceState.Present, ResourceState.RenderTarget);
cmd.ResourceBarrier(destination.AsResource(), ResourceState.Present, ResourceState.RenderTarget);
}
// For now, we'll do a simple copy operation
@@ -208,29 +221,26 @@ public unsafe class D3D12Renderer : IRenderer
// For now, just clear the destination (this should be replaced with actual blit)
var clearColor = new Color128 { r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f };
cmd.BeginRenderPass(destination, clearColor);
cmd.BeginRenderPass(destination, Handle<Texture>.Invalid, clearColor);
cmd.EndRenderPass();
// Handle swap chain back buffer transitions if needed
if (_swapChain != null)
{
// Transition back buffer to present
cmd.ResourceBarrier(destination, ResourceState.RenderTarget, ResourceState.Present);
cmd.ResourceBarrier(destination.AsResource(), ResourceState.RenderTarget, ResourceState.Present);
}
}
private void CreateOrUpdateOffScreenRenderTarget(uint width, uint height)
{
// Check if we need to recreate the off-screen render target
if (_offScreenRenderTarget == null ||
_offScreenRenderTarget.Width != width ||
_offScreenRenderTarget.Height != height)
if (_offScreenRenderTarget.IsValid)
{
_offScreenRenderTarget?.Dispose();
var desc = RenderTargetDesc.Color(width, height, 1, TextureFormat.R8G8B8A8_UNorm);
_offScreenRenderTarget = _resourceAllocator.CreateRenderTarget(in desc);
_resourceAllocator.ReleaseResource(_offScreenRenderTarget.AsResource());
}
var desc = RenderTargetDesc.Color(width, height, 1, TextureFormat.R8G8B8A8_UNorm);
_offScreenRenderTarget = _resourceAllocator.CreateRenderTarget(in desc);
}
public void WaitIdle()
@@ -259,7 +269,8 @@ public unsafe class D3D12Renderer : IRenderer
frame.Dispose();
}
_offScreenRenderTarget?.Dispose();
_resourceDatabase.ReleaseResource(_customRenderTarget.AsResource());
_resourceDatabase.ReleaseResource(_offScreenRenderTarget.AsResource());
_disposed = true;

View File

@@ -24,7 +24,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase;
private UnsafeQueue<ResourceHandle> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private UnsafeQueue<Handle<GPUResource>> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private Guid* IID_NULL
{
@@ -78,9 +78,9 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ResourceHandle TrackResource(ref readonly Allocation allocation, ResourceStates state, bool isTemp)
private Handle<GPUResource> TrackResource(ref readonly Allocation allocation, ResourceStates state, D3D12ResourceDescriptor resourceDescriptor, ResourceDesc desc, bool isTemp)
{
var handle = _resourceDatabase.AddResource(in allocation, _renderSystem.CPUFenceValue, D3D12StatesToRHIState(state));
var handle = _resourceDatabase.AddResource(in allocation, _renderSystem.CPUFenceValue, D3D12StatesToRHIState(state), resourceDescriptor, desc);
if (isTemp)
{
@@ -90,13 +90,68 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
return handle;
}
public TextureHandle CreateTexture(ref readonly TextureDesc desc, bool tempResource = false)
private void CreateSRV(ID3D12Resource* pResource, CpuDescriptorHandle descriptorHandle, Format format, TextureDimension dimension, uint mipLevels, uint arraySize)
{
var srvDesc = new ShaderResourceViewDescription
{
Format = format,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
switch (dimension)
{
case TextureDimension.Texture2D:
srvDesc.ViewDimension = SrvDimension.Texture2D;
srvDesc.Texture2D = new Texture2DSrv
{
MipLevels = mipLevels,
};
break;
case TextureDimension.Texture3D:
srvDesc.ViewDimension = SrvDimension.Texture3D;
srvDesc.Texture3D = new Texture3DSrv
{
MipLevels = 0,
};
break;
case TextureDimension.Texture2DArray:
srvDesc.ViewDimension = SrvDimension.Texture2DArray;
srvDesc.Texture2DArray = new Texture2DArraySrv
{
MipLevels = mipLevels,
ArraySize = arraySize,
};
break;
case TextureDimension.TextureCube:
srvDesc.ViewDimension = SrvDimension.TextureCube;
srvDesc.TextureCube = new TexureCubeSrv
{
MipLevels = mipLevels,
};
break;
case TextureDimension.TextureCubeArray:
srvDesc.ViewDimension = SrvDimension.TextureCubeArray;
srvDesc.TextureCubeArray = new TexureCubeArraySrv
{
MipLevels = mipLevels,
NumCubes = arraySize / 6,
};
break;
default:
throw new ArgumentException($"Unsupported texture dimension for SRV: {dimension}");
}
_device->CreateShaderResourceView(pResource, &srvDesc, descriptorHandle);
}
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, bool isTemp = false)
{
CheckTexture2DSize(desc.Width, desc.Height);
var d3d12Format = ConvertTextureFormat(desc.Format);
var mipLevels = desc.MipLevels == 0 ? (ushort)(1 + Math.Floor(Math.Log2(Math.Max(desc.Width, desc.Height)))) : (ushort)desc.MipLevels;
var resourceFlags = ConvertTextureUsage(desc.Usage);
var resourceDesc = desc.Dimension switch
{
TextureDimension.Texture2D => ResourceDescription.Tex2D(
@@ -104,24 +159,34 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
desc.Width,
desc.Height,
mipLevels: mipLevels,
flags: ConvertTextureUsage(desc.Usage)),
flags: resourceFlags),
TextureDimension.Texture3D => ResourceDescription.Tex3D(
d3d12Format,
desc.Width,
desc.Height,
(ushort)desc.Slice,
flags: ConvertTextureUsage(desc.Usage)),
//case TextureDimension.TextureCube:
// break;
flags: resourceFlags),
TextureDimension.TextureCube => ResourceDescription.Tex2D(
d3d12Format,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: 6,
flags: resourceFlags),
TextureDimension.Texture2DArray => ResourceDescription.Tex2D(
d3d12Format,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: (ushort)desc.Slice,
flags: ConvertTextureUsage(desc.Usage)),
//case TextureDimension.TextureCubeArray:
// break;
flags: resourceFlags),
TextureDimension.TextureCubeArray => ResourceDescription.Tex2D(
d3d12Format,
desc.Width,
desc.Height,
mipLevels: mipLevels,
arraySize: (ushort)(desc.Slice * 6),
flags: resourceFlags),
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
};
@@ -136,58 +201,57 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, initialState, null, &allocation, IID_NULL, null));
var handle = TrackResource(in allocation, initialState, tempResource);
if (desc.CreationFlags.HasFlag(TextureCreationFlags.Bindless))
var resourceDescriptor = D3D12ResourceDescriptor.Invalid;
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
{
var descriptorHandle = _descriptorAllocator.AllocateBindless();
var srvDesc = new ShaderResourceViewDescription
{
Format = d3d12Format,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
switch (desc.Dimension)
{
case TextureDimension.Texture2D:
srvDesc.ViewDimension = SrvDimension.Texture2D;
srvDesc.Texture2D = new Texture2DSrv
{
MipLevels = mipLevels,
};
break;
case TextureDimension.Texture3D:
srvDesc.ViewDimension = SrvDimension.Texture3D;
srvDesc.Texture3D = new Texture3DSrv
{
MipLevels = 0,
};
break;
case TextureDimension.Texture2DArray:
srvDesc.ViewDimension = SrvDimension.Texture2DArray;
srvDesc.Texture2DArray = new Texture2DArraySrv
{
MipLevels = mipLevels,
ArraySize = desc.Slice,
};
break;
default:
throw new ArgumentException($"Unsupported texture dimension for SRV: {desc.Dimension}");
}
_device->CreateShaderResourceView(allocation.Resource, &srvDesc, _descriptorAllocator.GetCpuHandle(descriptorHandle));
CreateSRV(allocation.Resource, cpuHandle, d3d12Format, desc.Dimension, mipLevels, desc.Slice);
}
return new(handle);
if (desc.Usage.HasFlag(TextureUsage.RenderTarget))
{
resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV(isTemp);
var rtvDesc = new RenderTargetViewDescription(allocation.Resource);
_device->CreateRenderTargetView(allocation.Resource, &rtvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv));
}
if (desc.Usage.HasFlag(TextureUsage.DepthStencil))
{
resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV(isTemp);
var dsvDesc = new DepthStencilViewDescription(allocation.Resource);
_device->CreateDepthStencilView(allocation.Resource, &dsvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv));
}
if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess))
{
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var uavDesc = new UnorderedAccessViewDescription
{
ViewDimension = UavDimension.Texture2D,
Format = d3d12Format,
Texture2D = new Texture2DUav
{
MipSlice = 0,
PlaneSlice = 0,
}
};
_device->CreateUnorderedAccessView(allocation.Resource, null, &uavDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav));
}
var handle = TrackResource(ref allocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), isTemp);
return handle.AsTexture();
}
public TextureHandle CreateRenderTarget(ref readonly RenderTargetDesc desc, bool tempResource = false)
public Handle<Texture> CreateRenderTarget(ref readonly RenderTargetDesc desc, bool isTemp = false)
{
var textureDesc = RenderTargetDesc.ToTextureDescriptor(desc);
return CreateTexture(ref textureDesc, tempResource);
var textureDesc = RenderTargetDesc.ToTextureDescripton(desc);
return CreateTexture(ref textureDesc, isTemp);
}
public BufferHandle CreateBuffer(ref readonly BufferDesc desc, bool tempResource = false)
public Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, bool isTemp = false)
{
CheckBufferSize((uint)desc.Size);
@@ -203,12 +267,10 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Allocation allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null));
var handle = TrackResource(in allocation, initialState, tempResource);
var resourceDescriptor = D3D12ResourceDescriptor.Invalid;
if (desc.Usage.HasFlag(BufferUsage.ShaderResource) && desc.CreationFlags.HasFlag(BufferCreationFlags.Bindless))
{
var isRaw = desc.Usage.HasFlag(BufferUsage.Raw);
var descriptorHandle = _descriptorAllocator.AllocateBindless();
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var srvDesc = new ShaderResourceViewDescription
{
@@ -216,7 +278,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
};
if (isRaw)
if (desc.Usage.HasFlag(BufferUsage.Raw))
{
srvDesc.Format = Format.R32Typeless;
srvDesc.Buffer.FirstElement = 0;
@@ -233,15 +295,14 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
srvDesc.Buffer.Flags = BufferSrvFlags.None;
}
_device->CreateShaderResourceView(allocation.Resource, &srvDesc, _descriptorAllocator.GetCpuHandle(descriptorHandle));
return new(handle, descriptorHandle);
_device->CreateShaderResourceView(allocation.Resource, &srvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv));
}
return new(handle);
var handle = TrackResource(ref allocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), isTemp);
return handle.AsGraphicsBuffer();
}
public BufferHandle CreateUploadBuffer(ulong size, bool temp = true)
public Handle<GraphicsBuffer> CreateUploadBuffer(ulong size, bool isTemp = true)
{
var desc = new BufferDesc
{
@@ -250,14 +311,14 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
MemoryType = MemoryType.Upload,
};
return CreateBuffer(in desc, temp);
return CreateBuffer(ref desc, isTemp);
}
public Identifier<Mesh> CreateMesh(ReadOnlySpan<Vertex> vertices, ReadOnlySpan<uint> indices)
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
{
var vertexBufferDesc = new BufferDesc
{
Size = (ulong)(vertices.Length * Unsafe.SizeOf<Vertex>()),
Size = (ulong)(vertices.Count * Unsafe.SizeOf<Vertex>()),
Stride = (uint)Unsafe.SizeOf<Vertex>(),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
@@ -266,35 +327,42 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
var indexBufferDesc = new BufferDesc
{
Size = (ulong)(indices.Length * sizeof(uint)),
Size = (ulong)(indices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
CreationFlags = BufferCreationFlags.Bindless
};
var vertexBuffer = CreateBuffer(ref vertexBufferDesc, true);
var indexBuffer = CreateBuffer(ref indexBufferDesc, true);
var vertexBuffer = CreateBuffer(ref vertexBufferDesc);
var indexBuffer = CreateBuffer(ref indexBufferDesc);
var data = new Mesh(vertices, indices, vertexBuffer, indexBuffer);
return _resourceDatabase.AddMesh(in data);
var data = new Mesh
{
vertices = vertices,
indices = indices,
vertexBuffer = vertexBuffer,
indexBuffer = indexBuffer,
};
return _resourceDatabase.AddMesh(ref data);
}
public Identifier<Material> CreateMaterial(Identifier<Shader> shader)
public Handle<Material> CreateMaterial(Identifier<Shader> shader)
{
var materialData = new Material
{
Shader = shader,
};
var shaderResource = _resourceDatabase.GetShader(shader);
ref var shaderRef = ref _resourceDatabase.GetShaderReference(shader);
if (shaderResource.ConstantBuffers.Count > 0)
if (shaderRef.ConstantBuffers.Count > 0)
{
var maxSlot = shaderResource.ConstantBuffers.Max(cb => cb.RegisterSlot);
var maxSlot = shaderRef.ConstantBuffers.Max(cb => cb.RegisterSlot);
materialData._cBufferCaches = new UnsafeArray<CBufferCache>((int)maxSlot + 1, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
foreach (var cbufferInfo in shaderResource.ConstantBuffers)
foreach (var cbufferInfo in shaderRef.ConstantBuffers)
{
var desc = new BufferDesc
{
@@ -303,13 +371,19 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
MemoryType = MemoryType.Default,
};
var buffer = CreateBuffer(in desc);
var buffer = CreateBuffer(ref desc);
materialData._cBufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(buffer, cbufferInfo.Size);
}
}
return _resourceDatabase.AddMaterial(in materialData);
return _resourceDatabase.AddMaterial(ref materialData);
}
public Identifier<Shader> CreateShader()
{
var shaderData = new Shader();
return _resourceDatabase.AddShader(ref shaderData);
}
#region Conversion Methods
@@ -461,43 +535,39 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
#endregion
public void ReleaseTempResource()
public void ReleaseTempResources()
{
while (_temResources.Count > 0)
{
var handle = _temResources.Peek();
ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
if (exist && info.Allocated && info.cpuFenceValue > _renderSystem.GPUFenceValue)
if (!exist || !info.Allocated)
{
// Resource already released or invalid, just dequeue
_temResources.Dequeue();
continue;
}
if (info.cpuFenceValue > _renderSystem.GPUFenceValue)
{
// Resource still in use by GPU, stop processing.
// Since resources are enqueued in order, we can break here.
break;
}
ReleaseResource(handle);
_resourceDatabase.ReleaseResource(handle);
_temResources.Dequeue();
}
}
public void ReleaseResource(ResourceHandle handle)
public void ReleaseResource(Handle<GPUResource> handle)
{
if (!handle.IsValid)
{
return;
}
ref var info = ref _resourceDatabase.GetResourceInfo(handle, out var exist);
if (!exist || !info.Allocated)
{
return;
}
info.Dispose();
_resourceDatabase.RemoveResource(handle);
_resourceDatabase.ReleaseResource(handle);
}
public void Dispose()
{
#if DEBUG
#if DEBUG || GHOST_EDITOR
if (_temResources.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_temResources.Count} temp allocations still registered. Ensure all resources are released before disposing.");

View File

@@ -3,6 +3,8 @@ using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
using Win32;
using Win32.Graphics.D3D12MemoryAllocator;
using Win32.Graphics.Direct3D12;
@@ -10,42 +12,100 @@ namespace Ghost.Graphics.D3D12;
internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
{
internal unsafe struct ResourceInfo
{
[StructLayout(LayoutKind.Explicit)]
private struct ResourceUnion
{
[FieldOffset(0)]
public Allocation allocation;
[FieldOffset(0)]
public ComPtr<ID3D12Resource> resource;
public ResourceUnion(Allocation allocation)
{
this.allocation = allocation;
this.resource = default;
}
public ResourceUnion(ComPtr<ID3D12Resource> resource)
{
this.resource = resource;
this.allocation = default;
}
}
private ResourceUnion _resourceUnion;
private readonly bool _isExternal;
public D3D12ResourceDescriptor descriptor;
public uint cpuFenceValue;
public ResourceState state;
public ResourceDesc desc;
public readonly bool Allocated => _isExternal ? _resourceUnion.resource.Get() != null : _resourceUnion.allocation.IsNotNull;
public readonly ID3D12Resource* ResourcePtr => _isExternal ? _resourceUnion.resource.Get() : _resourceUnion.allocation.Resource;
public ResourceInfo(in Allocation allocation, uint cpuFenceValue, ResourceState state, D3D12ResourceDescriptor resourceDescriptor, ResourceDesc desc)
{
this._resourceUnion = new ResourceUnion(allocation);
this._isExternal = false;
this.descriptor = resourceDescriptor;
this.cpuFenceValue = cpuFenceValue;
this.state = state;
this.desc = desc;
}
public ResourceInfo(ComPtr<ID3D12Resource> resource, ResourceState state)
{
this._resourceUnion = new ResourceUnion(resource);
this._isExternal = true;
this.descriptor = default;
this.cpuFenceValue = ~0u;
this.state = state;
this.desc = ResourceDesc.FromD3D12(resource.Get()->GetDesc());
}
public uint Release()
{
var refCount = 0u;
if (Allocated)
{
if (_isExternal)
{
refCount = _resourceUnion.resource.Get()->Release();
}
else
{
refCount = _resourceUnion.allocation.Release();
}
_resourceUnion = default;
descriptor = default;
}
return refCount;
}
}
private struct Slot<T>
{
public T value;
public bool isValid;
}
public struct ResourceInfo : IDisposable
{
public readonly Allocation allocation;
public readonly uint cpuFenceValue;
public ResourceState state;
public readonly bool Allocated => allocation.IsNotNull;
public ResourceInfo(in Allocation allocation, uint cpuFenceValue, ResourceState state)
{
this.allocation = allocation;
this.cpuFenceValue = cpuFenceValue;
this.state = state;
}
public readonly void Dispose()
{
if (allocation.IsNull)
{
return;
}
allocation.Release();
}
public bool occupied;
}
private UnsafeSlotMap<ResourceInfo> _resources;
#if DEBUG || GHOST_EDITOR
private readonly Dictionary<ResourceInfo, string> _resourceName;
#endif
// NOTE: We use a simple list for shaders since they are not frequently added/removed. This can save 4 bytes for each ecs component.
private readonly DynamicArray<Slot<Mesh>> _meshDatas;
private readonly UnsafeSlotMap<Mesh> _meshes;
private readonly UnsafeSlotMap<Material> _materials;
// NOTE: We use a simple list since shader is not frequently added/removed. This can save 4 bytes for each ecs component.
private readonly DynamicArray<Slot<Shader>> _shaders;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
@@ -55,8 +115,12 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
{
_resources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
#if DEBUG || GHOST_EDITOR
_resourceName = new(64);
#endif
_meshDatas = new(64);
_meshes = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_materials = new(16, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_shaders = new(16);
_descriptorAllocator = descriptorAllocator;
@@ -67,15 +131,29 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
Dispose();
}
public ResourceHandle AddResource(ref readonly Allocation allocation, uint cpuFenceValue, ResourceState initialState)
public unsafe Handle<GPUResource> ImportExternalResource<T>(T resource, ResourceState initialState)
where T : unmanaged
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _resources.Add(new ResourceInfo(allocation, cpuFenceValue, initialState), out var generation);
return new ResourceHandle(id, generation);
if (resource is not ComPtr<ID3D12Resource> d3d12Resource)
{
throw new InvalidOperationException($"Expect ComPtr<ID3D12Resource> in D3D12ResourceDatabase, but got {typeof(T)}.");
}
var id = _resources.Add(new ResourceInfo(d3d12Resource, initialState), out var generation);
return new Handle<GPUResource>(id, generation);
}
public ref ResourceInfo GetResourceInfo(ResourceHandle handle)
public Handle<GPUResource> AddResource(ref readonly Allocation allocation, uint cpuFenceValue, ResourceState initialState, D3D12ResourceDescriptor resourceDescriptor, ResourceDesc desc)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _resources.Add(new ResourceInfo(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation);
return new Handle<GPUResource>(id, generation);
}
public ref ResourceInfo GetResourceInfo(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -88,56 +166,37 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return ref info;
}
public ref ResourceInfo GetResourceInfo(ResourceHandle handle, out bool exist)
public ref ResourceInfo GetResourceInfo(Handle<GPUResource> handle, out bool exist)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return ref _resources.GetElementReferenceAt(handle.id, handle.generation, out exist);
}
public unsafe T* GetResource<T>(ResourceHandle handle)
where T : unmanaged
public unsafe ID3D12Resource* GetResource(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (typeof(T) != typeof(ID3D12Resource))
{
return null;
}
var info = GetResourceInfo(handle);
ref var info = ref GetResourceInfo(handle);
if (!info.Allocated)
{
return null;
}
return (T*)info.allocation.Resource;
return info.ResourcePtr;
}
public unsafe ID3D12Resource* GetResource(ResourceHandle handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var info = GetResourceInfo(handle);
if (!info.Allocated)
{
return null;
}
return info.allocation.Resource;
}
public ResourceState GetResourceState(ResourceHandle handle)
public ResourceState GetResourceState(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return GetResourceInfo(handle).state;
}
public void SetResourceState(ResourceHandle handle, ResourceState state)
public void SetResourceState(Handle<GPUResource> handle, ResourceState state)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist || info.Allocated == false)
if (!exist || !info.Allocated)
{
throw new KeyNotFoundException($"Resource with handle {handle} not found.");
}
@@ -145,138 +204,170 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
info.state = state;
}
public void RemoveResource(ResourceHandle handle)
public ResourceDesc GetResourceDescription(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return GetResourceInfo(handle).desc;
}
public int GetBindlessIndex(Handle<GPUResource> handle)
{
ref var info = ref GetResourceInfo(handle, out var exist);
if (!exist || !info.Allocated)
{
return -1;
}
return info.descriptor.srv.value;
}
public unsafe void ReleaseResource(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist || info.Allocated == false)
if (!handle.IsValid)
{
return;
}
info.Dispose();
ref var info = ref _resources.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist || !info.Allocated)
{
return;
}
var refCount = info.Release();
#if DEBUG || GHOST_EDITOR
if (refCount > 0)
{
throw new GPUResourceLeakException(refCount, info.ResourcePtr, _resourceName[info]);
}
#endif
_resources.Remove(handle.id, handle.generation);
}
public Identifier<Mesh> AddMesh(ref readonly Mesh mesh)
public Handle<Mesh> AddMesh(ref readonly Mesh mesh)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = new Identifier<Mesh>(_meshDatas.Count);
_meshDatas.Add(new()
{
value = mesh,
isValid = true
});
return id;
var id = _meshes.Add(mesh, out var generation);
return new Handle<Mesh>(id, generation);
}
public bool HasMesh(Identifier<Mesh> id)
public bool HasMesh(Handle<Mesh> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.value >= 0 && id.value < _meshDatas.Count && _meshDatas[id.value].isValid;
return _meshes.Contain(handle.id, handle.generation);
}
public Mesh GetMesh(Identifier<Mesh> id)
public ref Mesh GetMeshReference(Handle<Mesh> handle)
{
if (!HasMesh(id))
ref var mesh = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
throw new ArgumentOutOfRangeException(nameof(id), $"Mesh ID {id.value} is out of range.");
throw new ArgumentOutOfRangeException(nameof(handle), $"Mesh {handle} is invalid.");
}
return _meshDatas[id.value].value;
return ref mesh;
}
public ref Mesh GetMeshReference(Identifier<Mesh> id)
private void ReleaseMeshResources(ref readonly Mesh mesh)
{
if (!HasMesh(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Mesh ID {id.value} is out of range.");
}
return ref _meshDatas[id.value].value;
}
public void RemoveMesh(Identifier<Mesh> id)
{
if (!HasMesh(id))
{
return;
}
ref var meshSlot = ref _meshDatas[id.value];
if (!meshSlot.isValid)
{
return;
}
ref var mesh = ref meshSlot.value;
mesh.ReleaseCpuResources();
RemoveResource(mesh.vertexBuffer.ResourceHandle);
RemoveResource(mesh.indexBuffer.ResourceHandle);
ref var vertexRef = ref GetResourceInfo(mesh.vertexBuffer.AsResource());
ref var indexRef = ref GetResourceInfo(mesh.indexBuffer.AsResource());
_descriptorAllocator.Release(vertexRef.descriptor);
_descriptorAllocator.Release(indexRef.descriptor);
_descriptorAllocator.Release(mesh.vertexBuffer.BindlessDescriptor);
_descriptorAllocator.Release(mesh.indexBuffer.BindlessDescriptor);
ReleaseResource(mesh.vertexBuffer.AsResource());
ReleaseResource(mesh.indexBuffer.AsResource());
}
public void ReleaseMesh(Handle<Mesh> handle)
{
ref var meshSlot = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
return;
}
ReleaseMeshResources(ref meshSlot);
_meshes.Remove(handle.id, handle.generation);
}
public Handle<Material> AddMaterial(ref readonly Material material)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _materials.Add(material, out var generation);
return new Handle<Material>(id, generation);
}
public bool HasMaterial(Handle<Material> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return _materials.Contain(handle.id, handle.generation);
}
public ref Material GetMaterialReference(Handle<Material> handle)
{
ref var material = ref _materials.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
throw new ArgumentOutOfRangeException(nameof(handle), $"Material handle {handle} is invalid.");
}
return ref material;
}
public void ReleaseMaterial(Handle<Material> handle)
{
ref var material = ref _materials.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
return;
}
material.Dispose();
}
public Identifier<Shader> AddShader(ref readonly Shader shader)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = new Identifier<Shader>(_shaders.Count);
_shaders.Add(new()
{
value = shader,
isValid = true
});
return id;
var id = _shaders.Count;
_shaders.Add(new Slot<Shader> { value = shader, occupied = true });
return new Identifier<Shader>(id);
}
public bool HasShader(Identifier<Shader> id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return id.value >= 0 && id.value < _shaders.Count && _shaders[id.value].isValid;
}
public Shader GetShader(Identifier<Shader> id)
{
if (!HasShader(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Shader ID {id.value} is out of range.");
}
return _shaders[id.value].value;
return id.value >= 0 && id.value < _shaders.Count && _shaders[id.value].occupied;
}
public ref Shader GetShaderReference(Identifier<Shader> id)
{
if (!HasShader(id))
{
throw new ArgumentOutOfRangeException(nameof(id), $"Shader ID {id.value} is out of range.");
throw new ArgumentOutOfRangeException(nameof(id), $"Shader id {id} is invalid.");
}
return ref _shaders[id.value].value;
ref var shader = ref _shaders[id.value].value;
return ref shader;
}
public void RemoveShader(Identifier<Shader> id)
public void ReleaseShader(Identifier<Shader> handle)
{
if (!HasShader(id))
if (!HasShader(handle))
{
return;
}
ref var shader = ref _shaders[id.value];
shader.value.Dispose();
shader.value = default;
shader.isValid = false;
ref var shader = ref _shaders[handle.value].value;
shader.Dispose();
}
public void Dispose()
@@ -286,15 +377,20 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return;
}
#if DEBUG
#if DEBUG || GHOST_EDITOR
if (_resources.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_resources.Count} allocations still registered. Ensure all resources are released before disposing.");
}
if (_meshDatas.Count > 0)
if (_meshes.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_meshDatas.Count} meshes still registered. Ensure all meshes are released before disposing.");
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_meshes.Count} meshes still registered. Ensure all meshes are released before disposing.");
}
if (_materials.Count > 0)
{
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_materials.Count} materials still registered. Ensure all materials are released before disposing.");
}
if (_shaders.Count > 0)
@@ -302,34 +398,9 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_shaders.Count} shaders still registered. Ensure all shaders are released before disposing.");
}
#endif
_resources.Dispose();
foreach (var mesh in _meshDatas)
{
if (!mesh.isValid)
{
continue;
}
mesh.value.ReleaseCpuResources();
RemoveResource(mesh.value.vertexBuffer.ResourceHandle);
RemoveResource(mesh.value.indexBuffer.ResourceHandle);
_descriptorAllocator.Release(mesh.value.vertexBuffer.BindlessDescriptor);
_descriptorAllocator.Release(mesh.value.indexBuffer.BindlessDescriptor);
}
foreach (var shader in _shaders)
{
if (!shader.isValid)
{
continue;
}
shader.value.Dispose();
}
_meshes.Dispose();
_materials.Dispose();
_disposed = true;

View File

@@ -0,0 +1,26 @@
using Ghost.Core;
namespace Ghost.Graphics.D3D12;
internal readonly struct RTVDesc : IIdentifierType;
internal readonly struct DSVDesc : IIdentifierType;
internal readonly struct CbvSrvUavDesc : IIdentifierType;
internal readonly struct SamplerDesc : IIdentifierType;
internal struct D3D12ResourceDescriptor
{
public Identifier<RTVDesc> rtv;
public Identifier<DSVDesc> dsv;
public Identifier<CbvSrvUavDesc> srv;
public Identifier<CbvSrvUavDesc> cbv;
public Identifier<CbvSrvUavDesc> uav;
public Identifier<SamplerDesc> sampler;
public static D3D12ResourceDescriptor Invalid => new()
{
rtv = Identifier<RTVDesc>.Invalid,
dsv = Identifier<DSVDesc>.Invalid,
srv = Identifier<CbvSrvUavDesc>.Invalid,
sampler = Identifier<SamplerDesc>.Invalid,
};
}

View File

@@ -1,7 +1,6 @@
using Ghost.Graphics.Data;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
using System.Text;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D.Dxc;
@@ -182,7 +181,14 @@ internal unsafe static class D3D12ShaderCompiler
}
}
public static void PerformDXCReflection(Shader shader, IDxcBlob* reflectionBlob)
private static void AddProperty(ref Shader shader, string name, PropertyInfo propertyInfo)
{
var id = shader.Properties.Count;
shader.Properties.Add(propertyInfo);
shader.PropertyNameToIdMap[name] = id;
}
public static void PerformDXCReflection(ref Shader shader, IDxcBlob* reflectionBlob)
{
// Create DXC utils to parse reflection data
using ComPtr<IDxcUtils> utils = default;
@@ -255,7 +261,7 @@ internal unsafe static class D3D12ShaderCompiler
Size = varDesc.Size
};
shader.AddProperty(variableName, propInfo);
AddProperty(ref shader, variableName, propInfo);
}
break;

View File

@@ -1,6 +1,10 @@
using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using Win32;
using Win32.Graphics.Direct3D12;
@@ -14,8 +18,10 @@ namespace Ghost.Graphics.D3D12;
/// </summary>
internal unsafe class D3D12SwapChain : ISwapChain
{
private readonly D3D12ResourceDatabase _resourceDatabase;
private ComPtr<IDXGISwapChain4> _swapChain;
private readonly D3D12RenderTarget[] _backBuffers;
private UnsafeArray<Handle<Texture>> _backBuffers;
private bool _disposed;
public uint Width
@@ -33,9 +39,11 @@ internal unsafe class D3D12SwapChain : ISwapChain
get;
}
public D3D12SwapChain(IDXGIFactory7* pFactory, ID3D12CommandQueue* pCommandQueue, SwapChainDesc desc)
public D3D12SwapChain(D3D12ResourceDatabase resourceDatabase, IDXGIFactory7* pFactory, ID3D12CommandQueue* pCommandQueue, SwapChainDesc desc)
{
_backBuffers = new D3D12RenderTarget[D3D12PipelineResource.BACK_BUFFER_COUNT];
_resourceDatabase = resourceDatabase;
_backBuffers = new UnsafeArray<Handle<Texture>>(D3D12PipelineResource.BACK_BUFFER_COUNT, Allocator.Persistent);
Width = desc.width;
Height = desc.height;
@@ -64,15 +72,15 @@ internal unsafe class D3D12SwapChain : ISwapChain
using ComPtr<IDXGISwapChain1> tempSwapChain = default;
switch (desc.target.Type)
switch (desc.target.type)
{
case SwapChainTargetType.Composition:
pFactory->CreateSwapChainForComposition((IUnknown*)commandQueue, &swapChainDesc, null, tempSwapChain.GetAddressOf());
// Set the composition surface
if (desc.target.CompositionSurface != null)
if (desc.target.compositionSurface != null)
{
using var swapChainPanelNative = ISwapChainPanelNative.FromSwapChainPanel(desc.target.CompositionSurface);
using var swapChainPanelNative = ISwapChainPanelNative.FromSwapChainPanel(desc.target.compositionSurface);
swapChainPanelNative.SetSwapChain((IntPtr)tempSwapChain.Get());
}
break;
@@ -85,7 +93,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
pFactory->CreateSwapChainForHwnd(
(IUnknown*)commandQueue,
desc.target.WindowHandle,
desc.target.windowHandle,
&swapChainDesc,
&swapChainFullscreenDesc,
null,
@@ -110,12 +118,12 @@ internal unsafe class D3D12SwapChain : ISwapChain
_swapChain.Get()->GetBuffer(i, __uuidof<ID3D12Resource>(), backBuffer.GetVoidAddressOf());
backBuffer.Get()->SetName($"SwapChain_BackBuffer_{i}");
_backBuffers[i] = D3D12RenderTarget.Create(backBuffer.Move(), Width, Height, 1, TextureFormat.B8G8R8A8_UNorm, RenderTargetType.Color);
_backBuffers[i] = _resourceDatabase.ImportExternalResource(backBuffer.Move(), ResourceState.Present).AsTexture();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IRenderTarget GetCurrentBackBuffer()
public Handle<Texture> GetCurrentBackBuffer()
{
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()];
}
@@ -134,12 +142,14 @@ internal unsafe class D3D12SwapChain : ISwapChain
public void Resize(uint width, uint height)
{
if (Width == width && Height == height)
{
return;
}
// Release old back buffers and render targets
for (var i = 0; i < _backBuffers.Length; i++)
for (var i = 0; i < _backBuffers.Count; i++)
{
_backBuffers[i]?.Dispose();
_resourceDatabase.ReleaseResource(_backBuffers[i].AsResource());
}
// Resize the swap chain
@@ -174,12 +184,13 @@ internal unsafe class D3D12SwapChain : ISwapChain
return;
}
for (var i = 0; i < _backBuffers.Length; i++)
for (var i = 0; i < _backBuffers.Count; i++)
{
_backBuffers[i]?.Dispose();
_resourceDatabase.ReleaseResource(_backBuffers[i].AsResource());
}
_swapChain.Dispose();
_backBuffers.Dispose();
_disposed = true;
}
}

View File

@@ -1,140 +0,0 @@
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// D3D12 implementation of texture interface using resource handles
/// </summary>
internal unsafe class D3D12Texture : ITexture
{
private readonly TextureHandle _handle;
private readonly ComPtr<ID3D12Resource> _externalResource;
private ResourceState _currentState;
private bool _disposed;
public uint Width
{
get;
}
public uint Height
{
get;
}
public uint Slice
{
get;
}
public TextureFormat Format
{
get;
}
public uint MipLevels
{
get;
}
public string Name
{
get; set;
} = string.Empty;
public ulong Size
{
get;
}
public ResourceState CurrentState => _currentState;
public ID3D12Resource* NativeResource => _handle.IsValid ? _handle.ResourceHandle.GetAllocation().Resource : _externalResource.Get();
public D3D12Texture(ComPtr<ID3D12Resource> resource, uint width, uint height, uint slice, TextureFormat format, uint mipLevels = 1)
{
_handle = TextureHandle.Invalid;
_externalResource = resource.Move();
Width = width;
Height = height;
Slice = slice;
Format = format;
MipLevels = mipLevels;
_currentState = ResourceState.Common;
Size = Width * Height * GetBytesPerPixel(Format);
}
public D3D12Texture(TextureHandle handle, ref readonly TextureDesc desc)
{
_handle = handle;
_externalResource = default;
Width = desc.Width;
Height = desc.Height;
Slice = desc.Slice;
Format = desc.Format;
var mipLevels = desc.MipLevels;
if (mipLevels <= 0)
{
mipLevels = (uint)(Math.Floor(Math.Log2(Math.Max(Width, Height))) + 1);
}
MipLevels = mipLevels;
_currentState = ResourceState.Common;
Size = Width * Height * GetBytesPerPixel(Format);
}
~D3D12Texture()
{
Dispose(false);
}
private static uint GetBytesPerPixel(TextureFormat format)
{
return format switch
{
TextureFormat.R8G8B8A8_UNorm => 4,
TextureFormat.B8G8R8A8_UNorm => 4,
TextureFormat.R16G16B16A16_Float => 8,
TextureFormat.R32G32B32A32_Float => 16,
TextureFormat.D24_UNorm_S8_UInt => 4,
TextureFormat.D32_Float => 4,
_ => 4
};
}
public void SetCurrentState(ResourceState state)
{
_currentState = state;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (_handle.IsValid)
{
_handle.Dispose();
}
else
{
_externalResource.Dispose();
}
_disposed = true;
}
}

View File

@@ -1,119 +0,0 @@
using Ghost.Graphics.Data;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
public unsafe class GraphicsBuffer : GraphicsResource
{
public enum Usage
{
Common,
Vertex,
Index,
CopySource,
CopyDestination,
Structured,
Raw,
Append,
Counter,
Indirect,
Constant,
}
private readonly Usage _usage;
public Usage BufferUsage => _usage;
private GraphicsBuffer(Usage usage, in BufferHandle handle, bool tempResource = false)
: base(handle.ResourceHandle, tempResource)
{
_usage = usage;
}
public static GraphicsBuffer Create(uint sizeInBytes, Usage usage, bool tempResource = false)
{
var heapType = HeapType.Default;
var state = ResourceStates.Common;
switch (usage)
{
case Usage.Vertex:
heapType = HeapType.Default;
state = ResourceStates.VertexAndConstantBuffer;
break;
case Usage.Index:
heapType = HeapType.Default;
state = ResourceStates.IndexBuffer;
break;
case Usage.CopySource:
heapType = HeapType.Readback;
state = ResourceStates.CopySource;
break;
case Usage.CopyDestination:
heapType = HeapType.Default;
state = ResourceStates.CopyDest;
break;
case Usage.Structured:
case Usage.Raw:
case Usage.Append:
case Usage.Counter:
heapType = HeapType.Default;
state = ResourceStates.AllShaderResource | ResourceStates.UnorderedAccess;
break;
case Usage.Indirect:
heapType = HeapType.Default;
state = ResourceStates.IndirectArgument;
break;
case Usage.Constant:
heapType = HeapType.Upload;
state = ResourceStates.GenericRead;
break;
default:
break;
}
var handle = GraphicsPipeline.ResourceAllocator.CreateBuffer(sizeInBytes, heapType, initialState: state, tempResource: tempResource);
return new GraphicsBuffer(usage, in handle, tempResource);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetData<T>(Span<T> data, uint offset)
where T : unmanaged
{
fixed (T* ptr = data)
{
SetData(ptr, offset, (uint)data.Length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void SetData<T>(T* data, uint offset, uint length)
where T : unmanaged
{
var size = (uint)(length * sizeof(T));
SetData((void*)data, offset, size);
}
public unsafe void SetData(void* data, uint offset, uint size)
{
ThrowIfDisposed();
if (data == null)
{
throw new ArgumentNullException(nameof(data), "Data pointer cannot be null.");
}
if (size > Size)
{
throw new ArgumentException($"Data size {size} exceeds buffer size {Size}.", nameof(size));
}
var range = new Win32.Graphics.Direct3D12.Range(offset, size);
void* mappedPtr;
ThrowIfFailed(NativeResource.Ptr->Map(0, &range, &mappedPtr));
Unsafe.CopyBlock(mappedPtr, data, size);
NativeResource.Ptr->Unmap(0, &range);
}
}

View File

@@ -1,99 +0,0 @@
using Ghost.Core;
using Win32;
using Win32.Graphics.Direct3D;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// Legacy D3D12 GraphicsDevice - DEPRECATED
/// Use D3D12RenderDevice instead for new code
/// This class remains for compatibility during migration
/// </summary>
[Obsolete("Use D3D12RenderDevice instead")]
internal unsafe class GraphicsDevice
{
private ComPtr<IDXGIFactory7> _dxgiFactory;
private ComPtr<ID3D12Device14> _device;
private ComPtr<IDXGIAdapter1> _adapter;
private ComPtr<ID3D12CommandQueue> _commandQueue;
private bool _disposed;
public ConstPtr<ID3D12Device14> NativeDevice => new(_device.Get());
public ConstPtr<IDXGIFactory7> DXGIFactory => new(_dxgiFactory.Get());
public ConstPtr<IDXGIAdapter1> Adapter => new(_adapter.Get());
public ConstPtr<ID3D12CommandQueue> CommandQueue => new(_commandQueue.Get());
public GraphicsDevice()
{
InitializeDevice();
InitializeCommandQueue();
}
private void InitializeDevice()
{
#if DEBUG
CreateDXGIFactory2(true, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf());
#else
CreateDXGIFactory2(false, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf());
#endif
using ComPtr<IDXGIAdapter1> adapter = default;
for (uint adapterIndex = 0;
_dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, GpuPreference.HighPerformance, __uuidof<IDXGIAdapter1>(), adapter.ReleaseAndGetVoidAddressOf()).Success;
adapterIndex++)
{
AdapterDescription1 desc = default;
adapter.Get()->GetDesc1(&desc);
// Don't select the Basic Render Driver adapter.
if ((desc.Flags & AdapterFlags.Software) != AdapterFlags.None)
{
continue;
}
if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_12_0, __uuidof<ID3D12Device14>(), _device.GetVoidAddressOf()).Success)
{
_adapter = adapter.Move();
break;
}
}
if (_device.Get() == null)
{
throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0");
}
}
private void InitializeCommandQueue()
{
var queueDesc = new CommandQueueDescription
{
Type = CommandListType.Direct,
Priority = (int)CommandQueuePriority.High,
Flags = CommandQueueFlags.None,
};
fixed (void* queuePtr = &_commandQueue)
{
_device.Get()->CreateCommandQueue(&queueDesc, __uuidof<ID3D12CommandQueue>(), (void**)queuePtr);
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_commandQueue.Dispose();
_device.Reset();
_dxgiFactory.Dispose();
_disposed = true;
}
}

View File

@@ -1,75 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
public unsafe class GraphicsResource : IDisposable
{
private readonly ResourceHandle _handle;
private string _name = string.Empty;
internal ConstPtr<ID3D12Resource> NativeResource
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(_handle.GetAllocation().Resource);
}
internal ulong GPUAddress
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => NativeResource.Ptr->GetGPUVirtualAddress();
}
public string Name
{
get => _name;
set
{
_name = value;
NativeResource.Ptr->SetName(_name);
}
}
public bool TempResource
{
get;
}
public ulong Size => _handle.GetAllocation().Size;
internal GraphicsResource(in ResourceHandle handle, bool tempResource = false)
{
_handle = handle;
TempResource = tempResource;
}
~GraphicsResource()
{
DisposeInternal();
}
/// <summary>
/// Throws an exception if the resource has been disposed.
/// </summary>
protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(!_handle.IsValid, this);
}
internal void DisposeInternal()
{
_handle.Dispose();
}
public virtual void Dispose()
{
if (!TempResource)
{
DisposeInternal();
}
GC.SuppressFinalize(this);
}
}

View File

@@ -1,361 +0,0 @@
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using System.Collections.Immutable;
using Win32;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
using Win32.Graphics.Dxgi.Common;
using Win32.Numerics;
namespace Ghost.Graphics.D3D12;
// TODO: We should split the renderer and swap chain into different classes to allow for more flexibility in rendering pipelines.
// Each renderer can have a render target (swap chain or texture).
// When render target is null, skip the render pass execution.
/// <summary>
/// Legacy D3D12 Renderer - DEPRECATED
/// Use D3D12Renderer instead for new code
/// This class remains for compatibility during migration
/// </summary>
[Obsolete("Use D3D12Renderer instead")]
internal unsafe class Renderer
{
private struct FrameResource : IDisposable
{
public ComPtr<ID3D12CommandAllocator> commandAllocator;
public ComPtr<ID3D12GraphicsCommandList10> commandList;
public ComPtr<ID3D12Resource> backBuffer;
public CommandList cmd;
public RenderTargetDescriptor rtvDescriptor;
public ulong fenceValue;
public FrameResource(Renderer renderer, uint index)
{
renderer._graphicsDevice.NativeDevice.Ptr->CreateCommandAllocator(CommandListType.Direct, __uuidof<ID3D12CommandAllocator>(), commandAllocator.GetVoidAddressOf());
renderer._graphicsDevice.NativeDevice.Ptr->CreateCommandList(0u, CommandListType.Direct, commandAllocator.Get(), null, __uuidof<ID3D12GraphicsCommandList10>(), commandList.GetVoidAddressOf());
cmd = new(commandList.Get());
rtvDescriptor = renderer.CreateBackBufferResource(index, backBuffer.GetAddressOf());
}
public readonly void ResetCommandBuffer()
{
commandAllocator.Get()->Reset();
commandList.Get()->Reset(commandAllocator.Get(), null);
}
public readonly void ExecuteCommandBuffer(ID3D12CommandQueue* queue)
{
commandList.Get()->Close();
var commandListPtr = (ID3D12CommandList*)commandList.Get();
queue->ExecuteCommandLists(1, &commandListPtr);
}
public void IncrementFenceValue()
{
fenceValue++;
}
public void Dispose()
{
commandAllocator.Dispose();
commandList.Dispose();
backBuffer.Dispose();
GraphicsPipeline.DescriptorAllocator.Release(rtvDescriptor);
}
}
private readonly GraphicsDevice _graphicsDevice;
private readonly SwapChainPresenter _swapChainPresenter;
private ComPtr<IDXGISwapChain4> _swapChain = default;
private ComPtr<ID3D12Fence1> _fence = default;
private uint _backBufferIndex;
private readonly FrameResource[] _frameResources;
private readonly AutoResetEvent _fenceEvent;
private ImmutableArray<IRenderPass> _renderPasses;
private readonly Lock _lock = new();
private uint _viewPortWidth;
private uint _viewPortHeight;
private uint _pendingWidth;
private uint _pendingHeight;
private bool _resizeRequested;
private bool _disposed;
public ReadOnlySpan<IRenderPass> RenderPasses => _renderPasses.AsSpan();
public Renderer(GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface)
{
_graphicsDevice = graphicsDevice;
_swapChainPresenter = swapChainSurface;
_viewPortWidth = swapChainSurface.Width;
_viewPortHeight = swapChainSurface.Height;
_fenceEvent = new(false);
_renderPasses = [];
InitializeSwapChain();
InitializeFrameResource(out _frameResources);
}
private void InitializeSwapChain()
{
var swapChainDesc = new SwapChainDescription1
{
Width = _swapChainPresenter.Width,
Height = _swapChainPresenter.Height,
Format = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT,
SampleDesc = new SampleDescription(1, 0),
BufferUsage = Usage.BackBuffer | Usage.RenderTargetOutput,
BufferCount = GraphicsPipeline._FRAME_COUNT,
Scaling = Scaling.Stretch,
SwapEffect = SwapEffect.FlipDiscard,
AlphaMode = AlphaMode.Ignore,
Flags = SwapChainFlags.AllowTearing,
Stereo = false,
};
using ComPtr<IDXGISwapChain1> tempSwapChain = default;
switch (_swapChainPresenter.Type)
{
case SwapChainPresenter.TargetType.Composition:
_graphicsDevice.DXGIFactory.Ptr->CreateSwapChainForComposition((IUnknown*)_graphicsDevice.CommandQueue.Ptr, &swapChainDesc, null, tempSwapChain.GetAddressOf());
break;
case SwapChainPresenter.TargetType.Hwnd:
var swapChainFullscreenDesc = new SwapChainFullscreenDescription
{
Windowed = true,
};
_graphicsDevice.DXGIFactory.Ptr->CreateSwapChainForHwnd(
(IUnknown*)_graphicsDevice.CommandQueue.Ptr,
_swapChainPresenter.Hwnd,
&swapChainDesc,
&swapChainFullscreenDesc,
null,
tempSwapChain.GetAddressOf());
break;
default:
throw new ArgumentException("Unsupported swap chain surface type.");
}
if (tempSwapChain.Get()->QueryInterface(__uuidof<IDXGISwapChain4>(), _swapChain.GetVoidAddressOf()).Failure)
{
throw new InvalidOperationException("Failed to create IDXGISwapChain4 interface.");
}
if (_swapChainPresenter.Type == SwapChainPresenter.TargetType.Composition)
{
_swapChainPresenter.SwapChainPanelNative.SetSwapChain((IntPtr)_swapChain.Get());
}
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
}
private void InitializeFrameResource(out FrameResource[] frameResources)
{
frameResources = new FrameResource[GraphicsPipeline._FRAME_COUNT];
for (var i = 0u; i < GraphicsPipeline._FRAME_COUNT; i++)
{
frameResources[i] = new FrameResource(this, i);
}
for (var i = 1u; i < GraphicsPipeline._FRAME_COUNT; i++)
{
ref var frameResource = ref frameResources[i];
frameResource.commandList.Get()->Close();
}
_graphicsDevice.NativeDevice.Ptr->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf());
frameResources[0].IncrementFenceValue();
}
public void RequestResize(uint width, uint height)
{
lock (_lock)
{
if (_pendingWidth == width && _pendingHeight == height)
{
return;
}
_resizeRequested = true;
_pendingWidth = width;
_pendingHeight = height;
}
}
private RenderTargetDescriptor CreateBackBufferResource(uint i, ID3D12Resource** backBuffer)
{
_swapChain.Get()->GetBuffer(i, __uuidof<ID3D12Resource>(), (void**)backBuffer);
(*backBuffer)->SetName($"BackBuffer_{i}");
var rtvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateRTV();
var rtvHandle = rtvDescriptor.CpuHandle;
_graphicsDevice.NativeDevice.Ptr->CreateRenderTargetView(*backBuffer, null, rtvHandle);
return rtvDescriptor;
}
public void ExecutePendingResize()
{
if (!_resizeRequested)
{
return;
}
uint newWidth;
uint newHeight;
lock (_lock)
{
newWidth = _pendingWidth;
newHeight = _pendingHeight;
_resizeRequested = false;
}
WaitIdle();
for (var i = 0; i < GraphicsPipeline._FRAME_COUNT; i++)
{
ref var frameResource = ref _frameResources[i];
if (frameResource.backBuffer.Get() is not null)
{
var c = frameResource.backBuffer.Reset();
GraphicsPipeline.DescriptorAllocator.Release(frameResource.rtvDescriptor);
}
frameResource.fenceValue = _frameResources[_backBufferIndex].fenceValue;
}
if (_swapChain.Get()->ResizeBuffers(GraphicsPipeline._FRAME_COUNT, newWidth, newHeight, Format.B8G8R8A8Unorm, SwapChainFlags.AllowTearing).Failure)
{
throw new InvalidOperationException("Failed to resize swap chain buffers.");
}
for (var i = 0u; i < GraphicsPipeline._FRAME_COUNT; i++)
{
var index = CreateBackBufferResource(i, _frameResources[i].backBuffer.GetAddressOf());
_frameResources[i].rtvDescriptor = index;
}
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
_viewPortWidth = newWidth;
_viewPortHeight = newHeight;
}
public void Initialize()
{
ref var frameResource = ref _frameResources[_backBufferIndex];
foreach (var pass in _renderPasses)
{
pass.Initialize(frameResource.cmd);
}
frameResource.ExecuteCommandBuffer(_graphicsDevice.CommandQueue);
WaitIdle();
}
public void Render()
{
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
ref var frameResource = ref _frameResources[_backBufferIndex];
var cpuHandle = frameResource.rtvDescriptor.CpuHandle;
frameResource.ResetCommandBuffer();
frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.Present, ResourceStates.RenderTarget);
var clearColor = stackalloc float[4] { 1.0f, 0.0f, 1.0f, 1.0f };
frameResource.commandList.Get()->ClearRenderTargetView(cpuHandle, clearColor, 0, null);
var viewPort = new Viewport(_viewPortWidth, _viewPortHeight);
var rect = new Rect(0, 0, (int)_viewPortWidth, (int)_viewPortHeight);
frameResource.commandList.Get()->RSSetViewports(1, &viewPort);
frameResource.commandList.Get()->RSSetScissorRects(1, &rect);
frameResource.commandList.Get()->OMSetRenderTargets(1, &cpuHandle, false, null);
foreach (var pass in _renderPasses)
{
pass.Execute(frameResource.cmd);
}
frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.RenderTarget, ResourceStates.Present);
frameResource.ExecuteCommandBuffer(_graphicsDevice.CommandQueue.Ptr);
if (_swapChain.Get()->Present(1, PresentFlags.None).Failure)
{
throw new InvalidOperationException("Failed to present swap chain.");
}
WaitNextFrame();
}
public void WaitNextFrame()
{
ref var resource = ref _frameResources[_backBufferIndex];
if (_graphicsDevice.CommandQueue.Ptr->Signal((ID3D12Fence*)_fence.Get(), resource.fenceValue).Failure)
{
return;
}
var handle = new Handle((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
if (_fence.Get()->GetCompletedValue() < resource.fenceValue
&& _fence.Get()->SetEventOnCompletion(resource.fenceValue, handle).Success)
{
_fenceEvent.WaitOne();
}
resource.IncrementFenceValue();
}
public void WaitIdle()
{
ref var resource = ref _frameResources[_backBufferIndex];
_graphicsDevice.CommandQueue.Ptr->Signal((ID3D12Fence*)_fence.Get(), resource.fenceValue);
var handle = new Handle((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
if (_fence.Get()->SetEventOnCompletion(resource.fenceValue, handle).Success)
{
_fenceEvent.WaitOne();
resource.IncrementFenceValue();
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
WaitIdle();
_swapChainPresenter.SwapChainPanelNative.SetSwapChain(IntPtr.Zero);
foreach (var pass in _renderPasses)
{
pass.Dispose();
}
foreach (var frameResource in _frameResources)
{
frameResource.Dispose();
}
_swapChain.Dispose();
_fence.Dispose();
_fenceEvent.Dispose();
_backBufferIndex = 0;
_disposed = true;
}
}

View File

@@ -1,309 +0,0 @@
using System.Runtime.CompilerServices;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12;
/// <summary>
/// Resource upload batch for efficiently uploading resources to GPU memory
/// </summary>
internal unsafe class ResourceUploadBatch : IDisposable
{
private ComPtr<ID3D12CommandAllocator> _commandAllocator;
private ComPtr<ID3D12GraphicsCommandList10> _commandList;
private ComPtr<ID3D12Fence> _fence;
private readonly GraphicsDevice _device = GraphicsPipeline.GraphicsDevice;
private readonly AutoResetEvent _fenceEvent = new(false);
private ulong _fenceValue;
private bool _isRecording;
private bool _disposed;
/// <summary>
/// Gets whether the batch is currently recording commands
/// </summary>
public bool IsRecording => _isRecording;
/// <summary>
/// Creates a new ResourceUploadBatch
/// </summary>
internal ResourceUploadBatch()
{
Initialize();
}
~ResourceUploadBatch()
{
Dispose();
}
private void Initialize()
{
ThrowIfFailed(_device.NativeDevice.Ptr->CreateCommandAllocator(
CommandListType.Direct,
__uuidof<ID3D12CommandAllocator>(),
_commandAllocator.GetVoidAddressOf()));
ThrowIfFailed(_device.NativeDevice.Ptr->CreateCommandList1(
0,
CommandListType.Direct,
CommandListFlags.None,
__uuidof<ID3D12GraphicsCommandList10>(),
_commandList.GetVoidAddressOf()));
ThrowIfFailed(_device.NativeDevice.Ptr->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence>(), _fence.GetVoidAddressOf()));
}
/// <summary>
/// Begins recording upload commands
/// </summary>
public void Begin()
{
if (_isRecording)
{
throw new InvalidOperationException("Upload batch is already recording");
}
ThrowIfFailed(_commandAllocator.Get()->Reset());
ThrowIfFailed(_commandList.Get()->Reset(_commandAllocator.Get(), null));
_isRecording = true;
}
/// <summary>
/// Uploads buffer data to a resource
/// </summary>
/// <typeparam name="T">Type of data to upload</typeparam>
/// <param name="resource">Destination resource</param>
/// <param name="data">Source data</param>
public void Upload<T>(ID3D12Resource* resource, ReadOnlySpan<T> data)
where T : unmanaged
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
var sizeInBytes = (uint)(data.Length * sizeof(T));
var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(sizeInBytes, true);
void* mappedData;
var uploadResource = uploadBuffer.ResourceHandle.GetAllocation().Resource;
uploadResource->Map(0, null, &mappedData);
fixed (T* dataPtr = data)
{
Unsafe.CopyBlock(mappedData, dataPtr, sizeInBytes);
}
uploadResource->Unmap(0, null);
// Copy from upload buffer to destination
_commandList.Get()->CopyBufferRegion(
resource,
0,
uploadResource,
0,
sizeInBytes);
}
/// <summary>
/// Uploads subresource data to a texture
/// </summary>
/// <param name="resource">Destination texture resource</param>
/// <param name="firstSubresource">First subresource index</param>
/// <param name="subresources">Subresource data array</param>
/// <param name="numSubresources">Number of subresources</param>
public void Upload(ID3D12Resource* resource, uint firstSubresource, SubresourceData* subresources, uint numSubresources)
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
var resourceDesc = resource->GetDesc();
var requiredSize = GetRequiredIntermediateSize(resource, firstSubresource, numSubresources);
var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer((uint)requiredSize, true);
UpdateSubresources(
resource,
uploadBuffer.ResourceHandle.GetAllocation().Resource,
0,
firstSubresource,
numSubresources,
subresources);
}
/// <summary>
/// Adds a resource transition barrier
/// </summary>
/// <param name="resource">Resource to transition</param>
/// <param name="stateBefore">State before transition</param>
/// <param name="stateAfter">State after transition</param>
public void Transition(ID3D12Resource* resource, ResourceStates stateBefore, ResourceStates stateAfter)
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
_commandList.Get()->ResourceBarrierTransition(resource, stateBefore, stateAfter);
}
/// <summary>
/// Generates mipmaps for a texture (if supported)
/// </summary>
/// <param name="resource">Texture resource</param>
public void GenerateMips(GraphicsResource resource)
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
// This would require compute shader implementation for mipmap generation
// For now, this is a placeholder - DirectXTK12 uses a compute shader approach
throw new NotImplementedException("Mipmap generation not yet implemented");
}
/// <summary>
/// Ends recording and submits the batch for execution
/// </summary>
/// <returns>Future that completes when upload is finished</returns>
public ulong End()
{
if (!_isRecording)
{
throw new InvalidOperationException("Upload batch is not recording");
}
ThrowIfFailed(_commandList.Get()->Close());
_device.CommandQueue.Ptr->ExecuteCommandLists(1, (ID3D12CommandList**)_commandList.GetAddressOf());
ThrowIfFailed(_device.CommandQueue.Ptr->Signal(_fence.Get(), ++_fenceValue));
_isRecording = false;
return _fenceValue;
}
/// <summary>
/// Waits for the upload batch to complete
/// </summary>
/// <param name="fenceValue">Fence value to wait for</param>
public void WaitForCompletion(ulong fenceValue)
{
if (_fence.Get()->GetCompletedValue() < fenceValue)
{
var handle = new Handle((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
ThrowIfFailed(_fence.Get()->SetEventOnCompletion(fenceValue, handle));
_fenceEvent.WaitOne();
}
}
private ulong GetRequiredIntermediateSize(ID3D12Resource* destinationResource, uint firstSubresource, uint numSubresources)
{
var resourceDesc = destinationResource->GetDesc();
ulong requiredSize = 0;
var numRows = stackalloc uint[(int)numSubresources];
var rowSizeInBytes = stackalloc ulong[(int)numSubresources];
_device.NativeDevice.Ptr->GetCopyableFootprints(
&resourceDesc,
firstSubresource,
numSubresources,
0,
null,
numRows,
rowSizeInBytes,
&requiredSize);
return requiredSize;
}
private void UpdateSubresources(ID3D12Resource* destinationResource, ID3D12Resource* intermediate, ulong intermediateOffset, uint firstSubresource, uint numSubresources, SubresourceData* pSubresourceData)
{
var destDesc = destinationResource->GetDesc();
var layouts = stackalloc PlacedSubresourceFootprint[(int)numSubresources];
var numRows = stackalloc uint[(int)numSubresources];
var rowSizeInBytes = stackalloc ulong[(int)numSubresources];
ulong requiredSize = 0;
_device.NativeDevice.Ptr->GetCopyableFootprints(
&destDesc,
firstSubresource,
numSubresources,
intermediateOffset,
layouts,
numRows,
rowSizeInBytes,
&requiredSize);
void* pMappedData;
ThrowIfFailed(intermediate->Map(0, null, &pMappedData));
for (uint i = 0; i < numSubresources; i++)
{
var srcData = pSubresourceData[i];
var destLayout = layouts[i];
var pDestSlice = (byte*)pMappedData + destLayout.Offset;
var pSrcSlice = (byte*)srcData.pData;
for (uint y = 0; y < numRows[i]; y++)
{
var pDestRow = pDestSlice + (y * destLayout.Footprint.RowPitch);
var pSrcRow = pSrcSlice + (y * srcData.RowPitch);
Unsafe.CopyBlockUnaligned(pDestRow, pSrcRow, (uint)rowSizeInBytes[i]);
}
}
intermediate->Unmap(0, null);
if (destDesc.Dimension == ResourceDimension.Buffer)
{
_commandList.Get()->CopyBufferRegion(destinationResource, 0, intermediate, intermediateOffset, requiredSize);
}
else
{
for (uint i = 0; i < numSubresources; i++)
{
var destLocation = new TextureCopyLocation(destinationResource, firstSubresource + i);
var srcLocation = new TextureCopyLocation(intermediate, in layouts[i]);
var box = new Box
{
left = 0,
top = 0,
front = 0,
right = (uint)destDesc.Width,
bottom = destDesc.Height,
back = destDesc.DepthOrArraySize
};
_commandList.Get()->CopyTextureRegion(&destLocation, 0, 0, 0, &srcLocation, &box);
}
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
if (_isRecording)
{
_commandList.Get()->Close();
_isRecording = false;
}
_fence.Dispose();
_commandList.Dispose();
_commandAllocator.Dispose();
_fenceEvent.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
}

View File

@@ -1,215 +0,0 @@
using Ghost.Core;
using System.Diagnostics;
using Win32;
using Win32.Graphics.Direct3D12;
using DescriptorIndex = System.UInt32;
namespace Ghost.Graphics.D3D12.Utilities;
/// <summary>
/// Specialized descriptor heap allocator for SM 6.6 bindless rendering with ResourceDescriptorHeap[index].
/// This allocator maintains a 1:1 relationship between allocation indices and shader indices.
/// </summary>
internal unsafe struct BindlessDescriptorHeap : IDisposable
{
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
private readonly ComPtr<ID3D12Device14> _device;
private readonly Lock _lock = new();
private ComPtr<ID3D12DescriptorHeap> _bindlessHeap;
private CpuDescriptorHandle _startCpuHandle;
private GpuDescriptorHandle _startGpuHandle;
private Queue<uint> _freeDescriptors;
private uint _stride;
public DescriptorHeapType HeapType
{
get;
}
public uint NumDescriptors
{
get; private set;
}
public uint NumAllocatedDescriptors
{
get; private set;
}
public uint Stride => _stride;
public readonly ConstPtr<ID3D12DescriptorHeap> BindlessHeap => new(_bindlessHeap.Get());
public BindlessDescriptorHeap(ComPtr<ID3D12Device14> device, uint numDescriptors = 10000)
{
_device = device;
device.Get()->AddRef();
HeapType = DescriptorHeapType.CbvSrvUav;
NumDescriptors = numDescriptors;
_stride = device.Get()->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav);
_freeDescriptors = new Queue<uint>();
var success = AllocateResources(numDescriptors);
Debug.Assert(success);
_bindlessHeap.Get()->SetName("bindless");
}
public DescriptorIndex AllocateDescriptor()
{
lock (_lock)
{
if (_freeDescriptors.Count == 0)
{
// Try to grow the heap
if (!Grow(NumDescriptors * 2))
{
return _INVALID_DESCRIPTOR_INDEX;
}
}
var index = _freeDescriptors.Dequeue();
NumAllocatedDescriptors++;
return index;
}
}
public DescriptorIndex AllocateDescriptors(uint count)
{
lock (_lock)
{
if (_freeDescriptors.Count < count)
{
// Try to grow the heap
var newSize = Math.Max(NumDescriptors * 2, NumDescriptors + count);
if (!Grow(newSize))
{
return _INVALID_DESCRIPTOR_INDEX;
}
}
var baseIndex = _freeDescriptors.Dequeue();
for (uint i = 1; i < count; i++)
{
_freeDescriptors.Dequeue();
}
NumAllocatedDescriptors += count;
return baseIndex;
}
}
public void ReleaseDescriptor(DescriptorIndex index)
{
lock (_lock)
{
if (index >= NumDescriptors)
{
return;
}
_freeDescriptors.Enqueue(index);
NumAllocatedDescriptors--;
}
}
public void ReleaseDescriptors(DescriptorIndex baseIndex, uint count = 1)
{
lock (_lock)
{
for (uint i = 0; i < count; i++)
{
var index = baseIndex + i;
if (index >= NumDescriptors)
{
continue;
}
_freeDescriptors.Enqueue(index);
}
NumAllocatedDescriptors -= count;
}
}
public readonly CpuDescriptorHandle GetCpuHandle(DescriptorIndex index)
{
var handle = _startCpuHandle;
return handle.Offset((int)index, _stride);
}
public readonly GpuDescriptorHandle GetGpuHandle(DescriptorIndex index)
{
var handle = _startGpuHandle;
return handle.Offset((int)index, _stride);
}
public readonly GpuDescriptorHandle GetGpuHandleStart()
{
return _startGpuHandle;
}
private bool AllocateResources(uint numDescriptors)
{
NumDescriptors = numDescriptors;
_bindlessHeap.Dispose();
var heapDesc = new DescriptorHeapDescription
{
Type = HeapType,
NumDescriptors = numDescriptors,
Flags = DescriptorHeapFlags.ShaderVisible, // Must be shader visible for SM 6.6
NodeMask = 0
};
fixed (void* heapPtr = &_bindlessHeap)
{
var hr = _device.Get()->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
if (hr.Failure)
{
return false;
}
}
_startCpuHandle = _bindlessHeap.Get()->GetCPUDescriptorHandleForHeapStart();
_startGpuHandle = _bindlessHeap.Get()->GetGPUDescriptorHandleForHeapStart();
// Initialize free descriptor queue
_freeDescriptors.Clear();
for (uint i = 0; i < numDescriptors; i++)
{
_freeDescriptors.Enqueue(i);
}
return true;
}
private bool Grow(uint minRequiredSize)
{
var oldSize = NumDescriptors;
var newSize = Math.Max(minRequiredSize, oldSize * 2);
var oldHeap = _bindlessHeap;
if (!AllocateResources(newSize))
{
return false;
}
// Copy old descriptors to new heap
if (oldHeap.Get() is not null)
{
_device.Get()->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
}
return true;
}
public void Dispose()
{
_bindlessHeap.Dispose();
}
}

View File

@@ -1,16 +1,17 @@
using System.Diagnostics;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics;
using System.Numerics;
using Win32;
using Win32.Graphics.Direct3D12;
using DescriptorIndex = System.UInt32;
using DescriptorIndex = System.Int32;
namespace Ghost.Graphics.D3D12.Utilities;
internal unsafe struct D3D12DescriptorHeap : IDisposable
{
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = -1;
private readonly ID3D12Device14* _pDevice;
private readonly D3D12RenderDevice _device;
private ComPtr<ID3D12DescriptorHeap> _heap;
private ComPtr<ID3D12DescriptorHeap> _shaderVisibleHeap;
@@ -18,7 +19,10 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
private CpuDescriptorHandle _startCpuHandleShaderVisible;
private GpuDescriptorHandle _startGpuHandleShaderVisible;
private DescriptorIndex _searchStart;
private bool[] _allocatedDescriptors = [];
private UnsafeArray<bool> _allocatedDescriptors;
private readonly DescriptorIndex _dynamicHeapStart;
private DescriptorIndex _currentDynamicOffset;
private readonly Lock _lock = new();
@@ -27,12 +31,12 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
get;
}
public uint NumDescriptors
public int NumDescriptors
{
get; private set;
}
public uint NumAllocatedDescriptors
public int NumAllocatedDescriptors
{
get; private set;
}
@@ -50,14 +54,19 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
public readonly ID3D12DescriptorHeap* Heap => _heap.Get();
public readonly ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.Get();
public D3D12DescriptorHeap(string name, ID3D12Device14* device, DescriptorHeapType type, uint numDescriptors)
public D3D12DescriptorHeap(string name, D3D12RenderDevice device, DescriptorHeapType type, int numDescriptors, int dynamicHeapStart)
{
_pDevice = device;
numDescriptors = Math.Max(64, numDescriptors);
_device = device;
HeapType = type;
NumDescriptors = numDescriptors;
ShaderVisible = type == DescriptorHeapType.CbvSrvUav || type == DescriptorHeapType.Sampler;
Stride = device->GetDescriptorHandleIncrementSize(type);
Stride = device.NativeDevice->GetDescriptorHandleIncrementSize(type);
_dynamicHeapStart = Math.Clamp(dynamicHeapStart, 0, numDescriptors);
_currentDynamicOffset = 0;
var success = AllocateResources(numDescriptors);
Debug.Assert(success);
@@ -71,11 +80,11 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
public DescriptorIndex AllocateDescriptor() => AllocateDescriptors(1);
public DescriptorIndex AllocateDescriptors(uint count)
public DescriptorIndex AllocateDescriptors(int count)
{
lock (_lock)
{
DescriptorIndex foundIndex = 0;
var foundIndex = 0;
uint freeCount = 0;
var found = false;
@@ -99,15 +108,10 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
}
}
if (!found)
if (!found || foundIndex >= _dynamicHeapStart)
{
foundIndex = NumDescriptors;
if (!Grow(NumDescriptors + count))
{
Debug.WriteLine("ERROR: Failed to grow a descriptor heap!");
return _INVALID_DESCRIPTOR_INDEX;
}
Debug.Assert(false, "ERROR: Descriptor heap is full!");
return _INVALID_DESCRIPTOR_INDEX;
}
for (var index = foundIndex; index < foundIndex + count; index++)
@@ -121,20 +125,58 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
}
}
public DescriptorIndex AllocateDescriptorDynamic() => AllocateDescriptorsDynamic(1);
public DescriptorIndex AllocateDescriptorsDynamic(int count)
{
if (count <= 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "Count must be greater than zero.");
}
// NOTE: In dynamic allocation, we use arena-style allocation without freeing.
// We reset the offset at the beginning of each frame instead.
lock (_lock)
{
var baseIndex = _currentDynamicOffset + _dynamicHeapStart;
_currentDynamicOffset += count;
var requiredSize = baseIndex + count;
if (requiredSize > NumDescriptors)
{
if (!Grow(requiredSize))
{
Debug.Assert(false, "ERROR: Failed to grow a descriptor heap!");
return _INVALID_DESCRIPTOR_INDEX;
}
}
NumAllocatedDescriptors += count;
return baseIndex;
}
}
public void ReleaseDescriptor(DescriptorIndex index) => ReleaseDescriptors(index, 1);
public void ReleaseDescriptors(DescriptorIndex baseIndex, uint count = 1)
public void ReleaseDescriptors(DescriptorIndex baseIndex, int count = 1)
{
if (count == 0)
{
return;
}
if (baseIndex < _dynamicHeapStart)
{
// Dynamic allocations are not released individually.
return;
}
lock (_lock)
{
for (var index = baseIndex; index < baseIndex + count; index++)
{
#if DEBUG
#if DEBUG || GHOST_EDITOR
if (!_allocatedDescriptors[index])
{
Debug.WriteLine("Error: Attempted to release an un-allocated descriptor");
@@ -153,30 +195,73 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
}
}
public void ResetDynamicHeap()
{
lock (_lock)
{
_currentDynamicOffset = 0;
}
}
public readonly CpuDescriptorHandle GetCpuHandle(DescriptorIndex index)
{
var handle = _startCpuHandle;
return handle.Offset((int)index, Stride);
if (index < 0 || index >= NumDescriptors)
{
throw new ArgumentOutOfRangeException(nameof(index), "Descriptor index is out of range.");
}
return _startCpuHandle.Offset(index, Stride);
}
public readonly CpuDescriptorHandle GetCpuHandleShaderVisible(DescriptorIndex index)
{
var handle = _startCpuHandleShaderVisible;
return handle.Offset((int)index, Stride);
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.");
}
return _startCpuHandleShaderVisible.Offset(index, Stride);
}
public readonly GpuDescriptorHandle GetGpuHandle(DescriptorIndex index)
{
var handle = _startGpuHandleShaderVisible;
return handle.Offset((int)index, Stride);
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.");
}
return _startGpuHandleShaderVisible.Offset(index, Stride);
}
public readonly void CopyToShaderVisibleHeap(DescriptorIndex index, uint count = 1)
public DescriptorIndex CopyToPersistentHeap(DescriptorIndex index, int count = 1)
{
_pDevice->CopyDescriptorsSimple(count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
if (index < _dynamicHeapStart)
{
return index;
}
var newLocation = AllocateDescriptors(count);
_device.NativeDevice->CopyDescriptorsSimple((uint)count, GetCpuHandle(index), GetCpuHandle(newLocation), HeapType);
return newLocation;
}
private bool AllocateResources(uint numDescriptors)
public readonly void CopyToShaderVisibleHeap(DescriptorIndex index, int count = 1)
{
_device.NativeDevice->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
}
private bool AllocateResources(int numDescriptors)
{
NumDescriptors = numDescriptors;
_heap.Dispose();
@@ -185,14 +270,14 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
DescriptorHeapDescription heapDesc = new()
{
Type = HeapType,
NumDescriptors = numDescriptors,
NumDescriptors = (uint)numDescriptors,
Flags = DescriptorHeapFlags.None,
NodeMask = 0
};
fixed (void* heapPtr = &_heap)
{
var hr = _pDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
if (hr.Failure)
{
return false;
@@ -200,7 +285,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
}
_startCpuHandle = _heap.Get()->GetCPUDescriptorHandleForHeapStart();
Array.Resize(ref _allocatedDescriptors, (int)numDescriptors);
_allocatedDescriptors.Resize(numDescriptors);
if (ShaderVisible)
{
@@ -208,7 +293,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
fixed (void* heapPtr = &_shaderVisibleHeap)
{
var hr = _pDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
if (hr.Failure)
{
return false;
@@ -222,23 +307,23 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
return true;
}
private bool Grow(uint minRequiredSize)
private bool Grow(int minRequiredSize)
{
var oldSize = NumDescriptors;
var newSize = BitOperations.RoundUpToPowerOf2(minRequiredSize);
var newSize = (int)BitOperations.RoundUpToPowerOf2((uint)minRequiredSize);
var oldHeap = _heap;
using var oldHeap = _heap;
if (!AllocateResources(newSize))
{
return false;
}
_pDevice->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
_device.NativeDevice->CopyDescriptorsSimple((uint)oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
if (_shaderVisibleHeap.Get() is not null)
if (_shaderVisibleHeap.Get() != null)
{
_pDevice->CopyDescriptorsSimple(oldSize, _startCpuHandleShaderVisible, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
_device.NativeDevice->CopyDescriptorsSimple((uint)oldSize, _startCpuHandleShaderVisible, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
}
return true;
@@ -247,7 +332,15 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
/// <inheritdoc />
public void Dispose()
{
#if DEBUG
if (NumAllocatedDescriptors > 0)
{
Debug.WriteLine($"Warning: Descriptor heap of type {HeapType} is being disposed with {NumAllocatedDescriptors} allocated descriptors.");
}
#endif
_heap.Dispose();
_shaderVisibleHeap.Dispose();
_allocatedDescriptors.Dispose();
}
}