Okno s podporou OpenGL
Ve čtvrtém dílu bylo ukázáno, že jediným rozdílem mezi vytvořením „klasického“ okna a okna s podporou OpenGL je symbolická konstanta SDL_OPENGL (respektive SDL_OPENGLBLIT), která se při inicializaci předá spolu s ostatními flagy funkci SDL_SetVideoMode(). Tím bychom mohli celý článek skoro ukončit, ale zbývá probrat ještě několik věcí…
Soubory pro OpenGL
SDL nabízí programátorovi hlavičkový soubor SDL_opengl.h, který za něj vyřeší různé umístění OpenGL souborů gl.h a glu.h v některých systémech. Zároveň umožňuje používat rozšíření (extensiony), ale nevkládá je klasicky prostřednictvím glext.h, nýbrž obsah zahrnuje přímo v sobě.
Nemělo by být zapomenuto na přilinkování OpenGL knihoven (libGL.so, libGLU.so v Linuxu, popř. opengl32.lib a glu32.lib ve Visual C++ pod MS Windows), jinak program nepůjde s odkazy na neexistující funkce vytvořit.
Atributy OpenGL kontextu
Před samotným voláním SDL_SetVideoMode() by již měly být specifikovány atributy definující vlastnosti OpenGL kontextu, po vytvoření okna už nepůjdou změnit.
int SDL_GL_SetAttribute(SDL_GLattr attr, int value);
Prvním parametrem se určuje nastavovaný atribut a druhý parametr představuje jeho hodnotu. Za atributy lze použít některou z následujících konstant.
- SDL_GL_RED_SIZE, SDL_GL_GREEN_SIZE, SDL_GL_BLUE_SIZE, SDL_GL_ALPHA_SIZE
- Velikosti jednotlivých barevných komponent ve framebufferu
- SDL_GL_BUFFER_SIZE
- Velikost framebufferu v bitech
- SDL_GL_DOUBLEBUFFER
- Nula vypíná OpenGL double buffering, jednička zapíná. Tento parametr nemá nic společného s SDL_DOUBLEBUF předávaným do SDL_SetVideoMode().
- SDL_GL_DEPTH_SIZE
- Velikost bufferu hloubky
- SDL_GL_STENCIL_SIZE
- Velikost stencil bufferu
- SDL_GL_ACCUM_RED_SIZE, SDL_GL_ACCUM_GREEN_SIZE, SDL_GL_ACCUM_BLUE_SIZE, SDL_GL_ACCUM_ALPHA_SIZE
- Velikosti jednotlivých komponent v akumulačním bufferu
- SDL_GL_STEREO
- Stereoskopický OpenGL kontext; parametr není dostupný na všech systémech
- SDL_GL_MULTISAMPLEBUFFERS, SDL_GL_MULTISAMPLESAMPLES
- Zapíná fullscreenový antialiasing (fsaa) a specifikuje počet vzorků; do SDL přidán ve verzi 1.2.6 a je dostupný, pouze pokud grafická karta podporuje rozšíření GL_ARB_multisample. Tento parametr zlepšuje grafické vzezření aplikace – vyhlazuje ostré hrany barevných přechodů.
Pozn.: Poslednímu parametru, fsaa, se nebudu dále věnovat, protože moje grafická karta zmíněný extension nepodporuje. Projevuje se to tak, že se SDL_SetVideoMode() při jeho definování ukončí s chybou a následný SDL_GetError() vrátí řetězec „Couldn't find matching GLX visual“.
Pravděpodobně bude nutné vytvořit „obyčejné“ OpenGL okno a zeptat se gluCheckExtension(), zda je fsaa podporován. Pokud ano, zavřít okno a vytvořit ho znovu, tentokrát s podporou fsaa, pokud ne, pokračovat beze změny dále. Druhou možností je načítat konfiguraci ze souboru a nechat jeho zapnutí na uživateli.
Typický příklad nastavení OpenGL atributů
// Umístit PŘED volání SDL_SetVideoMode() SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // Doublebuffering ano SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 24); // 24 bitový framebuffer SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); // 24 bitový depth buffer SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); // Žádný stencil buffer SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE, 0); // Žádný akumulační buffer SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, 0); // Pouze pokud grafická karta podporuje GL_ARB_multisample // SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);// FSAA ano // SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 2);// 2 vzorky
Zjištění atributů
Někdy může být dobré po vytvoření okna zjistit, zda byl, nebo nebyl atribut nastaven. Slouží k tomu funkce SDL_GL_GetAttribute().
int SDL_GL_GetAttribute(SDLGLattr attr, int *value);
Stejně jako SDL_GL_SetAttribute() i tato funkce vrací při úspěchu 0 a při neúspěchu –1, ale měla by být volána až po SDL_SetVideoMode(). Hodnota zjišťovaného atributu bude uložena na adresu value.
Příklad na zjištění velikosti hloubkového bufferu:
// Umístit ZA volání SDL_SetVideoMode() int tmp; SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &tmp); printf("Velikost hloubkového bufferu je %d bitů\n.", tmp);
Prohození vykreslovacích bufferů
K prohození předního a zadního bufferu po renderingu scény slouží v SDL funkce SDL_GL_SwapBuffers(), bez jejího volání by se nikdy nic nezobrazilo.
void SDL_GL_SwapBuffers(void);
Pokud byla při vytváření okna definována možnost použití i klasické SDL grafiky (SDL_OPENGLBLIT), je nutné volat navíc i SDL_UpdateRects().
Získání adresy OpenGL funkce
Ukazatel na jakoukoli OpenGL rutinu (většinou se jedná o rozšíření) lze získat pomocí funkce
void *SDL_GL_GetProcAddress(const char* proc);
Parametrem je řetězec se jménem funkce a návratovou hodnotou daný ukazatel. Pokud nebude funkce nalezena, je vráceno NULL.
Specifikace OpenGL knihovny
SDL se v běžném případě linkuje s OpenGL knihovnou, která se nachází v systému, ale pokud programátor chce, může být SDL zkompilováno, aby nahrávalo OpenGL ovladač v runtimu (standardně vypnuto).
int SDL_GL_LoadLibrary(const char *path);
Tato funkce musí být opět volána ještě před SDL_SetVideoMode(), parametr specifikuje diskovou cesta k OpenGL knihovně. Pokud se ji podaří nahrát, je vrácena nula, jinak –1. Následně musí být pomocí SDL_GL_GetProcAddress() získány ukazatele na všechny OpenGL funkce, včetně glEnable(), glBegin() atd., takže použití této techniky může leckomu připadat velmi těžkopádné.
OpenGL textury a SDL_Surface
Jednou z velkých výhod spojení OpenGL s knihovnou SDL je možnost nahrávat obrázky pro textury za použití knihovny SDL_Image. Bohužel však existují dvě překážky, které znemožňují přímočaré použití.
První z nich je množství nejrůznějších vnitřních formátů SDL_Surface, které samotnému SDL sice nevadí, ale při použití kdekoli jinde se na ně musí pamatovat a vždy hlídat správný formát. Když pomineme paletový režim, stále zůstává prakticky libovolné umístění barevných složek (RGB, BGR apod.). Pravděpodobně nejspolehlivějším překonáním tohoto problému je vytvořit nový surface s pro OpenGL použitelným formátem a přes SDL_Blit() do něj zkopírovat původní surface.
Druhý problém spočívá v tom, že textura vytvořená ze SDL_Surface je v OpenGL vzhůru nohama, knihovny totiž používají vzájemně nekompatibilní souřadnicový systém – v SDL je bod 0, 0 nahoře, u OpenGL textur standardně dole.
Řešení je hned několik. Všude v programu lze zadávat v koordinát jako 1-v. Tím se sice problém spolehlivě vyřeší, ale musí se dávat pozor, aby toto pravidlo nebylo porušeno. Textury z více různých zdrojů se stanou vražednou kombinací…
Další možnost spočívá ve změně souřadnicového systému textur, stačí vložit následující kód do inicializace. Nicméně u textur z více zdrojů mohou opět vzniknout problémy, a psát tyto čtyři řádky zvlášť při každém použití nemusí být zrovna pohodlné.
glMatrixMode(GL_TEXTURE); glLoadIdentity(); glScalef(1.0f, -1.0f, 1.0f); glMatrixMode(GL_MODELVIEW);
Posledním a asi nejvhodnějším způsobem je před vlastním vytvořením textury přímo v surface natvrdo prohodit řádky. Tento postup je ukázán ve druhém ukázkovém programu z této lekce.
Poznámka ohledně změny velikosti okna
Při vytváření OpenGL aplikací pod knihovnou SDL jsem objevil jistou nekompatibilitu mezi systémy Linux a Windows. Když uživatel změní velikost okna, aplikace by měla zareagovat a přizpůsobit se. Ve Windows stačí aktualizovat OpenGL viewport a perspektivu, nicméně v Linuxu musí být zavolána i funkce SDL_SetVideoMode(). Bez ní bude program vypadat jako na následujícím obrázku – okno se sice roztáhne, ale oblast, do které se kreslí, zůstane nezměněna.
Problémem je, že volání SDL_SetVideoMode() způsobí ve Windows ztrátu OpenGL kontextu, čili resetují se všechna nastavení (barva pozadí, blending, mlha…), zmizí textury, display listy atd.
Tento problém řeším podmíněným překladem. Když kompiluji program pro Linux, definuji symbolickou konstantu, která způsobí přidání SDL_SetVideoMode() do kódu, když ve Windows, řádek s #define zakomentuji. Možná to není zrovna nejlepší cesta, ale bez problémů funguje. Pokud někdo znáte lepší řešení, svěřte se prosím do diskuse…
#define CALL_SETVIDEOMODE_WHEN_RESIZING // Ošetření události změny velikosti okna case SDL_VIDEORESIZE: #ifdef CALL_SETVIDEOMODE_WHEN_RESIZING g_screen = SDL_SetVideoMode(event.resize.w, event.resize.h, WIN_BPP, WIN_FLAGS); if(g_screen == NULL) { fprintf(stderr, "Unable to resize window: %s\n", SDL_GetError()); return false; } #endif ResizeGL(event.resize.w, event.resize.h); break;
Možná by to šlo celé automatizovat pomocí symbolických konstant, které se během překladu definují nezávisle na programátorovi a které většinou obsahují jméno kompilátoru, verzi, operační systém atd., ale proč si komplikovat život.
Ukázkové programy
RGB Trojúhelník
Příklad ukazuje nastavení OpenGL atributů a vytvoření okna s podporou OpenGL. Aby nezůstalo jen u černého pozadí, je vykreslován trojúhelník s lineárním mísením barev. (Zdrojový kód se zvýrazněním syntaxe.)
Rotující logo SDL
Druhý příklad vykresluje jednoduchou animaci rotujícího loga knihovny SDL. Obrázek pro texturu je uložen na disku ve formátu PNG a do programu je nahráván pomocí knihovny SDL_image. (Zdrojový kód se zvýrazněním syntaxe.)
Pohyb v mřížce
Jedná se o jednoduché demo ovládané myší, ve kterém se hráč pohybuje mřížkou. Díky periodickému opakování elementárních buněk v prostoru nelze nikdy dojít na okraj. Kód je založen na jedné malé knihovně, kterou se v poslední době snažím dát dohromady, ale zatím ještě nebyla nezveřejněna. (Zdrojový kód se zvýrazněním syntaxe: hlavičkový soubor třídy aplikace, implementace.)
Download
- Příklad: RGB Trojúhelník
- Příklad: Rotující logo SDL
- Příklad: Pohyb v mřížce
- Offline verze článku včetně všech příloh
Pokračování
V následujícím dílu se podíváme na knihovnu SDL_ttf, která umožňuje vypisovat texty.