Framebuffer na Raspberry Pi: vykreslování složitějších objektů

Pavel Tišnovský 4. 2. 2016

Ve třetí části článku o použití framebufferu na populárním jednodeskovém mikropočítači Raspberry Pi si na trojici příkladů ukážeme, jak lze využít znalosti o formátu framebufferu při vykreslování složitějších objektů, především úseček.

Obsah

1. Operace s framebufferem na Raspberry Pi (vykreslování složitějších objektů)

2. Vylepšení vykreslování pixelů do framebufferů s formáty RGBA a BGRA (true color)

3. Demonstrační příklad fpi_fb7.c: vylepšená operace typu putpixel

4. Slavný Bresenhamův algoritmus pro vykreslení úsečky

5. Adaptace Bresenhamova algoritmu s voláním funkce putpixel

6. Úplný zdrojový kód dnešního druhého demonstračního příkladu fpi_fb8.c

7. Urychlení vykreslování úseček: rychlejší výpočet barev a adres pixelů

8. Bresenhamův algoritmus pro framebuffery s formáty RGBA a BGRA (true color)

9. Bresenhamův algoritmus pro framebuffery s formátem hi-color 565

10. Úplný zdrojový kód dnešního třetího demonstračního příkladu fpi_fb9.c

11. Repositář s demonstračními příklady

12. Odkazy na Internetu

1. Operace s framebufferem na Raspberry Pi (vykreslování složitějších objektů)

Způsob zápisu barev jednotlivých pixelů do framebufferu, s nímž jsme se seznámili ve druhé části tohoto seriálu, je možné použít i pro vykreslování složitějších objektů. V některých knihovnách, jmenujme například populární SDL, je množina objektů (či geometrických tvarů), které je možné přímo vykreslit, poměrně malá a omezuje se vlastně jen na vyplněné osově orientované obdélníky (funkce SDL_FillRect). Pro vykreslování složitějších objektů je nutné použít nějaké nadstavbové knihovny postavené nad SDL. Podobně je koncipována i nová verze konkurenční knihovny Allegro, v níž se složitější objekty vykreslují s využitím přídavného modulu nazvaného allegro_primitives.

Obrázek 1: Pro tvorbu 2D a 3D grafů je ve skutečnosti zapotřebí implementovat vykreslování jen minimálního počtu grafických objektů – bodů, úseček a znaků.

Dnes si ukážeme, jakým způsobem je možné implementovat funkce pro vykreslování úseček. Tento geometrický tvar byl zvolen z toho důvodu, že si při jeho vykreslování již nevystačíme s pouhou operací typu putpixel, resp. přesněji řečeno je použití této funkce při kreslení úseček velmi pomalé (a to i na Raspberry Pi). Ovšem druhý důvod, proč jsem vybral zrovna algoritmus pro vykreslování úseček, je praktičtější: setkal jsem se již s několika projekty, v nichž by bylo vhodné využít Raspberry Pi nejenom k řízení nějakých zařízení či ke zjišťování různých údajů z připojených čidel (teplota atd.), ale i k zobrazení naměřených či vypočtených hodnot formou grafu na připojený monitor. A právě při kresbě os grafů, uživatelského rozhraní i vlastních průběhů hodnot může být rychlý algoritmus pro kreslení úseček užitečný, zejména po doplnění knihovny funkcemi pro práci s fonty.

Obrázek 2: I s naprostým minimem kreslicích funkcí je možné vytvořit působivé retro hry (zde screenshot z původní varianty slavné automatovky Lunar Lander).

2. Vylepšení vykreslování pixelů do framebufferů s formáty RGBA a BGRA (true color)

Před popisem algoritmu vykreslení úseček se ještě na chvíli zastavme u způsobu kódování barev u pixelů zapisovaných do framebufferů podporujících tzv. „pravé barvy“ neboli „true-color“. U takto nakonfigurovaných framebufferů se pro zakódování barvy pixelu používají buď tři bajty nebo bajty čtyři, přičemž druhá varianta sice vede k nárůstu kapacity framebufferu o 25%, ovšem předností je uchování hodnoty (barvy) pixelu v jediném 32bitovém slově, což může znamenat značné urychlení některých operací (to se týká i dnes prezentovaného algoritmu pro vykreslení úsečky). Problém je jen jeden – jak správně reagovat na různé způsoby ukládání barev pixelů. U těch framebufferů, kde se pro uložení barvy jednoho pixelu používají tři bajty, se setkáme s posloupností R-G-B či B-G-R. Zajímavější (čti komplikovanější) je situace v případě, že je jeden pixel reprezentován čtyřmi bajty. Zde se mohou vyskytnout kombinace R-G-B-A, B-G-R-A, A-R-G-B či A-B-G-R (A je buď alfa kanál nebo hodnota, která se nijak neinterpretuje).

