Anatomy of Direct3D 11 Create Device


In answering some questions today, I remembered a topic I had been meaning to post about for some time: the seemingly simple act of creating a Direct3D 11 device. At it’s core, it’s pretty simple, but there’s more to it than it first appears.

The standard code for creating a Direct3D 11 device starts out pretty simple using D3D11CreateDevice:

DWORD createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
D3D_FEATURE_LEVEL fl;
HRESULT hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE,
    nullptr, createDeviceFlags, nullptr,
    0, D3D11_SDK_VERSION, &device, &fl, &context );

This assumes you want the default hardware device and it handles requesting the debug device in debug builds of the application (see Direct3D SDK Debug Layer Tricks for more). This will create a device at the highest available feature level on most systems, but it has a subtle side-effect: you will never get a Feature Level 11.1 device. This is for better backwards compatibility and is easy to rectify, but for a Win32 desktop application (which can run on older versions of Windows) it’s a little tricky.

This following code is the robust way to get all possible feature levels while handling DirectX 11.0 systems:

D3D_FEATURE_LEVEL lvl[] = {
    D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1 };

DWORD createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
D3D_FEATURE_LEVEL fl;
HRESULT hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
    createDeviceFlags, lvl, _countof(lvl),
    D3D11_SDK_VERSION, &device, &fl, &context );
if ( hr == E_INVALIDARG )
{
    hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
        createDeviceFlags, &lvl[1], _countof(lvl)-1,
        D3D11_SDK_VERSION, &device, &fl, &context );
}

The E_INVALIDARG case is the one that trips up people, and it handles the case where the platform only supports DirectX 11.0 (Windows Vista, Windows 7 RTM, or Windows 7 SP1 without KB 2670838 installed). Note you can get a similar kind of failure if you are trying to create a resource with one of the 16-bit per pixel DXGI 1.2 formats (i.e. 5/5/5/1, 565, 4/44/4) on a system with DirectX 11.0 installed.

Update: The same thing happens if you include D3D_FEATURE_LEVEL_12_0 and/or D3D_FEATURE_LEVEL_12_1 in your array when run on versions of Windows prior to Windows 10.

Direct3D 11.1

Once you have the Direct3D device and context you can proceed, but if you want to use some of the newer features of Direct3D 11.1 you’ll need another step:

