在DirectX程序中当设备工作在全屏模式下时, 程序用到的很多资源(纹理, 缓冲区等)都保存在一定的显存中, 当我们改变显示模式或切换到其他程序时, 显存可能会被其他程序占用造成原来的数据丢失, 函数可能仍然返回成功但是将无法渲染任何东西, 这种情况也叫做设备丢失. 通常D3D设备在创建成功后是处于可操作状态的, 这种状态下程序可以正常的渲染物体, 但是当窗口程序改变大小时或是全屏程序最小化时, 设备将转变到丢失状态, 丢失状态表现为所有渲染操作的悄然失败, 这意味着即使渲染操作失败所有的渲染方法仍可以返回成功码. 在这种情况下, IDirect3DDevice9::Present 返回错误码 D3DERR_DEVICELOST.

当然会造成设备丢失还有很多的情况, 如切换到其他程序等. 在很多程序中通过禁止ALT+TAB切换程序, 禁止使用Windows健等. 但这样并不能有效解决丢失设备的问题, 而且还会让玩家感觉很郁闷! 当设备丢失以后, 其实函数将可能会返回 D3DERR_DEVICELOST 告诉你设备已丢失, 也可能返回一个响应设备丢失的值, 或者仍返回S_OK, 这种情况也叫悄然失败, 程序不知道是否执行成功. 在D3D中大部分被频繁调用的方法都不会返回任何关于设备丢失的信息. 你可以继续调用那些函数, 但是在D3D内部这些操作将被抛弃, 直到设备被重置到可以操作的状态为止. 以下是一般情况下检测和处理丢失设备的代码:

//********************************************************************
//      Filename: Main.cpp
//        Author: Chinafish
//      Modifier: Chinafish
//       Created: 2008-5-13 20:52
//       Updated: 2008-5-13 20:52
//            QQ: 149200849
//           MSN: china_fish@msn.com
//       Purpose: Recover from a lost device.
//====================================================================
//  Copyright(C) 2004-2008 by Chinafish. All Rights Reserved.
//********************************************************************

#define STRICT
#define WIN32_LEAN_AND_MEAN
#include "windows.h"

// Link D3D
#include "d3d9.h"
#pragma comment(lib, "d3d9.lib")

#define Release_Safe(p){if(p!=NULL) p->Release();}

#define WINDOW_TITLE    "www.csinx.org - Lost Device (DX9+VC7)"
#define WINDOW_WIDTH	800
#define WINDOW_HEIGHT	600

//-----------------------------------------------------------------------------
// GLOBALS
//-----------------------------------------------------------------------------
HWND					g_hWnd			= NULL;
LPDIRECT3D9				g_pD3D			= NULL;
LPDIRECT3DDEVICE9		g_pd3dDevice	= NULL;
D3DPRESENT_PARAMETERS	g_d3dpp;
bool					g_bDeviceLost	= false;

//-----------------------------------------------------------------------------
// PROTOTYPES
//-----------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void ReInitD3D();
HRESULT RestoreDeviceObjects();
HRESULT InvalidateDeviceObjects();
void ShutDown(void);
void Render(void);

