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>