ComPtr<ID3D11Device1> device1;
ComPtr<ID3D11DeviceContext1> context1;
if ( SUCCEEDED(device.As(&device1) )
{
    (void)context.As(&context1);
}

This code requires you include <d3d11_1.h> and have the Windows 8.0 SDK, Windows 8.1 SDK, or the Windows 10 SDK. You will get a valid device1 and context1 pointer on Windows 10,Windows 8.1, Windows 8.0, and Windows 7 SP1 with KB 2670838 installed.

Note: For modern games, DirectX 11.1 or later is a reasonable requirement. Games should already require Windows 7 Service Pack 1 as Windows 7 RTM is out of support and isn’t supported by VS 2015 in any case. If you fail to obtain a 11.1 device in this case, a fatal error telling the user they need to install KB 2670838 or upgrade to a newer OS is a reasonable course of action.

Direct3D 11.2

If you want to use Direct3D 11.2 features, you do basically the same thing:

ComPtr<ID3D11Device2> device2;
ComPtr<ID3D11DeviceContext2> context2;
if ( SUCCEEDED(device.As(&device2) )
{
    (void)context.As(&context2);
}

This code requires you include <d3d11_2.h> and have the Windows 8.1 SDK or the Windows 10 SDK. You will get a valid device2 and context2 pointer on Windows 8.1 or Windows 10.

Update: You could do the same with Direct3D 11.3 which would only return a valid pointer on Windows 10. Direct3D 11.4 requires Windows 10 (November 2015 or later). You need to include <d3d11_3.h> or <d3d11_4.h> and have the Windows 10 SDK.

DXGI

The primary reason to get a DXGI interface is to enumerate adapters and inputs, but you also use them to create swap chains. There’s a trick to making sure you get it robustly if you have passed nullptr to D3D11CreateDevice for the pAdapter pointer as we have above. Mixing different DXGI factory versions can cause problems, so ideally we want to use whatever the system used internally. You can do this with a little COM sequence that is perhaps familiar to Windows Store app and Xbox One developers:

ComPtr<IDXGIFactory1> dxgiFactory;
{
    ComPtr<IDXGIDevice> dxgiDevice;
    if (SUCCEEDED(device.As(&dxgiDevice)))
    {
        ComPtr<IDXGIAdapter> adapter;
        if (SUCCEEDED(dxgiDevice->GetAdapter(&adapter)))
        {
            hr = adapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
            if ( SUCCEEDED(hr) )
            {
                ...
            }
        }
    }
}

If you want to specify a particular adapter for the device creation, you need a DXGI factory. For DirectX 11 systems generally, you use CreateDXGIFactory1 (CreateDXGIFactory was for Direct3D 10 systems). You can make use of CreateDXGIFactory2 on Windows 8.1 or Windows 10 systems to specify DXGI debugging, but generally you use CreateDXGIFactory1 and then would QueryInterface other versions as needed.

Swap Chains

For Win32 classic desktop apps on DirectX 11.0 systems, you must use the IDXGIFactory1::CreateSwapChain function. On DirectX 11.1 or later systems you can use IDXGIFactory2::CreateSwapChainForHwnd.

For Universal Windows Platform (UWP) apps you use CreateSwapChainForCoreWindow or CreateSwapChainForComposition. You must also make use of 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 UWP.

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 on DirectX 11.1 systems.

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 required for UWP apps, 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).

Microsoft Basic Render Driver

In the original code, we use D3D_DRIVER_TYPE_HARDWARE. If you had wanted to use the WARP software device, you would have used D3D_DRIVER_TYPE_WARP. WARP is exceptionally useful as a much faster ‘ref’ for testing and can be used quite successfully as a software fallback for many kinds of applications, but it is still a software renderer. As such, most games don’t expect to be running under WARP.

With Windows 8.x and Windows 10, however, there is new situation to be aware of (see Desktop Games on Windows 8.x). In older versions of Windows, if a suitable video driver was not available it would fallback to a legacy VGA driver. Direct3D device creation with this driver would fail, which poses a problem for a desktop which requires it. Therefore, with Windows 8.x it defaults to the “Microsoft Basic Render Driver” instead. This is an extremely simple video output driver combined with WARP. This is a reasonable setup for servers, and the technology is very useful in making remote desktop work well.

For games, the most likely situation for this to come up is related to the fact that Direct3D9 era hardware is considered legacy for Windows 8.x. Users can obtain older Windows Vista WDDM 1.0 or Windows 7 WDDM 1.1 drivers for their DirectX9 era video cards, but the expectation is that most x86/x64 systems will have a DirectX 10+ capable video card. Users who upgrade their systems with a DX9 video card could be running the “Microsoft Basic Render Driver” and not realize they are missing a needed driver or that their video card is too old to be supported at all (i.e. it only has an XPDM driver available which are not supported by Windows 8.x)

One way to mitigate this scenario is for a game to detect if Microsoft Basic Render driver is active and warn the user. This is easy to do since the “Microsoft Basic Render Driver” has a well-known VendorID/DeviceID combination:

ComPtr<IDXGIDevice> dxgiDevice;
if (SUCCEEDED(device.As(&dxgiDevice)))
{
    ComPtr<IDXGIAdapter> adapter;
    if (SUCCEEDED(dxgiDevice->GetAdapter(&adapter)))
    {
        DXGI_ADAPTER_DESC desc;
        if (SUCCEEDED(adapter->GetDesc(&desc)))
        {
            if ( (desc.VendorId == 0x1414) && (desc.DeviceId == 0x8c) )
            {
                // WARNING: Microsoft Basic Render Driver is active.
                // Performance of this application may be unsatisfactory.
                // Please ensure that your video card is Direct3D10/11 capable
                // and has the appropriate driver installed.
            }
        }
    }
}

Note: If you are still supporting a Direct3D 9 game, you can detect this the same way:

IDirect3D9* d3ddevice = Direct3DCreate9(D3D_SDK_VERSION);
if ( d3ddevice )
{
    D3DADAPTER_IDENTIFIER9 adapterIdentifier;
    if (SUCCEEDED(d3ddevice->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &adapterIdentifier)))
    {
        if ( (adapterIdentifier.VendorId == 0x1414) && (adapterIdentifier.DeviceId == 0x8c) )
        {
            // WARNING: Microsoft Basic Render Driver is active.
            // Performance of this application may be unsatisfactory.
            // Please ensure that your video card is Direct3D9 capable
            // and has the appropriate driver installed.
        }
    }
}

Update: I’ve updated this blog post to make use of Microsoft::WRL::ComPtr. See this page for more information about this helpful smart-pointer for COM programming. Also updated with UWP information for Windows 10.

Related: Direct3D Game Visual Studio templates (Redux), Anatomy of Direct3D 12 Create Device

Comments (5)

  1. Peter Liu says:

    Good information to know. How could a developer become productive with so much unstructured tricks for DirectX 11.x? I would greatly appreciate it if you could address this question.

  2. walbourn says:

    In most cases when you are learning Direct3D (or any other technology) you focus on the basics and proficiency, not all the edge-cases. The purpose of this post for example was to cover a bunch of aspects of device creation for those looking to do complex things with it. For a standard application, just call D3D11CreateDevice and move on…

  3. Mustaphakd says:

    thanks Chuck!

    I am learning and posts such as this one are wonderful and make it easy to grasp.

    if you ever write a book, I'll read it!

  4. Maghin says:

    Thank you chuck for explanation. (Sorry for english level and mistakes: i'm french)

    One more question, on my computer i have Intel Gpu(to save battery) and NVidia GPU. With a basic loop, using factory->EnumAdapters (i, &adapter), i have:

    adapter[0] = Intel

    adapter[1] = NVidia

    adapter[2] = Microsoft.

    I force to use the NVidia one, because my computer's default is Intel. But when the application stops, it stay on Nvidia (and my computer heats). How can I force it to come back to his default settings?

    In fact on my MSI computer, the power switch is white when running Intel, and turn orange when running NVidia so it does when the application start, but doesn't return to white when it close.. On my nvidia settings, the switch bettwen GPU is set to "automatic".

  5. Frank Castle says:

    Can't you force the Intel adapter to be used before exiting, in the same way you force the Nvidia one to be used at startup? Or are you not doing that programmatically?