Handling Alt-Tab in Direct Draw?

Started by
10 comments, last by sakky 19 years, 7 months ago
Greetings faithful community, I'm trying to polish up my current game, but I'm running into a problem with dealing with users alt-tabbing. My app runs full screen and when they alt-tab out, you cannot return back to the game when you try to maximize the game window again. I am using Direct Draw(7) and the win32 API. Could anyone explain: 1) What is happening when a user alt-tabs? 2) How do I deal with this? Many thanks.
Advertisement
DirectX doesn't like youo pushing alt-tab. So until they fix there stuff. You have to handle it yourself. Windows sends a message to your program when a user push alt-tab. It then sends another message when it is returned to. It might be WM_POPUP but I don't know.

Anyway, once you get back into your program you have to recreate all the devices, textures, sounds..... everything!
Good luck!
im not familiear with direct draw or dx7 for that matter, but if is anything like direct 3d, this is the general concept of what happens.

windows sends a WM_ACTIVATE message to your window, with lparam = WA_INACTIVE, b/c your fullscreen application goes to the background and the user could potentally startup another direct draw program, it must push your program, all its textures and other direct draw resources off the video card, putting your device into a "lost state". and when this happens it wont tell you.

the only way to find out is to check for some error code when you present your front buffer, in direct3d this is D3DERR_LOSTDEVICE. when you get this error, you must go back recreate all the textures and surfaces you have and anything else that gets lost, then go back and reset your device.

hopefully this helps,
-good luck
Hmm, ok, I'm fairly confident I can figure this out for myself. I'm guess that I lose my surfaces, and all those images that I copied onto serfaces. In theory I'm guessing I have to restore all those surfaces in memory. Now my questions are, will I have to re-initialize my DirectDraw device, and will I have to recreate my primary and buffer surfaces?
That is wrong! You have to reset the Direct3D device first before re-initializing any textures or meshes. Actually, you have to release all of them before you can reset the device. The device will not reset if there are child interfaces still active that have been created by using it.

There are a few different ways of handling the Alt-Tab situation. One (for Windows XP) is to use a low-level keyboard hook procedure. Then you parse out the Alt-Tab command. Keep in mind, that there is other ways for the user to exit or steal the focus from your application. One that you are probably not thing of is the Ctrl +Alt + Del command or Ctrl + Esc, Alt + Esc.

For Windows 98 you would use a function called SystemParameterInfo. Look on MSDN for more information about this function.

I use DirectX extensively and I still have more to learn; mainly COM stuff. I love DirectX :)

You do not have to recreate you DirectDraw parent interface (IDirectDraw*). You do however, have to restore you main surfaces. You primary (front) and back buffer surfaces should be attached to each other (depending on you setup). You only have to call the restore method of the primary surface and the back buffer is automatically restored as well.

However, all of your imagery on off screen surfaces will need to be restored. DirectDraw returns the DDERR_SURFACELOST flag when any other the surfaces are lost. Just look for it, when you do find it, release all the child interfaces in use then restore you primary surfaces. Then you are ready to reload the imagery you need.

Almost all DirectX interface act this way; from what I remember they do. Show me you code or send it to me via email at merdman5987@msn.com. I will be more then happy to help you out as much as I can with you application.

The best way to handle all of this restoreing and lost device /surface crap is to use a single function for initializing what imagery you need (say init_stuff) and one for releasing all of that (say release_stuff). Then, make on for restoring the main surfaces (say restore_main_surfaces). In the restore_main_surfaces function you first call release_stuff, then restore you primary surface and back buffer via IDirectDrawSurface::Restore( ) function.

The IDirectDrawSurface::Restore( ) function will return to you a value indicating it’s success. After the primary surface and back buffer have been restored you may call init_stuff again.

Doing things using the approach I just mentions enables your users to Alt-Tab out of you application and Alt-Tab back into it with out effecting it, hence, the application is still able to run.

Alternatively, you may call the IDirectDrawSurface::IsLost method to find if a surface is lost. The function will turn D3DERR_SURFACELOST if the surface is indeed lost or D3D_OK if the surface is fine.

Read the DirectX documentation or look on MSDN for more information about lost surfaces in DirectDraw and how to handle them properly.
Take back the internet with the most awsome browser around, FireFox
Hmm, I think I understand what you're saying. Where would I put all my surface restoration/image copying? In the WM_ACTIVATE command in my WinProc method?
No sir! You would not put it there. But, you would use the message to keep track of the applications active state. With Direct3D, the IDirect3DDevice9::Present() method fails if the device has been lost. This is what signals the whole release / restore / reload procedure. The WM_ACTIVATE message has nothing to do with this.