V příkladu, který jsme si ukázali v závěru předchozího článku, se pro rozhodování, jak zapisovat barvové složky do framebufferu, používala poměrně jednoduchá funkce, v níž se rozhodovalo pouze na základě bitové hloubky framebufferu:

/*
 * 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;
}

Obrázek 3: Zápis barvových složek do framebufferu na Raspberry Pi ve špatném pořadí (viz demonstrační příklad z minulého článku).

Aby se tato funkce chovala správně minimálně na Raspberry Pi, je nutné ji nepatrně upravit:

/*
 * Funkce, ktera vraci korektni funkci pro operaci putpixel().
 */
PutpixelFunction getProperPutpixelFunction(int bits_per_pixel, int type, int visual, int redOffset)
{
    /* 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) {
            if (redOffset == 16) {
                return putpixelBGRA;
            }
            else {
                return putpixelRGBA;
            }
        }
    }
    return putpixelNull;
}

Záměr je zde zřejmý – u framebufferů s bitovou hloubkou 16bpp se stále používá stejná funkce putpixel565, zatímco u framebufferů s hloubkou 32bpp se na základě pozice (offsetu) červené barvové složky rozhodne, zda se jedná o formát RGBA či BGRA. Otázka pro čtenáře: co se stane v případě, že se program spustí na počítači s framebufferem, který má formát ABGR či ARGB? Resp. jinak: postačuje skutečně testovat pouze offset červené složky?

Obrázek 4: Zápis barvových složek do framebufferu na Raspberry Pi ve špatném pořadí (viz demonstrační příklad v další kapitole).

Doplňková funkce pro vykreslení pixelů je velmi jednoduchá:

/*
 * Funkce putpixel platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni pro Raspberry Pi s poradim bajtu R,G,B,A.
 */
void putpixelRGBA(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
    putpixelBGRA(x, y, b, g, r, pixels, line_length);
}

3. Demonstrační příklad fpi_fb7.c: vylepšená operace typu putpixel

Úplný zdrojový kód demonstračního příkladu, v němž se použije upravený algoritmus pro výběr správné funkce pro vykreslení pixelů, vypadá následovně a naleznete ho samozřejmě i na GitHubu. Tento příklad je funkční na Raspberry Pi jak při výběru 16bitové hloubky framebufferu, tak i při použití hloubky 32bitové:

/* Framebuffer na jednodeskovem mikropocitaci Raspberry Pi */
/* Autor: Pavel Tisnovsky, 2016 */
 
/* Demonstracni priklad cislo 7: rozdil mezi formatem RGBA a BGRA
 *                               pri vykreslovani pixelu. */
 
#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;
    }
 
    /* Pokud operace ioctl probehne v poradku, vrati se 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).
 * Funkcni napriklad pro graficke karty Intel.
 */
void putpixelBGRA(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;
    /* << 2 nahrazuje nasobeni ctyrmi */
 
    /* vlastni provedeni zapisu */
    *(pixels+index) = b;
    index++;
    *(pixels+index) = g;
    index++;
    *(pixels+index) = r;
}
 
 
 
/*
 * Funkce putpixel platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni pro Raspberry Pi s poradim bajtu R,G,B,A.
 */
void putpixelRGBA(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
    putpixelBGRA(x, y, b, g, r, pixels, line_length);
}
 
 
 
/*
 * 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;
    /* << 1 nahrazuje nasobeni dvema */
 
    /* 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, int redOffset)
{
    /* 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) {
            if (redOffset == 16) {
                return putpixelBGRA;
            }
            else {
                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,
                                                          framebufferInfoPtr->red.offset);
 
    /* 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
         * s gradientnim barevnym prechodem */
        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 demonstracniho 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 */

4. Slavný Bresenhamův algoritmus pro vykreslení úsečky

Pro vykreslení úseček bylo vyvinuto několik algoritmů, ovšem nejrychlejší (a v minulosti taktéž nejpoužívanější) je algoritmus navržený Jackem Eltonem Bresenhamem už v roce 1962. Tento algoritmus pro vykreslení úsečky, který je velmi podrobně popsán na Wikipedii, využívá pouze celočíselné operace a současně jsou v něm eliminovány aritmetické operace pro násobení a dělení, což je i dnes poměrně důležité (zejména na ARMech). Všechny výpočty se tak zjednoduší na aritmetický posun, sčítání, odčítání a podmíněné skoky. Právě díky těmto vlastnostem se Bresenhamův algoritmus stále v některých aplikacích používá, i když v případě požadavků na co nejvyšší kvalitu vykreslování se někdy přechází na pomalejší algoritmy s antialiasingem. Navíc, jak si ukážeme dále, se může dalšími optimalizacemi ještě více urychlit přístup k framebufferu, když se namísto souřadnic x a y pouze počítá offset od začátku framebufferu.

