Timer

Představte si, že děláte nějaký velký dynamický svět, pro který potřebujete mnoho výpočtů závislých na uplynulém čase (pohyb, rotace, animace, fyzika). Pokud synchronizujete klasicky pomocí FPS, neobejdete se při každém vykreslení bez spousty dělení. Základem všeho je, tyto operace provádět co nejméně, abychom zbytečně nezatěžovali procesor.

Celou aplikaci založíme na myšlence, že když zajistíme, aby se scéna v čase neaktualizovala náhodně, ale vždy po určitém úseku, nebudeme muset používat žádné dělení na základě FPS. Výsledkem našeho dnešního snažení bude SDL okno s otáčející se krychličkou, která bude na jakémkoli počítači rotovat vždy stejně rychle.

Začneme klasicky vložením hlavičkových souborů a přilinkováním knihoven. Nelekejte se knihovny SDL, můžete bez problémů použít i Win32 nebo jakékoli jiné API - záleží jen na vás.

#include <sdl.h>// Vlozi SDL

#include <sdl_opengl.h>// SDL za nas vlozi OpenGL

#pragma comment (lib, "opengl32.lib")// Přilinkování knihoven

#pragma comment (lib, "glu32.lib")

#pragma comment (lib, "sdl.lib")

#pragma comment (lib, "sdlmain.lib")

Definujeme symbolickou konstantu pro časovač. Každých 20 ms, což odpovídá 50 FPS, budeme aktualizovat poměry ve scéně. Dále deklarujeme dvě proměnné potřebné pro rotaci krychle. Last_time udržuje hodnotu času minulé aktualizace scény.

// Interval timeru v ms, odpovida 50 FPS

#define TIMER_INTERVAL 20

// Promenne

float rotx, roty;// Rotace na osach x a y

unsigned int last_time = 0;// Cas minule aktualizace sceny

Uběhlo-li od minulého překreslení více než 20 ms, zvýšíme hodnoty proměnných. V tomto případě je samostatná funkce relativně zbytečná, ale až budete mít desítky nebo stovky proměnných, bude se určitě hodit.

void OnTimer()// Aktualizace sceny

{

rotx += 2.0f;// Zvysi uhly rotace

roty += 2.0f;

}

Na začátku renderovací funkce vykreslíme vše potřebné, o časování se zatím nestaráme. Kdybychom proměnné inkrementovali v závislosti na FPS přímo u glRotatef(), vše by se provádělo i 1000 krát za sekundu. To je na jednu stranu zbytečné, protože oko více než 50 FPS nemá šanci postřehnout. Na druhou stranu, kdybyste měli ve scéně příliš mnoho objektů např. gigantický částicový systém a měli u každé částice dělit pozici, rotaci, životnost a další parametry pomocí FPS, vše by se, místo omezení rychlosti na požadovanou hodnotu, nadměrným dělením totálně zpomalilo. Chápete tu ironii? Prostředek, který má rychlost regulovat, je příčinou obrovských nároků na procesor a ve výsledku aplikaci spíše zpomaluje. Až budete pracovat na nějakém větším projektu, berte v úvahu, že každý faktor ovlivňující rychlost enginu je velice důležitý.

bool DrawGLScene()

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glLoadIdentity();

glTranslatef(0.0f, 0.0f, -4.0f);

glRotatef(rotx, 1.0f, 0.0f, 0.0f);

glRotatef(roty, 0.0f, 1.0f, 0.0f);

glBegin(GL_QUADS);

// Rendering např. krychle

glEnd();

V naší verzi časovače otestujeme, zda od minulé aktualizace uběhlo 20 ms. Pokud ne, může se scéna překreslit znovu. Proměnné neinkrementujeme a scéna tudíž zůstává beze změn. Pokud už ale uběhlo potřebných 20 ms, zavoláme funkci OnTimer(), která aktualizuje proměnné. Za sekundu by se vše mělo provést přibližně 50 krát (50 FPS). Co se ale stane, když místo po např. 5 ms se na toto místo program dostane až za 100 ms? Cyklus zajistí, že se proměnné aktualizují pětkrát místo pouze jednou, jak by to bylo u obyčejného větvení s testem na 20 ms.

// Ošetření timeru

unsigned int actual_time = SDL_GetTicks();// Grabovani casu (ve WinAPI GetTickCount())

while (SDL_GetTicks() > last_time + TIMER_INTERVAL)// Aktualizovat, dokud scena neni v "soucasnem" case

{

OnTimer();// Aktualizace sceny

last_time += TIMER_INTERVAL;// Pricteni 20 ms

Kdybychom měli extrémně pomalý počítač a/nebo hodně náročnou funkci OnTimer(), mohl by v ní program zůstat déle než 20 ms. Uvízli bychom v cyklu.

if(SDL_GetTicks() > actual_time + 1000)// Trva cely cyklus dele nez 1 sekundu?

{

fprintf(stderr, "Cyklus trva prilis dlouho! Slaby pocitac!\n");

fprintf(stderr, "Program ukoncen...");

return false;// Konec funkce a programu

}

}

return true;

}

A nakonec běžná inicializace SDL a OpenGL, kterou už nebudu popisovat.

int main(int argc, char *argv[])// Proste main()

{

bool quit = false;// Flag ukonceni programu

SDL_Event event;// Udalost

if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)// Inicializace SDL

{

SDL_Quit();

return 0;

}

if (!SDL_SetVideoMode(640, 480, 16, SDL_OPENGL))// Spusteni OpenGL a zmena rozliseni

{

SDL_Quit();

return 0;

}

SDL_WM_SetCaption("Timer Example by Eosie", NULL);// Titulek okna

// Inicializace OpenGL

glViewport(0, 0, 640, 480);// Resetuje aktualni nastaveni

glMatrixMode(GL_PROJECTION);// Zvoli projekcni matici

glLoadIdentity();// Reset matice

gluPerspective(45.0f, 640.f/480.0f, 1.0f, 100.0f);// Vypočet perspektivy

glMatrixMode(GL_MODELVIEW);// Zvoli matici Modelview

glLoadIdentity();// Reset matice

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// Cerne pozadi

glClearDepth(1.0);// Mazani hloubky

glDepthFunc(GL_LESS);// Nastaveni hloubky

glEnable(GL_DEPTH_TEST);// Povoleni testu hloubky

glShadeModel(GL_SMOOTH);// Jemne stinovani

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Perspektivni korekce

while (!quit)// Smycka na kresleni a hlidani udalosti

{

while (SDL_PollEvent(&event))// Hlidani udalosti

{

switch (event.type)// Vetvi podle dosle zpravy

{

case SDL_QUIT:// Konec

quit = true;

break;

case SDL_KEYDOWN:// Klavesa

if (event.key.keysym.sym == SDLK_ESCAPE)// ESC

{

quit = true;

}

break;

}

}

Po zpracování zpráv, překreslíme scénu.

if (!DrawGLScene())// Vykresleni sceny

{

quit = true;// Konec programu

}

SDL_GL_SwapBuffers();// Prohozeni bufferu

glFlush();

}

SDL_Quit();// Ukonceni SDL

return 0;// Konec programu

}

Tímto článkem neodepisuji časování přes FPS, to ne. Vždy je dobré vidět všechny možnosti a umět se (!před začátkem práce!) rozhodnout, která z nich je pro toto určité zadání vhodnější a kterou proto použít. Musím však přiznat, že tento timer má proti časování pomocí FPS mnoho výhod, a proto jej ve svých programech upřednostňuji.

napsal: Marek Olšák - Eosie <eosie (zavináč) seznam.cz>

Zdrojové kódy