Windows游戏开发——GameTimer的创建与解析

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值