Obrázek 5: Typické „schody“ vykreslované Bresenhamovým algoritmem.

5. Adaptace Bresenhamova algoritmu s voláním funkce putpixel

Pokud se při vykreslování úseček spokojíme s menší rychlostí celého programu, je možné Bresenhamův algoritmus implementovat poměrně přímočarým způsobem, což je ostatně patrné z výpisu kódu, který naleznete pod tímto odstavcem. Pomocné lokální proměnné sx a sy slouží pro posuny souřadnice vykreslovaného pixelu, čímž bylo možné eliminovat rozepsání tohoto algoritmu pro všech osm oktantů (díky existenci sx a sy se jakoby pohybujeme pouze v prvním oktantu, i když ve skutečnosti může vykreslování probíhat v jiném směru). Pomocné proměnné dx a dy společně s proměnnou err se používají k určení směru vykreslování. Povšimněte si, že vykreslování vždy začíná v prvním vrcholu [x1, y1] a končí přesně ve druhém vrcholu [x2, y2], nezávisle na vzájemné pozici těchto vrcholů (může se jednat i o jediný bod):

/*
 * Funkce pro vykresleni usecky.
 */
void line(const int x1, const int y1, const int x2, const int y2,
          const char r, const char g, const char b,
          char *pixels, const int line_length, PutpixelFunction putpixel)
{
#define ABS(x) ((x)<0 ? -(x) : (x))
    int x = x1;
    int y = y1;
    /* zrcadleni algoritmu pro dalsi oktanty */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    while (1) {
        putpixel(x, y, r, g, b, pixels, line_length);
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
             err += dx;
             /* posun na predchozi ci nasledujici radek */
             y += sy;
        }
    }
}

Pro vykreslení každého pixelu je volána funkce předaná v parametru putpixel (přesněji řečeno se předává ukazatel na funkci).

6. Úplný zdrojový kód druhého demonstračního příkladu fpi_fb8.c

Podívejme se nyní, jak je výše uvedená implementace Bresenhamova algoritmu přidána do dalšího demonstračního příkladu, po jehož spuštění by se měl na obrazovce objevit následující vzorek:

Obrázek 6: Vzorek vykreslený demonstračním příkladem fpi_fb8.c

Úplný zdrojový kód tohoto příkladu vypadá následovně:

/* Framebuffer na jednodeskovem mikropocitaci Raspberry Pi */
/* Autor: Pavel Tisnovsky, 2016 */
 
/* Demonstracni priklad cislo 8: pomale vykreslovani usecek. */
 
#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;
    }
 
    /* Pokud operace ioctl probehne v poradku, vrati se 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).
 * Funkcni napriklad pro graficke karty Intel.
 */
void putpixelBGRA(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;
    /* << 2 nahrazuje nasobeni ctyrmi */
 
    /* vlastni provedeni zapisu */
    *(pixels+index) = b;
    index++;
    *(pixels+index) = g;
    index++;
    *(pixels+index) = r;
}
 
 
 
/*
 * Funkce putpixel platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni pro Raspberry Pi s poradim bajtu R,G,B,A.
 */
void putpixelRGBA(const int x, const int y,
                 const char r, const char g, const char b,
                 char *pixels, const int line_length)
{
    putpixelBGRA(x, y, b, g, r, pixels, line_length);
}
 
 
 
/*
 * 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;
    /* << 1 nahrazuje nasobeni dvema */
 
    /* 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, int redOffset)
{
    /* 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) {
            if (redOffset == 16) {
                return putpixelBGRA;
            }
            else {
                return putpixelRGBA;
            }
        }
    }
    return putpixelNull;
}
 
 
 
/*
 * Funkce pro vykresleni usecky.
 */
void line(const int x1, const int y1, const int x2, const int y2,
          const char r, const char g, const char b,
          char *pixels, const int line_length, PutpixelFunction putpixel)
{
#define ABS(x) ((x)<0 ? -(x) : (x))
    int x = x1;
    int y = y1;
    /* zrcadleni algoritmu pro dalsi oktanty */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    while (1) {
        putpixel(x, y, r, g, b, pixels, line_length);
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
             err += dx;
             /* posun na predchozi ci nasledujici radek */
             y += sy;
        }
    }
}
 
 
 
/*
 * Vykresleni testovaciho obrazku s vyuzitim funkci line a putpixel.
 */
