Files
DX12/docs/wiki/D3D12学习Wiki.md
SpecialX 4d13d8df89 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、资源创建方式、纹理资源类章节
- 新增变更记录文档
2026-04-01 16:17:42 +08:00

1102 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Direct3D 12 学习 Wiki
## 概述
本文档是项目中 Direct3D 12 学习的基础知识汇总,帮助理解 D3D12 的核心概念和项目中的实现方式。
## 1. D3D12 核心概念
### 1.1 什么是 Direct3D 12
Direct3D 12 是微软推出的底层图形 API相比 D3D11它提供了
- **更底层的硬件控制**:开发者可以更精细地控制 GPU
- **更低的 CPU 开销**:减少驱动程序的 CPU 时间
- **更好的多线程支持**:支持多线程命令录制
- **显式的资源管理**:开发者完全控制资源生命周期
### 1.2 核心组件
| 组件 | 说明 |
|------|------|
| **Device设备** | 代表物理 GPU 的逻辑抽象,用于创建所有 D3D12 对象 |
| **Command Queue命令队列** | GPU 执行命令的队列 |
| **Command List命令列表** | 记录 GPU 命令的容器 |
| **Swap Chain交换链** | 管理后台缓冲区和前台显示 |
| **Descriptor Heap描述符堆** | 存储资源描述符(视图)的内存池 |
| **Root Signature根签名** | 定义着色器如何访问资源 |
## 2. DXGIDirectX Graphics Infrastructure
### 2.1 DXGI 的作用
DXGI 是 DirectX 与图形硬件之间的抽象层,负责:
- 枚举显示适配器
- 管理显示输出
- 创建交换链
- 处理全屏/窗口切换
### 2.2 关键接口
```cpp
IDXGIFactory6 // DXGI 工厂,创建其他 DXGI 对象
IDXGIAdapter4 // 代表物理 GPU 适配器
IDXGIOutput // 代表显示器输出
IDXGISwapChain // 交换链接口
```
### 2.3 项目中的使用
在 [D3D12CommonHeader.h](file:///d:/AllWX/AllC/FeatureExtractDemo/Engine/Graphics/Direct3D12/D3D12CommonHeader.h) 中引入了 `dxgi_6.h`
```cpp
#include <dxgi_6.h> // DXGI 6.0,支持枚举 GPU、查询显示模式
```
## 3. D3D12 初始化流程
### 3.1 标准初始化步骤
```
1. 创建 DXGI Factory
2. 枚举并选择适配器GPU
3. 创建 D3D12 Device
4. 创建命令队列
5. 创建交换链
6. 创建描述符堆
7. 创建命令分配器和命令列表
8. 创建同步对象Fence
```
### 3.2 项目当前状态
[D3D12Core.cpp](file:///d:/AllWX/AllC/FeatureExtractDemo/Engine/Graphics/Direct3D12/D3D12Core.cpp) 已实现完整的设备初始化:
```cpp
namespace {
ID3D12Device8* main_device{ nullptr };
IDXGIFactory7* dxgi_factory{ nullptr };
}
bool initialize() {
// 1. 启用调试层 (DEBUG 模式)
// 2. 创建 DXGI 工厂
// 3. 枚举并选择 GPU 适配器
// 4. 获取最高特性级别
// 5. 创建 D3D12 设备
// 6. 配置信息队列 (DEBUG 模式)
}
```
### 3.3 调试宏
项目定义了两个重要的调试宏:
```cpp
// DXCall - 检查 HRESULT 并断点
#define DXCall(x) if(FAILED(x)) { ... __debugbreak(); }
// NAME_D3D12_OBJECT - 为对象设置调试名称
#define NAME_D3D12_OBJECT(obj, name) obj->SetName(name);
```
## 4. COM 对象管理
### 4.1 什么是 COM
COMComponent Object Model是微软的组件对象模型D3D12 对象都是 COM 对象。
### 4.2 智能指针
项目使用 WRL 库的 `ComPtr` 管理 COM 对象生命周期:
```cpp
#include <wrl.h> // 提供 Microsoft::WRL::ComPtr
// 使用示例
Microsoft::WRL::ComPtr<ID3D12Device8> device;
```
### 4.3 ComPtr 的优势
- **自动引用计数**:无需手动 AddRef/Release
- **异常安全**:即使发生异常也能正确释放
- **代码简洁**:减少内存管理代码
## 5. 平台抽象设计
### 5.1 设计理念
项目采用**平台抽象层**设计,将图形 API 的差异封装在统一接口后:
```
┌────────────────────────────────────┐
│ 上层渲染代码 │
│ graphics::initialize(platform) │
└──────────────┬─────────────────────┘
┌────────────────────────────────────┐
│ platform_interface │
│ - initialize() │
│ - shutdown() │
└──────────────┬─────────────────────┘
┌──────────┼──────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│ D3D12 │ │Vulkan │ │OpenGL │
└───────┘ └───────┘ └───────┘
```
### 5.2 接口绑定机制
```cpp
// D3D12Interface.cpp
void get_platform_interface(platform_interface& pi) {
pi.initialize = core::initialize;
pi.shutdown = core::shutdown;
pi.render = core::render;
}
```
这种设计允许:
- 编译时或运行时切换图形后端
- 各后端独立开发和测试
- 上层代码与具体 API 解耦
## 6. 命令队列与多帧缓冲
### 6.1 d3d12_command 类
项目实现了命令队列管理类,支持多帧缓冲渲染:
```cpp
class d3d12_command
{
void begin_frame(); // 等待帧完成,重置分配器和命令列表
void end_frame(); // 关闭命令列表,提交执行
private:
ID3D12CommandQueue* _cmd_queue;
ID3D12GraphicsCommandList6* _cmd_list;
command_frame _cmd_frames[frame_buffer_count];
u32 _frame_index;
};
```
### 6.2 多帧缓冲原理
```cpp
constexpr u32 frame_buffer_count{ 3 };
```
采用三重缓冲设计:
- CPU 提前录制命令
- GPU 异步执行
- 最大化硬件利用率
### 6.3 帧索引轮转
```cpp
_frame_index = (_frame_index + 1) % frame_buffer_count;
```
环形缓冲区管理帧资源,确保 CPU 不会超前 GPU 超过 3 帧。
### 6.4 Fence 同步机制
项目实现了 Fence围栏同步确保 CPU-GPU 帧同步:
```cpp
struct command_frame
{
ID3D12CommandAllocator* cmd_allocator{ nullptr };
u64 fence_value{ 0 }; // 该帧的围栏值
void wait(HANDLE fence_event, ID3D12Fence1* fence);
};
```
**同步流程**
1. `begin_frame()` - 检查 GPU 是否完成当前帧,未完成则等待
2. `end_frame()` - 递增围栏值,向 GPU 发送信号
```cpp
// 帧结束信号
++_fence_value;
_cmd_frames[_frame_index].fence_value = _fence_value;
_cmd_queue->Signal(_fence, _fence_value);
```
**围栏值溢出**64位无符号整数每秒1000帧需要5.8亿年才回绕,无需担心。
## 7. 描述符堆
### 7.1 什么是描述符堆?
描述符堆是一块连续内存用于存储描述符Descriptor。描述符是告诉 GPU 如何访问资源的数据结构。
### 7.2 描述符堆类型
| 类型 | 用途 | 着色器可见 |
|------|------|------------|
| `CBV_SRV_UAV` | 常量缓冲区、着色器资源、无序访问 | 可选 |
| `SAMPLER` | 采样器 | 可选 |
| `RTV` | 渲染目标视图 | 否 |
| `DSV` | 深度模板视图 | 否 |
### 7.3 descriptor_handle 结构
项目封装了描述符句柄:
```cpp
struct descriptor_handle
{
D3D12_CPU_DESCRIPTOR_HANDLE cpu{}; // CPU 句柄
D3D12_GPU_DESCRIPTOR_HANDLE gpu{}; // GPU 句柄
constexpr bool is_valid() const { return cpu.ptr != 0; }
constexpr bool is_shader_visible() const { return gpu.ptr != 0; }
};
```
### 7.4 descriptor_heap 类
```cpp
class descriptor_heap
{
bool initialize(u32 capacity, bool is_shader_visible);
descriptor_handle allocate(); // 分配描述符
void free(descriptor_handle); // 释放描述符
private:
ID3D12DescriptorHeap* _heap;
std::unique_ptr<u32[]> _free_handles{}; // 空闲索引池
std::mutex _mutex; // 线程安全
};
```
### 7.5 内存模型
```
描述符堆内存布局:
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │...│ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
_cpu_start / _gpu_start
_free_handles[] = [0, 1, 2, 3, ...] // 空闲索引池
```
### 7.6 FreeList 栈式索引管理
#### 核心数据结构
```cpp
// 空闲索引栈(预分配数组)
std::unique_ptr<u32[]> _free_handles{};
// 栈顶指针(同时也是已分配数量)
u32 _size{0};
// 总容量
u32 _capacity{0};
```
#### 工作原理
`_free_handles` 是一个**预分配数组**,同时充当**栈**的角色:
| 概念 | 说明 |
|------|------|
| `_free_handles` | 存储所有可用索引的数组 |
| `_size` | 已分配数量 + 栈顶指针 |
| 分配 | `_free_handles[_size++]`,从栈顶弹出 |
| 释放 | `_free_handles[--_size] = index`,压入栈顶 |
#### 状态演变示例
```
初始化状态capacity = 5
┌─────────────────────────────────────┐
│ _free_handles = [0, 1, 2, 3, 4] │
│ _size = 0 (栈顶指向位置 0) │
│ 可用索引: 0, 1, 2, 3, 4 │
└─────────────────────────────────────┘
分配 2 个描述符后:
┌─────────────────────────────────────┐
│ _free_handles = [0, 1, 2, 3, 4] │
│ _size = 2 (栈顶指向位置 2) │
│ 已分配索引: 0, 1 │
│ 可用索引: 2, 3, 4 │
└─────────────────────────────────────┘
释放索引 0 后(延迟处理完成):
┌─────────────────────────────────────┐
│ _free_handles = [0, 0, 2, 3, 4] │
│ _size = 1 (栈顶指向位置 1) │
│ 已分配索引: 1 │
│ 可用索引: 0, 2, 3, 4 │
└─────────────────────────────────────┘
```
#### 分配算法
```cpp
descriptor_handle allocate()
{
std::lock_guard lock(_mutex);
// 从栈顶取出索引
const u32 index = _free_handles[_size];
++_size; // 栈顶指针上移
// 计算句柄地址
const u32 offset = index * _descriptor_size;
handle.cpu.ptr = _cpu_start.ptr + offset;
return handle;
}
```
**时间复杂度**: O(1)
#### 释放算法(延迟释放)
```cpp
// 释放时不立即回收,放入延迟队列
void free(descriptor_handle handle)
{
const u32 frame_index = current_frame_index();
_deferred_free_indices[frame_index].push_back(handle.index);
}
// GPU 完成帧后处理延迟释放
void process_deferred_release(u32 frame_index)
{
for(auto index : _deferred_free_indices[frame_index])
{
--_size; // 栈顶指针下移
_free_handles[_size] = index; // 索引压入栈顶
}
}
```
#### 设计优势
| 特性 | 说明 |
|------|------|
| **O(1) 分配** | 栈顶弹出,无需遍历查找 |
| **O(1) 释放** | 栈顶压入,无需查找位置 |
| **内存高效** | 预分配数组,无动态分配开销 |
| **简单可靠** | 栈结构天然保证索引不重复 |
### 7.7 延迟释放机制
#### 为什么需要延迟释放?
```
问题场景(无延迟释放):
帧 0: CPU 分配描述符索引 0 → 绑定纹理 A
帧 1: CPU 释放描述符索引 0 → 立即重用 → 绑定纹理 B
帧 0: GPU 还在执行,访问描述符索引 0 → 读到纹理 B 的数据!
GPU 危险!数据竞争!
```
#### 解决方案
```
帧 0: CPU 分配描述符索引 0 → 绑定纹理 A
帧 1: CPU 释放描述符索引 0 → 放入延迟队列(帧 1
帧 2: CPU 继续工作...
帧 0: GPU 完成帧 0 的执行Fence 同步确认)
帧 3: CPU 处理帧 0 的延迟释放 → 索引 0 回到空闲池
安全GPU 已完成使用
```
#### 数据结构
```cpp
// 每帧一个延迟释放队列
utl::vector<u32> _deferred_free_indices[frame_buffer_count]{};
// 全局延迟释放资源队列(用于 COM 对象)
utl::vector<IUnknown*> deferred_releases[frame_buffer_count]{};
// 延迟释放标志
u32 deferred_release_flag[frame_buffer_count]{};
```
#### 完整流程图
```
┌─────────────────────────────────────────────────────────────────┐
│ 初始化 │
│ _free_handles = [0, 1, 2, 3, 4], _size = 0 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ allocate() │
│ index = _free_handles[_size] // 取索引 0 │
│ _size++ // _size = 1 │
│ 返回描述符句柄(索引 0
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ free(索引 0) │
│ 放入延迟队列: _deferred_free_indices[当前帧].push_back(0) │
│ _size 不变_free_handles 不变 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ GPU 完成帧Fence 同步) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ process_deferred_release(frame_index) │
│ --_size // _size = 0 │
│ _free_handles[_size] = 0 // 索引 0 压入栈顶 │
│ _free_handles = [0, 1, 2, 3, 4] // 索引 0 可重用 │
└─────────────────────────────────────────────────────────────────┘
```
### 7.8 线程安全
描述符堆可能被多线程并发访问,使用互斥锁保护:
```cpp
std::mutex _mutex{};
void allocate() {
std::lock_guard lock(_mutex); // 自动加锁
// ... 操作
} // 自动解锁
```
### 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()` 实现前后缓冲区交换,将绘制内容显示到窗口
```
┌─────────────────────────────────────────────────────────────┐
│ 交换链工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 后台缓冲 │ │ 后台缓冲 │ │ 后台缓冲 │ │
│ │ 0 │ │ 1 │ │ 2 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Present() │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 前台缓冲 │ ───► 显示器 │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 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`
| 特性 | 说明 |
|------|------|
| **减少撕裂** | 前台缓冲独立于后台缓冲,避免部分更新 |
| **提高并行性** | CPU 可提前录制多帧命令 |
| **平滑帧率** | 缓冲区平滑帧时间波动 |
### 8.6 d3d12_surface 类
```cpp
class d3d12_surface
{
public:
// 创建交换链
void create_swap_chain(IDXGIFactory7* factory,
ID3D12CommandQueue* cmd_queue,
DXGI_FORMAT format);
// 呈现当前帧
void present() const;
// 调整大小
void resize();
// 获取当前后台缓冲区
ID3D12Resource* back_buffer() const;
descriptor_handle rtv() const;
private:
IDXGISwapChain4* _swap_chain;
render_target_data _render_target_data[frame_buffer_count];
D3D12_VIEWPORT _viewport;
D3D12_RECT _scissor_rect;
};
```
### 8.7 交换链创建流程
```cpp
void create_swap_chain(...)
{
// 1. 配置描述
DXGI_SWAP_CHAIN_DESC1 desc{};
desc.BufferCount = frame_buffer_count;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.Width = window.width();
desc.Height = window.height();
desc.SampleDesc.Count = 1; // 禁用 MSAA
// 2. 创建交换链
factory->CreateSwapChainForHwnd(cmd_queue, hwnd, &desc, ...);
// 3. 禁用 Alt+Enter由应用处理
factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER);
// 4. 创建渲染目标视图
for(u32 i = 0; i < frame_buffer_count; ++i)
{
_swap_chain->GetBuffer(i, &resource);
device->CreateRenderTargetView(resource, &desc, rtv.cpu);
}
}
```
### 8.8 视口与裁剪矩形
```cpp
// 视口:定义光栅化区域
D3D12_VIEWPORT viewport{
.TopLeftX = 0.0f,
.TopLeftY = 0.0f,
.Width = (float)width,
.Height = (float)height,
.MinDepth = 0.0f,
.MaxDepth = 1.0f
};
// 裁剪矩形:定义像素输出区域
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 结构
```cpp
struct render_surface {
platform::window window{}; // 平台窗口
surface surface{}; // 渲染表面
};
```
### 9.2 多窗口支持
TestRenderer 测试展示了多窗口渲染:
```cpp
graphics::render_surface _surfaces[4]; // 支持 4 个窗口
// 创建多个渲染表面
for (u32 i{0}; i < _countof(_surfaces); ++i) {
create_render_surface(_surfaces[i], info[i]);
}
```
### 9.3 全屏切换
通过 `WM_SYSCHAR` 消息处理 Alt+Enter
```cpp
if (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN)) {
win.set_fullscreen(!win.is_fullscreen());
}
```
## 10. 后续学习路径
### 10.1 基础阶段
- [x] 完成设备创建和适配器枚举
- [x] 创建命令队列和命令列表
- [x] 描述符堆管理
- [x] 实现交换链和后台缓冲区
- [ ] 渲染第一个三角形
### 10.2 进阶阶段
- [ ] 根签名和管线状态对象
- [ ] 资源屏障和同步
- [ ] 常量缓冲区和着色器资源
### 10.3 高级阶段
- [ ] 多线程渲染
- [ ] 资源绑定策略
- [ ] 动态资源管理
- [ ] 性能优化
## 11. 参考资源
### 11.1 官方文档
- [Microsoft D3D12 文档](https://docs.microsoft.com/en-us/windows/win32/direct3d12/direct3d-12-graphics)
- [DXGI 文档](https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/dx-graphics-dxgi)
### 11.2 推荐书籍
- 《Introduction to 3D Game Programming with DirectX 12》
- 《Real-Time 3D Rendering with DirectX and HLSL》
### 11.3 项目相关文档
- [Graphics渲染架构分析](./Graphics渲染架构分析.md)
- [项目约定规范](./项目约定规范.md)