Framebuffer devices are the most common way for a computer system to
display graphical output to users. There are immense variations in the
implementations of such devices. CYGPKG_IO_FRAMEBUF
provides an abstraction layer for use by application code and other
packages. It defines an API for manipulating framebuffers, mapping
this API on to functionality provided by the appropriate device
driver. It also defines the interface which such device drivers should
implement. For simple hardware it provides default implementations of
much of this interface, greatly reducing the effort needed to write a
device driver.
This package does not constitute a graphics library. It does not implement functionality like drawing text or arbitrary lines, let alone any kind of windowing system. Instead it operates at the lower level of individual pixels and blocks of pixels, in addition to control operations such as hardware initialization. Some applications may use the framebuffer API directly. Others will instead use a higher-level graphics library, and it is that library which uses the framebuffer API.
It is assumed that users are already familiar with the fundamentals of computer graphics, and no attempt is made here to explain terms like display depth, palette or pixel.
Note: This package is work-in-progress. The support for 1bpp, 2bpp and 4bpp display depths is incomplete. For double-buffered displays the code does not yet maintain a bounding box of the updated parts of the display. The package has also been designed to allow for expansion with new functionality.
CYGPKG_IO_FRAMEBUF
only contains
hardware-independent code. It should be complemented by one or more
framebuffer device drivers appropriate for the target platform. These
drivers may be specific to the platform, or they may be more generic
with platform-specific details such as the framebuffer memory base
address provided by the platform HAL. When creating a configuration
for a given target the device driver(s) will always be included
automatically (assuming one has been written or ported). However by
default this driver will be inactive and will not get built, so does
not add any unnecessary size overhead for applications which do not
require graphics. To activate the device driver
CYGPKG_IO_FRAMEBUF
must be added explicitly to the
configuration, for example using
ecosconfig add framebuf. After this the
full framebuffer API will be available to other packages and to
application code.
This package contains very few configuration options. Instead it is
left to device drivers or higher-level code to provide appropriate
configurability. One option,
CYGFUN_IO_FRAMEBUF_INSTALL_DEFAULT_PALETTE
, relates
to the initialization of paletted displays.
There are a number of calculated and inferred configuration options
and a number of interfaces. These provide information such as whether
or not there is a backlight. The most important one is
CYGDAT_IO_FRAMEBUF_DEVICES
, which holds a list of
framebuffer identifiers for use with the macro-based API. If there is a single
framebuffer device driver which supports one display in either
landscape or portrait mode, the configuration option may hold a value
like 240x320x8 320x240x8r90.
Framebuffer devices require a difficult choice between flexibility and performance. On the one hand the API should be able to support multiple devices driving separate displays, or a single device operating in different modes at different times. On the other hand graphics tends to involve very large amounts of I/O: even something as simple as drawing a background image can involve setting many thousands of pixels. Efficiency requires avoiding all possible overheads including function calls. Instead the API should make extensive use of macros or inline functions. Ideally details of the framebuffer device such as the stride would be known constants at compile-time, giving the compiler as much opportunity as possible to optimize the code. Clearly this is difficult if multiple framebuffer devices are in use or if the device mode may get changed at run-time.
To meet the conflicting requirements the generic framebuffer package provides two APIs: a fast macro API which requires selecting a single framebuffer device at compile or configure time; and a slower function API without this limitation. The two are very similar, for example:
#include <cyg/io/framebuf.h> void clear_screen(cyg_fb* fb, cyg_fb_colour colour) { cyg_fb_fill_block(fb, 0, 0, fb->fb_width, fb->fb_height, colour); } |
or the equivalent macro version:
#include <cyg/io/framebuf.h> #define FRAMEBUF 240x320x8 void clear_screen(cyg_fb_colour colour) { CYG_FB_FILL_BLOCK(FRAMEBUF, 0, 0, CYG_FB_WIDTH(FRAMEBUF), CYG_FB_HEIGHT(FRAMEBUF), colour); } |
The function-based API works in terms of
cyg_fb
structures, containing all the
information needed to manipulate the device. Each framebuffer device
driver will export one or more of these structures, for example
cyg_alaia_fb_240x320x8
, and the driver
documentation should list the variable names. The macro API works in
terms of identifiers such as 240x320x8, and by a
series of substitutions the main macro gets expanded to the
appropriate device-specific code, usually inline. Again the device
driver documentation should list the supported identifiers. In
addition the configuration option
CYGDAT_IO_FRAMEBUF_DEVICES
will contain the full
list. By convention the identifier will be specified by a
#define'd symbol such as
FRAMEBUF
, or in the case of graphics libraries by a
configuration option.
If a platform has multiple framebuffer devices connected to different
displays then there will be separate cyg_fb
structures and macro identifiers for each one. In addition some
devices can operate in multiple modes. For example a PC VGA card can
operate in a monochome 640x480 mode, an 8bpp 320x200 mode, and many
other modes, but only one of these can be active at a time. The
different modes are also represented by different
cyg_fb
structures and identifiers,
effectively treating the modes as separate devices. It is the
responsibility of higher-level code to ensure that only one mode is in
use at a time.
It is possible to use the macro API with more than one device,
basically by compiling the code twice with different values of
FRAMEBUF
, taking appropriate care to avoid
identifier name clashes. This gives the higher performance of the
macros at the cost of increased code size.
All of the framebuffer API, including exports of the device-specific
cyg_fb
structures, is available through a
single header file <cyg/io/framebuf.h>. The
API follows a number of conventions. Coordinates (0,0) correspond to
the top-left corner of the display. All functions and macros which
take a pair of coordinates have x first, y second. For block
operations these coordinates are followed by width, then height.
Coordinates and dimensions use cyg_ucount16 variables,
which for any processor should be the most efficient unsigned data
type with at least 16 bits - usually plain unsigned integers. Colours
are identified by cyg_fb_colour variables, again usually
unsigned integers.
To allow for the different variants of the English language, the API
allows for a number of alternate spellings. Colour and color can be
used interchangeably, so there are data types
cyg_fb_colour and cyg_fb_color, and
functions cyg_fb_make_colour
and
cyg_fb_make_color
. Similarly gray is accepted as
a variant of grey so the predefined colours
CYG_FB_DEFAULT_PALETTE_LIGHTGREY and
CYG_FB_DEFAULT_PALETTE_LIGHTGRAY are equivalent.
The API is split into the following categories:
getting information about a given framebuffer device such as width, height and depth. Colours management is complicated so has its own category.
operations such as switching the display on and off, and more device-specific ones such as manipulating the backlight.
determining the colour format (monochrome, paletted, …), manipulating the palette, or constructing true colours.
primitives for manipulating pixels and blocks of pixels.
efficiently iterating over blocks of pixels.
The framebuffer API never performs any locking so is not thread-safe. Instead it assumes that higher-level code such as a graphics library performs any locking that may be needed. Adding a mutex lock and unlock around every drawing primitive, including pixel writes, would be prohibitively expensive.
It is also assumed that the framebuffer will only be updated from thread context. With most hardware it will also be possible to access a framebuffer from DSR or ISR context, but this should be avoided in portable code.