本篇将简单整理Direct3D 10的计时器实现,具体内容参照《 Introduction to 3D Game Programming with DirectX 10》(中文版有汤毅翻译的电子书《DirectX 10 3D游戏编程入门》)。

1.高精度性能计数器

Direct3D10使用高精度性能计数器(精度达微秒级)实现精确时间测量,为了调用下面介绍的两个Win32计数器API,需要添加包含语句“#include <windows.h>”

 BOOL QueryPerformanceCounter(LARGE_INTEGER * lpPerformanceCount);

lpPerformanceCount:参数指向计数器的值,即该参数将用于返回当前的计时,为一个64位整型。其计时单位称为计数,即所谓的“滴答”。

由于性能计数器与系统有关,需要知道每秒滴答声的个数,即滴答的频率,然后用前后两次计时的差值去除以频率才能得到这段时间一共走过了多少秒,故还需要计算计数频率。

 BOOL QueryPerformanceFrequency(LARGE_INTEGER * lpFrequency);

lpFrequency:参数指向计数器频率的值,即该参数将用于返回系统计数频率。

计时示例:

     long long preTime,currTime,valueInSecs,valueInCounts;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&preTime));
long long CountsPerSec;
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&CountsPerSec));
//计算出转换因子SecondsPerCount,避免重复进行除法计算
double SecondsPerCount = 1.0f / static_cast<double>(countsPerSec);
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currTime));
//计算两次计时差值
valueInCounts = currTime - preTime;
//将两次计时差值转换成以秒为单位
valueInSecs = valueInCounts * SecondsPerCount;

2.设计游戏计时器类(GameTimer类)

类的头文件为GameTimer.h,具体实现放在GameTimer.cpp中。

一个计时器,需要的功能有初始化、开始计时、停止计时、恢复计时、计算两次计时时间差以及计算总计时时长。

初始化由构造函数完成,该过程中可将计时频率、计时转换因子等必要的值先求出来。需要添加的成员有:每次计时所需时间SecondsPerCount,初始计时BaseTime等。

计时过程中可一并计算两次计时的时间差。需要添加的成员有:前一次计时PreTime,当前计时CurrTime,两次计时时间间隔DeltaTime。

停止计时需要用一个状态量来阻止计时函数的执行,同时还应记录停止时刻的CurrTime,则需要添加的成员有:StopTime,StoppedState。

恢复计时需要额外计算暂停了多长时间,则需要添加的成员有:PauseTime。

同时,还需要添加一些返回计时的函数以及重置函数。

所以其头文件可以设计如下:

 GameTimer.h

 #include <windows.h>

 class GamerTimer
{
public:
GamerTimer();
~GamerTimer(); FLOAT getGameTime()const;
FLOAT getDeltaTime()const;
VOID reset();
VOID start();
VOID stop();
VOID tick(); private:
DOUBLE m_dSecondsPerCount;
DOUBLE m_dDeltaTime; LONGLONG m_llBaseTime;
LONGLONG m_llPauseTime;
LONGLONG m_llStopTime;
LONGLONG m_llPreTime;
LONGLONG m_llCurrTime; BOOL m_bStopped;
};

下面逐个讲解类方法的实现:

 GamerTimer::GamerTimer()
:m_dSecondsPerCount(0.0f), m_dDeltaTime(0.0f), m_llBaseTime(),
m_llPauseTime(), m_llStopTime(), m_llPreTime(), m_llCurrTime(),
m_bStopped(FALSE)
{
LONGLONG countsPerSec;
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&countsPerSec));
m_dSecondsPerCount = 1.0f / static_cast<DOUBLE>(countsPerSec);
}

在这个构造函数中,将所有数据初始化完毕后,还需计算出前面重复提到的转换因子,当计时单位由计数转换为秒时,就需要乘以这个转换因子。获取频率时用了reinterpret_cast进行指针类型的强制转换,不能用static_cast等,这属于C++的内容,今后不再累赘。

 VOID GamerTimer::tick()
{
//计时停止则重置时间间隔
if (m_bStopped)
{
m_dDeltaTime = 0.0f;
return;
} //否则,计算时间间隔 //获取当前帧时间计数
LONGLONG currTime;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currTime));
m_llCurrTime = currTime; //计算当前帧与上一帧时间差,单位为秒
m_dDeltaTime = (m_llCurrTime - m_llPreTime)*m_dSecondsPerCount; //为下一帧计时做准备
m_llPreTime = m_llCurrTime; //负值处理,当处理器进入省电模式或切换到另一处理器,时间间隔可能为负
if (m_dDeltaTime < 0.0f)
{
m_dDeltaTime = 0.0f;
} return;
}

