Framebuffer Pixel Manipulation

Name

Pixel Manipulation -- iterating over the display

Synopsis

#include <cyg/io/framebuf.h>
      

CYG_FB_PIXEL0_VAR(FRAMEBUF);

void CYG_FB_PIXEL0_SET(FRAMEBUF, cyg_ucount16 x, cyg_ucount16 y);

void CYG_FB_PIXEL0_GET(FRAMEBUF, cyg_ucount16 x, cyg_ucount16 y);

void CYG_FB_PIXEL0_ADDX(FRAMEBUF, cyg_ucount16 incr);

void CYG_FB_PIXEL0_ADDY(FRAMEBUF, cyg_ucount16 incr);

void CYG_FB_PIXEL0_WRITE(FRAMEBUF, cyg_fb_colour colour);

cyg_fb_colour CYG_FB_PIXEL0_READ(FRAMEBUF);

void CYG_FB_PIXEL0_FLUSHABS(FRAMEBUF, cyg_ucount16 x0, cyg_ucount16 y0, cyg_ucount16 width, cyg_ucount16 height);

void CYG_FB_PIXEL0_FLUSHREL(FRAMEBUF, cyg_ucount16 x0, cyg_ucount16 y0, cyg_ucount16 dx, cyg_ucount16 dy);

Description

A common requirement for graphics code is to iterate over parts of the framebuffer. Drawing text typically involves iterating over a block of pixels for each character, say 8 by 8, setting each pixel to either a foreground or background colour. Drawing arbitrary lines typically involves moving to the start position and then adjusting the x and y coordinates until the end position is reached, setting a single pixel each time around the loop. Drawing images which are not in the frame buffer's native format typically involves iterating over a block of pixels, from top to bottom and left to right, setting pixels as the image is decoded.

Functionality like this can be implemented in several ways. One approach is to use the pixel write primitive. Typically this involves some arithmetic to get from the x and y coordinates to a location within framebuffer memory so it is fairly expensive compared with a loop which just increments a pointer. Another approach is to write the data first to a separate buffer in memory and then use a block write primitive to move it to the framebuffer, but again this involves overhead. The eCos framebuffer support provides a third approach: a set of macros specifically for iterating over the frame buffer. Depending on the operation being performed and the details of the framebuffer implementation, these macros may be optimal or near-optimal. Obviously there are limitations. Most importantly the framebuffer device must be known at compile-time: the compiler can do a better job optimizing the code if information such as the frame buffer width are constant. Also each iteration must be performed within a single variable scope: it is not possible to do some of the iteration in one function, some in another.

The Pixel Macros

All the pixel macros take a framebuffer identifier as their first argument. This is the same identifier that can be used with the other macros like CYG_FB_WRITE_HLINE and CYG_FB_ON, one of the entries in the configuration option CYGDAT_IO_FRAMEBUF_DEVICES. Using an invalid identifier will result in numerous compile-time error messages which may bear little resemblance to the original code. In the examples below it is assumed that FRAMEBUF has been #define'd to a suitable identifier.

Typical use of the pixel macros will look like this:

    CYG_FB_PIXEL0_VAR(FRAMEBUF);
    …
    CYG_FB_PIXEL0_FLUSHABS(FRAMEBUF, x, y, width, height);

The VAR macro will define one or more local variables to keep track of the current pixel position, as appropriate to the framebuffer device. The other pixel macros will then use these variables. For a simple 8bpp linear framebuffer there will be just a byte pointer. For a 1bpp display there may be several variables: a byte pointer, a bit index within that byte, and possibly a cached byte; using a cached value means that the framebuffer may only get read and written once for every 8 pixels, and the compiler may well allocate a register for the cached value; on some platforms framebuffer access will bypass the processor's main cache, so reading from or writing to framebuffer memory will be slow; reducing the number of framebuffer accesses may greatly improve performance.

Because the VAR macro defines one or more local variables it is normally placed at the start of a function or block, alongside other local variable definitions.

One the iteration has been completed there should be a FLUSHABS or FLUSHREL macro. This serves two purposes. First, if the local variables involve a dirty cached value or similar state then this will be written back. Second, for double-buffered displays the macro sets a bounding box for the part of the screen that has been updated. This allows the double buffer synch operation to update only the part of the display that has been modified, without having to keep track of the current bounding box for every updated pixel. For FLUSHABS the x0 and y0 arguments specify the top-left corner of the bounding box, which extends for width by height pixels. For FLUSHREL x0 and y0 still specify the top-left corner, but the bottom-right corner is now determined from the current pixel position offset by dx and dy. More specifically, dx should move the current horizontal position one pixel to the right of the right-most pixel modified, such that (x + dx) - x0 gives the width of the bounding box. Similarly dy should move the current vertical position one pixel below the bottom-most pixel modified. In typical code the current pixel position will already correspond in part or in whole to the bounding box corner, as a consequence of iterating over the block of memory.

If a pixel variable has been used only for reading framebuffer memory, not for modifying it, then it should still be flushed. A FLUSHABS with a width and height of 0 can be used to indicate that the bounding box is empty. If it is known that the framebuffer device being used does not support double-buffering then again it is possible to specify an empty bounding box. Otherwise portable code should specify a correct bounding box. If the framebuffer device that ends up being used does not support double buffering then the relevant macro arguments are eliminated at compile-time and do not result in any unnecessary code. In addition if there is no cached value or other state then the whole flush operation will be a no-op and no code will be generated.

