#pragma once #include "D3D12CommonHeader.h" 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}; /** * @brief 描述符堆内的索引(仅 DEBUG 模式) * * @details * 存储此描述符在其所属堆中的逻辑索引。 * 用于调试和验证目的,跟踪描述符分配情况。 */ u32 index{u32_invalid_id}; #endif }; /** * @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: /** * @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; } /** * @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: /** * @brief 底层 D3D12 描述符堆接口 * * @details * 内存位置取决于着色器可见性: * - 着色器可见:分配在 GPU 可访问内存(显存或映射到 GPU 的系统内存) * - 非着色器可见:仅分配在系统内存 */ ID3D12DescriptorHeap* _heap{nullptr}; /** * @brief 堆中第一个描述符的 CPU 句柄 * * @details * 用作计算单个描述符 CPU 句柄的基地址。 * initialize() 成功调用后立即有效。 */ D3D12_CPU_DESCRIPTOR_HANDLE _cpu_start{}; /** * @brief 堆中第一个描述符的 GPU 句柄 * * @details * 仅对着色器可见堆有效。用作计算可通过根描述符表绑定到管线的 * GPU 句柄的基地址。值为 {0} 表示非着色器可见堆。 */ D3D12_GPU_DESCRIPTOR_HANDLE _gpu_start{}; /** * @brief 存储可用描述符索引的空闲列表 * * @details * 实现为预分配数组,实现 O(1) 分配/释放。 * 初始填充所有索引 [0, capacity-1]。 * 分配描述符时,从前面消耗索引。 * 释放描述符后(延迟处理完成),索引返回。 */ std::unique_ptr _free_handles{}; /** * @brief 每帧的延迟释放队列 * * @details * 当调用 free() 时,描述符索引放入当前帧的队列。 * 只有在该帧 GPU 完成后调用 process_deferred_release() 时, * 索引才会返回到 _free_handles。 * * 这防止了 GPU 危险:当描述符可能仍被 GPU 使用时被新资源重用。 * * 数组大小为 frame_buffer_count(通常为 3,三重缓冲)。 */ utl::vector _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