D3D9中的设备丢失

一、设备状态

Direct3D 中的设备可处于操作状态丢失状态操作状态是设备的正常状态,设备按预期运行并present所有渲染结果。当某些事件发生时,如全屏应用程序失去键盘输入焦点,设备就转变到丢失状态,这会导致渲染无法进行。丢失状态表现为所有渲染操作的悄然失败,这意味着即使渲染操作失败所有的渲染方法仍可以返回成功码。在这种情况下,IDirect3DDevice9::Present返回错误码D3DERR_DEVICELOST。

Direct3D没有对可能导致设备丢失的所有情况进行详细说明。一些典型的例子包括窗口失去焦点,例如用户按下了ALT+TAB或弹出了一个系统对话框。设备也会因为电源管理事件或者另一个应用程序进行全屏操作而丢失。另外,任何对IDirect3DDevice9::Reset调用的失败会把设备置为丢失状态。

注意   可以保证所有继承自IUnknown的方法在设备丢失后仍能正常工作。

设备丢失后,每个函数一般有三种可能:

  • 调用失败,返回值为D3DERR_DEVICELOST – 这意味着应用程序能发现设备已经丢失,从而知道一些事情没有按照预期进行。 
  • 悄然失败,返回值为S_OK或其它值 – 若函数调用悄然失败,则应用程序一般无法区分出“调用成功”或“悄然失败”。 
  • 函数返回一个返回值。 

二、响应设备丢失

设备在被重置后,应该重新创建资源(包括显存资源)。如果设备丢失了,那么应用程序应该查询设备状态,看是否可以将之恢复回操作状态。如果不行,那么就应等待直到设备可以被恢复为止。如果设备可以被恢复,那么应用程序应该销毁所有显存资源和交换链,并准备恢复。然后,应用程序调用IDirect3DDevice9::Reset方法。Reset方法是当设备丢失时唯一有效的方法,并且是应用程序可用来把设备从丢失状态恢复到操作状态的唯一方法。除非应用程序释放所有在D3DPOOL_DEFAULT资源池中分配的资源,包括用IDirect3DDevice9::CreateRenderTarget和IDirect3DDevice9::CreateDepthStencilSurface方法创建的资源,否则Reset将会失败。

Direct3D中大部分被频繁调用的方法不返回任何关于设备是否已丢失的信息。应用程序可以继续调用渲染方法,如IDirect3DDevice9::DrawPrimitive,而不会收到设备丢失的通知。在Direct3D内部,这些操作被抛弃,直到设备被重置为操作状态为止。

通过查询IDirect3DDevice9::TestCooperativeLevel方法的返回值,应用程序可以决定在遇到设备丢失时如何处理。

三、管理资源

资源管理是将资源从系统内存提升到设备可访问存储器及从设备可访问存储器中抛弃的过程。 Direct3D运行库有自己的基于最近最少使用(least-recently-used)优先级技术的管理算法。当Direct3D检测到在一帧中——在IDirect3DDevice9::BeginScene和IDirect3DDevice9::EndScene调用之间——设备可访问内存无法同时存储所有资源时,它就切换到最近最多使用(most-recently-used)优先级技术。

在创建时使用D3DPOOL_MANAGED标志指定一个由系统管理的资源。由系统管理的资源在设备的丢失状态和操作状态间的转换中持续存在。通过调用IDirect3DDevice9::Reset设备可以被重置,并且这类资源可以继续正常运作而无需重新载入图片。但是,如果设备必须被销毁和重建,那么所有用D3DPOOL_MANAGED创建的资源也必须被重建

在创建时使用D3DPOOL_DEFAULT标志指定把资源放在默认的池中。在默认的池中的资源在设备从丢失状态到操作状态的转换过程中不持续存在,这些资源必须在调用Reset之前释放,然后重建。

更多有关设备的丢失状态的信息,请参阅丢失的设备。

注意不是所有的类型和用途都支持资源管理。例如,用D3DUSAGE_RENDERTARGET标志创建的对象不支持资源管理。另外,不建议对需要频繁改变其内容的对象使用资源管理。例如,在某些硬件上对一个每帧都需改变的顶点缓存进行自动管理会严重降低性能。但是,对纹理资源来说这不是一个问题。

#include <Windows.h>
#include <d3d9.h>
#include <d3dx9.h>

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

IDirect3DDevice9* g_pd3dDevice;
IDirect3DTexture9* g_pTexture;
IDirect3DVertexBuffer9* g_pVertexBuffer;
ID3DXMesh* g_pTeapotMesh;
D3DMATERIAL9 g_teapotMtrl;
D3DMATERIAL9 g_quadMtrl;
D3DLIGHT9 g_pLight0;

bool g_bDeviceLost = false;
bool g_bHandleLostDevice = true;
D3DPRESENT_PARAMETERS g_d3dpp;
float g_fSpinX = 0.5;
float g_fSpinY = 0.2;

struct QuadVertex
{
	float x, y, z;
	static DWORD FVF_Flags;
};

QuadVertex g_quadVertices[20];