void drawTestImage(int framebufferDevice,
                   FramebufferInfo *framebufferInfoPtr,
                   ModeInfo        *modeInfoPtr)
{
#define OFFSET 300
    /* casto pouzivane konstanty */
    const int buffer_length = modeInfoPtr->smem_len;
    const int pitch = modeInfoPtr->line_length;
 
    /* ziskame spravnou verzi funkce putpixel */
    PutpixelFunction putpixel = getProperPutpixelFunction(framebufferInfoPtr->bits_per_pixel,
                                                          modeInfoPtr->type,
                                                          modeInfoPtr->visual,
                                                          framebufferInfoPtr->red.offset);
 
    /* 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 i;
        int r, g, b;
        /* nejprve vymazeme cely framebuffer */
        memset(pixels, 0, buffer_length);
 
        /* vykreslime nekolik usecek s ruznym sklonem a barvou*/
        for (i=0; i<256; i++) {
            r = i;
            g = i;
            b = 256-i;
            line(i*3, 0, i*3, 100, r, g, b, pixels, pitch, putpixel);
            r = 255;
            g = i;
            b = 255-i;
            line(i*4, 150, i*5, 250, r, g, b, pixels, pitch, putpixel);
        }
        for (i=0; i<=300; i+=10) {
            line(0, 300 + i, i, 300 + 300, 255, 255, 255, pixels, pitch, putpixel);
 
            line(300, 300, 600, 300 + i, 128, 128, 255, pixels, pitch, putpixel);
        }
        getchar();
        munmap(pixels, buffer_length);
    }
    else {
        perror("Nelze pristupovat k framebufferu");
    }
}
 
 
 
/* Vstupni bod do demonstracniho 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 */

7. Urychlení vykreslování úseček: rychlejší výpočet barev a adres pixelů

Samotný Bresenhamův algoritmus je sice velmi rychlý, ovšem naše původní implementace zcela jistě velkou rychlostí vykreslování neoplývala. Je tomu tak z jednoduchého důvodu – i když se souřadnice jednotlivých pixelů ležících na úsečce počítají bez použití výpočetně náročných operací pro násobení a dělení, nakonec se zavolá funkce typu putpixel, kde se již tyto operace používají. Co je ještě horší – barva se ve funkci putpixel počítá stále znovu, což znamená, že se pro jedinou úsečku o délce řekněme 500 pixelů spočte 500× barva a 500× offset, na němž je zapotřebí provést zápis do framebufferu. Aby se celá operace urychlila, je možné provést tyto úpravy:

  1. Eliminovat volání funkce putpixel v Bresenhamově algoritmu. To se provede jednoduše: přesunem kódu přímo do funkce pro vykreslení úsečky.
  2. Výpočet barvy provést jen jednou. Z předchozího bodu plyne, že po přesunu kódu se výpočet barvy skutečně může provést ještě před vlastním vykreslováním.
  3. Výpočet offsetu ze souřadnic x a y taktéž provést jen jedenkrát. Toto je asi nejzajímavější část. Při posunu pixelu horizontálním směrem se vlastně mění offset o hodnotu ±bpp (2 či 4 bajty), při posunu pixelu vertikálním směrem pak o ± délku řádku (tu známe).
  4. Optimalizace samotného zápisu (nezapisovat po bajtech, ale po slovech).

8. Bresenhamův algoritmus pro framebuffery s formáty RGBA a BGRA (true color)

Podívejme se, jak lze první tři body aplikovat u framebufferů s formáty RGBA či BGRA. Uvedeme si řešení pouze pro jeden z těchto formátů, protože úprava spočívající v prohození barvových složek je triviální (a není ji vlastně zapotřebí moc řešit, protože se ta stejná funkce zavolá s mírně pozměněnými parametry):

/*
 * Funkce line platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni naproklad pro graficke karty Intel.
 */
void lineBGRA(const int x1, const int y1, const int x2, const int y2,
              const char r, const char g, const char b,
              char *pixels, const int line_length)
{
    /* vypocet adresy zapisu dat */
    unsigned int index = (x1<<2) + y1*line_length;
    /* << 2 nahrazuje nasobeni ctyrmi */
 
    /* vlastni provedeni vykresleni usecky */
    int x = x1;
    int y = y1;
    /* zrcadleni algoritmu pro dalsi oktanty */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    /* pri posunu po x-ove ose se index musi zvysit ci snizit o 4 (bajty) */
    int offsetX = x1<x2 ? 4: -4;
    /* pri posunu po y-ove ose se index musi zvysit ci snizit o delku radku (v bajtech) */
    int offsetY = y1<y2 ? line_length : - line_length;
 
    while (1) {
        /* vlastni provedeni zapisu barvy pixelu */
        *(pixels+index) = b;
        *(pixels+index+1) = g;
        *(pixels+index+2) = r;
 
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
            index += offsetX;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
            err += dx;
            /* posun na predchozi ci nasledujici radek */
            y += sy;
            index += offsetY;
        }
    }
}

