9. díl - SDL - Práce s časovačem

C++ SDL SDL - Práce s časovačem

V dnešním díle se podíváme na časovač. Ukážeme si funkce pro uspání, spuštění funkce po určitém čase a nakonec si řekneme něco o tom, jak můžeme hru optimalizovat, abychom co nejméně zatěžovali procesor a zároveň aby měl uživatel co nejlepší zážitek.

Uspání vlákna

Ekvivalentem uspání vlákna je v SDL funkce SDL_Delay. Ta přijímá jako parametr počet milisekund, po které vlákno uspí. Je nutné si uvědomit, že se nejedná o přesnou hodnotu, ale o minimální hodnotu, po kterou bude vlákno uspáno. To, za jak dlouho bude vlákno opět aktivováno, závisí na plánovači v operačním systému. Zajímavou hodnotou je 0. Pokud funkci předáme hodnotu 0, řekneme vlastně operačnímu systému, že nyní už nepotřebujeme nic dělat. Můžeme si to představit tak, že operační systém vlákno neuspí, pouze pokračuje v cyklu plánování.

Druhou věcí, kterou si musíme uvědomit je, že uspání a probuzení vlákna něco stojí. Nějakou dobu tedy trvá, než se vlákno uspí a než se opět probudí. Experimentálně byla tato hodnota určena na 10ms. Hodnoty pod 10ms tedy nebudou přesné a zpravidla bude čas uspání vždy vyšší.

Časovače

Abychom mohli časovač používat, musíme do SDL_Init předat parametr SDL_INIT_TIMER.

Časovač se aktivuje po námi zadané době. Pro práci má SDL funkce SDL_AddTimer a SDL_RemoveTimer. Jako první parametr funkce přijímá počet milisekund, za kterou se akce spustí. Jako druhý parametr je callback, který se zavolá. Třetím parametrem je ukazatel na void, který se předá callbacku při volání. Callback přijímá jako první parametr interval, ve kterém SDL předá čas, který uběhl od posledního zavolání. Jako druhý parametr je předán ukazatel, který jsme nastavili třetím parametrem při vytváření časovače. Callback vrací čas v milisekundách, který má SDL čekat, než znovu funkci zavolá. Můžeme tedy interval vždy měnit, ale jen v závislosti na vrácených hodnotách z callbacku. Pokud callback vrátí hodnotu 0, časovač se zruší a nebude znovu zavolán.

Důležité je, že callback běží v samotném vlákně. S tím souvisí také kontext vlákna. Nemáme přístup k proměnným, které jsme v hlavním vlákně vytvořili. Musíme si dát také pozor na přístup do paměti. Pokud chceme stejnou třídu používat v hlavním vlákně i v callbacku, je ji potřeba uzamknout a používat atomické operace. Práce s více vláknovými aplikacemi je mimo rozsah tohoto seriálu, a proto nebudu tyto problémy dále rozebírat, a doporučuji časovač používat opatrně.

Získání uběhnutého času

Čas můžeme v SDL získat dvěma způsoby. Čas se počítá od inicializace SDL (od volání funkce SDL_Init). To také znamená, že informace jsou většinou adekvátní vzhledem k jinému volání funkce. Pokud budeme chtít zjistit, jak dlouho trvá nějaká část kódu, zavoláme funkci před a po operaci. Po odečtení těchto hodnot zjistíme, jak dlouho operace trvala.

První z funkcí je SDL_GetTicks. Ta vrací čas v milisekundách. Vrací Uint32, proto přibližně po 49 dnech hodnota přeteče a funkce bude vracet stejné hodnoty jako na počátku. Pokud budeme dělat aplikaci, která může běžet tak dlouho, je potřeba s tím počítat.

Druhou funkcí je SDL_GetPerfor­manceCounter. Je mnohem přesnější než SDL_GetTicks, ale vrací počet tiků. Budeme ji používat především pro optimalizaci, abychom přesně věděli, jak dlouho akce trvala. Tentokrát vrací funkce Uint64, musíme si dát tedy pozor, abychom výslednou hodnotu neuložili v jednoduchém integeru. Za prvé je to hodnota bez znaménka a za druhé by velice rychle přetekl. Budeme-li chtít získat čas v sekundách, budeme potřebovat funkci SDL_GetPerfor­manceFrequency. Navrací počet tiků za sekundu. Po vydělení dostaneme čas v sekundách. Pokud budeme chtít získat milisekundy, musíme hodnotu vrácenou SDL_GetPerfor­manceCounter vynásobit tisícem.

Použití jiných knihoven

Použití SDL časování je čistě dobrovolné. Samozřejmě existují i další knihovny, které plní stejnou funkci, a často i mnohem lépe. Není mým cílem zde popisovat všechny knihovny a jejich použití. Pro začátek odkáži na ctime a chrono které by měly být multiplatformní a podporované většinou kompilátorů. Pokud máte stále málo, stačí použít Google.

Optimalizace FPS

Pro optimalizaci FPS (nebo také frame-rate) existuje několik způsobů. Každý má svoje výhody a nevýhody. Nebudu se konkrétně zabývat implementací, popíšu pouze způsoby, jakými můžeme optimalizace docílit.

Proč optimalizovat

