fix(d3d12): 修复 Surface 重复释放问题,完善容器文档
- 改用 utl::free_list 管理 surface,避免 vector 扩容导致的资源重复释放 - 为 d3d12_surface 添加移动语义,禁用拷贝构造 - 添加撕裂检测支持(DXGI_PRESENT_ALLOW_TEARING) - 为 FreeList.h 和 Vector.h 添加完整的 Doxygen 中文注释 - 更新 D3D12 学习 Wiki,添加 free_list 章节
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# 变更记录:交换链与渲染表面实现
|
||||
|
||||
**提交日期**: 2026-03-31
|
||||
**提交哈希**: `待定`
|
||||
**提交哈希**: `95d8893`
|
||||
**变更类型**: 功能新增
|
||||
|
||||
---
|
||||
|
||||
221
docs/changelogs/2026-03/20260331-surface-freelist-refactor.md
Normal file
221
docs/changelogs/2026-03/20260331-surface-freelist-refactor.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# 变更记录: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)
|
||||
@@ -604,6 +604,75 @@ D3D12_VIEWPORT viewport{
|
||||
D3D12_RECT scissor_rect{0, 0, width, height};
|
||||
```
|
||||
|
||||
### 8.6 Surface 管理与 free_list
|
||||
|
||||
#### 问题:Vector 扩容导致的资源重复释放
|
||||
|
||||
使用 `utl::vector` 管理 surface 时,扩容会触发元素移动:
|
||||
|
||||
```
|
||||
vector 扩容流程:
|
||||
1. 分配新内存块
|
||||
2. 移动元素到新内存(默认移动是浅拷贝)
|
||||
3. 析构旧位置的元素 → 调用 release()
|
||||
4. 新位置的元素持有悬空指针 → 崩溃!
|
||||
```
|
||||
|
||||
#### 解决方案:使用 free_list
|
||||
|
||||
`utl::free_list` 是带槽位复用机制的容器:
|
||||
|
||||
```cpp
|
||||
// 定义 surface 集合类型
|
||||
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 数据结构
|
||||
|
||||
```
|
||||
初始状态:
|
||||
_array: [ 空 | 空 | 空 | 空 ]
|
||||
_next_free_index = invalid_id
|
||||
|
||||
添加元素 A、B、C 后:
|
||||
_array: [ A | B | C | 空 ]
|
||||
_next_free_index = invalid_id
|
||||
|
||||
删除元素 B 后:
|
||||
_array: [ A | ->2 | C | 空 ] // 槽位1存储下一个空闲索引
|
||||
_next_free_index = 1
|
||||
|
||||
添加新元素 D:
|
||||
_array: [ D | ->2 | C | 空 ] // 复用槽位1
|
||||
_next_free_index = 2
|
||||
```
|
||||
|
||||
#### free_list vs vector
|
||||
|
||||
| 特性 | free_list | vector |
|
||||
|------|-----------|--------|
|
||||
| 删除复杂度 | O(1) | O(n) |
|
||||
| 索引稳定性 | 删除后可复用 | 删除后失效 |
|
||||
| 内存管理 | 槽位复用 | 可能扩容移动 |
|
||||
| 适用场景 | 资源句柄管理 | 顺序数据存储 |
|
||||
|
||||
## 9. 渲染表面与窗口
|
||||
|
||||
### 9.1 render_surface 结构
|
||||
|
||||
Reference in New Issue
Block a user