1. DirectX 12 到底是什么?
首先,DirectX 和 Direct3D 不是完全相同的概念。
DirectX 是微软的一组多媒体与图形相关技术,而 Direct3D 12(D3D12) 是其中负责 3D 图形编程的 API。我们平时所说的 DX12 图形开发,主要就是指 D3D12。
如果已经接触过 OpenGL,例如:
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
在 OpenGL 中,我们通过这些函数提交渲染操作,驱动和运行时负责处理不少内部细节。
DX12 则要求程序员更明确地管理 GPU 工作的组织方式。
看一下两者的概念对比:
这种设计给了渲染器开发者更精细的控制权,但也意味着你必须自己处理更多资源生命周期和同步问题。
2. DX12 是如何控制 GPU 的?
先认识最重要的三个对象。
CPU / C++ 程序
组织资源、创建对象、记录绘制命令
↓
Command List(命令列表)
记录 GPU 应执行的操作
↓
Command Queue(命令队列)
接收并提交 GPU 工作
↓
GPU 执行
执行绘制或计算任务,产生渲染结果
这是简化的命令提交流程。实际上,DX12 还涉及内存、资源状态和 CPU/GPU 同步。
其中:
- Device:代表用于创建和管理 D3D12 资源及相关对象的逻辑设备接口。
- Command List:可以理解成记录 GPU 操作的命令清单。
- Command Queue:负责将这些命令提交给 GPU 执行。
注意,CPU 提交命令并不等于 GPU 已经执行完成。这正是后面学习 Fence(围栏同步)的原因。
3. 第一次 DX12 编程
本节不急着绘制三角形,先完成一个最小实验:
使用 C++ 成功创建一个 D3D12 Device。
#include <iostream>
#include <d3d12.h>
#include <wrl/client.h>
#pragma comment(lib, "d3d12.lib")
using namespace std;
using Microsoft::WRL::ComPtr;
int main()
{
ComPtr<ID3D12Device> device;
HRESULT hr = D3D12CreateDevice(
nullptr,
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(device.GetAddressOf())
);
if (FAILED(hr))
{
cout << "DX12 Device 创建失败!" << endl;
return 1;
}
cout << "DX12 Device 创建成功!" << endl;
return 0;
}
如果成功,控制台会输出:
DX12 Device 创建成功!
这表示你成功初始化了一个 DX12 设备接口,但尚未创建窗口或绘制图像。
4. 关键代码讲解
第一句:
ComPtr<ID3D12Device> device;
ID3D12Device 是 DX12 的设备接口。
ComPtr 是一种管理 COM 接口指针的智能指针,可以在合适的时候自动释放接口引用,降低资源泄漏的风险。
你可以暂时将它理解为:用一个对象来安全管理指向 DX12 接口的指针。
第二句:
D3D12CreateDevice(
nullptr,
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(device.GetAddressOf())
);
这是创建设备的核心函数,它的三个实参分别表示:
nullptr使用默认显示适配器D3D_FEATURE_LEVEL_11_0要求至少支持该硬件功能级别IID_PPV_ARGS(...)指定接口类型并传入接收设备接口指针的位置这里的 11_0 是 Feature Level,不代表我们正在使用 DirectX 11 API。整个程序依然使用 D3D12。
第三句:
HRESULT hr = D3D12CreateDevice(...);
HRESULT 用来表示调用结果。
if (FAILED(hr))
则检查这次操作是否失败。
这也是以后 DX12 编程中会反复出现的错误处理模式。