本教程将引导你完成 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_CORRUPTIOND3D12_MESSAGE_SEVERITY_WARNINGD3D12_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 渲染开发奠定了坚实的基础。