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:
2025-08-01 21:34:48 +09:00
parent 1284bb17de
commit eafbfb2fa1
43 changed files with 3845 additions and 2183 deletions

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;
}

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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];

View File

@@ -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();
}
}

View File

@@ -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);