민돌이2 2022. 1. 20. 17:53

애니메이션을 구현하려면 애니메이션의 프레임들 사이에 흐른 시간의 양을 측정할 수 있어야 한다. 프레임률이 높은 경우 프레임 간 경과 시간이 상당히 짧으므로, 정밀도가 높은 타이머가 필요로 한다. 내가 DirectX 포트폴리오 만들 때 고질적인 문제였다. 타이머를 사용하는 방식이 아니라 30프레임 고정으로 했기 때문에 렌더링할 것이 얼마 없는 초기단계에는 깔끔했던 애니메이션이 후반가면 느려져서 속터지는 경우가 생겼었다.

 

 

1.성능 타이머

정밀한 시간 측정을 위해 Windows가 제공하는 성능 타이머(performance timer)를 사용한다. #include <windows.h>에 있다.

성능 타이머는 시간을 개수(count) 단위로 측정하는데, 개수 단위의 현재 시간을 얻을 때에는 QueryPerformanceCounter 함수를 사용한다.

__int64 currTime;
QueryPerformanceCounter((LARGE_INTERGER*)&currTime);

 

성능 타이머의 주파수(초당 개수)를 얻을 때에는 QueryPerformanceFrequency 함수를 사용한다.

__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTERGER*)&countsPerSec);

 

개수를 얻은 currTime과 주파수를 얻은 countsPerSec을 이용하여 초 단위 시간을 얻을 수 있다.

mSecondsPerCount = 1.0f / (double)countsPerSec; //1개수의 시간
valueInSecs = currTime * mSecondsPerCount; //초 단위 시간

 

현실 세계에서 타이머라고 하면 10초 시간을 쟀다가 0으로 초기화해서 다시 사용하는 방식이지만, 적어도 내가 아는선에서 프로그래밍에서는 아닌 것 같다. 사용하고 시간(개수)을 0으로 초기화하는 것이 아니라 시간은 계속 흐르고 있다.

#include <windows.h>

class Timer
{
public:
	Timer()	{ QueryPerformanceFrequency(&tick); }

	void Start() { QueryPerformanceCounter(&start);	}

	void End() { QueryPerformanceCounter(&end); }

	float RunningTime()
	{
		_int64 elapsed = end.QuadPart - start.QuadPart;//QuadPart -> 해당시간
		return (float)(((double)elapsed / (double)tick.QuadPart) * 1000);
	}

private:
	LARGE_INTEGER tick;
	LARGE_INTEGER start, end;
};

int main()
{
	Timer timer;

	timer.Start();
	/*
	작업
	*/
	timer.End();

	std::cout << timer.RunningTime() << std::endl;
}

실제로 내가 사용하고 있는 타이머 인데 시간 계산을 멤버 변수 start와 end의 차이로 계산하고 있고, start와 end를 초기화하는 코드는 없다. 사실 타이머의 기본 뼈대일 뿐이지 타이머 자체를 보기 좋게 만든다고 하면 초기화도 구현할 순 있다.

하고 싶은 말은 현실 세계에서도 사람이 시계를 조작할 순 있어도 시간 자체를 멈출순 없다라는 것이다. 즉 프로그램이 컴파일이 시작되고 QueryPerformanceCounter 함수는 초기화가 불가능 하므로 시간을 구할 때는 타이머를 누른 시점과 타이머를 종료한 시점의 차이로 구하는 방법밖에 없다.

 

 

2.GameTimer 클래스

class GameTimer
{
public:
	GameTimer();

	float TotalTime() const;
	float DeltaTime() const;

	void Reset();
	void Start();
	void Stop();
	void Tick();

private:
	double mSecondsPerCount;
	double mDeltaTime;

	__int64 mBaseTime;
	__int64 mPausedTime;
	__int64 mStopTime;
	__int64 mPrevTime;
	__int64 mCurrTime;

	bool mStopped;
};

GameTimer::GameTimer()
	: mSecondsPerCount(0.0), mDeltaTime(-1.0), mBaseTime(0),	
	mPausedTime(0), mPrevTime(0), mCurrTime(0), mStopped(false)
{
	__int64 countsPerSec;
	QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
	mSecondsPerCount = 1.0 / (double)countsPerSec;
}

float GameTimer::TotalTime() const
{
	if (mStopped == true)
		return (float)(((mStopTime - mPausedTime) - mBaseTime) * mSecondsPerCount);
	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);

	if (mStopped == true)
	{
		mPausedTime += (startTime - mStopTime);

		mPrevTime = startTime;
		mStopTime = 0;
		mStopped = false;
	}
}

void GameTimer::Stop()
{
	if (mStopped == false)
	{
		__int64 currTime;
		QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

		mStopTime = currTime;
		mStopped = true;
	}
}

void GameTimer::Tick()
{
	if (mStopped == true)
	{
		mDeltaTime = 0.0;
		return;
	}

	__int64 currTime;
	QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
	mCurrTime = currTime;

	mDeltaTime = (mCurrTime - mPrevTime) * mSecondsPerCount;

	mPrevTime = mCurrTime;

	if (mDeltaTime < 0.0)
		mDeltaTime = 0.0;
}
728x90