# 变更记录:延迟释放机制与描述符堆完善 **提交日期**: 2026-03-30 **提交哈希**: `6e0df60` **变更类型**: 功能完善 --- ## 变更概述 本次提交完善了描述符堆的延迟释放机制,实现了完整的 FreeList 栈式索引管理,并添加了详细的中文注释文档。 ## 修改文件 ### Engine/Graphics/Direct3D12/ | 文件 | 变更说明 | |------|----------| | `D3D12Resources.h` | 添加完整的中文 Doxygen 注释 | | `D3D12Resource.cpp` | 实现 `process_deferred_release()` 延迟释放处理 | | `D3D12Core.h` | 添加 `deferred_release` 模板函数和 `current_frame_index()` | | `D3D12Core.cpp` | 实现延迟释放队列和帧索引管理 | --- ## 技术要点 ### 1. FreeList 栈式索引管理 #### 数据结构 ```cpp // 空闲索引栈 std::unique_ptr _free_handles{}; // 栈顶指针(同时也是已分配数量) u32 _size{0}; // 总容量 u32 _capacity{0}; ``` #### 核心原理 `_free_handles` 是一个**预分配的数组**,同时充当**栈**的角色: - `_size` 既是已分配数量,也是栈顶指针 - 分配时:从栈顶弹出索引 - 释放时:将索引压入栈顶 ``` 初始化状态(capacity = 5): ┌─────────────────────────────────────┐ │ _free_handles = [0, 1, 2, 3, 4] │ │ _size = 0 (栈顶指向位置 0) │ └─────────────────────────────────────┘ 分配 2 个描述符后: ┌─────────────────────────────────────┐ │ _free_handles = [0, 1, 2, 3, 4] │ │ _size = 2 (栈顶指向位置 2) │ │ 已分配索引: 0, 1 │ └─────────────────────────────────────┘ 释放索引 0 后(延迟处理完成): ┌─────────────────────────────────────┐ │ _free_handles = [0, 0, 2, 3, 4] │ │ _size = 1 (栈顶指向位置 1) │ │ 已分配索引: 1 │ │ 索引 0 已回收到空闲栈 │ └─────────────────────────────────────┘ ``` #### 分配算法 ```cpp descriptor_handle allocate() { std::lock_guard lock(_mutex); // 从栈顶取出索引 const u32 index = _free_handles[_size]; ++_size; // 栈顶指针上移 // 计算句柄地址 const u32 offset = index * _descriptor_size; handle.cpu.ptr = _cpu_start.ptr + offset; return handle; } ``` **时间复杂度**: O(1) #### 释放算法 ```cpp void free(descriptor_handle handle) { // 不立即释放,放入延迟队列 const u32 frame_index = current_frame_index(); _deferred_free_indices[frame_index].push_back(handle.index); } void process_deferred_release(u32 frame_index) { for(auto index : _deferred_free_indices[frame_index]) { --_size; // 栈顶指针下移 _free_handles[_size] = index; // 索引压入栈顶 } } ``` **时间复杂度**: O(n),n 为该帧释放的描述符数量 --- ### 2. 延迟释放机制 #### 为什么需要延迟释放? ``` 问题场景: 帧 0: CPU 分配描述符索引 0 → 绑定纹理 A 帧 1: CPU 释放描述符索引 0 → 立即重用 → 绑定纹理 B 帧 0: GPU 还在执行,访问描述符索引 0 → 读到纹理 B 的数据! ↑ GPU 危险!数据竞争! ``` #### 解决方案 ``` 帧 0: CPU 分配描述符索引 0 → 绑定纹理 A 帧 1: CPU 释放描述符索引 0 → 放入延迟队列(帧 1) 帧 2: CPU 继续工作... 帧 0: GPU 完成帧 0 的执行 帧 3: CPU 处理帧 0 的延迟释放 → 索引 0 回到空闲池 ↑ 安全!GPU 已完成使用 ``` #### 数据结构 ```cpp // 每帧一个延迟释放队列 utl::vector _deferred_free_indices[frame_buffer_count]{}; // 全局延迟释放资源队列 utl::vector deferred_releases[frame_buffer_count]{}; // 延迟释放标志 u32 deferred_release_flag[frame_buffer_count]{}; ``` --- ### 3. 完整流程图 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 初始化 │ │ _free_handles = [0, 1, 2, 3, 4] │ │ _size = 0 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ allocate() │ │ index = _free_handles[_size] // 取索引 0 │ │ _size++ // _size = 1 │ │ 返回描述符句柄(索引 0) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ allocate() │ │ index = _free_handles[_size] // 取索引 1 │ │ _size++ // _size = 2 │ │ 返回描述符句柄(索引 1) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ free(索引 0) │ │ 放入延迟队列: _deferred_free_indices[当前帧].push_back(0) │ │ _size 不变,_free_handles 不变 │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ GPU 完成帧(Fence 同步) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ process_deferred_release(frame_index) │ │ --_size // _size = 1 │ │ _free_handles[_size] = 0 // 索引 0 压入栈顶 │ │ _free_handles = [0, 0, 2, 3, 4] │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ allocate() │ │ index = _free_handles[_size] // 取索引 0(重用) │ │ _size++ // _size = 2 │ │ 返回描述符句柄(索引 0) │ └─────────────────────────────────────────────────────────────────┘ ``` --- ### 4. 线程安全 ```cpp // 所有操作都使用互斥锁保护 std::mutex _mutex{}; void allocate() { std::lock_guard lock(_mutex); // 自动加锁 // ... 操作 } // 自动解锁 ``` --- ## 设计优势 | 特性 | 说明 | |------|------| | **O(1) 分配** | 栈顶弹出,无需遍历查找 | | **O(1) 释放** | 栈顶压入,无需查找位置 | | **内存高效** | 预分配数组,无动态分配开销 | | **线程安全** | 互斥锁保护所有操作 | | **GPU 安全** | 延迟释放防止数据竞争 | --- ## 后续工作 - [ ] 实现交换链 - [ ] 实现渲染目标视图 - [ ] 渲染第一个三角形 --- ## 相关文档 - [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)