大部分内容都在注释里呈现了,值得留意的是,该方法的负值处理部分,提到了切换到另一处理器,意味着我们可能会使用多线程计时,只要保证计时器在子线程内一直正常运作,条件是该子线程未结束或未被阻塞/锁上(当然也还是有办法通过使用额外的计时来消除这段误差)。C++11/14标准提供了很方便的线程库,所以将计时器分离出来或许是个不错的尝试。

 FLOAT GamerTimer::getDeltaTime()const
{
return static_cast<FLOAT>(m_dDeltaTime);
}

返回两次计时的时间差,单位为秒。

 VOID GamerTimer::reset()
{
LONGLONG currTime;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currTime)); m_llBaseTime = currTime;
m_llPreTime = currTime;
m_llStopTime = ;
m_bStopped = FALSE; return;
}

用于重置计时器,而不必销毁原来的计时器再构造一个。

 VOID GamerTimer::stop()
{
//如果已停止,不做任何操作;否则停止计时
if (!m_bStopped)
{
LONGLONG currTime;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currTime)); //记录停止时刻,更新状态
m_llStopTime = currTime;
m_bStopped = TRUE;
} return;
}

停止/暂停计时的方法,记录调用时刻,并将停止状态设置为TRUE。

 VOID GamerTimer::start()
{
LONGLONG startTime;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&startTime)); //从初始状态开始计时或从暂停状态恢复计时
if (m_bStopped)
{
//计算暂停时长
m_llPauseTime = startTime - m_llStopTime; //由于中途暂停,上一帧时刻已无效,需更新到恢复计时时刻
m_llPreTime = startTime; //更新停止状态
m_llStopTime = ;
m_bStopped = FALSE;
} return;
}

开始/恢复计时的方法,主要注意需计算出暂停的时长。该方法将开始计时和恢复计时合并,原因是两种计时方法的前一时刻的停止状态m_bStopped均为FALSE,故它们能有相同的行为。

 FLOAT GamerTimer::getGameTime()const
{
//如果处于停止状态
if (m_bStopped)
{
return static_cast<FLOAT>((m_bStopped - m_llBaseTime - m_llPauseTime)*m_dSecondsPerCount);
}
//如果仍在计时
else
{
return static_cast<FLOAT>((m_llCurrTime - m_llBaseTime - m_llPauseTime)*m_dSecondsPerCount);
}
}

计算出总计时时长的方法。计算时注意应该剔除暂停总共花去的时间。

完整的实现代码如下:

 GameTimer.cpp

 #include "GameTimer.h"

 GamerTimer::GamerTimer()
:m_dSecondsPerCount(0.0f), m_dDeltaTime(0.0f), m_llBaseTime(),
m_llPauseTime(), m_llStopTime(), m_llPreTime(), m_llCurrTime(),
m_bStopped(FALSE)
{
LONGLONG countsPerSec;
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&countsPerSec));
m_dSecondsPerCount = 1.0f / static_cast<DOUBLE>(countsPerSec);
} VOID GamerTimer::tick()
{
//计时停止则重置时间间隔
if (m_bStopped)
{
m_dDeltaTime = 0.0f;
return;
} //否则,计算时间间隔 //获取当前帧时间计数
LONGLONG currTime;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currTime));
m_llCurrTime = currTime; //计算当前帧与上一帧时间差,单位为秒
m_dDeltaTime = (m_llCurrTime - m_llPreTime)*m_dSecondsPerCount; //为下一帧计时做准备
m_llPreTime = m_llCurrTime; //负值处理,当处理器进入省电模式或切换到另一处理器,时间间隔可能为负
if (m_dDeltaTime < 0.0f)
{
m_dDeltaTime = 0.0f;
} return;
} FLOAT GamerTimer::getDeltaTime()const
{
return static_cast<FLOAT>(m_dDeltaTime);
} VOID GamerTimer::reset()
{
LONGLONG currTime;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currTime)); m_llBaseTime = currTime;
m_llPreTime = currTime;
m_llStopTime = ;
m_bStopped = FALSE; return;
} VOID GamerTimer::stop()
{
//如果已停止,不做任何操作;否则停止计时
if (!m_bStopped)
{
LONGLONG currTime;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&currTime)); //记录停止时刻,更新状态
m_llStopTime = currTime;
m_bStopped = TRUE;
} return;
} VOID GamerTimer::start()
{
LONGLONG startTime;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&startTime)); //从初始状态开始计时或从暂停状态恢复计时
if (m_bStopped)
{
//计算暂停时长
m_llPauseTime = startTime - m_llStopTime; //由于中途暂停,上一帧时刻已无效,需更新到恢复计时时刻
m_llPreTime = startTime; //更新停止状态
m_llStopTime = ;
m_bStopped = FALSE;
} return;
} FLOAT GamerTimer::getGameTime()const
{
//如果处于停止状态
if (m_bStopped)
{
return static_cast<FLOAT>((m_bStopped - m_llBaseTime - m_llPauseTime)*m_dSecondsPerCount);
}
//如果仍在计时
else
{
return static_cast<FLOAT>((m_llCurrTime - m_llBaseTime - m_llPauseTime)*m_dSecondsPerCount);
}
} GamerTimer::~GamerTimer(){}

