核心变更: - 新增 d3d12_texture 和 d3d12_render_texture 类 - 新增 d3d12_texture_init_info 结构,支持三种资源创建方式 - 新增 D3D12Helpers.h,提供堆属性辅助结构 - 改用 utl::free_list 管理 surface,解决重复释放问题 - 为 d3d12_surface 添加移动语义,支持撕裂检测 文档完善: - 为 FreeList.h 和 Vector.h 添加完整 Doxygen 中文注释 - 更新 D3D12 学习 Wiki,添加 SRV、资源创建方式、纹理资源类章节 - 新增变更记录文档
10 KiB
10 KiB
变更记录:Surface 管理重构、纹理资源与文档完善
提交日期: 2026-04-01
提交哈希: b72fcf4
变更类型: Bug修复 + 功能新增 + 文档完善
变更概述
本次提交修复了 surface 管理中的资源重复释放问题,新增纹理资源类,并为容器和 D3D12 资源添加了完整的文档。
修改文件
核心修复
| 文件 | 变更说明 |
|---|---|
D3D12Core.cpp |
改用 utl::free_list<d3d12_surface> 管理 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() 被调用超过预期次数,导致空指针崩溃。
根本原因:
-
手动调用析构函数:
// 错误做法 surfaces[id].~d3d12_surface(); // 对象仍在 vector 中 // vector 析构时会再次调用析构函数 -
Vector 扩容浅拷贝:
vector 扩容时: 1. 分配新内存 2. 移动元素(默认移动是浅拷贝) 3. 析构旧元素 → 释放资源 4. 新元素持有悬空指针 → 崩溃 -
描述符句柄未重置:
core::rtv_heap().free(data.rtv); // free() 只修改局部变量,data.rtv 仍指向已释放的描述符
解决方案
1. 使用 utl::free_list 替代 utl::vector
// 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. 实现移动语义
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. 重置描述符句柄
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 控制元素析构行为:
// 默认:删除时调用析构函数
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
无序删除优化
// 普通删除: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 参数说明
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 结构
纹理初始化信息结构,支持三种资源创建方式:
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 绑定:
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;
};
资源创建逻辑
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 辅助结构
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;
}
后续工作
- 实现深度模板视图
- 渲染第一个三角形
- 实现根签名和管线状态对象