SDL: Hry nejen pro Linux (8)

12. 4. 2005
Doba čtení: 7 minut

Sdílet

Díky přímé podpoře OpenGL umožňuje SDL renderovat i 3D grafické objekty, které se staly nepsaným standardem naprosté většiny dnešních her. Tentokrát se tedy budeme věnovat podpoře OpenGL v SDL.

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_SI­ZE, SDL_GL_ACCUM_GRE­EN_SIZE, SDL_GL_ACCUM_BLU­E_SIZE, SDL_GL_ACCUM_AL­PHA_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_MULTISAM­PLEBUFFERS, SDL_GL_MULTISAM­PLESAMPLES
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 gluCheckExten­sion(), 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_GetAttri­bute().

int SDL_GL_GetAttribute(SDLGLattr attr, int *value);

Stejně jako SDL_GL_SetAttri­bute() 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_SwapBuf­fers(), 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_UpdateRec­ts().

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_GetPro­cAddress() 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.

Změna velikosti okna v Linuxu

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.

Změna velikosti okna ve Windows

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.)

RGB trojúhelník

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.)

bitcoin_skoleni

Rotující logo SDL

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.)

Pohyb v mřížce

Download

Pokračování

V následujícím dílu se podíváme na knihovnu SDL_ttf, která umožňuje vypisovat texty.

Autor článku

Backend programátor ve společnosti Avast, kde vyvíjí a spravuje BigData systém pro příjem a analýzu událostí odesílaných z klientských aplikací založený na Apache Kafka a Apache Hadoop.