# 变更记录:Surface 管理重构、纹理资源与文档完善 **提交日期**: 2026-04-01 **提交哈希**: `b72fcf4` **变更类型**: Bug修复 + 功能新增 + 文档完善 --- ## 变更概述 本次提交修复了 surface 管理中的资源重复释放问题,新增纹理资源类,并为容器和 D3D12 资源添加了完整的文档。 ## 修改文件 ### 核心修复 | 文件 | 变更说明 | |------|----------| | `D3D12Core.cpp` | 改用 `utl::free_list` 管理 surface | | `D3D12Surface.h` | 添加移动语义,禁用拷贝,支持撕裂检测 | | `D3D12Surface.cpp` | 完善交换链创建和资源释放逻辑 | ### 新增功能 | 文件 | 变更说明 | |------|----------| | `D3D12Helpers.h` | 新增堆属性辅助结构 `d3dx::heap_properties` | | `D3D12Resources.h` | 新增 `d3d12_texture_init_info`、`d3d12_texture`、`d3d12_render_texture` 类 | | `D3D12Resource.cpp` | 实现纹理资源创建和 SRV 绑定 | ### 文档完善 | 文件 | 变更说明 | |------|----------| | `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); // 适用于顺序不重要的场景 ``` --- ## 交换链详解 ### 核心职责 交换链连接窗口与渲染管线,管理一组后台缓冲区,并通过 `Present()` 实现缓冲区翻转,将绘制内容显示到窗口。 **交换链只负责**: - 缓冲区的分配与翻转 **开发者需显式完成**: - 绑定渲染目标(`OMSetRenderTargets`) - GPU 同步(Fence) - 状态转换(资源屏障) ### 标准使用流程 ``` 1. 获取当前后台缓冲区(GetBuffer) ↓ 2. 将其绑定为渲染目标(OMSetRenderTargets) ↓ 3. 执行绘制命令(写入该缓冲区) ↓ 4. 提交命令并同步(Fence) ↓ 5. 调用 Present(翻转缓冲区,显示图像) ``` ### 职责边界 | 操作 | 负责方 | 说明 | |------|--------|------| | 缓冲区分配 | 交换链 | 创建指定数量的后台缓冲区 | | 缓冲区翻转 | 交换链 | `Present()` 切换前后缓冲区 | | 渲染目标绑定 | 开发者 | `OMSetRenderTargets()` 绑定 RTV | | GPU 同步 | 开发者 | 使用 Fence 确保 GPU 完成渲染 | | 状态转换 | 开发者 | 资源屏障管理缓冲区状态 | | 窗口大小调整 | 开发者 | 调用 `ResizeBuffers()` 重新分配 | --- ## SRV 创建与资源创建方式 ### CreateShaderResourceView 参数说明 ```cpp void CreateShaderResourceView( ID3D12Resource *pResource, const D3D12_SHADER_RESOURCE_VIEW_DESC *pDesc, D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor ); ``` | 参数 | 说明 | |------|------| | `pResource` | 要创建 SRV 的 GPU 资源指针(必须是有效资源,不能为 nullptr) | | `pDesc` | 视图描述符,指定格式、维度、Mip 级别范围等。为 `nullptr` 时使用资源默认属性 | | `DestDescriptor` | SRV 描述符堆中 CPU 描述符句柄的位置 | **关键点**: - `pDesc` 为 `nullptr` 时,视图描述符默认使用资源本身的格式和全部子资源(空描述符初始化) - 同一个资源可以创建多个不同的 SRV(不同格式、不同 Mip 切片) ### D3D12 资源创建函数对比 | 函数 | 堆类型 | 说明 | |------|--------|------| | `CreateCommittedResource` | 隐式堆 | D3D12 自动分配堆,资源直接映射。适用于大多数常规资源 | | `CreatePlacedResource` | 显式堆 | 资源放置在用户创建的堆的特定偏移位置。用于精确控制内存布局 | | `CreateReservedResource` | 预留资源 | 仅预留虚拟地址,不提交物理内存。用于稀疏资源,支持流式加载 | **选择建议**: - **Committed**:最常用,堆由系统隐式管理 - **Placed**:需要显式堆,资源放置于堆的指定偏移 - **Reserved**:仅预留虚拟地址,用于稀疏资源,实现内存的按需提交 --- ## 纹理资源类 ### d3d12_texture_init_info 结构 纹理初始化信息结构,支持三种资源创建方式: ```cpp struct d3d12_texture_init_info { ID3D12Heap1* heap{nullptr}; // 显式堆(Placed Resource) ID3D12Resource* resource{nullptr}; // 已有资源(直接使用) D3D12_SHADER_RESOURCE_VIEW_DESC* srv_desc{nullptr}; // SRV 描述(nullptr 使用默认) D3D12_RESOURCE_DESC* desc{nullptr}; // 资源描述 D3D12_RESOURCE_ALLOCATION_INFO1 allocation_info{}; // 分配信息(偏移量) D3D12_RESOURCE_STATES initial_state{}; // 初始状态 D3D12_CLEAR_VALUE clear_value{}; // 清除值(RTV/DSV) }; ``` ### d3d12_texture 类 基础纹理类,封装资源创建和 SRV 绑定: ```cpp class d3d12_texture { public: constexpr static u32 max_mips{ 14 }; explicit d3d12_texture(d3d12_texture_init_info info); // 移动语义 d3d12_texture(d3d12_texture&& o); d3d12_texture& operator=(d3d12_texture&& o); // 禁用拷贝 DISABLE_COPY(d3d12_texture); void release(); ID3D12Resource* resource() const; descriptor_handle srv() const; private: ID3D12Resource* _resource{nullptr}; descriptor_handle _srv; }; ``` ### 资源创建逻辑 ```cpp d3d12_texture::d3d12_texture(d3d12_texture_init_info info) { // 优先级 1:使用已有资源 if (info.resource) { _resource = info.resource; } // 优先级 2:Placed Resource(显式堆) else if (info.heap && info.desc) { device->CreatePlacedResource( info.heap, info.allocation_info.Offset, info.desc, ...); } // 优先级 3:Committed Resource(隐式堆) else if (info.desc) { device->CreateCommittedResource( &d3dx::heap_properties.default_heap, D3D12_HEAP_FLAG_NONE, info.desc, ...); } // 创建 SRV _srv = core::srv_heap().allocate(); device->CreateShaderResourceView(_resource, info.srv_desc, _srv.cpu); } ``` ### d3dx::heap_properties 辅助结构 ```cpp namespace XEngine::graphics::d3d12::d3dx { constexpr struct { D3D12_HEAP_PROPERTIES default_heap{ D3D12_HEAP_TYPE_DEFAULT, // GPU 可读写,CPU 不可访问 D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN, 0, // 单 GPU 系统 0 }; } heap_properties; } ``` --- ## 后续工作 - [ ] 实现深度模板视图 - [ ] 渲染第一个三角形 - [ ] 实现根签名和管线状态对象 --- ## 相关文档 - [D3D12学习Wiki](../wiki/D3D12学习Wiki.md)