# 变更记录:Surface 管理重构与容器文档完善 **提交日期**: 2026-03-31 **提交哈希**: `b284d81` **变更类型**: Bug修复 + 文档完善 --- ## 变更概述 本次提交修复了 surface 管理中的资源重复释放问题,重构为使用 `utl::free_list` 容器,并为 `FreeList.h` 和 `Vector.h` 添加了完整的国际标准中文注释。 ## 修改文件 ### 核心修复 | 文件 | 变更说明 | |------|----------| | `D3D12Core.cpp` | 改用 `utl::free_list` 管理 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; 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 strings; // destruct = true // POD 类型优化:跳过析构 utl::vector 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)