Navigation: DirectX 9.0

Tutorial 2 - Direct3D Error Handling


Overview

This tutorial is about making a DirectX Application survive a device lost scenario. It continues with the program that was created in the previous tutorial.

Device States

A Direct3D Device can be in one of three states, "Lost", "Operational" and "Not reset". I have defined two constants to reflect this. These will be used to control the flow of the error handling system and that is why I have named them as follows. You can put these at the top of the main.cpp file.
#define DEVICE_LOST_OR_OPERATIONAL 0
#define DEVICE_NOTRESET 1

Modifications to the Game class

There are three basic modifications that could prove useful for error handling. I have added an OnDeviceGained() function, an OnDeviceLost() function and a variable to hold the status of the d3d device (See changes in bold):

class Game
{
public:
	~Game();
	bool InitGame(HWND render_window);
	void UpdateGame(float deltaTime);
	void RenderWorld();

private:
	bool InitializeDirect3D();
	void FreeAllResources();

	void OnDeviceGained();
	void OnDeviceLost();


private:
	//Member Variables have m_ prefix
	HWND m_mainWindow;
	IDirect3DDevice9* m_d3dDevice;
	D3DPRESENT_PARAMETERS m_present_parameters;
	int m_deviceStatus;

private:
	//Helper Functions.
	D3DPRESENT_PARAMETERS CreatePresentParameters(HWND render_window);
};

I set the initial status of the device in the Game::InitializeDirect3D() function since it is related to Direct3D:

bool Game::InitializeDirect3D()
{
	...

	//We no longer need the direct3d object
	SAFE_RELEASE(d3d9);

	m_deviceStatus = DEVICE_LOST_OR_OPERATIONAL;

	//Success.
	return true;
}

The two functions are implemented as follows:
void Game::OnDeviceLost()
{
	try
	{
		//Add OnDeviceLost() calls for DirectX COM objects
		m_deviceStatus = DEVICE_NOTRESET;
	}
	catch(...)
	{
		//Catch any errors.
	}
}

void Game::OnDeviceGained()
{
	try
	{
		m_d3dDevice->Reset(&m_present_parameters);

		//Add OnResetDevice() calls for DirectX COM objects

		m_deviceStatus = DEVICE_LOST_OR_OPERATIONAL;
	}
	catch(...)
	{
		//Catch any errors.
	}
}

That's all there is to those functions for now. We will add to them a little bit when we create an ID3DXEffect*. Now we need to check the cooperative level of the Direct3D Device. The cooperative level tells us what state the device is in and since it needs to be checked regularly the Update() function is a good place to check it. We check the cooperative level regularly so that we know what state the device is in at any point in time.

Modify the Update() function as follows:
void Game::UpdateGame(float deltaTime)
{
	//Handle device lost event.
	HRESULT coop = m_d3dDevice->TestCooperativeLevel();

	if(coop != D3D_OK)
	{
		if(coop == D3DERR_DEVICELOST)
		{
			if(m_deviceStatus == DEVICE_LOST_OR_OPERATIONAL)
				OnDeviceLost();		
		}
		else if(coop == D3DERR_DEVICENOTRESET)
		{
			if(m_deviceStatus == DEVICE_NOTRESET)
				OnDeviceGained();
		}
	}

	//Update game objects here.
}

Since the device could be in the D3DERR_DEVICELOST state for quite some time, we add the check "if(m_deviceStatus == DEVICE_LOST_OR_OPERATIONAL)" so that OnDeviceLost() is only called once in the event of D3DERR_DEVICELOST. It is only called once because OnDeviceLost() sets our m_deviceStatus variable to DEVICE_NOTRESET and we add the check "if(m_deviceStatus == DEVICE_LOST_OR_OPERATIONAL)". We only want it to be called once while the device is in a lost state because the DirectX objects in the OnDeviceLost() function should call ->OnDeviceLost() and we don't want them to keep calling ->OnDeviceLost().

After a while the device will enter a D3DERR_DEVICENOTRESET state. Game::OnDeviceGained() will then be called and reset the device using the present parameters we defined earlier. Our m_deviceStatus will then be set to the DEVICE_LOST_OR_OPERATIONAL state.

