forked from Misaki/GhostEngine
Refactoring Rendering backend
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
324
Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs
Normal file
324
Ghost.Graphics/D3D12/D3D12PipelineLibrary.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
26
Ghost.Graphics/D3D12/D3D12ResourceDescriptor.cs
Normal file
26
Ghost.Graphics/D3D12/D3D12ResourceDescriptor.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user