Direct3D 11 Swap-Chain Madness

Started by
21 comments, last by Jason Z 9 years, 10 months ago
Hi I am L. Spiro, long-time lurker first-time poster.
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

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Advertisement

Do you by any chance have FRAPS running?

I've had a major PITA from reference counting issues due to FRAPS. Then closed it...

I do not.

It also happens in both 32- and 64- bit versions.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

This is just a crazy first shot, but is the exception handled somewhere other than your code? I have had cases where the first chance exception occurs, only to be handled in one of the underlying libraries (i.e. the exception is used as a normal part of one of the sub-systems). If you set VS to not break on first chance exceptions then you would never even know it occurred.

Before digging into the nitty gritty details, have you tried that out?

Are you sure you got debug logging properly working? IIRC exception 0x87a is the debug layer error exception.

Very strange bug. Have you tested it on other machines?

My current game project Platform RPG

Before digging into the nitty gritty details, have you tried that out?

Indeed I have only tried in Visual Studio. I will try this when I can.

Are you sure you got debug logging properly working?

No. If you mean the debug run-time, I am expecting it to print some information at start-up and I am not even getting that.

Have you tested it on other machines?

Not yet. Maybe I will be able tomorrow.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Have you turned the Debug runtime on, because as of WIN8 SDK this has to be done through the VS2012 and up window or through executing the settings window from the WinSDK directory. It will not turn your debug runtime on other wise even though you did set it on in the D3D june SDK. http://blogs.msdn.com/b/chuckw/archive/2012/11/30/direct3d-sdk-debug-layer-tricks.aspx

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

The article says all I have to do in Direct3D 11 is add it to the list in the control panel, which I’ve done.

Staring with Direct3D 10.0, a new mechanism was created for the "Developer Runtime" via a API layering mechanism and is implemented in the D3D10SDKLAYERS.DLL. Developers could opt in their specific application to the debug validation either by creating the device with D3Dxx_CREATE_DEVICE_DEBUG, or by adding the executable to a list in the DirectX Control Panel.


I’ve used it before but this is the first time it simply ignores me.
In order to be positive that I have added the correct path to my executable, I ran it in Visual Studio and got this message:
'LSTest.exe': Loaded 'E:\My Projects\LSEngine\Win32\DX11 Debug\LSTest.exe', Symbols loaded.
I copied and pasted “E:\My Projects\LSEngine\Win32\DX11 Debug\LSTest.exe” into the DirectX Control Panel, so there is no mistake as to which version of my program is running and in their list.

[attachment=22278:DCP1.png]
[attachment=22279:DCP2.png]

My SDK is v6.0a, included with Visual Studio 2008 (the compiler I am using).


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

The article says all I have to do in Direct3D 11 is add it to the list in the control panel, which I’ve done.

Staring with Direct3D 10.0, a new mechanism was created for the "Developer Runtime" via a API layering mechanism and is implemented in the D3D10SDKLAYERS.DLL. Developers could opt in their specific application to the debug validation either by creating the device with D3Dxx_CREATE_DEVICE_DEBUG, or by adding the executable to a list in the DirectX Control Panel.


I’ve used it before but this is the first time it simply ignores me.

I ran into a similar problem on Windows 7 just a week or two ago. It would never actually turn on the debug runtime, and every time I closed and re-opened the DX control panel, everything was unchecked back to its defaults.

A bit of internet searching revealed that for reasons I didn't bother to investigate, the TrustedInstaller user had taken ownership of the relevant hunk of Registry data, and thus the DX control panel changes were silently being rejected.

So what you might want to try is to navigate to HKLM/SOFTWARE/Microsoft/Direct3D in RegEdit, open up the security permissions for the Direct3D key, and if the owner is indeed TrustedInstaller, change it to yourself (or a user group you belong to), and then grant yourself (or the selected user group) full control permissions (they might be read only by default).

"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke

This topic is closed to new replies.

Advertisement