D3D11 Programming Tip #3
Over the last few days, I have been working to get multiple swap chain support added into Hieroglyph. Overall, the process wasn't too hard, but there is one really big gotcha that can seem really confusing if you run into it - so that is what today's tip is going to be about.
There are two different functions for creating the Direct3D device: D3D11CreateDevice and D3D11CreateDeviceAndSwapChain. Most tutorials, samples, and descriptions that I have seen about creating a device use the latter of the two functions to instantiate the device, and as a side effect you get a nice new swap chain too. This works great for single window applications, but if you want to be able to create multiple rendering windows it would be nice to use the D3D11CreateDevice method instead. Then you could create the device separately from the swap chains, and use a single code path for creating the swap chain together with its window (of course you could use the D3D11CreateDeviceAndSwapChain function for the first window, then separately create each additional one, but that seems hackish...).
So, creating the device with D3D11CreateDevice is pretty much the same as the the more common D3D11CreateDeviceAndSwapChain, minus the parameters for a swap chain. I'll assume that the device has been created first, and will be followed by the swap chains for each window in the application.
The process for creating a swap chain outside of the D3D11CreateDeviceAndSwapChain function requires use of the IDXGIFactory interface. A naive implementation, based on the documentation of the IDXGIFactory interface would look something like this:
IDXGIFactory* pFactory;HRESULT hr = CreateDXGIFactory( __uuidof(IDXGIFactory), (void**)(&pFactory) );if ( FAILED( hr ) ){ CLog::Get().Write( "Failed to create DXGI Factory!" ); return( -1 );}// Attempt to create the swap chain.IDXGISwapChain* pSwapChain = 0;hr = pFactory->CreateSwapChain( m_pDevice, &pConfig->m_State, &pSwapChain );// Release the factory regardless of pass or fail.pDXGIDevice->Release();pFactory->Release();if ( FAILED( hr ) ){ CLog::Get().Write( "Failed to create swap chain!" ); return( -1 );}
This technique proceeds as follows: you get a reference to the DXGIFactory, and then use its CreateSwapChain method to create a swap chain. This will work perfectly well, passing all HRESULTs and not outputting any errors. But here's the gotcha - when you try to present the first frame to the swap chain, you will get an invalid access exception. There isn't much description of why it is producing an exception, but surely it will produce the exception with a seemingly perfectly good swap chain interface.
The problem source/solution can be found in the DXGI Factory documentation. If the swap chain is going to be used by Direct3D 11, it must be created with the same factory that was used to create the Direct3D device! The sample code in the IDXGIFactory documentation shows how to do this correctly through COM interface queries - essentially using the device to query up the chain through the adapter to get to the factory. This is not obvious if you haven't read the page on the IDXGIFactory interface, to say the least! So the correct way to create the swap chains is as follows:
// Attempt to create the DXGI Factory.IDXGIDevice * pDXGIDevice;HRESULT hr = m_pDevice->QueryInterface(__uuidof(IDXGIDevice), (void **)&pDXGIDevice); IDXGIAdapter * pDXGIAdapter;hr = pDXGIDevice->GetParent(__uuidof(IDXGIAdapter), (void **)&pDXGIAdapter);IDXGIFactory * pFactory;pDXGIAdapter->GetParent(__uuidof(IDXGIFactory), (void **)&pFactory);// Attempt to create the swap chain.IDXGISwapChain* pSwapChain = 0;hr = pFactory->CreateSwapChain( m_pDevice, &pConfig->m_State, &pSwapChain );// Release the factory regardless of pass or fail.pDXGIDevice->Release();pDXGIAdapter->Release();pFactory->Release();if ( FAILED( hr ) ){ CLog::Get().Write( "Failed to create swap chain!" ); return( -1 );}
It took several hours to figure out where the exceptions were coming from, so hopefully this post will show up if someone searches the net for a similar issue. In all fairness to Microsoft, the answer is clearly stated in the DXGI documentation - but it would be nice if it was also listed in the Direct3D documentation as well...
With the swap chains created for each window, we can easily create multiple windows for an application that can utilize the same device, but still have individual swap chains. Here's a quick screen shot of the resulting window sections, each one cleared to a different back color (ermm - don't mind the errors in the debug output window - my depth buffer wasn't the same size as the several funny shaped windows...[rolleyes]):