Obsah
1. 2D grafika s využitím knihovny OpenVG (nejenom) na Raspberry Pi
2. Výběr vhodné konfigurace framebufferu
3. Vytvoření kontextu pro kreslení a kreslicí plochy (surface)
5. Vymazání obrazovky černou barvou
6. Nakreslení vyplněného obdélníku
7. Úplný zdrojový kód dnešního prvního demonstračního příkladu
8. Vykreslování úseček, polyčar a polygonů
10. Úplný zdrojový kód dnešního druhého demonstračního příkladu
11. Repositář s demonstračními příklady
1. 2D grafika s využitím knihovny OpenVG (nejenom) na Raspberry Pi
V předchozím článku jsme si vysvětlili, jaká je úloha knihovny EGL při programování aplikací využívajících OpenGL, OpenGL ES či OpenVG. Připomeňme si, že EGL tvoří poměrně tenkou, ale unifikovanou vrstvu ležící mezi zmíněnými platformově nezávislými knihovnami (OpenGL, OpenGL ES či OpenVG) na jedné straně a nativními knihovnami určenými pro ovládání oken či celoobrazovkových režimů na straně druhé. Použitím EGL je tak možné z aplikací odstranit platformově závislý kód, který by jinak bylo nutné implementovat několikrát, což je neefektivní a současně to i klade větší požadavky na dostupnost testovacího hardware atd. Minule jsme si taktéž ukázali, jakým způsobem je možné použít funkce poskytované knihovnou EGL pro základní inicializaci framebufferu při spouštění aplikace (několik kroků si však ještě budeme muset popsat dnes) a jak naopak při ukončování aplikace provést „finalizaci“ framebufferu a na něj navázaných datových struktur.
Před použitím knihovny OpenVG však ještě musíme provést několik dalších operací, které budou stručně popsány v navazujících kapitolách (pro většinu aplikací psaných v OpenVG je možné popsané funkce přímo převzít a začít používat; většinou není nutné je podrobněji zkoumat). Druhá polovina článku již bude konečně věnována použití OpenVG. Pokud vás tedy zajímá hlavně OpenVG, klidně přeskočte na pátou kapitolu.
V demonstračních příkladech jsme stav grafického subsystému ukládali do jednoduché datové struktury, kterou budeme používat i dnes:
/* * 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;
První verze funkce pro inicializaci grafického subsystému s využitím EGL vypadala 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); } }
Ještě si připomeňme, jak vypadá „finalizér“ (ten je již dokonalý a nebudeme ho tedy muset měnit):
/* * 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); }
2. Výběr vhodné konfigurace framebufferu
Po prvních krocích spočívajících ve volání funkcí eglGetDisplay a eglInitialize je nutné si vybrat vhodnou konfiguraci framebufferu takovým způsobem, aby to přesně odpovídalo požadavkům aplikace. Již naposledy si připomeňme, že konkrétně na Raspberry Pi je k dispozici celkem 28 různých kombinací framebufferu, které se od sebe odlišují jak hloubkou barvového bufferu, tak i přítomností alpha bufferu (resp. průhlednosti v color bufferu), Z-bufferu či stencil bufferu. Jedná se o tyto kombinace (získané demonstračním příkladem popsaným minule):
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
Při práci s knihovnou OpenVG, která je orientovaná výhradně na kvalitní 2D grafiku, se počet vhodných kombinací zmenšuje – nepotřebujeme totiž používat paměť hloubky (Z-buffer, depth buffer), nepotřebujeme dokonce ani stencil buffer a většinou nás nezajímá vazba vykreslovacích rutin na textury. Počet vhodných kombinací se tak rapidně snižuje:
Configuration R G B A bpp depth stencil bind RGB/RGBA 14 8 8 8 8 32 0 0 no no 15 8 8 8 0 24 0 0 no no 23 5 6 5 0 16 0 0 no no
Ukažme si nyní, jak se vhodná konfigurace framebufferu vybírá. Používá se k tomu funkce nazvaná (celkem logicky) eglChooseConfig. Hlavička této funkce vypadá poměrně děsivě:
EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config)
Ve skutečnosti není použití této funkce tak složité, jak to možná na první pohled může vypadat. Prvním parametrem je struktura obsahující informace o primárním displeji; tu již máme k dispozici. Zajímavější je druhý parametr, což je pole obsahující požadované atributy framebufferu, a to konkrétně dvojice identifikátor_atributu (celočíselná konstanta) a hodnota. V céčku jsou prvky 2D polí uloženy za sebou, takže není problém všechny požadované atributy uložit za sebou do jediného 1D pole. Celé pole je ukončeno speciální hodnotou EGL_NONE. Pokud požadujeme vytvoření framebufferu s hloubkou 24bpp a osmibitovým alfa kanálem, bude pole s atributy vypadat takto:
static const EGLint attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // typ kreslicí plochy EGL_NONE };
Ve čtvrtém parametru se specifikuje, kolik konfigurací framebufferu odpovídajících zadaným atributům se má vrátit. Nás bude zajímat jediná (první vhodná) kombinace, tudíž zde předáme jedničku. Pole s konfiguracemi se předává přes třetí parametr. Pole=ukazatel na jeho první prvek, takže v našem případě postačuje předat přímo ukazatel na strukturu typu EGLConfig. Poslední parametr je ukazatel na proměnnou typu EGLint (celé číslo). Do této proměnné se přes předaný ukazatel předá počet nalezených konfigurací (přičemž předpokládáme, že se nalezne aspoň jedna vhodná konfigurace). Celý kód volající funkci eglChooseConfig může vypadat takto:
EGLint num_config; EGLConfig config; EGLDisplay display; /* vychozi displej */ display = eglGetDisplay(EGL_DEFAULT_DISPLAY); /* inicializace displeje */ eglInitialize(display, NULL, NULL); /* ziskani konfigurace framebufferu */ eglChooseConfig(display, attribute_list, &config, 1, &num_config);
Samozřejmě musíme ještě přidat kontrolu, zda všechny funkce proběhly v pořádku, takže vylepšený kód může vypadat následovně:
EGLBoolean result; EGLint num_config; EGLConfig config; EGLDisplay display; /* vychozi displej */ display = eglGetDisplay(EGL_DEFAULT_DISPLAY); /* inicializace displeje */ result = eglInitialize(display, NULL, NULL); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL init failed"); exit(1); } /* ziskani konfigurace framebufferu */ result = eglChooseConfig(display, attribute_list, &config, 1, &num_config); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL choose config failed"); exit(1); }
3. Vytvoření kontextu pro kreslení a kreslicí plochy (surface)
Předpokládejme, že předchozí operace proběhla v pořádku, takže v proměnné config máme uloženou vhodnou konfiguraci framebufferu. V dalším kroku specifikujeme, že na knihovnu EGL je navázána knihovna OpenVG. To je snadné – použije se funkce eglBindAPI, která akceptuje hodnoty EGL_OPENGL_API, EGL_OPENGL_ES_API nebo EGL_OPENVG_API (význam je zřejmý):
/* navazani EGL na OpenVG */ eglBindAPI(EGL_OPENVG_API);
Nyní již nastal čas na využití proměnné config, kterou jsme tak pracně naplnili. Vytvoříme takzvaný rendering kontext zavoláním funkce eglCreateContext. V nejjednodušším případě bude volání této funkce vypadat takto (poslední dva parametry by se použily při sdílení kontextu):
EGLContext context; /* vytvoreni kontextu */ context = eglCreateContext(display, config, EGL_NO_CONTEXT, NULL); /* kontrola, zda operace probehla v poradku */ if (context == EGL_NO_CONTEXT) { puts("EGL create context failed"); exit(1); }
Další část programového kódu slouží k inicializaci takzvaného nativního okna či celoobrazovkového režimu. V tomto úryvku kódu se volají funkce z knihovny Dispman, kterou si prozatím (alespoň dnes) nebudeme popisovat; proto prosím následující řádky pouze použijte, nic zlého se vašemu Raspberry Pi ani připojenému monitoru nestane :-)
/* vytvoreni surface */ int32_t success = graphics_get_display_size(0, &screen_width, &screen_height); /* kontrola, zda operace probehla v poradku */ if (success < 0) { puts("get display size failed"); exit(1); } dispman_display = vc_dispmanx_display_open(0); dispman_update = vc_dispmanx_update_start(0); dispman_element = vc_dispmanx_element_add(dispman_update, dispman_display, 0 /*layer */ , &dst_rect, 0 /*src */ , &src_rect, DISPMANX_PROTECTION_NONE, &alpha, 0 /*clamp */ , 0 /*transform */ ); nativewindow.element = dispman_element; nativewindow.width = state->window_width; nativewindow.height = state->window_height; vc_dispmanx_update_submit_sync(dispman_update);
Následuje již pochopitelnější kód sloužící pro vytvoření kreslicí plochy (surface). Ta se vytváří pomocí funkce eglCreateWindowSurface s následující hlavičkou:
EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list)
Hodnoty dosazované pro první dva parametry již máme připraveny (viz popis eglGetDisplay a eglChooseConfig). Ve třetím parametru se předává struktura popisující okno či celoobrazovkový režim (zde získaná přes knihovnu Dispman), čtvrtý parametr může (ale nemusí) opět obsahovat seznam požadovaných atributů:
EGLSurface surface; /* vytvoreni surface */ surface = eglCreateWindowSurface(display, config, &nativewindow, NULL); /* kontrola, zda operace probehla v poradku */ if (surface == EGL_NO_SURFACE) { puts("no surface!"); exit(1); }
4. Specifikace chování zadního bufferu při operaci „swap“ a propojení kontextu pro kreslení s kreslicí plochou
Už jsme skoro připraveni na použití knihovny OpenVG; zbývá nám pouze specifikovat chování zadního bufferu při prohazování předního a zadního bufferu (překreslení scény). Prozatím netvoříme žádné dynamické scény, které by se neustále překreslovaly, takže nám bude vyhovovat, když se nový zadní buffer automaticky NEvymaže. Nastavení tohoto chování je jednoduché:
/* nastaveni chovani bufferu pri operaci swap */ result = eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("can not set surface attributes!"); exit(1); }
Úplně nakonec specifikujeme, která kreslicí plocha bude výchozí pro všechny další operace. Knihovna EGL a OpenVG bude nastavenou kreslicí plochu implicitně používat, aniž by bylo nutné neustále přenášet její identifikátor při volání ostatních funkcí (což je vlastnost, která může být někdy výhodná a někdy nikoli, záleží na množství připojených displejů atd.):
/* propojeni kontextu se surface */ result = eglMakeCurrent(display, surface, surface, context); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("can not connect context with the surface!"); exit(1); }
5. Vymazání obrazovky černou barvou
Inicializace grafického subsystému Raspberry Pi (či jiného počítače s EGL) se snad podařila, takže se konečně dostáváme k popisu možností knihovny OpenVG. Tato knihovna je určena pro tvorbu kvalitní 2D grafiky, protože právě s 2D grafikou se uživatelé v současnosti setkávají při práci na desktopu, tabletu či smartphonu nejčastěji. Typickou 2D aplikací dneška je webový prohlížeč, od něhož se mj. vyžaduje i co nejrychlejší vykreslení mnohdy velmi složitého layoutu stránek, plynulý scrolling, kvalitně zobrazené písmo, dynamické změny ve stránkách, podpora HTML 5 canvasu apod. Dalšími 2D aplikacemi mohou být některé oblíbené hry, ať již se jedná o typicky dvourozměrné Angry Birds či o hry s isometrickou grafikou. Na tabletech, smartphonech a některých „moderních“ desktopech se taktéž velmi často požaduje zmenšení aplikace do živé ikony či použití vektorových ikon a widgetů (škálovatelné grafické uživatelské rozhraní).
Právě na tento segment se soustředí knihovna OpenVG, která dokonce vyžaduje, aby její implementace prošly poměrně velkým množstvím testů, které zjišťují, jakým způsobem a jak kvalitně je rendering 2D grafiky proveden. Podívejme se na některé základní operace. Začneme skutečně od triviálních operací, ovšem i na nich si lze ukázat některé vlastnosti OpenVG. První nutnou operací je vymazání obrazovky (resp. většinou zadního bufferu). Pro tyto účely použijeme funkci vgClear, které se předají souřadnice jednoho vrcholu a rozměry osově orientovaného obdélníku, který se má vymazat (rozměry framebufferu už máme zjištěné z předchozího kódu):
vgClear(0, 0, window_width, window_height);
Ještě předtím je ale nutné specifikovat barvu mazání, a to s využitím „setteru“ nazvaného vgSetfv. U názvu této funkce se na chvíli zastavme, protože je z něj patrné, že se používá podobný (nikoli však totožný) způsob pojmenování, jaký možná čtenáři znají z knihovny OpenGL. Ve skutečnosti je „setter“ realizován hned několika funkcemi, které se odlišují typem a počtem předávaných parametrů:
void vgSeti(VGParamType paramType, VGint val) | nastavení jednoho parametru typu int |
void vgSetf(VGParamType paramType, VGfloat val) | nastavení jednoho parametru typu float |
void vgSetiv(VGParamType paramType, VGint cnt, const VGint * val) | nastavení pole parametrů typu int |
void vgSetfv(VGParamType paramType, VGint cnt, const VGfloat * val) | nastavení pole parametrů typu float |
Pokud je zapotřebí nastavit barvu mazání, je jméno příslušného parametru VG_CLEAR_COLOR a jeho hodnotou může být například pole čtyř hodnot typu float (RGBA), i když existují i další možnosti. Délka pole (počet prvků) je tedy 4 a celý kód pro vymazání bufferu může vypadat takto:
/* vymazani pozadi cernou barvou */ VGfloat color1[4] = {0.0f, 0.0f, 0.0f, 1.0f}; vgSetfv(VG_CLEAR_COLOR, 4, color1); vgClear(0, 0, window_width, window_height);
Poznámka: v OpenVG se pro nastavení stavu (narozdíl od OpenGL) skutečně důsledně používají výše zmíněné čtyři funkce. Stav vykreslovací pipeline je součástí aktuálního kontextu nastaveného funkcí eglMakeCurrent (viz též předchozí kapitoly).
6. Nakreslení vyplněného obdélníku
Pohled na černou obrazovku asi není příliš zajímavý, proto do scény přidáme další grafický prvek. Bude se jednat o vyplněný osově orientovaný obdélník. Barva výplně a barva okrajů obdélníka bude odlišná. Nejprve se podívejme, jak se nastaví barva okrajů vykreslovaných objektů (to není zcela přesný termín, protože se nastavuje barva vykreslované „cesty“, která je u obdélníka současně i jeho hranicí, u otevřených cest to však samozřejmě neplatí, neboť ty žádný vyplňovatelný vnitřek nemají). Nejprve se vytvoří objekt typu „paint“ funkcí vgCreatePaint. Přes tento objekt se nastaví vnitřní stav knihovny OpenVG, která si udržuje dvě důležité informace: jak vykreslovat cesty a jak vyplňovat vnitřky uzavřených cest. Posléze nastavíme dva parametry udržované tímto objektem: typ (vykreslování cest) a barvu. Následně se pomocí funkce vgSetPaint určí, že nastavené parametry se mají uložit do vnitřního stavu OpenVG udržujícího informace o vykreslování cest (a nikoli výplní). Nakonec se může objekt typu „paint“ zrušit a uvolnit tak jeho paměť:
/* barva stetce */ VGfloat color2[4] = {0.75f, 0.25f, 0.25f, 1.0f}; VGPaint strokePaint = vgCreatePaint(); vgSetParameteri(strokePaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR); vgSetParameterfv(strokePaint, VG_PAINT_COLOR, 4, color2); vgSetPaint(strokePaint, VG_STROKE_PATH); vgDestroyPaint(strokePaint);
Zcela stejný postup použijeme při nastavení barvy výplně. Zde se bude lišit pouze typ objektu typu „paint“: namísto VG_STROKE_PATH použijeme VG_FILL_PATH a následně se objekt opět zruší:
/* barva vyplne */ VGfloat color3[4] = {0.25f, 0.25f, 0.75f, 1.0f}; VGPaint fillPaint = vgCreatePaint(); vgSetParameteri(fillPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR); vgSetParameterfv(fillPaint, VG_PAINT_COLOR, 4, color3); vgSetPaint(fillPaint, VG_FILL_PATH); vgDestroyPaint(fillPaint);
Zbývá nám nastavit šířku vykreslovaných cest, způsob vykreslení jejich zalomení a taktéž konců. Tímto tématem se budeme zabývat v dalších kapitolách, proto jen stručně:
/* sirka a styl stetce */ vgSetf(VG_STROKE_LINE_WIDTH, 10); vgSeti(VG_STROKE_CAP_STYLE, VG_CAP_ROUND); vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_ROUND);
Samotný obdélník je tvořen „cestou“ vytvořenou pomocí funkce vgCreatePath. Při definici vrcholů vytvářené křivky máme více možností, já jsem zde pro jednoduchost použil knihovnu VGU a v ní deklarovanou funkci vguRect. Cesta se vykreslí do zadního bufferu příkazem vgDrawPath a nakonec se může paměť alokovaná pro cestu uvolnit přes funkci vgDestroyPath:
/* vykresleni obdelnika */ VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_APPEND_TO); vguRect(path, x, y, width, height); vgDrawPath(path, VG_FILL_PATH | VG_STROKE_PATH); vgDestroyPath(path);
Parametry funkce vgCreatePath budou přesněji popsány příště.
7. Úplný zdrojový kód dnešního prvního demonstračního příkladu
Všechny kroky popsané ve druhé až šesté kapitole jsou součástí dnešního prvního demonstračního příkladu, jehož úplný zdrojový kód i příslušný soubor Makefile samozřejmě naleznete na GitHubu. Příklad je plně funkční na Raspberry Pi (konkrétně byl otestován na RPi 1 Modelu B):
/* OpenVG (nejenom) na Raspberry Pi - ctvrty 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; EGLint num_config; EGLConfig config; /* nutne pro RPi */ bcm_host_init(); /* pro jistotu vymazeme datovou strukturu nesouci stav EGL */ memset(state, 0, sizeof(*state)); static EGL_DISPMANX_WINDOW_T nativewindow; DISPMANX_ELEMENT_HANDLE_T dispman_element; DISPMANX_DISPLAY_HANDLE_T dispman_display; DISPMANX_UPDATE_HANDLE_T dispman_update; VC_RECT_T dst_rect; VC_RECT_T src_rect; static VC_DISPMANX_ALPHA_T alpha = { DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, 255, 0 }; static const EGLint attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; /* vychozi displej */ state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); /* inicializace displeje */ result = eglInitialize(state->display, NULL, NULL); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL init failed"); exit(1); } /* navazani EGL na OpenVG */ eglBindAPI(EGL_OPENVG_API); /* ziskani konfigurace framebufferu */ result = eglChooseConfig(state->display, attribute_list, &config, 1, &num_config); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL choose config failed"); exit(1); } /* vytvoreni kontextu */ state->context = eglCreateContext(state->display, config, EGL_NO_CONTEXT, NULL); /* kontrola, zda operace probehla v poradku */ if (state->context == EGL_NO_CONTEXT) { puts("EGL create context failed"); exit(1); } /* vytvoreni surface */ int32_t success = graphics_get_display_size(0, &state->screen_width, &state->screen_height); /* kontrola, zda operace probehla v poradku */ if (success < 0) { puts("get display size failed"); exit(1); } if ((state->window_width == 0) || (state->window_width > state->screen_width)) state->window_width = state->screen_width; if ((state->window_height == 0) || (state->window_height > state->screen_height)) state->window_height = state->screen_height; dispman_display = vc_dispmanx_display_open(0); dispman_update = vc_dispmanx_update_start(0); dispman_element = vc_dispmanx_element_add(dispman_update, dispman_display, 0 /*layer */ , &dst_rect, 0 /*src */ , &src_rect, DISPMANX_PROTECTION_NONE, &alpha, 0 /*clamp */ , 0 /*transform */ ); nativewindow.element = dispman_element; nativewindow.width = state->window_width; nativewindow.height = state->window_height; vc_dispmanx_update_submit_sync(dispman_update); /* vytvoreni surface */ state->surface = eglCreateWindowSurface(state->display, config, &nativewindow, NULL); /* kontrola, zda operace probehla v poradku */ if (state->surface == EGL_NO_SURFACE) { puts("no surface!"); exit(1); } /* nastaveni chovani bufferu pri operaci swap */ result = eglSurfaceAttrib(state->display, state->surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("can not set surface attributes!"); exit(1); } /* propojeni kontextu se surface */ result = eglMakeCurrent(state->display, state->surface, state->surface, state->context); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("can not connect context with the surface!"); exit(1); } } /* * 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); } /* * Vykresleni vyplneneho obdelnika. */ void draw(EGL_STATE_T *state) { /* vymazani pozadi cernou barvou */ VGfloat color1[4] = {0.0f, 0.0f, 0.0f, 1.0f}; vgSetfv(VG_CLEAR_COLOR, 4, color1); vgClear(0, 0, state->window_width, state->window_height); /* barva stetce */ VGfloat color2[4] = {0.75f, 0.25f, 0.25f, 1.0f}; VGPaint strokePaint = vgCreatePaint(); vgSetParameteri(strokePaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR); vgSetParameterfv(strokePaint, VG_PAINT_COLOR, 4, color2); vgSetPaint(strokePaint, VG_STROKE_PATH); vgDestroyPaint(strokePaint); /* barva vyplne */ VGfloat color3[4] = {0.25f, 0.25f, 0.75f, 1.0f}; VGPaint fillPaint = vgCreatePaint(); vgSetParameteri(fillPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR); vgSetParameterfv(fillPaint, VG_PAINT_COLOR, 4, color3); vgSetPaint(fillPaint, VG_FILL_PATH); vgDestroyPaint(fillPaint); /* sirka a styl stetce */ vgSetf(VG_STROKE_LINE_WIDTH, 10); vgSeti(VG_STROKE_CAP_STYLE, VG_CAP_ROUND); vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_ROUND); /* vykresleni obdelnika */ VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_APPEND_TO); vguRect(path, state->window_width/3, state->window_height/3, state->window_width/3, state->window_height/3); vgDrawPath(path, VG_FILL_PATH | VG_STROKE_PATH); vgDestroyPath(path); /* prohozeni predniho a zadniho bufferu (pokud je to zapotrebi) */ eglSwapBuffers(state->display, state->surface); } /* * Vstupni bod do programu. */ int main(int argc, char *argv[]) { EGL_STATE_T egl_state; initialize_egl(&egl_state); puts("initialize_egl OK"); draw(&egl_state); getchar(); finalize_egl(&egl_state); puts("finalize_egl OK"); return 0; }
Soubor Makefile použitý pro překlad dnešního prvního demonstračního příkladu vypadá následovně:
# Makefile pro preklad ctvrteho 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=example4 # 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 $@
8. Vykreslování úseček, polyčar a polygonů
Pro vykreslování samostatných úseček, polyčar (lomených čar) či polygonů (uzavřených polyčar) je nejjednodušší postupovat následovně:
- Vytvořit cestu pomocí funkce vgCreatePath
- Zavolat funkci vguPolygon a předat jí pole vrcholů (jednorozměrné pole či pole dvojic)
- Vykreslit cestu funkcí vgDrawPath
- Zrušit cestu funkcí vgDestroyPath
V praxi to může vypadat následovně:
/* * Vykresleni polycary slozene ze dvou usesek. */ void drawPolyline(int xoffset, int yoffset) { /* vykresleni polycary */ VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_APPEND_TO); float vertexes2[] = {xoffset+100, yoffset+100, xoffset+200, yoffset+100, xoffset+200, yoffset+200}; vguPolygon(path, vertexes2, 3, VG_FALSE); vgDrawPath(path, VG_STROKE_PATH); vgDestroyPath(path); }
9. Styly štětce
Styly vykreslování cest (a tedy i úseček, polyčar apod.) se nastavují funkcemi vgSeti, vgSetf, vgSetiv a vgSetfv (viz též pátou kapitolu). Nás nyní budou zajímat tři základní parametry: šířka vykreslované stopy, tvar stopy při zakončení křivek (konce úseček) a tvar stopy při lomení křivek (jak se například vykreslí rohy obdélníka):
Parametr | Funkce | Hodnota parametru |
---|---|---|
Šířka stopy | vgSetf(VG_STROKE_LINE_WIDTH, …) | float (šířka v pixelech) |
Tvar zakončení | vgSeti(VG_STROKE_CAP_STYLE, …) | VG_CAP_BUTT, VG_CAP_ROUND, VG_CAP_SQUARE |
Tvar zalomení | vgSeti(VG_STROKE_JOIN_STYLE, …) | VG_JOIN_MITER, VG_JOIN_BEVEL, VG_JOIN_ROUND |
Pokud tedy budeme požadovat stopu kreslenou šířkou deseti pixelů se zakulacenými konci i zalomeními, stačí napsat:
vgSetf(VG_STROKE_LINE_WIDTH, 10); vgSeti(VG_STROKE_CAP_STYLE, VG_CAP_ROUND); vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_ROUND);
Funkci drawPolyline, kterou jsme si ukázali v předchozí kapitole, můžeme rozšířit takovým způsobem, aby bylo možné nastavit šířku štětce, způsob vykreslení zakončení křivek a taktéž způsob vykreslení zalomení křivek:
/* * Vykresleni polycary slozene ze dvou usesek. */ void drawPolyline(int xoffset, int yoffset, VGfloat strokeWidth, VGCapStyle capStyle, VGJoinStyle joinStyle) { /* sirka a styl stetce */ vgSetf(VG_STROKE_LINE_WIDTH, strokeWidth); vgSeti(VG_STROKE_CAP_STYLE, capStyle); vgSeti(VG_STROKE_JOIN_STYLE, joinStyle); /* vykresleni polycary */ VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_APPEND_TO); float vertexes2[] = {xoffset+100, yoffset+100, xoffset+200, yoffset+100, xoffset+200, yoffset+200}; vguPolygon(path, vertexes2, 3, VG_FALSE); vgDrawPath(path, VG_STROKE_PATH); vgDestroyPath(path); }
Všechny kombinace stylů zakončení a zalomení si můžeme ihned odzkoušet na dostatečně širokých polyčárách:
/* vykresleni nekolika polycar */ drawPolyline( 0, 0, 1, VG_CAP_BUTT, VG_JOIN_MITER); drawPolyline( 0, 150, 20, VG_CAP_BUTT, VG_JOIN_MITER); drawPolyline(150, 150, 20, VG_CAP_ROUND, VG_JOIN_MITER); drawPolyline(300, 150, 20, VG_CAP_SQUARE, VG_JOIN_MITER); drawPolyline( 0, 300, 20, VG_CAP_BUTT, VG_JOIN_ROUND); drawPolyline(150, 300, 20, VG_CAP_ROUND, VG_JOIN_ROUND); drawPolyline(300, 300, 20, VG_CAP_SQUARE, VG_JOIN_ROUND); drawPolyline( 0, 450, 20, VG_CAP_BUTT, VG_JOIN_BEVEL); drawPolyline(150, 450, 20, VG_CAP_ROUND, VG_JOIN_BEVEL); drawPolyline(300, 450, 20, VG_CAP_SQUARE, VG_JOIN_BEVEL);
10. Úplný zdrojový kód dnešního druhého demonstračního příkladu
Předchozí úryvek kódu byl zakomponován do dnešního druhého demonstračního příkladu, jehož úplný zdrojový kód i příslušný soubor Makefile opět naleznete na GitHubu:
/* OpenVG (nejenom) na Raspberry Pi - paty 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; EGLint num_config; EGLConfig config; /* nutne pro RPi */ bcm_host_init(); /* pro jistotu vymazeme datovou strukturu nesouci stav EGL */ memset(state, 0, sizeof(*state)); static EGL_DISPMANX_WINDOW_T nativewindow; DISPMANX_ELEMENT_HANDLE_T dispman_element; DISPMANX_DISPLAY_HANDLE_T dispman_display; DISPMANX_UPDATE_HANDLE_T dispman_update; VC_RECT_T dst_rect; VC_RECT_T src_rect; static VC_DISPMANX_ALPHA_T alpha = { DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS, 255, 0 }; static const EGLint attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; /* vychozi displej */ state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); /* inicializace displeje */ result = eglInitialize(state->display, NULL, NULL); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL init failed"); exit(1); } /* navazani EGL na OpenVG */ eglBindAPI(EGL_OPENVG_API); /* ziskani konfigurace framebufferu */ result = eglChooseConfig(state->display, attribute_list, &config, 1, &num_config); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("EGL choose config failed"); exit(1); } /* vytvoreni kontextu */ state->context = eglCreateContext(state->display, config, EGL_NO_CONTEXT, NULL); /* kontrola, zda operace probehla v poradku */ if (state->context == EGL_NO_CONTEXT) { puts("EGL create context failed"); exit(1); } /* vytvoreni surface */ int32_t success = graphics_get_display_size(0, &state->screen_width, &state->screen_height); /* kontrola, zda operace probehla v poradku */ if (success < 0) { puts("get display size failed"); exit(1); } if ((state->window_width == 0) || (state->window_width > state->screen_width)) state->window_width = state->screen_width; if ((state->window_height == 0) || (state->window_height > state->screen_height)) state->window_height = state->screen_height; dispman_display = vc_dispmanx_display_open(0); dispman_update = vc_dispmanx_update_start(0); dispman_element = vc_dispmanx_element_add(dispman_update, dispman_display, 0 /*layer */ , &dst_rect, 0 /*src */ , &src_rect, DISPMANX_PROTECTION_NONE, &alpha, 0 /*clamp */ , 0 /*transform */ ); nativewindow.element = dispman_element; nativewindow.width = state->window_width; nativewindow.height = state->window_height; vc_dispmanx_update_submit_sync(dispman_update); /* vytvoreni surface */ state->surface = eglCreateWindowSurface(state->display, config, &nativewindow, NULL); /* kontrola, zda operace probehla v poradku */ if (state->surface == EGL_NO_SURFACE) { puts("no surface!"); exit(1); } /* nastaveni chovani bufferu pri operaci swap */ result = eglSurfaceAttrib(state->display, state->surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("can not set surface attributes!"); exit(1); } /* propojeni kontextu se surface */ result = eglMakeCurrent(state->display, state->surface, state->surface, state->context); /* kontrola, zda operace probehla v poradku */ if (result == EGL_FALSE) { puts("can not connect context with the surface!"); exit(1); } } /* * 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); } /* * Vykresleni polycary slozene ze dvou usesek. */ void drawPolyline(int xoffset, int yoffset, VGfloat strokeWidth, VGCapStyle capStyle, VGJoinStyle joinStyle) { /* sirka a styl stetce */ vgSetf(VG_STROKE_LINE_WIDTH, strokeWidth); vgSeti(VG_STROKE_CAP_STYLE, capStyle); vgSeti(VG_STROKE_JOIN_STYLE, joinStyle); /* vykresleni polycary */ VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_APPEND_TO); float vertexes2[] = {xoffset+100, yoffset+100, xoffset+200, yoffset+100, xoffset+200, yoffset+200}; vguPolygon(path, vertexes2, 3, VG_FALSE); vgDrawPath(path, VG_STROKE_PATH); vgDestroyPath(path); } /* * Vykresleni vyplneneho obdelnika. */ void draw(EGL_STATE_T *state) { /* vymazani pozadi cernou barvou */ VGfloat color1[4] = {0.0f, 0.0f, 0.0f, 1.0f}; vgSetfv(VG_CLEAR_COLOR, 4, color1); vgClear(0, 0, state->window_width, state->window_height); /* barva stetce */ VGfloat color2[4] = {0.75f, 0.25f, 0.25f, 1.0f}; VGPaint strokePaint = vgCreatePaint(); vgSetParameteri(strokePaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR); vgSetParameterfv(strokePaint, VG_PAINT_COLOR, 4, color2); vgSetPaint(strokePaint, VG_STROKE_PATH); vgDestroyPaint(strokePaint); /* vykresleni nekolika polycar */ drawPolyline( 0, 0, 1, VG_CAP_BUTT, VG_JOIN_MITER); drawPolyline( 0, 150, 20, VG_CAP_BUTT, VG_JOIN_MITER); drawPolyline(150, 150, 20, VG_CAP_ROUND, VG_JOIN_MITER); drawPolyline(300, 150, 20, VG_CAP_SQUARE, VG_JOIN_MITER); drawPolyline( 0, 300, 20, VG_CAP_BUTT, VG_JOIN_ROUND); drawPolyline(150, 300, 20, VG_CAP_ROUND, VG_JOIN_ROUND); drawPolyline(300, 300, 20, VG_CAP_SQUARE, VG_JOIN_ROUND); drawPolyline( 0, 450, 20, VG_CAP_BUTT, VG_JOIN_BEVEL); drawPolyline(150, 450, 20, VG_CAP_ROUND, VG_JOIN_BEVEL); drawPolyline(300, 450, 20, VG_CAP_SQUARE, VG_JOIN_BEVEL); /* prohozeni predniho a zadniho bufferu (pokud je to zapotrebi) */ eglSwapBuffers(state->display, state->surface); } /* * Vstupni bod do programu. */ int main(int argc, char *argv[]) { EGL_STATE_T egl_state; initialize_egl(&egl_state); puts("initialize_egl OK"); draw(&egl_state); getchar(); finalize_egl(&egl_state); puts("finalize_egl OK"); return 0; }
Soubor Makefile použitý pro překlad dnešního prvního demonstračního příkladu vypadá následovně:
# Makefile pro preklad pateho 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=example5 # 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 $@
11. Repositář s demonstračními příklady
Oba dva 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 oboi zmíněných demonstračních příkladů přímé odkazy:
# | Příklad/knihovna | Github |
---|---|---|
1 | example4 | https://github.com/tisnik/presentations/tree/master/openvg/example4 |
2 | example5 | https://github.com/tisnik/presentations/tree/master/openvg/example5 |
Poznámka1: pro zjednodušení překladu je ke každému demonstračnímu příkladu přiložen i příslušný soubor Makefile.
Poznámka2: nenechte se prosím zmást číslováním – příklady example1 až example3 byly totiž popsány v předchozí části tohoto seriálu.
12. 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/ - Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: transformace rastrových obrázků
http://mojefedora.cz/programovaci-jazyky-a-knihovny-urcene-pro-vyuku-zakladu-pocitacove-grafiky-transformace-rastrovych-obrazku/ - 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/