So one day I was walking along and Direct3D 11 jumped out and bitch-slapped me, then it stepped on my hair, and it told me I was fat.
I am trying to swap between windowed and full-screen and “for reasons” I am not going the standard route by calling IDXGISwapChain->SetFullscreenState() etc. My way is to destroy the window, make a new one, and make a new swap chain for it. I have reasons relating to my engine’s tools and also complicity with the way my other targeted platforms work. Simply put, it is necessary for me to be able to create and destroy multiple windows. And my system works fine except for 1 very strange bug.
If I start in windowed mode, I get an unhandled exception when I go to full-screen.
If I start in full-screen, I can switch to windowed, then if I switch back to full-screen I get an unhandled exception.
I normally get it here:
LSE_INLINE LSVOID LSE_FCALL CDirectX11::Clear( LSG_RENDER_TARGET * _prtRenderTarget ) {
GetDirectX11Context()->ClearRenderTargetView( _prtRenderTarget, m_rsCurRenderState.fClearColor );
}
_prtRenderTarget will be the address of the newly created color buffer. The address is valid and the buffer does exist. Also, the Direct3D context is untouched and valid throughout.The error is this:
First-chance exception at 0x000007fefd1e940d in LSTest.exe: 0x0000087A: 0x87a.
You want to say, “Are you sure all the involved pointers/objects are valid?”.
I am glad you asked that. Let me show you.
When the window mode changes, 4 objects are destroyed and then recreated with the new window:
/** Our swap chain. */
IDXGISwapChain * m_pscSwapChain;
/** View to the back color buffer. */
ID3D11RenderTargetView * m_prtvRenderTargetView;
/** The depth-stencil texture. */
ID3D11Texture2D * m_pt2dDepthStencilBuffer;
/** View to the back depth/stencil buffer. */
ID3D11DepthStencilView * m_pdsvDepthStencilView;
(_prtRenderTarget matches m_prtvRenderTargetView when the exception happens.)So I logged the creation and deletion of m_prtvRenderTargetView since that is the object being passed to Clear().
RELEASED 00000000
CREATED 0BD66A88
RELEASED 0BD66A88
RELEASED 00000000
CREATED 0BD63708
First-chance exception at 0x000007fefd1e940d in LSTest.exe: 0x0000087A: 0x87a.
* Debugger shows _prtRenderTarget = 0x0BD63708 inside Clear().Now let me show you why this is strange.
I changed my code to this:
LSE_INLINE LSVOID LSE_FCALL CDirectX11::Clear( LSG_RENDER_TARGET * _prtRenderTarget ) {
static LSBOOL bFailedLast = false;
try {
if ( bFailedLast ) {
LSCHAR szBuffer[43];
CStd::SPrintF( szBuffer, 43, "PASS 2 %.8X\r\n", _prtRenderTarget );
CStd::DebugPrintA( szBuffer );
}
GetDirectX11Context()->ClearRenderTargetView( _prtRenderTarget, m_rsCurRenderState.fClearColor );
bFailedLast = false;
}
catch ( ... ) {
bFailedLast = true;
LSCHAR szBuffer[43];
CStd::SPrintF( szBuffer, 43, "DIE %.8X\r\n", _prtRenderTarget );
CStd::DebugPrintA( szBuffer );
}
}
This basically means if it fails I will print “DIE” and then on the next call I will print “PASS 2”.Here is the log now:
RELEASED 00000000
CREATED 000F5588
RELEASED 000F5588
RELEASED 00000000
CREATED 0BD669C8
RELEASED 0BD669C8
RELEASED 00000000
CREATED 0BD63708
First-chance exception at 0x000007fefd1e940d in LSTest.exe: 0x0000087A: 0x87a.
DIE 0BD63708
PASS 2 0BD63708
RELEASED 0BD63708
RELEASED 00000000
CREATED 0BDBA808
RELEASED 0BDBA808
RELEASED 00000000
CREATED 0BDBA808
RELEASED 0BDBA808
RELEASED 00000000
CREATED 0BEB9908
RELEASED 0BEB9908
RELEASED 00000000
CREATED 0BEBB508
RELEASED 0BEBB508
RELEASED 00000000
CREATED 0BD67748
RELEASED 0BD67748
RELEASED 00000000
CREATED 0BEB9248
RELEASED 0BEB9248
RELEASED 00000000
CREATED 0BEB9248
RELEASED 0BEB9248
RELEASED 00000000
CREATED 0DD4F7C8
RELEASED 0DD4F7C8
RELEASED 00000000
CREATED 0BD66688
RELEASED 0BD66688
RELEASED 00000000
CREATED 0BDBA808
RELEASED 0BDBA808
RELEASED 00000000
CREATED 0DD47708
RELEASED 0DD47708
RELEASED 00000000
CREATED 0DD47708
RELEASED 0DD47708
RELEASED 00000000
CREATED 0DD56A88
RELEASED 0DD56A88
RELEASED 00000000
CREATED 0BD66688
RELEASED 0BD66688
RELEASED 00000000
CREATED 0BD67748
RELEASED 0BD67748
RELEASED 00000000
CREATED 0BD98888
RELEASED 0BD98888
RELEASED 00000000
CREATED 0DD47708
RELEASED 0DD47708
RELEASED 00000000
CREATED 0BD59EC8
Changing state.
*==
RELEASED 0BD59EC8
RELEASED 00000000
RELEASED 00000000
Go home Direct3D 11, you’re drunk.
Start in full-screen:
RELEASED 00000000
CREATED 000F5588
Full-screen to windowed:
RELEASED 000F5588
RELEASED 00000000
CREATED 0BD669C8
Windowed to full-screen:
RELEASED 0BD669C8
RELEASED 00000000
CREATED 0BD63708
First-chance exception at 0x000007fefd1e940d in LSTest.exe: 0x0000087A: 0x87a.
DIE 0BD63708
PASS 2 0BD63708
* Draws fine for a while, no bugs. *
Full-screen to windowed:
RELEASED 0BD63708
RELEASED 00000000
CREATED 0BDBA808
* Draws fine, many mode switches follow, never throws the exception again. *
So, yes, I am positive that all objects are valid.
It creates object 0BD63708, fails to clear it once, but then has no problem clearing it after that.
After it throws 1 (one) exception it can not only continue working with the exact object that caused the exception but the exception never happens again no matter how many times I destroy the window and swap chain and recreate them both.
Here is the code for destroying and recreating the swap chain and back buffers (minus the debug prints) with a new window:
LSE_INLINE LSVOID LSE_CALL CDirectX11::SetWindowParms( LSBOOL _bWindowed, HWND _hWnd ) {
// Get the width and height of the window.
RECT rClient;
::GetClientRect( _hWnd, &rClient );
m_dscdSwapChainDesc.BufferDesc.Width = rClient.right - rClient.left;
m_dscdSwapChainDesc.BufferDesc.Height = rClient.bottom - rClient.top;
m_dscdSwapChainDesc.OutputWindow = _hWnd;
m_dscdSwapChainDesc.Windowed = _bWindowed;
ReleaseBackBuffers();
SafeRelease( m_pscSwapChain );
IDXGIFactory * pfFactory;
if ( SUCCEEDED( ::CreateDXGIFactory( __uuidof( IDXGIFactory ), reinterpret_cast<void **>(&pfFactory) ) ) ) {
pfFactory->CreateSwapChain( m_pdDevice, &m_dscdSwapChainDesc,
&m_pscSwapChain );
SafeRelease( pfFactory );
}
CreateSwapChainBackBuffers( _hWnd );
}
LSVOID LSE_CALL CDirectX11::ReleaseBackBuffers() {
SafeRelease( m_pdsvDepthStencilView );
SafeRelease( m_pt2dDepthStencilBuffer );
SafeRelease( m_prtvRenderTargetView );
}
LSBOOL LSE_CALL CDirectX11::CreateSwapChainBackBuffers( HWND /*_hWnd*/ ) {
ReleaseBackBuffers();
// Create the color buffer.
ID3D11Texture2D * ptBuffer = NULL;
if ( FAILED( m_pscSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ),
reinterpret_cast<void **>(&ptBuffer) ) ) ) {
CStd::DebugPrintA( "Failed to access front buffer.\r\n" );
ReleaseBackBuffers();
return false;
}
if ( FAILED( m_pdDevice->CreateRenderTargetView( ptBuffer, NULL, &m_prtvRenderTargetView ) ) ) {
CStd::DebugPrintA( "Failed to create the main render target.\r\n" );
ReleaseBackBuffers();
SafeRelease( ptBuffer );
return false;
}
SafeRelease( ptBuffer );
// Make the depth/stencil buffer.
D3D11_TEXTURE2D_DESC tdDepthStencilDesc = { 0 };
[SET ALL THE PARAMETERS]
if ( FAILED( m_pdDevice->CreateTexture2D( &tdDepthStencilDesc, NULL, &m_pt2dDepthStencilBuffer ) ) ) {
CStd::DebugPrintA( "Failed to create the main depth/stencil buffer.\r\n" );
ReleaseBackBuffers();
return false;
}
// And now the view.
D3D11_DEPTH_STENCIL_VIEW_DESC dsvdDesc = { tdDepthStencilDesc.Format };
dsvdDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
dsvdDesc.Texture2D.MipSlice = 0;
if ( FAILED( m_pdDevice->CreateDepthStencilView( m_pt2dDepthStencilBuffer, &dsvdDesc, &m_pdsvDepthStencilView ) ) ) {
CStd::DebugPrintA( "Failed to create the depth/stencil view.\r\n" );
ReleaseBackBuffers();
return false;
}
return true;
}
I get no failure debug outputs and all the objects are created successfully.I have enabled DirectX 11 Debug Mode in the control panel. It prints nothing.
The first time and only the first time it goes into full-screen mode from windowed mode it throws this exception.
It can also throw it here:
LSE_INLINE LSVOID LSE_FCALL CDirectX11::Present() {
m_pscSwapChain->Present( 0, 0 );
}
In either case it throws only one time and then works perfectly fine after that.The double RELEASE lines in the log are because ReleaseBackBuffers() is being called twice, but SafeRelease() sets the pointers to NULL.
I have no errors with my reference counters etc.
My window code (creating and destroying, windowed mode and full-screen mode) is the same on all platforms and is not the problem.
This is all single-threaded.
Is there something I need to do when switching from windowed to full-screen the first time? It always works after the first time, so the concept and my implementation should be sound. Honestly this is looking like a driver bug.
Any ideas?
L. Spiro