KoreanFoodie's Study
4-4. 시간 측정과 애니메이션 (타이머, GameTimer 클래스) 본문

'DirectX 12를 이용한 3D 게임 프로그래밍 입문'을 읽으며 내용을 정리하고 중요한 부분을 기록하는 글입니다.
4-4. 시간 측정과 애니메이션 (타이머, GameTimer 클래스)
알아 두어야 할 개념들 :
1. 성능 타이머
정밀한 시간 측정을 위해, 이 책의 예제들은 Windows가 제공하는 성능타이머(perfomance timer)를 사용한다. 이를 성능 카운터(performance counter)라고도 부른다(Windows.h 를 include).
성능 타이머의 시간 측정 단위는 '지나간 클럭 틱(tick)'들의 개수(count)이다. 성능 타이머로부터 틱 수 단위의 현재 시간을 얻을 때에는 다음과 같이 QueryPerformanceCounter 함수를 사용한다.
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
이 함수는 반환값이 아니라 매개변수를 통해 64비트 정수로 현재 시간 값을 돌려준다.
QueryPerformanceFrequency 함수는 초 단위 틱 수를 돌려준다. 이 값의 역수를 취해 틱당 초 수를 구한 다음, 실제 틱 수 valueInCounts를 곱하면 초 단위 시간이 나온다.
어떤 작업에 걸린 시간을 계산할 때는 다음과 같은 값을 활용한다.
__int64 A = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&A);
/* 어떤 작업을 수행한다. */
__int64 B = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&B);
어떤 작업에 걸린 시간은 (B-A) * mSecondsPerCount 초이다.
2. GameTimer 클래스
GameTimer 클래스의 구현을 보자.
class GameTimer
{
public:
  GameTimer();
  float GameTime()const; // in seconds
  float DeltaTime()const; // in seconds
  void Reset(); // Call before message loop.
  void Start(); // Call when unpaused.
  void Stop(); // Call when paused.
  void Tick(); // Call every frame.
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;
}
3. 프레임 간 경과 시간
프레임 사이의 경과 시간을 구하는 코드를 보자.
void GameTimer::Tick()
{
  if( mStopped )
  {
    mDeltaTime = 0.0;
    return;
  }
  // Get the time this frame.
  __int64 currTime;
  QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
  mCurrTime = currTime;
  // Time difference between this frame and the previous.
  mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount;
  // 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;
  }
}
float GameTimer::DeltaTime()const
{
  return (float)mDeltaTime;
}
응용 프로그램의 메시지 루프에서는 Tick 메서드를 다음과 같은 방식으로 호출한다.
int D3DApp::Run()
{
  MSG msg = {0};
 
  mTimer.Reset();
  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();
      if( !mAppPaused )
      {
        CalculateFrameStats();
        Update(mTimer); 
        Draw(mTimer);
      }
      else
      {
        Sleep(100);
      }
    }
  }
  return (int)msg.wParam;
}
4. 전체 시간
전체 시간은 타임어택 등의 구현에 꼭 필요하다.

또한 일시정지 상황에서 물체의 이동도 제어해야 한다.

응용 프로그램이 타이머를 일시 정지하거나 재개하는 코드를 보자.
void GameTimer::Stop()
{
  // If we are already stopped, then don’t do anything.
  if( !mStopped )
  {
    __int64 currTime;
    QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
    // Otherwise, save the time we stopped at, and set 
    // the Boolean flag indicating the timer is stopped.
    mStopTime = currTime;
    mStopped = true;
  }
}
void GameTimer::Start()
{
  __int64 startTime;
  QueryPerformanceCounter((LARGE_INTEGER*)&startTime);
  // Accumulate the time elapsed between stop and start pairs.
  //
  //        |<-------d------->|
  // ---------------*-----------------*------------> time
  //       mStopTime    startTime   
  // If we are resuming the timer from a stopped state...
  if( mStopped )
  {
    // then accumulate the paused time.
    mPausedTime += (startTime - mStopTime); 
    // since we are starting the timer back up, the current 
    // previous time is not valid, as it occurred while paused.
    // So reset it to the current time.
    mPrevTime = startTime;
    // no longer stopped...
    mStopTime = 0;
    mStopped = false;
  }
}
마지막으로 TotalTime 멤버 함수는 Reset이 호출된 이후 흐른 시간에서 일시 정지된 시간을 제외한 시간을 돌려준다.
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: 
//
//        previous paused time
//         |<----------->|
// ---*------------*-------------*-------*-----------*------> time
// mBaseTime             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);
  }
}
'Game Dev > DirectX' 카테고리의 다른 글
| 4-3. Direct3D 초기화 (ID3D12Device, Fence, 4X MSAA 점검 등) (0) | 2021.11.17 | 
|---|---|
| 4-2. Direct3D 기초 : CPU와 GPU의 상호작용 , 명령 대기열, CPU/GPU 동기화 (0) | 2021.11.17 | 
| 4-1. Direct3D 기초 : COM, 텍스쳐 형식, 교환사슬과 페이지 전환, 깊이 버퍼링, 다중 표본화, DXGI, 상주성 (0) | 2021.11.16 | 
| 3. 선형변환, 아핀변환, 좌표 변환, DirectXMath 변환 함수 (0) | 2021.11.15 | 
| 2. DirectXMath 라이브러리의 행렬 다루기 (0) | 2021.11.15 |