//-----------------------------------------------------------------------------
// 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(void)
{
	// To see how mismanagement of an object''s reference counter can cause us
	// problems when calling Reset on the device, uncomment the line below.
	// The line below will call AddRef() on the vertex buffer object, which w
	// ill add one to the vertex buffer''s reference count. This will cause it
	// to hang around after we call Release() on it, which is not what we 
	// wanted to happen here.
	//g_pVertexBuffer->AddRef();

	// NOTE: You could use the SAFE_RELEASE macro to invalidate your device 
	//       objects like so:
	SAFE_RELEASE(g_pTexture);
	SAFE_RELEASE(g_pVertexBuffer);
	SAFE_RELEASE(g_pTeapotMesh);

	return S_OK;
}
//-----------------------------------------------------------------------------
// 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(void)
{
	// Set some important state settings...
	D3DXMATRIX matProj;
	D3DXMatrixPerspectiveFovLH(&matProj, D3DXToRadian(45.0f),
		640.0f / 480.0f,
		//(float)(g_d3dpp.BackBufferWidth / g_d3dpp.BackBufferHeight), 
		0.1f, 100.0f);
	g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);

	g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
	g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
	g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, TRUE);

	g_pd3dDevice->SetLight(0, &g_pLight0);
	g_pd3dDevice->LightEnable(0, TRUE);

	g_pd3dDevice->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_COLORVALUE(0.2f, 0.2f, 0.2f, 1.0f));

	// Create a texture object	
	D3DXCreateTextureFromFile(g_pd3dDevice, L"test.bmp", &g_pTexture);

	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

	// NOTE: When a device is lost, vertex buffers created using  
	// D3DPOOL_DEFAULT must be released properly before calling 
	// IDirect3DDevice9::Reset.
	g_pd3dDevice->CreateVertexBuffer(4 * sizeof(QuadVertex),
		D3DUSAGE_WRITEONLY,
		QuadVertex::FVF_Flags,
		//D3DPOOL_MANAGED, // Does not have to be properly Released before calling IDirect3DDevice9::Reset
		D3DPOOL_DEFAULT,   // Must be Released properly before calling IDirect3DDevice9::Reset
		&g_pVertexBuffer, NULL);
	void* pVertices = NULL;

	g_pVertexBuffer->Lock(0, sizeof(g_quadVertices), (void**)&pVertices, 0);
	memcpy(pVertices, g_quadVertices, sizeof(g_quadVertices));
	g_pVertexBuffer->Unlock();

	// NOTE: When a device is lost, meshes created using D3DXMESH_DYNAMIC 
	// must be released properly before calling IDirect3DDevice9::Reset.

	D3DXLoadMeshFromX(L"teapot.x",
		//D3DXMESH_SYSTEMMEM, // Does not have to be properly Released before calling IDirect3DDevice9::Reset
		//D3DXMESH_MANAGED,   // Does not have to be properly Released before calling IDirect3DDevice9::Reset
		//D3DXMESH_WRITEONLY, // Does not have to be properly Released before calling IDirect3DDevice9::Reset
		D3DXMESH_DYNAMIC,     // Must be Released properly before calling IDirect3DDevice9::Reset
		g_pd3dDevice,
		NULL, NULL, NULL, NULL, &g_pTeapotMesh);
	return S_OK;
}

void render(void)
{
	// Before we render, we need to make sure we haven''t lost our device.
	// If we have, we''ll need to restore it before we can continue.
	HRESULT hr;
	if (g_bDeviceLost == true)
	{
		// Yield some CPU time to other processes
		Sleep(100); // 100 milliseconds
		// Test the cooperative level to see if it''s okay to render.
		// The application can determine what to do on encountering a lost 
		// device by querying the return value of the TestCooperativeLevel 
		// method.

		if (FAILED(hr = g_pd3dDevice->TestCooperativeLevel()))
		{
			// 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();

				// Then, the application calls the Reset method.
				//
				// 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;
	}
	// Render a teapot and textured quad...	
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
		D3DCOLOR_COLORVALUE(0.35f, 0.53f, 0.7f, 1.0f), 1.0f, 0);
	g_pd3dDevice->BeginScene();
	{
		D3DXMATRIX matView;
		D3DXMATRIX matWorld;
		D3DXMATRIX matRotation;
		D3DXMATRIX matTranslation;

		D3DXMatrixIdentity(&matView);
		g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);

		// Place and render first teapot...
		D3DXMatrixRotationYawPitchRoll(&matRotation, D3DXToRadian(g_fSpinX), D3DXToRadian(g_fSpinY), 0.0f);
		D3DXMatrixTranslation(&matTranslation, 1.5f, 0.0f, 6.0f);
		matWorld = matRotation * matTranslation;
		g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);

		g_pd3dDevice->SetMaterial(&g_teapotMtrl);
		g_pTeapotMesh->DrawSubset(0);

		// Place and render textured quad...
		D3DXMatrixTranslation(&matTranslation, -1.5f, 0.0f, 6.0f);
		matWorld = matRotation * matTranslation;
		g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);

		g_pd3dDevice->SetMaterial(&g_quadMtrl);
		g_pd3dDevice->SetTexture(0, g_pTexture);
		g_pd3dDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(QuadVertex));
		g_pd3dDevice->SetFVF(QuadVertex::FVF_Flags);
		g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
	}
	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 (g_bHandleLostDevice == true)
	{
		if (hr == D3DERR_DEVICELOST)
			g_bDeviceLost = true;
	}
}

