DirectX怪象之二, 程序很吃CPU
学习DirectX编程的兄弟们可能经常遇到的一个情况是,程序经常莫名奇妙的占用大量的CPU资源,其实吃CPU的问题并不是DirectX程序所特有的,几乎任何程序都可能,只不过DirectX程序更加容易产生而已,总结了一下,主要有以下几个方面
没有及时释放资源
这种情况的现象多发生在程序运行的时候,也就是窗口处于active状态时,大家都知道,DX是基于COM的,这也就意味着,你需要手动释放COM对象,如果没有及时释放的话,CPU就会吃紧,尤其是当在Render函数中创建对象的时候更是如此。至于哪些需要释放,哪些不需要释放,需要视具体情况而定,这里有一个简单的方法,如果创建该对象的函数形如CreateXXX,那么基本都需要释放,这种对象一般都有一个Release方法,直接调用这个方法即可。给大家分享一个宏定义,来自于DX SDK,这个宏可以很方便的释放COM对象
我们在编程时应该遵循一些好的编程习惯,这样就会避免错误发生,比如
1 对于那些只需要一次创建的对象,通通放到Render函数之外,所以我们通常设置一个函数名叫InitD3D,可以把一次性初始化的代码都放到这里。切记不可把非渲染操作放到Render函数里面,因为Render函数就是用来做渲染的,而且调用频率极高,几乎是时时刻刻都在调用,如果把其他操作资源的过程也放到这里,势必会影响效率。
2 在开发的时候尽量使用Debug版本的库,这样可以及时发现资源泄露。
没有处理窗口inactive的情况
这种情况多发生在窗口处于inactive状态时,比如被遮挡或者最小化到任务栏。先看一段Windows下的渲染框架,下面的代码逻辑很简单,有消息则处理消息,无消息则渲染,如果你使用了这段代码,那么你可以试着将窗口最小化,你将会看到CPU使用率上升一大截,为什么呢?这是由于PeekMessage的机制造成的,说到PeekMessage就不得不说GetMessage,这二者都是用于从消息队列中取得消息,不同的是,PeekMessage如果没取到消息,会立即返回0,而GetMessage如果没取到消息,则会一直等待有消息可取。也就是说PeekMessage是异步的,而GetMessage是同步的。下面的代码就是利用没有消息处理的时间进行渲染,而当窗口最小化时,PeekMessage一直取不到消息,所以程序一直在渲染,这样就占用了大量的CPU时间。
MSG msg ; ZeroMemory( &msg, sizeof(msg) ); PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE ); // Get latest time static DWORD lastTime = timeGetTime(); while (msg.message != WM_QUIT) { if( PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) !=0) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } else// Render the scene if there is no message to handle { // Get current time DWORD currTime = timeGetTime(); // Calculate time elapsed float timeDelta = (currTime - lastTime) *0.001f; // Render Render(timeDelta) ; // Update lastTime lastTime = currTime; } }
有了上面的分析,我们可以分两种情况来处理,一是程序处于active状态时,我们按照上面的方法渲染。二是程序处于inactive状态时,我们可以使用GetMessage来等待一个消息,因为此时程序不需要渲染(已经最小化了,还渲染啥呀?)。对应的代码如下
BOOL bGotMsg; MSG msg; PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE ); while( WM_QUIT != msg.message ) { // Use PeekMessage() if the app is active, so we can use idle time to // render the scene. Else, use GetMessage() to avoid eating CPU time. if( m_bActive ) bGotMsg = PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ); else bGotMsg = GetMessage( &msg, NULL, 0U, 0U ); if( bGotMsg ) { // Translate and dispatch the message if( 0== TranslateAccelerator( m_hWnd, hAccel, &msg ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } } else { // Render a frame during idle time (no messages are waiting) if( m_bActive ) { if( FAILED( Render3DEnvironment() ) ) SendMessage( m_hWnd, WM_CLOSE, 0, 0 ); } } }
下面的问题就是如何处理m_bActive这个变量了,可以响应WM_SIZE消息,SIZE_MAXHIDE表示有其他窗口最大化了,这样就意味着本窗口被完全遮挡了,SIZE_MINIMIZED表示窗口最小化了。这两种情况我们将窗口视为inactive状态。
// Check to see if we are losing our window...
if( SIZE_MAXHIDE==wParam || SIZE_MINIMIZED==wParam )
m_bActive = FALSE;
else
m_bActive = TRUE;
break;
没有做场景管理
由于我对场景管理不是很熟练,所以只能简单说说,场景管理应该属于中高层次的技术,在刚学DirectX的时候,根本不会考虑这些,但是随着知识的深入,程序代码的增大,逻辑越来越复杂,模型越来越多,难免会涉及到场景管理,什么是场景管理呢,如果从渲染的角度来讲,就是区分哪些应该渲染,哪些不用渲染,就是说要提高渲染的速度,场景管理中最基本的就是Frustum剪裁,四叉树,BSP场景管理,八叉树等。
Frustum,中文翻译成视锥,视锥剪裁是最基本的场景管理,也就是只渲染在视野内的模型,其他的一律剪裁掉。
四叉树多用于地形渲染
BSP多用于室内场景管理,著名的Quake系列使用的就是BSP树
八叉树比较通用
如果你的场景中有大量的模型,而又没有做场景管理,也就意味着,一次性将所有的模型都渲染一遍,不管模型是否在视野里,这势必很浪费CPU资源。
当然,肯定还有许多其他的原因,欢迎大家补充!