核心变更: - 新增 d3d12_texture 和 d3d12_render_texture 类 - 新增 d3d12_texture_init_info 结构,支持三种资源创建方式 - 新增 D3D12Helpers.h,提供堆属性辅助结构 - 改用 utl::free_list 管理 surface,解决重复释放问题 - 为 d3d12_surface 添加移动语义,支持撕裂检测 文档完善: - 为 FreeList.h 和 Vector.h 添加完整 Doxygen 中文注释 - 更新 D3D12 学习 Wiki,添加 SRV、资源创建方式、纹理资源类章节 - 新增变更记录文档
481 lines
15 KiB
C++
481 lines
15 KiB
C++
#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;
|
||
};
|
||
|
||
struct d3d12_texture_init_info
|
||
{
|
||
ID3D12Heap1* heap{nullptr};
|
||
ID3D12Resource* resource{nullptr};
|
||
D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr};
|
||
D3D12_RESOURCE_DESC* desc{nullptr};
|
||
D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{};
|
||
D3D12_RESOURCE_STATES initial_state{};
|
||
D3D12_CLEAR_VALUE clear_value{};
|
||
};
|
||
|
||
|
||
class d3d12_texture
|
||
{
|
||
public:
|
||
constexpr static u32 max_mips{ 14 };
|
||
d3d12_texture() = default;
|
||
explicit d3d12_texture(d3d12_texture_init_info info);
|
||
DISABLE_COPY(d3d12_texture);
|
||
constexpr d3d12_texture(d3d12_texture&& o)
|
||
: _resource(o._resource), _srv(o._srv) //这些值只是指针和句柄,不需要move
|
||
{
|
||
o.reset();
|
||
}
|
||
|
||
constexpr d3d12_texture& operator=(d3d12_texture&& o)
|
||
{
|
||
assert(this != &o);
|
||
if(this != &o)
|
||
{
|
||
release();
|
||
move(o);
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
void release();
|
||
constexpr ID3D12Resource *const resource() const { return _resource; }
|
||
constexpr descriptor_handle srv() const { return _srv; }
|
||
|
||
private:
|
||
constexpr void move(d3d12_texture& o)
|
||
{
|
||
_resource = o._resource;
|
||
_srv = o._srv;
|
||
o.reset();
|
||
}
|
||
|
||
constexpr void reset()
|
||
{
|
||
_resource = nullptr;
|
||
_srv = {};
|
||
}
|
||
|
||
ID3D12Resource* _resource{nullptr};
|
||
descriptor_handle _srv;
|
||
};
|
||
|
||
|
||
|
||
} // namespace XEngine::graphics::d3d12
|