forked from Misaki/GhostEngine
Refactor graphics architecture and resource management
Added DescriptorAllocator.cs to manage descriptor allocations for Direct3D 12. Added Texture2D.cs to handle 2D textures and GPU resource creation. Added DescriptorAllocatorExample.cs to demonstrate the new descriptor allocator interface. Changed project files to reference Misaki.HighPerformance.LowLevel instead of Misaki.HighPerformance.Unsafe. Changed _renderView type from IRenderer? to Renderer? in ScenePage.xaml.cs. Changed EngineCore.cs to remove explicit graphics API specification during initialization. Changed Logger.cs to enhance the Assert method with a DoesNotReturnIf attribute. Changed resource types in Mesh.cs from IResource to GraphicsResource. Removed multiple interfaces including ICommandBuffer, IDebugLayer, IGraphicsDevice, IPipelineResource, IRenderPass, IRenderer, IResource, and IResourceAllocator to simplify the graphics architecture. Removed D3D12DebugLayer class from DebugLayer.cs to streamline the debug layer implementation. Updated CommandList.cs and D3D12CommandBuffer.cs to implement a new command list structure for Direct3D 12. Updated Material.cs to improve handling of constant buffers and textures. Updated Shader.cs to include new structures for texture and property information. Updated GraphicsPipeline.cs to support the new graphics device and resource management system. Updated UnitTestAppWindow.xaml.cs to reflect changes in the renderer type and ensure proper resource management. Updated BindlessMeshRenderPass.cs and MeshRenderPass.cs to implement modern rendering techniques, including bindless textures and improved shader management. Updated CBufferCache.cs to align with the new resource management system and improve memory handling.
This commit is contained in:
59
Ghost.Graphics/D3D12/CommandList.cs
Normal file
59
Ghost.Graphics/D3D12/CommandList.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
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;
|
||||
}
|
||||
|
||||
public void BarrierTransition(GraphicsResource resource, ResourceStates beforeState, ResourceStates afterState)
|
||||
{
|
||||
_commandList.Ptr->ResourceBarrierTransition(resource.NativeResource.Ptr, beforeState, afterState);
|
||||
}
|
||||
|
||||
public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress)
|
||||
{
|
||||
_commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress);
|
||||
}
|
||||
|
||||
public void DrawMesh(Mesh mesh, Material material)
|
||||
{
|
||||
_commandList.Ptr->SetGraphicsRootSignature(material.Shader.RootSignature);
|
||||
_commandList.Ptr->SetPipelineState(material.Shader.PipelineState);
|
||||
|
||||
// Bind shader-visible descriptor heaps before setting descriptor tables
|
||||
if (material.Shader.Textures.Count > 0)
|
||||
{
|
||||
var shaderVisibleHeaps = GraphicsPipeline.DescriptorAllocator.GetShaderVisibleHeaps();
|
||||
var heapPtrs = stackalloc ID3D12DescriptorHeap*[shaderVisibleHeaps.Length];
|
||||
for (var i = 0; i < shaderVisibleHeaps.Length; i++)
|
||||
{
|
||||
heapPtrs[i] = shaderVisibleHeaps[i].Ptr;
|
||||
}
|
||||
_commandList.Ptr->SetDescriptorHeaps((uint)shaderVisibleHeaps.Length, heapPtrs);
|
||||
}
|
||||
|
||||
material.Bind(this);
|
||||
|
||||
_commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
|
||||
_commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView);
|
||||
_commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView);
|
||||
|
||||
_commandList.Ptr->DrawIndexedInstanced(mesh.IndexCount, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
public void CopyResource(GraphicsResource dstResource, uint dstOffset, GraphicsResource srcResource, uint srcOffset, uint size)
|
||||
{
|
||||
_commandList.Ptr->CopyBufferRegion(dstResource.NativeResource, dstOffset, srcResource.NativeResource, srcOffset, size);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.Data;
|
||||
using Win32.Graphics.Direct3D;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12CommandBuffer : ICommandBuffer
|
||||
{
|
||||
private readonly ConstPtr<ID3D12GraphicsCommandList10> _commandList;
|
||||
|
||||
internal ConstPtr<ID3D12GraphicsCommandList10> CommandList => _commandList;
|
||||
|
||||
public D3D12CommandBuffer(ID3D12GraphicsCommandList10* commandList)
|
||||
{
|
||||
_commandList = commandList;
|
||||
}
|
||||
|
||||
public void BarrierTransition(IResource resource, ResourceStates beforeState, ResourceStates afterState)
|
||||
{
|
||||
var dxResource = (D3D12Resource)resource;
|
||||
_commandList.Ptr->ResourceBarrierTransition(dxResource.NativeResource.Ptr, beforeState, afterState);
|
||||
}
|
||||
|
||||
public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress)
|
||||
{
|
||||
_commandList.Ptr->SetGraphicsRootConstantBufferView(slot, gpuAddress);
|
||||
}
|
||||
|
||||
public void DrawMesh(Mesh mesh, Material material)
|
||||
{
|
||||
_commandList.Ptr->SetGraphicsRootSignature(material.Shader.RootSignature);
|
||||
_commandList.Ptr->SetPipelineState(material.Shader.PipelineState);
|
||||
|
||||
material.UploadAndBind(this);
|
||||
|
||||
_commandList.Ptr->IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
|
||||
_commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView);
|
||||
_commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView);
|
||||
|
||||
_commandList.Ptr->DrawIndexedInstanced(mesh.IndexCount, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
public void CopyResource(IResource dstResource, uint dstOffset, IResource srcResource, uint srcOffset, uint size)
|
||||
{
|
||||
var dstDXResource = (D3D12Resource)dstResource;
|
||||
var srcDXResource = (D3D12Resource)srcResource;
|
||||
|
||||
_commandList.Ptr->CopyBufferRegion(dstDXResource.NativeResource, dstOffset, srcDXResource.NativeResource, srcOffset, size);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Win32;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12DebugLayer : IDebugLayer
|
||||
internal unsafe class DebugLayer
|
||||
{
|
||||
private readonly ComPtr<ID3D12Debug6> _d3d12Debug;
|
||||
private readonly ComPtr<IDXGIDebug1> _dxgiDebug;
|
||||
private readonly ComPtr<IDXGIInfoQueue> _dxgiInfoQueue;
|
||||
|
||||
public D3D12DebugLayer()
|
||||
public DebugLayer()
|
||||
{
|
||||
D3D12GetDebugInterface(__uuidof<ID3D12Debug6>(), _d3d12Debug.GetVoidAddressOf());
|
||||
_d3d12Debug.Get()->EnableDebugLayer();
|
||||
330
Ghost.Graphics/D3D12/DescriptorAllocator.cs
Normal file
330
Ghost.Graphics/D3D12/DescriptorAllocator.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of descriptor allocator that manages different types of descriptor heaps.
|
||||
/// </summary>
|
||||
internal class DescriptorAllocator : IDisposable
|
||||
{
|
||||
private readonly DescriptorHeapAllocator _rtvHeap;
|
||||
private readonly DescriptorHeapAllocator _dsvHeap;
|
||||
private readonly DescriptorHeapAllocator _srvHeap;
|
||||
private readonly DescriptorHeapAllocator _samplerHeap;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public DescriptorAllocator(uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256)
|
||||
{
|
||||
var device = GraphicsPipeline.GraphicsDevice;
|
||||
|
||||
_rtvHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.Rtv, initialRtvCount);
|
||||
_dsvHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.Dsv, initialDsvCount);
|
||||
_srvHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount);
|
||||
_samplerHeap = new DescriptorHeapAllocator(device.NativeDevice, DescriptorHeapType.Sampler, initialSamplerCount);
|
||||
}
|
||||
|
||||
#region RTV Methods
|
||||
|
||||
public RenderTargetDescriptor AllocateRTV()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _rtvHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate RTV descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _rtvHeap.GetCpuHandle(index);
|
||||
return new RenderTargetDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
public RenderTargetDescriptor[] AllocateRTVs(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _rtvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} RTV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new RenderTargetDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _rtvHeap.GetCpuHandle(index);
|
||||
descriptors[i] = new RenderTargetDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseRTV(RenderTargetDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is RenderTargetDescriptor d3d12Descriptor)
|
||||
{
|
||||
_rtvHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseRTVs(RenderTargetDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseRTV(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DSV Methods
|
||||
|
||||
public DepthStencilDescriptor AllocateDSV()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _dsvHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate DSV descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _dsvHeap.GetCpuHandle(index);
|
||||
return new DepthStencilDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
public DepthStencilDescriptor[] AllocateDSVs(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _dsvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} DSV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new DepthStencilDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _dsvHeap.GetCpuHandle(index);
|
||||
descriptors[i] = new DepthStencilDescriptor(index, cpuHandle);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseDSV(DepthStencilDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is DepthStencilDescriptor d3d12Descriptor)
|
||||
{
|
||||
_dsvHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseDSVs(DepthStencilDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseDSV(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SRV Methods
|
||||
|
||||
public ShaderResourceDescriptor AllocateSRV()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _srvHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate SRV descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _srvHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _srvHeap.GetGpuHandle(index);
|
||||
|
||||
// Copy to shader visible heap
|
||||
_srvHeap.CopyToShaderVisibleHeap(index);
|
||||
|
||||
return new ShaderResourceDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
public ShaderResourceDescriptor[] AllocateSRVs(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _srvHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} SRV descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new ShaderResourceDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _srvHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _srvHeap.GetGpuHandle(index);
|
||||
descriptors[i] = new ShaderResourceDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
// Copy all descriptors to shader visible heap
|
||||
_srvHeap.CopyToShaderVisibleHeap(baseIndex, count);
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseSRV(ShaderResourceDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is ShaderResourceDescriptor d3d12Descriptor)
|
||||
{
|
||||
_srvHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseSRVs(ShaderResourceDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseSRV(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sampler Methods
|
||||
|
||||
public SamplerDescriptor AllocateSampler()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _samplerHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate Sampler descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _samplerHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _samplerHeap.GetGpuHandle(index);
|
||||
|
||||
// Copy to shader visible heap
|
||||
_samplerHeap.CopyToShaderVisibleHeap(index);
|
||||
|
||||
return new SamplerDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
public SamplerDescriptor[] AllocateSamplers(uint count)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var baseIndex = _samplerHeap.AllocateDescriptors(count);
|
||||
if (baseIndex == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to allocate {count} Sampler descriptors");
|
||||
}
|
||||
|
||||
var descriptors = new SamplerDescriptor[count];
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var index = baseIndex + i;
|
||||
var cpuHandle = _samplerHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _samplerHeap.GetGpuHandle(index);
|
||||
descriptors[i] = new SamplerDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
// Copy all descriptors to shader visible heap
|
||||
_samplerHeap.CopyToShaderVisibleHeap(baseIndex, count);
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public void ReleaseSampler(SamplerDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is SamplerDescriptor d3d12Descriptor)
|
||||
{
|
||||
_samplerHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseSamplers(SamplerDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseSampler(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RTV heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetRTVHeap() => _rtvHeap.Heap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DSV heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetDSVHeap() => _dsvHeap.Heap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SRV heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetSRVHeap() => _srvHeap.ShaderVisibleHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sampler heap for binding to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shader visible heaps that need to be bound to the command list.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap>[] GetShaderVisibleHeaps()
|
||||
{
|
||||
return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rtvHeap.Dispose();
|
||||
_dsvHeap.Dispose();
|
||||
_srvHeap.Dispose();
|
||||
_samplerHeap.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
110
Ghost.Graphics/D3D12/Descriptors.cs
Normal file
110
Ghost.Graphics/D3D12/Descriptors.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for D3D12 descriptor implementations.
|
||||
/// </summary>
|
||||
internal abstract class Descriptor
|
||||
{
|
||||
protected readonly uint index;
|
||||
protected readonly bool isShaderVisible;
|
||||
|
||||
protected Descriptor(uint index, bool isShaderVisible)
|
||||
{
|
||||
this.index = index;
|
||||
this.isShaderVisible = isShaderVisible;
|
||||
}
|
||||
|
||||
public bool IsValid => index != uint.MaxValue;
|
||||
public uint Index => index;
|
||||
public bool IsShaderVisible => isShaderVisible;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CPU descriptor handle.
|
||||
/// </summary>
|
||||
public abstract CpuDescriptorHandle CpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GPU descriptor handle (only valid for shader-visible descriptors).
|
||||
/// </summary>
|
||||
public abstract GpuDescriptorHandle GpuHandle
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of render target view (RTV) descriptor.
|
||||
/// </summary>
|
||||
internal sealed class RenderTargetDescriptor : Descriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
|
||||
public RenderTargetDescriptor(uint index, CpuDescriptorHandle cpuHandle)
|
||||
: base(index, false)
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of depth stencil view (DSV) descriptor.
|
||||
/// </summary>
|
||||
internal sealed class DepthStencilDescriptor : Descriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
|
||||
public DepthStencilDescriptor(uint index, CpuDescriptorHandle cpuHandle)
|
||||
: base(index, false) // DSVs are not shader visible
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of shader resource view (SRV) descriptor.
|
||||
/// </summary>
|
||||
internal sealed class ShaderResourceDescriptor : Descriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
private readonly GpuDescriptorHandle _gpuHandle;
|
||||
|
||||
public ShaderResourceDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
|
||||
: base(index, true)
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
_gpuHandle = gpuHandle;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => _gpuHandle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of sampler descriptor.
|
||||
/// </summary>
|
||||
internal sealed class SamplerDescriptor : Descriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
private readonly GpuDescriptorHandle _gpuHandle;
|
||||
|
||||
public SamplerDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
|
||||
: base(index, true)
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
_gpuHandle = gpuHandle;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => _gpuHandle;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.Data;
|
||||
using System.Collections.Immutable;
|
||||
using Win32;
|
||||
@@ -9,39 +8,38 @@ using Win32.Graphics.Dxgi;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
|
||||
internal unsafe class GraphicsDevice
|
||||
{
|
||||
#if DEBUG
|
||||
private readonly D3D12DebugLayer _debugLayer;
|
||||
private readonly DebugLayer _debugLayer;
|
||||
#endif
|
||||
private ComPtr<IDXGIFactory7> _dxgiFactory;
|
||||
private ComPtr<ID3D12Device14> _device;
|
||||
private ComPtr<ID3D12CommandQueue> _commandQueue;
|
||||
|
||||
private ImmutableArray<IRenderer> _initializeQueue;
|
||||
private ImmutableArray<IRenderer> _renderers;
|
||||
private ImmutableArray<Renderer> _initializeQueue;
|
||||
private ImmutableArray<Renderer> _renderers;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public static GraphicsAPI TargetAPI => GraphicsAPI.D3D12;
|
||||
public ReadOnlySpan<IRenderer> InitializeQueue => _initializeQueue.AsSpan();
|
||||
public ReadOnlySpan<IRenderer> Renderers => _renderers.AsSpan();
|
||||
public ReadOnlySpan<Renderer> InitializeQueue => _initializeQueue.AsSpan();
|
||||
public ReadOnlySpan<Renderer> Renderers => _renderers.AsSpan();
|
||||
|
||||
public ConstPtr<ID3D12Device14> NativeDevice => new(_device.Get());
|
||||
public ConstPtr<IDXGIFactory7> DXGIFactory => new(_dxgiFactory.Get());
|
||||
public ConstPtr<ID3D12CommandQueue> CommandQueue => new(_commandQueue.Get());
|
||||
|
||||
public D3D12GraphicsDevice()
|
||||
public GraphicsDevice()
|
||||
{
|
||||
#if DEBUG
|
||||
_debugLayer = new D3D12DebugLayer();
|
||||
_debugLayer = new DebugLayer();
|
||||
#endif
|
||||
|
||||
InitializeDevice();
|
||||
InitializeCommandQueue();
|
||||
|
||||
_initializeQueue = ImmutableArray<IRenderer>.Empty;
|
||||
_renderers = ImmutableArray<IRenderer>.Empty;
|
||||
_initializeQueue = ImmutableArray<Renderer>.Empty;
|
||||
_renderers = ImmutableArray<Renderer>.Empty;
|
||||
}
|
||||
|
||||
private void InitializeDevice()
|
||||
@@ -67,7 +65,7 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
|
||||
continue;
|
||||
}
|
||||
|
||||
if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_11_0, __uuidof<ID3D12Device14>(), _device.GetVoidAddressOf()).Success)
|
||||
if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_12_0, __uuidof<ID3D12Device14>(), _device.GetVoidAddressOf()).Success)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -75,7 +73,7 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
|
||||
|
||||
if (_device.Get() == null)
|
||||
{
|
||||
throw new PlatformNotSupportedException("Cannot create ID3D12Device");
|
||||
throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,17 +92,17 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
|
||||
}
|
||||
}
|
||||
|
||||
public IRenderer CreateRenderer(in SwapChainPresenter presenter)
|
||||
public Renderer CreateRenderer(in SwapChainPresenter presenter)
|
||||
{
|
||||
var renderView = new D3D12Renderer(this, in presenter);
|
||||
var renderView = new Renderer(this, in presenter);
|
||||
ImmutableInterlocked.Update(ref _initializeQueue, old => old.Add(renderView));
|
||||
|
||||
return renderView;
|
||||
}
|
||||
|
||||
public void RemoveRenderer(IRenderer renderer)
|
||||
public void RemoveRenderer(Renderer renderer)
|
||||
{
|
||||
if (renderer is D3D12Renderer dx12RenderView)
|
||||
if (renderer is Renderer dx12RenderView)
|
||||
{
|
||||
dx12RenderView.Dispose();
|
||||
|
||||
@@ -148,13 +146,8 @@ internal unsafe class D3D12GraphicsDevice : IGraphicsDevice
|
||||
renderer.Dispose();
|
||||
}
|
||||
|
||||
foreach (var renderView in _renderers)
|
||||
{
|
||||
renderView.Dispose();
|
||||
}
|
||||
|
||||
_commandQueue.Dispose();
|
||||
_device.Dispose();
|
||||
_device.Reset();
|
||||
_dxgiFactory.Dispose();
|
||||
|
||||
#if DEBUG
|
||||
@@ -1,5 +1,4 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Win32;
|
||||
@@ -7,7 +6,7 @@ using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
public unsafe class D3D12Resource : IResource
|
||||
public unsafe class GraphicsResource
|
||||
{
|
||||
private ComPtr<ID3D12Resource> _nativeResource;
|
||||
private string _name = string.Empty;
|
||||
@@ -33,13 +32,13 @@ public unsafe class D3D12Resource : IResource
|
||||
get;
|
||||
}
|
||||
|
||||
internal D3D12Resource(ComPtr<ID3D12Resource> nativeResource, bool temp)
|
||||
internal GraphicsResource(ComPtr<ID3D12Resource> nativeResource, bool temp = false)
|
||||
{
|
||||
_nativeResource = nativeResource;
|
||||
TempResource = temp;
|
||||
}
|
||||
|
||||
~D3D12Resource()
|
||||
~GraphicsResource()
|
||||
{
|
||||
DisposeInternal();
|
||||
}
|
||||
@@ -68,12 +67,7 @@ public unsafe class D3D12Resource : IResource
|
||||
var range = new Win32.Graphics.Direct3D12.Range(0, size);
|
||||
|
||||
void* mappedPtr;
|
||||
var hr = _nativeResource.Get()->Map(0, &range, &mappedPtr);
|
||||
if (hr.Failure)
|
||||
{
|
||||
var message = hr.ToString();
|
||||
throw new InvalidOperationException($"Failed to map resource: {message}");
|
||||
}
|
||||
ThrowIfFailed(_nativeResource.Get()->Map(0, &range, &mappedPtr));
|
||||
|
||||
Unsafe.CopyBlock(mappedPtr, data, size);
|
||||
_nativeResource.Get()->Unmap(0, &range);
|
||||
@@ -99,7 +93,7 @@ public unsafe class D3D12Resource : IResource
|
||||
public void ReadData<T>(T* pData, uint* size)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadData(pData, size);
|
||||
ReadData((void*)pData, size);
|
||||
}
|
||||
|
||||
public void ReadData(void* pData, uint* size)
|
||||
@@ -14,7 +14,7 @@ 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.
|
||||
internal unsafe class D3D12Renderer : IRenderer
|
||||
internal unsafe class Renderer
|
||||
{
|
||||
private struct FrameResource : IDisposable
|
||||
{
|
||||
@@ -22,17 +22,17 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
public ComPtr<ID3D12GraphicsCommandList10> commandList;
|
||||
public ComPtr<ID3D12Resource> backBuffer;
|
||||
|
||||
public ICommandBuffer commandBuffer;
|
||||
public uint backBufferDescriptorIndexes;
|
||||
public CommandList cmd;
|
||||
public RenderTargetDescriptor rtvDescriptor;
|
||||
public ulong fenceValue;
|
||||
|
||||
public FrameResource(D3D12Renderer renderer, uint index)
|
||||
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());
|
||||
|
||||
commandBuffer = new D3D12CommandBuffer(commandList.Get());
|
||||
backBufferDescriptorIndexes = renderer.CreateBackBufferResource(index, backBuffer.GetAddressOf());
|
||||
cmd = new(commandList.Get());
|
||||
rtvDescriptor = renderer.CreateBackBufferResource(index, backBuffer.GetAddressOf());
|
||||
}
|
||||
|
||||
public readonly void ResetCommandBuffer()
|
||||
@@ -58,13 +58,11 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
commandAllocator.Dispose();
|
||||
commandList.Dispose();
|
||||
backBuffer.Dispose();
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseRTV(rtvDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private const int _RENDER_TARGET_VIEW_HEAP_SIZE = 1024;
|
||||
private const int _DEPTH_STENCIL_VIEW_HEAP_SIZE = 256;
|
||||
|
||||
private readonly D3D12GraphicsDevice _graphicsDevice;
|
||||
private readonly GraphicsDevice _graphicsDevice;
|
||||
private readonly SwapChainPresenter _swapChainPresenter;
|
||||
|
||||
private ComPtr<IDXGISwapChain4> _swapChain = default;
|
||||
@@ -74,8 +72,6 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
private readonly FrameResource[] _frameResources;
|
||||
private readonly AutoResetEvent _fenceEvent;
|
||||
|
||||
private D3D12DescriptorAllocator _rtvHeap;
|
||||
|
||||
private ImmutableArray<IRenderPass> _renderPasses;
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
@@ -89,16 +85,15 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
|
||||
public ReadOnlySpan<IRenderPass> RenderPasses => _renderPasses.AsSpan();
|
||||
|
||||
public D3D12Renderer(D3D12GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface)
|
||||
public Renderer(GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface)
|
||||
{
|
||||
_graphicsDevice = graphicsDevice;
|
||||
_swapChainPresenter = swapChainSurface;
|
||||
_viewPortWidth = swapChainSurface.Width;
|
||||
_viewPortHeight = swapChainSurface.Height;
|
||||
|
||||
_rtvHeap = new(_graphicsDevice.NativeDevice, DescriptorHeapType.Rtv, _RENDER_TARGET_VIEW_HEAP_SIZE);
|
||||
_fenceEvent = new(false);
|
||||
_renderPasses = [new MeshRenderPass()];
|
||||
_renderPasses = [new BindlessMeshRenderPass()];
|
||||
|
||||
InitializeSwapChain();
|
||||
InitializeFrameResource(out _frameResources);
|
||||
@@ -190,14 +185,14 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
}
|
||||
}
|
||||
|
||||
private uint CreateBackBufferResource(uint i, ID3D12Resource** backBuffer)
|
||||
private RenderTargetDescriptor CreateBackBufferResource(uint i, ID3D12Resource** backBuffer)
|
||||
{
|
||||
_swapChain.Get()->GetBuffer(i, __uuidof<ID3D12Resource>(), (void**)backBuffer);
|
||||
(*backBuffer)->SetName($"BackBuffer_{i}");
|
||||
var index = _rtvHeap.AllocateDescriptor();
|
||||
var rtvHandle = _rtvHeap.GetCpuHandle(index);
|
||||
var rtvDescriptor = GraphicsPipeline.DescriptorAllocator.AllocateRTV();
|
||||
var rtvHandle = rtvDescriptor.CpuHandle;
|
||||
_graphicsDevice.NativeDevice.Ptr->CreateRenderTargetView(*backBuffer, null, rtvHandle);
|
||||
return index;
|
||||
return rtvDescriptor;
|
||||
}
|
||||
|
||||
public void ExecutePendingResize()
|
||||
@@ -225,7 +220,7 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
if (frameResource.backBuffer.Get() is not null)
|
||||
{
|
||||
var c = frameResource.backBuffer.Reset();
|
||||
_rtvHeap.ReleaseDescriptor(frameResource.backBufferDescriptorIndexes);
|
||||
GraphicsPipeline.DescriptorAllocator.ReleaseRTV(frameResource.rtvDescriptor);
|
||||
}
|
||||
|
||||
frameResource.fenceValue = _frameResources[_backBufferIndex].fenceValue;
|
||||
@@ -239,7 +234,7 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
for (var i = 0u; i < GraphicsPipeline._FRAME_COUNT; i++)
|
||||
{
|
||||
var index = CreateBackBufferResource(i, _frameResources[i].backBuffer.GetAddressOf());
|
||||
_frameResources[i].backBufferDescriptorIndexes = index;
|
||||
_frameResources[i].rtvDescriptor = index;
|
||||
}
|
||||
|
||||
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
|
||||
@@ -253,7 +248,7 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
|
||||
foreach (var pass in _renderPasses)
|
||||
{
|
||||
pass.Initialize(frameResource.commandBuffer);
|
||||
pass.Initialize(frameResource.cmd);
|
||||
}
|
||||
|
||||
frameResource.ExecuteCommandBuffer(_graphicsDevice.CommandQueue);
|
||||
@@ -265,7 +260,7 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
_backBufferIndex = _swapChain.Get()->GetCurrentBackBufferIndex();
|
||||
|
||||
ref var frameResource = ref _frameResources[_backBufferIndex];
|
||||
var cpuHandle = _rtvHeap.GetCpuHandle(frameResource.backBufferDescriptorIndexes);
|
||||
var cpuHandle = frameResource.rtvDescriptor.CpuHandle;
|
||||
|
||||
frameResource.ResetCommandBuffer();
|
||||
frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.Present, ResourceStates.RenderTarget);
|
||||
@@ -282,7 +277,7 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
|
||||
foreach (var pass in _renderPasses)
|
||||
{
|
||||
pass.Execute(frameResource.commandBuffer);
|
||||
pass.Execute(frameResource.cmd);
|
||||
}
|
||||
|
||||
frameResource.commandList.Get()->ResourceBarrierTransition(frameResource.backBuffer.Get(), ResourceStates.RenderTarget, ResourceStates.Present);
|
||||
@@ -353,8 +348,6 @@ internal unsafe class D3D12Renderer : IRenderer
|
||||
_fence.Dispose();
|
||||
_fenceEvent.Dispose();
|
||||
|
||||
_rtvHeap.Dispose();
|
||||
|
||||
_backBufferIndex = 0;
|
||||
|
||||
_disposed = true;
|
||||
@@ -1,23 +1,22 @@
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Win32;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class D3D12ResourceAllocator : IResourceAllocator
|
||||
internal unsafe class ResourceAllocator
|
||||
{
|
||||
private readonly struct TempResourceAllocInfo
|
||||
{
|
||||
public readonly D3D12Resource resource;
|
||||
public readonly GraphicsResource resource;
|
||||
public readonly uint cpuFenceValue;
|
||||
|
||||
public TempResourceAllocInfo(D3D12Resource resource, uint cpuFenceValue)
|
||||
public TempResourceAllocInfo(GraphicsResource resource, uint cpuFenceValue)
|
||||
{
|
||||
this.resource = resource;
|
||||
this.cpuFenceValue = cpuFenceValue;
|
||||
}
|
||||
|
||||
public TempResourceAllocInfo(D3D12Resource resource)
|
||||
public TempResourceAllocInfo(GraphicsResource resource)
|
||||
: this(resource, GraphicsPipeline.CPUFenceValue + 1)
|
||||
{
|
||||
}
|
||||
@@ -224,29 +223,28 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator
|
||||
// }
|
||||
//}
|
||||
|
||||
public IResource CreateUploadBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
|
||||
public GraphicsResource CreateUploadBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
|
||||
{
|
||||
if (sizeInBytes > _MAX_BYTES)
|
||||
{
|
||||
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
||||
}
|
||||
|
||||
var device = GraphicsPipeline.GetGraphicsDevice<D3D12GraphicsDevice>();
|
||||
var heapProperties = new HeapProperties(HeapType.Upload);
|
||||
var resourceDescription = ResourceDescription.Buffer(sizeInBytes, flags);
|
||||
|
||||
ComPtr<ID3D12Resource> buffer = default;
|
||||
device.NativeDevice.Ptr->CreateCommittedResource(
|
||||
GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource(
|
||||
&heapProperties,
|
||||
HeapFlags.None,
|
||||
&resourceDescription,
|
||||
ResourceStates.Common,
|
||||
ResourceStates.GenericRead,
|
||||
null,
|
||||
__uuidof<ID3D12Resource>(),
|
||||
buffer.GetVoidAddressOf()
|
||||
);
|
||||
|
||||
var resource = new D3D12Resource(buffer.Move(), tempResource);
|
||||
var resource = new GraphicsResource(buffer.Move(), tempResource);
|
||||
if (tempResource)
|
||||
{
|
||||
_temResources.Enqueue(new(resource));
|
||||
@@ -255,19 +253,18 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator
|
||||
return resource;
|
||||
}
|
||||
|
||||
public IResource CreateCopyDestinationBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
|
||||
public GraphicsResource CreateCopyDestinationBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
|
||||
{
|
||||
if (sizeInBytes > _MAX_BYTES)
|
||||
{
|
||||
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
||||
}
|
||||
|
||||
var device = GraphicsPipeline.GetGraphicsDevice<D3D12GraphicsDevice>();
|
||||
var heapProperties = new HeapProperties(HeapType.Default);
|
||||
var resourceDescription = ResourceDescription.Buffer(sizeInBytes, flags);
|
||||
|
||||
ComPtr<ID3D12Resource> buffer = default;
|
||||
device.NativeDevice.Ptr->CreateCommittedResource(
|
||||
GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource(
|
||||
&heapProperties,
|
||||
HeapFlags.None,
|
||||
&resourceDescription,
|
||||
@@ -277,7 +274,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator
|
||||
buffer.GetVoidAddressOf()
|
||||
);
|
||||
|
||||
var resource = new D3D12Resource(buffer.Move(), tempResource);
|
||||
var resource = new GraphicsResource(buffer.Move(), tempResource);
|
||||
if (tempResource)
|
||||
{
|
||||
_temResources.Enqueue(new(resource));
|
||||
339
Ghost.Graphics/D3D12/ResourceUploadBatch.cs
Normal file
339
Ghost.Graphics/D3D12/ResourceUploadBatch.cs
Normal file
@@ -0,0 +1,339 @@
|
||||
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 struct TrackedResource
|
||||
{
|
||||
public GraphicsResource resource;
|
||||
public ResourceStates state;
|
||||
|
||||
public TrackedResource(GraphicsResource resource, ResourceStates state)
|
||||
{
|
||||
this.resource = resource;
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
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>(GraphicsResource resource, ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (!_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Upload batch is not recording");
|
||||
}
|
||||
|
||||
var sizeInBytes = (uint)(data.Length * sizeof(T));
|
||||
|
||||
// Create upload buffer
|
||||
var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(sizeInBytes, true);
|
||||
|
||||
// Copy data to upload buffer
|
||||
fixed (T* dataPtr = data)
|
||||
{
|
||||
uploadBuffer.SetData(dataPtr, (uint)data.Length);
|
||||
}
|
||||
|
||||
// Copy from upload buffer to destination
|
||||
_commandList.Get()->CopyBufferRegion(
|
||||
resource.NativeResource.Ptr,
|
||||
0,
|
||||
uploadBuffer.NativeResource.Ptr,
|
||||
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(GraphicsResource resource, uint firstSubresource, SubresourceData* subresources, uint numSubresources)
|
||||
{
|
||||
if (!_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Upload batch is not recording");
|
||||
}
|
||||
|
||||
var resourceDesc = resource.NativeResource.Ptr->GetDesc();
|
||||
|
||||
var requiredSize = GetRequiredIntermediateSize(resource, firstSubresource, numSubresources);
|
||||
|
||||
var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer((uint)requiredSize, true);
|
||||
|
||||
UpdateSubresources(
|
||||
resource.NativeResource.Ptr,
|
||||
uploadBuffer.NativeResource.Ptr,
|
||||
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(GraphicsResource resource, ResourceStates stateBefore, ResourceStates stateAfter)
|
||||
{
|
||||
if (!_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Upload batch is not recording");
|
||||
}
|
||||
|
||||
// Apply the transition immediately
|
||||
var barrier = new ResourceBarrier
|
||||
{
|
||||
Type = ResourceBarrierType.Transition,
|
||||
Flags = ResourceBarrierFlags.None,
|
||||
Anonymous = new ResourceBarrier._Anonymous_e__Union
|
||||
{
|
||||
Transition = new ResourceTransitionBarrier
|
||||
{
|
||||
pResource = resource.NativeResource.Ptr,
|
||||
Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
|
||||
StateBefore = stateBefore,
|
||||
StateAfter = stateAfter
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_commandList.Get()->ResourceBarrier(1, &barrier);
|
||||
}
|
||||
|
||||
/// <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(GraphicsResource destinationResource, uint firstSubresource, uint numSubresources)
|
||||
{
|
||||
var resourceDesc = destinationResource.NativeResource.Ptr->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);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using DescriptorIndex = System.UInt32;
|
||||
|
||||
namespace Ghost.Graphics.D3D12.Utilities;
|
||||
|
||||
internal unsafe struct D3D12DescriptorAllocator : IDisposable
|
||||
internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
{
|
||||
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
|
||||
|
||||
@@ -50,7 +50,7 @@ internal unsafe struct D3D12DescriptorAllocator : IDisposable
|
||||
public readonly ConstPtr<ID3D12DescriptorHeap> Heap => new(_heap.Get());
|
||||
public readonly ConstPtr<ID3D12DescriptorHeap> ShaderVisibleHeap => new(_shaderVisibleHeap.Get());
|
||||
|
||||
public D3D12DescriptorAllocator(ConstPtr<ID3D12Device14> device, DescriptorHeapType type, uint numDescriptors)
|
||||
public DescriptorHeapAllocator(ConstPtr<ID3D12Device14> device, DescriptorHeapType type, uint numDescriptors)
|
||||
{
|
||||
_device = device;
|
||||
HeapType = type;
|
||||
Reference in New Issue
Block a user