feat(d3d12): 新增纹理资源类,修复 Surface 重复释放问题

核心变更:
- 新增 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、资源创建方式、纹理资源类章节
- 新增变更记录文档
This commit is contained in:
SpecialX
2026-04-01 16:15:12 +08:00
parent 95d8893182
commit 4d13d8df89
19 changed files with 1821 additions and 233 deletions

View File

@@ -1,7 +1,7 @@
# 变更记录:交换链与渲染表面实现
**提交日期**: 2026-03-31
**提交哈希**: `待定`
**提交哈希**: `95d8893`
**变更类型**: 功能新增
---

View File

@@ -0,0 +1,404 @@
# 变更记录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()` 被调用超过预期次数,导致空指针崩溃。
**根本原因**
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); // 适用于顺序不重要的场景
```
---
## 交换链详解
### 核心职责
交换链连接窗口与渲染管线,管理一组后台缓冲区,并通过 `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;
}
// 优先级 2Placed Resource显式堆
else if (info.heap && info.desc) {
device->CreatePlacedResource(
info.heap,
info.allocation_info.Offset,
info.desc, ...);
}
// 优先级 3Committed 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)

View File

@@ -489,12 +489,307 @@ void allocate() {
} // 自动解锁
```
### 7.9 着色器资源视图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` 时,视图描述符默认使用资源本身的格式和全部子资源:
```cpp
// 使用默认视图属性
device->CreateShaderResourceView(texture, nullptr, rtv_handle);
// 等价于显式指定完整描述
D3D12_SHADER_RESOURCE_VIEW_DESC desc{};
desc.Format = texture->GetDesc().Format;
desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
desc.Texture2D.MostDetailedMip = 0;
desc.Texture2D.MipLevels = texture->GetDesc().MipLevels;
device->CreateShaderResourceView(texture, &desc, rtv_handle);
```
#### 多视图支持
同一个资源可以创建多个不同的 SRV
```cpp
// 原始格式视图
device->CreateShaderResourceView(texture, nullptr, srv_handle0);
// 不同格式视图(如 R32_FLOAT 作为 RGBA 视图)
D3D12_SHADER_RESOURCE_VIEW_DESC rgba_desc{};
rgba_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
// ...
device->CreateShaderResourceView(texture, &rgba_desc, srv_handle1);
// 特定 Mip 切片视图
D3D12_SHADER_RESOURCE_VIEW_DESC mip_desc{};
mip_desc.Texture2D.MostDetailedMip = 2;
mip_desc.Texture2D.MipLevels = 1;
// ...
device->CreateShaderResourceView(texture, &mip_desc, srv_handle2);
```
### 7.10 资源创建方式
D3D12 提供三种资源创建函数,对应不同的堆管理策略:
#### 函数对比
| 函数 | 堆类型 | 说明 |
|------|--------|------|
| `CreateCommittedResource` | 隐式堆 | D3D12 自动分配堆,资源直接映射。适用于大多数常规资源 |
| `CreatePlacedResource` | 显式堆 | 资源放置在用户创建的堆的特定偏移位置。用于精确控制内存布局 |
| `CreateReservedResource` | 预留资源 | 仅预留虚拟地址,不提交物理内存。用于稀疏资源,支持流式加载 |
#### CreateCommittedResource最常用
```cpp
// 系统自动管理堆,最简单的方式
D3D12_HEAP_PROPERTIES heap_props{
.Type = D3D12_HEAP_TYPE_DEFAULT
};
D3D12_RESOURCE_DESC resource_desc{
.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
.Width = width,
.Height = height,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
// ...
};
ID3D12Resource* texture;
device->CreateCommittedResource(
&heap_props,
D3D12_HEAP_FLAG_NONE,
&resource_desc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&texture)
);
```
#### CreatePlacedResource精确控制
```cpp
// 先创建堆
ID3D12Heap* heap;
D3D12_HEAP_DESC heap_desc{
.SizeInBytes = heap_size,
.Properties = { .Type = D3D12_HEAP_TYPE_DEFAULT },
// ...
};
device->CreateHeap(&heap_desc, IID_PPV_ARGS(&heap));
// 在堆的特定偏移放置资源
ID3D12Resource* texture;
device->CreatePlacedResource(
heap,
0, // 偏移量(必须满足对齐要求)
&resource_desc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&texture)
);
```
**适用场景**
- 资源复用(同一堆位置放置不同资源)
- 精确内存对齐
- 自定义内存管理
#### CreateReservedResource稀疏资源
```cpp
// 仅预留虚拟地址,不分配物理内存
ID3D12Resource* sparse_texture;
device->CreateReservedResource(
&resource_desc,
D3D12_RESOURCE_STATE_COMMON,
nullptr,
IID_PPV_ARGS(&sparse_texture)
);
// 后续按需提交物理内存页面
// 使用 UpdateTileMappings 和 MakeResident
```
**适用场景**
- 超大纹理(如地形纹理)按需加载
- 流式资源管理
- 虚拟纹理系统
#### 选择建议
```
┌─────────────────────────────────────────────────────────────┐
│ 资源创建方式选择 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 常规纹理/缓冲区? │
│ │ │
│ ├── 是 ──► CreateCommittedResource │
│ │ (简单、自动管理) │
│ │ │
│ └── 否 ──► 需要精确内存控制? │
│ │ │
│ ├── 是 ──► CreatePlacedResource │
│ │ (资源复用、对齐控制) │
│ │ │
│ └── 否 ──► 超大资源按需加载? │
│ │ │
│ ├── 是 ──► │
│ │ CreateReservedResource│
│ │ (稀疏资源) │
│ │ │
│ └── 否 ──► 重新评估需求 │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 7.11 纹理资源类
#### 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;
};
```
#### 资源创建优先级
```
┌─────────────────────────────────────────────────────────────┐
│ 纹理资源创建优先级 │
├─────────────────────────────────────────────────────────────┤
│ │
│ info.resource != nullptr ? │
│ │ │
│ ├── 是 ──► 直接使用已有资源 │
│ │ (适用于外部管理的资源) │
│ │ │
│ └── 否 ──► info.heap != nullptr ? │
│ │ │
│ ├── 是 ──► CreatePlacedResource │
│ │ (显式堆,精确控制) │
│ │ │
│ └── 否 ──► CreateCommittedResource │
│ (隐式堆,最常用) │
│ │
└─────────────────────────────────────────────────────────────┘
```
#### 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;
}
```
#### 使用示例
```cpp
// 创建默认纹理Committed Resource
D3D12_RESOURCE_DESC desc{
.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
.Width = 1024,
.Height = 1024,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
// ...
};
d3d12_texture_init_info info{
.desc = &desc,
.initial_state = D3D12_RESOURCE_STATE_COPY_DEST,
};
d3d12_texture texture{info};
// 获取资源用于后续操作
ID3D12Resource* resource = texture.resource();
descriptor_handle srv = texture.srv();
```
## 8. 交换链Swap Chain
### 8.1 什么是交换链?
交换链是 DXGI 提供的机制,用于管理前后缓冲区的交换,实现流畅的画面显示。
**核心职责**
- **连接窗口与渲染管线**:将渲染输出与显示器关联
- **管理后台缓冲区**:分配和维护一组用于渲染的缓冲区
- **缓冲区翻转**:通过 `Present()` 实现前后缓冲区交换,将绘制内容显示到窗口
```
┌─────────────────────────────────────────────────────────────┐
│ 交换链工作原理 │
@@ -520,7 +815,65 @@ void allocate() {
└─────────────────────────────────────────────────────────────┘
```
### 8.2 三重缓冲
### 8.2 交换链的职责边界
交换链**只负责**缓冲区的分配与翻转,以下操作需开发者显式完成:
| 操作 | 负责方 | 说明 |
|------|--------|------|
| 缓冲区分配 | 交换链 | 创建指定数量的后台缓冲区 |
| 缓冲区翻转 | 交换链 | `Present()` 切换前后缓冲区 |
| 渲染目标绑定 | 开发者 | `OMSetRenderTargets()` 绑定 RTV |
| GPU 同步 | 开发者 | 使用 Fence 确保 GPU 完成渲染 |
| 状态转换 | 开发者 | 资源屏障管理缓冲区状态 |
| 窗口大小调整 | 开发者 | 调用 `ResizeBuffers()` 重新分配 |
### 8.3 标准渲染流程
```
┌─────────────────────────────────────────────────────────────┐
│ 交换链标准使用流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 获取当前后台缓冲区 │
│ ┌─────────────────────────────────────────┐ │
│ │ ID3D12Resource* buffer = swap_chain-> │ │
│ │ GetBuffer(current_index); │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. 创建并绑定渲染目标视图 │
│ ┌─────────────────────────────────────────┐ │
│ │ device->CreateRenderTargetView( │ │
│ │ buffer, nullptr, rtv_handle); │ │
│ │ cmd_list->OMSetRenderTargets( │ │
│ │ 1, &rtv_handle, nullptr); │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. 执行绘制命令(写入后台缓冲区) │
│ ┌─────────────────────────────────────────┐ │
│ │ cmd_list->ClearRenderTargetView(...); │ │
│ │ cmd_list->DrawInstanced(...); │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 4. 提交命令并同步 │
│ ┌─────────────────────────────────────────┐ │
│ │ cmd_queue->ExecuteCommandLists(...); │ │
│ │ // 等待 GPU 完成Fence 同步) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 5. 呈现(翻转缓冲区) │
│ ┌─────────────────────────────────────────┐ │
│ │ swap_chain->Present(sync_interval, 0); │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 8.5 三重缓冲
项目使用三重缓冲(`frame_buffer_count = 3`
@@ -530,7 +883,7 @@ void allocate() {
| **提高并行性** | CPU 可提前录制多帧命令 |
| **平滑帧率** | 缓冲区平滑帧时间波动 |
### 8.3 d3d12_surface 类
### 8.6 d3d12_surface 类
```cpp
class d3d12_surface
@@ -559,7 +912,7 @@ private:
};
```
### 8.4 交换链创建流程
### 8.7 交换链创建流程
```cpp
void create_swap_chain(...)
@@ -587,7 +940,7 @@ void create_swap_chain(...)
}
```
### 8.5 视口与裁剪矩形
### 8.8 视口与裁剪矩形
```cpp
// 视口:定义光栅化区域
@@ -604,6 +957,75 @@ D3D12_VIEWPORT viewport{
D3D12_RECT scissor_rect{0, 0, width, height};
```
### 8.9 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 结构