I use the WM_ACTIVATE message to keep track of my applications active state for use ion my main loop. I use a Boolean variable to record this value. Then in the main loop, if the application is activate, hence a boolean value of TRUE, I use PeekMessage. If the application is not method, a FALSE value, I use GetMessage. I do this so my application is not hogging up resources and processor time if it is not in focus. During the main loop, when I Present to the user, I check the return value of this method. The I restore or contine as normal based upon that value. It’s relatively simple.

Here is an example. I use MFC a lot because it’s cool. But the workings of this are the same. I’ll show you the main loop code so you can geta grip of what I’m talking about
	MSG						 sMsg;	BOOL					 bGotMsg;	// Grab any pending messages	//	PeekMessage( &sMsg, NULL, 0U, 0U, PM_NOREMOVE );	while ( WM_QUIT != sMsg.message )	{		// Peek for a message while app is active (in focus)		// Wait for a message other wise		//		if ( m_pDevWnd->IsAppActive( ) )		{			bGotMsg = ( PeekMessage( &sMsg, NULL, 0U, 0U, PM_REMOVE ) != 0 );		}		else		{			bGotMsg = ( GetMessage( &sMsg, NULL, 0U, 0U ) != 0 );		}		// Process pending messages, is any		//		if ( bGotMsg )		{			TranslateMessage( &sMsg );			DispatchMessage( &sMsg );		}		else		{			// TODO : Perform logical routines		}	}


You could do this with DirectDraw too. I would recommend that you create four functions for initializing DirectDraw, restoring DirectDraw, loading BS to DirectDraw, and releasing the BS from DirectDraw you loading into it. FIY, the restore function will look a lot like the initialization function, but it is needed. So don’t feel like your wasting time on creating it. Here is a little framework I use a lot in my applications that might help you out.

class CApplication{public:	CApplication( );	~ CApplication( );	HRESULT InitializeDirect3D( )	{		/* Do Initialization */	}	HRESULT RestoreDirect3D( )	{		InvadliateDeviceObjects( );		/* Reset / Restore the device */		Setup3DEnvironment( );	}	HRESULT Setup3DEnvironment( )	{		/* Load images and set render states, etc.. */}	HRESULT InvalidateDeviceObjects( ){	/* Release device objects still in use */}	HRESULT ReleaseDirect3D( ){	InvadlidateDeviceObjects( );	/* Release primary & parent interfaces for Direct3D */}};


Something similar to that. Of course you will be using DirectDraw instead of Direct3D, so you would change the Direct3D names to DirectDraw, unless of course you thought that Direct3D looked cooler then DirectDraw ;)
Take back the internet with the most awsome browser around, FireFox
I personally handle all activation calls whn I detect Alt-Tab, and when my application is restored. I cant remember the exact messages I handle, I dont have the source code to hand, but when I get a losing focus message, I call a system wide SuspendInterface(...) function, and when I get a returning focus method, I call a system wide ResumeInterface(...) method.

When the (fill screen) app loses focus, I destroy all my DX interfaces and resources (textures, fonts etc), so that the application that isn't in focus isn't using what could be valuable system resources.

Then when the applcation is given focus again, i recreate my DX interface, and all the resources that i previously destroyed.

While this might not be the fastest and most efficent way (such as detecting what exactly has been lost, and only recovering those), IMHO I feel that this method is slightly cleaner (as to be honest, if one thing is lost, then so will everything else), giving you a clean slate from which to start with when you restore the application

Spree
Thanks for being patient with me everyone.

Ok, so here's what your basicly suggesting, if I am understanding you correctly. I should keep track of my applications active state based on the WM_ACTIVATE command's lParam. Then some where within my game loop I should check to see if my app has been deactivated (to suspend game logic) and then check to see when it's reactivated. Upon reactivation I should restore my DirectDraw stuff (using one of the various methods people have suggested). Am I getting this straight?

Thanks again everyone for your sincere patience.
Yes, that is how you do it. But let me give another tip. To retrieve the active status of the application, use something like this

// Initialize application active status to false// The message procedure will update it by it’s self//BOOL g_bIsAppActive = FALSE;LRESULT CALLBACK WindowProc( HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam ){	switch ( uMessage ){	case WM_ACTIVATEAPP:		g_bIsAppActive = ( BOOL )( wParam ) ? TRUE : FALSE;		return 0L;}	return DefWindowProc( hWnd, uMessage, wParam, lParam );}


That is how I do it. Another thing, I never really check for the reactivation. I only check for the last device command (IDirect3Device9::Present( ), or IDirectDrawSurface::Flip in your case) to see if the surface is lost. I makes no cense checking each DirectDraw command, so you would only check the most important ones.

