Jump to content
  • Advertisement
Sign in to follow this  
Zedix

Simple question: Managing alt-tabbing

This topic is 4833 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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

Share this post


Link to post
Share on other sites
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;
}

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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;

Share this post


Link to post
Share on other sites
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. :)

Share this post


Link to post
Share on other sites
It really is deceptivly simple, but handling lost devices isn't the most documented thing out there.

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!