Direct3D 10学习笔记(二)——计时器的更多相关文章

  1. Direct3D 10学习笔记(三)——文本输出

    本篇将简单整理Direct3D 10的文本输出的实现,具体内容参照< Introduction to 3D Game Programming with DirectX 10>(中文版有汤毅 ...

  2. Direct3D 10学习笔记(四)——Windows编程

    本篇将简单整理基本的Windows应用程序的实现,并作为创建Direct3D 10应用程序的铺垫.具体内容参照< Introduction to 3D Game Programming with ...

  3. Direct3D 10学习笔记(一)——初始化

    本篇将简单整理Direct3D 10的初始化,具体内容参照< Introduction to 3D Game Programming with DirectX 10>(中文版有汤毅翻译的电 ...

  4. WPF的Binding学习笔记(二)

    原文: http://www.cnblogs.com/pasoraku/archive/2012/10/25/2738428.htmlWPF的Binding学习笔记(二) 上次学了点点Binding的 ...

  5. java之jvm学习笔记二(类装载器的体系结构)

    java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...

  6. NumPy学习笔记 二

    NumPy学习笔记 二 <NumPy学习笔记>系列将记录学习NumPy过程中的动手笔记,前期的参考书是<Python数据分析基础教程 NumPy学习指南>第二版.<数学分 ...

  7. Learning ROS for Robotics Programming Second Edition学习笔记(二) indigo tools

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  8. Redis学习笔记二 (BitMap算法分析与BitCount语法)

    Redis学习笔记二 一.BitMap是什么 就是通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身.我们知道8个bit可以组成一个Byte,所以bitmap本身会极大的节省 ...

  9. Django学习笔记二

    Django学习笔记二 模型类,字段,选项,查询,关联,聚合函数,管理器, 一 字段属性和选项 1.1 模型类属性命名限制 1)不能是python的保留关键字. 2)不允许使用连续的下划线,这是由dj ...

随机推荐

  1. Joomla软件功能介绍与开源程序大比拼Joomla,wordpress,Drupal哪个好?

    Joomla 软件功能介绍:    Joomla!是一套在国外相当知名的内容管理系统 (Content Management System, CMS),它属于Portal(企业入口网站)类型,顾名思义 ...

  2. 在update时用触发器插入数据

    CREATE trigger [dbo].[Debt_Insert] on [dbo].[Debt] for insert as declare @tmpOrderID1 varchar(30)sel ...

  3. Could not parse mapping document from input stream

    无法从输入流解析映射文档 1.定义的类名或属性名不对,如:*.hbm.xml文件中属性name对应的实体类name不一致.2.xml头文件中"http://www.hibernate.org ...

  4. vuejs开发组件分享之H5图片上传、压缩及拍照旋转的问题处理

    一.前言 三年.net开发转前端已经四个月了,前端主要用webpack+vue,由于后端转过来的,前端不够系统,希望分享下开发心得与园友一起学习. 图片的上传之前都是用的插件(ajaxupload), ...

  5. Yii2.0 rules验证规则大全

    required : 必须值验证属性 [['字段名'],required,'requiredValue'=>'必填值','message'=>'提示信息']; #说明:CRequiredV ...

  6. The trip(Uva 11100)

    题目大意: 给出n个数,要求将其分成最少的递增序列,保证序列最少的同时要使得序列长度的最大值最小.  n<=10000 题解: 1.我是直接看着<训练指南>里的中文题面的,lrj似乎 ...

  7. C#中Thread与ThreadPool的比较

    最近同事在编写一个基于UPD RTP协议的通信软件,在处理接收Listen时,发现了一个问题到底是用Thread还是ThreadPool呢? 我看同事的问题比较有典型性,还是做以整理培训一下吧 Thr ...

  8. js图片拖放原理(很简单,不是框架,入门基础)

    <html> <meta> <script src='jquery-1.8.3.min.js'></script> <script> /* ...

  9. WordPress程序伪静态规则(Nginx/Apache)及二级目录规则

    在众多CMS程序中,我们使用WORDPRESS还是比较多的,不仅仅是安全度较好,二来在于插件和主题很多,即便对于不会建站技术的用户也很简单的就可以搭建属于自己的网站项目.对于网站我们肯定是需要让有用户 ...

  10. HalconMFC(二)之VS2010下配置Halcon11教程

    现在halcon最新版本是halcon11.0.3,所以在此说说halcon11.0.3的配置方法(至今还不知道halcon11怎么破解...halcon10早都可以破解了) 我们可以把相应的文件(头 ...