feat: implement descriptor heap with thread-safe allocation

D3D12 Resources:
- Add descriptor_handle struct with CPU/GPU handles
- Add descriptor_heap class for descriptor management
- Implement allocate() and free() methods
- Add mutex for thread-safe access
- Support all D3D12 descriptor heap types

D3D12 Core:
- Add device() function to expose main device
- Add release() template function for COM objects

Documentation:
- Add changelog for descriptor heap implementation
- Update D3D12 Wiki with descriptor heap section
- Mark descriptor heap task as completed
This commit is contained in:
SpecialX
2026-03-30 14:03:16 +08:00
parent f1584ec3c6
commit 54916b0ac6
10 changed files with 495 additions and 13 deletions

View File

@@ -0,0 +1,101 @@
#include "D3D12Resources.h"
#include "D3D12Core.h"
namespace XEngine::graphics::d3d12{
//////////// DESCRIPTOR HEAP ////////////
// 该类将被多个线程并发访问:资源创建(如纹理)与资源销毁/释放可能发生在不同线程,
// 因此需要同步机制保护内部数据结构
bool
descriptor_heap::initialize(u32 capacity, bool is_shader_visible)
{
std::lock_guard lock(_mutex);
// 检查容量有效性必须大于0且不超过最大限制
assert(capacity && capacity <= D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2);
// 对于采样器描述符堆,需要额外检查容量是否超过最大采样器堆大小限制
assert(!(_type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER &&
capacity > D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE));
// 对于DSV和RTV他们在描述符堆的内存模型中本身就是GPU不可见的
if(_type == D3D12_DESCRIPTOR_HEAP_TYPE_DSV ||
_type == D3D12_DESCRIPTOR_HEAP_TYPE_RTV)
{
is_shader_visible = false;
}
// 应为这个功能将被调用多次,所以需要先释放之前的资源
release();
ID3D12Device *const device {core::device()};
assert(device);
D3D12_DESCRIPTOR_HEAP_DESC desc{};
desc.Flags = is_shader_visible
? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE
: D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
desc.NumDescriptors = capacity;
desc.Type = _type;
desc.NodeMask = 0;
HRESULT hr {S_OK};
DXCall(hr = device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_heap)));
if(FAILED(hr)) return false;
_free_handles = std::move(std::make_unique<u32[]>(capacity));
_capacity = capacity;
_size = 0;
for(u32 i = 0; i < capacity; ++i) _free_handles[i] = i;
_descriptor_size = device->GetDescriptorHandleIncrementSize(_type);
_cpu_start = _heap->GetCPUDescriptorHandleForHeapStart();
_gpu_start = is_shader_visible
? _heap->GetGPUDescriptorHandleForHeapStart()
: D3D12_GPU_DESCRIPTOR_HANDLE{0};
return true;
}
void
descriptor_heap::release()
{
}
descriptor_handle
descriptor_heap::allocate()
{
std::lock_guard lock(_mutex);
assert(_heap);
assert(_size < _capacity);
const u32 index { _free_handles[_size] };
const u32 offset { index * _descriptor_size };
++_size;
descriptor_handle handle{};
handle.cpu.ptr = _cpu_start.ptr + offset;
if(is_shader_visible())
{
handle.gpu.ptr = _gpu_start.ptr + offset;
}
DEBUG_OP(handle.container = this);
DEBUG_OP(handle.index = index);
return handle;
}
void
descriptor_heap::free(descriptor_handle handle)
{
if(!handle.is_valid()) return;
std::lock_guard lock(_mutex);
assert(_heap && _size);
assert(handle.container == this);
assert(handle.cpu.ptr >= _cpu_start.ptr);
assert((handle.cpu.ptr - _cpu_start.ptr) % _descriptor_size == 0);
assert(handle.index < _capacity);
const u32 index{ (u32)(handle.cpu.ptr - _cpu_start.ptr) / _descriptor_size };
assert(handle.index == index);
handle = {};
}
} //XEngine::graphics::d3d12