四、处理设备丢失

  设备丢失后,所有的操作几乎都会失效(其实D3D会保证有部分操作可以成功,但是也仅仅是“可以”成功而不是“一定”成功,所以你还不如认定丢失的时候全都会失败比较好),只有Release()、Reset()以及IDirect3DDevice9::TestCooperativeLevel 可以用。因此在设备丢失之后,你应该停止整个游戏循环,而通过反复调用IDirect3DDevice9::TestCooperativeLevel判断设备是否可用。

IDirect3DDevice9::TestCooperativeLevel   这个方法检测当前的设备状态。返回值有四种:

  • D3D_OK一切正常,
  • D3DERR_DEVICELOST设备丢失,
  • D3DERR_DEVICENOTRESET设备可以Reset。
  • D3DERR_DRIVERINTERNALERROR,D3D9新增的,遇到这个你就完蛋了,基本不可能恢复了,终止程序吧。

  按照顺序来讲,如果游戏在正常运行,D3D_OK会返回;如果发生了设备丢失并且在这个时候不能恢复,比如全屏幕模式的时候用户切换到了Windows桌面,就会返回D3DERR_DEVICELOST;如果用户又切换回了游戏,设备可以恢复了(还没恢复呢!只是“可以”恢复而已),就会返回D3DERR_DEVICENOTRESET。
  另外,IDirect3DDevice9::Present也会返回类似的值,不过你最好别指望这个,老老实实的用TestCooperativeLevel。因为Present在设备可以恢复的时候还是返回D3DERR_DEVICELOST(外一句:D3D10的时候TestCooperativeLevel就会完全整合到Present里面了,可喜可贺可喜可贺)

看下面的伪代码:

	switch (IDirect3DDevice9::TestCooperativeLevel())
	{
	case D3D_OK:
		GameLoop();
		break;
	case D3DERR_DEVICELOST:
		break;
	case D3DERR_DEVICENOTRESET:
		OnLostDevice();
		IDirect3DDevice9::Reset();
		OnResetDevice();
		break;
	default:
		QuitGame();
		break;
	}
  1.  GameLoop()就是你的游戏运行循环。把这个switch写在我们游戏框架的GameMain()部分。
  2.  OnLostDevice()是重置设备前释放所有D3DPOOL_DEFAULT的资源,如果你没有这么做,也就是说还保留着任何D3DPOOL_DEFAULT的资源的话,IDirect3DDevice9::Reset就一定会失败。
  3. Reset 是重置设备,她的参数是指向D3DPRESENT_PARAMS的指针,传入能成功创建设备时的D3DPRESENT_PARAMS 变量。
  4. OnResetDevice()就是设备重置后,重新创建D3DPOOL_DEFAULT资源!你可能注意到ID3DXFont、ID3DXSprite等等都有同名的方法,就是在这个时候调用的。在OnResetDevice里面你还要重新进行SetRenderState、SetSamplerState等渲染状态,Reset之后这些东西也丢失了。

实际上Reset和重新创建一次设备类似,所不同的是重新创建设备的话你需要连D3DPOOL_MANAGED 的资源也Release掉。

  从代码可以看出来,D3DERR_DEVICELOST时程序什么都没做,只是在傻等。我认为这是一个好习惯,因为实在不能保证在D3DERR_DEVICELOST时除了Release还能干什么,与其这样还不如等设备能用了再说。

五、人工制造“设备丢失”

如果主动修改游戏分辨率、色深,切换全屏幕及窗口状态,进行这样的操作也要通过Reset。同样的,Reset之前也要释放掉所有D3DPOOL_DEFAULT资源(其实严格来说,还有更多的资源也要释放,不过在2D下基本不会创建这类资源,你就不用管了)并且调用ID3DXSprite::OnLostDevice之类的方法。这就是人工制造“设备丢失”了。

实际上这个过程,设备并没有真正的丢失,只是会有一段时间处于不可用的状态,此时Reset尚未返回,整个D3D设备就好像死了一样。举个例子,你切换桌面分辨率,会有那么一段时间显示器上什么都不显示,然后很快就正常了。和这个现象是同一个原因。Reset成功后记得恢复资源。

你可能注意到这里的Reset和上面的Reset不是一回事。的确是这样,这里是为了重设状态而不是恢复设备。因此更改分辨率、色深的Reset需要写到switch外面,也就是别和它搅和的意思-_-bb。而且你只需要OnLostDevice -> Reset -> OnResetDevice。

记住:正确的调用Reset不会造成设备丢失,这个概念别弄混了。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值