The last thing we need to do is check our m_deviceStatus status in our Game::RenderWorld() function. If Game::OnDeviceLost() was called, then the m_deviceStatus status will never be in the DEVICE_LOST_OR_OPERATIONAL state until Game::OnDeviceGained() is called. Therefore we just need to check if the status is DEVICE_LOST_OR_OPERATIONAL as follows:

void Game::RenderWorld()
{
	if(m_deviceStatus == DEVICE_LOST_OR_OPERATIONAL)
	{
		//TODO: Add Device lost handling.
		m_d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff0000ff, 1.0f, 0);

		if(SUCCEEDED(m_d3dDevice->BeginScene()))
		{

			m_d3dDevice->EndScene();
		}

		// Swap buffers.
		m_d3dDevice->Present(NULL, NULL, NULL, NULL);
	}
}

Putting it All Together

Here is the Application that we have covered in this tutorial:

Macros.h
#pragma once

#ifndef SAFE_RELEASE
#define SAFE_RELEASE(x) \
   if(x != NULL)        \
   {                    \
      x->Release();     \
      x = NULL;         \
   }
#endif


main.cpp
#include "Macros.h"
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>

#define DEVICE_LOST_OR_OPERATIONAL 0
#define DEVICE_NOTRESET 1

class Game
{
public:
	~Game();
	bool InitGame(HWND render_window);
	void UpdateGame(float deltaTime);
	void RenderWorld();

private:
	bool InitializeDirect3D();
	void FreeAllResources();
	void OnDeviceGained();
	void OnDeviceLost();

private:
	//Member Variables have m_ prefix
	HWND m_mainWindow;
	IDirect3DDevice9* m_d3dDevice;
	D3DPRESENT_PARAMETERS m_present_parameters;
	int m_deviceStatus;

private:
	//Helper Functions.
	D3DPRESENT_PARAMETERS CreatePresentParameters(HWND render_window);
};

bool Game::InitGame(HWND render_window)
{
	this->m_mainWindow = render_window;

	return InitializeDirect3D();
}

void Game::UpdateGame(float deltaTime)
{
	//Handle device lost event.
	HRESULT coop = m_d3dDevice->TestCooperativeLevel();

	if(coop != D3D_OK)
	{
		if(coop == D3DERR_DEVICELOST)
		{
			if(m_deviceStatus == DEVICE_LOST_OR_OPERATIONAL)
				OnDeviceLost();		
		}
		else if(coop == D3DERR_DEVICENOTRESET)
		{
			if(m_deviceStatus == DEVICE_NOTRESET)
				OnDeviceGained();
		}
	}

	//Update game objects here.
}

void Game::OnDeviceLost()
{
	try
	{
		//Add OnDeviceLost() calls for DirectX COM objects
		//g_pEffect->OnLostDevice();
		m_deviceStatus = DEVICE_NOTRESET;
	}
	catch(...)
	{
		//Error handling code
	}
}

void Game::OnDeviceGained()
{
	try
	{
		m_d3dDevice->Reset(&m_present_parameters);
		//Add OnResetDevice() calls for DirectX COM objects
		//g_pEffect->OnResetDevice();
		m_deviceStatus = DEVICE_LOST_OR_OPERATIONAL;
	}
	catch(...)
	{
		//Error handling code
	}
}

void Game::RenderWorld()
{
	if(m_deviceStatus == DEVICE_LOST_OR_OPERATIONAL)
	{
		//TODO: Add Device lost handling.
		m_d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff0000ff, 1.0f, 0);

		if(SUCCEEDED(m_d3dDevice->BeginScene()))
		{

			m_d3dDevice->EndScene();
		}

		// Swap buffers.
		m_d3dDevice->Present(NULL, NULL, NULL, NULL);
	}
}

void Game::FreeAllResources()
{
	SAFE_RELEASE(m_d3dDevice);
}

Game::~Game()
{
	//Make sure all resources are freed when game is destroyed.
	FreeAllResources();
}

