书法字典:https://www.shufadict.com

DirectX怪象之二, 程序很吃CPU

学习DirectX编程的兄弟们可能经常遇到的一个情况是,程序经常莫名奇妙的占用大量的CPU资源,其实吃CPU的问题并不是DirectX程序所特有的,几乎任何程序都可能,只不过DirectX程序更加容易产生而已,总结了一下,主要有以下几个方面

没有及时释放资源

这种情况的现象多发生在程序运行的时候,也就是窗口处于active状态时,大家都知道,DX是基于COM的,这也就意味着,你需要手动释放COM对象,如果没有及时释放的话,CPU就会吃紧,尤其是当在Render函数中创建对象的时候更是如此。至于哪些需要释放,哪些不需要释放,需要视具体情况而定,这里有一个简单的方法,如果创建该对象的函数形如CreateXXX,那么基本都需要释放,这种对象一般都有一个Release方法,直接调用这个方法即可。给大家分享一个宏定义,来自于DX SDK,这个宏可以很方便的释放COM对象

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

我们在编程时应该遵循一些好的编程习惯,这样就会避免错误发生,比如

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状态。

case WM_SIZE:
// 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资源。

当然,肯定还有许多其他的原因,欢迎大家补充!

 

posted on 2012-05-07 09:10  翰墨小生  阅读(5120)  评论(6编辑  收藏  举报

导航

书法字典:https://www.shufadict.com