Simple question: Managing alt-tabbing

Started by
14 comments, last by Zedix 18 years, 8 months ago
Hi! I'm new here, but I've been reading these forums for a while. I have a simple question that perhaps relates more to GDI than DirectX, but I thought this was the most appropriate forum. I'm currently coding a zelda-clone RPG with a friend of mine, and I'm fairly advanced in the engine departement. It uses (as a base) the Game Programming Genesis tutorial that I found on gamedev, so the game is coded in C, using DirectX7. My question is that I'd like to know how to manage alt-tabbing calls from Windows, that is, right now if the program alt-tabs, or another window takes precedence over the game window, the screen stops drawing (it becomes black), and the game has to be restarted to obtain an image. I'm under the impression that when the game alt-tabs, a call is sent to the program so I'd want to stop the main loop, and when it receives focus, I'd want to restart the main loop and redraw everything. 1) Am I correct in this assumption? 2) What are the calls that windows sends to the program? (WM_ACTIVE or something of the like?) 3) and finally, how would I go about redrawing the surfaces once the program has focus again ? Thanks in advance :) - Zedix
Advertisement
What is happening is that the device is lost and needs to be reset. You should check the return value of the call to present. When that is D3DERR_DEVICELOST you should free up all the stuff that is unmanaged. Then you should call the Reset function of the d3d device. If that succeeds it was able to reset itself and you should recreate anything you had to free up.

