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资源。
当然,肯定还有许多其他的原因,欢迎大家补充!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述
2010-05-07 Build OGRE with CMake
2009-05-07 切莫开一块地荒一块地