Do původní implementace Bresenhamova algoritmu jsme zasáhli jen na dvou místech: namísto volání putpixel se přímo provede vykreslení a kromě posunu x-ové a y-ové složky souřadnic pixelu se mění i hodnota offsetu.

Stále se však při změně barvy pixelů provádí tři zápisy, a – což je možná ještě horší – zapisují se bajty a nikoli širší slova. To lze relativně snadno upravit, a to tak, že se na framebuffer nebudeme dívat jako na pole bajtů, ale jako na pole 32bitových hodnot. Ukazatelová aritmetika se nám tak paradoxně poněkud zjednoduší, pouze na začátku budeme muset vypočítat barvu pixelu a uložit ji do 32bitového slova:

/*
 * Funkce line platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni naproklad pro graficke karty Intel.
 */
void lineBGRA(const int x1, const int y1, const int x2, const int y2,
              const unsigned char r, const unsigned char g, const unsigned char b,
              char *pixels, const int line_length)
{
    /* vypocet adresy zapisu dat */
    /* pocitame v 32bitovych slovech, tj. line_length je nutne podelit ctyrmi */
    unsigned int index = x1 + (y1*line_length >> 2);
    /* >> 2 nahrazuje deleni ctyrmi */
 
    /* vlastni provedeni vykresleni usecky */
    int x = x1;
    int y = y1;
    /* zrcadleni algoritmu pro dalsi oktanty */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    /* uplna barva v jednom slove */
    __u32 color = (r<<16) | (g<<8) | b;
    __u32 *pixels32 = (__u32*)pixels;
 
    /* pri posunu po x-ove ose se index musi zvysit ci snizit o 4 (bajty) tj. o jedno 32-bitove slovo */
    int offsetX = x1<x2 ? 1: -1;
    /* pri posunu po y-ove ose se index musi zvysit ci snizit o delku radku (ve slovech) */
    int offsetY = y1<y2 ? (line_length >> 2) : - (line_length >> 2);
 
    while (1) {
        /* vlastni provedeni zapisu barvy pixelu */
        *(pixels32+index) = color;
 
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
            index += offsetX;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
            err += dx;
            /* posun na predchozi ci nasledujici radek */
            y += sy;
            index += offsetY;
        }
    }
}

V algoritmu bylo nutné udělat jednu změnu: barvové složky jsou zde explicitně popsány typem unsigned char a nikoli pouze char, protože s nimi provádíme bitové posuny (při pouhém zápisu bajtů vlastně bylo jedno, jakého jsou typu).

9. Bresenhamův algoritmus pro framebuffery s formátem hi-color 565

Pro framebuffer s formátem hi-color 565, tj. s formátem, který je pro Raspberry Pi a Raspbian výchozí, si ukážeme pouze jednu variantu Bresenhamova algoritmu pro vykreslování úseček. V této variantě se pro každý pixel provedou dva zápisy (horní a dolní bajt). Úpravu stylem, s nímž jsme se seznámili na konci předchozí kapitoly, ponechám laskavému a zvídavému čtenáři za domácí úkol :-) [v případě zájmu samozřejmě stačí počkat, až commitnu změny do https://github.com/tisnik/pre­sentations/blob/master/rpi_fra­mebuffer/rpi_fb10.c]:

/*
 * Funkce line platna pouze pro graficke rezimy hi-color
 * s formatem 5-6-5.
 */
void line565(const int x1, const int y1, const int x2, const int y2,
             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 = (x1<<1) + y1*line_length;
    /* << 1 nahrazuje nasobeni dvema */
 
    /* vykresleni usecky */
    int x = x1;
    int y = y1;
    /* zrcadleni algoritmu pro dalsi oktanty */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    /* pri posunu po x-ove ose se index musi zvysit ci snizit o 2 (bajty) */
    int offsetX = x1<x2 ? 2: -2;
    /* pri posunu po y-ove ose se index musi zvysit ci snizit o delku radku (v bajtech) */
    int offsetY = y1<y2 ? line_length : - line_length;
 
    while (1) {
        /* vlastni provedeni zapisu barvy pixelu */
        *(pixels+index) = byte1;
        *(pixels+index+1) = byte2;
 
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
            index += offsetX;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
            err += dx;
            /* posun na predchozi ci nasledujici radek */
            y += sy;
            index += offsetY;
        }
    }
}

