Framebuffer SupportOverviewOvervieweCos Support for Framebuffer DevicesDescription
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.
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.
ConfigurationCYGPKG_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.
Application Programmer Interfaces
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:
parameters
getting information about a given framebuffer device such as width,
height and depth. Colours management is complicated so has its own
category.
control
operations such as switching the display on and off, and more
device-specific ones such as manipulating the backlight.
colours
determining the colour format (monochrome, paletted, &hellip),
manipulating the palette, or constructing true colours.
drawing
primitives for manipulating pixels and blocks of pixels.
iteration
efficiently iterating over blocks of pixels.
Thread Safety
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.
Framebuffer ParametersParametersdetermining framebuffer capabilities
#include <cyg/io/framebuf.h>
typedef struct cyg_fb {
cyg_ucount16 fb_depth;
cyg_ucount16 fb_format;
cyg_ucount16 fb_width;
cyg_ucount16 fb_height;
#ifdef CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_VIEWPORT
cyg_ucount16 fb_viewport_width;
cyg_ucount16 fb_viewport_height;
#endif
void* fb_base;
cyg_ucount16 fb_stride;
cyg_uint32 fb_flags0;
…
} cyg_fb;
cyg_fb* CYG_FB_STRUCTFRAMEBUFcyg_ucount16 CYG_FB_DEPTHFRAMEBUFcyg_ucount16 CYG_FB_FORMATFRAMEBUFcyg_ucount16 CYG_FB_WIDTHFRAMEBUFcyg_ucount16 CYG_FB_HEIGHTFRAMEBUFcyg_ucount16 CYG_FB_VIEWPORT_WIDTHFRAMEBUFcyg_ucount16 CYG_FB_VIEWPORT_HEIGHTFRAMEBUFvoid* CYG_FB_BASEFRAMEBUFcyg_ucount16 CYG_FB_STRIDEFRAMEBUFcyg_uint32 CYG_FB_FLAGS0FRAMEBUFDescription
When developing an application for a specific platform the various
framebuffer parameters such as width and height are known, and the
code can be written accordingly. However when writing code that should
work on many platforms with different framebuffer devices, for example
a graphics library, the code must be able to get these parameters and
adapt.
Code using the function API can extract the parameters from the
cyg_fb structures at run-time. The macro API
provides dedicated macros for each parameter. These do not follow the
usual eCos convention where the result is provided via an extra
argument. Instead the result is returned as normal, and is guaranteed
to be a compile-time constant. This allows code like the following:
#if CYG_FB_DEPTH(FRAMEBUF) < 8
…
#else
…
#endif
or alternatively:
if (CYG_FB_DEPTH(FRAMEBUF) < 8) {
…
} else {
…
}
or:
switch (CYG_FB_DEPTH(FRAMEBUF)) {
case 1 : … break;
case 2 : … break;
case 4 : … break;
case 8 : … break;
case 16 : … break;
case 32 : … break;
}
In terms of the code actually generated by the compiler these
approaches have much the same effect. The macros expand to a
compile-time constant so unnecessary code can be easily eliminated.
The available parameters are as follows:
depth
The number of bits per pixel or bpp. The common depths are 1, 2, 4, 8,
16 and 32.
format
How the pixel values are mapped on to visible colours, for example true colour
or paletted or greyscale.
widthheight
The number of framebuffer pixels horizontally and vertically.
viewport widthviewport height
With some devices the framebuffer height and/or width are greater than
what the display can actually show. The display is said to offer a
viewport into the larger framebuffer. The number of visible pixels is
determined from the viewport width and height. The position of the
viewport is controlled via an ioctl.
Within a cyg_fb structure these fields are
only present if
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_VIEWPORT
is defined, to avoid wasting data space on fields that are unnecessary
for the current platform. For the macro API the viewport macros should only be used
if CYG_FB_FLAGS0_VIEWPORT is set for the framebuffer:
#if (CYG_FB_FLAGS0(FRAMEBUF) & CYG_FB_FLAGS0_VIEWPORT)
…
#endif
basestride
For linear
framebuffers these parameters provide the information needed to access
framebuffer memory. The stride is in bytes.
flags0
This gives further information about the hardware capabilities.
Some of this overlaps with other parameters, especially when it comes
to colour, because it is often easier to test for a single flag than
for a range of colour modes. The current flags are:
CYG_FB_FLAGS0_LINEAR_FRAMEBUFFER
Framebuffer memory is organized in a conventional fashion and can be
accessed directly by
higher-level code using the base and stride parameters.
CYG_FB_FLAGS0_LE
This flag is only relevant for 1bpp, 2bpp and 4bpp devices and
controls how the pixels are organized within each byte. If the flag is
set then the layout is little-endian: for a 1bpp device pixel (0,0)
occupies bit 0 of the first byte of framebuffer memory. The more
common layout is big-endian where pixel (0,0) occupies bit 7 of the
first byte.
CYG_FB_FLAGS0_TRUE_COLOUR
The framebuffer uses a true colour format where the value of each
pixel directly encodes the red, green and blue intensities. This is
common for 16bpp and 32bpp devices, and is occasionally used for 8bpp
devices.
CYG_FB_FLAGS0_PALETTE
The framebuffer uses a palette. A pixel value does not directly encode
the colours, but instead acts as an index into a separate table of
colour values. That table may be read-only or read-write. Paletted
displays are common for 8bpp and some 4bpp displays.
CYG_FB_FLAGS0_WRITEABLE_PALETTE
The palette is read-write.
CYG_FB_FLAGS0_DELAYED_PALETTE_UPDATE
Palette updates can be synchronized to a vertical blank, in other
words a brief time period when the display is not being updated,
by using CYG_FB_UPDATE_VERTICAL_RETRACE as the last
argument to cyg_fb_write_palette or
CYG_FB_WRITE_PALETTE. With some hardware updating
the palette in the middle of a screen update may result in visual noise.
CYG_FB_FLAGS0_VIEWPORT
The framebuffer contains more pixels than can be shown on the display.
Instead the display provides a viewport into the framebuffer. An
ioctl
can be used to move the viewport.
CYG_FB_FLAGS0_DOUBLE_BUFFER
The display does not show the current contents of the framebuffer, so
the results of drawing into the framebuffer are not immediately
visible. Instead higher-level code needs to perform an explicit
synch operation to
update the display.
CYG_FB_FLAGS0_PAGE_FLIPPING
The hardware supports two or more pages, each of width*height pixels,
only one of which is visible on the display. This allows higher-level
code to update one page without disturbing what is currently visible.
An ioctl
is used to switch the visible page.
CYG_FB_FLAGS0_BLANK
The display can be blanked
without affecting the framebuffer contents or settings.
CYG_FB_FLAGS0_BACKLIGHT
There is a backlight which can be switched
on or off. Some hardware provides finer-grained control over the
backlight intensity.
CYG_FB_FLAGS0_MUST_BE_ON
Often it is desirable to perform some initialization such as clearing
the screen or setting the palette before the display is switched on, to avoid visual
noise. However not all hardware allows this. If this flag is set then
it is possible to access framebuffer memory and the palette before the
cyg_fb_on or CYG_FB_ON
operation. It may also be possible to perform some other operations
such as activating the backlight, but that is implementation-defined.
To allow for future expansion there are also flags1, flags2, and
flags3 fields. These may get used for encoding additional
ioctl functionality, support for hardware
acceleration, and similar features.
Linear Framebuffers
There are drawing primitives for writing and reading individual
pixels. However these involve a certain amount of arithmetic each time
to get from a position to an address within the frame buffer, plus
function call overhead if the function API is used, and this will slow
down graphics operations.
When the framebuffer device is known at compile-time and the macro API
is used then there are additional macros specifically for iterating over parts of the frame
buffer. These should prove very efficient for many graphics
operations. However if the device is selected at run-time then the
macros are not appropriate and code may want to manipulate framebuffer
memory directly. This is possible if two conditions are satisfied:
The CYG_FB_FLAGS0_LINEAR_FRAMEBUFFER flag must be
set. Otherwise framebuffer memory is either not directly accessible or
has a non-linear layout.
The CYG_FB_FLAGS0_DOUBLE_BUFFER flag must be clear.
An efficient double buffer synch operation requires knowing what part
of the framebuffer have been updated, and the various drawing
primitives will keep track of this. If higher-level code then starts
manipulating the framebuffer directly the synch operation may perform
only a partial update.
The base, stride, depth, width and height parameters, plus the
CYG_FB_FLAGS0_LE flag for 1bpp, 2bpp and 4bpp
devices, provide all the information needed to access framebuffer
memory. A linear framebuffer has pixel (0,0) at the base address.
Incrementing y means adding stride bytes to the pointer.
The base and stride parameters may be set even if
CYG_FB_FLAGS0_LINEAR_FRAMEBUFFER is clear. This can
be useful if for example the display is rotated in software from
landscape to portrait mode. However the meaning of these parameters
for non-linear framebuffers is implementation-defined.
Framebuffer Control OperationsControl Operationsmanaging a framebuffer
#include <cyg/io/framebuf.h>
int cyg_fb_oncyg_fb* fbdevint cyg_fb_offcyg_fb* fbdevint cyg_fb_ioctlcyg_fb* fbdevcyg_uint16 keyvoid* datasize_t* lenint CYG_FB_ONFRAMEBUFint CYG_FB_OFFFRAMEBUFint CYG_FB_IOCTLFRAMEBUFcyg_uint16 keyvoid* datasize_t* lenDescription
The main operations on a framebuffer are drawing and colour
management. However on most hardware it is also necessary to switch
the display on before the
user can see anything, and application code should be able to control
when this happens. There are also miscellaneous operations such as
manipulating the backlight or moving the viewpoint. These do not
warrant dedicated functions, especially since the functionality will
only be available on some hardware, so an ioctl
interface is used.
Switching the Display On or Off
With most hardware nothing will be visible until there is a call to
cyg_fb_on or an invocation of the
CYG_FB_ON macro. This will initialize the
framebuffer control circuitry, start sending the data signals to the
display unit, and switch on the display if necessary. The exact
initialization semantics are left to the framebuffer device driver. In
some cases the hardware may already be partially or fully initialized
by a static constructor or by boot code that ran before eCos.
There are some circumstances in which initialization can fail, and
this is indicated by a POSIX error code such as
ENODEV. An example would be plug and play hardware
where the framebuffer device is not detected at run-time. Another
example is hardware which can operate in several modes, with separate
cyg_fb structures for each mode, if the
hardware is already in use for a different mode. A return value of 0
indicates success.
Some but not all hardware allows the framebuffer memory and, if
present, the palette to be manipulated before the device is switched
on. That way the user does not see random noise on the screen during
system startup. The flag CYG_FB_FLAGS0_MUST_BE_ON
should be checked:
static void
init_screen(cyg_fb_colour background)
{
int result;
#if (! (CYG_FB_FLAGS0(FRAMEBUF) & CYG_FB_FLAGS0_MUST_BE_ON))
CYG_FB_FILL_BLOCK(FRAMEBUF, 0, 0,
CYG_FB_WIDTH(FRAMEBUF), CYG_FB_HEIGHT(FRAMEBUF),
background);
#endif
result = CYG_FB_ON(FRAMEBUF);
if (0 != result) {
<handle unusual error condition>
}
#if (CYG_FB_FLAGS0(FRAMEBUF) & CYG_FB_FLAGS0_MUST_BE_ON)
CYG_FB_FILL_BLOCK(FRAMEBUF, 0, 0,
CYG_FB_WIDTH(FRAMEBUF), CYG_FB_HEIGHT(FRAMEBUF),
background);
#endif
}
Obviously if the application has already manipulated framebuffer
memory or the palette but then the cyg_fb_on
operation fails, the system is left in an undefined state.
It is also possible to switch a framebuffer device off, using the
function cyg_fb_off or the macro
CYG_FB_OFF, although this functionality is rarely
used in embedded systems. The exact semantics of switching a device
off are implementation-defined, but typically it involves shutting
down the display, stopping the data signals to the display, and
halting the control circuitry. The framebuffer memory and the palette
are left in an undefined state, and application code should assume
that both need full reinitializing when the device is switched back
on. Some hardware may also provide a blank operation which
typically just manipulates the display, not the whole framebuffer
device. Normally cyg_fb_on returns 0. The API
allows for a POSIX error code as with cyg_fb_on,
but switching a device off is not an operation that is likely to fail.
If a framebuffer device can operate in several modes, represented by
several cyg_fb structures and macro
identifiers, then switching modes requires turning the current device
off before turning the next one one.
Miscellaneous Control Operations
Some hardware functionality such as an LCD panel backlight is common
but not universal. Supporting these does not warrant dedicated
functions. Instead a catch-all ioctl interface is
provided, with the arguments just passed straight to the device
driver. This approach also allows for future expansion and for
device-specific operations. cyg_fb_ioctl and
CYG_FB_IOCTL take four arguments: a
cyg_fb structure or framebuffer identifier; a
key that specifies the operation to be performed; an arbitrary
pointer, which should usually be a pointer to a data structure
specific to the key; and a length field. Key values from 0 to 0x7fff
are generic. Key values from 0x8000 onwards are reserved for the
individual framebuffer device drivers, for device-specific
functionality. The length field should be set to the size of the data
structure, and may get updated by the device driver.
With most ioctl operations the device can indicate whether or not it
supports the functionality by one of the flags, for example:
void
backlight_off(cyg_fb* fb)
{
if (fb->fb_flags0 & CYG_FB_FLAGS0_BACKLIGHT) {
cyg_fb_ioctl_backlight new_setting;
size_t len = sizeof(cyg_fb_ioctl_backlight);
int result;
new_setting.fbbl_current = 0;
result = cyg_fb_ioctl(fb, CYG_FB_IOCTL_BACKLIGHT_SET,
&new_setting, &len);
if (0 != result) {
…
}
}
}
The operation returns zero for success or a POSIX error code on
failure, for example ENOSYS if the device driver
does not implement the requested functionality.
Viewport
# define CYG_FB_IOCTL_VIEWPORT_GET_POSITION 0x0100
# define CYG_FB_IOCTL_VIEWPORT_SET_POSITION 0x0101
typedef struct cyg_fb_ioctl_viewport {
cyg_ucount16 fbvp_x; // position of top-left corner of the viewport within
cyg_ucount16 fbvp_y; // the framebuffer
cyg_ucount16 fbvp_when; // set-only, now or vert retrace
} cyg_fb_ioctl_viewport;
On some targets the framebuffer device has a higher resolution than
the display. Only a subset of the pixels, the viewport, is currently
visible. Application code can exploit this functionality to achieve
certain effects, for example smooth scrolling. Framebuffers which
support this functionality will have the
CYG_FB_FLAGS0_VIEWPORT flag set. The viewport
dimensions are available as additional parameters to the normal
framebuffer width and height.
The current position of the viewport can be obtained using an
CYG_FB_IOCTL_VIEWPORT_GET_POSITION ioctl operation.
The data argument should be a pointer to a
cyg_fb_ioctl_viewport structure. On return
the fbvp_x and
fbvp_y fields will be filled in. To move
the viewport use CYG_FB_IOCTL_VIEWPORT_SET_POSITION
with fbvp_x and
fbvp_y set to the top left corner of the
new viewport within the framebuffer, and
fbvp_when set to either
CYG_FB_UPDATE_NOW or
CYG_FB_UPDATE_VERTICAL_RETRACE. If the device
driver cannot easily synchronize to a vertical retrace period then
this last field is ignored.
void
move_viewport(cyg_fb* fb, int dx, int dy)
{
#ifdef CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_VIEWPORT
cyg_fb_ioctl_viewport viewport;
int len = sizeof(cyg_fb_ioctl_viewport);
int result;
result = cyg_fb_ioctl(fb, CYG_FB_IOCTL_VIEWPORT_GET_POSITION,
&viewport, &len);
if (result != 0) {
…
}
if (((int)viewport.fbvp_x + dx) < 0) {
viewport.fbvp_x = 0;
} else if ((viewport.fbvp_x + dx + fb->fb_viewport_width) > fb->fb_width) {
viewport.fbvp_x = fb->fb_width - fb->fb_viewport_width;
} else {
viewport.fbvp_x += dx;
}
if (((int)viewport.fbvp_y + dy) < 0) {
viewport.fbvp_y = 0;
} else if ((viewport.fbvp_y + dy + fb->fb_viewport_height) > fb->fb_height) {
viewport.fbvp_y = fb->fb_height - fb->fb_viewport_height;
} else {
viewport.fbvp_y += dy;
}
result = cyg_fb_ioctl(fb, CYG_FB_IOCTL_VIEWPORT_SET_POSITION,
&viewport, &len);
if (result != 0) {
…
}
#else
CYG_UNUSED_PARAM(cyg_fb*, fb);
CYG_UNUSED_PARAM(int, dx);
CYG_UNUSED_PARAM(int, dy);
#endif
}
If an attempt is made to move the viewport beyond the boundaries of
the framebuffer then the resulting behaviour is undefined. Some
hardware may behave reasonably, wrapping around as appropriate, but
portable code cannot assume this. The above code fragment is careful
to clip the viewport to the framebuffer dimensions.
Page Flipping
# define CYG_FB_IOCTL_PAGE_FLIPPING_GET_PAGES 0x0200
# define CYG_FB_IOCTL_PAGE_FLIPPING_SET_PAGES 0x0201
typedef struct cyg_fb_ioctl_page_flip {
cyg_uint32 fbpf_number_pages;
cyg_uint32 fbpf_visible_page;
cyg_uint32 fbpf_drawable_page;
cyg_ucount16 fbpf_when; // set-only, now or vert retrace
} cyg_fb_ioctl_page_flip;
On some targets the framebuffer has enough memory for several pages,
only one of which is visible at a time. This allows the application
to draw into one page while displaying another. Once drawing is
complete the display is flipped to the newly drawn page, and the
previously displayed page is now available for updating. This
technique is used for smooth animation, especially in games. The flag
CYG_FB_FLAGS0_PAGE_FLIPPING indicates support for
this functionality.
CYG_FB_IOCTL_PAGE_FLIPPING_GET_PAGES can be used to
get the current settings of the page flipping support. The data
argument should be a pointer to a
cyg_fb_ioctl_page_flip structure. The
resulting fbpf_number_pages field indicates
the total number of pages available: 2 is common, but more pages are
possible. fbpf_visible_page gives the page
that is currently visible to the user, and will be between 0 and
(fbpf_number_pages - 1).
Similarly fbpf_drawable_page gives the page
that is currently visible. It is implementation-defined whether or not
the visible and drawable page can be the same one.
CYG_FB_IOCTL_PAGE_FLIPPING_SET_PAGES can be used to
change the visible and drawable page. The
fbpf_number_pages field is ignored.
fbpf_visible_page and
fbpf_drawable_page give the new settings.
fbpf_when should be one of
CYG_FB_UPDATE_NOW or
CYG_FB_UPDATE_VERTICAL_RETRACE, but may be ignored
by some device drivers.
#if !(CYG_FB_FLAGS0(FRAMEBUF) & CYG_FB_FLAGS0_PAGE_FLIPPING)
# error Current framebuffer device does not support page flipping
#endif
static cyg_uint32 current_visible = 0;
static void
page_flip_init(cyg_fb_colour background)
{
cyg_fb_ioctl_page_flip flip;
size_t len = sizeof(cyg_fb_ioctl_page_flip);
flip.fbpf_visible_page = current_visible;
flip.fbpf_drawable_page = 1 - current_visible;
flip.fbpf_when = CYG_FB_UPDATE_NOW;
CYG_FB_IOCTL(FRAMEBUF, CYG_FB_IOCTL_PAGE_FLIPPING_SET_PAGES,
&flip, &len);
CYG_FB_FILL_BLOCK(FRAMEBUF, 0, 0,
CYG_FB_WIDTH(FRAMEBUF), CYG_FB_HEIGHT(FRAMEBUF),
background);
flip.fbpf_visible_page = 1 - current_visible;
flip.fbpf_drawable_page = current_visible;
CYG_FB_IOCTL(FRAMEBUF, CYG_FB_IOCTL_PAGE_FLIPPING_SET_PAGES,
&flip, &len);
CYG_FB_FILL_BLOCK(FRAMEBUF, 0, 0,
CYG_FB_WIDTH(FRAMEBUF), CYG_FB_HEIGHT(FRAMEBUF),
background);
current_visible = 1 - current_visible;
}
static void
page_flip_toggle(void)
{
cyg_fb_ioctl_page_flip flip;
size_t len = sizeof(cyg_fb_ioctl_page_flip);
flip.fbpf_visible_page = 1 - current_visible;
flip.fbpf_drawable_page = current_visible;
CYG_FB_IOCTL(FRAMEBUF, CYG_FB_IOCTL_PAGE_FLIPPING_SET_PAGES,
&flip, &len);
current_visible = 1 - current_visible;
}
A page flip typically just changes a couple of pointers within the
hardware and device driver. No attempt is made to synchronize the
contents of the pages, that is left to higher-level code.
Blanking the Screen
# define CYG_FB_IOCTL_BLANK_GET 0x0300
# define CYG_FB_IOCTL_BLANK_SET 0x0301
typedef struct cyg_fb_ioctl_blank {
cyg_bool fbbl_on;
} cyg_fb_ioctl_blank;
Some hardware allows the display to be switched off or blanked without
shutting down the entire framebuffer device, greatly reducing power
consumption. The current blanking state can be obtained using
CYG_FB_IOCTL_BLANK_GET and the state can be updated
using CYG_FB_IOCTL_BLANK_SET. The data argument
should be a pointer to a cyg_fb_ioctl_blank
structure. Support for this functionality is indicated by the
CYG_FB_FLAGS0_BLANK flag.
static cyg_bool
display_blanked(cyg_fb_* fb)
{
cyg_fb_ioctl_blank blank;
size_t len = sizeof(cyg_fb_ioctl_blank);
if (! (fb->fb_flags0 & CYG_FB_FLAGS0_BLANK)) {
return false;
}
(void) cyg_fb_ioctl(fb, CYG_FB_IOCTL_BLANK_GET, &blank, &len);
return !blank.fbbl_on;
}
Controlling the Backlight
# define CYG_FB_IOCTL_BACKLIGHT_GET 0x0400
# define CYG_FB_IOCTL_BACKLIGHT_SET 0x0401
typedef struct cyg_fb_ioctl_backlight {
cyg_ucount32 fbbl_current;
cyg_ucount32 fbbl_max;
} cyg_fb_ioctl_backlight;
Many LCD panels provide some sort of backlight, making the display
easier to read at the cost of increased power consumption. Support for
this is indicated by the CYG_FB_FLAGS0_BACKLIGHT
flag. CYG_FB_IOCTL_BACKLIGHT_GET can be used to get
both the current setting and the maximum value. If the maximum is 1
then the backlight can only be switched on or off. Otherwise it is
possible to control the intensity.
static void
set_backlight_50_percent(void)
{
#if (CYG_FB_FLAGS0(FRAMEBUF) & CYG_FB_FLAGS0_BACKLIGHT)
cyg_fb_ioctl_backlight backlight;
size_t len = sizeof(cyg_fb_ioctl_backlight);
CYG_FB_IOCTL(FRAMEBUF, CYG_FB_IOCTL_BACKLIGHT_GET, &backlight, &len);
backlight.fbbl_current = (backlight.fbbl_max + 1) >> 1;
CYG_FB_IOCTL(FRAMEBUF, CYG_FB_IOCTL_BACKLIGHT_SET, &backlight, &len);
#endif
}
Framebuffer ColoursColoursformats and palette management
#include <cyg/io/framebuf.h>
typedef struct cyg_fb {
cyg_ucount16 fb_depth;
cyg_ucount16 fb_format;
cyg_uint32 fb_flags0;
…
} cyg_fb;
extern const cyg_uint8 cyg_fb_palette_ega[16 * 3];
extern const cyg_uint8 cyg_fb_palette_vga[256 * 3];
#define CYG_FB_DEFAULT_PALETTE_BLACK 0x00
#define CYG_FB_DEFAULT_PALETTE_BLUE 0x01
#define CYG_FB_DEFAULT_PALETTE_GREEN 0x02
#define CYG_FB_DEFAULT_PALETTE_CYAN 0x03
#define CYG_FB_DEFAULT_PALETTE_RED 0x04
#define CYG_FB_DEFAULT_PALETTE_MAGENTA 0x05
#define CYG_FB_DEFAULT_PALETTE_BROWN 0x06
#define CYG_FB_DEFAULT_PALETTE_LIGHTGREY 0x07
#define CYG_FB_DEFAULT_PALETTE_LIGHTGRAY 0x07
#define CYG_FB_DEFAULT_PALETTE_DARKGREY 0x08
#define CYG_FB_DEFAULT_PALETTE_DARKGRAY 0x08
#define CYG_FB_DEFAULT_PALETTE_LIGHTBLUE 0x09
#define CYG_FB_DEFAULT_PALETTE_LIGHTGREEN 0x0A
#define CYG_FB_DEFAULT_PALETTE_LIGHTCYAN 0x0B
#define CYG_FB_DEFAULT_PALETTE_LIGHTRED 0x0C
#define CYG_FB_DEFAULT_PALETTE_LIGHTMAGENTA 0x0D
#define CYG_FB_DEFAULT_PALETTE_YELLOW 0x0E
#define CYG_FB_DEFAULT_PALETTE_WHITE 0x0F
cyg_ucount16 CYG_FB_FORMATframebufvoid cyg_fb_read_palettecyg_fb* fbcyg_ucount32 firstcyg_ucount32 countvoid* datavoid cyg_fb_write_palettecyg_fb* fbcyg_ucount32 firstcyg_ucount32 countconst void* datacyg_ucount16 whencyg_fb_colour cyg_fb_make_colourcyg_fb* fbcyg_ucount8 rcyg_ucount8 gcyg_ucount8 bvoid cyg_fb_break_colourcyg_fb* fbcyg_fb_colour colourcyg_ucount8* rcyg_ucount8* gcyg_ucount8* bvoid CYG_FB_READ_PALETTEFRAMEBUFcyg_ucount32 firstcyg_ucount32 countvoid* datavoid CYG_FB_WRITE_PALETTEFRAMEBUFcyg_ucount32 firstcyg_ucount32 countconst void* datacyg_ucount16 whencyg_fb_colour CYG_FB_MAKE_COLOURFRAMEBUFcyg_ucount8 rcyg_ucount8 gcyg_ucount8 bvoid CYG_FB_BREAK_COLOURFRAMEBUFcyg_fb_colour colourcyg_ucount8* rcyg_ucount8* gcyg_ucount8* bDescription
Managing colours can be one of the most difficult aspects of writing
graphics code, especially if that code is intended to be portable to
many different platforms. Displays can vary from 1bpp monochrome, via
2bpp and 4bpp greyscale, through 4bpp and 8bpp paletted, and up to
16bpp and 32bpp true colour - and those are just the more common
scenarios. The various drawing
primitives like cyg_fb_write_pixel work in
terms of cyg_fb_colour values, usually an unsigned
integer. Exactly how the hardware interprets a
cyg_fb_colour depends on the format.
Colour Formats
There are a number of ways of finding out how these values will be
interpreted by the hardware:
The CYG_FB_FLAGS0_TRUE_COLOUR flag is set for all
true colour displays. The format parameter can be examined for more
details but this is not usually necessary. Instead code can use
cyg_fb_make_colour
or CYG_FB_MAKE_COLOUR
to construct a cyg_fb_colour value from red, green and
blue components.
If the CYG_FB_FLAGS0_WRITEABLE_PALETTE flag is set
then a cyg_fb_colour value is an index into a lookup
table known as the palette, and this table contains red, green and
blue components. The size of the palette is determined by the display
depth, so 16 entries for a 4bpp display and 256 entries for an 8bpp
display. Application code or a graphics library can install its own palette so
can control exactly what colour each cyg_fb_colour value
corresponds to. Alternatively there is support for installing a
default palette.
If CYG_FB_FLAGS0_PALETTE is set but
CYG_FB_FLAGS0_WRITEABLE_PALETTE is clear then the
hardware uses a fixed palette. There is no easy way for portable
software to handle this case. The palette can be read at run-time,
allowing the application's desired colours to be mapped to whichever
palette entry provides the best match. However normally it will be
necessary to write code specifically for the fixed palette.
Otherwise the display is monochrome or greyscale, depending on the
depth. There are still variations, for example on a monochrome display
colour 0 can be either white or black.
As an alternative or to provide additional information, the exact
colour format is provided by the fb_format
field of the cyg_fb structure or by the
CYG_FB_FORMAT macro. It can be one of the
following (more entries may be added in future):
CYG_FB_FORMAT_1BPP_MONO_0_BLACK
simple 1bpp monochrome display, with 0 as black or the darker of the
two colours, and 1 as white or the ligher colour.
CYG_FB_FORMAT_1BPP_MONO_0_WHITE
simple 1bpp monochrome display, with 0 as white or the lighter of the
two colours, and 1 as black or the darker colour.
CYG_FB_FORMAT_1BPP_PAL888
a 1bpp display which cannot easily be described as monochrome. This is
unusual and not readily supported by portable code. It can happen if
the framebuffer normally runs at a higher depth, for example 4bpp or
8bpp paletted, but is run at only 1bpp to save memory. Hence only two
of the palette entries are used, but can be set to arbitrary colours.
The palette may be read-only or read-write.
CYG_FB_FORMAT_2BPP_GREYSCALE_0_BLACK
a 2bpp display offering four shades of grey, with 0 as black or the
darkest of the four shades, and 3 as white or the lightest.
CYG_FB_FORMAT_2BPP_GREYSCALE_0_WHITE
a 2bpp display offering four shades of grey, with 0 as white or the
lightest of the four shades, and 3 as black or the darkest.
CYG_FB_FORMAT_2BPP_PAL888
a 2bpp display which cannot easily be described as greyscale, for
example providing black, red, blue and white as the four colours.
This is unusual and not readily supported by portable code. It can
happen if the framebuffer normally runs at a higher depth, for example
4bpp or 8bpp paletted, but is run at only 2bpp to save memory. Hence
only four of the palette entries are used, but can be set to arbitrary
colours. The palette may be read-only or read-write.
CYG_FB_FORMAT_4BPP_GREYSCALE_0_BLACK
a 4bpp display offering sixteen shades of grey, with 0 as black or the
darkest of the 16 shades, and 15 as white or the lighest.
CYG_FB_FORMAT_4BPP_GREYSCALE_0_WHITE
a 4bpp display offering sixteen shades of grey, with 0 as white or the
lightest of the 16 shades, and 15 as black or the darkest.
CYG_FB_FORMAT_4BPP_PAL888
a 4bpp paletted display, allowing for 16 different colours on screen
at the same time. The palette may be read-only or read-write.
CYG_FB_FORMAT_8BPP_PAL888
an 8bpp paletted display, allowing for 256 different colours on screen
at the same time. The palette may be read-only or read-write.
CYG_FB_FORMAT_8BPP_TRUE_332
an 8bpp true colour display, with three bits (eight levels) of red and
green intensity and two bits (four levels) of blue intensity.
CYG_FB_FORMAT_16BPP_TRUE_565
a 16bpp true colour display with 5 bits each for red and blue and 6
bits for green.
CYG_FB_FORMAT_16BPP_TRUE_555
a 16bpp true colour display with five bits each for red, green and
blue, and one unused bit.
CYG_FB_FORMAT_32BPP_TRUE_0888
a 32bpp true colour display with eight bits each for red, green and
blue and eight bits unused.
For the true colour formats the format does not define exactly which
bits in the pixel are used for which colour. Instead the
cyg_fb_make_colour
and cyg_fb_break_colour functions or the
equivalent macros should be used to construct or decompose pixel
values.
Paletted Displays
Palettes are the common way of implementing low-end colour displays.
There are two variants. A read-only palette provides a fixed set of
colours and it is up to application code to use these colours
appropriately. A read-write palette allows the application to select
its own set of colours. Displays providing a read-write palette will
have the CYG_FB_FLAGS0_WRITEABLE_PALETTE flag set
in addition to CYG_FB_FLAGS0_PALETTE.
Even if application code can install its own palette, many
applications do not exploit this functionality and instead stick with
a default. There are two standard palettes: the 16-entry PC EGA for
4bpp displays; and the 256-entry PC VGA, a superset of the EGA one,
for 8bpp displays. This package provides the data for both, in the
form of arrays cyg_fb_palette_ega and
cyg_fb_palette_vga, and 16
#define's such as
CYG_FB_DEFAULT_PALETTE_BLACK for the EGA colours
and the first 16 VGA colours. By default device drivers for read-write
paletted displays will install the appropriate default palette, but
this can be suppressed using configuration option
CYGFUN_IO_FRAMEBUF_INSTALL_DEFAULT_PALETTE. If a
custom palette will be used then installing the default palette
involves wasting 48 or 768 bytes of memory.
It should be emphasized that displays vary widely. A colour such
as CYG_FB_DEFAULT_PALETTE_YELLOW may appear rather
differently on two different displays, although it should always be
recognizable as yellow. Developers may wish to fine-tune the palette
for specific hardware.
The current palette can be retrieved using
cyg_fb_read_palette or
CYG_FB_READ_PALETTE. The
first and count
arguments control which palette entries should be retrieved. For
example, to retrieve just palette entry 12
first should be set to 12 and
count should be set to 1. To retrieve all 256
entries for an 8bpp display, first should be
set to 0 and count should be set to 256. The
data argument should point at an array of
bytes, allowing three bytes for every entry. Byte 0 will contain the red
intensity for the first entry, byte 1 green and byte 2 blue.
For read-write palettes the palette can be updated using
cyg_fb_write_palette or
CYG_FB_WRITE_PALETTE. The
first and count arguments
are the same as for cyg_fb_read_palette, and the
data argument should point at a suitable byte
array packed in the same way. The when argument
should be one of CYG_FB_UPDATE_NOW or
CYG_FB_UPDATE_VERTICAL_RETRACE. With some displays
updating the palette in the middle of an update may result in visual
noise, so synchronizing to the vertical retrace avoids this. However
not all device drivers will support this.
There is an assumption that palette entries use 8 bits for each of the
red, green and blue colour intensities. This is not always the case,
but the device drivers will perform appropriate adjustments. Some
hardware may use only 6 bits per colour, and the device driver will
ignore the bottom two bits of the supplied intensity values.
Occasionally hardware may use more than 8 bits, in which case the
supplied 8 bits are shifted left appropriately and zero-padded. Device
drivers for such hardware may also provide device-specific routines to
manipulate the palette in a non-portable fashion.
True Colour displays
True colour displays are often easier to manage than paletted
displays. However this comes at the cost of extra memory. A 16bpp true
colour display requires twice as much memory as an 8bpp paletted
display, yet can offer only 32 or 64 levels of intensity for each
colour as opposed to the 256 levels provided by a palette. It also
requires twice as much video memory bandwidth to send all the pixel
data to the display for every refresh, which may impact the
performance of the rest of the system. A 32bpp true colour display
offers the same colour intensities but requires four times the memory
and four times the bandwidth.
Exactly how the colour bits are organized in
a cyg_fb_colour pixel value is not
defined by the colour format. Instead code should use the
cyg_fb_make_colour or
CYG_FB_MAKE_COLOUR primitives. These take 8-bit
intensity levels for red, green and blue, and return the corresponding
cyg_fb_colour. When using the macro interface the
arithmetic happens at compile-time, for example:
#define BLACK CYG_FB_MAKE_COLOUR(FRAMEBUF, 0, 0, 0)
#define WHITE CYG_FB_MAKE_COLOUR(FRAMEBUF, 255, 255, 255)
#define RED CYG_FB_MAKE_COLOUR(FRAMEBUF, 255, 0, 0)
#define GREEN CYG_FB_MAKE_COLOUR(FRAMEBUF, 0, 255, 0)
#define BLUE CYG_FB_MAKE_COLOUR(FRAMEBUF, 0, 0, 255)
#define YELLOW CYG_FB_MAKE_COLOUR(FRAMEBUF, 255, 255, 80)
Displays vary widely so the numbers may need to be adjusted to give
the exact desired colours.
For symmetry there are also cyg_fb_break_colour
and CYG_FB_BREAK_COLOUR primitives. These take a
cyg_fb_colour value and decompose it into its red, green
and blue components.
Framebuffer Drawing PrimitivesDrawing Primitivesupdating the display
#include <cyg/io/framebuf.h>
void cyg_fb_write_pixelcyg_fb* fbdevcyg_ucount16 xcyg_ucount16 ycyg_fb_colour colourcyg_fb_colour cyg_fb_read_pixelcyg_fb* fbdevcyg_ucount16 xcyg_ucount16 yvoid cyg_fb_write_hlinecyg_fb* fbdevcyg_ucount16 xcyg_ucount16 ycyg_ucount16 lencyg_fb_colour colourvoid cyg_fb_write_vlinecyg_fb* fbdevcyg_ucount16 xcyg_ucount16 ycyg_ucount16 lencyg_fb_colour colourvoid cyg_fb_fill_blockcyg_fb* fbdevcyg_ucount16 xcyg_ucount16 ycyg_ucount16 widthcyg_ucount16 heightcyg_fb_colour colourvoid cyg_fb_write_blockcyg_fb* fbdevcyg_ucount16 xcyg_ucount16 ycyg_ucount16 widthcyg_ucount16 heightconst void* datacyg_ucount16 offsetcyg_ucount16 stridevoid cyg_fb_read_blockcyg_fb* fbdevcyg_ucount16 xcyg_ucount16 ycyg_ucount16 widthcyg_ucount16 heightvoid* datacyg_ucount16 offsetcyg_ucount16 stridevoid cyg_fb_move_blockcyg_fb* fbdevcyg_ucount16 xcyg_ucount16 ycyg_ucount16 widthcyg_ucount16 heightcyg_ucount16 new_xcyg_ucount16 new_yvoid cyg_fb_synchcyg_fb* fbdevcyg_ucount16 whenvoid CYG_FB_WRITE_PIXELFRAMEBUFcyg_ucount16 xcyg_ucount16 ycyg_fb_colour colourcyg_fb_colour CYG_FB_READ_PIXELFRAMEBUFcyg_ucount16 xcyg_ucount16 yvoid CYG_FB_WRITE_HLINEFRAMEBUFcyg_ucount16 xcyg_ucount16 ycyg_ucount16 lencyg_fb_colour colourvoid CYG_FB_WRITE_VLINEFRAMEBUFcyg_ucount16 xcyg_ucount16 ycyg_ucount16 lencyg_fb_colour colourvoid CYG_FB_FILL_BLOCKFRAMEBUFcyg_ucount16 xcyg_ucount16 ycyg_ucount16 widthcyg_ucount16 heightcyg_fb_colour colourvoid CYG_FB_WRITE_BLOCKFRAMEBUFcyg_ucount16 xcyg_ucount16 ycyg_ucount16 widthcyg_ucount16 heightconst void* datacyg_ucount16 offsetcyg_ucount16 stridevoid CYG_FB_READ_BLOCKFRAMEBUFcyg_ucount16 xcyg_ucount16 ycyg_ucount16 widthcyg_ucount16 heightvoid* datacyg_ucount16 offsetcyg_ucount16 stridevoid CYG_FB_MOVE_BLOCKFRAMEBUFcyg_ucount16 xcyg_ucount16 ycyg_ucount16 widthcyg_ucount16 heightcyg_ucount16 new_xcyg_ucount16 new_yvoid CYG_FB_SYNCHFRAMEBUFcyg_ucount16 whenDescription
The eCos framebuffer infrastructure defines a small number of drawing
primitives. These are not intended to provide full graphical
functionality like multiple windows, drawing text in arbitrary fonts,
or anything like that. Instead they provide building blocks for
higher-level graphical toolkits. The available primitives are:
Manipulating individual pixels.
Drawing horizontal and vertical lines.
Block fills.
Moving blocks between the framebuffer and main memory.
Moving blocks within the framebuffer.
For double-buffered devices, synchronizing the framebuffer contents
with the actual display.
There are two versions for each primitive: a macro and a function. The
macro can be used if the desired framebuffer device is known at
compile-time. Its first argument should be a framebuffer identifier,
for example 320x240x16, and must be one of the
entries in the configuration option
CYGDAT_IO_FRAMEBUF_DEVICES. In the examples below
it is assumed that FRAMEBUF has been
#define'd to a suitable identifier. The function
can be used if the desired framebuffer device is selected at
run-time. Its first argument should be a pointer to the appropriate
cyg_fb structure.
The pixel, line, and block fill primitives take a
cyg_fb_colour argument. For details of colour handling
see . This argument should have no
more bits set than are appropriate for the display depth. For example
on a 4bpp only the bottom four bits of the colour may be set,
otherwise the behaviour is undefined.
None of the primitives will perform any run-time error checking,
except possibly for some assertions in a debug build. If higher-level
code provides invalid arguments, for example trying to write a block
which extends past the right hand side of the screen, then the
system's behaviour is undefined. It is the responsibility of
higher-level code to perform clipping to the screen boundaries.
Manipulating Individual Pixels
The primitives for manipulating individual pixels are very simple: a
pixel can be written or read back. The following example shows one way
of drawing a diagonal line:
void
draw_diagonal(cyg_fb* fb,
cyg_ucount16 x, cyg_ucount16 y, cyg_ucount16 len,
cyg_fb_colour colour)
{
while ( len-- ) {
cyg_fb_write_pixel(fb, x++, y++, colour);
}
}
The next example shows how to draw a horizontal XOR line on a 1bpp
display.
void
draw_horz_xor(cyg_ucount16 x, cyg_ucount16 y, cyg_ucount16 len)
{
cyg_fb_colour colour;
while ( len--) {
colour = CYG_FB_READ_PIXEL(FRAMEBUF, x, y);
CYG_FB_WRITE_PIXEL(FRAMEBUF, x++, y, colour ^ 0x01);
}
}
The pixel macros should normally be avoided. Determining the correct
location within framebuffer memory corresponding to a set of
coordinates for each pixel is a comparatively expensive operation.
Instead there is direct support for iterating over parts of the
display, avoiding unnecessary overheads.
Drawing Simple Lines
Higher-level graphics code often needs to draw single-pixel horizontal
and vertical lines. If the application involves multiple windows then
these will usually have thin borders around them. Widgets such as
buttons and scrollbars also often have thin borders.
cyg_fb_draw_hline and
CYG_FB_DRAW_HLINE draw a horizontal line of the
specified colour, starting at the
x and y coordinates and
extending to the right (increasing x) for a total of
len pixels. A 50 pixel line starting at
(100,100) will end at (149,100).
cyg_fb_draw_vline and
CYG_FB_DRAW_VLINE take the same arguments, but
the line extends down (increasing y).
These primitives do not directly support drawing lines more than one
pixel thick, but block
fills can be used to achieve those. There is no generic support
for drawing arbitrary lines, instead that is left to higher-level
graphics toolkits.
Block Fills
Filling a rectangular part of the screen with a solid colour is
another common requirement for higher-level code. The simplest example
is during initialization, to set the display's whole background to a
known value. Block fills are also often used when creating new windows
or drawing the bulk of a simple button or scrollbar widget.
cyg_fb_fill_block and
CYG_FB_FILL_BLOCK provide this functionality.
The x and y arguments
specify the top-left corner of the block to be filled. The
width and height
arguments specify the number of pixels affected, a total of
width * height. The following example
illustrates part of the process for initializing a framebuffer,
assumed here to have a writeable palette with default settings.
int
display_init(void)
{
int result = CYG_FB_ON(FRAMEBUF);
if ( result ) {
return result;
}
CYG_FB_FILL_BLOCK(FRAMEBUF, 0, 0,
CYG_FB_WIDTH(FRAMEBUF), CYG_FB_HEIGHT(FRAMEBUF),
CYG_FB_DEFAULT_PALETTE_WHITE);
…
}
Copying Blocks between the Framebuffer and Main Memory
The block transfer primitives serve two main purposes: drawing images.
and saving parts of the current display to be restored later. For
simple linear framebuffers the primitives just implement copy
operations, with no data conversion of any sort. For non-linear ones
the primitives act as if the framebuffer memory was linear. For
example, consider a 2bpp display where the two bits for a single pixel
are split over two separate bytes in framebuffer memory, or two
planes. For a block write operation the source data should still be
organized with four full pixels per byte, as for a linear framebuffer
of the same depth. and the block write primitive will distribute the
bits over the framebuffer memory as required. Similarly a block read
will combine the appropriate bits from different locations in
framebuffer memory and the resulting memory block will have four full
pixels per byte.
Because the block transfer primitives perform no data conversion, if
they are to be used for rendering images then those images should be
pre-formatted appropriately for the framebuffer device. For small
images this would normally happen on the host-side as part of the
application build process. For larger images it will usually be better
to store them in a compressed format and decompress them at run-time,
trading off memory for cpu cycles.
The x and y arguments
specify the top-left corner of the block to be transferred, and the
width and height
arguments determine the size. The data,
offset and stride
arguments determine the location and layout of the block in main
memory:
data
The source or destination for the transfer. For 1bpp, 2bpp and 4bpp
devices the data will be packed in accordance with the framebuffer
device's endianness as per the CYG_FB_FLAGS0_LE
flag. Each row starts in a new byte so there may be some padding on
the right. For 16bpp and 32bpp the data should be aligned to the
appropriate boundary.
offset
Sometimes only part of an image should be written to the screen. A
vertical offset can be achieved simply by adjusting
data to point at the appropriate row within the
image instead of the top row. For 8bpp, 16bpp and 32bpp displays
an additional horizontal offset can also be achieved by adjusting
data. However for 1bpp, 2bpp and 4bpp displays
the starting position within the image may be in the middle of a byte.
Hence the horizontal pixel offset can instead be specified with the
offset argument.
stride
This indicates the number of bytes between rows. Usually it will be
related to the width, but there are exceptions
such as when drawing only part of an image.
The following example fills a 4bpp display with an image held in
memory and already in the right format. If the image is smaller than
the display it will be centered. If the image is larger then the
center portion will fill the entire display.
void
draw_image(const void* data, int width, int height)
{
cyg_ucount16 stride;
cyg_ucount16 x, y, offset;
#if (4 != CYG_FB_DEPTH(FRAMEBUF))
# error This code assumes a 4bpp display
#endif
stride = (width + 1) >> 1; // 4bpp to byte stride
if (width < CYG_FB_WIDTH(FRAMEBUF)) {
x = (CYG_FB_WIDTH(FRAMEBUF) - width) >> 1;
offset = 0;
} else {
x = 0;
offset = (width - CYG_FB_WIDTH(FRAMEBUF)) >> 1;
width = CYG_FB_WIDTH(FRAMEBUF);
}
if (height < CYG_FB_HEIGHT(FRAMEBUF)) {
y = (CYG_FB_HEIGHT(FRAMEBUF) - height) >> 1;
} else {
y = 0;
data = (const void*)((const cyg_uint8*)data +
(stride * ((height - CYG_FB_HEIGHT(FRAMEBUF)) >> 1));
height = CYG_FB_HEIGHT(FRAMEBUF);
}
CYG_FB_WRITE_BLOCK(FRAMEBUF, x, y, width, height, data, offset, stride);
}
Moving Blocks with the Framebuffer
Sometimes it is necessary to move a block of data around the screen,
especially when using a higher-level graphics toolkit that supports
multiple windows. Block moves can be implemented by a read into main
memory followed by a write block, but this is expensive and imposes an
additional memory requirement. Instead the framebuffer infrastructure
provides a generic block move primitive. It will handle all cases
where the source and destination positions overlap. The
x and y arguments
specify the top-left corner of the block to be moved, and
width and height
determine the block size. new_x and
new_y specify the destination. The source data
will remain unchanged except in areas where it overlaps the destination.
Synchronizing Double-Buffered Displays
Some framebuffer devices are double-buffered: the framebuffer memory
that gets manipulated by the drawing primitives is separate from what
is actually displayed, and a synch operation is needed to update the
display. In some cases this may be because the actual display memory
is not directly accessible by the processor, for example it may
instead be attached via an SPI bus. Instead drawing happens in a
buffer in main memory, and then this gets transferred over the SPI bus
to the actual display hardware during a synch. In other cases it may
be a software artefact. Some drawing operations, especially ones
involving complex curves, can take a very long time and it may be
considered undesirable to have the user see this happening a few
pixels at a time. Instead the drawing happens in a separate buffer in
main memory and then a double buffer synch just involves a block move
to framebuffer memory. Typically that block move is much faster than
the drawing operation. Obviously there is a cost: an extra area of
memory, and the synch operation itself can consume many cycles and
much of the available memory bandwidth.
It is the responsibility of the framebuffer device driver to provide
the extra main memory. As far as higher-level code is concerned the
only difference between an ordinary and a double-buffered display is
that with the latter changes do not become visible until a synch
operation has been performed. The framebuffer infrastructure provides
support for a bounding box, keeping track of what has been updated
since the last synch. This means only the updated part of the screen
has to be transferred to the display hardware.
The synch primitives take two arguments. The first identifies the
framebuffer device. The second should be one of
CYG_FB_UPDATE_NOW for an immediate update, or
CYG_FB_UPDATE_VERTICAL_RETRACE. Some display
hardware involves a lengthy vertical retrace period every 10-20
milliseconds during which nothing gets drawn to the screen, and
performing the synch during this time means that the end user is
unaware of the operation (assuming the synch can be completed in the
time available). When the hardware supports it, specifying
CYG_FB_UPDATE_VERTICAL_RETRACE means that the synch
operation will block until the next vertical retrace takes place and
then perform the update. This may be an expensive operation, for
example it may involve polling a bit in a register. In a
multi-threaded environment it may also be unreliable because the
thread performing the synch may get interrupted or rescheduled in the
middle of the operation. When the hardware does not involve vertical
retraces, or when there is no easy way to detect them, the second
argument to the synch operation will just be ignored and the update
will always happen immediately.
It is up to higher level code to determine when a synch operation is
appropriate. One approach for typical event-driven code is to perform
the synch at the start of the event loop, just before waiting for an
input or timer event. This may not be optimal. For example if there
two small updates to opposite corners of the screen then it would be
better to make two synch calls with small bounding boxes, rather than
a single synch call with a a large bounding box that requires most of
the framebuffer memory to be updated.
Leaving out the synch operations leads to portability problems. On
hardware which does not involve double-buffering the synch operation
is a no-op, usually eliminated at compile-time, so invoking synch does
not add any code size or cpu cycle overhead. On double-buffered
hardware, leaving out the synch means the user cannot see what has
been drawn into the framebuffer.
Framebuffer Pixel ManipulationPixel Manipulationiterating over the display
#include <cyg/io/framebuf.h>
CYG_FB_PIXEL0_VARFRAMEBUFvoid CYG_FB_PIXEL0_SETFRAMEBUFcyg_ucount16 xcyg_ucount16 yvoid CYG_FB_PIXEL0_GETFRAMEBUFcyg_ucount16 xcyg_ucount16 yvoid CYG_FB_PIXEL0_ADDXFRAMEBUFcyg_ucount16 incrvoid CYG_FB_PIXEL0_ADDYFRAMEBUFcyg_ucount16 incrvoid CYG_FB_PIXEL0_WRITEFRAMEBUFcyg_fb_colour colourcyg_fb_colour CYG_FB_PIXEL0_READFRAMEBUFvoid CYG_FB_PIXEL0_FLUSHABSFRAMEBUFcyg_ucount16 x0cyg_ucount16 y0cyg_ucount16 widthcyg_ucount16 heightvoid CYG_FB_PIXEL0_FLUSHRELFRAMEBUFcyg_ucount16 x0cyg_ucount16 y0cyg_ucount16 dxcyg_ucount16 dyDescription
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 FLUSHRELx0 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);
…
Writing a Framebuffer Device DriverPortingwriting a new framebuffer device driverDescription
As with most device drivers, the easiest way to write a new
framebuffer package is to start with an existing one. Suitable ones
include the PC VGA mode13 driver, an 8bpp paletted display, and the
ARM iPAQ driver, a 16bpp true colour display. This document only
outlines the process.
Before writing any code it is necessary to decide how many framebuffer
devices should be provided by the device driver. Each such device
requires a cyg_fb structure and appropriate
functions, and an identifier for use with the macro API plus
associated macros. There are no hard rules here. Some device drivers
may support just a single device, others may support many devices
which drive the hardware in different modes or orientations. Optional
functionality such as viewports and page flipping may be supported by
having different cyg_fb devices, or by a
number of configuration options which affect a single
cyg_fb device. Usually providing multiple
cyg_fb structures is harmless because the
unused ones will get eliminated at link-time.
Configuration
The CDL for a framebuffer package is usually straightforward. A
framebuffer package should be a hardware package and reside in the
devs/framebuf hierarchy,
further organized by architecture. Generic framebuffer packages, if
any, can go into a generic
subdirectory, and will normally rely on the platform HAL to provide
some platform-specific information such as base addresses. The package
should be part of the target definition and hence loaded
automatically, but should be
active_if CYGPKG_IO_FRAMEBUF so that the
driver only gets built if the generic framebuffer support is
explicitly added to the configuration.
The configuration option CYGDAT_IO_FRAMEBUF_DEVICES
should hold all the valid identifiers which can be used as the first
argument for the macro API. This helps application developers to
select the appropriate identifier, and allows higher-level graphics
library packages to check that they have been configured correctly.
This is achieved using something like the following, where
mode13_320x200x8 is a valid identifier for the PC
VGA driver:
requires { is_substr(CYGDAT_IO_FRAMEBUF_DEVICES, " mode13_320x200x8 ") }
The spaces ensure that the CDL inference engine keeps the identifiers
separate.
CYGPKG_IO_FRAMEBUF contains a number of interfaces
which should be implemented by individual device drivers when
appropriate. This is used to eliminate some code or data structure
fields at compile-time, keeping down memory requirements. The
interfaces are
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_32BPP,
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_TRUE_COLOUR,
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_PALETTE,
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_WRITEABLE_PALETTE,
CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_DOUBLE_BUFFER,
and CYGHWR_IO_FRAMEBUF_FUNCTIONALITY_VIEWPORT.
For example if a device driver provides a true colour display but
fails to implement the relevant interface then functions like
cyg_fb_make_colour will be no-ops.
Device drivers for paletted displays should observe the generic
configuration option
CYGFUN_IO_FRAMEBUF_INSTALL_DEFAULT_PALETTE and
install either cyg_fb_palette_ega or
cyg_fb_palette_vga as part of their
cyg_fb_on implementation.
Exported Header File(s)
Each framebuffer device driver should export one or more header files
to cyg/io/framebufs. A custom
build step in CYGPKG_IO_FRAMEBUF ensures that
application code can just #includecyg/io/framebuf.h and this will
automatically include the device-specific headers. Drivers may export
one header per cyg_fb device or a single
header for all devices, without affecting any code outside the device
driver.
Each exported header serves two purposes. First it defines the
parameters, drawing primitive macros, and
iteration macros for each
device. Second it declares the cyg_fb
structure.
Parameters
The parameter section should resemble the following:
#define CYG_FB_320x240x16_STRUCT cyg_ipaq_fb_320x240x16
#define CYG_FB_320x240x16_DEPTH 16
#define CYG_FB_320x240x16_FORMAT CYG_FB_FORMAT_16BPP_TRUE_565
#define CYG_FB_320x240x16_WIDTH 320
#define CYG_FB_320x240x16_HEIGHT 240
#define CYG_FB_320x240x16_VIEWPORT_WIDTH 320
#define CYG_FB_320x240x16_VIEWPORT_HEIGHT 240
#define CYG_FB_320x240x16_FLAGS0 (CYG_FB_FLAGS0_LINEAR_FRAMEBUFFER | \
CYG_FB_FLAGS0_TRUE_COLOUR | \
CYG_FB_FLAGS0_BLANK | \
CYG_FB_FLAGS0_BACKLIGHT)
#define CYG_FB_320x240x16_FLAGS1 0
#define CYG_FB_320x240x16_FLAGS2 0
#define CYG_FB_320x240x16_FLAGS3 0
#define CYG_FB_320x240x16_BASE ((void*)0x01FC0020)
#define CYG_FB_320x240x16_STRIDE 640
Here 320x240x16 is the framebuffer identifier for
use with the macro API. Application code like:
#define FRAMEBUF 320x240x16
cyg_ucount16 width = CYG_FB_WIDTH(FRAMEBUF);
will end up using the CYG_FB_320x240x16_WIDTH
definition. To allow for efficient portable code all parameters must
be compile-time constants. If the hardware may allow some of the
parameters to be varied, for example different resolutions, then this
should be handled either by defining separate devices for each
resolution or by configuration options.
The viewport width and height should always be defined. If the device
driver does not support a viewport then these will be the same as the
standard width and height.
To allow for future expansion there are FLAGS1,
FLAGS2 and FLAGS3 parameters. No
flags are defined for these at present, but device drivers should
still define the parameters.
Drawing Primitives
For each device the exported header file should define macros for the
drawing primitives, using the same naming convention as for
parameters. In the case of true colour displays there should also be
macros for the make-colour and break-colour primitives:
#define CYG_FB_320x240x16_WRITE_PIXEL(_x_, _y_, _colour_) …
#define CYG_FB_320x240x16_READ_PIXEL(_x_, _y_) …
#define CYG_FB_320x240x16_WRITE_HLINE(_x_, _y_, _len_, _colour_) …
#define CYG_FB_320x240x16_WRITE_VLINE(_x_, _y_, _len_, _colour_) …
#define CYG_FB_320x240x16_FILL_BLOCK(_x_, _y_, _w_, _h_, _colour_) …
#define CYG_FB_320x240x16_WRITE_BLOCK(_x_, _y_, _w_, _h_, _data_, _off_, _s_) …
#define CYG_FB_320x240x16_READ_BLOCK(_x_, _y_, _w_, _h_, _data_, _off_, _s_) …
#define CYG_FB_320x240x16_MOVE_BLOCK(_x_, _y_, _w_, _h_, _new_x_, _new_y_) …
#define CYG_FB_320x240x16_MAKE_COLOUR(_r_, _g_, _b_) …
#define CYG_FB_320x240x16_BREAK_COLOUR(_colour_, _r_, _g_, _b_) …
For typical linear framebuffers there are default implementations of
all of these primitives in the generic framebuffer package, held in
the exported header cyg/io/framebuf.inl. Hence the
definitions will typically look something like:
#include <cyg/io/framebuf.inl>
…
#define CYG_FB_320x240x16_WRITE_PIXEL(_x_, _y_, _colour_) \
CYG_MACRO_START \
cyg_fb_linear_write_pixel_16_inl(CYG_FB_320x240x16_BASE, \
CYG_FB_320x240x16_STRIDE, \
_x_, _y_, _colour_); \
CYG_MACRO_END
#define CYG_FB_320x240x16_READ_PIXEL(_x_, _y_) \
({ cyg_fb_linear_read_pixel_16_inl(CYG_FB_320x240x16_BASE, \
CYG_FB_320x240x16_STRIDE, \
_x_, _y_); })
…
All of the drawing primitives have variants for the common display
depths and layouts: 1le, 1be, 2le, 2be, 4le, 4be, 8, 16 and 32. The
inlines take the framebuffer memory base address as the first
argument, and the stride in bytes as the second. Similarly there are
default definitions of the true colour primitives for
8BPP_TRUE_332, 16BPP_TRUE_565,
16BPP_TRUE_555, and 32BPP_TRUE_0888:
#define CYG_FB_320x240x16_MAKE_COLOUR(_r_, _g_, _b_) \
({ CYG_FB_MAKE_COLOUR_16BPP_TRUE_565(_r_, _g_, _b_); })
#define CYG_FB_320x240x16_BREAK_COLOUR(_colour_, _r_, _g_, _b_) \
CYG_MACRO_START \
CYG_FB_BREAK_COLOUR_16BPP_TRUE_565(_colour_, _r_, _g_, _b_); \
CYG_MACRO_END
These default definitions assume the most common layout of colours
within a pixel value, so for
example CYG_FB_MAKE_COLOUR_16BPP_TRUE_565 assumes
bits 0 to 4 hold the blue intensity, bits 5 to 10 the green, and bits
11 to 15 the red.
If the hardware does not implement a linear framebuffer then obviously
writing the device driver will be significantly more work. The macros
will have to perform the operations themselves instead of relying on
generic implementations. The required functionality should be obvious,
and the generic implementations can still be consulted as a reference.
For complicated hardware it may be appropriate to map the macros onto
function calls, rather than try to implement everything inline.
At the time of writing the support for linear
framebuffers is incomplete. Only 8bpp, 16bpp and 32bpp depths have
full support. There may also be future extensions, for example
r90, r180 and
r270 variants to support rotation in software, and
db variants to support double-buffered displays.
Iteration Macros
In addition to the drawing primitives the exported header file should
define iteration macros:
#define CYG_FB_320x240x16_PIXELx_VAR( _fb_, _id_) …
#define CYG_FB_320x240x16_PIXELx_SET( _fb_, _id_, _x_, _y_) …
#define CYG_FB_320x240x16_PIXELx_GET( _fb_, _id_, _x_, _y_) …
#define CYG_FB_320x240x16_PIXELx_ADDX( _fb_, _id_, _incr_) …
#define CYG_FB_320x240x16_PIXELx_ADDY( _fb_, _id_, _incr_) …
#define CYG_FB_320x240x16_PIXELx_WRITE(_fb_, _id_, _colour_) …
#define CYG_FB_320x240x16_PIXELx_READ( _fb_, _id_)…
#define CYG_FB_320x240x16_PIXELx_FLUSHABS( _fb_, _id_, _x0_, _y0_, _w_, _h_) …
#define CYG_FB_320x240x16_PIXELx_FLUSHREL( _fb_, _id_, _x0_, _y0_, _dx_, _dy_) …
The _fb_ argument will be the identifier, in
this case 320x240x16, and the
_id_ will be a small number, 0 for a
PIXEL0 iteration, 1 for PIXEL1,
and so on. Together these two should allow unique local variable names
to be constructed. Again there are default definitions of the macros
in cyg/io/framebuf.inl for
linear framebuffers:
#define CYG_FB_320x240x16_PIXELx_VAR( _fb_, _id_) \
CYG_FB_PIXELx_VAR_16( _fb_, _id_)
#define CYG_FB_320x240x16_PIXELx_SET( _fb_, _id_, _x_, _y_) \
CYG_MACRO_START \
CYG_FB_PIXELx_SET_16( _fb_, _id_, \
CYG_FB_320x240x16_BASE, \
320, _x_, _y_); \
CYG_MACRO_END
The linear SET and GET macros
take base and stride information. The ADDX and
ADDY macros only need the stride. By convention
most of the macros are wrapped in
CYG_MACRO_START/CYG_MACRO_END or
({/}) pairs, allowing debug code
to be inserted if necessary. However the _VAR macro
must not be wrapped in this way: its purpose is to define one or more
local variables; wrapping the macro would declare the variables in a
new scope, inaccessible to the other macros.
Again for non-linear framebuffers it will be necessary to implement
these macros fully rather than rely on generic implementations, but
the generic versions can be consulted as a reference.
The cyg_fb declaration
Finally there should be an export of the
cyg_fb structure or structures. Typically
this uses the _STRUCT parameter, reducing the
possibility of an accidental mismatch between the macro and function APIs:
extern cyg_fb CYG_FB_320x240x16_STRUCT;
Driver-Specific Source Code
Exporting parameters and macros in a header file is not enough. It is
also necessary to actually define the cyg_fb
structure or structures, and to provide hardware-specific versions of
the control operations. For non-linear framebuffers it will also be
necessary to provide the drawing functions. There is a utility macro
CYG_FB_FRAMEBUFFER for instantiating a
cyg_fb structure. Drivers may ignore this
macro and do the work themselves, but at an increased risk of
compatibility problems with future versions of the generic code.
CYG_FB_FRAMEBUFFER(CYG_FB_320x240x16_STRUCT,
CYG_FB_320x240x16_DEPTH,
CYG_FB_320x240x16_FORMAT,
CYG_FB_320x240x16_WIDTH,
CYG_FB_320x240x16_HEIGHT,
CYG_FB_320x240x16_VIEWPORT_WIDTH,
CYG_FB_320x240x16_VIEWPORT_HEIGHT,
CYG_FB_320x240x16_BASE,
CYG_FB_320x240x16_STRIDE,
CYG_FB_320x240x16_FLAGS0,
CYG_FB_320x240x16_FLAGS1,
CYG_FB_320x240x16_FLAGS2,
CYG_FB_320x240x16_FLAGS3,
0, 0, 0, 0, // fb_driver0 -> fb_driver3
&cyg_ipaq_fb_on,
&cyg_ipaq_fb_off,
&cyg_ipaq_fb_ioctl,
&cyg_fb_nop_synch,
&cyg_fb_nop_read_palette,
&cyg_fb_nop_write_palette,
&cyg_fb_dev_make_colour_16bpp_true_565,
&cyg_fb_dev_break_colour_16bpp_true_565,
&cyg_fb_linear_write_pixel_16,
&cyg_fb_linear_read_pixel_16,
&cyg_fb_linear_write_hline_16,
&cyg_fb_linear_write_vline_16,
&cyg_fb_linear_fill_block_16,
&cyg_fb_linear_write_block_16,
&cyg_fb_linear_read_block_16,
&cyg_fb_linear_move_block_16,
0, 0, 0, 0 // fb_spare0 -> fb_spare3
);
The first 13 arguments to the macro correspond to the device
parameters. The next four are arbitrary CYG_ADDRWORD
values for use by the device driver. Typically these are used to share
on/off/ioctl functions between multiple
cyg_fb structure. They are followed by
function pointers: on/off/ioctl control; double buffer synch; palette
management; true colour support; and the drawing primitives.
nop versions of the on, off, ioctl, synch, palette
management and true colour functions are provided by the generic
framebuffer package, and often these arguments to the
CYG_FB_FRAMEBUFFER macro will be discarded
at compile-time because the relevant CDL interface is not implemented.
The final four arguments are currently unused and should be 0. They
are intended for future expansion, with a value of 0 indicating that a
device driver does not implement non-core functionality.
As with the macros there are default implementations of the true
colour primitives for 8bpp_true_332,
16bpp_true_565, 16bpp_true_555
and 32bpp_true_0888, assuming the most common
layout for these colour modes. There are also default
implementations of the drawing primitives for linear framebuffers,
with variants for the common display depths and layouts. Obviously
non-linear framebuffers will need rather more work.
Typically a true colour or grey scale framebuffer device driver will
have to implement just three hardware-specific functions:
int
cyg_ipaq_fb_on(cyg_fb* fb)
{
…
}
int
cyg_ipaq_fb_off(cyg_fb* fb)
{
…
}
int
cyg_ipaq_fb_ioctl(cyg_fb* fb, cyg_ucount16 key, void* data, size_t* len)
{
int result;
switch(key) {
case CYG_FB_IOCTL_BLANK_GET: …
…
default: result = ENOSYS; break;
}
return result;
}
These control operations are entirely hardware-specific and cannot be
implemented by generic code. Paletted displays will need two more
functions, again hardware-specific:
void
cyg_pcvga_fb_read_palette(cyg_fb* fb, cyg_ucount32 first, cyg_ucount32 len,
void* data)
{
…
}
void
cyg_pcvga_fb_write_palette(cyg_fb* fb, cyg_ucount32 first, cyg_ucount32 len,
const void* data, cyg_ucount16 when)
{
…
}
Future Expansion
As has been mentioned before framebuffer hardware varies widely. The
design of a generic framebuffer API requires complicated trade-offs
between efficiency, ease of use, ease of porting, and still supporting
a very wide range of hardware. To some extent this requires a lowest
common denominator approach, but the design allows for some future
expansion and optional support for more advanced features like
hardware acceleration.
The most obvious route for expansion is the ioctl
interface. Device drivers can define their own keys, values
0x8000 and higher, for any operation. Alternatively
a device driver does not have to implement just the interface provided
by the generic framebuffer package: additional functions and macros
can be exported as required.
Currently there are only a small number of ioctl
operations. Additional ones may get added in future, for example to
support a hardware mouse cursor, but only in cases where the
functionality is likely to be provided by a significant number of
framebuffer devices. Adding new generic functionality adds to the
maintenance overhead of both code and documentation. When a new
generic ioctl operation is added there will
usually also be one or more new flags, so that device drivers can
indicate they support the functionality. At the time of writing only
12 of the 32 FLAGS0 flags are used, and a further
96 are available in FLAGS1,
FLAGS2 and FLAGS3.
Another route for future expansion is the four spare arguments to the
CYG_FB_FRAMEBUFFER macro. As an example of how
these may get used in future, consider support for 3d hardware
acceleration. One of the spare fields would become another table of
function pointers to the various accelerators, or possibly a
structure. A FLAGS0 flag would indicate that the
device driver implements such functionality.
Other forms of expansion such as defining a new standard drawing
primitive would be more difficult, since this would normally involve
changing the CYG_FB_FRAMEBUFFER macro. Such
expansion should not be necessary because the existing primitives
provide all reasonable core functionality. Instead other packages such
as graphics libraries can work on top of the existing primitives.