Files
DX12/Engine/Graphics/Direct3D12/D3D12Resources.h
SpecialX 95d8893182 feat(d3d12): 实现交换链与渲染表面管理
- 新增 d3d12_surface 类,管理交换链和渲染目标
- 实现三重缓冲后台缓冲区管理
- 添加视口和裁剪矩形配置
- 修复 GraphicsPlatformInterface.h 循环包含问题
- 添加完整的中文 Doxygen 注释
- 更新 D3D12 学习 Wiki,添加交换链章节
2026-03-31 11:12:11 +08:00

421 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include "D3D12CommonHeader.h"
namespace XEngine::graphics::d3d12 {
/**
* @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<u32[]> _free_handles{};
/**
* @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