DirectX 9.0c: Handling "Lost Device"?

Started by
14 comments, last by 21st Century Moose 10 years, 5 months ago

Hi,

What is the best method to handle a "lost device" in DirectX 9.0c?

Please look at the source code below and tell me if I am doing it correctly or not.

I "Reset" the DirectX device and reload the DirectX Sprites...

Thanks!

JeZ+Lee

Sprite Loading Function:


bool Visuals::LoadSpritesIntoMemoryAndInitialize(bool DeviceReset)
{
HRESULT result;
char filePath[256];

    strcpy_s(filePath, "~\0");

    for (int index = 0; index < NumberOfSprites; index++)
    {
        switch(index)
        {
            case 0:
                strcpy_s(filePath, "Data/Visuals/Screen-Fade-Black-Box.png");
                break;

            case 5:
                strcpy_s(filePath, "Data/Visuals/16BitSoft-Logo.png");
                break;

            case 6:
                strcpy_s(filePath, "Data/Visuals/Title-BG.png");
                break;

            case 7:
                strcpy_s(filePath, "Data/Visuals/TC5-Logo.png");
                break;

            default:
                strcpy_s(filePath, "~");
                break;
        }

        strcat_s(filePath, "\0");

        if (filePath[0] != '~')
        {
            D3DXIMAGE_INFO imageInfo;
            result = D3DXGetImageInfoFromFileA(filePath, &imageInfo);
//            if FAILED (hResult){
//            return false;
//            }

            D3DXCreateTextureFromFileExA(DXDevice, filePath,  imageInfo.Width, imageInfo.Height, D3DX_DEFAULT, 0, D3DFMT_UNKNOWN,
                                         D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, &Sprites[index].Texture);

            result = D3DXCreateSprite(DXDevice, &Sprites[index].DXSprite);

            if (DeviceReset == false)
            {
                Sprites[index].ScreenX = 400.0f;
                Sprites[index].ScreenY = 240.0f;
                Sprites[index].ScaleX = 1.0f;
                Sprites[index].ScaleY = 1.0f;
                Sprites[index].RotationDegree = 0.0f;
                Sprites[index].RedHue = 255;
                Sprites[index].GreenHue = 255;
                Sprites[index].BlueHue = 255;
                Sprites[index].Transparency = 255;
                Sprites[index].Smooth = true;
                Sprites[index].FlipX = false;
                Sprites[index].FlipY = false;
                Sprites[index].OriginalWidth = imageInfo.Width;
                Sprites[index].OriginalHeight = imageInfo.Height;

                D3DSURFACE_DESC textureInfo;
                Sprites[index].Texture->GetLevelDesc(0, &textureInfo);
                Sprites[index].TextureWidth = textureInfo.Width;
                Sprites[index].TextureHeight = textureInfo.Height;

                Sprites[index].AnimationTimer = -1.0f;
            }
        }
    }

    return(true);
}

Screen Render Function:


void Visuals::DisplayScreenBufferOntoDisplay(void)
{
HRESULT result;

    result = DXDevice->Present( NULL, NULL, NULL, NULL );

    if (result != D3D_OK)
    {
        D3DDISPLAYMODE d3ddm;
        DXD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm );
        D3DPRESENT_PARAMETERS d3dpp;
        ZeroMemory( &d3dpp, sizeof(d3dpp) );
        d3dpp.Windowed               = TRUE;
        d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;
        d3dpp.BackBufferFormat       = d3ddm.Format;
        d3dpp.EnableAutoDepthStencil = TRUE;
        d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
        d3dpp.PresentationInterval   = D3DPRESENT_INTERVAL_IMMEDIATE;
        DXDevice->Reset(&d3dpp);

        for (int index = 0; index < NumberOfSprites; index++)
        {
            if (Sprites[index].DXSprite != NULL)  Sprites[index].DXSprite->Release();
            if (Sprites[index].Texture != NULL)  Sprites[index].Texture->Release();
        }

        LoadSpritesIntoMemoryAndInitialize(true);
    }
}

JeZxLee
Fallen Angel Software
www.FallenAngelSoftware.com

Advertisement

"if (result != D3D_OK)" is certainly wrong; Present returns a D3DERR_DEVICELOST which you should be checking instead.

If you're using ID3DXSprite (instead of your own sprite class) there's no need to fully release and recreate the sprites; ID3DXSprite has OnLostDevice and OnResetDevice members which you should use instead.

Releasing default pool resources (and calling OnLostDevice for D3DX objects that have it) should be done before resetting the device, not after. The correct order is release/Reset/recreate.

Otherwise your code will work fine for transient conditions which may cause the device to become lost during regular gameplay, but these kind of conditions almost never happen. It won't work too well for Alt-Tabbing away, because you're Resetting the device as soon as it becomes lost. Best case is that the Reset will succeed but the device will become lost again in the very next frame. More likely is that the Reset will fail, object recreation will fail, and the only way out will be by forcibly terminating your program.

