https://soonang2.tistory.com/26
시간 동기화 (1) 에 대한 글에서 이어지는 내용입니다.
위 포스팅을 보고 오시는 것을 추천드립니다.
이제 시간 동기화 관련 코드를 구현해보자.
다만 구현하기 전에 새로운 함수에 대해서 살펴보아야 한다.
이전에 우리의 PC에서의 초당 함수 호출 횟수를 확인하는 부분을 설계하여서
그 값을 살펴본 적이 있었고, 그 값을 무려 2만 ~ 4만의 수치가 나왔다.
즉, 초당 2만 ~ 4만의 프레임을 갖는다는 뜻이 된다.
하지만, 지금까지 다루던 GetTickCount() 함수는 겨우 초당 1000회, 즉 1ms 단위로 동작하는 함수이다.
이는 수만에 달하는 함수 호출 횟수를 다루기에는 부적합하다.
(GetTickCount() 함수가 1번 호출되는 동안, 약 26번의 프레임이 돌아가게 되는 것이다.)
그래서 등장한 것이 Queryperformancecounter (QPC) 이다.
https://docs.microsoft.com/ko-kr/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
지금까지는 GetTickCount()로도 충분했기에 임시로 사용했지만, 이제부터는 1ms (초당 1000회) 한계를 해결하기
위한 함수인 고해상도 타임스탬프 : Queryperformancecounter (QPC) 함수를 사용할 것이다. (초당 1000만회)
WINBASEAPI
BOOL
WINAPI
QueryPerformanceCounter(
_Out_ LARGE_INTEGER* lpPerformanceCount
);
WINBASEAPI
BOOL
WINAPI
QueryPerformanceFrequency(
_Out_ LARGE_INTEGER* lpFrequency
);
위는 QPC 내부 구조이다. (상세한 내용은 보여지지 않고 있다.)
QPC의 매개변수로 받은 LARGE_INTEGER 변수를 _Out_ 용도로 값을 뱉어준다는 것을 알 수 있다.
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
} DUMMYSTRUCTNAME;
struct {
DWORD LowPart;
LONG HighPart;
} u;
LONGLONG QuadPart;
} LARGE_INTEGER;
QueryPerformanceCounter 함수는 LARGE_INTEGER라는 특수한 자료형을 매개변수로 취하고,
LARGE_INTEGER는 위와 같은 형태의 STRUCT 2개가 묶인 UNION 자료형이다.
GetTickCount()는 1초가 벌어지면 카운트 값 1000이 차이가 난다는 고정 값이 있었지만,
1초가 벌어졌을 때, 카운트 값 차이가 얼만큼 나는지도 직접 구해와야 한다.
m_llFrequency의 값이 10,000,000인 것을 볼 수 있다.
(1) 기존에 GetTickCount64()로 구현했던 코드
void CCore::progress()
{
static int callCount = 0;
++callCount;
static int iPrevCount = GetTickCount64();
int iCurCount = GetTickCount64();
if (iCurCount - iPrevCount > 1000) // 1초 경과시 진입
{
iPrevCount = iCurCount;
callCount = 0;
}
// Manager 클래스들 일괄 Update
CTimeMgr::GetInst()->Update(); // 매 프레임마다 TimerMgr이 업데이트 되도록
update();
// Manager 클래스들 일괄 Render
render();
}
(2) QPC를 이용해서 새로 구현한 코드
코드의 내용을 미리 보자면, 아래와 같다.
CTimeMgr.h 코드
#pragma once
class CTimeMgr
{
SINGLE(CTimeMgr);
private:
// Frame Per a Secound (FPS)
// = (1 / Delta Time)
// Time Per a Frame (Delta Time)
// = (1 / FPS)
LARGE_INTEGER m_llCurCount;
LARGE_INTEGER m_llPrevCount;
LARGE_INTEGER m_llFrequency;
double m_dDT; // 프레임 당(간) 걸린 시간
double m_FPS; // (1 / m_dDT)
double m_dAcc; // 1초 체크를 위한 누적 시간
UINT m_iCallCount;
UINT m_iFPS;
public:
void Init();
void Update();
public:
double GetDT() { return m_dDT; }
float GetfDT() { return (float)m_dDT; }
};
CTimeMgr.cpp 코드
#include "pch.h"
#include "CTimeMgr.h"
#include "CCore.h"
CTimeMgr::CTimeMgr()
: m_llCurCount{}
, m_llFrequency{}
, m_dDT(0.)
, m_FPS(0.)
, m_dAcc(0.)
, m_iCallCount(0)
{
}
CTimeMgr::~CTimeMgr()
{
}
void CTimeMgr::Init()
{
// 현재 카운트
QueryPerformanceCounter(&m_llPrevCount);
// 초당 카운트 횟수 (10,000,000)
QueryPerformanceFrequency(&m_llFrequency);
}
void CTimeMgr::Update()
{
QueryPerformanceCounter(&m_llCurCount);
// DeltaTime 값을 구한다. (1프레임 당 걸리는 시간)
m_dDT = (double)(m_llCurCount.QuadPart - m_llPrevCount.QuadPart)
/ (double)m_llFrequency.QuadPart; // QuadPart에 실제 longlong값이 들어있다.
// PrevCount를 최신 값으로 갱신
m_llPrevCount = m_llCurCount;
++m_iCallCount;
// deltaTime을 누적시키면, 현재까지 흐른 총 흐른 시간이 된다.
m_dAcc += m_dDT;
if (m_dAcc >= 1.)
{
// 초당 프레임 횟수 갱신
m_iFPS = m_iCallCount;
// Windows 창 bar에 출력
wchar_t szBuffer[255] = {};
swprintf_s(szBuffer, L"FPS : %d, DT : %lf", m_iFPS, m_dDT);
SetWindowText(CCore::GetInst()->GetMainHwnd(), szBuffer);
// 값들 0으로 다시 초기화
m_dAcc = 0.;
m_iCallCount = 0;
}
// m_FPS = 1. / m_dDT;
}
m_FPS = 1. / m_dDT 와 같은 방식으로 FPS 값을 갱신하게 되면 너무 들쑥날쑥한 값이 되므로,
m_dAcc += m_dDT 와 같이, DeltaTime을 1초가 되는 시점까지 누적시키고
if (m_dAcc >= 1.)
m_iFPS = m_iCallCount;
위와 같이, 직접 호출 횟수를 센 카운트 값을 FPS 값으로 갱신하였다.
LARGE_INTEGER m_llFrequency;
CPU 주파수에 따른 1초당 진행되는 틱수를 나타낸다. 변동이 없어서 한번만 읽어주면 된다.
LARGE_INTEGER m_llCurCount;
현재 performance counter 주파수의 포인터를 반환한다.
LARGE_INTEGER m_llPrevCount;
DeltaTime 측정을 위해 이전 Count 값을 기록한다.
// FPS는 매순간 변동이 있기 때문에,
// 항상 FPS는 어딘가에서 계산이 되고 있어야 한다.
if (GetAsyncKeyState(VK_LEFT) & 0x8000)
{
vPos.x -= 300.f * CTimeMgr::GetInst()->GetfDT();
}
if (GetAsyncKeyState(VK_RIGHT) & 0x8000)
{
vPos.x += 300.f * CTimeMgr::GetInst()->GetfDT();
}
if (GetAsyncKeyState(VK_UP) & 0x8000)
{
vPos.y -= 300.f * CTimeMgr::GetInst()->GetfDT();
}
if (GetAsyncKeyState(VK_DOWN) & 0x8000)
{
vPos.y += 300.f * CTimeMgr::GetInst()->GetfDT();
}
이렇게 구한 DeltaTime 값을 speed 값에 곱해주게 되면,
CPU 처리 속도와는 무관하게 어느 PC에서도 동일한 속도로 동작하게 된다.
(GetfDT() 함수는 DeltaTime 값을 float으로 형변환하여 반환하는 함수이다.)
이상으로 포스팅을 마칩니다.
'(2022) 공부 (Study) > 게임 개발 (Development)' 카테고리의 다른 글
[Win32API] Rendering과 Double Buffering (0) | 2022.04.13 |
---|---|
[Win32API] GetAsyncKeyState() 와 키 입력 받기 (1) (0) | 2022.04.13 |
[Win32API] 오브젝트 이동 : 시간 동기화 (1) (0) | 2022.04.13 |
[Win32API] GetTickCount64를 이용한 호출 횟수 및 프레임 체크 (0) | 2022.04.13 |
[Win32API] GetDC() ~ ReleaseDC() 함수 (0) | 2022.04.13 |