本篇将简单整理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. 使用简单NGUI加载进度条

    1.在Panel上添加Slider,GNUI--Open--Widget Wizard--Slider,设置Empty和Full 2.在Panel上添加Label,GNUI--Open--Widget ...

  2. UEditor-从客户端(editorValue="<p>asd</p>")中检测到有潜在危险的 Request.Form 值。

    在用富文本编辑器时经常会遇到的问题是asp.net报的”检测到有潜在危险的 Request.Form 值“一般的解法是在aspx页面   page  标签中加上 validaterequest='fa ...

  3. Redis常用命令入门2:散列类型

    散列命令 散列类型的键值其实也是一种字典解耦,其存储了字段和字段值的映射,但字段值只能是字符串,不支持其他数据类型,所以说散列类型不能嵌套其他的数据类型.一个散列类型的键可以包含最多2的32次方-1个 ...

  4. jmeter 内存溢出解决方法

    执行“评论新鲜事”200并发就内存溢出 解决方法: [caozijuan@test09 bin]$ vi jmeter JVM_ARGS="-Xms1024m -Xmx4096m" ...

  5. The type String cannot be constructed. You must configure the container to supply this value.

    利用 Enterprise Library 5.0 Microsoft.Practices.EnterpriseLibrary.Common Microsoft.Practices.Enterpris ...

  6. js对特殊字符转义、时间格式化、获取URL参数

    /*特殊字符转义*/ function replace_html(str) { var str = str.toString().replace(/&/g, "&" ...

  7. sql 多级内查询

    最近在开发一个外包的项目,其中有个需求,一直困扰我好几天,今天终于找到了解决方案.大致需求如下:公司总部发货给经销商,其中经销商包含四种级别,钻石.金牌.银牌和铜牌,等级依次下发,钻石包含金牌,金牌包 ...

  8. error MSB6006: “CL.exe”已退出,代码为X —— 的解决办法

    错误 : error MSB6006: “CL.exe”已退出,代码为X . 解决方法: 1.有少可能是执行目录引起的. 参考 http://bbs.csdn.net/topics/370064083 ...

  9. 读取.properties配置文件

    方法1 public  class SSOUtils { protected static String URL_LOGIN = "/uas/service/api/login/info&q ...

  10. C# 进程和线程

    一.进程和线程 进程是对一段静态指令序列的动态执行过程,是系统进行资源分配和调度的基本单位.与进程相关的信息包括进程的用户标志.正在执行的已经编译好的程序.程序和数据在存储器中的位置等.同一个进程有可 ...