Files
DX12/docs/changelogs/2026-03/20260330-d3d12-deferred-release.md
SpecialX b6c0211d6a feat(d3d12): 完善描述符堆延迟释放机制与FreeList栈式索引管理
- 添加完整的中文Doxygen注释文档
- 实现process_deferred_release()延迟释放处理
- 添加deferred_release模板函数和current_frame_index()
- 实现延迟释放队列和帧索引管理
- 详细说明FreeList栈式索引分配/释放算法
- 更新D3D12学习Wiki,添加延迟释放机制章节
2026-03-30 16:59:27 +08:00

251 lines
9.8 KiB
Markdown
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.
# 变更记录:延迟释放机制与描述符堆完善
**提交日期**: 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<u32[]> _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<u32> _deferred_free_indices[frame_buffer_count]{};
// 全局延迟释放资源队列
utl::vector<IUnknown*> 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)