Anatomy of Direct3D 12 Create Device
direct3d, win10Originally posted to Chuck Walbourn's Blog on MSDN,
Based on some questions I’ve been getting lately, it seems like now’s a good time to revisit my classic post Anatomy of Direct3D 11 Create Device updated for Direct3D 12.
The first thing to note is that while you can pass a nullptr
for the ‘default’ device with Direct3D 12 to D3D12CreateDevice, that’s probably not the best solution. At this point, every driver on Windows 7 or later supports Direct3D 11, so you can pretty safely assume the default device is going to support Direct3D 11 at some Direct3D hardware feature level. While a lot of existing (as well as new) GPUs support Direct3D 12, this doesn’t apply to all GPUs. Specifically, a new WDDM 2 driver is required to support Direct3D 12, and there are no devices below Direct3D Feature Level 11.0 that are expected to get such updated drivers.
Another difference is that the debug device is not enabled through a creation flag like it is in Direct3D 11. Therefore, our first step is to enable debugging if available (the debug device is only present if the Graphics Tools Windows 10 optional feature is enabled). I’m making use of Microsoft::WRL::ComPtr
which is recommended for both Direct3D 11 and Direct3D 12 (see this page for more information on this useful smart-pointer for COM programming).
#if defined(_DEBUG)
// Enable the debug layer.
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
}
}
#endif
Next, we create a DXGI factory–if you are using DirectX 12 you can also assume DXGI 1.4:
ComPtr<IDXGIFactory4> dxgiFactory;
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory));
if (FAILED(hr))
// Error!
For DXGI Debugging, we’d actually use
CreateDXGIFactory2
withDXGI_CREATE_FACTORY_DEBUG
, but this only works if the Windows Graphics Tools optional feature is installed. See DXGI Debug Device for more information.
Then we scan DXGI adapters looking for one that supports Direct3D 12:
ComPtr<IDXGIAdapter1> adapter;
for (UINT adapterIndex = 0;
DXGI_ERROR_NOT_FOUND !=
dxgiFactory->EnumAdapters1(adapterIndex, &adapter);
++adapterIndex)
{
DXGI_ADAPTER_DESC1 desc;
hr = adapter->GetDesc1(&desc);
if (FAILED(hr))
continue;
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{
// Don't select the Basic Render Driver adapter.
continue;
}
// Check to see if the adapter supports Direct3D 12,
// but don't create the actual device yet.
if (SUCCEEDED(
D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0,
_uuidof(ID3D12Device), nullptr)))
{
break;
}
}
Note: We are excluding the Microsoft Basic Render adapter here (aka WARP+VGA driver) since games typically don’t play well using WARP. Keep in mind that WARP12 is not present on standard Windows 10 systems; it's only installed as part of the Graphics Tools optional feature. WARP for DirectX 12 is supported on Windows 10 (Version 1709, Build 16299) or later.
You could also query a
IDXGIFactory6
interface and useEnumAdapterByGpuPreference
to prefer using discrete (DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE
) vs. integrated (DXGI_GPU_PREFERENCE_MINIMUM_POWER
) graphics on hybrid systems. This interface is supported on Windows 10 April 2018 Update (17134) or later.
If there’s no Direct3D 12-capable hardware, then for development builds it is useful to fallback to the WARP software device for Direct3D 12. Here is another difference compared to Direct3D 11: WARP12 is a specific adapter you obtain from the DXGI factory (IDXGIFactory4
or later):
#if !defined(NDEBUG)
if (!adapter)
{
if (FAILED(dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&adapter))))
{
adapter.Reset();
}
}
#endif
If at this point, we still don’t have a valid adapter, then either a fatal error should be displayed, -or- if the application supports it, you should fall back to using Direct3D 11 (in which case, you should use a IDXGIFactory1
initially and then use QueryInterface
to obtain IDXGIFactory4
as needed).
Otherwise, it’s time to create the device. Here’s another Direct3D 11 difference: Instead of providing an input array of every possible Direct3D feature level your application supports, you simply provide the minimum feature level you can use. Because as I noted above there’s no expected drivers for anything below Feature Level 11.0, that’s the minimum I’m using in this code and you’ll find the same in the Visual Studio DirectX 12 templates.
ComPtr<ID3D12Device> device;
hr = D3D12CreateDevice(adapter.Get(),
D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device));
if (FAILED(hr))
// Error!
Great, so we have a device, but how do you know if you managed to get a higher feature level than your minimum? Here we use CheckFeatureSupport to find that out:
static const D3D_FEATURE_LEVEL s_featureLevels[] =
{
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
};
D3D12_FEATURE_DATA_FEATURE_LEVELS featLevels =
{
_countof(s_featureLevels), s_featureLevels, D3D_FEATURE_LEVEL_11_0
};
D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
hr = device->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS,
&featLevels, sizeof(featLevels));
if (SUCCEEDED(hr))
{
featureLevel = featLevels.MaxSupportedFeatureLevel;
}
With the DirectX Agility SDK or the Windows SDK for Windows 11, you can also add
D3D_FEATURE_LEVEL_12_2
to the array above.
Swap Chain
At this point, you are ready to create the swap chain. For Win32 classic desktop apps you use CreateSwapChainForHwnd, and for Universal Windows Platform (UWP) apps you use CreateSwapChainForCoreWindow or CreateSwapChainForComposition, all of which require IDXGIFactory2
or later.
The key thing to note about swap chain creation is that you must use either DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
or DXGI_SWAP_EFFECT_FLIP_DISCARD
for the DXGI_SWAP_CHAIN_DESC1.SwapEffect
because the older swap effects are not supported for Direct3D 12.
For Universal Windows Platform (UWP) apps, you should also consider using DXGI_SCALING_ASPECT_RATIO_STRETCH
for the DXGI_SWAP_CHAIN_DESC1.Scaling
, but for Win32 classic desktop swap chains you need to stick with DXGI_SCALING_NONE
or DXGI_SCALING_STRETCH
.
One more consideration: For gamma-correct rendering to standard 8-bit per channel UNORM formats, you’ll want to create the Render Target using an sRGB format. The new flip modes, however, do not allow you to create a swap chain back buffer using an sRGB format. In this case, you create one using the non-sRGB format (i.e. DXGI_SWAP_CHAIN_DESC1.Format = DXGI_FORMAT_B8G8R8A8_UNORM
) and use sRGB for the Render Target View (i.e. D3D12_RENDER_TARGET_VIEW_DESC.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB
).
Note: With Direct3D 12, you cannot use the device.As(&dxgiDevice)
sequence to obtain the DXGI factory from the Direct3D 12 device for cases where you use nullptr
to create the D3D12CreateDevice
instance. You must always explicitly create the DXGI factory using CreateDXGIFactory1
or CreateDXGIFactory2
.
Update: If you want to make use of variable refresh displays or HDR10 output see DeviceResources.
Windows SDK
To build an application using Direct3D 12 APIs, you must make use of the Windows 10 SDK. The latest version is the Windows 10 Anniversary Update SDK (14393). With Visual Studio 2015, you install it via a custom install option and for Win32 classic desktop projects you’ll need to explicitly change the project property to use it rather than the default Windows 8.1 SDK (Spring 2015).
Update: With Visual Studio 2017, you can use the Windows 10 Creators Update SDK (15063) or Windows 10 Fall Creators Update SDK (16299). VS 2015 is not supported by these newer SDKs.
GitHub: Note that you can now obtain the latest Direct3D 12 headers directly from DirectX-Headers.
Direct3D 12.x
With the Windows 10 Anniversary Update (14393), newer drivers and devices can support some additional features for Direct3D 12. You can obtain the 12.1 interface from your 12.0 device by using QueryInterface
or ComPtr::As
–this will fail on older versions of Windows 10.
ComPtr<ID3D12Device1> device1;
if (SUCCEEDED(device.As(&device1)))
{
// Direct3D 12.1 Runtime is available
}
Interface | Windows OS version |
---|---|
ID3D12Device1 |
Windows 10 Anniversary Update (14393) |
ID3D12Device2 |
Windows 10 Creators Update (15063) |
ID3D12Device3 |
Windows 10 Fall Creators Update (16299) |
ID3D12Device4 |
Windows 10 April 2018 Update (17134) |
ID3D12Device5 |
Windows 10 October 2018 (17763) |
ID3D12Device6 |
Windows 10 May 2019 (18362) |
ID3D12Device7 , ID3D12Device8 |
Windows 10 May 2020 Update (19041) |
ID3D12Device9 |
Agility SDK 1.4 and Windows 11 (22000) |
ID3D12Device10 |
Agility SDK 1.6 and Windows 11 (22621) |
ID3D12Device11 , ID3D12Device12 |
in preview |
The D3D12CreateDevice
method can directly create newer versions of the interface if you know your target environment supports it. For example, if you are using DirectX 12 on Xbox or if your installer has already ensured a particular version of Windows 10 as a minimum.
Win32 desktop application notes
If your application supports Windows 8.1 or earlier, then you need to make use of explicit rather than implicit linking to the Direct3D 12 functions since they are not available before Windows 10. Implicit linking to dxgi.lib
(and d3d11.lib
if needed) is not a problem unless you are trying to support Windows XP as well. You’ll need to make sure the code above uses DXGI 1.1 for the initial detection to support Windows 7 systems.
HMODULE dx12 = LoadLibraryEx(L"d3d12.dll",
nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!dx12)
// Fallback to using Direct3D 11
auto pD3D12CreateDevice = reinterpret_cast<PFN_D3D12_CREATE_DEVICE>(
GetProcAddress(dx12, "D3D12CreateDevice"));
if (!pD3D12CreateDevice)
// Fallback to using Direct3D 11
...
#if defined(_DEBUG)
// Enable the debug layer.
auto pD3D12GetDebugInterface =
reinterpret_cast<PFN_D3D12_GET_DEBUG_INTERFACE>(
GetProcAddress(dx12, "D3D12GetDebugInterface"));
if (pD3D12GetDebugInterface)
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(pD3D12GetDebugInterface(
IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
}
}
#endif
...
// Change both cases where we use D3D12CreateDevice above to
// pD3D12CreateDevice. If you fail to find an adapter, use
// Direct3D 11 instead
Windows 7: For details on using Direct3D 12 on Windows 7, see GitHub.
Universal Windows Platform (UWP) notes
Because the minimum OS version is enforced for the platform, you can count on IDXGIFactory4
always being available.
Link libraries: The standard list of libraries to link to for a DirectX 12 application is d3d12.lib dxgi.lib dxguid.lib
. To use Windows Imaging Component (WIC) add uuid.lib
.
Related: Direct3D Game Visual Studio templates (Redux), DirectX Tool Kit for DirectX 12, Getting Started with Direct3D 12, HDR Lighting and Displays, The Care and Feeding of Modern Swap Chains