• entries
    316
  • comments
    485
  • views
    322276

D3D11 Programming Tip #3

Sign in to follow this  

3259 views


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]):

Sign in to follow this  


6 Comments


Recommended Comments

You're welcome! I was pulling my hair out trying to figure it out, so I thought I would try to spare someone else from the same experience...

Share this comment


Link to comment
Yeah, I can see how this can send anyone into the wonderland for a good couple of hours. Thanks again.

I was always under the impression though, that creation of a swap chain must logically precede that of a device. D3D9 never made it clear which order is correct since IDirect3D9::CreateDevice creates them both in a single call and later swap chains, each required a call to IDirect3DDevice9::CreateAdditionalSwapChain. On the other hand, I take OpenGL's device context to be the equivalent of a swap chain and the rendering context akin to a device, in which case swap chains are to be created before the device.

So this is how I assumed the natural order of things are and as a result this is how things are designed in my engine. With a swap chain being a separate object, one which must be created first and passed to a device, I now have to find a workaround whenever I start working on D3D11. :/

Share this comment


Link to comment
To be honest, I don't know if you can do it in the other way or not - I've never tried it! I guess if there is some way to specify the IDXGIFactory to the device creation code, then it should work (allowing you to use the same factory for both...).

You'll have to let me know when you try it out with your shiny new D3D11 renderer!

Share this comment


Link to comment
When one uses IDXGIFactory::EnumAdapters to find the adapter for D3D11CreateDevice, instead of passing "0" to get the default adapter, the device is associated to the factory that enumerated the adapter.

When you do request a default adapter, the system creates a temporary factory. It would seem that the private device handles are different across instances of the factory even though they would correspond to same actual devices.

Share this comment


Link to comment
I ran into the same problem. I tried just creating a new factory to create swap chains from like you originally did, and got some warning in the debug output about using a different factory from the one the device was created with. I was scratching my head trying to figure out how to get the original factory from the created device, and just getting ready to give up when I found your journal entry. Exactly what I needed, thanks a bunch!

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now