Obsah
1. Použití knihoven OpenVG a EGL (nejenom) na Raspberry Pi
2. Základní datové typy a objekty, s nimiž se v EGL pracuje
3. Datová struktura se stavem EGL, kterou použijeme v demonstračních příkladech
4. Inicializace a finalizace EGL
5. První demonstrační příklad – inicializace a finalizace EGL
7. Funkce eglGetConfigAttrib()
8. Zjištění základních informací o konfiguraci framebufferu
9. Druhý demonstrační příklad – výpis základních informací o konfiguraci framebufferu
10. Výsledky vypsané druhým demonstračním příkladem
11. Informace o Z-bufferu, stencil bufferu a o možnosti navázání barvových bufferů na textury
12. Třetí demonstrační příklad – výpis podrobnějších informací o konfiguraci framebufferu
13. Výsledky vypsané třetím demonstračním příkladem
14. Obsah následující části seriálu
15. Repositář s demonstračními příklady
1. Použití knihoven OpenVG a EGL (nejenom) na Raspberry Pi
V dnešním článku o programování počítačové grafiky (nejenom) na jednodeskovém mikropočítači Raspberry Pi si přiblížíme problematiku použití knihovny EGL a následně pak v navazující části i knihovny OpenVG. Připomeňme si, že EGL neboli též Native Platform Interface, tvoří mezivrstvu mezi grafickým procesorem (přičemž každý grafický procesor může mít zcela odlišný způsob ovládání) a systémem pro správu oken na jedné straně a knihovnou OpenGL, OpenVG či OpenGL ES na straně druhé. Díky použití EGL je možné zajistit, aby se v knihovnách OpenGL, OpenVG a OpenGL ES nemusely implementovat a samozřejmě ani specifikovat funkce určené pro otevření okna či funkce pro zpřístupnění framebufferu; dokonce tyto knihovny ani nemusí poskytovat funkce pro zjištění schopností grafického subsystému. Před vznikem EGL byla situace poměrně komplikovaná, protože všechny aplikace, které například volaly funkce OpenGL, musely nějakým způsobem otevřít okno, získat přístup k vykreslovací ploše atd. K tomu se používaly různé knihovny a rozhraní: SDL, WinAPI, GLX, GLUT. S využitím EGL se situace zjednodušuje, což je patrné z následujícího diagramu, kde můžeme vidět použití EGL na třech odlišných platformách:
+-----------+ +---------------------------------+ | OpenGL |........ ........| Linux (Display, Pixmap, Window) | +-----------+ \ / +---------------------------------+ \ / +-----------+ +-----+ +---------------------------------+ | OpenGL ES |........| EGL |........| Windows (HDC, HBITMAP, HWND) | +-----------+ +-----+ +---------------------------------+ / \ +-----------+ / \ +---------------------------------+ | OpenVG |......./ \.......| Android (ANativeWindow, ...) | +-----------+ +---------------------------------+
Za vývojem knihovny EGL stojí sdružení Khronos, které kromě této knihovny „pečuje“ i o specifikace a implementace OpenGL, OpenGL ES, OpenVG, dnes tak populárního nástupce OpenGL jménem Vulkan atd. (viz též https://www.khronos.org/). Jedním ze základních úkolů, které musí knihovna EGL zabezpečit, je vytvoření a správa grafického kontextu, ploch (surface), do kterých je možné přes knihovny OpenGL ES a OpenVG provádět vykreslování atd. Mimochodem – plochy (surface) mohou být vytvořeny tak, aby aplikace běžela v systému X Window (i v okně), přes framebuffer nebo lze vykreslování provádět do zadního bufferu. Další důležitou funkcí nabízenou EGL je kopie obsahu bitmap mezi jednotlivými plochami, tj. operace typu bitblt. Zapomenout nesmíme ani na funkce pro zjištění či nastavení konfigurace grafického subsystému, ostatně právě tyto funkce budou použity v dnešních třech demonstračních příkladech.
2. Základní datové typy a objekty, s nimiž se v EGL pracuje
V hlavičkovém souboru dodávaném s knihovnou EGL nalezneme několik deklarací datových typů použitých při volání funkcí EGL. Tyto datové typy jsou vypsány v následující tabulce společně s jejich stručným popisem. Posledních pět typů je sice deklarovaných jako ukazatele (typu void*), interně se však samozřejmě jedná o záznamy (struct), jejichž vnitřní struktura se ve specifikaci nepopisuje (a ani nás vlastně nezajímá):
# | Typ v C | Jméno typu | Stručný popis |
---|---|---|---|
1 | unsigned int | EGLBoolean | může nabývat jen hodnot EGL_TRUE nebo EGL_FALSE, typicky výsledek (návratová hodnota) mnoha funkcí |
2 | unsigned int | EGLenum | použito pro všechny symbolické konstanty i bitové masky |
3 | int32_t | EGLint | celé číslo, použito v mnoha funkcích |
4 | void * | EGLConfig | popisuje formát, typ a velikost bufferů |
5 | void * | EGLContext | udržuje takzvaný kontext, který propojuje stav na klientovi a serveru |
6 | void * | EGLDisplay | abstraktní displej, na RPi displej jde o konkrétní displej připojený k počítači (teoreticky ale lze vykreslovat i do paměti apod.) |
7 | void * | EGLSurface | buffer, do něhož se vykresluje (je buď viditelný nebo tzv.off-screen) |
8 | void * | EGLClientBuffer | buffer na klientovi, použito právě v OpenVG pro kreslení |
Poznámka: jak jste asi (správně) poznali, budu se v dalším textu soustředit na programování v céčku, takže vlastně navážeme na předchozí části seriálu, kde jsme použili céčko pro přístup do framebufferu Raspberry Pi.
3. Datová struktura se stavem EGL, kterou použijeme v demonstračních příkladech
Ve všech demonstračních příkladech bude stav EGL (a vlastně i stav celé grafické pipeline) reprezentován jednoduchým záznamem (struct), jehož struktura vypadá následovně:
/* * Datova struktura obsahujici cely stav EGL "sezeni". */ typedef struct { uint32_t screen_width; uint32_t screen_height; uint32_t window_x; uint32_t window_y; int32_t window_width; int32_t window_height; EGLDisplay display; EGLSurface surface; EGLContext context; EGLConfig config; } EGL_STATE_T;
Způsob korektního naplnění většiny prvků této datové struktury si ukážeme až příště, protože se bude jednat o poměrně dlouhý programový kód. Nicméně význam by měl být zřejmý: budeme si pamatovat rozměry obrazovky, rozměry okna, do něhož se bude vykreslovat a taktéž aktuální konfiguraci EGL, přiřazený displej (ten je u Raspberry Pi jen jeden), vykreslovací plochu (pokud bude vytvořena) a taktéž kontext propojující stav klienta (OpenGL/OpenVG/OpenGL ES) a serveru (což zde znamená stav okenního systému a grafického procesoru).
4. Inicializace a finalizace EGL
Při používání knihovny EGL je nutné nejdříve provést její inicializaci a při ukončení práce taktéž takzvanou „finalizaci“. V případě inicializace se postupně provádí několik kroků. Typickým prvním krokem je získání datové struktury popisující primární displej, dále pak vlastní inicializace interních datových struktur EGL, napojení některé z knihoven OpenGL, OpenGL ES nebo OpenVG na EGL a nakonec i získání přístupu k plochám (surface), do nichž je možné provádět vykreslování (na tomto místě se může vývojář rozhodnout, zda preferuje vykreslování do okna či naopak vykreslování na celou obrazovku, což se pochopitelně týká především počítačových her, přehrávačů videa apod.). Ve skutečnosti je celá inicializace poměrně složitá (jedná se o více než 100 řádků programového kódu, pokud počítáme i reakci na chyby), takže se většina vývojářů spolehne na další knihovnu, která inicializaci provede za ně. My prozatím takový luxus nevyužijeme, ale postupně si všechny důležité kroky popíšeme a ukážeme.
První důležitou funkcí volanou při inicializaci je funkce nazvaná eglGetDisplay(), která slouží pro získání datové struktury popisující zvolený reálný či virtuální displej:
EGLDisplay eglGetDisplay( NativeDisplayType native_display);
Na mikropočítači Raspberry Pi a dalších počítačích s jediným displejem se této funkci předává konstanta EGL_DEFAULT_DISPLAY. V případě chyby se vrátí hodnota EGL_NO_DISPLAY, jakákoli odlišná hodnota znamená skutečný displej.
Poznámka: návratová hodnota funkce eglGetDisplay() je velmi důležitá, protože se předává do prakticky všech dalších funkcí knihovny EGL.
Druhou funkcí používanou při inicializaci knihovny EGL je funkce eglInitialize(). V prvním parametru se předává (primární) displej, druhé dva parametry jsou ukazatele na celočíselné proměnné, které mohou být naplněny verzí knihovny EGL. Pokud nás tato informace nezajímá, mohou být namísto ukazatelů předány konstanty NULL. Výsledkem této funkce je pravdivostní hodnota značící, zda se inicializace podařila či nikoli:
EGLBoolean eglInitialize( EGLDisplay display, EGLint * major, EGLint * minor);
Základní inicializace knihovny EGL (prozatím bez bufferů a kreslicích ploch) bude vypadat takto:
/* * Inicializace EGL. */ void initialize_egl(EGL_STATE_T *state) { EGLBoolean result; /* nutne pro RPi */ bcm_host_init(); /* pro jistotu vymazeme datovou strukturu nesouci stav EGL */ memset(state, 0, sizeof(*state)); /* propojeni na vychozi displej */ state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); /* inicializace displeje */ result = eglInitialize(state->display, NULL, NULL); /* kontrola, zda inicializace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL init failed"); exit(1); } }
Důvody vedoucí k nutnosti napsání programového kódu, který provádí inicializaci EGL a její napojení na OpenGL, OpenGL ES či OpenVG, jsou pochopitelné. Ovšem stejně důležitá je i „finalizace“ prováděná ve chvíli, kdy se má aplikace ukončit. Ve fázi finalizace je nutné uvolnit všechnu paměť používanou GPU, což se týká jak samotného framebufferu, tak i případných textur apod. Dále je nutné aplikaci odpojit od vykreslovací plochy i primárního displeje, takže se tyto systémové prostředky uvolní a může je začít používat další aplikace. „Finalizace“ může vypadat následovně:
/* * Ukonceni prace s EGL. */ void finalize_egl(EGL_STATE_T *state) { eglSwapBuffers(state->display, state->surface); eglMakeCurrent(state->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(state->display, state->surface); eglDestroyContext(state->display, state->context); eglTerminate(state->display); }
Vidíme, že se provádí tyto kroky:
- Prohození předního a zadního bufferu (tímto se vynutí dokončení všech operací v grafické pipeline).
- Odstranění vazby na výchozí kreslicí plochu.
- Uvolnění kreslicí plochy (pokud existuje).
- Uvolnění kontextu mezi EGL a další knihovnou (OpenGL/OpenGL ES/OpenVG).
- Vlastní ukončení knihovny EGL.
V dnešních příkladech sice „finalizátor“ budeme volat, ovšem ve skutečnosti by postačovalo zavolat jen poslední funkci.
5. První demonstrační příklad – inicializace a finalizace EGL
Funkce, které jsme si popsali v předchozích kapitolách, jsou použity v dnešním prvním demonstračním příkladu, který je vlastně velmi jednoduchý, protože obsahuje jen základní inicializaci a finalizaci EGL. V příkladu je použita již zmíněná datová struktura držící informace o stavu EGL, přičemž většina prvků této struktury není prozatím použita (ovšem příště již všechny prvky využijeme). Nejprve se podívejme na úplný zdrojový kód tohoto příkladu, v němž upozorním především na řádek, který je specifický pro Raspberry Pi. Jedná se o řádek s voláním funkce bcm_host_init(), jejíž hlavičku nalezneme v souboru bcm_host.h:
/* OpenVG (nejenom) na Raspberry Pi - prvni demonstracni priklad */ #include <stdio.h> #include <VG/openvg.h> #include <VG/vgu.h> #include <EGL/egl.h> #include <bcm_host.h> /* * Datova struktura obsahujici cely stav EGL "sezeni". */ typedef struct { uint32_t screen_width; uint32_t screen_height; uint32_t window_x; uint32_t window_y; int32_t window_width; int32_t window_height; EGLDisplay display; EGLSurface surface; EGLContext context; EGLConfig config; } EGL_STATE_T; /* * Inicializace EGL. */ void initialize_egl(EGL_STATE_T *state) { EGLBoolean result; /* nutne pro RPi */ bcm_host_init(); /* pro jistotu vymazeme datovou strukturu nesouci stav EGL */ memset(state, 0, sizeof(*state)); /* propojeni na vychozi displej */ state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); /* inicializace displeje */ result = eglInitialize(state->display, NULL, NULL); /* kontrola, zda inicializace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL init failed"); exit(1); } } /* * Ukonceni prace s EGL. */ void finalize_egl(EGL_STATE_T *state) { /* nyni jsou tyto kroky prozatim zbytecne, v dalsich prikladech se vsak budou hodit */ eglSwapBuffers(state->display, state->surface); eglMakeCurrent(state->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(state->display, state->surface); eglDestroyContext(state->display, state->context); eglTerminate(state->display); } /* * Vstupni bod do programu. */ int main(int argc, char *argv[]) { EGL_STATE_T egl_state; initialize_egl(&egl_state); puts("initialize_egl OK"); finalize_egl(&egl_state); puts("finalize_egl OK"); return 0; }
Jakým způsobem se tento demonstrační příklad překládá? Předpokládejme, že je použita standardní instalace Raspbianu. Na tomto systému je přítomen v určitém ohledu speciální adresář /opt/vc/, v němž jsou kromě několika dem (podle mého názoru zbytečně komplikovaných :-) uloženy i hlavičkové soubory a knihovny, které jsou v příkladu použity. Abychom se vyhnuli nutnosti předávání parametrů na příkazové řádce, bude lepší použít následující Makefile soubor. Celý překlad se potom provede jednoduše příkazem make, adresář dostaneme do původního stavu příkazem make clean:
# Makefile pro preklad tretiho prikladu ukazujiciho # praci s OpenVG a EGL. # Parametry prekladace. CFLAGS=-Wall # Dalsi parametry prekladace, zde adresare, kde se maji # hledat hlavickove soubory. INCLUDES=-I/opt/vc/include/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux # Parametry linkeru. LDFLAGS=-L/opt/vc/lib/ -lGLESv2 -lEGL -lopenmaxil -lbcm_host -lvcos -lvchiq_arm -lpthread -lrt -lm PROGNAME=example1 # Vychozi pravidlo pro vytvoreni vysledne spustitelne aplikace. all: $(PROGNAME) clean: rm -f *.o rm -f $(PROGNAME) # Pravidlo pro slinkovani vsech objektovych souboru a vytvoreni # vysledne spustitelne aplikace. $(PROGNAME): $(PROGNAME).o $(CC) -o $@ $(LDFLAGS) $< # Pravidlo pro preklad kazdeho zdrojoveho souboru do prislusneho # objektoveho souboru. %.o: %.c $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
Poznámka: hlavičkové soubory uložené v adresáři /opt/vc/ jsou napsány takovým způsobem, že při překladu není možné použít přepínač -ansi.
6. Funkce eglGetConfigs()
Ještě předtím, než je možné zahájit vykreslování (například s použitím OpenVG), je nutné zjistit všechny dostupné konfigurace framebufferu a vybrat z nich tu správnou konfiguraci (bitovou hloubku barvového bufferu, bitovou hloubku Z-bufferu, stencil bufferu atd.). K tomuto účelu slouží funkce nazvaná eglGetConfigs():
EGLBoolean eglGetConfigs( EGLDisplay display, EGLConfig * configs, EGLint config_size, EGLint * num_config);
Typicky se tato funkce volá dvakrát. Poprvé se jí ve druhém parametru předá hodnota NULL. V tomto případě funkce eglGetConfigs() do proměnné, jejíž adresa je předána v posledním parametru, vloží celkový dostupný počet konfigurací. Tato hodnota se následně použije pro alokaci pole prvků typu EGLConfig. Při druhém volání funkce eglGetConfigs() se toto pole předá ve druhém parametru (pole==adresa prvního prvku, což nikdy nebude NULL) a následně je toto pole naplněno (přesněji řečeno se naplní maximálně config_size položek).
Pokud tato funkce z nějakého důvodu skončí s chybou, vrátí se hodnota EGL_FALSE a současně nedojde ke změně datových struktur a proměnných, jejichž ukazatele jsou předány ve druhém a čtvrtém parametru. Pokud naopak funkce skončí v pořádku, vrátí se podle očekávání hodnota EGL_TRUE a obě datové struktury by měly být nastaveny správně.
7. Funkce eglGetConfigAttrib()
Pole získané funkcí eglGetConfigs() obsahuje prvky typu EGLConfig, přičemž každý prvek popisuje jednu možnou (či možná lépe řečeno dostupnou) konfiguraci framebufferu. Pro získání jednotlivých atributů každé konfigurace je nutné použít funkci nazvanou eglGetConfigAttrib(), jejíž hlavička vypadá následovně:
EGLBoolean eglGetConfigAttrib( EGLDisplay display, EGLConfig config, EGLint attribute, EGLint *value);
Význam prvního parametru již známe. Druhým parametrem je jeden z prvků pole přečteného přes eglGetConfigs(), třetím parametrem konstanta představující jméno atributu (viz též další kapitolu) a v posledním parametru se předává adresa proměnné. Tato proměnná je naplněna hodnotou vybraného atributu (samozřejmě jen v případě, že funkce neskončí s chybou).
V následující tabulce jsou vypsány konstanty představující symbolická jména atributů. Tyto konstanty se předávají ve třetím parametru:
Symbolická konstanta |
---|
EGL_ALPHA_SIZE |
EGL_ALPHA_MASK_SIZE |
EGL_BIND_TO_TEXTURE_RGB |
EGL_BIND_TO_TEXTURE_RGBA |
EGL_BLUE_SIZE |
EGL_BUFFER_SIZE |
EGL_COLOR_BUFFER_TYPE |
EGL_CONFIG_CAVEAT |
EGL_CONFIG_ID |
EGL_CONFORMANT |
EGL_DEPTH_SIZE |
EGL_GREEN_SIZE |
EGL_LEVEL |
EGL_LUMINANCE_SIZE |
EGL_MAX_PBUFFER_WIDTH |
EGL_MAX_PBUFFER_HEIGHT |
EGL_MAX_PBUFFER_PIXELS |
EGL_MAX_SWAP_INTERVAL |
EGL_MIN_SWAP_INTERVAL |
EGL_NATIVE_RENDERABLE |
EGL_NATIVE_VISUAL_ID |
EGL_NATIVE_VISUAL_TYPE |
EGL_RED_SIZE |
EGL_RENDERABLE_TYPE |
EGL_SAMPLE_BUFFERS |
EGL_SAMPLES |
EGL_STENCIL_SIZE |
EGL_SURFACE_TYPE |
EGL_TRANSPARENT_TYPE |
EGL_TRANSPARENT_RED_VALUE |
EGL_TRANSPARENT_GREEN_VALUE |
EGL_TRANSPARENT_BLUE_VALUE |
V případě, že tato funkce skončí s chybou, vrátí se hodnota EGL_FALSE a současně nedojde ke změně hodnoty proměnné, jejíž ukazatel byl předán ve čtvrtém parametru. Pokud naopak funkce skončí v pořádku, vrátí se podle očekávání hodnota EGL_TRUE a proměnná bude nastavena korektně.
8. Zjištění základních informací o konfiguraci framebufferu
Podívejme se nyní na postup používaný pro zjištění základních informací o barvovém bufferu, což je jedna součást celého framebufferu sloužící pro uložení zobrazované scény (v případě knihovny OpenVG potřebujeme pouze barvový buffer!). Nejprve zjistíme počet všech dostupných konfigurací (druhý parametr je NULL):
/* precteni poctu konfiguraci dostupnych pres EGL */ /* pocet se ulozi do promenne configurations_count */ eglGetConfigs(state->display, NULL, 0, &configurations_count); printf("EGL has %d configurations available\n", configurations_count);
Následně se alokuje pole s prvky typu EGLConfig a prvky tohoto pole se naplní aktuálními konfiguracemi (druhý parametr je ukazatel na první prvek pole):
/* nacteni vsech konfiguraci do pripraveneho pole */ all_configurations = malloc(configurations_count * sizeof(*all_configurations)); eglGetConfigs(state->display, all_configurations, configurations_count, &configurations_count);
Nyní v programové smyčce procházíme všemi prvky pole a voláme funkci, která vypíše základní atributy:
int i; for (i = 0; i < configurations_count; i++) { printf("%3d ", i); print_egl_configuration(state->display, &all_configurations[i]); }
Samotná volaná funkce je již jednoduchá – nejprve přečte všechny hledané atributy:
int red, green, blue, alpha, buffer; eglGetConfigAttrib(display, *config, EGL_RED_SIZE, &red); eglGetConfigAttrib(display, *config, EGL_GREEN_SIZE, &green); eglGetConfigAttrib(display, *config, EGL_BLUE_SIZE, &blue); eglGetConfigAttrib(display, *config, EGL_ALPHA_SIZE, &alpha); eglGetConfigAttrib(display, *config, EGL_BUFFER_SIZE, &buffer);
A posléze jejich hodnoty vypíše na standardní výstup:
if (alpha) { printf("%1d %1d %1d %1d %2d\n", red, green, blue, alpha, buffer); } else { printf("%1d %1d %1d x %2d\n", red, green, blue, buffer); }
Na konci jenom pro pořádek dealokujeme pole s konfiguracemi:
free(all_configurations);
9. Druhý demonstrační příklad – výpis základních informací o konfiguraci framebufferu
Obě funkce popsané v předchozích třech kapitolách jsou použity v dnešním druhém demonstračním příkladu pro zjištění a následný výpis základních informací o dostupných konfiguracích framebufferu. Nejprve je provedena inicializace knihovny EGL, následně se zjistí počet dostupných konfigurací, tyto konfigurace se načtou do pole a nakonec se pro každý prvek tohoto pole zjistí pět atributů: počet bitů rezervovaných pro červenou barvovou složku, počet bitů rezervovaných pro zelenou barvovou složku, počet bitů rezervovaných pro modrou barvovou složku, počet bitů rezervovaných pro alfa kanál (průhlednost pixelu) a konečně celková bitová hloubka barvového bufferu. Následuje výpis zdrojového kódu tohoto demonstračního příkladu:
/* OpenVG (nejenom) na Raspberry Pi - druhy demonstracni priklad */ #include <stdio.h> #include <VG/openvg.h> #include <VG/vgu.h> #include <EGL/egl.h> #include <bcm_host.h> /* * Datova struktura obsahujici cely stav EGL "sezeni". */ typedef struct { uint32_t screen_width; uint32_t screen_height; uint32_t window_x; uint32_t window_y; int32_t window_width; int32_t window_height; EGLDisplay display; EGLSurface surface; EGLContext context; EGLConfig config; } EGL_STATE_T; /* * Vypis konfigurace displeje nabizene pres EGL */ void print_egl_configuration(EGLDisplay display, EGLConfig *config) { int red, green, blue, alpha, buffer; eglGetConfigAttrib(display, *config, EGL_RED_SIZE, &red); eglGetConfigAttrib(display, *config, EGL_GREEN_SIZE, &green); eglGetConfigAttrib(display, *config, EGL_BLUE_SIZE, &blue); eglGetConfigAttrib(display, *config, EGL_ALPHA_SIZE, &alpha); eglGetConfigAttrib(display, *config, EGL_BUFFER_SIZE, &buffer); if (alpha) { printf("%1d %1d %1d %1d %2d\n", red, green, blue, alpha, buffer); } else { printf("%1d %1d %1d x %2d\n", red, green, blue, buffer); } } /* * Inicializace EGL. */ void initialize_egl(EGL_STATE_T *state) { EGLBoolean result; EGLint configurations_count; EGLConfig *all_configurations; /* nutne pro RPi */ bcm_host_init(); /* pro jistotu vymazeme datovou strukturu nesouci stav EGL */ memset(state, 0, sizeof(*state)); /* propojeni na vychozi displej */ state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); /* inicializace displeje */ result = eglInitialize(state->display, NULL, NULL); /* kontrola, zda inicializace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL init failed"); exit(1); } /* precteni poctu konfiguraci dostupnych pres EGL */ eglGetConfigs(state->display, NULL, 0, &configurations_count); printf("EGL has %d configurations available\n", configurations_count); /* nacteni vsech konfiguraci do pripraveneho pole */ all_configurations = malloc(configurations_count * sizeof(*all_configurations)); eglGetConfigs(state->display, all_configurations, configurations_count, &configurations_count); puts("Configuration R G B A bpp"); /* postupny vypis vsech konfiguraci */ int i; for (i = 0; i < configurations_count; i++) { printf("%3d ", i); print_egl_configuration(state->display, &all_configurations[i]); } free(all_configurations); } /* * Ukonceni prace s EGL. */ void finalize_egl(EGL_STATE_T *state) { /* nyni jsou tyto kroky prozatim zbytecne, v dalsich prikladech se vsak budou hodit */ eglSwapBuffers(state->display, state->surface); eglMakeCurrent(state->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(state->display, state->surface); eglDestroyContext(state->display, state->context); eglTerminate(state->display); } /* * Vstupni bod do programu. */ int main(int argc, char *argv[]) { EGL_STATE_T egl_state; initialize_egl(&egl_state); puts("initialize_egl OK"); finalize_egl(&egl_state); puts("finalize_egl OK"); return 0; }
Soubor Makefile je prakticky shodný se souborem použitým v prvním demonstračním příkladu:
# Makefile pro preklad tretiho prikladu ukazujiciho # praci s OpenVG a EGL. # Parametry prekladace. CFLAGS=-Wall # Dalsi parametry prekladace, zde adresare, kde se maji # hledat hlavickove soubory. INCLUDES=-I/opt/vc/include/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux # Parametry linkeru. LDFLAGS=-L/opt/vc/lib/ -lGLESv2 -lEGL -lopenmaxil -lbcm_host -lvcos -lvchiq_arm -lpthread -lrt -lm PROGNAME=example2 # Vychozi pravidlo pro vytvoreni vysledne spustitelne aplikace. all: $(PROGNAME) clean: rm -f *.o rm -f $(PROGNAME) # Pravidlo pro slinkovani vsech objektovych souboru a vytvoreni # vysledne spustitelne aplikace. $(PROGNAME): $(PROGNAME).o $(CC) -o $@ $(LDFLAGS) $< # Pravidlo pro preklad kazdeho zdrojoveho souboru do prislusneho # objektoveho souboru. %.o: %.c $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
10. Výsledky vypsané druhým demonstračním příkladem
Podívejme se nyní na výstup, který získáme po spuštění druhého demonstračního příkladu na jednodeskovém mikropočítači Raspberry Pi. Tento výstup by měl vypadat následovně:
EGL has 28 configurations available Configuration R G B A bpp 0 8 8 8 8 32 1 8 8 8 x 24 2 8 8 8 8 32 3 8 8 8 x 24 4 8 8 8 8 32 5 8 8 8 x 24 6 8 8 8 8 32 7 8 8 8 x 24 8 8 8 8 8 32 9 8 8 8 x 24 10 8 8 8 8 32 11 8 8 8 x 24 12 8 8 8 8 32 13 8 8 8 x 24 14 8 8 8 8 32 15 8 8 8 x 24 16 5 6 5 x 16 17 5 6 5 x 16 18 5 6 5 x 16 19 5 6 5 x 16 20 5 6 5 x 16 21 5 6 5 x 16 22 5 6 5 x 16 23 5 6 5 x 16 24 8 8 8 8 32 25 8 8 8 x 24 26 5 6 5 x 16 27 5 6 5 x 16 initialize_egl OK finalize_egl OK
Můžeme zde rozeznat tři skupiny konfigurací:
- Framebuffer s bitovou hloubkou 16bpp bez alfa kanálu (hi-color).
- Framebuffer s bitovou hloubkou 24bpp bez alfa kanálu (true color).
- Framebuffer s bitovou hloubkou 32bpp s alfa kanálem (true color).
11. Informace o Z-bufferu, stencil bufferu a o možnosti navázání barvových bufferů na textury
Na výpisu uvedeném v předchozí kapitole si povšimněte toho, že některé řádky popisují (alespoň zdánlivě) stejnou konfiguraci framebufferu. To není chyba v programu ani chyba v knihovně EGL, protože tyto zdánlivé duplicity jsou způsobeny tím, že prozatím nezískáváme všechny důležité informace o tom, jaké formáty framebufferu jsou dostupné. Pojďme si tedy naše znalosti rozšířit. Již dokážeme načíst a zpracovat všechny důležité informace o barvovém bufferu (RGB) i o případném alfa kanálu. Dále nás může zajímat informace o paměti hloubky neboli Z-bufferu (to v případě 3D grafiky, nikoli u OpenVG). U některých aplikací taktéž využijeme takzvaný stencil buffer, který lze použít pro implementaci některých vizualizačních algoritmů, například pro výpočty stínů apod. Rendering lze navázat i na textury, což je poslední zajímavá a užitečná informace (více viz příště). Tyto informace lze opět velmi snadno přečíst a zobrazit:
#define yes_no(x) ((x)==EGL_TRUE ? "yes":"no ") int depth, stencil; int bind_to_rgb, bind_to_rgba; eglGetConfigAttrib(display, *config, EGL_DEPTH_SIZE, &depth); eglGetConfigAttrib(display, *config, EGL_STENCIL_SIZE, &stencil); eglGetConfigAttrib(display, *config, EGL_BIND_TO_TEXTURE_RGB, &bind_to_rgb); eglGetConfigAttrib(display, *config, EGL_BIND_TO_TEXTURE_RGBA, &bind_to_rgba); printf("%1d %1d %1d %1d %2d %2d %2d %s %s\n", red, green, blue, alpha, buffer, depth, stencil, yes_no(bind_to_rgb), yes_no(bind_to_rgba));
12. Třetí demonstrační příklad – výpis podrobnějších informací o konfiguraci framebufferu
Ve třetím demonstračním příkladu se po inicializaci knihovny EGL zjistí základní i podrobnější informace o všech dostupných konfiguracích framebufferu. Vzhledem k tomu, že je struktura tohoto příkladu prakticky shodná s příkladem předchozím, se můžeme přímo podívat na jeho zdrojový kód:
/* OpenVG (nejenom) na Raspberry Pi - treti demonstracni priklad */ #include <stdio.h> #include <VG/openvg.h> #include <VG/vgu.h> #include <EGL/egl.h> #include <bcm_host.h> /* * Datova struktura obsahujici cely stav EGL "sezeni". */ typedef struct { uint32_t screen_width; uint32_t screen_height; uint32_t window_x; uint32_t window_y; int32_t window_width; int32_t window_height; EGLDisplay display; EGLSurface surface; EGLContext context; EGLConfig config; } EGL_STATE_T; /* * Vypis konfigurace displeje nabizene pres EGL */ void print_egl_configuration(EGLDisplay display, EGLConfig *config) { #define yes_no(x) ((x)==EGL_TRUE ? "yes":"no ") int red, green, blue, alpha, buffer; int depth, stencil; int bind_to_rgb, bind_to_rgba; eglGetConfigAttrib(display, *config, EGL_RED_SIZE, &red); eglGetConfigAttrib(display, *config, EGL_GREEN_SIZE, &green); eglGetConfigAttrib(display, *config, EGL_BLUE_SIZE, &blue); eglGetConfigAttrib(display, *config, EGL_ALPHA_SIZE, &alpha); eglGetConfigAttrib(display, *config, EGL_BUFFER_SIZE, &buffer); eglGetConfigAttrib(display, *config, EGL_DEPTH_SIZE, &depth); eglGetConfigAttrib(display, *config, EGL_STENCIL_SIZE, &stencil); eglGetConfigAttrib(display, *config, EGL_BIND_TO_TEXTURE_RGB, &bind_to_rgb); eglGetConfigAttrib(display, *config, EGL_BIND_TO_TEXTURE_RGBA, &bind_to_rgba); printf("%1d %1d %1d %1d %2d %2d %2d %s %s\n", red, green, blue, alpha, buffer, depth, stencil, yes_no(bind_to_rgb), yes_no(bind_to_rgba)); } /* * Inicializace EGL. */ void initialize_egl(EGL_STATE_T *state) { EGLBoolean result; EGLint configurations_count; EGLConfig *all_configurations; /* nutne pro RPi */ bcm_host_init(); /* pro jistotu vymazeme datovou strukturu nesouci stav EGL */ memset(state, 0, sizeof(*state)); /* propojeni na vychozi displej */ state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); /* inicializace displeje */ result = eglInitialize(state->display, NULL, NULL); /* kontrola, zda inicializace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL init failed"); exit(1); } /* precteni poctu konfiguraci dostupnych pres EGL */ eglGetConfigs(state->display, NULL, 0, &configurations_count); printf("EGL has %d configurations available\n", configurations_count); /* nacteni vsech konfiguraci do pripraveneho pole */ all_configurations = malloc(configurations_count * sizeof(*all_configurations)); eglGetConfigs(state->display, all_configurations, configurations_count, &configurations_count); puts("Configuration R G B A bpp depth stencil bind RGB/RGBA"); /* postupny vypis vsech konfiguraci */ int i; for (i = 0; i < configurations_count; i++) { printf("%3d ", i); print_egl_configuration(state->display, &all_configurations[i]); } free(all_configurations); } /* * Ukonceni prace s EGL. */ void finalize_egl(EGL_STATE_T *state) { /* nyni jsou tyto kroky prozatim zbytecne, v dalsich prikladech se vsak budou hodit */ eglSwapBuffers(state->display, state->surface); eglMakeCurrent(state->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(state->display, state->surface); eglDestroyContext(state->display, state->context); eglTerminate(state->display); } /* * Vstupni bod do programu. */ int main(int argc, char *argv[]) { EGL_STATE_T egl_state; initialize_egl(&egl_state); puts("initialize_egl OK"); finalize_egl(&egl_state); puts("finalize_egl OK"); return 0; }
Soubor Makefile použitý pro překlad a slinkování třetího příkladu má tento obsah:
# Makefile pro preklad tretiho prikladu ukazujiciho # praci s OpenVG a EGL. # Parametry prekladace. CFLAGS=-Wall # Dalsi parametry prekladace, zde adresare, kde se maji # hledat hlavickove soubory. INCLUDES=-I/opt/vc/include/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux # Parametry linkeru. LDFLAGS=-L/opt/vc/lib/ -lGLESv2 -lEGL -lopenmaxil -lbcm_host -lvcos -lvchiq_arm -lpthread -lrt -lm PROGNAME=example3 # Vychozi pravidlo pro vytvoreni vysledne spustitelne aplikace. all: $(PROGNAME) clean: rm -f *.o rm -f $(PROGNAME) # Pravidlo pro slinkovani vsech objektovych souboru a vytvoreni # vysledne spustitelne aplikace. $(PROGNAME): $(PROGNAME).o $(CC) -o $@ $(LDFLAGS) $< # Pravidlo pro preklad kazdeho zdrojoveho souboru do prislusneho # objektoveho souboru. %.o: %.c $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
13. Výsledky vypsané třetím demonstračním příkladem
Podívejme se nyní na výstup, který získáme po spuštění třetího demonstračního příkladu na jednodeskovém mikropočítači Raspberry Pi. Tento výstup by měl vypadat následovně:
EGL has 28 configurations available Configuration R G B A bpp depth stencil bind RGB/RGBA 0 8 8 8 8 32 24 8 no yes 1 8 8 8 0 24 24 8 yes yes 2 8 8 8 8 32 24 0 no yes 3 8 8 8 0 24 24 0 yes yes 4 8 8 8 8 32 0 8 no yes 5 8 8 8 0 24 0 8 yes yes 6 8 8 8 8 32 0 0 no yes 7 8 8 8 0 24 0 0 yes yes 8 8 8 8 8 32 24 8 no no 9 8 8 8 0 24 24 8 no no 10 8 8 8 8 32 24 0 no no 11 8 8 8 0 24 24 0 no no 12 8 8 8 8 32 0 8 no no 13 8 8 8 0 24 0 8 no no 14 8 8 8 8 32 0 0 no no 15 8 8 8 0 24 0 0 no no 16 5 6 5 0 16 24 8 yes yes 17 5 6 5 0 16 24 0 yes yes 18 5 6 5 0 16 0 8 yes yes 19 5 6 5 0 16 0 0 yes yes 20 5 6 5 0 16 24 8 no no 21 5 6 5 0 16 24 0 no no 22 5 6 5 0 16 0 8 no no 23 5 6 5 0 16 0 0 no no 24 8 8 8 8 32 0 0 no yes 25 8 8 8 0 24 0 0 yes yes 26 5 6 5 0 16 0 0 yes yes 27 5 6 5 0 16 16 0 yes yes initialize_egl OK finalize_egl OK
Získané informace jsou již mnohem zajímavější, protože původní tři skupiny:
- Framebuffer s bitovou hloubkou 16bpp bez alfa kanálu (hi-color).
- Framebuffer s bitovou hloubkou 24bpp bez alfa kanálu (true color).
- Framebuffer s bitovou hloubkou 32bpp s alfa kanálem (true color).
se nyní rozpadají na další podskupiny:
- Bez paměti hloubky (Z-bufferu).
- S pamětí hloubky (Z-bufferem), 24bpp (resp. přesněji 24 bits per fragment).
- Bez stencil bufferu.
- Se stencil bufferem (ten má hloubku 8 bitů, což lze využít při implementaci čítačů atd., nejenom pro čistě bitovou masku).
14. Obsah následující části seriálu
Dnešní článek byl pravděpodobně pro některé čtenáře dosti nezáživný, protože jsme se v něm zabývali pouze problematikou řešenou knihovnou EGL, tj. relativně nízkoúrovňovými věcmi. Nicméně i tuto část je dobré pochopit a nejenom bez rozmyslu použít již hotové knihovny tvořící uživatelsky přívětivější vrstvu nad EGL. Příště si však již ukážeme, jakým způsobem je možné prakticky využít knihovnu OpenVG pro tvorbu dvoudimenzionálních scén složených jak z rastrových obrázků, tak i z různých 2D primitiv (úsečka, obdélník, Bézierova křivka, kružnice, elipsa apod.). Zájemci se mohou – prozatím ovšem bez dalšího popisu – podívat na demonstrační příklad, kterým se budeme zabývat příště. Tento příklad je dostupný na adrese https://github.com/tisnik/presentations/tree/master/openvg/example4 (zdrojový kód byl opět odladěn na Raspberry Pi s Raspbiannem).
15. Repositář s demonstračními příklady
Všechny tři demonstrační příklady, které jsme si v dnešním článku popsali, byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. V tabulce zobrazené pod tímto odstavcem naleznete na zdrojové kódy všech tří zmíněných demonstračních příkladů přímé odkazy:
# | Příklad/knihovna | Github |
---|---|---|
1 | example1 | https://github.com/tisnik/presentations/tree/master/openvg/example1 |
2 | example2 | https://github.com/tisnik/presentations/tree/master/openvg/example2 |
3 | example3 | https://github.com/tisnik/presentations/tree/master/openvg/example3 |
Pro zjednodušení překladu je ke každému příkladu přiložen i příslušný Makefile.
16. Odkazy na Internetu
- EGL quick reference card
https://www.khronos.org/files/egl-1–4-quick-reference-card.pdf - EGL Reference Pages Index
https://www.khronos.org/registry/egl/sdk/docs/man/html/indexflat.php - Funkce eglInitialize
https://www.khronos.org/registry/egl/sdk/docs/man/html/eglInitialize.xhtml - Funkce eglGetDisplay
https://www.khronos.org/registry/egl/sdk/docs/man/html/eglGetDisplay.xhtml - Funkce eglGetConfigs
https://www.khronos.org/registry/egl/sdk/docs/man/html/eglGetConfigs.xhtml - Funkce eglGetConfigAttrib
https://www.khronos.org/registry/egl/sdk/docs/man/html/eglGetConfigAttrib.xhtml - Funkce eglDestroySurface
https://www.khronos.org/registry/egl/sdk/docs/man/html/eglDestroySurface.xhtml - Funkce eglDestroyContext
https://www.khronos.org/registry/egl/sdk/docs/man/html/eglDestroyContext.xhtml - Funkce eglTerminate
https://www.khronos.org/registry/egl/sdk/docs/man/html/eglTerminate.xhtml - Khronos Native Platform Graphics Interface
https://www.khronos.org/registry/egl/specs/eglspec.1.4.pdf - Khronos Group
https://www.khronos.org/ - Khronos Group (Wikipedia)
https://en.wikipedia.org/wiki/Khronos_Group - Raspberry Pi VideoCore APIs
http://elinux.org/Raspberry_Pi_VideoCore_APIs - Programming AudioVideo on the Raspberry Pi GPU
https://jan.newmarch.name/RPi/index.html - The Standard for Vector Graphics Acceleration
https://www.khronos.org/openvg/ - OpenVG (Wikipedia)
https://en.wikipedia.org/wiki/OpenVG - OpenVG Quick Reference Card
https://www.khronos.org/files/openvg-quick-reference-card.pdf - OpenVG on the Raspberry Pi
http://mindchunk.blogspot.cz/2012/09/openvg-on-raspberry-pi.html - ShivaVG: open-source ANSI C OpenVG
http://ivanleben.blogspot.cz/2007/07/shivavg-open-source-ansi-c-openvg.html - Testbed for exploring OpenVG on the Raspberry Pi
https://github.com/ajstarks/openvg - Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: knihovna Pygame
http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-knihovna-pygame/ - Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: knihovna Pygame prakticky
http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-knihovna-pygame-prakticky/ - Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: práce s bitmapami a TrueType fonty
http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-prace-s-bitmapami-a-truetype-fonty/ - Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: sprity v knihovně Pygame
http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-sprity-v-knihovne-pygame/ - Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: detekce kolize spritů
http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-detekce-kolize-spritu/ - Seriál Grafické karty a grafické akcelerátory
http://www.root.cz/serialy/graficke-karty-a-graficke-akceleratory/ - Grafika na osmibitových počítačích firmy Sinclair II
http://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair-ii/ - Xiaolin_Wu's Line Algorithm
https://en.wikipedia.org/wiki/Xiaolin_Wu's_line_algorithm - Grafické čipy v osmibitových počítačích Atari
http://www.root.cz/clanky/graficke-cipy-v-osmibitovych-pocitacich-atari/ - Osmibitové počítače Commodore a čip VIC-II
http://www.root.cz/clanky/osmibitove-pocitace-commodore-a-cip-vic-ii/ - Grafika na osmibitových počítačích firmy Apple
http://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-apple/ - Počátky grafiky na PC: grafické karty CGA a Hercules
http://www.root.cz/clanky/pocatky-grafiky-na-pc-graficke-karty-cga-a-hercules/ - Karta EGA: první použitelná barevná grafika na PC
http://www.root.cz/clanky/karta-ega-prvni-pouzitelna-barevna-grafika-na-pc/ - Grafické karty MCGA a VGA
http://www.root.cz/clanky/graficke-karty-mcga-a-vga/ - Grafický subsystém počítačů Amiga
http://www.root.cz/clanky/graficky-subsystem-pocitacu-amiga/ - Grafický subsystém počítačů Amiga II
http://www.root.cz/clanky/graficky-subsystem-pocitacu-amiga-ii/ - Raspberry Pi pages
https://www.raspberrypi.org/ - BCM2835 registers
http://elinux.org/BCM2835_registers - VideoCore (archiv stránek společnosti Alphamosaic)
http://web.archive.org/web/20030209213838/www.alphamosaic.com/videocore/ - VideoCore (Wikipedia)
https://en.wikipedia.org/wiki/Videocore - RPi lessons: Lesson 6 Screen01
http://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/screen01.html - Raspberry Pi forum: Bare metal
https://www.raspberrypi.org/forums/viewforum.php?f=72 - C library for Broadcom BCM 2835 as used in Raspberry Pi
http://www.airspayce.com/mikem/bcm2835/ - Raspberry Pi Hardware Components
http://elinux.org/RPi_Hardware#Components - (Linux) Framebuffer
http://wiki.linuxquestions.org/wiki/Framebuffer - (Linux) Framebuffer HOWTO
http://tldp.org/HOWTO/Framebuffer-HOWTO/ - Linux framebuffer (Wikipedia)
https://en.wikipedia.org/wiki/Linux_framebuffer - RPi Framebuffer
http://elinux.org/RPi_Framebuffer - HOWTO: Boot your Raspberry Pi into a fullscreen browser kiosk
http://blogs.wcode.org/2013/09/howto-boot-your-raspberry-pi-into-a-fullscreen-browser-kiosk/ - Zdrojový kód fb.c pro RPI
https://github.com/jncronin/rpi-boot/blob/master/fb.c - RPiconfig
http://elinux.org/RPi_config.txt - Mailbox framebuffer interface
https://github.com/raspberrypi/firmware/wiki/Mailbox-framebuffer-interface - Seriál Grafické formáty
http://www.root.cz/serialy/graficke-formaty/