Lidské oko má své omezení. Do frekvence 24-30 snímků za sekundu dokážeme rozeznat jednotlivé obrazy mezi sebou. Se zvyšováním snímků za sekundu se obraz stává plynulejší. Při frekvenci nad 60Hz už oko nerozezná rozdíl, protože větší frekvence není schopné vnímat. Často vidíme, že se hry spouštějí s 60FPS.

Druhou věcí je obnovovací frekvence monitoru. Pokud má monitor obnovovací frekvenci 60Hz (většina) nebo 75Hz, více snímků na něm nezobrazíme, ani kdybychom se rozkrájeli. Obnovovací frekvenci můžeme v SDL zjistit ze struktury SDL_DisplayMode. K vytvoření struktury můžeme použít funkci SDL_GetCloses­tDisplayMode, které předáme náš vytvořený SDL_DisplayMode, a ona vrátí SDL_DisplayMode, který se tomu našemu nejvíce přibližuje. Druhou funkcí je SDL_GetCurren­tDispalyMode, která vrátí aktuální display mode. Naše hra by se měla vykreslovat stejně rychle, jaká je obnovovací frekvence monitoru. Posledním faktorem je spotřeba procesoru. Proč budeme procesor vytěžovat na maximum, když to není potřeba? Na stolním PC o tolik nejde, to je připojeno k elektrické síti neustále, ale na noteboocích a tabletech výrazně snížíme dobu provozu, protože to baterka nezvládne.

Popíšu způsoby, jak můžeme FPS optimalizovat. V praxi se většinou používají dohromady. Nebudu ukazovat konkrétní implementace, pouze uvedu teoretické fungování. Praktickou ukázku implementujeme až ve výsledné hře.

Stroje, na kterých aplikace poběží, musíme rozlišit na dvě skupiny. Jedna skupina dokáže vykreslovat rychleji než 60Hz, druhá naopak méně. Pokud chceme, aby aplikace správně fungovala, musíme vyřešit oba problémy. Kdybychom například nastavili, že se postava bude v každé aktualizaci posouvat o 5 pixelů, na rychlejším stroji se bude postava posouvat rychleji než na stroji pomalejším.

Delta timing

Jedná se o princip aktualizace scény na základě uběhnutého času. Delta v názvu vychází z matematického výrazu delta, tedy rozdíl. Uvedeme-li příklad na postavě ve hře, delta timing bude pracovat tak, že postava bude mít nadefinovanou rychlost pohybu. Například 10 pixelů za sekundu. V aktualizační funkci budeme mít uložený čas minulé aktualizace a zjistíme aktuální čas. Na základě rozdílu určíme vzdálenost, o kterou se postava posune. Pokud se bude aktualizační funkce volat každých 100ms, v každém cyklu se posune o jeden pixel.

Výhodou je použitelnost i pro sestavy, které nedisponují dostatečným výkonem. Delta timingem docílíme toho, že na všech zařízení se bude postava pohybovat stejně rychle, nezávisle na výkonu procesoru. Nevýhodou je, že neřeší frekvence vyšší. Delta timing vyřeší rychlost pohybu na všech strojích, ale nedokážeme s jeho pomocí získat konstantních 60FPS.

Uspávání vlákna

Druhou možností je vždy vlákno uspat. Snížíme tím FPS a nebudeme vytěžovat procesor. Problémem jsou stroje, které nestíhají vykreslovat 60 snímků za sekundu. Pokud bychom vlákno ještě uspali, jejich FPS by snížilo a hra by se nedala hrát. V herní smyčce by musely probíhat výpočty, podle kterých by se program rozhodl, zda má vlákno uspat nebo ne. Jak jsem zmiňoval, nemůžeme na funkci SDL_Delay spoléhat, protože její přesnost závisí na operačním systému. Navíc tím celou aplikaci zastavíme. Pro uživatele se bude jevit jako zamrznutá.

Uspání ani neřeší rychlost pohybu. Na rozdíl od delta timingu, zde je pohyb vypočítán vždy k jedné smyčce. Pokud stroj nebude zvládat potřebnou rychlost vykreslování, bude se hra zdát pomalejší. Například při posunu 2 pixelů za snímek se na stroji, který aplikaci spustí s 60 FPS, postava za sekundu posune o 120 pixelů, zatímco na stroji, který zvládá pouze 30 FPS, bude posun pouze o 60 pixelů.

Ukázkový příklad

Do ukázkového příkladu jsem dnes přidal pouze uspání vlákna. Navíc jsem přidal část kódu, která vypisuje FPS. Všimněme si, jak se zpomalil posun přechodu, který je nyní mnohem plynulejší.

Od příští lekce se budeme víc věnovat událostem. Podíváme se na události vyvolané myší a klávesnicí a zběžně se podíváme i na další zařízení.


 

Stáhnout

Staženo 157x (9.57 MB)

 

  Aktivity (1)

Článek pro vás napsal patrik.valkovic
Avatar
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu.

Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!


 


Miniatura
Předchozí článek
SDL - Errory a logování
Miniatura
Všechny články v sekci
SDL
Miniatura
Následující článek
SDL - Události klávesnice

 

 

Komentáře

Avatar
Jan Vargovský
Redaktor
Avatar
Jan Vargovský:

Oko rozezná rozdíl mezi 60 a 60+ FPS... Hry jsou omezené z drtivé většiny na 60, protože více prostě nedovolí monitor.

 
Odpovědět 8.9.2015 8:20
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 1 zpráv z 1.