Poznámky k předchozímu kódu:

  1. Barvy vykreslovaných pixelů jsou opět vypočteny před vstupem do programové smyčky, zapisované hodnoty jsou uloženy do proměnných pojmenovanýchbyte1 a byte2.
  2. Při posunu na další (předchozí) pixel na řádku se adresa změní o ±1: viz proměnnou offsetX naplňovanou opět před smyčkou.
  3. Při posunu na další (předchozí) řádek se adresa změní o hodnotu line_length: viz proměnnou offsetY.
  4. Proč je podmínka pro ukončení smyčky umístěna přesně tam kde je? Druhá otázka za domácí úkol :-)

10. Úplný zdrojový kód třetího demonstračního příkladu fpi_fb9.c

Bez dalších komentářů se podívejme, jak se předchozí dvě funkce staly součástí demonstračního příkladu https://github.com/tisnik/pre­sentations/blob/master/rpi_fra­mebuffer/rpi_fb9.c:

/* Framebuffer na jednodeskovem mikropocitaci Raspberry Pi */
/* Autor: Pavel Tisnovsky, 2016 */
 
/* Demonstracni priklad cislo 9: vykreslovani usecek: rychlejsi varianta
 *                               nepouzivajici funkci 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>
 
 
 
#define ABS(x) ((x)<0 ? -(x) : (x))
 
/*
 * 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;
    }
 
    /* Pokud operace ioctl probehne v poradku, vrati se 0 */
    if (ioctl(framebufferDevice, FBIOGET_FSCREENINFO, modeInfoPtr)) {
        perror("Nelze precist informace o rezimu");
        return 0;
    }
    return 1;
}
 
 
 
/*
 * Funkce line() platna pro nezname graficke rezimy.
 */
void lineNull(const int x1, const int y1, const int x2, const int y2,
              const char r, const char g, const char b,
              char *pixels, const int line_length)
{
}
 
 
 
/*
 * Funkce line platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni naproklad pro graficke karty Intel.
 */
void lineBGRA(const int x1, const int y1, const int x2, const int y2,
              const char r, const char g, const char b,
              char *pixels, const int line_length)
{
    /* vypocet adresy zapisu dat */
    unsigned int index = (x1<<2) + y1*line_length;
    /* << 2 nahrazuje nasobeni ctyrmi */
 
    /* vlastni provedeni vykresleni usecky */
    int x = x1;
    int y = y1;
    /* zrcadleni algoritmu pro dalsi oktanty */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    /* pri posunu po x-ove ose se index musi zvysit ci snizit o 4 (bajty) */
    int offsetX = x1<x2 ? 4: -4;
    /* pri posunu po y-ove ose se index musi zvysit ci snizit o delku radku (v bajtech) */
    int offsetY = y1<y2 ? line_length : - line_length;
 
    while (1) {
        /* vlastni provedeni zapisu barvy pixelu */
        *(pixels+index) = b;
        *(pixels+index+1) = g;
        *(pixels+index+2) = r;
 
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
            index += offsetX;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
            err += dx;
            /* posun na predchozi ci nasledujici radek */
            y += sy;
            index += offsetY;
        }
    }
}
 
 
 
/*
 * Funkce line platna pouze pro graficke rezimy true-color
 * s formatem 8-8-8-8 (popr. muze byt alfa kanal ignorovan).
 * Funkcni pro Raspberry Pi s poradim bajtu R,G,B,A.
 */
void lineRGBA(const int x1, const int y1, const int x2, const int y2,
              const char r, const char g, const char b,
              char *pixels, const int line_length)
{
    lineBGRA(x1, y1, x2, y2, b, g, r, pixels, line_length);
}
 
 
 
/*
 * Funkce line platna pouze pro graficke rezimy hi-color
 * s formatem 5-6-5.
 */
void line565(const int x1, const int y1, const int x2, const int y2,
             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 = (x1<<1) + y1*line_length;
    /* << 1 nahrazuje nasobeni dvema */
 
    /* vykresleni usecky */
    int x = x1;
    int y = y1;
    /* zrcadleni algoritmu pro dalsi oktanty */
    int dx = ABS(x2-x1), sx = x1<x2 ? 1 : -1;
    int dy = ABS(y2-y1), sy = y1<y2 ? 1 : -1;
    int err = (dx>dy ? dx : -dy)/2, e2;
 
    /* pri posunu po x-ove ose se index musi zvysit ci snizit o 2 (bajty) */
    int offsetX = x1<x2 ? 2: -2;
    /* pri posunu po y-ove ose se index musi zvysit ci snizit o delku radku (v bajtech) */
    int offsetY = y1<y2 ? line_length : - line_length;
 
    while (1) {
        /* vlastni provedeni zapisu barvy pixelu */
        *(pixels+index) = byte1;
        *(pixels+index+1) = byte2;
 
        if (x==x2 && y==y2) {
            break;
        }
        e2 = err;
        if (e2 >-dx) {
            /* prepocet kumulovane chyby */
            err -= dy;
            /* posun na predchozi ci dalsi pixel na radku */
            x += sx;
            index += offsetX;
        }
        if (e2 < dy) {
            /* prepocet kumulovane chyby */
            err += dx;
            /* posun na predchozi ci nasledujici radek */
            y += sy;
            index += offsetY;
        }
    }
}
 
 
 
