Files
DX12/docs/changelogs/2026-03/20260331-surface-freelist-refactor.md
SpecialX 80cb696a3c fix(d3d12): 修复 Surface 重复释放问题,完善容器文档
- 改用 utl::free_list 管理 surface,避免 vector 扩容导致的资源重复释放
- 为 d3d12_surface 添加移动语义,禁用拷贝构造
- 添加撕裂检测支持(DXGI_PRESENT_ALLOW_TEARING)
- 为 FreeList.h 和 Vector.h 添加完整的 Doxygen 中文注释
- 更新 D3D12 学习 Wiki,添加 free_list 章节
2026-03-31 16:49:25 +08:00

222 lines
5.0 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.
# 变更记录Surface 管理重构与容器文档完善
**提交日期**: 2026-03-31
**提交哈希**: `b284d81`
**变更类型**: Bug修复 + 文档完善
---
## 变更概述
本次提交修复了 surface 管理中的资源重复释放问题,重构为使用 `utl::free_list` 容器,并为 `FreeList.h``Vector.h` 添加了完整的国际标准中文注释。
## 修改文件
### 核心修复
| 文件 | 变更说明 |
|------|----------|
| `D3D12Core.cpp` | 改用 `utl::free_list<d3d12_surface>` 管理 surface |
| `D3D12Surface.h` | 添加移动语义,禁用拷贝,支持撕裂检测 |
| `D3D12Surface.cpp` | 完善交换链创建和资源释放逻辑 |
### 文档完善
| 文件 | 变更说明 |
|------|----------|
| `FreeList.h` | 添加完整 Doxygen 中文注释 |
| `Vector.h` | 添加完整 Doxygen 中文注释 |
---
## Bug 修复详情
### 问题Surface 重复释放
**现象**:关闭多个窗口时,`release()` 被调用超过预期次数,导致空指针崩溃。
**根本原因**
1. **手动调用析构函数**
```cpp
// 错误做法
surfaces[id].~d3d12_surface(); // 对象仍在 vector 中
// vector 析构时会再次调用析构函数
```
2. **Vector 扩容浅拷贝**
```
vector 扩容时:
1. 分配新内存
2. 移动元素(默认移动是浅拷贝)
3. 析构旧元素 → 释放资源
4. 新元素持有悬空指针 → 崩溃
```
3. **描述符句柄未重置**
```cpp
core::rtv_heap().free(data.rtv);
// free() 只修改局部变量data.rtv 仍指向已释放的描述符
```
### 解决方案
#### 1. 使用 `utl::free_list` 替代 `utl::vector`
```cpp
// D3D12Core.cpp
using surface_collection = utl::free_list<d3d12_surface>;
surface_collection surfaces;
// 创建 surface
surface create_surface(platform::window window)
{
surfaces.emplace_back(window);
surface_id id{ (u32)surfaces.size() - 1 };
surfaces[id].create_swap_chain(...);
return surface{id};
}
// 删除 surface
void remove_surface(surface_id id)
{
gfx_command.flush();
surfaces.remove(id); // free_list 的 remove 正确处理
}
```
#### 2. 实现移动语义
```cpp
class d3d12_surface
{
public:
// 移动构造函数
d3d12_surface(d3d12_surface&& other) noexcept
: _swap_chain{other._swap_chain}
, _window{other._window}
// ... 转移所有资源
{
other._swap_chain = nullptr; // 源对象置空
}
// 禁用拷贝
d3d12_surface(const d3d12_surface&) = delete;
d3d12_surface& operator=(const d3d12_surface&) = delete;
};
```
#### 3. 重置描述符句柄
```cpp
void d3d12_surface::release()
{
for(u32 i = 0; i < frame_buffer_count; ++i)
{
render_target_data& data{ _render_target_data[i] };
core::release(data.resource);
core::rtv_heap().free(data.rtv);
data.rtv = {}; // 重置句柄,防止重复释放
}
core::release(_swap_chain);
}
```
---
## FreeList 容器详解
### 数据结构原理
`utl::free_list` 使用空闲链表管理已删除的槽位:
```
初始状态:
_array: [ 空 | 空 | 空 | 空 ]
_next_free_index = invalid_id
_size = 0
添加元素 A、B、C 后:
_array: [ A | B | C | 空 ]
_next_free_index = invalid_id
_size = 3
删除元素 B 后(槽位 1 被标记为空闲):
_array: [ A | ->2 | C | 空 ] // 槽位 1 存储下一个空闲索引
_next_free_index = 1
_size = 2
添加新元素 D 时(复用槽位 0:
_array: [ D | ->2 | C | 空 ]
_next_free_index = 1
_size = 2
```
### 核心优势
| 特性 | 说明 |
|------|------|
| **O(1) 增删** | 新增和删除操作近似常数时间 |
| **槽位复用** | 删除后的槽位被回收,避免频繁内存分配 |
| **稳定索引** | 使用 u32 索引作为外部句柄,与 ID 系统配合 |
| **内存紧凑** | 底层使用连续内存,缓存友好 |
### 与 Vector 的区别
| 特性 | free_list | vector |
|------|-----------|--------|
| 删除操作 | O(1),槽位复用 | O(n) 需要移动元素 |
| 索引稳定性 | 删除后索引可能被复用 | 删除后索引失效 |
| 内存碎片 | 无碎片,槽位复用 | 删除后内存不释放 |
| 适用场景 | 游戏对象、资源句柄 | 顺序容器、临时数据 |
---
## Vector 容器详解
### 析构策略
模板参数 `destruct` 控制元素析构行为:
```cpp
// 默认:删除时调用析构函数
utl::vector<std::string> strings; // destruct = true
// POD 类型优化:跳过析构
utl::vector<float, false> vertices; // destruct = false
```
### 扩容策略
```
扩容公式new_capacity = (old_capacity + 1) * 3 / 2
初始容量 0 → 1
容量 4 → 6
容量 10 → 15
```
### 无序删除优化
```cpp
// 普通删除O(n),保持顺序
vec.erase(index);
// 无序删除O(1),用末尾元素替换
vec.erase_unordered(index); // 适用于顺序不重要的场景
```
---
## 后续工作
- [ ] 实现深度模板视图
- [ ] 渲染第一个三角形
- [ ] 实现根签名和管线状态对象
---
## 相关文档
- [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)