D3D12 Core: - Add ID3D12Fence1 and fence_value to d3d12_command - Add fence_event for CPU waiting - Implement wait() in command_frame for frame sync - Implement flush() to wait all frames complete - Add fence_value tracking per frame - Signal fence at end_frame with incremented value TestRenderer: - Call graphics::render() in run() Documentation: - Add changelog for Fence sync implementation - Update D3D12 Wiki with Fence sync section
8.0 KiB
8.0 KiB
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 关键接口
IDXGIFactory6 // DXGI 工厂,创建其他 DXGI 对象
IDXGIAdapter4 // 代表物理 GPU 适配器
IDXGIOutput // 代表显示器输出
IDXGISwapChain // 交换链接口
2.3 项目中的使用
在 D3D12CommonHeader.h 中引入了 dxgi_6.h:
#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 已实现完整的设备初始化:
namespace {
ID3D12Device8* main_device{ nullptr };
IDXGIFactory7* dxgi_factory{ nullptr };
}
bool initialize() {
// 1. 启用调试层 (DEBUG 模式)
// 2. 创建 DXGI 工厂
// 3. 枚举并选择 GPU 适配器
// 4. 获取最高特性级别
// 5. 创建 D3D12 设备
// 6. 配置信息队列 (DEBUG 模式)
}
3.3 调试宏
项目定义了两个重要的调试宏:
// 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 对象生命周期:
#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 接口绑定机制
// 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 类
项目实现了命令队列管理类,支持多帧缓冲渲染:
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 多帧缓冲原理
constexpr u32 frame_buffer_count{ 3 };
采用三重缓冲设计:
- CPU 提前录制命令
- GPU 异步执行
- 最大化硬件利用率
6.3 帧索引轮转
_frame_index = (_frame_index + 1) % frame_buffer_count;
环形缓冲区管理帧资源,确保 CPU 不会超前 GPU 超过 3 帧。
6.4 Fence 同步机制
项目实现了 Fence(围栏)同步,确保 CPU-GPU 帧同步:
struct command_frame
{
ID3D12CommandAllocator* cmd_allocator{ nullptr };
u64 fence_value{ 0 }; // 该帧的围栏值
void wait(HANDLE fence_event, ID3D12Fence1* fence);
};
同步流程:
begin_frame()- 检查 GPU 是否完成当前帧,未完成则等待end_frame()- 递增围栏值,向 GPU 发送信号
// 帧结束信号
++_fence_value;
_cmd_frames[_frame_index].fence_value = _fence_value;
_cmd_queue->Signal(_fence, _fence_value);
围栏值溢出:64位无符号整数,每秒1000帧需要5.8亿年才回绕,无需担心。
7. 渲染表面与窗口
7.1 render_surface 结构
struct render_surface {
platform::window window{}; // 平台窗口
surface surface{}; // 渲染表面
};
7.2 多窗口支持
TestRenderer 测试展示了多窗口渲染:
graphics::render_surface _surfaces[4]; // 支持 4 个窗口
// 创建多个渲染表面
for (u32 i{0}; i < _countof(_surfaces); ++i) {
create_render_surface(_surfaces[i], info[i]);
}
7.3 全屏切换
通过 WM_SYSCHAR 消息处理 Alt+Enter:
if (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN)) {
win.set_fullscreen(!win.is_fullscreen());
}
8. 后续学习路径
8.1 基础阶段
- 完成设备创建和适配器枚举
- 创建命令队列和命令列表
- 实现交换链和后台缓冲区
- 渲染第一个三角形
8.2 进阶阶段
- 描述符堆管理
- 根签名和管线状态对象
- 资源屏障和同步
- 常量缓冲区和着色器资源
8.3 高级阶段
- 多线程渲染
- 资源绑定策略
- 动态资源管理
- 性能优化
9. 参考资源
9.1 官方文档
9.2 推荐书籍
- 《Introduction to 3D Game Programming with DirectX 12》
- 《Real-Time 3D Rendering with DirectX and HLSL》