Anatomy of Direct3D 12 Create Device


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:

ComPtr<IDXGIFactory1> dxgiFactory;
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory));
if (FAILED(hr))
    // Error!

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, but 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.

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:

#if !defined(NDEBUG)
    if (!adapter)
    {
        ComPtr<IDXGIFactory4> dxgiFactory4;
        if (SUCCEEDED(dxgiFactory.As(&dxgiFactory4)))
        {
            if (FAILED(dxgiFactory4->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.

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;
}

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:

ComPtr<IDXGIFactory2> dxgiFactory2;
if (FAILED(dxgiFactory.As(&dxgiFactory2)))
    // Fatal error (shouldn't happen in practice at this point)

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.

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).

Direct3D 12.1

With the Windows 10 Anniversary Update, 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
}

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. The code above is careful to try to use 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

Universal Windows Platform (UWP) notes

Because the minimum OS version is enforced for the platform, you can count on IDXGIFactory4 always being available. Therefore, you can simplify the code above by starting out with that version--which is exactly what you'll find in the Visual Studio DirectX 12 UWP templates:

ComPtr<IDXGIFactory4> dxgiFactory;
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory));
if (FAILED(hr))
    // Error!

Related: Direct3D Game Visual Studio templates (Redux), DirectX Tool Kit for DirectX 12, Getting Started with Direct3D 12

Comments (0)

Skip to main content