Games for Windows and the DirectX SDK blog

Technical tips, tricks, and news about game development for Microsoft platforms including desktop, Xbox, and UWP


Project maintained by walbourn Hosted on GitHub Pages — Theme by mattgraham
Home | Posts by Tag | Posts by Month

Anatomy of Direct3D 12 Create Device

direct3d, win10

Originally 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 with DXGI_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 use EnumAdapterByGpuPreference 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