Update rendering architecture and resource management
Added a new `Ref<T>` struct for reference semantics. Added the `RenderGraph` system for managing rendering passes. Added the `RenderTexture` class for encapsulating GPU resources. Added `GraphicsBuffer` class for effective GPU resource management. Changed `CommandList` methods from public to internal for visibility control. Changed `IRenderPass` interface from internal to public for accessibility. Changed `GetData<T>()` in `ComponentObject.cs` to return `CompRef<T>`. Changed `GetComponent<T>()` in `EntityManager.cs` to return `CompRef<T>`. Changed `GetSingleton<T>()` in `World.cs` to use `CompRef<T>`. Changed `IQueryTypeParameter` to use `CompRef<T>` for consistency. Changed `QueryItem<T0>` and related structs to use `CompRef<T>`. Changed `Material` class to support bindless textures. Changed `Shader` class to support bindless rendering. Changed `Mesh` class to support bindless vertex and index buffer access. Updated documentation to reflect the new bindless rendering architecture.
This commit is contained in:
@@ -16,40 +16,60 @@ public unsafe class CommandList
|
||||
_commandList = commandList;
|
||||
}
|
||||
|
||||
public void BarrierTransition(GraphicsResource resource, ResourceStates beforeState, ResourceStates afterState)
|
||||
internal void BarrierTransition(GraphicsResource resource, ResourceStates beforeState, ResourceStates afterState)
|
||||
{
|
||||
_commandList.Ptr->ResourceBarrierTransition(resource.NativeResource.Ptr, beforeState, afterState);
|
||||
}
|
||||
|
||||
public void SetGraphicsRootConstantBufferView(uint slot, ulong gpuAddress)
|
||||
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(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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
_commandList.Ptr->IASetVertexBuffers(0, 1, mesh.VertexBufferView);
|
||||
_commandList.Ptr->IASetIndexBuffer(mesh.IndexBufferView);
|
||||
|
||||
_commandList.Ptr->DrawIndexedInstanced(mesh.IndexCount, 1, 0, 0, 0);
|
||||
// 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)
|
||||
|
||||
@@ -13,17 +13,19 @@ internal class DescriptorAllocator : IDisposable
|
||||
private readonly DescriptorHeapAllocator _dsvHeap;
|
||||
private readonly DescriptorHeapAllocator _srvHeap;
|
||||
private readonly DescriptorHeapAllocator _samplerHeap;
|
||||
private readonly BindlessDescriptorHeapAllocator _bindlessHeap;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public DescriptorAllocator(uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256)
|
||||
public DescriptorAllocator(uint initialRtvCount = 256, uint initialDsvCount = 256, uint initialSrvCount = 1024, uint initialSamplerCount = 256, uint initialBindlessCount = 10000)
|
||||
{
|
||||
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);
|
||||
_rtvHeap = new DescriptorHeapAllocator("rtv", device.NativeDevice, DescriptorHeapType.Rtv, initialRtvCount);
|
||||
_dsvHeap = new DescriptorHeapAllocator("dsv", device.NativeDevice, DescriptorHeapType.Dsv, initialDsvCount);
|
||||
_srvHeap = new DescriptorHeapAllocator("srv", device.NativeDevice, DescriptorHeapType.CbvSrvUav, initialSrvCount);
|
||||
_samplerHeap = new DescriptorHeapAllocator("sampler", device.NativeDevice, DescriptorHeapType.Sampler, initialSamplerCount);
|
||||
_bindlessHeap = new BindlessDescriptorHeapAllocator(device.NativeDevice, initialBindlessCount);
|
||||
}
|
||||
|
||||
#region RTV Methods
|
||||
@@ -280,6 +282,81 @@ internal class DescriptorAllocator : IDisposable
|
||||
|
||||
#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()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var index = _bindlessHeap.AllocateDescriptor();
|
||||
if (index == uint.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to allocate bindless descriptor");
|
||||
}
|
||||
|
||||
var cpuHandle = _bindlessHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _bindlessHeap.GetGpuHandle(index);
|
||||
|
||||
return new BindlessDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
var cpuHandle = _bindlessHeap.GetCpuHandle(index);
|
||||
var gpuHandle = _bindlessHeap.GetGpuHandle(index);
|
||||
descriptors[i] = new BindlessDescriptor(index, cpuHandle, gpuHandle);
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases a bindless descriptor.
|
||||
/// </summary>
|
||||
public void ReleaseBindless(BindlessDescriptor descriptor)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
if (descriptor is BindlessDescriptor d3d12Descriptor)
|
||||
{
|
||||
_bindlessHeap.ReleaseDescriptor(d3d12Descriptor.Index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases multiple bindless descriptors.
|
||||
/// </summary>
|
||||
public void ReleaseBindless(BindlessDescriptor[] descriptors)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
ReleaseBindless(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
/// <summary>
|
||||
@@ -302,6 +379,11 @@ internal class DescriptorAllocator : IDisposable
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetSamplerHeap() => _samplerHeap.ShaderVisibleHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bindless heap for SM 6.6 bindless rendering.
|
||||
/// </summary>
|
||||
public ConstPtr<ID3D12DescriptorHeap> GetBindlessHeap() => _bindlessHeap.BindlessHeap;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shader visible heaps that need to be bound to the command list.
|
||||
/// </summary>
|
||||
@@ -310,6 +392,14 @@ internal class DescriptorAllocator : IDisposable
|
||||
return [_srvHeap.ShaderVisibleHeap, _samplerHeap.ShaderVisibleHeap];
|
||||
}
|
||||
|
||||
/// <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];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
@@ -323,6 +413,7 @@ internal class DescriptorAllocator : IDisposable
|
||||
_dsvHeap.Dispose();
|
||||
_srvHeap.Dispose();
|
||||
_samplerHeap.Dispose();
|
||||
_bindlessHeap.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
@@ -3,9 +3,9 @@ using Win32.Graphics.Direct3D12;
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for D3D12 descriptor implementations.
|
||||
/// Base class descriptor implementations.
|
||||
/// </summary>
|
||||
internal abstract class Descriptor
|
||||
public abstract class Descriptor
|
||||
{
|
||||
protected readonly uint index;
|
||||
protected readonly bool isShaderVisible;
|
||||
@@ -38,9 +38,9 @@ internal abstract class Descriptor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of render target view (RTV) descriptor.
|
||||
/// Implementation of render target view (RTV) descriptor.
|
||||
/// </summary>
|
||||
internal sealed class RenderTargetDescriptor : Descriptor
|
||||
public sealed class RenderTargetDescriptor : Descriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
|
||||
@@ -55,9 +55,9 @@ internal sealed class RenderTargetDescriptor : Descriptor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of depth stencil view (DSV) descriptor.
|
||||
/// Implementation of depth stencil view (DSV) descriptor.
|
||||
/// </summary>
|
||||
internal sealed class DepthStencilDescriptor : Descriptor
|
||||
public sealed class DepthStencilDescriptor : Descriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
|
||||
@@ -72,9 +72,9 @@ internal sealed class DepthStencilDescriptor : Descriptor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of shader resource view (SRV) descriptor.
|
||||
/// Implementation of shader resource view (SRV) descriptor.
|
||||
/// </summary>
|
||||
internal sealed class ShaderResourceDescriptor : Descriptor
|
||||
public sealed class ShaderResourceDescriptor : Descriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
private readonly GpuDescriptorHandle _gpuHandle;
|
||||
@@ -91,9 +91,9 @@ internal sealed class ShaderResourceDescriptor : Descriptor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D3D12 implementation of sampler descriptor.
|
||||
/// Implementation of sampler descriptor.
|
||||
/// </summary>
|
||||
internal sealed class SamplerDescriptor : Descriptor
|
||||
public sealed class SamplerDescriptor : Descriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
private readonly GpuDescriptorHandle _gpuHandle;
|
||||
@@ -105,6 +105,26 @@ internal sealed class SamplerDescriptor : Descriptor
|
||||
_gpuHandle = gpuHandle;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => _gpuHandle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of bindless descriptor for SM 6.6 rendering.
|
||||
/// This descriptor maintains a 1:1 relationship between allocation indices and shader indices.
|
||||
/// </summary>
|
||||
public sealed class BindlessDescriptor : Descriptor
|
||||
{
|
||||
private readonly CpuDescriptorHandle _cpuHandle;
|
||||
private readonly GpuDescriptorHandle _gpuHandle;
|
||||
|
||||
public BindlessDescriptor(uint index, CpuDescriptorHandle cpuHandle, GpuDescriptorHandle gpuHandle)
|
||||
: base(index, true)
|
||||
{
|
||||
_cpuHandle = cpuHandle;
|
||||
_gpuHandle = gpuHandle;
|
||||
}
|
||||
|
||||
public override CpuDescriptorHandle CpuHandle => _cpuHandle;
|
||||
public override GpuDescriptorHandle GpuHandle => _gpuHandle;
|
||||
}
|
||||
119
Ghost.Graphics/D3D12/GraphicsBuffer.cs
Normal file
119
Ghost.Graphics/D3D12/GraphicsBuffer.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,9 @@ namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class GraphicsDevice
|
||||
{
|
||||
#if DEBUG
|
||||
private readonly DebugLayer _debugLayer;
|
||||
#endif
|
||||
private ComPtr<IDXGIFactory7> _dxgiFactory;
|
||||
private ComPtr<ID3D12Device14> _device;
|
||||
private ComPtr<IDXGIAdapter1> _adapter;
|
||||
private ComPtr<ID3D12CommandQueue> _commandQueue;
|
||||
|
||||
private ImmutableArray<Renderer> _initializeQueue;
|
||||
@@ -27,14 +25,11 @@ internal unsafe class GraphicsDevice
|
||||
|
||||
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()
|
||||
{
|
||||
#if DEBUG
|
||||
_debugLayer = new DebugLayer();
|
||||
#endif
|
||||
|
||||
InitializeDevice();
|
||||
InitializeCommandQueue();
|
||||
|
||||
@@ -67,6 +62,7 @@ internal unsafe class GraphicsDevice
|
||||
|
||||
if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_12_0, __uuidof<ID3D12Device14>(), _device.GetVoidAddressOf()).Success)
|
||||
{
|
||||
_adapter = adapter.Move();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -150,9 +146,6 @@ internal unsafe class GraphicsDevice
|
||||
_device.Reset();
|
||||
_dxgiFactory.Dispose();
|
||||
|
||||
#if DEBUG
|
||||
_debugLayer.Dispose();
|
||||
#endif
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,26 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Ghost.Graphics.Data;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
public unsafe class GraphicsResource
|
||||
public unsafe class GraphicsResource : IDisposable
|
||||
{
|
||||
private ComPtr<ID3D12Resource> _nativeResource;
|
||||
private readonly ResourceHandle _handle;
|
||||
private string _name = string.Empty;
|
||||
|
||||
private bool _disposed;
|
||||
internal ConstPtr<ID3D12Resource> NativeResource
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new(_handle.GetAllocation().Resource);
|
||||
}
|
||||
|
||||
internal ConstPtr<ID3D12Resource> NativeResource => new(_nativeResource.Get());
|
||||
|
||||
public ulong GPUAddress => _nativeResource.Get()->GetGPUVirtualAddress();
|
||||
internal ulong GPUAddress
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => NativeResource.Ptr->GetGPUVirtualAddress();
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
@@ -23,7 +28,7 @@ public unsafe class GraphicsResource
|
||||
set
|
||||
{
|
||||
_name = value;
|
||||
_nativeResource.Get()->SetName(_name);
|
||||
NativeResource.Ptr->SetName(_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +37,12 @@ public unsafe class GraphicsResource
|
||||
get;
|
||||
}
|
||||
|
||||
internal GraphicsResource(ComPtr<ID3D12Resource> nativeResource, bool temp = false)
|
||||
public ulong Size => _handle.GetAllocation().Size;
|
||||
|
||||
internal GraphicsResource(in ResourceHandle handle, bool tempResource = false)
|
||||
{
|
||||
_nativeResource = nativeResource;
|
||||
TempResource = temp;
|
||||
_handle = handle;
|
||||
TempResource = tempResource;
|
||||
}
|
||||
|
||||
~GraphicsResource()
|
||||
@@ -43,99 +50,26 @@ public unsafe class GraphicsResource
|
||||
DisposeInternal();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetData<T>(Span<T> data)
|
||||
where T : unmanaged
|
||||
/// <summary>
|
||||
/// Throws an exception if the resource has been disposed.
|
||||
/// </summary>
|
||||
protected void ThrowIfDisposed()
|
||||
{
|
||||
fixed (T* ptr = data)
|
||||
{
|
||||
SetData(ptr, (uint)data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetData<T>(T* data, uint length)
|
||||
where T : unmanaged
|
||||
{
|
||||
var size = (uint)(length * sizeof(T));
|
||||
SetData((void*)data, size);
|
||||
}
|
||||
|
||||
public unsafe void SetData(void* data, uint size)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var range = new Win32.Graphics.Direct3D12.Range(0, size);
|
||||
|
||||
void* mappedPtr;
|
||||
ThrowIfFailed(_nativeResource.Get()->Map(0, &range, &mappedPtr));
|
||||
|
||||
Unsafe.CopyBlock(mappedPtr, data, size);
|
||||
_nativeResource.Get()->Unmap(0, &range);
|
||||
}
|
||||
|
||||
public UnsafeArray<T> ReadData<T>(Allocator allocator)
|
||||
where T : unmanaged
|
||||
{
|
||||
var size = (uint)_nativeResource.Get()->GetDesc().Width;
|
||||
var data = new UnsafeArray<T>((int)(size / (uint)sizeof(T)), allocator);
|
||||
try
|
||||
{
|
||||
ReadData(data.GetUnsafePtr(), &size);
|
||||
return data;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
data.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadData<T>(T* pData, uint* size)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadData((void*)pData, size);
|
||||
}
|
||||
|
||||
public void ReadData(void* pData, uint* size)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
var range = new Win32.Graphics.Direct3D12.Range(0, (uint)_nativeResource.Get()->GetDesc().Width);
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
Unsafe.CopyBlock(pData, mappedPtr, (uint)(range.End - range.Begin));
|
||||
_nativeResource.Get()->Unmap(0, &range);
|
||||
if (size != null)
|
||||
{
|
||||
*size = (uint)(range.End - range.Begin);
|
||||
}
|
||||
ObjectDisposedException.ThrowIf(!_handle.IsValid, this);
|
||||
}
|
||||
|
||||
internal void DisposeInternal()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_nativeResource.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
_handle.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (!TempResource)
|
||||
{
|
||||
DisposeInternal();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Ghost.Graphics.D3D12.Utilities;
|
||||
using Ghost.Graphics.Data;
|
||||
using Ghost.Graphics.RenderPasses;
|
||||
using System.Collections.Immutable;
|
||||
using Win32;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
@@ -93,7 +92,7 @@ internal unsafe class Renderer
|
||||
_viewPortHeight = swapChainSurface.Height;
|
||||
|
||||
_fenceEvent = new(false);
|
||||
_renderPasses = [new BindlessMeshRenderPass()];
|
||||
_renderPasses = [];
|
||||
|
||||
InitializeSwapChain();
|
||||
InitializeFrameResource(out _frameResources);
|
||||
|
||||
@@ -1,305 +1,251 @@
|
||||
using Win32;
|
||||
using Ghost.Graphics.Data;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Win32.Graphics.D3D12MemoryAllocator;
|
||||
using Win32.Graphics.Direct3D12;
|
||||
using Win32.Graphics.Dxgi;
|
||||
using Win32.Graphics.Dxgi.Common;
|
||||
using static Win32.Graphics.D3D12MemoryAllocator.Apis;
|
||||
using ResourceHandle = Ghost.Graphics.Data.ResourceHandle;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal unsafe class ResourceAllocator
|
||||
{
|
||||
private readonly struct TempResourceAllocInfo
|
||||
private readonly struct AllocationInfo : IDisposable
|
||||
{
|
||||
public readonly GraphicsResource resource;
|
||||
public readonly Allocation allocation;
|
||||
public readonly uint cpuFenceValue;
|
||||
public readonly uint generation;
|
||||
|
||||
public TempResourceAllocInfo(GraphicsResource resource, uint cpuFenceValue)
|
||||
public bool Allocated => allocation.IsNotNull;
|
||||
|
||||
public AllocationInfo(in Allocation allocation, uint cpuFenceValue, uint generation)
|
||||
{
|
||||
this.resource = resource;
|
||||
this.allocation = allocation;
|
||||
this.cpuFenceValue = cpuFenceValue;
|
||||
this.generation = generation;
|
||||
}
|
||||
|
||||
public TempResourceAllocInfo(GraphicsResource resource)
|
||||
: this(resource, GraphicsPipeline.CPUFenceValue + 1)
|
||||
public AllocationInfo(in Allocation allocation, uint generation)
|
||||
: this(allocation, GraphicsPipeline.CPUFenceValue + 1, generation)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (allocation.IsNull)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
allocation.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private const ResourceStates _INITIALCOPYTARGETSTATE = ResourceStates.Common;
|
||||
private const ResourceStates _INITIALREADTARGETSTATE = ResourceStates.Common;
|
||||
private const ResourceStates _INITIALUAVTARGETSTATE = ResourceStates.Common;
|
||||
|
||||
private const uint _MAX_BYTES = D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u;
|
||||
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
|
||||
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
|
||||
|
||||
private readonly Queue<TempResourceAllocInfo> _temResources = new();
|
||||
private readonly Allocator _allocator;
|
||||
private UnsafeList<AllocationInfo> _allocations = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
private UnsafeQueue<int> _freeSlots = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
private UnsafeQueue<ResourceHandle> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
|
||||
//public static ID3D12Resource CreateStaticBuffer<T>(
|
||||
// ID3D12Device device,
|
||||
// D3D12ResourceUploadBatch resourceUpload,
|
||||
// T[] data, ResourceStates afterState,
|
||||
// ResourceFlags flags = ResourceFlags.None)
|
||||
// where T : unmanaged
|
||||
//{
|
||||
// Span<T> span = data;
|
||||
// return CreateStaticBuffer(device, resourceUpload, span, afterState, flags);
|
||||
//}
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
//public static ID3D12Resource CreateStaticBuffer<T>(
|
||||
// ID3D12Device device,
|
||||
// D3D12ResourceUploadBatch resourceUpload,
|
||||
// Span<T> data,
|
||||
// ResourceStates afterState,
|
||||
// ResourceFlags flags = ResourceFlags.None)
|
||||
// where T : unmanaged
|
||||
//{
|
||||
// var sizeInBytes = (uint)(sizeof(T) * data.Length);
|
||||
// if (sizeInBytes > _MAX_BYTES)
|
||||
// {
|
||||
// throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
||||
// }
|
||||
|
||||
// var buffer = device.CreateCommittedResource(
|
||||
// HeapType.Default,
|
||||
// HeapFlags.None,
|
||||
// ResourceDescription.Buffer(sizeInBytes, flags),
|
||||
// _INITIALCOPYTARGETSTATE
|
||||
// );
|
||||
|
||||
// fixed (T* dataPtr = data)
|
||||
// {
|
||||
// SubresourceData initData = new()
|
||||
// {
|
||||
// pData = dataPtr,
|
||||
// };
|
||||
|
||||
// resourceUpload.Upload(buffer, 0, &initData, 1);
|
||||
// resourceUpload.Transition(buffer, ResourceStates.CopyDest, afterState);
|
||||
|
||||
// return buffer;
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static ID3D12Resource CreateUploadBuffer<T>(
|
||||
// ID3D12Device device,
|
||||
// T[] data,
|
||||
// ResourceFlags flags = ResourceFlags.None)
|
||||
// where T : unmanaged
|
||||
//{
|
||||
// var sizeInBytes = (uint)(sizeof(T) * data.Length);
|
||||
// fixed (T* dataPtr = data)
|
||||
// {
|
||||
// return CreateUploadBuffer(device, sizeInBytes, dataPtr, flags);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static ID3D12Resource CreateUploadBuffer<T>(
|
||||
// ID3D12Device device,
|
||||
// Span<T> data,
|
||||
// ResourceFlags flags = ResourceFlags.None)
|
||||
// where T : unmanaged
|
||||
//{
|
||||
// var sizeInBytes = (uint)(sizeof(T) * data.Length);
|
||||
// fixed (T* dataPtr = data)
|
||||
// {
|
||||
// return CreateUploadBuffer(device, sizeInBytes, dataPtr, flags);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static ID3D12Resource CreateUploadBuffer(
|
||||
// ID3D12Device device,
|
||||
// uint sizeInBytes,
|
||||
// void* data = default,
|
||||
// ResourceFlags flags = ResourceFlags.None)
|
||||
//{
|
||||
// if (sizeInBytes > _MAX_BYTES)
|
||||
// {
|
||||
// throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
||||
// }
|
||||
|
||||
// var buffer = device.CreateCommittedResource(
|
||||
// HeapType.Upload,
|
||||
// HeapFlags.None,
|
||||
// ResourceDescription.Buffer(sizeInBytes, flags),
|
||||
// ResourceStates.GenericRead
|
||||
// );
|
||||
|
||||
// if (data is not null)
|
||||
// {
|
||||
// void* mappedPtr = default;
|
||||
// buffer.Map(0, null, &mappedPtr).CheckError();
|
||||
// Unsafe.CopyBlock(data, mappedPtr, sizeInBytes);
|
||||
// buffer.Unmap(0, null);
|
||||
// }
|
||||
|
||||
// return buffer;
|
||||
//}
|
||||
|
||||
//public static ID3D12Resource CreateReadbackBuffer(
|
||||
// ID3D12Device device,
|
||||
// uint sizeInBytes,
|
||||
// ResourceFlags flags = ResourceFlags.None)
|
||||
//{
|
||||
// if (sizeInBytes > _MAX_BYTES)
|
||||
// {
|
||||
// throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
||||
// }
|
||||
// var buffer = device.CreateCommittedResource(
|
||||
// HeapType.Readback,
|
||||
// HeapFlags.None,
|
||||
// ResourceDescription.Buffer(sizeInBytes, flags),
|
||||
// _INITIALREADTARGETSTATE
|
||||
// );
|
||||
// return buffer;
|
||||
//}
|
||||
|
||||
//public static ID3D12Resource CreateUAVBuffer(ID3D12Device device, uint bufferSize,
|
||||
// ResourceStates initialState = ResourceStates.Common,
|
||||
// ResourceFlags flags = ResourceFlags.None)
|
||||
//{
|
||||
// if (bufferSize > _MAX_BYTES)
|
||||
// {
|
||||
// throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {bufferSize})");
|
||||
// }
|
||||
|
||||
// var buffer = device.CreateCommittedResource(
|
||||
// HeapType.Default,
|
||||
// HeapFlags.None,
|
||||
// ResourceDescription.Buffer(bufferSize, ResourceFlags.AllowUnorderedAccess | flags),
|
||||
// _INITIALCOPYTARGETSTATE
|
||||
// );
|
||||
|
||||
// return buffer;
|
||||
//}
|
||||
|
||||
//public static ID3D12Resource CreateTexture2D<T>(
|
||||
// ID3D12Device device,
|
||||
// D3D12ResourceUploadBatch resourceUpload,
|
||||
// uint width, uint height, Format format,
|
||||
// Span<T> data,
|
||||
// bool generateMips = false,
|
||||
// ResourceStates afterState = ResourceStates.PixelShaderResource,
|
||||
// ResourceFlags flags = ResourceFlags.None)
|
||||
// where T : unmanaged
|
||||
//{
|
||||
// if (width > D3D12.RequestTexture2DUOrVDimension || height > D3D12.RequestTexture2DUOrVDimension)
|
||||
// {
|
||||
// throw new InvalidOperationException($"ERROR: Resource dimensions too large for DirectX 12 (2D: size {width} by {height})");
|
||||
// }
|
||||
|
||||
// ushort mipLevels = 1;
|
||||
// if (generateMips)
|
||||
// {
|
||||
// generateMips = resourceUpload.IsSupportedForGenerateMips(format);
|
||||
// if (generateMips)
|
||||
// {
|
||||
// mipLevels = (ushort)TextureUtility.CountMips(width, height);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// var texture = device.CreateCommittedResource(
|
||||
// HeapType.Default,
|
||||
// HeapFlags.None,
|
||||
// ResourceDescription.Texture2D(format, width, height, 1, mipLevels, 1, 0, flags),
|
||||
// _INITIALCOPYTARGETSTATE
|
||||
// );
|
||||
|
||||
// fixed (T* dataPtr = data)
|
||||
// {
|
||||
// FormatHelper.GetSurfaceInfo(format, width, height, out var rowPitch, out var slicePitch);
|
||||
// SubresourceData initData = new()
|
||||
// {
|
||||
// pData = dataPtr,
|
||||
// RowPitch = (nint)rowPitch,
|
||||
// SlicePitch = (nint)slicePitch
|
||||
// };
|
||||
|
||||
// resourceUpload.Upload(texture, 0, &initData, 1);
|
||||
// resourceUpload.Transition(texture, ResourceStates.CopyDest, afterState);
|
||||
|
||||
// if (generateMips)
|
||||
// {
|
||||
// resourceUpload.GenerateMips(texture);
|
||||
// }
|
||||
|
||||
// return texture;
|
||||
// }
|
||||
//}
|
||||
|
||||
public GraphicsResource CreateUploadBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
|
||||
private Guid* IID_NULL
|
||||
{
|
||||
if (sizeInBytes > _MAX_BYTES)
|
||||
get
|
||||
{
|
||||
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
||||
fixed (Guid* pGuid = &Guid.Empty)
|
||||
{
|
||||
return pGuid;
|
||||
}
|
||||
}
|
||||
|
||||
var heapProperties = new HeapProperties(HeapType.Upload);
|
||||
var resourceDescription = ResourceDescription.Buffer(sizeInBytes, flags);
|
||||
|
||||
ComPtr<ID3D12Resource> buffer = default;
|
||||
GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource(
|
||||
&heapProperties,
|
||||
HeapFlags.None,
|
||||
&resourceDescription,
|
||||
ResourceStates.GenericRead,
|
||||
null,
|
||||
__uuidof<ID3D12Resource>(),
|
||||
buffer.GetVoidAddressOf()
|
||||
);
|
||||
|
||||
var resource = new GraphicsResource(buffer.Move(), tempResource);
|
||||
if (tempResource)
|
||||
{
|
||||
_temResources.Enqueue(new(resource));
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
public GraphicsResource CreateCopyDestinationBuffer(uint sizeInBytes, bool tempResource = false, ResourceFlags flags = ResourceFlags.None)
|
||||
public ResourceAllocator()
|
||||
{
|
||||
var desc = new AllocatorDesc
|
||||
{
|
||||
pAdapter = (IDXGIAdapter*)GraphicsPipeline.GraphicsDevice.Adapter.Ptr,
|
||||
pDevice = (ID3D12Device*)GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr,
|
||||
Flags = AllocatorFlags.DefaultPoolsNotZeroed | AllocatorFlags.MSAATexturesAlwaysCommitted
|
||||
};
|
||||
|
||||
CreateAllocator(in desc, out _allocator);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void CheckBufferSize(uint sizeInBytes)
|
||||
{
|
||||
if (sizeInBytes > _MAX_BYTES)
|
||||
{
|
||||
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
|
||||
}
|
||||
}
|
||||
|
||||
var heapProperties = new HeapProperties(HeapType.Default);
|
||||
var resourceDescription = ResourceDescription.Buffer(sizeInBytes, flags);
|
||||
|
||||
ComPtr<ID3D12Resource> buffer = default;
|
||||
GraphicsPipeline.GraphicsDevice.NativeDevice.Ptr->CreateCommittedResource(
|
||||
&heapProperties,
|
||||
HeapFlags.None,
|
||||
&resourceDescription,
|
||||
ResourceStates.Common,
|
||||
null,
|
||||
__uuidof<ID3D12Resource>(),
|
||||
buffer.GetVoidAddressOf()
|
||||
);
|
||||
|
||||
var resource = new GraphicsResource(buffer.Move(), tempResource);
|
||||
if (tempResource)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void CheckTexture2DSize(uint width, uint height)
|
||||
{
|
||||
if (width > _MAX_TEXTURE2D_DIMENSION || height > _MAX_TEXTURE2D_DIMENSION)
|
||||
{
|
||||
_temResources.Enqueue(new(resource));
|
||||
throw new InvalidOperationException($"ERROR: Texture size too large for DirectX 12 (width {width}, height {height})");
|
||||
}
|
||||
}
|
||||
|
||||
return resource;
|
||||
private ResourceHandle TrackResource(in Allocation allocation, bool isTemp)
|
||||
{
|
||||
int id;
|
||||
uint generation;
|
||||
AllocationInfo allocInfo;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_freeSlots.Count > 0)
|
||||
{
|
||||
id = _freeSlots.Dequeue();
|
||||
var info = _allocations[id];
|
||||
if (info.Allocated)
|
||||
{
|
||||
throw new InvalidOperationException($"ERROR: Resource ID {id} registered as free but still allocated.");
|
||||
}
|
||||
|
||||
generation = info.generation + 1;
|
||||
allocInfo = new AllocationInfo(in allocation, generation);
|
||||
|
||||
_allocations[id] = allocInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
id = _allocations.Count;
|
||||
generation = 0u;
|
||||
allocInfo = new AllocationInfo(in allocation, generation);
|
||||
|
||||
_allocations.Add(allocInfo);
|
||||
}
|
||||
|
||||
var handle = new ResourceHandle(id, generation);
|
||||
if (isTemp)
|
||||
{
|
||||
_temResources.Enqueue(handle);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
public TextureHandle CreateTexture2D(uint width, uint height, ushort mipLevels, Format format = Format.R8G8B8A8Unorm, ResourceFlags resFlags = ResourceFlags.None, AllocationFlags allocFlags = AllocationFlags.None, ResourceStates state = ResourceStates.Common, bool tempResource = false)
|
||||
{
|
||||
CheckTexture2DSize(width, height);
|
||||
|
||||
var resourceDesc = ResourceDescription.Tex2D(format, width, height, mipLevels: mipLevels, arraySize: 1, flags: resFlags);
|
||||
var allocationDesc = new AllocationDesc
|
||||
{
|
||||
HeapType = HeapType.Default,
|
||||
Flags = allocFlags
|
||||
};
|
||||
|
||||
Allocation allocation = default;
|
||||
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, state, null, &allocation, IID_NULL, null));
|
||||
|
||||
return new(TrackResource(in allocation, tempResource));
|
||||
}
|
||||
|
||||
public BufferHandle CreateBuffer(uint sizeInBytes, HeapType heapType = HeapType.Default, ResourceFlags resFlags = ResourceFlags.None, AllocationFlags allocFlags = AllocationFlags.None, ResourceStates initialState = ResourceStates.Common, bool tempResource = false)
|
||||
{
|
||||
CheckBufferSize(sizeInBytes);
|
||||
|
||||
var resourceDescription = ResourceDescription.Buffer(sizeInBytes, resFlags);
|
||||
var allocationDesc = new AllocationDesc
|
||||
{
|
||||
HeapType = heapType,
|
||||
Flags = allocFlags
|
||||
};
|
||||
|
||||
Allocation allocation = default;
|
||||
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null));
|
||||
|
||||
return new(TrackResource(in allocation, tempResource));
|
||||
}
|
||||
|
||||
public BufferHandle CreateUploadBuffer(uint sizeInBytes, bool tempResource = false)
|
||||
{
|
||||
return CreateBuffer(sizeInBytes, HeapType.Upload, ResourceFlags.None, AllocationFlags.None, ResourceStates.GenericRead, tempResource);
|
||||
}
|
||||
|
||||
public void ReleaseTempResource()
|
||||
{
|
||||
while (_temResources.Count > 0)
|
||||
{
|
||||
var info = _temResources.Peek();
|
||||
ref var handle = ref _temResources.Peek();
|
||||
ref var info = ref _allocations[handle.id];
|
||||
if (info.cpuFenceValue > GraphicsPipeline.GPUFenceValue)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
info.resource.DisposeInternal();
|
||||
ReleaseAllocation(in handle);
|
||||
_temResources.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public Allocation GetAllocation(in ResourceHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid resource handle.");
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
ref var allocationInfo = ref _allocations[handle.id];
|
||||
if (!allocationInfo.Allocated || allocationInfo.generation != handle.generation)
|
||||
{
|
||||
throw new InvalidOperationException($"Resource with ID {handle.id} and generation {handle.generation} is not allocated or has been released.");
|
||||
}
|
||||
|
||||
return allocationInfo.allocation;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseAllocation(in ResourceHandle handle)
|
||||
{
|
||||
if (!handle.IsValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
ref var allocationInfo = ref _allocations[handle.id];
|
||||
|
||||
if (!allocationInfo.Allocated || allocationInfo.generation != handle.generation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
allocationInfo.Dispose();
|
||||
_freeSlots.Enqueue(handle.id);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseTempResource();
|
||||
#if DEBUG
|
||||
if (_allocations.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException($"ResourceAllocator is being disposed with {_allocations.Count} allocations still registered. Ensure all resources are released before disposing.");
|
||||
}
|
||||
#endif
|
||||
for (var i = 0; i < _allocations.Count; i++)
|
||||
{
|
||||
_allocations[i].Dispose();
|
||||
}
|
||||
|
||||
_allocations.Dispose();
|
||||
_temResources.Dispose();
|
||||
_allocator.Release();
|
||||
}
|
||||
}
|
||||
@@ -9,18 +9,6 @@ namespace Ghost.Graphics.D3D12;
|
||||
/// </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;
|
||||
@@ -89,7 +77,7 @@ internal unsafe class ResourceUploadBatch : IDisposable
|
||||
/// <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)
|
||||
public void Upload<T>(ID3D12Resource* resource, ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (!_isRecording)
|
||||
@@ -98,21 +86,22 @@ internal unsafe class ResourceUploadBatch : IDisposable
|
||||
}
|
||||
|
||||
var sizeInBytes = (uint)(data.Length * sizeof(T));
|
||||
|
||||
// Create upload buffer
|
||||
var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer(sizeInBytes, true);
|
||||
|
||||
// Copy data to upload buffer
|
||||
void* mappedData;
|
||||
var uploadResource = uploadBuffer.ResourceHandle.GetAllocation().Resource;
|
||||
uploadResource->Map(0, null, &mappedData);
|
||||
fixed (T* dataPtr = data)
|
||||
{
|
||||
uploadBuffer.SetData(dataPtr, (uint)data.Length);
|
||||
Unsafe.CopyBlock(mappedData, dataPtr, sizeInBytes);
|
||||
}
|
||||
uploadResource->Unmap(0, null);
|
||||
|
||||
// Copy from upload buffer to destination
|
||||
_commandList.Get()->CopyBufferRegion(
|
||||
resource.NativeResource.Ptr,
|
||||
resource,
|
||||
0,
|
||||
uploadBuffer.NativeResource.Ptr,
|
||||
uploadResource,
|
||||
0,
|
||||
sizeInBytes);
|
||||
}
|
||||
@@ -124,22 +113,20 @@ internal unsafe class ResourceUploadBatch : IDisposable
|
||||
/// <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)
|
||||
public void Upload(ID3D12Resource* resource, uint firstSubresource, SubresourceData* subresources, uint numSubresources)
|
||||
{
|
||||
if (!_isRecording)
|
||||
{
|
||||
throw new InvalidOperationException("Upload batch is not recording");
|
||||
}
|
||||
|
||||
var resourceDesc = resource.NativeResource.Ptr->GetDesc();
|
||||
|
||||
var resourceDesc = resource->GetDesc();
|
||||
var requiredSize = GetRequiredIntermediateSize(resource, firstSubresource, numSubresources);
|
||||
|
||||
var uploadBuffer = GraphicsPipeline.ResourceAllocator.CreateUploadBuffer((uint)requiredSize, true);
|
||||
|
||||
UpdateSubresources(
|
||||
resource.NativeResource.Ptr,
|
||||
uploadBuffer.NativeResource.Ptr,
|
||||
resource,
|
||||
uploadBuffer.ResourceHandle.GetAllocation().Resource,
|
||||
0,
|
||||
firstSubresource,
|
||||
numSubresources,
|
||||
@@ -152,31 +139,14 @@ internal unsafe class ResourceUploadBatch : IDisposable
|
||||
/// <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)
|
||||
public void Transition(ID3D12Resource* 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);
|
||||
_commandList.Get()->ResourceBarrierTransition(resource, stateBefore, stateAfter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -229,9 +199,9 @@ internal unsafe class ResourceUploadBatch : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetRequiredIntermediateSize(GraphicsResource destinationResource, uint firstSubresource, uint numSubresources)
|
||||
private ulong GetRequiredIntermediateSize(ID3D12Resource* destinationResource, uint firstSubresource, uint numSubresources)
|
||||
{
|
||||
var resourceDesc = destinationResource.NativeResource.Ptr->GetDesc();
|
||||
var resourceDesc = destinationResource->GetDesc();
|
||||
|
||||
ulong requiredSize = 0;
|
||||
var numRows = stackalloc uint[(int)numSubresources];
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
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 BindlessDescriptorHeapAllocator : IDisposable
|
||||
{
|
||||
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
|
||||
|
||||
private readonly ConstPtr<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 BindlessDescriptorHeapAllocator(ConstPtr<ID3D12Device14> device, uint numDescriptors = 10000)
|
||||
{
|
||||
_device = device;
|
||||
HeapType = DescriptorHeapType.CbvSrvUav;
|
||||
NumDescriptors = numDescriptors;
|
||||
_stride = device.Ptr->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))
|
||||
{
|
||||
Debug.WriteLine("ERROR: Failed to grow bindless descriptor heap!");
|
||||
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))
|
||||
{
|
||||
Debug.WriteLine("ERROR: Failed to grow bindless descriptor heap!");
|
||||
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)
|
||||
{
|
||||
Debug.WriteLine("Error: Attempted to release an invalid descriptor index");
|
||||
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)
|
||||
{
|
||||
Debug.WriteLine("Error: Attempted to release an invalid descriptor index");
|
||||
continue;
|
||||
}
|
||||
|
||||
_freeDescriptors.Enqueue(index);
|
||||
}
|
||||
|
||||
NumAllocatedDescriptors -= count;
|
||||
}
|
||||
}
|
||||
|
||||
public CpuDescriptorHandle GetCpuHandle(DescriptorIndex index)
|
||||
{
|
||||
var handle = _startCpuHandle;
|
||||
return handle.Offset((int)index, _stride);
|
||||
}
|
||||
|
||||
public GpuDescriptorHandle GetGpuHandle(DescriptorIndex index)
|
||||
{
|
||||
var handle = _startGpuHandle;
|
||||
return handle.Offset((int)index, _stride);
|
||||
}
|
||||
|
||||
public 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.Ptr->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.Ptr->CopyDescriptorsSimple(oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_bindlessHeap.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
public readonly ConstPtr<ID3D12DescriptorHeap> Heap => new(_heap.Get());
|
||||
public readonly ConstPtr<ID3D12DescriptorHeap> ShaderVisibleHeap => new(_shaderVisibleHeap.Get());
|
||||
|
||||
public DescriptorHeapAllocator(ConstPtr<ID3D12Device14> device, DescriptorHeapType type, uint numDescriptors)
|
||||
public DescriptorHeapAllocator(string name, ConstPtr<ID3D12Device14> device, DescriptorHeapType type, uint numDescriptors)
|
||||
{
|
||||
_device = device;
|
||||
HeapType = type;
|
||||
@@ -60,6 +60,12 @@ internal unsafe struct DescriptorHeapAllocator : IDisposable
|
||||
|
||||
var success = AllocateResources(numDescriptors);
|
||||
Debug.Assert(success);
|
||||
|
||||
_heap.Get()->SetName(name);
|
||||
if (ShaderVisible)
|
||||
{
|
||||
_shaderVisibleHeap.Get()->SetName($"{name} Shader Visible");
|
||||
}
|
||||
}
|
||||
|
||||
public DescriptorIndex AllocateDescriptor() => AllocateDescriptors(1);
|
||||
|
||||
Reference in New Issue
Block a user