Obsah
1. Operace s framebufferem na Raspberry Pi (vykreslování do framebufferu)
2. Formát uložení pixelů ve framebufferu
3. Přečtení informací o typu framebufferu
4. Přečtení informací o grafickém režimu
5. Přečtení informací o formátu uložení barev pixelů
6. Úplný zdrojový kód dnešního prvního demonstračního příkladu
7. Informace získané na Raspberry Pi
8. Informace získané na počítači s framebufferem s 32 bitovou hloubkou
9. Operace putpixel pro framebuffer s 32bitovou hloubkou
10. Operace putpixel pro framebuffer se 16bitovou hloubkou
11. Výběr správné funkce putpixel
12. Úplný zdrojový kód dnešního druhého demonstračního příkladu
13. Repositář s demonstračními příklady
1. Operace s framebufferem na Raspberry Pi (vykreslování do framebufferu)
V první části článku o používání framebufferu (obrazové paměti) na populárním jednodeskovém mikropočítači Raspberry Pi jsme se seznámili se dvěma základními možnostmi, jak k framebufferu přistupovat z uživatelských aplikací. Připomeňme si jen ve stručnosti, že první možnost spočívá v přímém přístupu k řídicím registrům GPU (grafického procesoru BCM2835 a BCM2836) přes takzvané mailboxy a druhá možnost pak ve využití driveru dostupného z uživatelského prostoru přes speciální zařízení /dev/fb0. Toto zařízení podporuje tři základní operace: čtení/read (čte se obsah framebufferu, tj. barvy jednotlivých pixelů), zápis/write (změna barvy jednotlivých pixelů) a taktéž operaci typu IOCTL, přes kterou lze získat další informace o framebufferu, zejména o jeho vnitřní struktuře, formátu uložení pixelů atd. S využitím operace IOCTL je taktéž možné měnit některé parametry framebufferu.
Taktéž jsme si ukázali jednu z možností, jak do framebufferu přistupovat přímo z céčkového kódu. Využili jsme přitom funkci mmap, která namapovala obsah speciálního zařízení /dev/fb0 do adresního rozsahu aplikace. Změnou jednotlivých bajtů této paměti je možné přímo měnit barvy jednotlivých pixelů na obrazovce (a samozřejmě si barvy zpětně přečíst atd.). Mohlo by se tedy zdát, že nám již nic nebrání ve vytvoření funkce typu putpixel, která na určené místo na obrazovce vykreslí bod zvolené barvy. Ve skutečnosti však i takto primitivní operace vyžaduje od programátorů poměrně hodně práce, a to z jednoduchého důvodu, který prostupuje celým oborem IT: „pokud je možné nějakou věc implementovat dvěma způsoby, je zaručeno, že návrháři přijdou minimálně se třemi vzájemně nekompatibilními řešeními“.
Přesně toto pravidlo můžeme aplikovat i na framebuffery – v průběhu posledních čtyřiceti let vzniklo takřka nepřeberné množství variant, jak může být framebuffer uspořádán (bitové roviny, packed-pixels, X-mode), jak jsou uspořádány obrazové řádky (za sebou, prokládaně sudá-lichá, po osmi řádcích, …) a taktéž jak jsou zakódovány barvy pixelů (barvová paleta, minimálně čtyři používané varianty hi-color, několik variant kódování true color, dobové triky typu HAM na Amize atd.).
2. Formát uložení pixelů ve framebufferu
Minule jsme si řekli, jakým způsobem lze přečíst základní informace o framebufferu, zejména:
- Rozlišení (fyzické i virtuální).
- Bitovou hloubku (počet bitů na jeden pixel).
- Délku obrazového řádku (ta obecně neodpovídá horizontálnímu rozlišení!).
- Celkovou velikost framebufferu.
Tyto informace nám však ještě nestačí k tomu, aby bylo možné implementovat korektní funkci typu putpixel. Potřebujeme navíc znát minimálně tyto údaje:
- Zda framebuffer používá bitové roviny (Amiga, EGA, VGA) či uložení pixelů v jedné rovině.
- Zda jsou řádky ve framebufferu uloženy za sebou (odshora dolů) či nějak prokládaně (interlaced).
- Jak je zakódována barva jednotlivých pixelů.
Všechny tyto údaje je možné získat již zmíněnou operací IOCTL. Navíc máme tu výhodu, že framebuffer je v mikropočítači Raspberry Pi implementován „příčetně“, takže nebudeme muset implementovat všechny možné kombinace. Pro Raspberry Pi totiž platí:
- Oblast paměti framebufferu je kontinuální, šířka řádku odpovídá jeho délce.
- Používá se formát packed-pixels, tj. všechny bity popisující jeden pixel jsou uloženy za sebou.
- Obrazové řádky jsou taktéž uloženy lidsky za sebou bez mezer a bez prokládání.
- Formát kódování barev je volitelný 16bpp, 24bpp a 32bpp (též 8bpp, zde je však situace složitější).
Před zkoumáním vlastností framebufferu je nutné přečíst dvě datové struktury popsané minule. To zajišťuje funkce nazvaná readFramebufferInfo(), které se předá handle otevřeného zařízení /dev/fb0 a ukazatele na obě struktury, které se mají naplnit:
/* * Precteni vsech relevantnich informaci zjistenych o framebufferu. Pro korektni * funkci je zapotrebi, aby mel uzivatel pristup k zarizeni /dev/fb0 * (postacuje byt ve skupine 'video' ci pouziti su/sudo) */ int readFramebufferInfo(int framebufferDevice, FramebufferInfo *framebufferInfoPtr, ModeInfo *modeInfoPtr) { /* Pokud operace ioctl probehne v poradku, vrati se 0 */ if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, framebufferInfoPtr)) { perror("Nelze precist informace o framebufferu"); return 0; } if (ioctl(framebufferDevice, FBIOGET_FSCREENINFO, modeInfoPtr)) { perror("Nelze precist informace o rezimu"); return 0; } return 1; }
3. Přečtení informací o typu framebufferu
Dvě informace, které musíme znát, souvisí se strukturou kódování pozic pixelů a jednotlivých bitů reprezentujících pixely. Tato informace je uložena v datové položce ModeInfo.type. Jedná se o celočíselnou položku, jejíž hodnota odpovídá jedné z konstant pojmenovaných FB_TYPE_…. O převod takové konstanty na řetězec se postará funkce getFramebufferType():
/* * Ziskani informace o typu framebufferu. */ const char* getFramebufferType(const int type) { static const char* FRAMEBUFFER_TYPES[]={ "Packed Pixels", "Non interleaved planes", "Interleaved planes", "Text/attributes", "EGA/VGA planes" }; if (type >= FB_TYPE_PACKED_PIXELS && type <= FB_TYPE_VGA_PLANES) { /* vypocet indexu do pole retezcu */ return FRAMEBUFFER_TYPES[type - FB_TYPE_PACKED_PIXELS]; } return "unknown"; }
Funkce getFramebufferType() se volá takto:
/* * Vypis vsech relevantnich informaci zjistenych o framebufferu. */ void printFramebufferInfo(int framebufferDevice, FramebufferInfo *framebufferInfoPtr, ModeInfo *modeInfoPtr) { ... ... ... printf("Organizace framebufferu: %d == %s\n", modeInfoPtr->type, getFramebufferType(modeInfoPtr->type)); ... ... ... }
Další zajímavou informací, kterou jádro Linuxu uživatelům zpřístupní, je režim vykreslování. Ten je opět reprezentován celočíselnou hodnotou, která zde však má odlišný význam: jde vlastně o bitové příznaky, z nichž nás zajímají jen příznaky uložené v nejnižším bitu a v bitu s indexem 1. Můžeme získat informaci o tom, zda se při zobrazování na monitoru používá prokládaný režim a/nebo režim double scan (většinou se dnes setkáme s neprokládaným režimem bez double scanu, tj. zopakování dvou po sobě jdoucích řádků):
/* * Ziskani informace rezimu zobrazovani. */ const char* getVideoMode(const int mode) { static const char* VIDEO_MODE_TYPES[]={ "non interlaced", "interlaced", "non interlaced, double scan", "interlaced, double scan" }; int index = mode & 0x03; return VIDEO_MODE_TYPES[index]; }
Funkce getVideoMode() se volá takto:
/* * Vypis vsech relevantnich informaci zjistenych o framebufferu. */ void printFramebufferInfo(int framebufferDevice, FramebufferInfo *framebufferInfoPtr, ModeInfo *modeInfoPtr) { ... ... ... printf("Rezim zobrazovani: %d == %s\n", framebufferInfoPtr->vmode, getVideoMode(framebufferInfoPtr->vmode)); ... ... ... }
4. Přečtení informací o grafickém režimu
Dále nás bude zajímat informace o grafickém režimu. Naprostá většina současných počítačů podporuje režim true color (sem spadá i hi-color, minimálně při práci s Linux framebufferem) popř. režimy s barvovou paletou (pseudo color). Grafický režim přečteme jednoduše:
/* * Vypis vsech relevantnich informaci zjistenych o framebufferu. */ void printFramebufferInfo(int framebufferDevice, FramebufferInfo *framebufferInfoPtr, ModeInfo *modeInfoPtr) { ... ... ... printf("Graficky rezim: %d == %s\n", modeInfoPtr->visual, getGraphicsMode(modeInfoPtr->visual)); ... ... ... }
Celočíselná hodnota, která je tímto způsobem přečtena, se rozepíše v další funkci:
/* * Ziskani informace grafickem rezimu. */ const char* getGraphicsMode(const int mode) { static const char* GRAPHIC_MODES[]={ "Monochr. 1=Black 0=White", "Monochr. 1=White 0=Black", "True color", "Pseudo color (like Atari)", "Direct color", "Pseudo color readonly" }; if (mode >= FB_VISUAL_MONO01 && mode <= FB_VISUAL_STATIC_PSEUDOCOLOR) { /* vypocet indexu do pole retezcu */ return GRAPHIC_MODES[mode - FB_VISUAL_MONO01]; } return "unknown"; }
Všechny možnosti, které framebuffer nabízí, jsou reprezentovány konstantami začínajícími na FB_VISUAL_.
5. Přečtení informací o formátu uložení barev pixelů
Nejdůležitější informace, která asi nejvíce ovlivní implementaci funkce typu putpixel, je informace o způsobu kódování barev pixelů ve framebufferu. Raspberry Pi je možné nakonfigurovat takovým způsobem, že se používá osmibitová barevná hloubka (potom je ovšem nutné nastavit paletu, jinak na obrazovce nebude nic vidět), 16bitová hloubka (výchozí a asi nejlepší nastavení), 24bitová hloubka (s problémy) a konečně 32bitová hloubka, ovšem bez alfa kanálu, což znamená, že jeden bajt z 32bitové hodnoty pixelu bude ignorován. Toto nastavení je možné změnit v souboru /boot/config.txt. O kódování barev pixelů se dozvíme ze čtyř datových struktur, v nichž se pro každou barvovou složku (red, green, blue, alfa kanál) určuje počet bitů rezervovaných pro barvovou složku (typicky 2, 3 či 8), počet bitů, o kolik je nutné složku posunout pro složení barvy pixelu a taktéž informaci o tom, zda je nejvyšší bit (MSB) uložen ve výsledném slovu vpravo či vlevo). Podívejme se nyní, jak je možné tyto informace přečíst a vypsat:
/* * Vypis podrobnejsich informaci o zpusobu zakodovani jedne barvove slozky * pixelu ve framebufferu. Zobrazene informace maji vyznam pouze ve chvili, * kdy se nepouzivaji graficke rezimy s barvovovu paletou. */ void printColorInfo(const char *message, const struct fb_bitfield colorInfo) { puts(message); printf(" sirka: %d\n", colorInfo.length); printf(" offset: %d\n", colorInfo.offset); printf(" MSB: %s\n", colorInfo.msb_right ? "vpravo" : "vlevo"); }
Tuto funkci voláme následujícím způsobem:
/* * Vypis vsech relevantnich informaci zjistenych o framebufferu. */ void printFramebufferInfo(int framebufferDevice, FramebufferInfo *framebufferInfoPtr, ModeInfo *modeInfoPtr) { ... ... ... printColorInfo("Cervena barvova slozka (RED):", framebufferInfoPtr->red); printColorInfo("Zelena barvova slozka (GREEN):", framebufferInfoPtr->green); printColorInfo("Modra barvova slozka (BLUE):", framebufferInfoPtr->blue); printColorInfo("Alfa kanal (ALPHA):", framebufferInfoPtr->transp); ... ... ... }
6. Úplný zdrojový kód dnešního prvního demonstračního příkladu
Všechny úryvky zdrojových kódů a funkcí, o nichž jsme se zmínili v předchozích kapitolách, jsou součástí dnešního prvního demonstračního příkladu, jehož zdrojový kód je zobrazen pod tímto odstavcem a který je možné v případě zájmu získat na adrese https://github.com/tisnik/presentations/blob/master/rpi_framebuffer/rpi_fb5.c:
/* Framebuffer na jednodeskovem mikropocitaci Raspberry Pi */ /* Autor: Pavel Tisnovsky, 2016 */ /* Demonstracni priklad cislo 5: ziskani informaci o grafickem rezimu */ /* a o zpusobu kodovani jednotlivych pixelu */ #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <linux/fb.h> /* * Datova struktura, do niz se ulozi informace o framebufferu. * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru * dostupnem v adresari "/usr/include/linux/fb.h" */ typedef struct fb_var_screeninfo FramebufferInfo; /* * Druha datova struktura popisujici zbyvajici vlastnosti framebufferu. * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru * dostupnem v adresari "/usr/include/linux/fb.h" */ typedef struct fb_fix_screeninfo ModeInfo; /* * Vypis podrobnejsich informaci o zpusobu zakodovani jedne barvove slozky * pixelu ve framebufferu. Zobrazene informace maji vyznam pouze ve chvili, * kdy se nepouzivaji graficke rezimy s barvovovu paletou. */ void printColorInfo(const char *message, const struct fb_bitfield colorInfo) { puts(message); printf(" sirka: %d\n", colorInfo.length); printf(" offset: %d\n", colorInfo.offset); printf(" MSB: %s\n", colorInfo.msb_right ? "vpravo" : "vlevo"); } /* * Ziskani informace o typu framebufferu. */ const char* getFramebufferType(const int type) { static const char* FRAMEBUFFER_TYPES[]={ "Packed Pixels", "Non interleaved planes", "Interleaved planes", "Text/attributes", "EGA/VGA planes" }; if (type >= FB_TYPE_PACKED_PIXELS && type <= FB_TYPE_VGA_PLANES) { /* vypocet indexu do pole retezcu */ return FRAMEBUFFER_TYPES[type - FB_TYPE_PACKED_PIXELS]; } return "unknown"; } /* * Ziskani informace rezimu zobrazovani. */ const char* getVideoMode(const int mode) { static const char* VIDEO_MODE_TYPES[]={ "non interlaced", "interlaced", "non interlaced, double scan", "interlaced, double scan" }; int index = mode & 0x03; return VIDEO_MODE_TYPES[index]; } /* * Ziskani informace grafickem rezimu. */ const char* getGraphicsMode(const int mode) { static const char* GRAPHIC_MODES[]={ "Monochr. 1=Black 0=White", "Monochr. 1=White 0=Black", "True color", "Pseudo color (like Atari)", "Direct color", "Pseudo color readonly" }; if (mode >= FB_VISUAL_MONO01 && mode <= FB_VISUAL_STATIC_PSEUDOCOLOR) { /* vypocet indexu do pole retezcu */ return GRAPHIC_MODES[mode - FB_VISUAL_MONO01]; } return "unknown"; } /* * Precteni vsech relevantnich informaci zjistenych o framebufferu. Pro korektni * funkci je zapotrebi, aby mel uzivatel pristup k zarizeni /dev/fb0 * (postacuje byt ve skupine 'video' ci pouziti su/sudo) */ int readFramebufferInfo(int framebufferDevice, FramebufferInfo *framebufferInfoPtr, ModeInfo *modeInfoPtr) { /* Pokud operace ioctl probehne v poradku, vrati se 0 */ if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, framebufferInfoPtr)) { perror("Nelze precist informace o framebufferu"); return 0; } if (ioctl(framebufferDevice, FBIOGET_FSCREENINFO, modeInfoPtr)) { perror("Nelze precist informace o rezimu"); return 0; } return 1; } /* * Vypis vsech relevantnich informaci zjistenych o framebufferu. */ void printFramebufferInfo(int framebufferDevice, FramebufferInfo *framebufferInfoPtr, ModeInfo *modeInfoPtr) { /* Nyni je datova struktura FramebufferInfo naplnena, lze vytisknout jeji prvky. */ printf("Realne rozliseni: %dx%d pixelu\n", framebufferInfoPtr->xres, framebufferInfoPtr->yres); printf("Virtualni rozliseni: %dx%d pixelu\n", framebufferInfoPtr->xres_virtual, framebufferInfoPtr->yres_virtual); printf("Odstiny sedi: %s\n", framebufferInfoPtr->grayscale ? "ano" : "ne"); printf("Nestandardni format: %s\n", framebufferInfoPtr->nonstd ? "ano" : "ne"); printf("Rezim zobrazovani: %d == %s\n", framebufferInfoPtr->vmode, getVideoMode(framebufferInfoPtr->vmode)); printf("Bitu na pixel: %d bitu\n", framebufferInfoPtr->bits_per_pixel); printColorInfo("Cervena barvova slozka (RED):", framebufferInfoPtr->red); printColorInfo("Zelena barvova slozka (GREEN):", framebufferInfoPtr->green); printColorInfo("Modra barvova slozka (BLUE):", framebufferInfoPtr->blue); printColorInfo("Alfa kanal (ALPHA):", framebufferInfoPtr->transp); putchar('\n'); /* Nyni je datova struktura modeInfo naplnena, lze vytisknout jeji prvky. */ printf("Identifikace: %s\n", modeInfoPtr->id); printf("Delka obrazoveho radku: %d bajtu\n", modeInfoPtr->line_length); printf("Velikost framebufferu: %d bajtu\n", modeInfoPtr->smem_len); printf("Organizace framebufferu: %d == %s\n", modeInfoPtr->type, getFramebufferType(modeInfoPtr->type)); printf("Graficky rezim: %d == %s\n", modeInfoPtr->visual, getGraphicsMode(modeInfoPtr->visual)); } /* Vstupni bod do demonstraniho prikladu... :) */ int main(int argc, char **argv) { FramebufferInfo framebufferInfo; ModeInfo modeInfo; int framebufferDevice = 0; /* Ze zarizeni potrebujeme pouze cist.*/ framebufferDevice = open("/dev/fb0", O_RDONLY); /* Pokud otevreni probehlo uspesne, nacteme * a nasledne vypiseme informaci o framebufferu.*/ if (framebufferDevice != -1) { /* Precteni informaci o framebufferu a test, zda se vse podarilo */ if (readFramebufferInfo(framebufferDevice, &framebufferInfo, &modeInfo)) { printFramebufferInfo(framebufferDevice, &framebufferInfo, &modeInfo); } close(framebufferDevice); return 0; } /* Otevreni se nezadarilo, vypiseme tudiz pouze chybove hlaseni.*/ else { perror("Nelze otevrit ovladac /dev/fb0"); return 1; } } /* finito */
7. Informace získané na Raspberry Pi
Na stejném kusu Raspberry Pi s externím monitorem o rozlišení 1280×1024 jsem spustil testovací program celkem třikrát. Poprvé pro hloubku 16bpp, posléze pro 24bpp a nakonec pro 32bpp (s vypnutým alfa kanálem). Podívejme se na výsledky:
Realne rozliseni: 1280x1024 pixelu Virtualni rozliseni: 1280x1024 pixelu Odstiny sedi: ne Nestandardni format: ne Rezim zobrazovani: 0 == non interlaced Bitu na pixel: 16 bitu Cervena barvova slozka (RED): sirka: 5 offset: 11 MSB: vlevo Zelena barvova slozka (GREEN): sirka: 6 offset: 5 MSB: vlevo Modra barvova slozka (BLUE): sirka: 5 offset: 0 MSB: vlevo Alfa kanal (ALPHA): sirka: 0 offset: 16 MSB: vlevo Identifikace: BCM2708 FB Delka obrazoveho radku: 2560 bajtu Velikost framebufferu: 2621440 bajtu Organizace framebufferu: 0 == Packed Pixels Graficky rezim: 2 == True color
Realne rozliseni: 1280x1024 pixelu Virtualni rozliseni: 1280x1024 pixelu Odstiny sedi: ne Nestandardni format: ne Rezim zobrazovani: 0 == non interlaced Bitu na pixel: 24 bitu Cervena barvova slozka (RED): sirka: 8 offset: 0 MSB: vlevo Zelena barvova slozka (GREEN): sirka: 8 offset: 8 MSB: vlevo Modra barvova slozka (BLUE): sirka: 8 offset: 16 MSB: vlevo Alfa kanal (ALPHA): sirka: 0 offset: 24 MSB: vlevo Identifikace: BCM2708 FB Delka obrazoveho radku: 3840 bajtu Velikost framebufferu: 3932160 bajtu Organizace framebufferu: 0 == Packed Pixels Graficky rezim: 2 == True color
Realne rozliseni: 1280x1024 pixelu Virtualni rozliseni: 1280x1024 pixelu Odstiny sedi: ne Nestandardni format: ne Rezim zobrazovani: 0 == non interlaced Bitu na pixel: 32 bitu Cervena barvova slozka (RED): sirka: 8 offset: 0 MSB: vlevo Zelena barvova slozka (GREEN): sirka: 8 offset: 8 MSB: vlevo Modra barvova slozka (BLUE): sirka: 8 offset: 16 MSB: vlevo Alfa kanal (ALPHA): sirka: 8 offset: 24 MSB: vlevo Identifikace: BCM2708 FB Delka obrazoveho radku: 5120 bajtu Velikost framebufferu: 5242880 bajtu Organizace framebufferu: 0 == Packed Pixels Graficky rezim: 2 == True color
Vidíme, že framebuffer na Raspberry Pi má stále stejně jednoduchou strukturu (packed pixels, non interlaced), mění se jen počet bitů pro jednotlivé barvové složky a samozřejmě i bitová hloubka.
Ještě si pro přehlednost uveďme všechny hodnoty v jedné tabulce:
Hodnota | 16bpp | 24bpp | 32bpp |
---|---|---|---|
Realne rozliseni: | 1280×1024 pixelu | 1280×1024 pixelu | 1280×1024 pixelu |
Virtualni rozliseni: | 1280×1024 pixelu | 1280×1024 pixelu | 1280×1024 pixelu |
Odstiny sedi: | ne | ne | ne |
Nestandardni format: | ne | ne | ne |
Rezim zobrazovani: | non interlaced | non interlaced | non interlaced |
Bitu na pixel: | 16 bitu | 24 bitu | 32 bitu |
RED: sirka: | 5 | 8 | 8 |
RED: offset: | 11 | 0 | 0 |
RED: MSB: | vlevo | vlevo | vlevo |
GREEN: sirka: | 6 | 8 | 8 |
GREEN: offset: | 5 | 8 | 8 |
GREEN: MSB: | vlevo | vlevo | vlevo |
BLUE: sirka: | 5 | 8 | 8 |
BLUE: offset: | 0 | 16 | 16 |
BLUE: MSB: | vlevo | vlevo | vlevo |
ALPHA: sirka: | 0 | 0 | 8 |
ALPHA: offset: | 16 | 24 | 24 |
ALPHA: MSB: | vlevo | vlevo | vlevo |
Identifikace: | BCM2708 FB | BCM2708 FB | BCM2708 FB |
Delka obrazoveho radku: | 2560 bajtu | 3840 bajtu | 5120 bajtu |
Velikost framebufferu: | 2621440 bajtu | 3932160 bajtu | 5242880 bajtu |
Organizace framebufferu: | Packed Pixels | Packed Pixels | Packed Pixels |
Graficky rezim: | True color | True color | True color |
8. Informace získané na počítači s framebufferem s 32 bitovou hloubkou
Pro porovnání se podívejme na to, jak vypadá výstup ze stejného programu, tentokrát však spuštěný na běžném notebooku s displejem o rozlišení 1440×900 pixelů:
Realne rozliseni: 1440x900 pixelu Virtualni rozliseni: 1440x900 pixelu Odstiny sedi: ne Nestandardni format: ne Rezim zobrazovani: 0 == non interlaced Bitu na pixel: 32 bitu Cervena barvova slozka (RED): sirka: 8 offset: 16 MSB: vlevo Zelena barvova slozka (GREEN): sirka: 8 offset: 8 MSB: vlevo Modra barvova slozka (BLUE): sirka: 8 offset: 0 MSB: vlevo Alfa kanal (ALPHA): sirka: 0 offset: 0 MSB: vlevo Identifikace: inteldrmfb Delka obrazoveho radku: 5760 bajtu Velikost framebufferu: 5185536 bajtu Organizace framebufferu: 0 == Packed Pixels Graficky rezim: 2 == True color
Všimli jste si změny? Ano, jedná se stále o formát s 32bitovou hloubkou, ale barvové složky RGB jsou oproti framebufferu na Raspberry Pi prohozeny.
9. Operace putpixel pro framebuffer s 32bitovou hloubkou
Podívejme se nyní na způsob implementace funkce putpixel v případě, že se používá framebuffer s hloubkou 32bpp, což znamená, že pro každou barvovou složku je rezervován přesně jeden bajt a zbývající bajt je buď nevyužitý (což je případ Raspberry Pi) nebo obsahuje alfa kanál. Teoreticky by s takto definovaným kódováním neměl být žádný větší problém, ve skutečnosti však existuje hned několik způsobů uspořádání barvových složek v 32bitovém slově. Setkáme se s formáty RGB0 (bez alfa kanálu), RGBA, ARGB, ABGR, 0RGB a 0BGR (možná existují i další reálně používané kombinace). Pro jednoduchost prozatím implementujme jen jedinou verzi funkce putpixel, v níž bude uspořádání barvových složek „zadrátováno“. Implementace se tak značně zjednoduší, protože pouze vypočteme adresu prvního bajtu pixelu s využitím znalostí o délce obrazového řádku a následně jen zapíšeme tři bajty s informacemi o barvových složkách. Celá implementace může vypadat následovně:
/* * Funkce putpixel platna pouze pro graficke rezimy true-color * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan). */ void putpixelRGBA(const int x, const int y, const char r, const char g, const char b, char *pixels, const int line_length) { /* vypocet adresy zapisu dat */ unsigned int index = (x<<2) + y*line_length; /* vlastni provedeni zapisu */ *(pixels+index) = b; index++; *(pixels+index) = g; index++; *(pixels+index) = r; }
Poznámka: sami si vyzkoušejte, jak tato funkce pracuje na Raspberry Pi a zda se barvy skutečně počítají správně. Tato funkce totiž byla naschvál odladěna na počítači s odlišným framebufferem, takže oprava (vcelku triviální) je ponechána na váženém čtenáři v rámci procvičení.
Překlad (s optimalizacemi) do assembleru procesorů ARMv6/v7 je vcelku přímočarý:
putpixelRGBA: @ args = 12, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 @ link register save eliminated. str r4, [sp, #-4]! ldr r4, [sp, #12] ldr ip, [sp, #8] mul r4, r4, r1 ldrb r1, [sp, #4] @ zero_extendqisi2 add r4, r4, r0, asl #2 strb r1, [ip, r4]! strb r3, [ip, #1] strb r2, [ip, #2] ldmfd sp!, {r4} bx lr
To ovšem neznamená, že putpixel je rychlá operace! Nemá smysl ji používat na rozsáhlejší vykreslování, protože část kódu pro výpočet barev a adresy se neustále (zbytečně) opakuje. To platí například i pro implementaci algoritmu pro vykreslování úseček.
10. Operace putpixel pro framebuffer se 16bitovou hloubkou
Složitější je již funkce putpixel, která musí pracovat v dalším grafickém režimu podporovaném počítačem Raspberry Pi. Jedná se o výchozí režim se šestnáctibitovou hloubkou, v němž se pro červenou a modrou složku používá pět bitů a pro složku zelenou pak bitů šest (z toho důvodu, že lidské oko dobře rozlišuje intenzitu světla právě v rozsahu zelené barvy). Kódování pixelů vypadá následovně:
1 1 1 1 1 1 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +-----------+-------------+-----------+ | R R R R R | G G G G G G | B B B B B | +-----------+-------------+-----------+
Jak tedy bude operace putpixel probíhat v tomto případě?
- Vstupem jsou složky Red, Green, Blue, každá o šířce osmi bitů.
- Složku Red snížíme na pět bitů posunem doprava o 3 bity.
- Složku Green snížíme na šest bitů posunem doprava o 2 bity.
- Složku Blue snížíme na pět bitů posunem doprava o 3 bity.
- Nyní složku Red posuneme doleva o 11 bitů, tj. na správnou pozici ve slově.
- Dále složku Green posuneme doleva o 5 bitů, tj. na správnou pozici ve slově.
- Složku Red nikam neposunujeme, je umístěna správně od bitu číslo 0.
- Výsledné Red, Green a Blue sečteme popř. použijeme operaci bitového OR (což vyjde nastejno, OR je však jednodušší).
- Výsledné 16bitové slovo rozdělíme na vyšší a nižší bajt.
- Oba bajty posléze uložíme do framebufferu.
/* * Funkce putpixel platna pouze pro graficke rezimy hi-color * s formatem 5-6-5. */ void putpixel565(const int x, const int y, const char r, const char g, const char b, char *pixels, const int line_length) { #define RED_OFFSET 11 #define GREEN_OFFSET 5 #define BLUE_OFFSET 0 #define RED_LOST_BITS 3 #define GREEN_LOST_BITS 2 #define BLUE_LOST_BITS 3 /* vypocet barvy pixelu, v zavorce nejdrive snizime bitovou sirku * rezervovanou pro jednotlive barvove slozky a posleze bity, ktere * reprezentuji barvovou slozku posuneme do spravne pozice ve slove */ unsigned int pixel_value = (r >> RED_LOST_BITS) << RED_OFFSET | (g >> GREEN_LOST_BITS) << GREEN_OFFSET | (b >> BLUE_LOST_BITS) << BLUE_OFFSET; /* prevod na dvojici bajtu */ unsigned char byte1 = pixel_value & 0xff; unsigned char byte2 = pixel_value >> 8; /* vypocet adresy zapisu dat */ unsigned int index = (x<<1) + y*line_length; /* vlastni provedeni zapisu */ *(pixels+index) = byte1; index++; *(pixels+index) = byte2; }
Podívejme se, jak vypadá překlad do assembleru procesorů ARMv6/v7:
putpixel565: @ args = 12, pretend = 0, frame = 0 @ frame_needed = 0, uses_anonymous_args = 0 @ link register save eliminated. stmfd sp!, {r4, r5} mov r3, r3, lsr #2 ldrb ip, [sp, #8] @ zero_extendqisi2 ldr r4, [sp, #16] mov r2, r2, lsr #3 ldr r5, [sp, #12] mov ip, ip, lsr #3 mul r4, r4, r1 orr r3, ip, r3, asl #5 ; kombinace přesunu a aritmetického posunu orr r2, r3, r2, asl #11 ; dtto add r4, r4, r0, asl #1 mov r3, r2, lsr #8 strb r2, [r5, r4]! strb r3, [r5, #1] ldmfd sp!, {r4, r5} bx lr
Kód je komplikovanější a taktéž pomalejší, ovšem na druhou stranu se například při vyplňování ploch nebo při vykreslování úseček zapisuje poloviční množství dat, což celou operaci urychluje (výpočet barev se provádí jen jedenkrát).
11. Výběr správné funkce putpixel
Další operací je výběr správné funkce putpixel na základě aktuální konfigurace framebufferu. Následující kód je velmi jednoduchý, protože rozeznává pouze dva formáty, nicméně přidání dalšího formátu je již poměrně jednoduché, minimálně v případě framebufferu počítače Raspberry Pi (povšimněte si způsobu použití ukazatele na funkce v céčku, kvůli tomu jsem vytvořil nový datový typ, jinak by byl kód nečitelný :-):
/* * Novy datovy typ - ukazatel na (libovolnou) funkci putpixel. */ typedef void (*PutpixelFunction)(const int, const int, const char, const char, const char, char*, const int); /* * Funkce, ktera vraci korektni funkci pro operaci putpixel(). */ PutpixelFunction getProperPutpixelFunction(int bits_per_pixel, int type, int visual) { /* umime rozeznat pouze format bez bitovych rovin a bez palety */ if (type == FB_TYPE_PACKED_PIXELS && visual == FB_VISUAL_TRUECOLOR) { if (bits_per_pixel == 16) { return putpixel565; } if (bits_per_pixel == 32) { /* toto neni zcela korektni, bylo by nutne rozlisit RGBA, ABGR, ARGB atd.*/ /* (ukol pro vazene ctenare :) */ return putpixelRGBA; } } return putpixelNull; }
12. Úplný zdrojový kód dnešního druhého demonstračního příkladu
Všechny úryvky zdrojových kódů a funkcí, o nichž jsme se zmínili v předchozích třech kapitolách, jsou součástí dnešního druhého demonstračního příkladu, jehož zdrojový kód je zobrazen pod tímto odstavcem a který je možné v případě zájmu získat na adrese https://github.com/tisnik/presentations/blob/master/rpi_framebuffer/rpi_fb6.c:
/* Framebuffer na jednodeskovem mikropocitaci Raspberry Pi */ /* Autor: Pavel Tisnovsky, 2016 */ /* Demonstracni priklad cislo 6: operace putpixel */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <linux/fb.h> /* * Datova struktura, do niz se ulozi informace o framebufferu. * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru * dostupnem v adresari "/usr/include/linux/fb.h" */ typedef struct fb_var_screeninfo FramebufferInfo; /* * Druha datova struktura popisujici zbyvajici vlastnosti framebufferu. * Blizsi informace o teto strukture je mozne nalezt v hlavickovem souboru * dostupnem v adresari "/usr/include/linux/fb.h" */ typedef struct fb_fix_screeninfo ModeInfo; /* * Precteni vsech relevantnich informaci zjistenych o framebufferu. Pro korektni * funkci je zapotrebi, aby mel uzivatel pristup k zarizeni /dev/fb0 * (postacuje byt ve skupine 'video' ci pouziti su/sudo) */ int readFramebufferInfo(int framebufferDevice, FramebufferInfo *framebufferInfoPtr, ModeInfo *modeInfoPtr) { /* Pokud operace ioctl probehne v poradku, vrati se 0 */ if (ioctl(framebufferDevice, FBIOGET_VSCREENINFO, framebufferInfoPtr)) { perror("Nelze precist informace o framebufferu"); return 0; } if (ioctl(framebufferDevice, FBIOGET_FSCREENINFO, modeInfoPtr)) { perror("Nelze precist informace o rezimu"); return 0; } return 1; } /* * Funkce putpixel platna pro nezname graficke rezimy. */ void putpixelNull(const int x, const int y, const char r, const char g, const char b, char *pixels, const int line_length) { } /* * Funkce putpixel platna pouze pro graficke rezimy true-color * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan). */ void putpixelRGBA(const int x, const int y, const char r, const char g, const char b, char *pixels, const int line_length) { /* vypocet adresy zapisu dat */ unsigned int index = (x<<2) + y*line_length; /* vlastni provedeni zapisu */ *(pixels+index) = b; index++; *(pixels+index) = g; index++; *(pixels+index) = r; } /* * Funkce putpixel platna pouze pro graficke rezimy hi-color * s formatem 5-6-5. */ void putpixel565(const int x, const int y, const char r, const char g, const char b, char *pixels, const int line_length) { #define RED_OFFSET 11 #define GREEN_OFFSET 5 #define BLUE_OFFSET 0 #define RED_LOST_BITS 3 #define GREEN_LOST_BITS 2 #define BLUE_LOST_BITS 3 /* vypocet barvy pixelu, v zavorce nejdrive snizime bitovou sirku * rezervovanou pro jednotlive barvove slozky a posleze bity, ktere * reprezentuji barvovou slozku posuneme do spravne pozice ve slove */ unsigned int pixel_value = (r >> RED_LOST_BITS) << RED_OFFSET | (g >> GREEN_LOST_BITS) << GREEN_OFFSET | (b >> BLUE_LOST_BITS) << BLUE_OFFSET; /* prevod na dvojici bajtu */ unsigned char byte1 = pixel_value & 0xff; unsigned char byte2 = pixel_value >> 8; /* vypocet adresy zapisu dat */ unsigned int index = (x<<1) + y*line_length; /* vlastni provedeni zapisu */ *(pixels+index) = byte1; index++; *(pixels+index) = byte2; } /* * Novy datovy typ - ukazatel na (libovolnou) funkci putpixel. */ typedef void (*PutpixelFunction)(const int, const int, const char, const char, const char, char*, const int); /* * Funkce, ktera vraci korektni funkci pro operaci putpixel(). */ PutpixelFunction getProperPutpixelFunction(int bits_per_pixel, int type, int visual) { /* umime rozeznat pouze format bez bitovych rovin a bez palety */ if (type == FB_TYPE_PACKED_PIXELS && visual == FB_VISUAL_TRUECOLOR) { if (bits_per_pixel == 16) { return putpixel565; } if (bits_per_pixel == 32) { /* toto neni zcela korektni, bylo by nutne rozlisit RGBA, ABGR, ARGB atd.*/ /* (ukol pro vazene ctenare :) */ return putpixelRGBA; } } return putpixelNull; } /* * Vykresleni testovaciho obrazku s vyuzitim funkce putpixel. */ void drawTestImage(int framebufferDevice, FramebufferInfo *framebufferInfoPtr, ModeInfo *modeInfoPtr) { #define OFFSET 300 /* casto pouzivane konstanty */ const int buffer_length = modeInfoPtr->smem_len; const int xres = framebufferInfoPtr->xres; const int yres = framebufferInfoPtr->yres; /* ziskame spravnou verzi funkce putpixel */ PutpixelFunction putpixel = getProperPutpixelFunction(framebufferInfoPtr->bits_per_pixel, modeInfoPtr->type, modeInfoPtr->visual); /* ziskat primy pristup do framebufferu */ char *pixels = (char*)mmap(0, buffer_length, PROT_READ | PROT_WRITE, MAP_SHARED, framebufferDevice, 0); if (pixels != MAP_FAILED) { int x, y; int r, g, b; /* nejprve vymazeme cely framebuffer */ memset(pixels, 0, buffer_length); /* vykreslime nekolik ctvercu o velikosti 256x256 pixelu */ for (y=0; y<256; y++) { for (x=0; x<256; x++) { /* prvni rada - gradientni prechody */ if (yres > 256) { /* cerveny gradient */ if (xres > 256) { r=y; g=0; b=0; putpixel(x, y, r, g, b, pixels, modeInfoPtr->line_length); } /* zeleny gradient */ if (xres > 256 + OFFSET) { r=0; g=y; b=0; putpixel(OFFSET+x, y, r, g, b, pixels, modeInfoPtr->line_length); } /* modry gradient */ if (xres > 256 + OFFSET*2) { r=0; g=0; b=y; putpixel(OFFSET*2+x, y, r, g, b, pixels, modeInfoPtr->line_length); } /* grayscale gradient */ if (xres > 256 + OFFSET*3) { r=y; g=y; b=y; putpixel(OFFSET*3+x, y, r, g, b, pixels, modeInfoPtr->line_length); } } /* druha rada - palety */ if (yres > 256 + OFFSET) { if (xres > 256) { r=x; g=y; b=0; putpixel(x, OFFSET+y, r, g, b, pixels, modeInfoPtr->line_length); } if (xres > 256 + OFFSET) { r=x; g=y; b=255; putpixel(OFFSET+x, OFFSET+y, r, g, b, pixels, modeInfoPtr->line_length); } if (xres > 256 + OFFSET*2) { r=255; g=x; b=y; putpixel(OFFSET*2+x, OFFSET+y, r, g, b, pixels, modeInfoPtr->line_length); } if (xres > 256 + OFFSET*3) { r=y; g=255; b=x; putpixel(OFFSET*3+x, OFFSET+y, r, g, b, pixels, modeInfoPtr->line_length); } } } } getchar(); munmap(pixels, buffer_length); } else { perror("Nelze pristupovat k framebufferu"); } } /* Vstupni bod do demonstraniho prikladu... :) */ int main(int argc, char **argv) { FramebufferInfo framebufferInfo; ModeInfo modeInfo; int framebufferDevice = 0; /* Ze zarizeni potrebujeme cist i zapisovat.*/ framebufferDevice = open("/dev/fb0", O_RDWR); /* Pokud otevreni probehlo uspesne, nacteme * a nasledne vypiseme informaci o framebufferu.*/ if (framebufferDevice != -1) { /* Precteni informaci o framebufferu a test, zda se vse podarilo */ if (readFramebufferInfo(framebufferDevice, &framebufferInfo, &modeInfo)) { drawTestImage(framebufferDevice, &framebufferInfo, &modeInfo); } close(framebufferDevice); return 0; } /* Otevreni se nezadarilo, vypiseme tudiz pouze chybove hlaseni.*/ else { perror("Nelze otevrit ovladac /dev/fb0"); return 1; } } /* finito */
Poznámka: kód pracuje korektně pro hloubku 16bpp, zatímco pro hloubku 32bpp budou barvy prohozené. Úprava je jednoduchá, jak jsem se již ostatně zmínil na konci deváté kapitoly.
13. Repositář s demonstračními příklady
Oba dva demonstrační příklady, s nimiž jsme se v dnešním článku seznámili, byly uloženy do Git repositáře umístěného na GitHubu na adrese (https://github.com/tisnik/presentations):
# | Příklad | Zdrojový kód |
---|---|---|
1 | rpi_fb5.c | https://github.com/tisnik/presentations/blob/master/rpi_framebuffer/rpi_fb5.c |
2 | rpi_fb6.c | https://github.com/tisnik/presentations/blob/master/rpi_framebuffer/rpi_fb6.c |
Pro překlad obou demonstračních příkladů je zapotřebí mít nainstalován překladač GNU C (či Clang), linker a vývojářskou verzi libc.
14. Odkazy na Internetu
- 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/ - 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/