feat(d3d12): 完善描述符堆延迟释放机制与FreeList栈式索引管理

- 添加完整的中文Doxygen注释文档
- 实现process_deferred_release()延迟释放处理
- 添加deferred_release模板函数和current_frame_index()
- 实现延迟释放队列和帧索引管理
- 详细说明FreeList栈式索引分配/释放算法
- 更新D3D12学习Wiki,添加延迟释放机制章节
This commit is contained in:
SpecialX
2026-03-30 16:58:35 +08:00
parent 54916b0ac6
commit b6c0211d6a
6 changed files with 1055 additions and 51 deletions

View File

@@ -1,5 +1,6 @@
#include "D3D12Core.h"
#include "D3D12CommonHeader.h"
#include "D3D12Resources.h"
using namespace Microsoft::WRL;
namespace XEngine::graphics::d3d12::core {
@@ -216,6 +217,7 @@ private:
void release()
{
core::release(cmd_allocator);
fence_value = 0;
}
};
@@ -247,6 +249,47 @@ IDXGIFactory7* dxgi_factory{ nullptr };
*/
d3d12_command gfx_command;
/**
* @brief RTV 描述符堆
* @details 用于存储渲染目标视图的描述符,用于渲染管线中的输出
*/
descriptor_heap rtv_descriptor_heap{D3D12_DESCRIPTOR_HEAP_TYPE_RTV};
/**
* @brief DSV 描述符堆
* @details 用于存储深度模板视图的描述符,用于渲染管线中的深度模板测试
*/
descriptor_heap dsv_descriptor_heap{D3D12_DESCRIPTOR_HEAP_TYPE_DSV};
/**
* @brief SRV 描述符堆
* @details 用于存储着色器资源视图的描述符,用于渲染管线中的着色器资源访问
*/
descriptor_heap srv_descriptor_heap{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV};
/**
* @brief UAV 描述符堆
* @details 用于存储无序访问视图的描述符,用于渲染管线中的无序访问
*/
descriptor_heap uav_descriptor_heap{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV};
/**
* @brief 延迟释放队列
* @details 每个帧缓冲区对应一个待释放资源的队列,用于在渲染完成后释放资源
*/
utl::vector<IUnknown*> deferred_releases[frame_buffer_count]{};
/**
* @brief 延迟释放标志数组
* @details 每个帧缓冲区对应一个标志位用于记录是否需要延迟释放资源
*/
u32 deferred_release_flag[frame_buffer_count]{};
/**
* @brief 延迟释放互斥锁
* @details 用于保护延迟释放标志数组的并发访问,确保线程安全
*/
std::mutex deferred_release_mutex{};
// 最小支持的 Direct3D 特本级别
constexpr D3D_FEATURE_LEVEL minumum_feature_level{ D3D_FEATURE_LEVEL_11_0 };
@@ -311,8 +354,48 @@ get_max_feature_level(IDXGIAdapter4* adapter)
return feature_level_info.MaxSupportedFeatureLevel;
}
/**
* @brief 处理延迟释放资源
* @details 遍历指定帧索引的延迟释放资源索引数组,释放每个资源
* @param frame_index 要处理的帧索引
* @note 使用 __declspec(noinline) 防止编译器内联此函数,确保在调试时能够准确断点
*/
void __declspec(noinline)
process_deferred_release(u32 frame_index)
{
std::lock_guard lock{ deferred_release_mutex };
// 我们在开始的的时候清楚这个帧的标志位,如果我们在结尾的时候清除,
// 他可能被其他线程重写.
deferred_release_flag[frame_index] = 0;
rtv_descriptor_heap.process_deferred_release(frame_index);
dsv_descriptor_heap.process_deferred_release(frame_index);
srv_descriptor_heap.process_deferred_release(frame_index);
uav_descriptor_heap.process_deferred_release(frame_index);
utl::vector<IUnknown*>& resources{ deferred_releases[frame_index] };
if(!resources.empty())
{
for(auto& resource : resources) release(resource);
resources.clear();
}
}
}// anonymous namespace
namespace detail{
void
deferred_release(IUnknown* resource)
{
const u32 frmae_idx {current_frame_index()};
std::lock_guard lock{ deferred_release_mutex };
deferred_releases[frmae_idx].push_back(resource);
set_deferred_release_flag();
}
} // detail namespace
bool
initialize()
@@ -326,8 +409,15 @@ initialize()
#ifdef _DEBUG
{
ComPtr<ID3D12Debug3> debug_interface;
DXCall(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_interface)));
debug_interface->EnableDebugLayer();
if(SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_interface))))
{
debug_interface->EnableDebugLayer();
}
else
{
OutputDebugStringA("warning: d3d12 debug interface is not available\n");
}
dxgi_factory_flag |= DXGI_CREATE_FACTORY_DEBUG;
}
#endif
@@ -351,18 +441,6 @@ initialize()
DXCall(hr = D3D12CreateDevice(main_adapter.Get(), max_feature_level, IID_PPV_ARGS(&main_device)));
if (FAILED(hr)) return failed_init();
// 为 Direct3D 12 设备设置名称
NAME_D3D12_OBJECT(main_device, L"Main Device");
// 使用 placement new 在已分配的内存上构造对象
// new (&gfx_command) 表示在 gfx_command 的地址处调用构造函数
// 这种用法允许我们在不分配新内存的情况下,在指定内存位置构造对象
// 常用于需要在特定内存地址构造对象,或重新初始化已存在的对象
// 这里 gfx_command 是一个类成员变量,我们直接在其内存位置上构造 d3d12_command 对象
// 避免了额外的内存分配同时可以传递构造参数main_device 和命令队列类型)
new (&gfx_command) d3d12_command(main_device, D3D12_COMMAND_LIST_TYPE_DIRECT);
if(!gfx_command.command_queue()) return failed_init();
#ifdef _DEBUG
{
ComPtr<ID3D12InfoQueue> info_queue;
@@ -374,6 +452,31 @@ initialize()
}
#endif // _DEBUG
// 使用 placement new 在已分配的内存上构造对象
// new (&gfx_command) 表示在 gfx_command 的地址处调用构造函数
// 这种用法允许我们在不分配新内存的情况下,在指定内存位置构造对象
// 常用于需要在特定内存地址构造对象,或重新初始化已存在的对象
// 这里 gfx_command 是一个类成员变量,我们直接在其内存位置上构造 d3d12_command 对象
// 避免了额外的内存分配同时可以传递构造参数main_device 和命令队列类型)
new (&gfx_command) d3d12_command(main_device, D3D12_COMMAND_LIST_TYPE_DIRECT);
if(!gfx_command.command_queue()) return failed_init();
bool result{true};
result &= rtv_descriptor_heap.initialize(512, false);
result &= dsv_descriptor_heap.initialize(512, false);
result &= srv_descriptor_heap.initialize(4096, false);
result &= uav_descriptor_heap.initialize(512, false);
if(!result) return failed_init();
// 为 Direct3D 12 设备设置名称
NAME_D3D12_OBJECT(main_device, L"Main Device");
NAME_D3D12_OBJECT(rtv_descriptor_heap.heap(), L"RTV Descriptor Heap");
NAME_D3D12_OBJECT(dsv_descriptor_heap.heap(), L"DSV Descriptor Heap");
NAME_D3D12_OBJECT(srv_descriptor_heap.heap(), L"SRV Descriptor Heap");
NAME_D3D12_OBJECT(uav_descriptor_heap.heap(), L"UAV Descriptor Heap");
return true;
}
@@ -381,8 +484,25 @@ void
shutdown()
{
gfx_command.release();
// 注意,我们需要在所有的依赖资源之前调用延迟释放函数.
// 否则会导致依赖当基础前资源(贴图等)的资源(着色器等)不能被释放.
for(u32 i{ 0 }; i < frame_buffer_count; ++i)
{
process_deferred_release(i);
}
release(dxgi_factory);
rtv_descriptor_heap.release();
dsv_descriptor_heap.release();
srv_descriptor_heap.release();
uav_descriptor_heap.release();
// 某些类型仅在 shutdown/reset/clear 时使用延迟释放机制来释放资源,
// 为了确保这些资源被正确释放,需要在 shutdown 最后额外调用一次延迟释放函数。
process_deferred_release(0);
#ifdef _DEBUG
{
{
@@ -415,6 +535,11 @@ render()
gfx_command.begin_frame();
ID3D12GraphicsCommandList6* cmd_list{ gfx_command.command_list() };
const u32 frame_index{ current_frame_index() };
if(deferred_release_flag[frame_index])
{
process_deferred_release(frame_index);
}
// 记录命令
//
// 完成命令记录,立即提交命令列表到命令队列执行
@@ -428,4 +553,19 @@ device()
return main_device;
}
u32
current_frame_index()
{
return gfx_command.frame_index();
}
// X86结构上的整数访问权架构原子的,所以不需要加锁
void
set_deferred_release_flag()
{
deferred_release_flag[current_frame_index()] = 1;
}
}// namespace XEngine::graphics::d3d12::core

View File

@@ -25,9 +25,11 @@ void render();
/**
* @brief 通用资源释放模板函数
* @details 用于安全释放 DirectX COM 对象,检查空指针后调用 Release 并置空
* @tparam T COM 接口类型
* @brief 立即释放 DirectX COM 对象并将指针置空
* @details 安全释放资源:若指针非空则调用 Release() 方法,随后将原指针置为 nullptr。
* 适用于所有继承自 IUnknown 的 COM 接口类型
* @tparam T COM 接口类型(如 ID3D12Resource、ID3D11Buffer 等)
* @param resource 待释放的 COM 接口指针(引用传递,释放后自动置空)
*/
template<typename T>
constexpr void release(T*& resource)
@@ -39,6 +41,32 @@ constexpr void release(T*& resource)
}
}
namespace detail {
/**
* @brief 延迟释放 COM 对象(内部实现)
* @details 将资源加入延迟释放队列,不立即调用 Release(),用于异步或帧末统一回收。
* @param resource COM 对象指针(按值传递,仅用于记录,不修改外部指针)
*/
void deferred_release(IUnknown* resource);
} // namespace detail
/**
* @brief 安全包装延迟释放,并将外部指针置空
* @details 调用 detail::deferred_release 将资源加入延迟释放队列,同时将传入的指针置为 nullptr
* 防止调用者误用悬空指针。适用于需要延迟释放但希望立即清空原指针的场景。
* @tparam T COM 接口类型
* @param resource 待延迟释放的 COM 接口指针(引用传递,释放后自动置空)
*/
template<typename T>
constexpr void deferred_release(T*& resource)
{
if(resource)
{
detail::deferred_release(resource);
resource = nullptr;
}
}
/**
* @brief 获取 Direct3D 12 设备
* @details 返回 Direct3D 12 设备的智能指针
@@ -46,4 +74,17 @@ constexpr void release(T*& resource)
*/
ID3D12Device *const device();
}// namespace XEngine::graphics::d3d12
/**
* @brief 获取当前帧索引
* @details 返回当前渲染的帧索引
* @return u32 当前帧索引
*/
u32 current_frame_index();
/**
* @brief 设置延迟释放标志
* @details 用于在渲染循环中设置延迟释放标志,通知资源管理器在当前帧渲染完成后释放资源
*/
void set_deferred_release_flag();
}// namespace XEngine::graphics::d3d12::core

