Posts tagged ‘timer’

Dokładny czas na wielu platformach

2010-06-26 23:54

Gry i inne podobne aplikacje wymagają precyzyjnego pomiaru czasu, aby realizować obliczenia związane z fizyką, animacją, itp. Jak niemal wszystko, API potrzebne do tego celu jest różne w zależności od platformy, czyli (głównie) systemu operacyjnego. Dzisiaj chciałbym pokazać, jak wygląda to na dwóch najpopularniejszych systemach: Windows i POSIX (a więc między innymi na wszelkiego typu Linuksach).

Interfejs programistyczny systemu spod znaku okienek udostępnia dwie funkcje od dokładnego mierzenia czasu. Są to QueryPerformanceFrequency i QueryPerformanceCounter. Tę pierwszą wywołuje się tylko raz, a jej wynikiem jest częstotliwość wewnętrznego systemowego zegara, który służy do precyzyjnego pomiaru upływającego czasu. Wyrażana jest ona w liczbie “tyknięć” na sekundę i na dzisiejszym sprzęcie może być bardzo duża, bo liczona w (kilku(nastu/dziesięciu)) milionach.
Druga funkcja jest używana nieporównywalnie częściej, gdyż to ona zwraca aktualną wartość zegara, czyli liczbę jego “tyknięć”. Stąd wynika, że obliczenie tej wartości w sekundach wymaga podzielenia rezultatu QPC przez uzyskaną wcześniej częstotliwość:

  1. LARGE_INTEGER freq, counter;
  2. QueryPerformanceFrequency (&freq);
  3. // ...
  4. QueryPerformanceCounter (&counter);
  5. double secs = counter.QuadPart / (double)freq.QuadPart;

Używana tu unia LARGE_INTERGER to nic innego, jak zwykła liczba 64-bitowa.

W systemach POSIX-owych rzecz jest odrobinę prostsza, jako że tutaj dokładność zegara jest ściśle ustalona. Funkcja gettimeofday (z nagłówka sys/time.h) zwraca rezultat z precyzją mikrosekundową w postaci struktury timeval:

  1. struct timeval tv;
  2. gettimeofday (&tv, 0);
  3. double sec = tv.tv_sec + tv.tv_usec / 1000000.0;

Część odpowiadającą niepełnym sekundom (pole tv_usec) trzeba więc podzielić przez milion.

Chcąc mieć bardziej uniwersalne rozwiązanie, możemy napisać klasę opakowującą zegar z implementacją kontrolowaną docelową platformą, na którą kompilujemy:

  1. class Clock
  2. {
  3.     #ifdef _WINDOWS
  4.         LARGE_INTEGER freq;
  5.         public: Clock() { QueryPerformanceFrequence (&freq); }
  6.     #endif
  7.  
  8.     double GetTicks() const
  9.     {
  10.         #ifdef _WINDOWS
  11.             LARGE_INTEGER counter;
  12.             QueryPerformanceCounter (&counter);
  13.             return counter.QuadPart / (double)freq.QuadPart;
  14.         #else
  15.             struct timeval tv; gettimeofday (&tv, 0);
  16.             return tv.tv_sec + tv.tv_usec / 1000000.0;
  17.         #endif
  18.     }
  19. };

Można by na koniec zapytać jeszcze, co tak naprawdę znaczy zwracana wartość zegara. Kiedy wynosi ona zero?… Otóż wbrew pozorom odpowiedź na to pytanie nie jest istotna, bo w praktyce interesuje nas tylko interwał czasowy, tj. różnica między dwoma wskazaniami timera. To na jej podstawie liczymy zmianę w prędkościach obiektów, klatkach animacji czy w końcu niezbędnie konieczną do wyświetlenia wartość FPS :)

Tags: , , , ,
Author: Xion, posted under Programming » 4 comments
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.