I know you are not using MFC, but I though this might help you. Take a look at my code for the device window (CFrameWnd subclass). I use the same solutiuons I’ve discussed aboce.
// --------------------------------------------------------// File - DevWnd.h// Desc - Device window interface//#include <AFXWIN.H>#include <AFXEXT.H>#include <D3D9.H>#include <D3DX9.H>#include "DEVWND.H"BEGIN_MESSAGE_MAP( CDevWnd, CFrameWnd )	// CDevWnd message handlers	ON_WM_CREATE( )	ON_WM_ACTIVATEAPP( )	ON_WM_SIZE( )	ON_WM_ERASEBKGND( )	ON_WM_PAINT( )	ON_WM_DESTROY( )END_MESSAGE_MAP( )CDevWnd::CDevWnd( ){	m_lpD3DDevice	= NULL;	m_lpD3D			= NULL;	m_bIsAppActive	= FALSE;	m_bIsAppInited	= FALSE;}CDevWnd::~CDevWnd( ){	// ...}BOOL CDevWnd::PreCreateWindow( CREATESTRUCT& refCS ){	if ( FALSE == CFrameWnd::PreCreateWindow( refCS ) )	{		return ( FALSE );	}	// DO NOT allow extended window styles and start with a decent size window too!	// Centered on the screen too of course	//	refCS.dwExStyle	= 0L;	refCS.cx		= 640;	refCS.cy		= 480;	refCS.x			= ( GetSystemMetrics( SM_CXSCREEN ) / 2 ) - 320;	refCS.y			= ( GetSystemMetrics( SM_CYSCREEN ) / 2 ) - 240;	return ( TRUE );}VOID CDevWnd::DrawScene( ){	HRESULT					 hRet;	// Begin rendering	//	m_lpD3DDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 80, 80, 100 ), 1.0F, 0L );	// ...	// Present the scene to the user	//	if ( FAILED( hRet = m_lpD3DDevice->Present( NULL, NULL, NULL, NULL ) ) )	{		if ( D3DERR_DEVICELOST == hRet )		{			// Restore the device if needed			//			if ( FAILED( hRet = RestoreDevice( ) ) )			{				if ( D3DERR_DRIVERINTERNALERROR == hRet )				{					::MessageBox( NULL, "ERROR: Driver internal error during graphics loop!",										"Bean",										MB_OK | MB_ICONERROR );					PostQuitMessage( 0 );				}						if ( E_OUTOFMEMORY == hRet || D3DERR_OUTOFVIDEOMEMORY == hRet )				{					::MessageBox( NULL, "ERROR: Not enough memory to complete the requested operations!",										"Bean",										MB_OK | MB_ICONERROR );					PostQuitMessage( 0 );				}			}		}		else if ( D3DERR_DRIVERINTERNALERROR == hRet )		{			::MessageBox( NULL, "ERROR: Driver internal error during graphics loop!",								"Bean",								MB_OK | MB_ICONERROR );			PostQuitMessage( 0 );		}	}}const BOOL CDevWnd::IsAppActive( ){	return ( m_bIsAppActive );}HRESULT CDevWnd::InitializeDirect3D( ){	D3DPRESENT_PARAMETERS	 sD3Dpp;	CRect					 rcClient;	HRESULT					 hRet;	// Create the parent Direct3D 9 interface	//	if ( NULL == ( m_lpD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )	{		return E_OUTOFMEMORY;	}	// Setup the device parameters	//	memset( &sD3Dpp, 0, sizeof( D3DPRESENT_PARAMETERS ) );	sD3Dpp.AutoDepthStencilFormat = D3DFMT_D16;	sD3Dpp.EnableAutoDepthStencil = TRUE;	sD3Dpp.BackBufferFormat	 = D3DFMT_UNKNOWN;	sD3Dpp.BackBufferCount	 = 1;	sD3Dpp.BackBufferWidth	 = 512;	sD3Dpp.BackBufferHeight	 = 512;	sD3Dpp.SwapEffect		 = D3DSWAPEFFECT_DISCARD;	sD3Dpp.Windowed			 = TRUE;	// Create a windowed device	//	if ( FAILED( hRet = m_lpD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hWnd,											   D3DCREATE_SOFTWARE_VERTEXPROCESSING,											   &sD3Dpp, &m_lpD3DDevice ) ) )	{		return hRet;	}	// Setup the environment	//	if ( FAILED( hRet = Setup3DEnvironment( ) ) )	{		return hRet;	}	return D3D_OK;}HRESULT CDevWnd::RestoreDevice( ){	D3DPRESENT_PARAMETERS	 sD3Dpp;	HRESULT					 hRet;	// Release the device objects in use	//	InvalidateDeviceObjects( );	// Reset the device parameters	//	memset( &sD3Dpp, 0, sizeof( D3DPRESENT_PARAMETERS ) );	sD3Dpp.AutoDepthStencilFormat = D3DFMT_D16;	sD3Dpp.EnableAutoDepthStencil = TRUE;	sD3Dpp.BackBufferFormat	 = D3DFMT_UNKNOWN;	sD3Dpp.BackBufferCount	 = 1;	sD3Dpp.BackBufferWidth	 = 512;	sD3Dpp.BackBufferHeight	 = 512;	sD3Dpp.SwapEffect		 = D3DSWAPEFFECT_DISCARD;	sD3Dpp.Windowed			 = TRUE;	// Restore the lost device	//	if ( FAILED( hRet = m_lpD3DDevice->Reset( &sD3Dpp ) ) )	{		return hRet;	}	if ( FAILED( hRet = Setup3DEnvironment( ) ) )	{		return hRet;	}	return D3D_OK;}HRESULT CDevWnd::Setup3DEnvironment( ){	D3DXMATRIX				 xWorld, xView, xProj;	CRect					 rcClient;	m_lpD3DDevice->SetRenderState( D3DRS_LIGHTING,	FALSE );	m_lpD3DDevice->SetRenderState( D3DRS_ZENABLE,	TRUE );	D3DXMatrixIdentity( &xWorld );	m_lpD3DDevice->SetTransform( D3DTS_WORLD, &xWorld );	D3DXMatrixLookAtLH( &xView, &D3DXVECTOR3( 0.0F, 0.0F, -5.0F ),								&D3DXVECTOR3( 0.0F, 0.0F,  0.0F ),								&D3DXVECTOR3( 0.0F, 0.0F,  0.0F ) );	m_lpD3DDevice->SetTransform( D3DTS_VIEW, &xView );	GetClientRect( &rcClient );	D3DXMatrixPerspectiveFovLH( &xProj, D3DX_PI / 4, ( ( FLOAT )rcClient.Width( ) ) / rcClient.Height( ), 1.0F, 1000.0F );	m_lpD3DDevice->SetTransform( D3DTS_PROJECTION, &xProj );	return D3D_OK;}HRESULT CDevWnd::InvalidateDeviceObjects( ){	// ..	return D3D_OK;}HRESULT CDevWnd::ReleaseDirect3D( ){	// Relese device objects in use	//	InvalidateDeviceObjects( );	// Release Direct3D parent & device interfaces	//	if ( NULL != m_lpD3DDevice )	{		m_lpD3DDevice->Release( );		m_lpD3DDevice = NULL;	}	if ( NULL != m_lpD3D )	{		m_lpD3D->Release( );		m_lpD3D = NULL;	}	return D3D_OK;}int CDevWnd::OnCreate( LPCREATESTRUCT lpCS ){	HRESULT					 hRet;	// Use base class method to create window	//	if ( ( -1 ) == CFrameWnd::OnCreate( lpCS ) )	{		return ( -1 );	}	else	{		// Bring Direct3D online		//		if ( FAILED( hRet = InitializeDirect3D( ) ) )		{			::MessageBox( NULL, "ERROR: Initialization failure with Direct3D!",								"Bean",								MB_OK | MB_ICONERROR );			return ( -1 );		}		else		{			m_bIsAppInited = TRUE;		}		// ...	}	return ( 0 );}void CDevWnd::OnActivateApp( BOOL bActiveStatus, DWORD dwThreadID ){	// Retrive applications' active status	//	m_bIsAppActive = bActiveStatus;}void CDevWnd::OnSize( UINT uType, int nCX, int nCY ){	D3DXMATRIX				 xProj;	// Only attempt this if the application has successfully been initialized	//	if ( m_bIsAppInited )	{		D3DXMatrixPerspectiveFovLH( &xProj, D3DX_PI / 4, ( ( FLOAT )nCX ) / nCY, 1.0F, 1000.0F );		m_lpD3DDevice->SetTransform( D3DTS_PROJECTION, &xProj );	}}BOOL CDevWnd::OnEraseBkgnd( CDC* pDC ){	return ( TRUE );}void CDevWnd::OnPaint( ){	// Only attempt this if the application has successfully been initialized	//	if ( m_bIsAppInited )	{		DrawScene( );	}}void CDevWnd::OnDestroy( ){	ReleaseDirect3D( );}


I know it’s not much, but it might give you some clues. If you want more extensive help, please email me at merdman5987@msn.com. I will be more the happy to give you more help.

I think I’m going to start adding that for a signature now.
Take back the internet with the most awsome browser around, FireFox

This topic is closed to new replies.

Advertisement