Here's my render code
void PXRenderer::Render(void){	if(!Valid())	{		// critical error time		PXApplication::DebugLog("PXRenderer::Render(): Invalid renderer");		HWND hWnd = PXApplication::GetApplication()					->GetWindow()->GetHandle();		ShowWindow(hWnd,SW_HIDE);		::MessageBox(hWnd,"Critical Error. Closing Application. See log file for details.","PXEngine Error",MB_OK);		PXApplication::GetApplication()->Exit();		return;	}	if(!m_pObject)	{		PXApplication::DebugLog("PXRenderer::Render(): No graphics object");		return;	}	if(!m_pDevice)	{		PXApplication::DebugLog("PXRenderer::Render(): No graphics device");		return;	}	if(m_DeviceLost)	{		PXApplication::DebugLog("PXRenderer::Render(): Attempting to recover device");		if(RestoreDevice())		{			PXApplication::DebugLog("PXRenderer::Render(): Succeeded recovering device");						if(!OnDeviceReset())			{				PXApplication::DebugLog("PXRenderer::Render(): Failed recovering scene");				m_DeviceLost = true;			}			else			{				PXApplication::DebugLog("PXRenderer::Render(): Succeeded recovering scene");							}		}		else			PXApplication::DebugLog("PXRenderer::Render(): Failed recovering device");		return;	}	HRESULT hr;	hr = m_pDevice->Clear(0,0,PXCLEAR_ZBUFFER | PXCLEAR_TARGET,							m_BackBufferColor,1.0f,0);	if(FAILED(hr))	{		PXApplication::DebugLog("PXRenderer::Render(): Failed clearing");		return;	}	// begin the scene	hr = m_pDevice->BeginScene();	if(FAILED(hr))	{		PXApplication::DebugLog("PXRenderer::Render(): Failed beginning scene");		return;	}	// render	m_Rendering = true;	        // render stuff here			// render the batched text before finishing the scene	if(!RenderText())	{		PXApplication::DebugLog("PXRenderer::Render(): Failed drawing text");	}	//// end the scene	hr = m_pDevice->EndScene();	m_Rendering = false;	if(FAILED(hr))	{		PXApplication::DebugLog("PXRenderer::Render(): Failed ending scene");		return;	}	hr = m_pDevice->Present(0,0,0,0);	if(hr == D3DERR_DEVICELOST)	{		PXApplication::DebugLog("PXRenderer::Render(): Device lost");		// do this only once		if(!m_DeviceLost)		{						m_DeviceLost = true;			OnDeviceLost();					}		return;	}	else if(hr == D3DERR_DRIVERINTERNALERROR)	{		PXApplication::DebugLog("PXRenderer::Render(): Internal driver error");		HWND hWnd = PXApplication::GetApplication()					->GetWindow()->GetHandle();		ShowWindow(hWnd,SW_HIDE);		::MessageBox(hWnd,"Critical Error. Closing Application. See log file for details.","PXEngine Error",MB_OK);		PXApplication::GetApplication()->Exit();	}	else if(hr == D3DERR_INVALIDCALL)	{		PXApplication::DebugLog("PXRenderer::Render(): Invalid call");		return;	}}


And this is the code I use to reset the device
bool PXRenderer::RestoreDevice(void){		// already freed up other stuff in the scene.	// Query device to see if it can be reset	HRESULT hr;	hr = m_pDevice->TestCooperativeLevel();	if(hr == D3DERR_DEVICELOST)	{		// device cannot be reset yet		return false;	}	else if(hr == D3DERR_DEVICENOTRESET)	{		// were good to reset the device		HRESULT hrReset;		hrReset = m_pDevice->Reset(&m_PresentParams);		if(SUCCEEDED(hrReset))		{			m_DeviceLost = false;			PXApplication::DebugLog("PXRenderer::RestoreDevice(): Succeeded resetting device");			return true;							}		PXApplication::DebugLog("PXRenderer::RestoreDevice(): Failed resetting device");				return false;	}	else if(hr == D3DERR_DRIVERINTERNALERROR)	{		PXApplication::DebugLog("PXRenderer::RestoreDevice(): Internal driver error");		HWND hWnd = PXApplication::GetApplication()					->GetWindow()->GetHandle();		ShowWindow(hWnd,SW_HIDE);		::MessageBox(hWnd,"Critical Error. Closing Application. See log file for details.","PXEngine Error",MB_OK);		PXApplication::GetApplication()->Exit();	}	return false;}
1) Am I correct in this assumption?
Not exactly but close. There is not a message your program receives (well perhaps there is but it is not the generic way). Instead the IDirect3DDevice9::Present() call will return D3DDEVICELOST. This should stop your program from rendering until further notice.

This further notice is received by polling TestCooperativeLevel(). It will return D3DDEVICELOST for a while but at some point it will return D3DDEVICENOTRESET. This is when your program is ready for a device reset.

This proceeds as follows. You must unload all device resources (vertex buffers, textures) and call Reset(). Then you reload all the resources. For managed resources this is not necessary as it is unnecessary for certain deviceish objects such as HLSL shaders and fonts.

You need a crisp separation between data and gamestate because the reload of data should not bring the player to the start of the game again -- you'd want the game to continue where it left of. Hence the position etc. must be stored in places separate from, say, vertex buffers.

Hope this is clear. See the SDK docs for more information. Good luck!

Greetz,

Illco
If you want to window message for this, I believe you get a WM_KILLFOCUS message as you lose the focus, when the user alt-tabs, so can pause your app then.
Minimize is covered by WM_SIZE, so something like the following will help...

        case WM_SIZE:            // Check to see if we are losing our window...            if( SIZE_MAXHIDE == wParam || SIZE_MINIMIZED == wParam )                g_bActive = FALSE;            else                g_bActive = TRUE;            break;	case WM_KILLFOCUS:	    g_bRunning = FALSE;	    break;
Great! Thanks a lot for the quick answers, makes a lot of sense. With the examples of code you've given me, I'll be able to code something to handle it. :)
It really is deceptivly simple, but handling lost devices isn't the most documented thing out there.
This problem has bugged me in the past, so I have some experience with it ^^. If you use the D3DX objects for fonts, sprites, swap chains etc.. make sure you call all OnLostDevice() and OnResetDevice() methods when the device is lost/reset. Missing just one will prevent it from resetting properly.

Also, make sure all resources are created as managed (D3DPOOL_MANAGED or something) so you won't have to reset them manually. There *may* be some speed issues having them managed that some DX-hardcore fan soon will lecture me, but I doubt it matter much. The relief of not having to manage them manually makes it worth it...
----------------------------------------MagosX.com
Quote:Original post by Magos
Also, make sure all resources are created as managed (D3DPOOL_MANAGED or something) so you won't have to reset them manually. There *may* be some speed issues having them managed that some DX-hardcore fan soon will lecture me, but I doubt it matter much. The relief of not having to manage them manually makes it worth it...

Managed resources are copied to memory, thus wasting several megabytes of space. If at all possible, you should use D3DPOOL_DEFAULT, as it's faster. And resetting the device is rather easy, my game does it in like 30 lines of code.
Ok so I went to try your methods, and as much sense as it does to me, I obviously overlooked something, I'm not using D3D, I'm using DirectDraw (running on DX7). These methods don't seem to apply at all in DX7 (no method called Present(), no such thing as D3DERR_*), so does anyone know the equivalent in DX7?

Thanks ;)

Edit: I've been looking into the DX7 SDK and the errors start by DDERR_ instead, but apart from TestCooperativeLevel which would return DDERR_NOEXCLUSIVEMODE (I'm guessing this is what happens when the game isn't full screen anymore - eg: alt-tabbed), I can't find much else. Is this the right direction at least?
Quote:Original post by Daggett
Managed resources are copied to memory, thus wasting several megabytes of space. If at all possible, you should use D3DPOOL_DEFAULT, as it's faster. And resetting the device is rather easy, my game does it in like 30 lines of code.

On the other hand the SDK clearly says that the managed pool should be the preffered pool for storage. Sure, everything has a copy in system memory, but unless the device is constantly being reset most of the data will simply be swapped out to the hard drive and not occupy memory. Since this is the common scenario, you aren't going to be facing actual memory consumption problems all that much. When the device is lost all of the managed resources get swapped back into memory, sent to the device, and then swapped back out. Sounds like an ideal solution to me, since setting the managed flag is always going to be less code then manually resetting everything.
Turring Machines are better than C++ any day ^_~

This topic is closed to new replies.

Advertisement