在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::CreateRenderTarget 和 IDirect3DDevice9::CreateDepthStencilSurface 方法创建的资源以后才可能成功的重置设备. 最后你必须重新创建所用到的资源包括顶点, 纹理, 状态和变换等. 另外在设备丢失以后除 IDirect3DDevice9::Reset 外, IDirect3DDevice9::ValidateDevice 方法也可以对照单次渲染用硬件验证纹理和渲染状态, 返回D3DERR_DEVICELOST. 当设备丢失时,因为不存在主表面,所以复制操作IDirect3DDevice9::GetFrontBufferData 会失败,返回值为D3DERR_DEVICELOST. 当设备丢失时, IDirect3DDevice9::CreateAdditionalSwapChain 也会因为无法创建后缓存而失败,并返回D3DERR_DEVICELOST. 当设备丢失时, IDirect3DDevice9::Present 因为无法渲染而失败, 返回D3DERR_DEVICELOST.