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

ID3D11Device* pDevice = nullptr;
ID3D11DeviceContext* pContext = nullptr;
D3D_FEATURE_LEVEL fl;
HRESULT hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE,
nullptr, createDeviceFlags, nullptr,
0, D3D11_SDK_VERSION, &pDevice, &fl, &pContext );

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

ID3D11Device* pDevice = nullptr;
ID3D11DeviceContext* pContext = nullptr;
D3D_FEATURE_LEVEL fl;
HRESULT hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
createDeviceFlags, lvl, _countof(lvl),
D3D11_SDK_VERSION, &pDevice, &fl, &pContext );
if ( hr == E_INVALIDARG )
{
hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
createDeviceFlags, &lvl[1], _countof(lvl)-1,
D3D11_SDK_VERSION, &pDevice, &fl, &pContext );
}

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.

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:

 ID3D11Device1* pDevice1 = nullptr;
ID3D11DeviceContext1* pContext1 = nullptr;
hr = pDevice->QueryInterface( __uuidof( ID3D11Device1 ), reinterpret_cast<void**>( &pDevice1 ) );
if ( SUCCEEDED(hr) )
{
(void)pContext->QueryInterface( __uuidof( ID3D11DeviceContext1 ), reinterpret_cast<void**>( &pContext1 ) );
}

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

Direct3D 11.2

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

 ID3D11Device2* pDevice2 = nullptr;
ID3D11DeviceContext2* pContext2 = nullptr;
hr = pDevice->QueryInterface( __uuidof( ID3D11Device2 ), reinterpret_cast<void**>( &pDevice2 ) );
if ( SUCCEEDED(hr) )
{
(void)pContext->QueryInterface( __uuidof( ID3D11DeviceContext2 ), reinterpret_cast<void**>( &pContext2 ) );
}

This code requires you include <d3d11_2.h> and have the Windows 8.1 SDK. You will get a valid pDevice2 and pContext2 pointer on Windows 8.1.

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 'null' 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:

 IDXGIFactory1* dxgiFactory = nullptr;
{
IDXGIDevice* dxgiDevice = nullptr;
hr = pDevice->QueryInterface( __uuidof( IDXGIDevice ), reinterpret_cast<void**>( &dxgiDevice ) );
if ( SUCCEEDED(hr) )
{
IDXGIAdapter* adapter = nullptr;
hr = dxgiDevice->GetAdapter( &adapter );
if ( SUCCEEDED(hr) )
{
hr = adapter->GetParent( __uuidof( IDXGIFactory1 ), reinterpret_cast<void**>( &dxgiFactory ) );
if ( SUCCEEDED(hr) )
{
...
}
adapter->Release();
}
dxgiDevice->Release();
}
}

This particular sequence is a bit less verbose when using Microsoft::WRL::ComPtr.

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 systems to specify DXGI debugging, but generally you use CreateDXGIFactory1 and then would QueryInterface other versions as needed.

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.0 and Windows 8.1, 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:

 IDXGIDevice* dxgiDevice = nullptr;
hr = pDevice->QueryInterface( __uuidof( IDXGIDevice ), reinterpret_cast<void**>( &dxgiDevice ) );
if ( SUCCEEDED(hr) )
{
IDXGIAdapter* adapter = nullptr;
hr = dxgiDevice->GetAdapter( &adapter );
if ( SUCCEEDED(hr) )
{
DXGI_ADAPTER_DESC desc;
hr = adapter->GetDesc( &desc );
if ( SUCCEEDED(hr) )
{
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.
}
}
adapter->Release();
}
dxgiDevice->Release();
}

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;
HRESULT hr = d3ddevice->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &adapterIdentifier);
if ( SUCCEEDED(hr) )
{
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.
}