1.主体思路
在游戏创建中,GameTimer用于计算两个动画帧之间的资源消耗,由于不同机器的CPU的频率(f)不同,即时间周期(T)不同,它们之间计算资源消耗的基本单位数值不同,所以需要获取当前机器处理器的频率,在记录两帧之间的时间间隔Δt后,再计算两帧之间时间周期的消耗ΔT。
T = 2 GHz //当前机器的时钟周期
Δt = CurrentTime - PreviousTime // 当前t帧的Time - 上一帧t-1帧的Time
ΔT = T*Δt
而如何获取CPU的时钟周期和计时?即通过QueryPerformanceFrequency和QueryPerformanceCounter方法,它们正是构成GameTimer的主要部分。

2.使用场景
Windows DirectX的文档对计时器的使用如下所示:
BeginScene();
...
// Start profiling
LARGE_INTEGER start, stop, freq;
QueryPerformanceCounter(&start); //获取t-1帧的counter
SetTexture(...);
DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);//绘制动画 t-1帧->t帧
QueryPerformanceCounter(&stop); //获取t帧的counter
stop.QuadPart -= start.QuadPart;
QueryPerformanceFrequency(&freq); //获取当前CPU的频率f,与时间周期的关系:T=1/f
// Stop profiling
...
EndScene();
Present();
在Direct3D12的游戏开发中,GameTimer用于在异步消息循环队列中调用,计算上一帧与当前帧的时间周期T的消耗,以此来更新游戏动画场景。如下所示:
int D3DApp::Run()
{
MSG msg = {0};
mTimer.Reset(); //初始化GameTimer计数器
while(msg.message != WM_QUIT)
{
// If there are Window messages then process them.
if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// Otherwise, do animation/game stuff.
else
{
mTimer.Tick(); //在每一帧中计算ΔT
if( !mAppPaused )
{
CalculateFrameStats();
Update(mTimer); //根据GameTimer来更新,绘制游戏对象和场景
Draw(mTimer);
}
else
{
Sleep(100);
}
}
}
return (int)msg.wParam;
}
3.GameTimer
下述代码来自introduction - 12to 3D Game Programming With Directx12书中。
- Reset()作为GameTimer对象中各参数的初始化函数,只在进入循环消息队列之前调用一次,如前文代码所示。
- TotalTime()用于记录游戏程序启动时间点到当前时间点的时间间隔,或启动时间点到暂停时间点的时间间隔,与Start和Stop函数相关,中间的暂停时间段在总时间的记录中需要跳过,即TotalTime-PausedTime。在不同的阶段,TotalTime的取值不同,如游戏停止阶段,TotalTime取值为游戏暂停时的那一刻即StopTime,如游戏运行阶段,TotalTime取值为游戏运行的当前时间点即CurrentTime。
该函数的应用场景十分丰富,如LOL中每隔N秒生成一波兵线,这个N秒就是由TotalTime()计时而来。如点燃一颗炸弹,N秒后爆炸,也是由该函数进行计时。
//***************************************************************************************
// GameTimer.cpp by Frank Luna (C) 2011 All Rights Reserved.
//***************************************************************************************
#include <windows.h>
#include "GameTimer.h"
GameTimer::GameTimer()
: mSecondsPerCount(0.0), mDeltaTime(-1.0), mBaseTime(0),
mPausedTime(0), mPrevTime(0), mCurrTime(0), mStopped(false)
{
__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);//获取当前CPU的频率f
mSecondsPerCount = 1.0 / (double)countsPerSec; //取倒数 =》T= 1/f
}
// Returns the total time elapsed since Reset() was called, NOT counting any
// time when the clock is stopped.
float GameTimer::TotalTime()const
{
// If we are stopped, do not count the time that has passed since we stopped.
// Moreover, if we previously already had a pause, the distance
// mStopTime - mBaseTime includes paused time, which we do not want to count.
// To correct this, we can subtract the paused time from mStopTime:
//
// |<--paused time-->|
// ----*---------------*-----------------*------------*------------*------> time
// mBaseTime mStopTime startTime mStopTime mCurrTime
if( mStopped )
{
return (float)(((mStopTime - mPausedTime)-mBaseTime)*mSecondsPerCount);
}
// The distance mCurrTime - mBaseTime includes paused time,
// which we do not want to count. To correct this, we can subtract
// the paused time from mCurrTime:
//
// (mCurrTime - mPausedTime) - mBaseTime
//
// |<--paused time-->|
// ----*---------------*-----------------*------------*------> time
// mBaseTime mStopTime startTime mCurrTime
else
{
return (float)(((mCurrTime-mPausedTime)-mBaseTime)*mSecondsPerCount);
}
}
float GameTimer::DeltaTime()const
{
return (float)mDeltaTime;
}
void GameTimer::Reset()
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mBaseTime = currTime;
mPrevTime = currTime;
mStopTime = 0;
mStopped = false;
}
void GameTimer::Start()
{
__int64 startTime;
QueryPerformanceCounter((LARGE_INTEGER*)&startTime);
// Accumulate the time elapsed between stop and start pairs.
//
// |<-------d------->|
// ----*---------------*-----------------*------------> time
// mBaseTime mStopTime startTime
if( mStopped )
{
mPausedTime += (startTime - mStopTime);
mPrevTime = startTime;
mStopTime = 0;
mStopped = false;
}
}
void GameTimer::Stop()
{
if( !mStopped )
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mStopTime = currTime;
mStopped = true;
}
}
//计算两帧之间的Δt = t(i) - t(i-1)
void GameTimer::Tick()
{
if( mStopped )
{
mDeltaTime = 0.0;
return;
}
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime); //lager_integer存储64位整数
mCurrTime = currTime;
// Time difference between this frame and the previous.
mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount; //两帧之间消耗的CPU周期数
// Prepare for next frame.
mPrevTime = mCurrTime;
// Force nonnegative. The DXSDK's CDXUTTimer mentions that if the
// processor goes into a power save mode or we get shuffled to another
// processor, then mDeltaTime can be negative.
if(mDeltaTime < 0.0)
{
mDeltaTime = 0.0;
}
}
对于GameTimer的作用,主要是获取两个动画帧之间的时间周期T的消耗,ΔT,根据ΔT来进行游戏动画资源的更新,即FPS画面每秒刷新多少帧, 而这个QueryPerformanceFrequency获取的是CPU的频率,并不是GPU的频率,由于Direct3D12采取的是CPU提交指令到CommandQueue中,GPU再消费命令队列中的指令。这里根据CPU提交指令的频率来调整GPU消费指令的频率,对于一个平滑的游戏动画效果,至少需要FPS为30,每秒的刷新帧数为30。

753

被折叠的 条评论
为什么被折叠?



