D3D12 初始化教程:构建图形引擎的基础
本教程将引导你完成 Direct3D 12 (D3D12) 图形引擎的核心初始化过程。正确的初始化是所有后续图形渲染工作的基础。我们将深入探讨设备创建、适配器选择、调试层设置等关键步骤,并提供清晰的代码注释和解释,帮助你理解每个环节的作用。
1. 准备工作
首先,确保你的开发环境已配置好 D3D12 SDK。在代码中,我们包含了必要的头文件,并链接了 D3D12 和 DXGI 库:
#include "CommonHeader.h"
和#include "Graphics/Renderer.h"
: 项目自定义的头文件,用于通用定义和渲染器接口。#include <dxgi1_6.h>
: DirectX Graphics Infrastructure (DXGI) 头文件,用于枚举适配器、输出和创建交换链。虽然本节代码没有直接使用交换链,但 DXGI 工厂是创建设备和交换链的基础。#include <d3d12.h>
: D3D12 图形 API 的核心头文件,包含了设备创建、命令列表、资源等核心接口的定义。#include <wrl.h>
: Windows Runtime Library 头文件,提供了ComPtr
智能指针,用于 COM 对象的自动内存管理,避免手动Release
。
在代码的头部,我们还通过 #pragma comment(lib, "dxgi.lib")
和 #pragma comment(lib, "d3d12.lib")
指示链接器链接 DXGI 和 D3D12 库。
2. 调试层和错误处理
在调试版本 (_DEBUG
宏定义) 下,启用 D3D12 调试层至关重要。调试层可以提供丰富的运行时警告和错误信息,帮助开发者快速定位和解决问题。
C++
#ifdef _DEBUG
{
ComPtr<ID3D12Debug3> debug_interface;
DXCall(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_interface)));
debug_interface->EnableDebugLayer();
dxgi_factory_flags |= DXGI_CREATE_FACTORY_DEBUG;
}
#endif // _DEBUG
D3D12GetDebugInterface
: 获取ID3D12Debug3
调试接口。debug_interface->EnableDebugLayer()
: 启用调试层。dxgi_factory_flags |= DXGI_CREATE_FACTORY_DEBUG
: 在创建 DXGI 工厂时,如果启用了调试层,则设置DXGI_CREATE_FACTORY_DEBUG
标志,以便 DXGI 工厂也支持调试功能。
代码中还定义了 DXCall
宏,用于简化 DirectX 函数调用和错误检查。在调试版本下,如果 DirectX 函数返回失败,DXCall
会输出错误信息并触发断点,方便调试。
3. 创建 DXGI 工厂
DXGI 工厂是与显示适配器、输出和交换链交互的入口点。我们使用 CreateDXGIFactory2
函数创建 DXGI 工厂:
C++
DXCall(hr = CreateDXGIFactory2(dxgi_factory_flags, IID_PPV_ARGS(&dxgi_factory)));
if (FAILED(hr)) return failed_init();
CreateDXGIFactory2
: 创建 DXGI 工厂。第一个参数是工厂创建标志,我们传入之前设置的dxgi_factory_flags
。第二个参数IID_PPV_ARGS(&dxgi_factory)
是一个宏,用于获取IDXGIFactory7
接口的 IID 和接收接口指针的地址。if (FAILED(hr)) return failed_init()
: 检查函数是否成功执行。FAILED
宏用于判断 HRESULT 值是否表示失败。如果失败,则调用failed_init
函数进行清理并返回错误。
4. 选择适配器 (GPU)
在多 GPU 系统中,选择合适的适配器非常重要。determin_main_adapter
函数负责根据性能偏好选择最佳的适配器:
C++
IDXGIAdapter4*
determin_main_adapter()
{
IDXGIAdapter4* adapter{ nullptr };
for (u32 i{ 0 };
dxgi_factory->EnumAdapterByGpuPreference(i, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)) != DXGI_ERROR_NOT_FOUND;
++i)
{
if (SUCCEEDED(D3D12CreateDevice(adapter, minimum_feature_level, __uuidof(ID3D12Device), nullptr)))
{
return adapter;
}
release(adapter);
}
return nullptr;
}
dxgi_factory->EnumAdapterByGpuPreference
: 枚举适配器。DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE
标志表示优先枚举高性能 GPU。D3D12CreateDevice(adapter, minimum_feature_level, __uuidof(ID3D12Device), nullptr)
: 尝试在当前适配器上创建 D3D12 设备,但传入nullptr
作为设备指针,只用于检查适配器是否支持minimum_feature_level
。release(adapter)
: 如果当前适配器不满足最低 Feature Level 要求,则释放适配器并尝试下一个。
5. 获取最大 Feature Level
Feature Level 代表了 D3D API 的特性等级。get_amx_feature_level
函数查询适配器支持的最大 Feature Level:
C++
D3D_FEATURE_LEVEL
get_amx_feature_level(IDXGIAdapter4* adapter)
{
constexpr D3D_FEATURE_LEVEL feature_levels[4]{
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_12_1,
};
D3D12_FEATURE_DATA_FEATURE_LEVELS feature_level_info{};
feature_level_info.NumFeatureLevels = _countof(feature_levels);
feature_level_info.pFeatureLevelsRequested = feature_levels;
ComPtr<ID3D12Device> device;
DXCall(D3D12CreateDevice(adapter, minimum_feature_level, IID_PPV_ARGS(&device)));
DXCall(device->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, &feature_level_info, sizeof(feature_level_info)));
return feature_level_info.MaxSupportedFeatureLevel;
}
D3D12_FEATURE_DATA_FEATURE_LEVELS
: 用于存储 Feature Level 支持信息的结构体。device->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, &feature_level_info, sizeof(feature_level_info))
: 调用CheckFeatureSupport
函数,检查适配器对feature_levels
数组中 Feature Level 的支持情况,并将结果存储在feature_level_info
中。feature_level_info.MaxSupportedFeatureLevel
: 返回适配器支持的最大 Feature Level。
6. 创建 D3D12 设备
有了适配器和 Feature Level 信息,我们就可以创建 D3D12 设备了:
C++
DXCall(hr = D3D12CreateDevice(main_adapter.Get(), max_feature_level, IID_PPV_ARGS(&main_device)));
if (FAILED(hr)) return failed_init();
D3D12CreateDevice(main_adapter.Get(), max_feature_level, IID_PPV_ARGS(&main_device))
: 使用选定的适配器和最大 Feature Level 创建 D3D12 设备。IID_PPV_ARGS(&main_device)
用于获取ID3D12Device8
接口的 IID 和接收接口指针的地址。
7. 设置设备名称 (Debug)
为了方便调试,我们为 D3D12 设备设置一个名称:
C++
NAME_D3D12_OBJECT(main_device, L"Main D3D12 Device");
NAME_D3D12_OBJECT
宏在调试版本下会调用 SetName
函数,并在 OutputDebugString 中输出对象创建信息。
8. 配置信息队列 (Debug)
信息队列用于在调试时接收 D3D12 运行时产生的消息,我们可以设置在特定严重级别的消息上断点,以便及时发现错误:
C++
#ifdef _DEBUG
{
ComPtr<ID3D12InfoQueue> info_queue;
DXCall(main_device->QueryInterface(IID_PPV_ARGS(&info_queue)));
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true);
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true);
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true);
}
#endif // _DEBUG
main_device->QueryInterface(IID_PPV_ARGS(&info_queue))
: 查询设备的ID3D12InfoQueue
接口。info_queue->SetBreakOnSeverity(...)
: 设置在D3D12_MESSAGE_SEVERITY_CORRUPTION
、D3D12_MESSAGE_SEVERITY_WARNING
和D3D12_MESSAGE_SEVERITY_ERROR
严重级别的消息上断点。
9. 关闭 D3D12 核心
shutdown
函数负责释放所有 D3D12 和 DXGI 资源:
C++
void
shutdown()
{
release(dxgi_factory);
#ifdef _DEBUG
{
ComPtr<ID3D12InfoQueue> info_queue;
DXCall(main_device->QueryInterface(IID_PPV_ARGS(&info_queue)));
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, false);
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, false);
info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, false);
ComPtr<ID3D12DebugDevice2> debug_device;
DXCall(main_device->QueryInterface(IID_PPV_ARGS(&debug_device)));
release(main_device);
DXCall(debug_device->ReportLiveDeviceObjects(
D3D12_RLDO_SUMMARY | D3D12_RLDO_DETAIL | D3D12_RLDO_IGNORE_INTERNAL
));
}
#endif // _DEBUG
release(main_device);
}
release(dxgi_factory)
和release(main_device)
: 使用release
模板函数释放 DXGI 工厂和 D3D12 设备。release
函数内部会检查指针是否为空,并调用Release
函数并置空指针,避免悬空指针。在调试版本下,
shutdown
函数还会移除信息队列的断点设置,并调用ReportLiveDeviceObjects
报告未释放的 D3D12 对象,帮助检测资源泄漏。
总结
这段代码提供了一个健壮的 D3D12 初始化框架,涵盖了设备创建、适配器选择、Feature Level 查询、调试层和信息队列设置等关键步骤。通过合理的错误处理和调试支持,为后续的 D3D12 渲染开发奠定了坚实的基础。