Ogre Timer 类 解析

 Ogre Timer

    一个图形绘制引擎底层有很多工具类(utility),通过读基础工具类源码可以学习到不少东西。

        Ogre引擎中与Timer相关的文件大致不多,大致如下(只列出头文件)

WIN32/OgreTimerImp.h
GLX/OgreTimerImp.h
OSX/OgreTimerImp.h
iPhone/OgreTimerImp.h
OgreTimer.h

        其实这部分代码很少,真正意义上的头文件就是OgreTimer.h,以下是这个头文件的全部了.

 
1#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
2# include "WIN32/OgreTimerImp.h"
3#elif (OGRE_PLATFORM == OGRE_PLATFORM_LINUX) || (OGRE_PLATFORM == OGRE_PLATFORM_SYMBIAN)
4# include "GLX/OgreTimerImp.h"
5#elif OGRE_PLATFORM == OGRE_PLATFORM_APPLE
6# include "OSX/OgreTimerImp.h"
7#elif OGRE_PLATFORM == OGRE_PLATFORM_IPHONE
8# include "iPhone/OgreTimerImp.h"
9#endif

        看明白了么?很简单,根据OGRE_PLATFORM宏,选择包含相应平台的Timer实现头文件。如win32下,OgreTimer.h就简单地include WIN32/OgreTimerImp.h就可以了。OgreTimerImp.h中就是Timer的定义了。(下面的源码都以win32平台的Timer为例)



成员变量

 
1clock_t mZeroClock;
2DWORD mStartTick;
3LONGLONG mLastTime;
4LARGE_INTEGER mStartTime;
5LARGE_INTEGER mFrequency;
6DWORD_PTR mTimerMask;

(1)mZeroClock

        程序启动后所执行的时间。

 
1mZeroClock = clock();

        clock函数返回进程启动后的执行时间,表示方法是CPU的tick数(也俗称滴答数)。笔者的环境是vs2008,宏CLOCKS_PER_SEC的定义是1000,也就是说一个滴答数大致1/1000秒。用mZeroClock乘以这个0.001秒就可以得到进程启动后的运行时间了,当然在计算的时候只计算tick数就足够了。

(2)mStartTick

        系统启动后所经历的时间,时间单位是毫秒。

 
1mStartTick = GetTickCount();

(3)mLastTime

        在调用读取时间函数 unsigned long getMilliseconds() / unsigned long getMicroseconds() 后记录当前读取的最后时间,作用是当下一次时间读取错误时,进行调整。(关于时间读取错误后面会有展开。)

(4)mStartTime / mFrequency

        用于高精度计时器时间读取 分别通过函数QueryPerformanceCounter和QueryPerformanceFrequency获得count数以及frequency。这里需要注意的是,Timer里应用两套计时度量方法。

        一套是通过使用clock()获得cpu中时钟tick,在获取时间时,使用的api如下

 
1unsigned long getMillisecondsCPU();
2unsigned long getMicrosecondsCPU();

        另一种就是高精度计时器函数,api如下

 
1unsigned long getMilliseconds();
2unsigned long getMicroseconds();

        在后面会讨论下这些api值得关注的细节。

(5)mTimerMask

        这个变量看名字有点不明白,其实这个变量和高精度计时器读取时间有关。查询msdn中QueryPerformanceCounter函数的注解中可以发现,这个函数调用环境如果是多处理器(CPU)的话,由于BIOS或者HAL的原因,切换到不同CPU上的线程调用后会造成不正确的结果。所以必须保证每次调用这个函数都是在同一个CPU上进行。这个mask就是将线程调用限定在某一个CPU上。

 
01HANDLE thread = GetCurrentThread();
02  
03// Set affinity to the first core
04DWORD_PTR oldMask = SetThreadAffinityMask(thread, mTimerMask);
05  
06// Get the constant frequency
07QueryPerformanceFrequency(&mFrequency);
08  
09// Query the timer
10QueryPerformanceCounter(&mStartTime);
11mStartTick = GetTickCount();
12  
13// Reset affinity
14SetThreadAffinityMask(thread, oldMask);

        通过SetThreadAffinityMask函数,设置允许执行本线程的CPU标示位,就可以达到这一目的。每次时间获取都在限定处理器上执行,执行完后恢复oldMask即可。