//Create function prototypes.
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

D3DPRESENT_PARAMETERS Game::CreatePresentParameters(HWND render_window)
{
	D3DPRESENT_PARAMETERS pp;

	memset(&pp, 0, sizeof(D3DPRESENT_PARAMETERS));
	pp.BackBufferWidth = 800;
	pp.BackBufferHeight = 600;
	pp.BackBufferFormat = D3DFMT_A8R8G8B8;
	pp.BackBufferCount = 1;
	pp.MultiSampleType = D3DMULTISAMPLE_NONE;
	pp.MultiSampleQuality = 0;
	pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	pp.hDeviceWindow = render_window;
	pp.Windowed = true;
	pp.EnableAutoDepthStencil = true;
	pp.AutoDepthStencilFormat = D3DFMT_D24S8;
	pp.Flags = 0;
	pp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

	return pp;
}

bool Game::InitializeDirect3D()
{
	//Create the Direct3D object
	IDirect3D9* d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

	if(d3d9 == NULL)
	{
		return false;
	}

	m_present_parameters = CreatePresentParameters(m_mainWindow);

	if(FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT,
			     D3DDEVTYPE_HAL,
			     m_mainWindow,
			     D3DCREATE_HARDWARE_VERTEXPROCESSING,
			     &m_present_parameters,
			     &m_d3dDevice)))
	{
		return false;
	}

	//We no longer need the direct3d object
	SAFE_RELEASE(d3d9);

	m_deviceStatus = DEVICE_LOST_OR_OPERATIONAL;

	//Success.
	return true;
}

HWND CreateMyWindow(HINSTANCE hInstance)
{
	WNDCLASS wc;
	memset(&wc, 0, sizeof(WNDCLASS));
	
	wc.style=CS_HREDRAW | CS_VREDRAW; //Redraws the window if width or height changes.
	wc.hbrBackground = CreateSolidBrush(RGB(0,0,0));
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.lpfnWndProc=(WNDPROC)WndProc; //Associates the window procedure with the window.
	wc.hInstance=hInstance; //Record handle to this application's instance.
	wc.lpszClassName=L"My Window Class"; //Class Name.

	//Register the window class with the operating system.
	if(FAILED(RegisterClass(&wc))){
		//Failed to create window.
		return NULL;
	}

	//Create the Window.
	HWND my_window = CreateWindow(L"My Window Class", //The window class to use
			      L"Basic Window", //window title
				  WS_OVERLAPPEDWINDOW, //window style
			      200, //x
			      200, //y
			      CW_USEDEFAULT, //Default width
			      CW_USEDEFAULT, //Default height
				  NULL,
				  NULL,
			      hInstance, //Application instance
			      0); //Pointer to value parameter, lParam of WndProc

	//If window was created successfully then non-NULL
	//value will be returned.
	return my_window;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR cmdLine, int showCmd)
{
	HWND my_window = CreateMyWindow(hInstance);

	if(!my_window){return E_FAIL;}

	//Window has been created, now create an instance of the game.
	Game game;
	//Initialize the game.
	if(!game.InitGame(my_window))
	{
		//Failed to setup the game.
		return E_FAIL;
	}

	ShowWindow(my_window, SW_SHOW);
	UpdateWindow(my_window);

	MSG msg;
	memset(&msg, 0, sizeof(MSG));

	DWORD startTime = GetTickCount();
	float deltaTime = 0.0f;

	//Enter the message loop.
	while(msg.message != WM_QUIT)
	{
		if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			//Put the message in a recognizable form
			TranslateMessage(&msg);
			//Send the message to the window procedure
			DispatchMessage(&msg);
		}
		else{

			//Update the game
			DWORD t=GetTickCount();
			deltaTime=float(t-startTime);
			//Pass time in seconds
			game.UpdateGame(deltaTime);
			startTime = t;

			//Render the world
			game.RenderWorld();
		}
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	//Handle window creation and destruction events
	switch(msg)
	{
	case WM_CREATE:
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	}

	//Handle all other events with default procedure
	return DefWindowProc(hWnd, msg, wParam, lParam);
}