/*
 * Novy datovy typ - ukazatel na (libovolnou) funkci line.
 */
typedef void (*LineFunction)(const int, const int, const int, const int,
                             const char, const char, const char,
                             char*, const int);
 
 
 
/*
 * Funkce, ktera vraci korektni funkci pro operaci line().
 */
LineFunction getProperLineFunction(int bits_per_pixel, int type, int visual, int redOffset)
{
    /* 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 line565;
        }
        if (bits_per_pixel == 32) {
            if (redOffset == 16) {
                return lineBGRA;
            }
            else {
                return lineRGBA;
            }
        }
    }
    return lineNull;
}
 
 
 
/*
 * Vykresleni testovaciho obrazku s vyuzitim funkce line.
 */
void drawTestImage(int framebufferDevice,
                   FramebufferInfo *framebufferInfoPtr,
                   ModeInfo        *modeInfoPtr)
{
#define OFFSET 300
    /* casto pouzivane konstanty */
    const int buffer_length = modeInfoPtr->smem_len;
    const int pitch = modeInfoPtr->line_length;
 
    /* ziskame spravnou verzi funkce line */
    LineFunction line = getProperLineFunction(framebufferInfoPtr->bits_per_pixel,
                                              modeInfoPtr->type,
                                              modeInfoPtr->visual,
                                              framebufferInfoPtr->red.offset);
 
    /* 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 i;
        int r, g, b;
        /* nejprve vymazeme cely framebuffer */
        memset(pixels, 0, buffer_length);
 
        /* vykreslime nekolik usecek s ruznym sklonem a barvou*/
        for (i=0; i<256; i++) {
            r = i;
            g = i;
            b = 256-i;
            line(i*3, 0, i*3, 100, r, g, b, pixels, pitch);
            r = 255;
            g = i;
            b = 255-i;
            line(i*4, 150, i*5, 250, r, g, b, pixels, pitch);
        }
        for (i=0; i<=300; i+=10) {
            line(0, 300 + i, i, 300 + 300, 255, 255, 255, pixels, pitch);
 
            line(300, 300, 600, 300 + i, 128, 128, 255, pixels, pitch);
        }
        getchar();
        munmap(pixels, buffer_length);
    }
    else {
        perror("Nelze pristupovat k framebufferu");
    }
}
 
 
 
/* Vstupni bod do demonstracniho 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 */

Obrázek 7: Výsledek běhu dnešního posledního demonstračního příkladu na počítači s framebufferem nakonfigurovaným pro použití true color formátu RGBA.

11. Repositář s demonstračními příklady

Všechny tři 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/pre­sentations):

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.