Failure to perform the flush may result in strange drawing artefacts on some displays which can be very hard to debug. A FLUSHABS or FLUSHREL macro only needs to be invoked once, at the end of the iteration.

The SET macro sets the current position within the framebuffer. It can be used many times within an iteration. However it tends to be somewhat more expensive than ADDX or ADDY, so usually SET is only executed once at the start of an iteration.

    CYG_FB_PIXEL0_VAR(FRAMEBUF);
    CYG_FB_PIXEL0_SET(FRAMEBUF, x, y);
    …
    CYG_FB_PIXEL0_FLUSHREL(FRAMEBUF, x, y, 0, 0);

The GET macro retrieves the x and y coordinates corresponding to the current position. It is provided mainly for symmetry, but can prove useful for debugging.

    CYG_FB_PIXEL0_VAR(FRAMEBUF);
    CYG_FB_PIXEL0_SET(FRAMEBUF, x, y);
    …
#ifdef DEBUG
    CYG_FB_PIXEL0_GET(FRAMEBUF, new_x, new_y);
    diag_printf("Halfway through: x now %d, y now %d\n", new_x, new_y);
#endif
    …
    CYG_FB_PIXEL0_FLUSHREL(FRAMEBUF, x, y, 0, 0);

The ADDX and ADDY macros adjust the current position. The most common increments are 1 and -1, moving to the next or previous pixel horizontally or vertically, but any increment can be used.

    CYG_FB_PIXEL0_VAR(FRAMEBUF);
    CYG_FB_PIXEL0_SET(FRAMEBUF, x, y);
    for (rows = height; rows; rows--) {
        for (columns = width; columns; columns--) {
            <perform operation>
            CYG_FB_PIXEL0_ADDX(FRAMEBUF, 1);
        }
        CYG_FB_PIXEL0_ADDX(FRAMEBUF, -1 * width);
        CYG_FB_PIXEL0_ADDY(FRAMEBUF, 1);
    }
    CYG_FB_PIXEL0_FLUSHREL(FRAMEBUF, x, y, width, 0);

Here the current position is moved one pixel to the right each time around the inner loop. In the outer loop the position is first moved back to the start of the current row, then moved one pixel down. For the final flush the current x position is off by width, but the current y position is already correct.

The final two macros READ and WRITE can be used to examine or update the current pixel value.

    CYG_FB_PIXEL0_VAR(FRAMEBUF);
    CYG_FB_PIXEL0_SET(FRAMEBUF, x, y);
    for (rows = height; rows; rows--) {
        for (columns = width; columns; columns--) {
            cyg_fb_colour colour = CYG_FB_PIXEL0_READ(FRAMEBUF);
            if (colour == colour_to_replace) {
                CYG_FB_PIXEL0_WRITE(FRAMEBUF, replacement);
            }
            CYG_FB_PIXEL0_ADDX(FRAMEBUF, 1);
        }
        CYG_FB_PIXEL0_ADDX(FRAMEBUF, -1 * width);
        CYG_FB_PIXEL0_ADDY(FRAMEBUF, 1);
    }
    CYG_FB_PIXEL0_FLUSHREL(FRAMEBUF, x, y, width, 0);

Concurrent Iterations

Although uncommon, in some cases application code may need to iterate over two or more blocks. An example might be an advanced block move where each copied pixel requires some processing. To support this there are PIXEL1, PIXEL2 and PIXEL3 variants of all the PIXEL0 macros. For example:

    CYG_FB_PIXEL0_VAR(FRAMEBUF);
    CYG_FB_PIXEL1_VAR(FRAMEBUF);

    CYG_FB_PIXEL0_SET(FRAMEBUF, dest_x, dest_y_);
    CYG_FB_PIXEL1_SET(FRAMEBUF, source_x, source_y);
    for (rows = height; rows; rows--) {
        for (columns = width; columns; columns--) {
            colour = CYG_FB_PIXEL1_READ(FRAMEBUF);
            <do some processing on colour>
            CYG_FB_PIXEL0_WRITE(FRAMEBUF, colour);
            CYG_FB_PIXEL0_ADDX(FRAMEBUF, 1);
            CYG_FB_PIXEL1_ADDX(FRAMEBUF, 1);
        }
        CYG_FB_PIXEL0_ADDX(FRAMEBUF, -100);
        CYG_FB_PIXEL0_ADDY(FRAMEBUF, 1);
        CYG_FB_PIXEL1_ADDX(FRAMEBUF, -100);
        CYG_FB_PIXEL1_ADDY(FRAMEBUF, 1);
    }

    CYG_FB_PIXEL0_FLUSHABS(FRAMEBUF, source_x, source_y, width, height);
    CYG_FB_PIXEL1_FLUSHABS(FRAMEBUF, 0, 0, 0, 0);  // Only used for reading

The PIXEL0, PIXEL1, PIXEL2 and PIXEL3 macros all use different local variables so there are no conflicts. The variable names also depend on the framebuffer device. If the target has two displays and two active framebuffer devices then the pixel macros can be used with the two devices without conflict:

    CYG_FB_PIXEL0_VAR(FRAMEBUF0);
    CYG_FB_PIXEL0_VAR(FRAMEBUF1);
    …