核心变更: - 新增 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、资源创建方式、纹理资源类章节 - 新增变更记录文档
1102 lines
38 KiB
Markdown
1102 lines
38 KiB
Markdown
# 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. DXGI(DirectX 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?
|
||
|
||
COM(Component 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)
|