12. Odkazy na Internetu

  1. Seriál Grafické karty a grafické akcelerátory
    http://www.root.cz/serialy/graficke-karty-a-graficke-akceleratory/
  2. Grafika na osmibitových počítačích firmy Sinclair II
    http://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair-ii/
  3. Grafické čipy v osmibitových počítačích Atari
    http://www.root.cz/clanky/graficke-cipy-v-osmibitovych-pocitacich-atari/
  4. Osmibitové počítače Commodore a čip VIC-II
    http://www.root.cz/clanky/osmibitove-pocitace-commodore-a-cip-vic-ii/
  5. Grafika na osmibitových počítačích firmy Apple
    http://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-apple/
  6. Počátky grafiky na PC: grafické karty CGA a Hercules
    http://www.root.cz/clanky/pocatky-grafiky-na-pc-graficke-karty-cga-a-hercules/
  7. Karta EGA: první použitelná barevná grafika na PC
    http://www.root.cz/clanky/karta-ega-prvni-pouzitelna-barevna-grafika-na-pc/
  8. Grafické karty MCGA a VGA
    http://www.root.cz/clanky/graficke-karty-mcga-a-vga/
  9. Grafický subsystém počítačů Amiga
    http://www.root.cz/clanky/graficky-subsystem-pocitacu-amiga/
  10. Grafický subsystém počítačů Amiga II
    http://www.root.cz/clanky/graficky-subsystem-pocitacu-amiga-ii/
  11. Raspberry Pi pages
    https://www.raspberrypi.org/
  12. BCM2835 registers
    http://elinux.org/BCM2835_registers
  13. VideoCore (archiv stránek společnosti Alphamosaic)
    http://web.archive.org/web/20030209213838/www­.alphamosaic.com/videocore/
  14. VideoCore (Wikipedia)
    https://en.wikipedia.org/wi­ki/Videocore
  15. RPi lessons: Lesson 6 Screen01
    http://www.cl.cam.ac.uk/pro­jects/raspberrypi/tutorial­s/os/screen01.html
  16. Raspberry Pi forum: Bare metal
    https://www.raspberrypi.or­g/forums/viewforum.php?f=72
  17. C library for Broadcom BCM 2835 as used in Raspberry Pi
    http://www.airspayce.com/mi­kem/bcm2835/
  18. Raspberry Pi Hardware Components
    http://elinux.org/RPi_Har­dware#Components
  19. (Linux) Framebuffer
    http://wiki.linuxquestion­s.org/wiki/Framebuffer
  20. (Linux) Framebuffer HOWTO
    http://tldp.org/HOWTO/Framebuffer-HOWTO/
  21. Linux framebuffer (Wikipedia)
    https://en.wikipedia.org/wi­ki/Linux_framebuffer
  22. RPi Framebuffer
    http://elinux.org/RPi_Framebuffer
  23. 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/
  24. Zdrojový kód fb.c pro RPI
    https://github.com/jncronin/rpi-boot/blob/master/fb.c
  25. RPiconfig
    http://elinux.org/RPi_config.txt
  26. Mailbox framebuffer interface
    https://github.com/raspbe­rrypi/firmware/wiki/Mailbox-framebuffer-interface
  27. Seriál Grafické formáty
    http://www.root.cz/serialy/graficke-formaty/
Našli jste v článku chybu?
Měšec.cz: Platíme NFC mobilem. Konečně to funguje!

Platíme NFC mobilem. Konečně to funguje!

Podnikatel.cz: Babiš bude mít přehled o vašich účtech

Babiš bude mít přehled o vašich účtech

DigiZone.cz: ČTÚ zveřejnil aktualizovaný D-Book

ČTÚ zveřejnil aktualizovaný D-Book

Lupa.cz: Co vzal čas: internetové kavárny a herny

Co vzal čas: internetové kavárny a herny

Měšec.cz: Investiční pasti. Děláte to, co ostatní, ale proděláváte

Investiční pasti. Děláte to, co ostatní, ale proděláváte

DigiZone.cz: Hodlá Markíza skončit v DVB-T?

Hodlá Markíza skončit v DVB-T?

Podnikatel.cz: Týká se vás EET? Chtějte od berňáku posudek

Týká se vás EET? Chtějte od berňáku posudek

Lupa.cz: Kdo vykrádá LinkedIn? Zjistit to má soud

Kdo vykrádá LinkedIn? Zjistit to má soud

DigiZone.cz: První Ultra HD (4K) Blu-ray je tady

První Ultra HD (4K) Blu-ray je tady

Vitalia.cz: Vakcína Cervarix je oficiálně i pro chlapce

Vakcína Cervarix je oficiálně i pro chlapce

Podnikatel.cz: OSA zdraží, ale taky přidá nový poplatek

OSA zdraží, ale taky přidá nový poplatek

Vitalia.cz: Ženy, které milují příliš, jsou neštěstí

Ženy, které milují příliš, jsou neštěstí

Podnikatel.cz: Česká pošta vycouvala ze služby ČP Cloud

Česká pošta vycouvala ze služby ČP Cloud

Vitalia.cz: 9 potravin, které nesmí chybět v jídelníčku těhotné

9 potravin, které nesmí chybět v jídelníčku těhotné

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

Měšec.cz: Kurzy platebních karet: vyplatí se platit? (TEST)

Kurzy platebních karet: vyplatí se platit? (TEST)

Lupa.cz: Nechcete datacentrum? Jsou na prodej

Nechcete datacentrum? Jsou na prodej

Vitalia.cz: Je bílý kokos fakt tak úžasný? Ano, je!

Je bílý kokos fakt tak úžasný? Ano, je!

Podnikatel.cz: Oznamte skutečné sídlo firmy, jinak zaplatíte

Oznamte skutečné sídlo firmy, jinak zaplatíte

Vitalia.cz: Za její cukrovkou stojí rodiče

Za její cukrovkou stojí rodiče