本篇将简单整理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. JAVA线程安全总结(转载)

    JAVA线程安全总结(一) JAVA线程安全总结(二) 最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣.已经拟好了提纲,大概分为这几个主题: java线程安全,jav ...

  2. Windows Store App 全球化 资源匹配规则

    上面几个小节通过示例介绍了如何引用资源以及设置应用语言来显示不同语言的信息,这些示例都只是添加了简体中文和英语两种语言来显示资源,而在一些复杂的应用程序中,字符串资源可能会被定义成多种语言,文件资源也 ...

  3. 10.6 CCPC northeast

    1001 Minimum's Revenge 点的编号从 1 到 n ,u  v 的边权是 LCM(u,v) ,求这个图的最下生成树 搞成一颗以 1 为 根 的菊花树 ---------------- ...

  4. WPF中Grid布局

    WPF中Grid布局XMAl与后台更改,最普通的登录界面为例. <Grid Width="200" Height="100" > <!--定义 ...

  5. Oracle查询

    1.普通查询 select * from 表格 查询所有内容 select 列名,列名 from 表格查询某几列 2.条件查询 select * from 表格 where 条件 条件查询 selec ...

  6. Docker简明教程(转)

    Docker自从诞生以来就一直备受追捧,学习Docker是一件很炫酷.很有意思的事情.我希望通过这篇文章能够让大家快速地入门Docker,并有一些学习成果来激发自己的学习兴趣.我也只是一个在Docke ...

  7. openssl

    openssl genrsa -des3 -out server.key 1024openssl req -new server.key -out servr.csr seserver-fnamese ...

  8. 作业3(PSP表格)

                            PSP2.1 Personal Software Process Stages Time(min) Planning 计划 11 Estimate 估计 ...

  9. 关于SQL安装问题及安装前的准备

    转载自:IceWee  原文连接:http://www.cnblogs.com/icewee/articles/2019783.html 由于工作需要,今天要在电脑上安装SQL Server 2005 ...

  10. Linux学习 :字符设备框架

    一.系统功能框架: U-boot : 启动内核 linux kernel: 启动应用 应用: open,read,write 都是通过C库实现,汇编就相当于swi val,引发中断,通过系统调用接口在 ...