成员函数

 
01bool setOption( const String& strKey, const void* pValue );
02  
03/** Resets timer */
04void reset();
05  
06/** Returns milliseconds since initialisation or last reset */
07unsigned long getMilliseconds();
08  
09/** Returns microseconds since initialisation or last reset */
10unsigned long getMicroseconds();
11  
12/** Returns milliseconds since initialisation or last reset, only CPU time measured */
13unsigned long getMillisecondsCPU();
14  
15/** Returns microseconds since initialisation or last reset, only CPU time measured */
16unsigned long getMicrosecondsCPU();

        注:此处省略了构造函数和析构函数,构造函数就简单调用reset函数,析构函数为空函数

(1) bool setOption( const String& strKey, const void* pValue )

        这个函数的作用是设置mTimerMask,方法是通过GetProcessAffinityMask()获取能够执行程序的CPU标示,以mask形式给出,然后取最低标示位标示的CPU作为mTimerMask的值,作为限定CPU执行高精度计时读取。这个函数在Ogre中并没有被调用,mTimerMask在构造函数中初始化为0,并在reset函数中有进一步的赋值。

(2)void reset()

        顾名思义,重置一次计时状态,也用于第一次Timer的初始化。这个函数主要做两件事情:

1、设置mTimerMask

 
01// Get the current process core mask
02DWORD_PTR procMask;
03DWORD_PTR sysMask;
04GetProcessAffinityMask(GetCurrentProcess(), &procMask, &sysMask);
05...
06// Find the lowest core that this process uses
07if( mTimerMask == 0 )
08{
09    mTimerMask = 1;
10    while( ( mTimerMask & procMask ) == 0 )
11    {
12    mTimerMask <<= 1;
13    }
14}

        设置的过程在讲解(5)mTimerMask已经提及,不再赘述。

2、在两种度量方法下,读取当前时间

 
01HANDLE thread = GetCurrentThread();
02  
03// Set affinity to the first core
04DWORD_PTR oldMask = SetThreadAffinityMask(thread, mTimerMask);
05  
06// Get the constant frequency
07QueryPerformanceFrequency(&mFrequency);
08  
09// Query the timer
10QueryPerformanceCounter(&mStartTime);
11  
12mStartTick = GetTickCount();
13  
14// Reset affinity
15SetThreadAffinityMask(thread, oldMask);
16  
17mLastTime = 0;
18mZeroClock = clock();

        可以看到使用上面提到过的方式,将CPU限定后,读取了两种度量下的时间,并将所有成员变量进行初始化。

(3)时间读取函数

        剩下四个函数就是两套度量方式读取时间的函数,每套方式有读取毫秒和百分之一秒两个API。两种度量方式也已经提到过,这里讲下各自的方法。

1、CPU tick度量

        基本一句代码就可以实现,就是通过clock读取当前的tick数,减去Timer初始化的mZeroClock就可以得到毫秒数,并不需要进行CPU限定。

2、高精度计时器度量

        这个度量步骤和CPU度量相似,在CPU限定的情况下,读取count数,进行相减。但是这里有个需要提及,就是在讲成员变量mLastTime时提到的错误时间调整。

        通过查询Microsoft KB: Q274323,由于芯片组的设计缺陷,QueryPerformanceCounter函数返回的结果会意外的产生跳跃的错误时间。这也就是不能单独使用高精度计时器的原因,必须有另一个计时方法来进行补偿调整。

 
01unsigned long check = GetTickCount() - mStartTick;
02signed long msecOff = (signed long)(newTicks - check);
03if (msecOff < -100 || msecOff > 100)
04{
05    LONGLONG adjust = (std::min)(msecOff * mFrequency.QuadPart / 1000, newTime - mLastTime);
06    mStartTime.QuadPart += adjust;
07    newTime -= adjust;
08  
09    // Re-calculate milliseconds
10    newTicks = (unsigned long) (1000 * newTime / mFrequency.QuadPart);
11}
12  
13// Record last time for adjust
14mLastTime = newTime;

        这个是读取完高精度时间后需要进行的检验。check变量是CPU tick度量方式得到的时间,然后将两种方式得到的时间相减,检查这个差值的绝对值,如果过大就需要进行调整。然后重新计算newTick就可以了。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值