//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: The application's entry point
//-----------------------------------------------------------------------------
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
	WNDCLASSEX winClass;
	MSG        uMsg;
	memset(&uMsg,0,sizeof(uMsg));

	winClass.lpszClassName = "CLASS_LOSTDEVICE";
	winClass.cbSize        = sizeof(WNDCLASSEX);
	winClass.style         = CS_HREDRAW | CS_VREDRAW;
	winClass.lpfnWndProc   = WindowProc;
	winClass.hInstance     = hInstance;
	winClass.hIcon         = LoadIcon(hInstance, (LPCTSTR)NULL);
	winClass.hIconSm       = LoadIcon(hInstance, (LPCTSTR)NULL);
	winClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
	winClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	winClass.lpszMenuName  = NULL;
	winClass.cbClsExtra    = 0;
	winClass.cbWndExtra    = 0;

	if( !RegisterClassEx(&winClass) )
		return E_FAIL;

	g_hWnd = CreateWindowEx( NULL, "CLASS_LOSTDEVICE",
		WINDOW_TITLE,
		WS_EX_TOPMOST, 0, 0,
		WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL,
		hInstance, NULL );

	if( g_hWnd == NULL )
		return E_FAIL;

	// Adjust window
	RECT rect = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT };
	AdjustWindowRect( &rect, GetWindowLong( g_hWnd, GWL_STYLE ), FALSE );
	SetWindowPos( g_hWnd, 0, 0, 0, rect.right - rect.left, rect.bottom - rect.top,
		SWP_NOZORDER | SWP_NOMOVE );

	ShowWindow( g_hWnd, nCmdShow );
	UpdateWindow( g_hWnd );

	// Init or reset D3D
	ReInitD3D();

	while( uMsg.message != WM_QUIT )
	{
		if( PeekMessage( &uMsg, NULL, 0, 0, PM_REMOVE ) )
		{
			TranslateMessage( &uMsg );
			DispatchMessage( &uMsg );
		}
		else
		{
			// Render a frame
			Render();
		}
	}

	ShutDown();

	UnregisterClass( "CLASS_LOSTDEVICE", winClass.hInstance );

	return uMsg.wParam;
}

//-----------------------------------------------------------------------------
// Name: WindowProc()
// Desc: The window's message handler
//-----------------------------------------------------------------------------
LRESULT CALLBACK WindowProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch( msg )
	{
	case WM_KEYDOWN:
		{
			switch( wParam )
			{
			case VK_ESCAPE:
				PostQuitMessage(0);
				break;
			}
		}
		break;
	case WM_CLOSE:
		{
			PostQuitMessage(0);
		}
	case WM_DESTROY:
		{
			PostQuitMessage(0);
		}
		break;
	default:
	{
		return DefWindowProc( hWnd, msg, wParam, lParam );
	}
	break;
	}

	return 0;
}

//-----------------------------------------------------------------------------
// Name: ReInitD3D()
// Desc: This function will only be called once during the application's
//       initialization phase. Therefore, it can't contain any resources that
//       need to be restored every time the Direct3D device is lost or the
//       window is resized.
//-----------------------------------------------------------------------------
void ReInitD3D()
{
	// Create D3D
	g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	if(!g_pD3D)
	{
		MessageBox(NULL, "Direct3DCreate9() - Failed!", "Error", NULL);
		return;
	}

	D3DDISPLAYMODE d3ddm;
	g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm );

	ZeroMemory( &g_d3dpp, sizeof(g_d3dpp) );
	g_d3dpp.Windowed               = TRUE;
	g_d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;
	g_d3dpp.BackBufferFormat       = d3ddm.Format;
	g_d3dpp.EnableAutoDepthStencil = TRUE;
	g_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
	g_d3dpp.PresentationInterval   = D3DPRESENT_INTERVAL_IMMEDIATE;

	g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
		g_hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,
		&g_d3dpp, &g_pd3dDevice );

	if(!g_pd3dDevice)
	{
		return;
	}

	// Any resources or settings that need to be restored after losing the
	// DirectX device should probably be grouped together into one function so
	// they can be re-created or reset in one call.
	RestoreDeviceObjects();
}

//-----------------------------------------------------------------------------
// Name: RestoreDeviceObjects()
// Desc: You are encouraged to develop applications with a single code path to
//       respond to device loss. This code path is likely to be similar, if not
//       identical, to the code path taken to initialize the device at startup.
//-----------------------------------------------------------------------------
HRESULT RestoreDeviceObjects()
{
	// Set some important state settings...
	// Set Transform
	// Create a texture object, mesh object, buffer etc.
	return S_OK;
}

//-----------------------------------------------------------------------------
// Name: InvalidateDeviceObjects()
// Desc: If the lost device can be restored, the application prepares the
//       device by destroying all video-memory resources and any
//       swap chains. This is typically accomplished by using the SAFE_RELEASE
//       macro.
//-----------------------------------------------------------------------------
HRESULT InvalidateDeviceObjects()
{
	// Invalidate the texture object, mesh object, buffer etc.

	return S_OK;
}

