在之前的章节当中,我们计算画面的帧率时,仅仅只考虑了Sleep
函数所占用的时间,并未考虑执行其他语句所占用的时间。例如,每绘制一帧画面后,使用Sleep(40)
休眠40毫秒。若绘制一帧画面所需的时间很短,我们可以忽略绘制画面的时间。但是,随着画面越来越复杂,绘制画面所需时间在几毫秒、几十毫秒时,很显然,已经不能再忽略画面绘制的时间了。此外,Sleep
函数所休眠的时间也并不是非常准确的,它也会对画面帧率的准确性产生影响。
这一节中,我们将展开讨论以上提到的问题,并找到一个可以精确地控制画面帧率的方案。
1. 高精度计时器
试想在计算机中有个计数器,开机后,它从0开始累加,每隔固定的时间就让计数器加1,每秒钟累加F
次。F
可以看作计数器累加的频率,一般情况下,F
都会很大,在以上。如此一来,我们可以利用它来做高精度计时。
例如,我们需要计算处理某一个事务所耗时长,可以遵循以下步骤:
- 获取一次计数器当前数值,记为
startCount
。 - 处理事务。
- 再次获取一次计算器当前数值,记为
endCount
。 (endCount - startCount) / F
即为所耗费的时间,单位为秒s
。
若需要将秒s
转换为微秒us
,可以在其后乘以1000000
。即为,(endCount - startCount) / F * 1000000
。
在windows系统上,上述步骤对应的代码如下:
// 开始时间、结束时间、频率F
LARGE_INTEGER startCount, endCount, F;
// 获取频率F
QueryPerformanceFrequency(&F);
// 获取起始计数
QueryPerformanceCounter(&startCount);
// 处理事务:休眠1000ms
Sleep(1000);
// 获取结束计数
QueryPerformanceCounter(&endCount);
// (结束 - 开始) / 频率 * 1000000 = 时差
long long elapse = (endCount.QuadPart - startCount.QuadPart)
/ F.QuadPart * 1000000;
// 打印各数值
printf("start %lld\n", startCount.QuadPart);
printf("end %lld\n", endCount.QuadPart);
printf("end - start %lld\n", endCount.QuadPart - startCount.QuadPart);
printf("F %lld\n", F.QuadPart);
printf("elapse %lld\n", elapse);
调用QueryPerformanceFrequency(&F)
获取计数器累加频率,并存储到变量F
当中。调用QueryPerformanceCounter(&startCount)
获取起始计数,并存储到变量startCount
当中。用休眠1000ms作为处理事务的代码。事务处理完成后,再调用QueryPerformanceCounter(&endCount)
获取终止计数,并存储到变量endCount
当中。
类型LARGE_INTEGER
是一个如下定义的结构体:
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
} DUMMYSTRUCTNAME;
struct {
DWORD LowPart;
LONG HighPart;
} u;
LONGLONG QuadPart;
} LARGE_INTEGER;
其中,成员QuadPart
用于存储计时器数值或频率,类型为long long
。另外,成员HighPart
可以获得QuadPart
的高4字节,成员LowPart
可以获得QuadPart
的低4字节。
接下来,我们用(endCount - startCount) / F * 1000000
计算处理事务所耗时长。
运行后,各变量数值结果如下:
变量 | 数值 | 意义 |
---|---|---|
startCount | 274399139118 | 起始计数 |
endCount | 274409271614 | 终止计数 |
endCount - startCount | 10132496 | 计数差值 |
F | 10000000 | 计数器频率 |
elapse | 1000000 | 时差 |