Directx12 3D 프로그래밍 입문/4. Directx12 초기화

4.4 시간 측정과 애니메이션

ftftgop3 2023. 10. 22. 17:45

애니메이션 실행 시 정확한 동작을 위해서 프레임 간의 시간을 구해야 한다.

프레임 간의 경과 시(elapsed time) : 인접한 두 프레임 사이의 시간

Windows 에서 지원하는 성능 타이머 (performance counter) 사용

시간의 측정 단위는 “지나간 클럭 틱 tick ” 이라 한다.

초당 틱 수의 역수로 1틱 당 초수를 계산 하는 방식

//현재 시간을 64 비트 틱 형태로 반환
__int currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

//선능 타이머의 주파수(초당 틱 수)
__int countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*) &countsPerSec);

//틱수 당 초
mSecondPerCount = 1.0 / countsPerSec;

//측정한 두 시간 값 사이의 상대적 차이가 중요 
__int64 StartTick =0;
QueryPerformanceCounter((LARGE_INTEGER*)&StartTick); //시작 시간을 틱으로 변환
//작 업 수 행
__int64 EndTick =0;
QueryPerformanceCounter((LARGE_INTEGER*)&EndTick); //끝 시간을 틱으로 변환

PlayTime = (EndTick - StartTick) * mSecondPerCount; // 걸린 시간 계산 

예제 클래스 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); // 1초당 틱 수 
	mSecondsPerCount = 1.0 / (double)countsPerSec; //1틱당 초 (역수로 구함)
}
//실시간 초당 프레임수는 30 은 넘어야 한다.
//초당 프레임 수 
void GameTimer::Tick()
{
	//스탑 시 리턴, 
	if( mStopped )
	{
		mDeltaTime = 0.0;
		return;
	}
	//현재 시간을 틱으로 변환
	__int64 currTime;
	QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
	mCurrTime = currTime;

	//현재 틱 - 이전 틱 * 1틱 당 초 
	mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount;

	//이전 틱에 현재 틱 갱신
	mPrevTime = mCurrTime;
	// SDK 문서 에 따르면 프로세서가 절전 및 다른 프로세서와 엉킹 경우 음수가 나올수 있다
	// 음수가 나오면 프로그램이 죽을 수 있어서 예외 처리
	if(mDeltaTime < 0.0)
	{
		mDeltaTime = 0.0;
	}
}

메시지 루프에서 Game Timer 동작 순서

int D3DApp::Run()
{
	MSG msg = {0};
 
	mTimer.Reset(); //시간 초기화
	
	while(msg.message != WM_QUIT) //윈도우 메시지 종료 없으면 계속 동작
	{
		// Window 메시지 확인 및 처리
		if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
		{
            TranslateMessage( &msg );
            DispatchMessage( &msg );
		}
		// window 메시지 없으면 애니메이션 및 다른 동작
		else
      {	
			mTimer.Tick(); // 프레임 마다 시간 갱신

			if( !mAppPaused ) // 프로그램 멈춤 확인
			{
				CalculateFrameStats();
				Update(mTimer); //시간 갱신 값으로 애니메이션 동작 	
        Draw(mTimer); 
			}
			else
			{
				Sleep(100);
			}
        }
    }

	return (int)msg.wParam;
}

void GameTimer::Reset()
{
	__int64 currTime;
	QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

	mBaseTime = currTime;
	//초기화 시 이전 틱을 넣어줘야 Tick() 동작 시
	// 프레임 간 사이 시간을 구할수 있다.
	mPrevTime = currTime; 	
	mStopTime = 0;
	mStopped  = false;
}
//타이머 정지
void GameTimer::Stop()
{
	//Stop 상태 체크
	if( !mStopped )
	{
		__int64 currTime;
		QueryPerformanceCounter((LARGE_INTEGER*)&currTime); //현재 시간 틱으로 변환

		mStopTime = currTime;//멈춘 시점 저장
		mStopped  = true;//Stop 상태 변경
	}
}
//타이머 시작
void GameTimer::Start()
{
	__int64 startTime;
	QueryPerformanceCounter((LARGE_INTEGER*)&startTime); //현재 시간 저장

	// 정지 시간과 다시 시작의 사이 값 
	//
	//                     |<-------d------->|
	// ----*---------------*-----------------*------------> time
	//  mBaseTime       mStopTime        startTime     
	//멈춘 상태에서 시작(재개)
	if( mStopped )
	{
		//정지한 시간의 값
		mPausedTime += (startTime - mStopTime);	
		//이전 시간 갱신
		mPrevTime = startTime;
		mStopTime = 0; //초기화
		mStopped  = false;//stop 해제
	}
}

// Returns the total time elapsed since Reset() was called, NOT counting any
// 프로그램 전체 흐른 시간, 정지된 시간 제외
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 )
	{
		//stoptime 기준으로 누적된 정지된 시간, 처음 프로그램 시간 
		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);
	}
}