이번글은 블로깅을 하면서 발견한 시간관련 글들을
종합하여 편집 함으로써 이부분에 대해 궁금해 하시는
분에게 도움이 되고자 해서 편집 & 작성하여 보았습니다.
1. Tick 구하기 - 1
타이머 문제로 골몰하다 멀티미디어 라이브러리의 바이너리를
뒤적거리기로 했다. 한참동안 winmm.lib 의 네이티브 코드를
뒤지다가 재미난 것을 한가지 발견. 일단 해당 부분을 뜯어
간단히 인라인 어셈으로 옮겨 보았다.
inline unsigned __int64 getMisteryCount()
{
__asm
{
mov edx,dword ptr ds:[7FFE000Ch]
mov eax,dword ptr ds:[7FFE0008h]
}
}
아무런 문서도 없이 바이너리를 뒤지다 얻어낸 정보인지라
저 주소가 무엇을 의미하는지는 나도 모른다. 구글에도
없는것을 보니 완전한 un-documented feature 같다.
어쨌든 저 주소에는 1/10000 초 단위의 시간이 저장되어 있다.
물론 아래와 같이 10000 을 나누어 milli-second 단위로 변환해서
사용하는것도 가능하다.
inline unsigned int getElapsedMilliSecond()
{
return static_cast< unsigned int >
( getMisteryCount()/10000 );
}
mov 를 두번 호출할 뿐인지라 속도는 경이적이다.
빠르지만 최악의 정밀도를 보여주는 GetTickCount()
보다도 4배정도 빠르다. 아무일도 하지 않고 루프만 돈
noproc() 에 거의 근접한다.
![]() |
getMisteryCount() 와 getElapsedMilliSecond() 의
속도차이의 이유는 64-bit div 연산에 있다. 32-bit 프로세서에서
__int64 나누기를 하려면 수십개의 인스트럭션이 필요하다.
테스트 결과 getMisteryCount() 는 Win95/98/Me 등에선 동작하지
않는다. XP , 2000, 2003 등 NT 커널 기반 OS 에서만 돌아가는 듯
하다. 어쨌건 퍼포먼스 카운트용으로 이보다 더 좋은 타이머는
없을 듯 하다.
2. Tick 구하기 - 2
대부분 Speed Hack은 kernel.dll에 있는 GetTickCount()나
winmm.dll에 있는 timeGetTime() 함수를 조작해서 만들어 진다.
그러나 그러한 역할을 하는 걸 Win32API를 쓰지 않고 아래와 같은
ASM을 사용한다면 해결을 할 수 있지 않을까 싶다.
_asm
{
RDTSC
mov DWORD PTR iCurrentTick, eax
mov DWORD PTR iCurrentTick+ 4, edx
}
RDTSC 어셈은 8x86 CPU에 들어 있는 ASM이다.
CPU의 클럭값을 eax와 edx에 return하는 어셈이라
2개의 레지스터에서 메모리로 옮기면 GetTickCount()
역할을 할 수 있다.
3. Win32 환경에서의 타이머 종류
Ansi C의 time(), _ftime()
Ansi 표준 C 라이브러리의 함수들로,
time()은 최소 1초 단위의 값을 돌려준다.
메인 루프 제어용으로는 부적합하나 게임 저장
날짜/시간의 기록 등에는 유용할 것임.
_ftime()은 밀리초(1000분의 1초) 단위의 값을 돌려준다.
Win32 API의 timeGetTime() 함수
흔히 멀티미디어 타이머라고 하는 것으로,
윈도우즈가 시작된 이후 흐른 시스템 시간을 돌려준다.
단위는 밀리초. 기본 정밀도는 Win9x의 경우 1 밀리초,
NT 패밀리의 경우 5 밀리초 이상이라고 함.
이정도면 _ftime()과 함께 게임에서 써먹을 수 있을 만한 함수.
타이머의 해상도는 timeGetDevCaps()와
timeBeginPeriod()로 조정할 수 있다.
Win32 API의 WM_TIMER 메시지
일정한 시간 주기로 발생하는 메시지.
주기의 최소 단위는 역시 밀리초 단위.
메시지 펌프에서 잡거나 SetTimer()를 이용해서
콜백 함수를 호출하게 만들 수 있음.
그러나 메시지의 우선 순위가 낮기 때문에 정확하지 못하다.
Win32 API의 GetTickCount() 함수
시스템 틱 카운트를 돌려주며, 단위는 밀리초.
Win9x의 경우 기본 정밀도는 약 55 밀리초,
NT 급은 10에서 16 정도... timeGetTime()보다 못함...
고해상도 타이머
하드웨어가 고해상도 타이머를 지원하는 경우라면
가장 좋은 선택으로, 매우 정밀한 시간을 얻을 수 있다.
얻을 수 있는 시간의 해상도는 하드웨어마다 다르나,
어쨌든 적어도 위에 나온 것들보다는 정밀하다고 함.
관련 함수(자세한 사항은 MSDN 참고)
둘 다 windows.h만 포함시키면 됨...
BOOL QueryPerformanceCounter(
LARGE_INTEGER *lpPerformanceCount // pointer to counter value
);
BOOL QueryPerformanceFrequency(
LARGE_INTEGER *lpFrequency // address of current frequency
);
고해상도 타이머의 해상도 및 지원 여부 알아내기
QueryPerformanceFrequency(&ticksPerSecond)의 반환값이 0이
아니면 고해상도 타이머를 지원하는 것이다.
이 때 tickPerSecond에는 초 당 틱 수가 설정된다.
예:
LARGE_INTEGER ticksPerSecond;
if (!QueryPerformanceFrequency(&ticksPerSecond))
{
// 지원하지 않음
return false;
}
현재 시간 얻기
QueryPerformanceCounter(&ticks)를 호출하면 ticks에
성능 카운터 수(음.. 컴퓨터가 켜진 후 지나간 틱 수)가 설정된다.
이를 위에서 얻은 ticksPerSecond로 나누면 초
단위의 시간을 얻을 수 있다.
예:
LARGE_INTEGER ticks;
QueryPerformanceCounter(&ticks);
float seconds = ((float)ticks.QuadPart
/ (float)ticksPerSecond.QuadPart;
지나간 시간 얻기
게임에서 실제로 필요한 것은 이전 프레임으로부터 흐른 시간인데,
그냥 현재 시간에서 이전 시간을 빼면 됨...
예: float GetElapsedSeconds(unsigned long elapsedFrames = 1)
{
static LARGE_INTEGER s_lastTime = m_startTime;
LARGE_INTEGER currentTime;
QueryPerformanceCounter(¤tTime);
float seconds = ((float)currentTime.QuadPart -
(float)s_lastTime.QuadPart) / (float)m_ticksPerSecond.QuadPart;
// reset the timer
s_lastTime = currentTime;
return seconds;
} // end GetElapsedSeconds()
QueryPerformanceCounter의 문제점
일부 메인보드/칩셋에서 시간이 건너뛴다는 보고가 있음.
RDTSC
인텔 펜티엄 계열 CPU에서 제공하는 어셈블리 명령이다.
펜티엄은 내부적으로 TSC(Time Stamp Counter)라는
64비트 카운터를 유지하는데 이 카운터의 값은 클럭
사이클마다 증가한다.
RDTSC 명령은 내부 TSC 카운터의 값을 EDX와 EAX 레지스터에
복사하는 명령이다.
이 명령은 6~11 클럭을 소요한다. 고해상도 타이머가
이 명령을 이용해 구현되었다고 한다.
시간에 기반한 게임 갱신
고정 시간 간격
한 프레임에 걸리는 시간을 고정시키는 것.
간단히 말하면, 한 프레임에 걸리는 표준 목표
시간을 설정하고, 한 프레임에 걸린 시간이 그보다
작으면 남는 부분만큼 아무 일도 하지 않고 기다리는 것이다.
의사코드:
게임 메인 루프
{
현재시각을 얻는다.
한 프레임 처리;
흐른시간 = 현재시각 - 아까 얻은 현재시각
만일 ( 흐른시간 < 목표시간)
{
(목표시간 - 흐른시간)만큼 쉰다. //예: Sleep(...);
}
}
장점: 시간 관리가 직관적이고 간편하다(시간 자체를 중심으로 바라볼 때).
또한 멀티미디어와의 시간 동기화가 편하다
(애니메이션과 배경음악/대사를 일치시키는 등).
단점: 한 프레임 처리에 걸린 시간이 목표시간보다 더 긴 경우 게임의 속도가
일정하지 않게 된다. 또한 최소 사양 이하의 컴퓨터라면 게임 전체가 느리게
돌아가게 된다.
가변 시간 간격
게임의 기능 및 객체들이 이전 프레임 이후 흐른 시간
(이하 프레임 시간)에 기반해서 갱신되게 하는 것.
예를 들어 어떤 객체가 1초당 10미터를 수평 이동
한다고 하면, 객체의 x 좌표는: x = x + 10 * (프레임 시간)
이렇게 하면 프레임 시간에 상관없이(즉 CPU 속도나 기타
다른 요인과 독립적으로) 객체는 항상 초 당 10 미터의
속력을 가지게 된다.
장점: 객체의 입장으로 바라볼 때 직관적.
게임을 실세계의 단위(미터, 초 등)에 기반해서 모델링할 때 크게 유용할 수 있다.
단점: 모든 객체들의 갱신 방식을 시간에 기반한 함수로 생각할 수 있어야 한다.
3D라면 당연하지만 전통적인 2D 게임이라면 사고의 전환이 필요함...
출처 : http://dual.inxzone.net/backup/blog/index.php?pl=184&ct1=1


