View File

@@ -1,7 +1,7 @@
#include "D3D12Resources.h"
#include "D3D12Core.h"
namespace XEngine::graphics::d3d12{
namespace XEngine::graphics::d3d12::core{
//////////// DESCRIPTOR HEAP ////////////
// 该类将被多个线程并发访问:资源创建(如纹理)与资源销毁/释放可能发生在不同线程,
// 因此需要同步机制保护内部数据结构
@@ -45,7 +45,8 @@ descriptor_heap::initialize(u32 capacity, bool is_shader_visible)
_size = 0;
for(u32 i = 0; i < capacity; ++i) _free_handles[i] = i;
DEBUG_OP(for(u32 i{0}; i<frame_buffer_count;++i) assert(_deferred_free_indices[i].empty()));
_descriptor_size = device->GetDescriptorHandleIncrementSize(_type);
_cpu_start = _heap->GetCPUDescriptorHandleForHeapStart();
_gpu_start = is_shader_visible
@@ -59,7 +60,39 @@ descriptor_heap::initialize(u32 capacity, bool is_shader_visible)
void
descriptor_heap::release()
{
assert(!_size);
core::deferred_release(_heap);
}
// 处理延迟释放
// 注意这里的free_handles对应的是一整块连续的内存,而不是对应的描述符句柄的索引index
// 描述符自生是记录了自己的地址(通过简单的计算可以得出偏移和索引)
// 所以释放后,只需要指向释放出来的索引index即可,这样下次新增时即可以
// 放在对应的空闲区域.
// 这一切都是应为freelist和内存是一对一对应的关系
// 延迟释放机制确保GPU完成使用后才回收描述符避免GPU仍在访问时重用
// 使用双缓冲/多帧缓冲机制,当前帧释放的描述符会在若干帧后安全回收
// _free_handles作为空闲索引栈_size指向栈顶回收的索引压入栈中供后续分配使用
void
descriptor_heap::process_deferred_release(u32 frame_index)
{
std::lock_guard lock(_mutex);
assert(frame_index < frame_buffer_count);
utl::vector<u32>& indices { _deferred_free_indices[frame_index] };
if(!indices.empty())
{
for(auto index : indices)
{
--_size;
_free_handles[_size] = index;
}
indices.clear();
}
}
descriptor_handle
descriptor_heap::allocate()
{
@@ -95,6 +128,9 @@ descriptor_heap::free(descriptor_handle handle)
const u32 index{ (u32)(handle.cpu.ptr - _cpu_start.ptr) / _descriptor_size };
assert(handle.index == index);
const u32 frame_index{ core::current_frame_index() };
_deferred_free_indices[frame_index].push_back(index);
core::set_deferred_release_flag();
handle = {};
}

View File

@@ -1,67 +1,420 @@
#pragma once
#include "D3D12CommonHeaders.h"
#include "D3D12CommonHeader.h"
namespace XEngine::graphics::d3d12{
namespace XEngine::graphics::d3d12::core {
/**
* @brief 前向声明,用于友元类关系
*/
class descriptor_heap;
/**
* @struct descriptor_handle
* @brief 封装 CPU 和 GPU 描述符句柄的包装结构体
*
* @details
* 该结构体为 D3D12 中的描述符句柄管理提供统一接口。
* 描述符句柄表示描述符堆中特定描述符的指针。
*
* - **CPU 句柄**CPU 用于创建和修改描述符视图SRV、UAV、CBV、RTV、DSV
* - **GPU 句柄**GPU 在着色器执行期间访问描述符(仅着色器可见)
*
* @note
* - CPU 句柄在分配后始终有效
* - GPU 句柄仅在从着色器可见的描述符堆分配时有效
* - DEBUG 模式下存储额外的跟踪信息用于验证和调试
*
* @see descriptor_heap
* @see D3D12_CPU_DESCRIPTOR_HANDLE
* @see D3D12_GPU_DESCRIPTOR_HANDLE
*
* @example
* @code
* descriptor_handle handle = heap.allocate();
* if (handle.is_valid()) {
* device->CreateShaderResourceView(texture, &srvDesc, handle.cpu);
* if (handle.is_shader_visible()) {
* cmdList->SetGraphicsRootDescriptorTable(0, handle.gpu);
* }
* }
* @endcode
*/
struct descriptor_handle
{
/**
* @brief CPU 端描述符句柄
*
* @details
* 用于 CPU 端操作如创建视图CreateShaderResourceView、
* CreateUnorderedAccessView、CreateRenderTargetView 等)。
* 从任何类型的描述符堆成功分配后,此句柄始终有效。
*/
D3D12_CPU_DESCRIPTOR_HANDLE cpu{};
/**
* @brief GPU 端描述符句柄
*
* @details
* 用于通过根描述符表将描述符绑定到图形管线。
* 仅当从着色器可见的描述符堆分配时有效
* CBV_SRV_UAV 或 SAMPLER 类型,带有 D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE
*
* @note 值为 0 表示此句柄不是着色器可见的
*/
D3D12_GPU_DESCRIPTOR_HANDLE gpu{};
/**
* @brief 检查此句柄是否指向有效的描述符
*
* @return 如果 CPU 句柄非空则返回 true否则返回 false
*
* @note 有效句柄不保证底层描述符已正确初始化
*/
constexpr bool is_valid() const { return cpu.ptr != 0; }
/**
* @brief 检查此句柄是否可从 GPU 着色器访问
*
* @return 如果 GPU 句柄非空(着色器可见)则返回 true否则返回 false
*
* @details
* 着色器可见句柄可通过 SetGraphicsRootDescriptorTable 或
* SetComputeRootDescriptorTable 绑定到管线。非着色器可见句柄RTV、DSV
* 只能通过 OMSetRenderTargets 或类似方法绑定。
*/
constexpr bool is_shader_visible() const { return gpu.ptr != 0; }
#ifdef _DEBUG
private:
/**
* @brief 指向所属描述符堆的指针(仅 DEBUG 模式)
*
* @details
* 用于验证以确保句柄被释放到正确的堆。
* 此指针在分配时设置,在释放操作时验证。
*/
friend class descriptor_heap;
descriptor_heap* container{ nullptr };
u32 index{ u32_invalid_id };
descriptor_heap* container{nullptr};
/**
* @brief 描述符堆内的索引(仅 DEBUG 模式)
*
* @details
* 存储此描述符在其所属堆中的逻辑索引。
* 用于调试和验证目的,跟踪描述符分配情况。
*/
u32 index{u32_invalid_id};
#endif
}; // descriptor_handle
};
/**
* @class descriptor_heap
* @brief 管理 D3D12 描述符堆的分配、释放和生命周期
*
* @details
* 该类为 D3D12 描述符堆提供高级抽象,实现:
*
* - **池化分配**:基于空闲列表管理,实现 O(1) 分配/释放
* - **线程安全**:互斥锁保护操作,支持多线程资源创建
* - **延迟释放**:帧感知的资源生命周期管理,防止 GPU 危险
* - **类型安全**:通过构造函数参数实现编译时类型绑定
*
* ## 描述符堆类型
*
* | 类型 | 着色器可见 | 典型用途 |
* |------|------------|----------|
* | CBV_SRV_UAV | 可选 | 常量缓冲区、纹理、UAV |
* | SAMPLER | 可选 | 纹理采样器 |
* | RTV | 否 | 渲染目标视图 |
* | DSV | 否 | 深度模板视图 |
*
* ## 内存模型
*
* - **着色器可见堆**:分配在 GPU 可见内存(显存或映射到 GPU 的系统内存)
* - **非着色器可见堆**仅分配在系统内存CPU 可访问)
*
* ## 线程安全
*
* 所有公有方法都是线程安全的。多个线程可以同时从同一个堆分配和释放描述符。
* 内部互斥锁确保操作的原子性。
*
* ## 延迟释放机制
*
* 当描述符被释放时,不会立即返回到空闲池。而是放入帧索引的延迟释放队列。
* 当对应帧在 GPU 上完成后,调用 process_deferred_release() 时,描述符才会
* 返回到空闲池,确保不会发生 GPU-GPU 或 CPU-GPU 危险。
*
* @see descriptor_handle
* @see D3D12_DESCRIPTOR_HEAP_TYPE
* @see ID3D12DescriptorHeap
*
* @example
* @code
* // 创建一个包含 1024 个描述符的着色器可见 CBV/SRV/UAV 堆
* descriptor_heap cbv_srv_heap{D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV};
* cbv_srv_heap.initialize(1024, true);
*
* // 分配描述符
* descriptor_handle tex_handle = cbv_srv_heap.allocate();
* device->CreateShaderResourceView(texture, &srvDesc, tex_handle.cpu);
*
* // 绑定到管线
* ID3D12DescriptorHeap* heaps[] = {cbv_srv_heap.heap()};
* cmdList->SetDescriptorHeaps(1, heaps);
* cmdList->SetGraphicsRootDescriptorTable(0, tex_handle.gpu);
*
* // 不再需要时释放(延迟到帧完成)
* cbv_srv_heap.free(tex_handle);
* @endcode
*/
class descriptor_heap
{
public:
explicit descriptor_heap(const D3D12_DESCRIPTOR_HEAP_TYPE type): _type(type){}
DISABLE_COPY_AND_MOVE(descriptor_heap);
~descriptor_heap(){assert(!_heap);}
/**
* @brief 构造指定类型的描述符堆
*
* @param type D3D12 描述符堆类型CBV_SRV_UAV、SAMPLER、RTV 或 DSV
*
* @note 此构造函数不分配任何资源。调用 initialize() 创建堆
*/
explicit descriptor_heap(const D3D12_DESCRIPTOR_HEAP_TYPE type) : _type(type) {}
/**
* @brief 禁用拷贝构造以防止资源重复
*/
DISABLE_COPY_AND_MOVE(descriptor_heap);
/**
* @brief 析构函数,断言所有资源已释放
*
* @note 堆必须在析构前通过 release() 显式释放
*/
~descriptor_heap() { assert(!_heap); }
/**
* @brief 初始化具有指定容量的描述符堆
*
* @param capacity 此堆可容纳的最大描述符数量
* @param is_shader_visible 堆是否应着色器可见
*
* @return 初始化成功返回 true否则返回 false
*
* @details
* - 对于 RTV/DSV 堆is_shader_visible 强制为 false设计上 GPU 不可访问)
* - 容量不得超过 D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2
* - 对于采样器堆,容量不得超过 D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE
*
* @pre 堆必须未初始化(如需重新初始化,先调用 release()
* @post 堆已准备好进行 allocate() 调用
*/
bool initialize(u32 capacity, bool is_shader_visible);
/**
* @brief 处理特定帧的延迟释放
*
* @param frame_index 应处理其延迟释放的帧索引
*
* @details
* 此方法应在新帧开始时调用,确认 GPU 已完成对应帧的处理。
* 它将该帧期间释放的所有描述符返回到空闲池。
*
* @pre GPU 必须已完成指定帧(通过 Fence 同步确认)
*/
void process_deferred_release(u32 frame_index);
/**
* @brief 释放此堆持有的所有资源
*
* @details
* - 通过 flush() 等待所有帧完成
* - 释放底层 ID3D12DescriptorHeap
* - 清除所有内部跟踪结构
*
* @post 堆处于未初始化状态,可重新初始化
*/
void release();
/**
* @brief 从堆中分配描述符
*
* @return 指向已分配描述符的 descriptor_handle
*
* @throws 如果堆已满或未初始化则触发断言失败
*
* @details
* - 线程安全:使用互斥锁保护内部状态
* - O(1) 复杂度:从空闲列表弹出
* - 返回的句柄包含 CPU 和 GPU如果着色器可见地址
*
* @pre 堆必须已初始化且有可用容量
* @note 标记 [[nodiscard]] 以防止意外丢弃句柄
*/
[[nodiscard]] descriptor_handle allocate();
/**
* @brief 释放描述符,将其放入延迟释放队列
*
* @param handle 要释放的描述符句柄
*
* @details
* - 线程安全:使用互斥锁保护内部状态
* - 描述符不会立即可重用
* - 在 GPU 完成当前帧后调用 process_deferred_release() 时,
* 描述符才会返回到空闲池
* - 无效句柄会被安全忽略
*
* @note DEBUG 模式下验证句柄属于此堆
*/
void free(descriptor_handle handle);
/**
* @brief 返回 D3D12 描述符堆类型
* @return 此堆的类型CBV_SRV_UAV、SAMPLER、RTV 或 DSV
*/
constexpr D3D12_DESCRIPTOR_HEAP_TYPE type() const { return _type; }
/**
* @brief 返回堆中第一个描述符的 CPU 句柄
* @return 起始 CPU 描述符句柄
*/
constexpr D3D12_CPU_DESCRIPTOR_HANDLE cpu_start() const { return _cpu_start; }
/**
* @brief 返回堆中第一个描述符的 GPU 句柄
* @return 起始 GPU 描述符句柄,如果非着色器可见则为 {0}
*/
constexpr D3D12_GPU_DESCRIPTOR_HANDLE gpu_start() const { return _gpu_start; }
constexpr ID3D12DescriptorHeap *const heap() const { return _heap; }
/**
* @brief 返回底层 D3D12 描述符堆接口
* @return 指向 ID3D12DescriptorHeap 的指针,如果未初始化则为 nullptr
*/
constexpr ID3D12DescriptorHeap* const heap() const { return _heap; }
/**
* @brief 返回此堆可容纳的最大描述符数量
* @return 堆的总容量
*/
constexpr u32 capacity() const { return _capacity; }
/**
* @brief 返回当前已分配的描述符数量
* @return 已分配描述符计数(不包括延迟释放的)
*/
constexpr u32 size() const { return _size; }
/**
* @brief 返回单个描述符的字节大小
* @return 描述符大小,因堆类型和硬件而异
*/
constexpr u32 descriptor_size() const { return _descriptor_size; }
/**
* @brief 检查此堆是否着色器可见
* @return 如果 GPU 句柄有效则返回 true否则返回 false
*/
constexpr bool is_shader_visible() const { return _gpu_start.ptr != 0; }
private:
// 一个描述符堆是一个内存块,基于他是否着色器可见,分配在系统内存还是显存
ID3D12DescriptorHeap* _heap;
/**
* @brief 底层 D3D12 描述符堆接口
*
* @details
* 内存位置取决于着色器可见性:
* - 着色器可见:分配在 GPU 可访问内存(显存或映射到 GPU 的系统内存)
* - 非着色器可见:仅分配在系统内存
*/
ID3D12DescriptorHeap* _heap{nullptr};
// CPU起始句柄用于CPU端描述符操作
D3D12_CPU_DESCRIPTOR_HANDLE _cpu_start{};
// GPU起始句柄仅当堆为着色器可见时有效
D3D12_GPU_DESCRIPTOR_HANDLE _gpu_start{};
/**
* @brief 堆中第一个描述符的 CPU 句柄
*
* @details
* 用作计算单个描述符 CPU 句柄的基地址。
* initialize() 成功调用后立即有效。
*/
D3D12_CPU_DESCRIPTOR_HANDLE _cpu_start{};
// 空闲的描述符句柄
std::unique_ptr<u32[]> _free_handles{};
/**
* @brief 堆中第一个描述符的 GPU 句柄
*
* @details
* 仅对着色器可见堆有效。用作计算可通过根描述符表绑定到管线的
* GPU 句柄的基地址。值为 {0} 表示非着色器可见堆。
*/
D3D12_GPU_DESCRIPTOR_HANDLE _gpu_start{};
// 用于保护资源初始化的互斥锁
std::mutex _mutex;
/**
* @brief 存储可用描述符索引的空闲列表
*
* @details
* 实现为预分配数组,实现 O(1) 分配/释放。
* 初始填充所有索引 [0, capacity-1]。
* 分配描述符时,从前面消耗索引。
* 释放描述符后(延迟处理完成),索引返回。
*/
std::unique_ptr<u32[]> _free_handles{};
// 描述符堆的容量
u32 _capacity{0};
// 已经分配的描述符数量
u32 _size{0};
// 不同的描述符堆类型在不同的硬件上有不同的大小,所以需要记录下描述符的大小
u32 _descriptor_size{0};
const D3D12_DESCRIPTOR_HEAP_TYPE _type;
/**
* @brief 每帧的延迟释放队列
*
* @details
* 当调用 free() 时,描述符索引放入当前帧的队列。
* 只有在该帧 GPU 完成后调用 process_deferred_release() 时,
* 索引才会返回到 _free_handles。
*
* 这防止了 GPU 危险:当描述符可能仍被 GPU 使用时被新资源重用。
*
* 数组大小为 frame_buffer_count通常为 3三重缓冲
*/
utl::vector<u32> _deferred_free_indices[frame_buffer_count]{};
/**
* @brief 用于线程安全访问内部状态的互斥锁
*
* @details
* 保护所有可变状态_size、_free_handles 和 _deferred_free_indices。
* 必需,因为资源创建/销毁可能发生在多个线程上。
*/
std::mutex _mutex{};
/**
* @brief 此堆可容纳的最大描述符数量
*
* @details
* 在 initialize() 时设置,此后不可变。
* 用于边界检查和空闲列表初始化。
*/
u32 _capacity{0};
/**
* @brief 当前已分配的描述符数量
*
* @details
* 表示从 _free_handles 消耗的索引数量。
* 不包括延迟释放队列中的描述符。
*/
u32 _size{0};
/**
* @brief 单个描述符的字节大小
*
* @details
* 从 ID3D12Device::GetDescriptorHandleIncrementSize() 获取。
* 因堆类型和硬件而异。用于句柄偏移计算。
*/
u32 _descriptor_size{0};
/**
* @brief D3D12 描述符堆类型
*
* @details
* 构造时设置,不可变。决定:
* - 可创建的视图类型SRV、UAV、RTV 等)
* - 是否支持着色器可见性
* - 允许的最大容量
*/
const D3D12_DESCRIPTOR_HEAP_TYPE _type;
};
}
} // namespace XEngine::graphics::d3d12::core