What you need to do instead is call IDirect3DDevice9::TestCooperativeLevel each frame after the device becomes lost. That will return one of the following HRESULT values:

D3DERR_DEVICELOST: the device is still lost so you shouldn't draw the frame; just run your message loop and Sleep for a bit.

D3DERR_DEVICENOTRESET: the device is ready to be Reset; destroy all default pool resources, call OnLostDevice for any D3DX objects that have it, then issue your Reset call. Skip the rest of the frame, just run your message loop.

D3D_OK: the Reset has completed and the device is now operational again. Recreate any default pool resources, call OnResetDevice for any D3DX objects that have it, and begin running frames normally again.

Anything else: this is most likely a device error so terminating the program seems the best option.

You're going to need similar code for display mode changing so you should design around that requirement.

Resist the temptation to do lost device handling in WM_ACTIVATE messages - that has the opposite problem in that it will only work for Alt-Tabbing, and won't work at all for any of the other events that can cause a lost device. Checking the HRESULT from your Present call is the correct way to do it, and you're on the right track in that you're doing that, so stay on the right track and you'll do good.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

mhagain has explained everything perfectly (at least to my knowledge).

Just 2 remarks:

It is almost always wrong to compare an HRESULT to a specific success code like D3D_OK. Please use the SUCCEEDED and FAILED macros instead.

The best way to wait for the next attempt to reset is IMHO MsgWaitForMultipleObjects, like this:


DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, 10, QS_ALLINPUT);

It is almost always wrong to compare an HRESULT to a specific success code like D3D_OK. Please use the SUCCEEDED and FAILED macros instead.

This is actually one of those cases where you do want to check for specific HRESULTS; consider it an exception to the general rule. You definitely want to act on D3DERR_DEVICELOST (and only D3DERR_DEVICELOST) from your Present call, and you can't implement the TestCooperativeLevel recovery without checking for specific values.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

This is what I use:


hr = device->Present(NULL, NULL, NULL, NULL);

	if (hr == D3DERR_DEVICELOST){
		LostDevice = true;
	}

if(LostDevice){
		RecoverDevice();
	} else {
		Render();
	}

RecoverDevice()
{
	HRESULT hr = NULL;

	hr = device->TestCooperativeLevel();
	switch (hr)
	{
	case D3D_OK:
		// device is no longer lost and reset has completed successfully; recover resources here
		LostDevice = false;
		return;  // note: this assumes that this test is the last thing in the function it's being called from
	case D3DERR_DEVICELOST:
		Sleep(5);
		// device is still lost; can't do anything so do nothing
		break;
	case D3DERR_DEVICENOTRESET:
		// device is ready to be reset; Release resources here and then Reset it
		// (be sure to test the hr from your Reset call too!)
		//ReleaseResources();
		ResetDevice();
		break;
	default:
		MessageBox(NULL, L"TestCooperativeLevel() failed", L"Error", MB_OK);
		PostQuitMessage(0);
		return;
	}
}


You definitely want to act on D3DERR_DEVICELOST

Yes, of course. This is why I wrote "don't compare with specific success codes" (... to check for success). You definetely need to handle specific failure codes sometimes.

But I don't want to be dogmatic; this starts getting of topic.

My experience/ what I do is take care of this in my main message handler, for example to handle wm_size messages, wm_... etc, which likely cause lost devices.

A few things nice to know:
- directinput devices can also get lost
- not applicable when in windowed mode
- use "OnLostDevice" and after resetting "OnResetDevice" for your resources, I believe freeing recources goes for everything in the d3d default pool, resources in the managed pool should be find
- always check if the reset was succesfull, I only quit my "switch" statement when it's succeeded

If you want I can post my handling code from my "winproc" message handler function.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

My experience/ what I do is take care of this in my main message handler, for example to handle wm_size messages, wm_... etc, which likely cause lost devices.

This is definitely the wrong way to do it; see http://msdn.microsoft.com/en-us/library/windows/desktop/bb174714%28v=vs.85%29.aspx

By design, the full set of scenarios that can cause a device to become lost is not specified. Some typical examples include loss of focus, such as when the user presses ALT+TAB or when a system dialog is initialized. Devices can also be lost due to a power management event, or when another application assumes full-screen operation. In addition, any failure from IDirect3DDevice9::Reset puts the device into a lost state.

(My emphasis).

If you're not checking for D3DERR_DEVICELOST from your Present call, you're going to miss some of those scenarios.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

Hi,

Thanks for all the replies and information...

I don't think I will be implementing a full screen option.

(just current maximize window button option)

So the DirectX device can never get lost in windowed mode?

Let me know, thanks!

JeZ+Lee

JeZxLee
Fallen Angel Software
www.FallenAngelSoftware.com

Normally no, but it's not that hard to implement it. I posted basically all the code you need.

This topic is closed to new replies.

Advertisement