//-----------------------------------------------------------------------------
// Name: ShutDown()
// Desc: Release all
//-----------------------------------------------------------------------------
void ShutDown( void )
{
	InvalidateDeviceObjects();

	Release_Safe(g_pd3dDevice);
	Release_Safe(g_pD3D);
}

//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Render something you want
//-----------------------------------------------------------------------------
void Render( void )
{
	HRESULT hr;
	if( g_bDeviceLost == true )
	{
		// Yield some CPU time to other processes
		Sleep( 100 ); // 100 milliseconds

		// Check for lost device
		hr = g_pd3dDevice->TestCooperativeLevel();
		if( FAILED( hr ) )
		{
			// The device has been lost but cannot be reset at this time.
			// Therefore, rendering is not possible and we'll have to return
			// and try again at a later time.
			if( hr == D3DERR_DEVICELOST )
				return;

			// The device has been lost but it can be reset at this time.
			if( hr == D3DERR_DEVICENOTRESET )
			{
				// If the device can be restored, the application prepares the
				// device by destroying all video-memory resources and any swap chains.
				InvalidateDeviceObjects();

				// Reset is the only method that has an effect when a device
				// is lost, and is the only method by which an application can
				// change the device from a lost to an operational state.
				// Reset will fail unless the application releases all
				// resources that are allocated in D3DPOOL_DEFAULT, including
				// those created by the IDirect3DDevice9::CreateRenderTarget
				// and IDirect3DDevice9::CreateDepthStencilSurface methods.
				hr = g_pd3dDevice->Reset( &g_d3dpp );

				if( FAILED(hr ) )
					return;

				//
				// Finally, a lost device must re-create resources (including
				// video memory resources) after it has been reset.
				//
				RestoreDeviceObjects();
			}

			return;
		}

		g_bDeviceLost = false;
	}

	// Clear screen with D3DCOLOR_XRGB(255, 0, 255)
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 0, 255), 1.0f, 0);

	g_pd3dDevice->BeginScene();

	// Render geometry here...

	g_pd3dDevice->EndScene();

	//
	// If Present fails with D3DERR_DEVICELOST the application needs to be
	// notified so it cleanup resources and reset the device.
	//

	hr = g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
	if( hr == D3DERR_DEVICELOST )
	{
		g_bDeviceLost = true;
	}
}
				

在以代码中我们定义恢复和销毁程序的函数, 只是在 IDirect3DDevice9::Present 返回错误码 D3DERR_DEVICELOST 时才去检测和恢复丢失的设备及重建程序资源. 我们可以通过 IDirect3DDevice9::TestCooperativeLevel 的返回值来确定是否设备已丢失, 如果设备已丢失并且无法重置, 我们就要等待设备可以重置时, 销毁所有已调用的显存资源和交换链. 然后再调用 IDirect3DDevice9::Reset 方法, 这个方法也是设备丢失以后唯一可以有效的方法, 并且也是用于把丢失的设备重置到可操作状态的唯一方法. 要注意的是你必须释放所有使用 D3DPOOL_DEFAULT 创建的资源, 包括使用 IDirect3DDevice9::CreateRenderTargetIDirect3DDevice9::CreateDepthStencilSurface 方法创建的资源以后才可能成功的重置设备. 最后你必须重新创建所用到的资源包括顶点, 纹理, 状态和变换等. 另外在设备丢失以后除 IDirect3DDevice9::Reset 外, IDirect3DDevice9::ValidateDevice 方法也可以对照单次渲染用硬件验证纹理和渲染状态, 返回D3DERR_DEVICELOST. 当设备丢失时,因为不存在主表面,所以复制操作IDirect3DDevice9::GetFrontBufferData 会失败,返回值为D3DERR_DEVICELOST. 当设备丢失时, IDirect3DDevice9::CreateAdditionalSwapChain 也会因为无法创建后缓存而失败,并返回D3DERR_DEVICELOST. 当设备丢失时, IDirect3DDevice9::Present 因为无法渲染而失败, 返回D3DERR_DEVICELOST.

[返回目录][我有话说]