LibGGI
Steve Cheng,
[email protected]
$Date: 1998/02/17 10:53:35 $
This document describes LibGGI, a flexible, extensible, dynamic draw-
ing library developed by the GGI project.
1. Introduction
LibGGI, the dynamic GGI (General Graphics Interface) library is a
flexible drawing library.
It provides an opaque interface to the display's acceleration
functions. It was originally intended to allow user programs to
interface with KGI, the kernel side of the GGI code, but other display
types can be easily used by loading the appropriate "display target"
(e.g. X, memory).
LibGGI consists of a main library (libggi.so) and a multitude of
dynamic drivers. The library then loads the necessary "drivers" for
the requested mode, taking hints from the graphics device if
necessary. LibGGI can also load extension libraries, e.g. to provide
enhanced 2D and 3D functions.
It has been designed after having a look at several existing
libraries, and so far we have found porting to be quite simple from
and to most of them.
1.1. What is known to work
o Modes:
o VGA 1bit, 16color, 8bit, and ModeX
o text-16, linear-1bit,-8bit,-15/16bit and -32bit frame buffers+ up
to 16bit ramdacs
o Targets:
o KGI: Kernel Graphics Interface
o X: X Window System (X must be in the same mode as the requested
visual)
o Xlib: X Window alternate method; uses X for primitives
o memory: off-screen memory
o multi: display on multiple targets at once
o AA: AAlib, an ASCII-Art renderer
o XF86DGA: XFree86 server Direct Graphics Access
o SVGAlib: SUID root console graphics library
o GLIDE: 3Dfx
o Terminfo: GGI textmodes on terminals
o Sub-target: Visual-within-a-visual
o Wrappers:
o SVGAlib (some functions are missing, but many demos work.)
o A number of demos
The code is still alpha, so things may break overnight or change
unexpectedly...
2. How To Install And Use
In the libggi directory, do:
$ ./configure # will be done automatically on first make
$ make
$ su -c "make install"
For some demos of libggi code, see the demos subdirectory which has
several programs showing around the features of LibGGI.
The most importent of them is demo.c which we use ourselves for
checking new features. demo.c is heavily commented to allow people to
learn LibGGI by example. There is also speed.c which is a speed and
consistency test application, which can be used if you changed
something in LibGGI to see if you have broken anything.
LibGGI will automatically default to the most suitable display target.
However, this can be overridden using the LIBGGI_DISPLAY environment
variable, set to the name of the display target, plus any additional
arguments.
The LIBGGI_DEBUG environment variable can be set to 0 (default) for no
debugging information, or 255 for verbose debugging information.
2.1. Required Software
To use LibGGI, you will need:
o Most likely GNU Make to build LibGGI.
o linuxthreads-0.5 or higher (or glibc-2.0.2) if compiling with
multi-thread support
o ld.so 1.7.14 or higher
o GGI Linux kernel if using display-KGI target.
o X Window System if using display-X target.
o XFree86 if using display-DGA target.
o AAlib if using display-AA target.
3. LibGGI Functions
3.1. Library control
3.1.1. int ggiInit(void);
Initalizes the library. This function must be called before using
other LibGGI functions; otherwise results will be undefined.
Return: 0 for OK, otherwise an error code.
Example:
______________________________________________________________________
ggiInit();
/* do some libggi stuff */
ggiExit();
______________________________________________________________________
3.1.2. void ggiExit(void);
Uninitializes the library and automatically cleanup. This should be
called after an application is finished with the library. If any GGI
functions are called after the use of ggiExit the results will be
undefined.
Return: None
Example:
______________________________________________________________________
ggiInit();
/* do some libggi stuff */
ggiExit();
______________________________________________________________________
3.1.3. void ggiPanic(const char *format, ...);
Graceful shutdown for fatal errors with reporting. May be used like
printf(3). It will shut down the graphics modes active, close all
visuals, print the error message given and then exit the application.
ggiPanic() should only be used by usermode programs when something is
really screwed, and they do not know what to do. The same applies for
libraries, but might be used in rare situations such as corruption of
critical data structures.
Return: never returns.
Example:
______________________________________________________________________
ggiPanic("Aiee! Penguin on fire! ;-) Error code: %d", err);
______________________________________________________________________
3.2. Visual management
A visual is simply a thing you can draw on. For example, a VT in
fullscreen-mode, an X window, an invisible memory area, and a printer.
It is identified by its handle of type ggi_visual_t, which is given to
all drawing functions to indicate which visual to operate on.
Each visual is completely independent of each other. You can use these
visuals to display on multiple monitors and/or in multiple windows or
to work on "virtual" graphics devices like in-memory pixmaps or even
PPM-files on disk.
ggi_visual_t is opaque to the user. Do not try to access any part of
the structure directly. It may change without notice.
3.2.1. ggi_visual_t ggiOpen(const char *display, ...);
Opens a visual. This function is given a null terminated list of the
names of LibGGI dynamic drivers to load. This is usually a display
target. Once loaded, they will all be associated with the single
visual. Giving no arguments except the NULL instructs LibGGI to open
the default display target (e.g. display-X, set by LIBGGI_DISPLAY).
Return: The opened visual, or NULL for error.
Example:
______________________________________________________________________
memory_visual = ggiOpen("display-memory", NULL);
/* Get from LIBGGI_DISPLAY enviroment variable */
default_visual = ggiOpen(NULL);
if(!memory_visual || !default_visual)
return EXIT_FAILURE;
______________________________________________________________________
3.2.2. int ggiClose(ggi_visual_t vis);
Releases and destroys the visual and the associated internal control
structures. This will close X windows, return KGI-consoles to text-
mode, etc.
If focus is on the closed visual, focus is set to NULL.
Return: 0 for OK, otherwise an error code.
Example:
______________________________________________________________________
memory_visual = ggiOpen("display-memory", NULL);
/* do some drawing, save image, etc. */
ggiClose(memory_visual);
______________________________________________________________________
3.3. Mode management
3.3.1. The ggi_mode struct
The definition in ggi.h is:
______________________________________________________________________
typedef struct { sint16 x, y; } ggi_coord;
typedef struct /* requested by user and changed by driver */
{
uint32 frames; /* frames needed */
ggi_coord visible; /* vis. pixels, may change slightly */
ggi_coord virt; /* virtual pixels, may change */
ggi_coord size; /* size of visible in mm */
ggi_graphtype graphtype; /* which mode ? */
ggi_coord dpp; /* fontsize */
} ggi_mode;
______________________________________________________________________
You don't need to care about the internal definition, if you want to
set a mode, but it is necessary if you want to find out the mode actu-
ally set.
Please note that the visible and virtual size are given in pixels, not
dots. This makes a difference for text modes, because a character is
treated as one pixel, but consists of a text.x*text.y sized matrix of
dots.
3.3.2. int ggiSetMode(ggi_visual_t visual, ggi_mode *tm);
Set any mode (text or graphics).
Return: 0 for OK, otherwise could not set mode.
Use this function only if you want something really strange that the
specific SetModes below cannot give you. You usually do not want to do
this, unless you really know what you are doing and understand the
values in ggi_mode.
3.3.3. int ggiGetMode(ggi_visual_t visual, ggi_mode *tm);
Get the current mode. tm will be filled with the current mode on
return.
Return: 0 for OK, otherwise an error code.
3.3.4. int ggiCheckMode(ggi_visual_t visual, ggi_mode *tm);
Check any mode (text or graphics).
Return: A return of 0 means that a setmode call for this mode would
succeed. Otherwise, the mode is given cannot be set.
If the mode can not be set, tm is changed to the suggested mode as
follows:
o Resolutions are always adjusted up. If you want the next lower,
start out at 1,1 (or somewhere else reasonable) and jump up the
ladder.
Only if the maximum resolution would be exceeded, resolutions are
adjusted down to the maximum.
The above applies to visible and virtual size. If there is
interference between them, the visible size is satified first if
possible, then the virtual size.
o Note that the adjustment of one value do not normally affect other
values. For example, if 320x100 (320x200) is requested, the visible
size may be adjusted to 320x200, but virtual size will be left
alone. Of course, if the virtual size becomes less than visible
size, then it will be adjusted as well. A good rule of thumb is
that anything returned by ggiCheckMode() is minimally valid.
o Font sizes (i.e. the size of the pixel in textmodes) are handled
the other way round: they are adjusted down except when there is
nothing below.
o Graphtype: Modes are normally card-dependant and not too dependant
on size (except for maxsize-limits etc.)
The basic rationale is to change graphtype only, if the card does
not support it at all. If max-resolution is exceeded, then that is
adjusted down and not the graphtype. This assumes, that if you
request true-color, you really want that and not so badly the
resolution you requested. If this is not the case, you can still
retry with another graphtype.
If it is necessary to change to change the type of other visuals,
the order should be ascending (if possible), i.e.
1->4->8->15->16->24/32 bit. So you always get a mode which can do
more than you requested. Only when no better modes are available,
the type is adjusted down.
o It is possible to pass GGI_AUTO as any of the parameters. GGI_AUTO
is a placeholder (e.g. the program does not care about a specific
parameter). The display target will reset GGI_AUTO paramter to a
default, such as its maximum/preferred/LIBGGI_DEFMODE resolution,
while being compatible with the other settings. Note that the
presence of GGI_AUTO is not flagged as an error. If
ggiSet{Graph/Text}Mode() is given GGI_AUTO as one or more of its
arguments, it will silently set a mode.
3.3.5. int ggiSetTextMode(ggi_visual_t visual, int cols, int rows,
int vcols, int vrows, int fontx, int fonty);
Set a textmode with given columns and rows, virtual columns and rows
and a font of the given size.
Return: 0 for OK, otherwise could not set mode.
The fontsize is actually the size of the pixel (ggi_mode.dpp), in
textmodes.
3.3.6. vcols, int vrows, fontx, int fonty, ggi_mode *suggested_mode,
...); int ggiCheckTextMode(ggi_visual_t visual, int cols, int rows,
int
Check a text mode, with a null-terminated list of mode features. The
last argument must be NULL.
Return: A return of 0 means that a setmode call for this mode would
succeed. Otherwise, the mode is given cannot be set. In this case, tm
is changed to the suggested mode:
If suggested_mode is not NULL, then it will be filled with the
suggested mode, as documented under ggiCheckMode(). In the future,
there may be more arguments, but currently there is nothing that may
be put between the final NULL and suggested_mode.
3.3.7. int yv, ggi_graphtype type); int ggiSetGraphMode(ggi_visual_t
visual, int x, int y, int xv,
Set a graphics mode with a visible area of size x/y and a virtual area
of size vx/vy (you can pan aound the virtual area using the
ggiSetOrigin()) and the specified graphics type.
Return: 0 for OK, otherwise could not set mode.
3.3.8. int yv, ggi_graphtype type, ggi_mode *suggested_mode, ...);
int ggiCheckGraphMode(ggi_visual_t visual, int x, int y, int xv,
Check a graphics mode, with a null-terminated list of mode features.
The last argument must be NULL.
Return: A return of 0 means that a setmode call for this mode would
succeed. Otherwise, the mode is given cannot be set. In this case, tm
is changed to the suggested mode:
If suggested_mode is not NULL, then it will be filled with the
suggested mode, as documented under ggiCheckMode(). In the future,
there may be more arguments, but currently there is nothing that may
be put between the final NULL and suggested_mode.
Example:
______________________________________________________________________
/* Use only my mode... but you really should try to negotiate though */
err = ggiCheckGraphMode(vis, 320, 200, 320, 200, GT_8BIT, NULL, NULL);
if(err)
return EXIT_FAILURE;
/* OR use a suggested mode */
err = ggiCheckGraphMode(vis, 320, 200, 320, 200, GT_8BIT, &sug_mode, NULL);
ggiSetMode(&sug_mode);
______________________________________________________________________
3.4. Graphics context
LibGGI has a current context associated with each visual. This is done
for performance reasons, as LibGGI can set up pointers to optimized
functions when the GC changes (which can be monitored, as it may only
be changed by the functions mentioned below).
3.4.1. int ggiSetGCForeground(ggi_visual_t vis, ggi_pixel color);
Sets the current colors for the foreground, used in all normal
graphics functions.
Return: 0 for OK, otherwise an error code.
3.4.2. int ggiSetGCBackground(ggi_visual_t vis, ggi_pixel color);
Sets the current colors for the background, used in some 2-color
operations like drawing text.
Return: 0 for OK, otherwise an error code.
3.4.3. int ggiGetGCForeground(ggi_visual_t vis, ggi_pixel * color);
Reads the current foreground color.
Return: 0 for OK, otherwise an error code.
3.4.4. int ggiGetGCBackground(ggi_visual_t vis, ggi_pixel * color);
Reads the current background color.
Return: 0 for OK, otherwise an error code.
3.5. Color and palette
Visuals may have an indirect mapping off the pixel-value to a color
via a programmable palette. This is e.g. true for the 8 bit IBM VGA
modes. But even for "direct-mapped" modes, you will need to know which
color maps to which pixel-value.
Please note that palette lookups can be quite expensive (especially
for palettized modes) and should thus be avoided e.g. by caching
earlier results.
The ggi_color struct has 16 bit wide entries for red (.r), green (.g),
and blue (.b) values. Please scale your palette values as necessary.
The ggi_pixel is a hardware-dependent representation of a color. It is
usually calculated from a ggi_color by ggiMapColor or read from the
visual by ggiGetPixel, and you can safely assume that the relationship
between a ggi_color and it's associated ggi_pixel value does not
change unless you change the visual or the mode the current visual is
in. You can not do calculations with ggi_pixel values. Well, you can,
but you should not. The results would be unpredictable and system
dependent.
3.5.1. ggi_pixel ggiMapColor(ggi_visual_t vis, ggi_color * col);
Gets the pixelvalue for the given color.
Return: 0 for OK, otherwise an error code.
3.5.2. int ggiUnmapPixel(ggi_visual_t vis, ggi_pixel pixel, ggi_color
*col);
Gets the color associated with a given pixelvalue.
Return: 0 for OK, otherwise an error code.
3.5.3. int len); int ggiPackColors(ggi_visual_t vis, void *buf,
ggi_color *cols,
Converts the colors in cols to pixelvalues in buf.
Return: 0 for OK, otherwise an error code.
3.5.4. int len); int ggiUnpackPixels(ggi_visual_t vis, void *buf,
ggi_color *cols,
Converts pixelvalues in buf to individual elements of cols.
Return: 0 for OK, otherwise an error code.
3.5.5. *cmap); int ggiSetPaletteVec(ggi_visual_t vis, int s, int len,
ggi_color
Sets a range of palette values, of length len, starting at index
number s.
Return: 0 for OK, otherwise an error code.
3.5.6. *cmap); int ggiGetPaletteVec(ggi_visual_t vis, int s, int len,
ggi_color
Gets a range of palette values, of length len, starting at index
number s.
Return: 0 for OK, otherwise an error code.
3.6. Primitives
LibGGI has three basic types of primitives when it comes to filling
rectangular areas (including the degenerate cases of horizontal and
vertical lines and single pixels).
We have found three operations commonly performed on such areas :
o Draw: This means you set all contained pixels to the current
foreground color (maybe modified by the update-operations as set in
the current graphics context).
o Put: Fill the area with pixels of different value from a buffer.
o Get: Read pixels from the screen into such a buffer.
3.6.1. int ggiDrawPixel(ggi_visual_t vis, int x, int y);
3.6.2. int ggiPutPixel(ggi_visual_t vis, int x, int y, ggi_pixel
col);
3.6.3. int ggiGetPixel(ggi_visual_t vis, int x, int y, ggi_pixel
*col);
Draws/Puts/Gets a single pixel at x/y.
Return: 0 for OK, otherwise an error code.
3.6.4. int ggiDrawHLine(ggi_visual_t vis, int x, int y, int w);
3.6.5. *buf); int ggiPutHLine(ggi_visual_t vis, int x, int y, int w,
void
3.6.6. *buf); int ggiGetHLine(ggi_visual_t vis, int x, int y, int w,
void
Draws/Puts/Gets a horizontal line from x/y, extending w pixels in the
positive x direction (normally left).
Return: 0 for OK, otherwise an error code.
3.6.7. int ggiDrawVLine(ggi_visual_t vis, int x, int y, int h);
3.6.8. *buf); int ggiPutVLine(ggi_visual_t vis, int x, int y, int h,
void
3.6.9. *buf); int ggiGetVLine(ggi_visual_t vis, int x, int y, int h,
void
Draws/Puts/Gets a vertical line from x/y, extending h pixels in the
positive y direction (normally down).
Return: 0 for OK, otherwise an error code.
3.6.10. int ggiDrawBox(ggi_visual_t vis, int x, int y, int w, int h);
3.6.11. *buf); int ggiPutBox(ggi_visual_t vis, int x, int y, int w,
int h, void
3.6.12. *buf); int ggiGetBox(ggi_visual_t vis, int x, int y, int w,
int h, void
Draws/Gets/Puts a filled rectangle at x/y and extending w pixels in
the positive x direction and h in the positive y direction.
Return: 0 for OK, otherwise an error code.
The buffers are filled with the x coordinate walking first.
3.6.13. int ggiFillscreen(ggi_visual_t vis);
Fills the entire virtual screen. May be more efficient than the
corresponding call to ggiDrawBox().
Return: 0 for OK, otherwise an error code.
3.6.14. int ggiDrawCircle(ggi_visual_t vis, int x, int y, int r);
Draws a circle of given radius r around x/y.
Return: 0 for OK, otherwise an error code.
This will be removed and replaced by a circle routine in libggi2d. It
gives already a warning.
3.6.15. int ggiDrawLine(ggi_visual_t vis, int x, int y, int xe, int
ye);
Draws any line from x/y to xe/ye. The line is exact; the pixel set is
no more than 0.5 pixels off the place it should be.
Return: 0 for OK, otherwise an error code.
3.7. Blits
3.7.1. nx, int ny); int ggiCopyBox(ggi_visual_t vis, int x, int y,
int w, int h, int
This is a screen-to-screen-blit, all in the same visual. Copy the box
described by x,y,w,h to the new location nx,ny. This automatically
takes care of overlaps and optimizes for the given target (e.g. uses
HW-accel or intermediate buffers as appropriate).
Return: 0 for OK, otherwise an error code.
3.7.2. ggi_visual *dst, int dx, int dy, int dw, int dh); int ggi-
CrossBlit(ggi_visual *src, int sx, int sy, int sw, int sh,
Blits a rectangular memory area from one visual to another. This is a
very complex function as it handles stretching and colorspace-
conversion.
Return: 0 for OK, otherwise an error code.
We try hard to optimize this, but avoid using it if possible. And try
to keep its work easy. If possible, do not copy between different
colorspaces or do any stretching.
This will most probably be removed and replaced by a routine in
libggi2d.
3.8. DirectBuffer
Dependent on the visual target and runtime environment found,
applications may be granted direct access to hardware and/or library
internal buffers. This may significantly enhance performance for
certain pixel oriented applications or libraries.
The DirectBuffer is a mechanism in which a LibGGI program can use to
determine all the characteristics of these buffers (typically the
framebuffer), including the method of addressing, the stride,
alignment requirements, and endianness.
However, use not conforming to this specification will have undefined
effects and may cause data loss or corruption, program malfunction or
abnormal program termination. So you don't really want to do this.
3.8.1. Types of Buffers
3.8.1.1. Framebuffer
A frame buffer stores a certain number of bits per pixel. Some visual
targets may store multiple frames in one buffer. If the visual target
in question does so, the number of bits stored per attribute and pixel
as well as the attributes and number of pixels stored per frame must
be the same for all frames. Some attributes, such as depth values, may
be shared for all frames. Each attribute, frame and shared attribute
uses an orthogonal subset of the bits stored per pixel. Hence simple
masking and shift operations may be used to extract and/or modify the
attribute values.
A frame buffer may be organized as several distinct buffers. Each
buffer may have a different layout. This means both the addressing
scheme to be used as well as the addressing parameters may differ from
buffer to buffer. Within the scope of this document, only pixel linear
addressing scheme buffers are specified (yet).
3.8.1.2. Pixel Linear Buffer
A linear buffer is a region in the application's virtual memory
address space. A pixel with the pixel coordinates (<x>,<y>) is
assigned a pixel number according to the following formula: <pixel
number> = (<origin_y> + <y>)*<stride> + <origin_x> + <x> In any case
both <x> and <y> must not be negative, <x> must be less than <size_x>
and <y> must be less than <size_y>. For top-left-origin screen
coordinates, <stride> and <origin_y> will both be positive. For
bottom-left-origin screen coordinates, <stride> and <origin_y> will
both be negative. This will result in the correct pixel number with
the same formula in both cases. The pixel number will be used to
address the pixel.
A certain number <bpp> of bits is stored per pixel. Bit <n> of the
pixel value for pixel number <i> is stored in bit number <i>*<bpp> +
<n> of the buffer. Bit <n> of byte <i> in the buffer corresponds to
bit <i>*8 + <n> of the buffer for host CPU native bit and byte
ordering. For some targets, the buffer might not be in host CPU native
format and swapping operations need to be performed before writes or
after reads.
3.8.2. Accessing the Buffer
Read and write access to the buffer is done using load and store
instructions of the host CPU. Depending on the visual target, only a
subset of the possible access widths may be allowed. Access may need
to be aligned to integer multiples of the access width but has to be
at least at 8 bit (byte) boundaries. Bit number log2(<width>) in
<align> is set if accesses with width <width> have to be aligned. Read
operations should be performed using the <read> buffer and write
operations should be performed using the <write> buffer. These might
be the same, but need not. If they are, read/write may be done to
either buffer. Please note, that either read or write may be NULL.
These are write-only or read-only buffers, which might be caused by
hardware limitations. Such buffers are not suited to do Read-Modify-
Write operations, so take care.
3.8.2.1. Alignment
If unaligned access is allowed, all bits to be accessed must belong to
the buffer. The last access unit will be padded with meaningless bits
so that an aligned access with broadest width is possible. Performing
unaligned accesses if alignment is required or access with unsupported
widths will have an undefined effect Thus it may cause data
corrruption or abnormal program termination. In any case, this must
not cause the machine to hang; neither temporarily nor permanently.
Though the application might hang due to page fault race conditions.
3.8.2.2. Access Widths
The access widths specified currently are 8 bit, 16 bit 32 bit and 64
bit. Any combination of these widths may be supported, but need not.
However, at least one access width is supported. Bit log2(<width>) in
<access> is set if this accesses wit width <width> are
supported/allowed.
3.8.2.3. Swapping Requirements
If the buffer is not stored with native bit and byte ordering,
swapping operations have to be performed before writes and after reads
according to the following scheme (shown for 16 bit access, <val>
being the value being read, and <swap> being a bit mask that selects
the swap operations to be performed):
______________________________________________________________________
if (swap & 1) {
val = (val & 0x5555) << 1) | ((val & 0xAAAA) >> 1);
}
if (swap & 2) {
val = (val & 0x3333) << 2) | ((val & 0xCCCC) >> 2);
}
if (swap & 4) {
val = (val & 0x0F0F) << 4) | ((val & 0xF0F0) >> 4);
}
if (swap & 8) {
val = (val & 0x00FF) << 8) | ((val & 0xFF00) >> 8);
}
______________________________________________________________________
A generalized scheme has to be used for other access widths. Note
that only access widths not less than the biggest defined swap should
be allowed to have consistent addressing guaranteed.
3.8.2.4. Paged Buffers
Successive access to addresses <addr> and <addr2> of either read or
write buffers with <addr0> <page_size> != <addr1> / <page_size>/
may be very expensive compared to successive accesses with <addr0>
<page_size> == <addr1> / <page_size>/. On i386 the penalty will be
about 1500 cycles plus 4 cycles per to be remapped. Because of this,
block transfer operations might become very inefficient for paged
buffers. If there are two different buffers provided for read and
write operations, you should do successive reads from one and do
successive writes to the other. If not, it is recommended to copy
pagewise into a temporary buffer and then to copy this temporary
buffer back to screen.
3.8.2.5. Update Operations
A write mask may be specified that indicates which bits within a pixel
value should be affected by write operations. Bits not set in the
write mask will not be affected by write accesses. This write
protection may be performed in hardware. If not, the application has
to perform the neccessary read-modify-write cycles. Modifying bits not
set in the write mask will have an undefined effect.
3.8.3. API Structures
______________________________________________________________________
/* Pixel Attributes
**
** These can be controlled per pixel of an image. Any of the attributes
** may be missing, and there might be some not defined yet. To query
** information about which attributes can be controlled, use the
** ggiLFBAttributeMask() function that returns a pixel value bitmask
** of the bits that are used to contol a given attribute. If a bitmask
** of zero is returned, the attribute queried cannot be controlled.
*/
typedef enum ggi_pixel_attribute {
paPrivate, /* Don't touch */
paApplication, /* store what you want in here */
paBackgroundIndex, /* index of the color of the pixel */
paForegroundIndex, /* index of the color of the texture */
paTextureIndex, /* index of the texture */
paBlink, /* blink bit/frequency */
paColor1, /* intensity of color channel 1 */
paColor2, /* intensity of color channel 2 */
paColor3, /* intensity of color channel 3 */
paAlpha, /* alpha value (all channels) */
paAlpha1, /* alpha value channel 1 */
paAlpha2, /* alpha value channel 2 */
paAlpha3, /* alpha value channel 3 */
paZValue, /* z-value */
paStencil, /* stencil buffer value */
paBlendKey, /* a key used for blending */
paBlendLow1, /* keying low value channel 1 */
paBlendHigh1, /* keying high value channel 1 */
paBlendLow2, /* keying low value channel 2 */
paBlendHigh2, /* keying high value channel 2 */
paBlendLow3, /* keying low value channel 3 */
paBlendHigh3, /* keying high value channel 3 */
paLastPixelAttribute /* the last defined, must(!) be less 32 */
} ggi_pixel_attribute;
enum ggi_buffer_layout {
blPixelLinearBuffer,
blLastBufferLayout
};
typedef struct ggi_buffer
{
/* administrative;
*/
struct ggi_buffer *next;
void *app_private;
void *lib_private;
void *drv_private;
/* access info
*/
enum ggi_buffer_layout layout;
void *read; /* buffer address for reads */
void *write; /* buffer address for writes */
ggi_uint page_size; /* zero for true linear buffers */
ggi_uint access; /* supported access widths */
ggi_uint align; /* alignment requirements */
ggi_uint swap; /* swapping requirements */
uintl write_mask;
/* buffer layout
*/
ggi_uint bpp; /* bits per pixel */
ggi_sint size_x, size_y; /* size of application area */
ggi_sint origin_x, origin_y; /* application area origin */
ggi_sint stride; /* pixels per row */
ggi_pixel_attribute left_frame_last_attr[MAX_NR_FRAMES];
ggi_uint *left_frame_attr_mask[MAX_NR_FRAMES];
ggi_pixel_attribute right_frame_last_attr[MAX_NR_FRAMES];
ggi_uint *right_frame_attr_mask[MAX_NR_FRAMES];
} ggi_buffer;
______________________________________________________________________
3.8.4. API Functions
3.8.4.1. ggi_uint frame, enum ggi_pixel_attribute attr); ggi_uint
ggiLFBAttributeMask(struct ggi_buffer *fb, ggi_uint eye,
This returns a bit mask for the given attribute defining the bits used
to control a given attribute for a given frame. If a mask is zero,
the attribute for the given frame and eye cannot be controlled with
this buffer. Masks are given for a pixel value are in host CPU native
format and have <bpp> valid bits filled from LSB to MSB. This is the
bit order within the attributes too.
3.8.4.2. ggi_error ggiLFBGetBuffer(ggi_context ctx, struct ggi_buffer
**buf);
This should initially be called with buf == NULL and will return the
first 'public' buffer then. Subsequent calls will return anymore
public buffers or NULL in buf if there are no buffers left.
3.8.4.3. void ggiLFBClaimBuffer(ggi_context ctx, struct ggi_buffer
*buf);
Tells the library that the application is using this buffer directly.
Thus changing framebuffer parameters will not be done unless there are
callback functions registered.
3.8.4.4. *buf); void ggiLFBReleaseBuffer(ggi_context ctx, struct
ggi_buffer
Tells the library that the application does not use the buffer any
longer.
3.8.4.5. ggi_lfb_callback, void *entry);/ void ggiLFBCall-
back(ggi_context ctx, ggi_buffer *fb,
______________________________________________________________________
typedef enum ggi_lfb_callback
{
cbLastLFBCallback
} ggi_lfb_callback;
______________________________________________________________________
This can be used to register a callback when certain framebuffer
parameters are changed. However, there are no call backs defined yet.
3.8.5. Example
This is a simple example to get a linear framebuffer.
______________________________________________________________________
ggi_visual_t vis;
ggi_directbuffer_t db;
ggi_pixellinearbuffer *plb;
char * fb;
vis = ggiOpen(NULL);
if(!vis) {
fprintf(stderr, "Cannot open visual.\n");
exit(1);
}
if(ggiSetGraphMode(vis, GGI_AUTO, GGI_AUTO,
GGI_AUTO, GGI_AUTO, GGI_AUTO))
{
fprintf(stderr, "Cannot set mode.\n");
exit(1);
}
if(ggiDBGetBuffer(vis, &db) ||
(ggiDBGetLayout(db) != blPixelLinearBuffer))
{
fprintf(stderr, "Cannot find a linear framebuffer.\n");
exit(1);
}
plb = ggiDBGetPLB(db);
if(!plb) {
fprintf(stderr, "Cannot get a linear framebuffer.\n");
exit(1);
}
/* Now you can use fb to draw whatever you want */
fb = plb->write;
______________________________________________________________________
3.9. Origin and splitline
3.9.1. int ggiSetOrigin(ggi_visual_t vis, int x, int y);
Set the top-left corner of the displayed area to x/y.
Return: 0 for OK, otherwise an error code.
When using a larger virtual area, you can pan the visible area over
the virtual one to do scrolling. Some targets have extemely efficient
means to do this (i.e. they do it in hardware).
Thus this is commonly used for double-buffering by requesting a
double- height virtual screen (i.e. virtual size is double of the
visible size), and then drawing on the upper/lower half alternatingly
and displaying the currently "steady" buffer (the one which is not
drawn to) using ggiSetOrigin().
Example: Pan from the top to the bottom of the virtual screen
______________________________________________________________________
for(i = 0; i<virt_y-visible_y; i++) {
ggiSetOrigin(vis, 0, i);
}
______________________________________________________________________
Note that this call takes dot coordinates, not pixel coordinates as
the drawing primitives do. This makes a difference in text mode.
3.9.2. int ggiSetSplitline(ggi_visual_t vis, int y);
Splitline is a feature which originated from the IBM VGA which is able
to display the start of the display-memory (i.e. position 0/0) at some
arbitrary line. This is a hardware feature but may be emulated by some
other targets (e.g. X).
Return: 0 for OK, otherwise an error code.
In other words, splitline is an IBM VGA feature which allows to
overlay the lower parts of the screen with the graphics at 0/0. This
is nice to e.g. scroll in status lines or the like. It is not
guaranteed to work and will return an error code stating this, if it
is not supported. Please note, that there are some broken VGA clones
which will silently ignore the command.
In emulated splitlines, the image from the point specified by
ggiSetOrigin is displayed at the top part of the image before the
splitline.
Example:
______________________________________________________________________
for (i=vy;i>=0;i--)
{
ggiSetSplitline(vis,i);
usleep(10000);
if (ggiKbhit(vis)) break;
}
______________________________________________________________________
Note that this call takes a dot line, not a pixel value as the drawing
primitives do. This makes a difference in text mode.
3.10. Event handling
In addition to graphics operations, LibGGI provides several event
handling functions.
Events are of type ggi_event:
______________________________________________________________________
typedef union ggi_event {
uint8 size; /* size of this event */
ggi_any_event any; /* access COMMON_DATA */
ggi_cmd_event cmd; /* command/broadcast */
ggi_raw_event raw; /* raw data event */
ggi_val_event val; /* valuator change */
ggi_key_event key; /* key press/release */
ggi_pmove_event pmove; /* pointer move */
ggi_pbutton_event pbutton; /* pointer buttons */
} ggi_event;
______________________________________________________________________
First, size is the size of the given event. Then the COMMON_DATA
follows:
______________________________________________________________________
uint8 size; /* size of event in bytes */
uint8 type; /* type of this event */
uint8 focus; /* focus this is reported from */
uint8 device; /* who sent this */
uint32 time; /* timestamp */
______________________________________________________________________
Type is the type of event, which may be the following:
______________________________________________________________________
typedef enum ggi_event_type {
evNothing = 0, /* event is not valid. (must be zero) */
evCommand, /* report command/do action */
evBroadcast, /* notification of general interest */
evDeviceInfo, /* report input device information */
evRawData, /* raw data received from device */
evKeyPress, /* key has been pressed */
evKeyRelease, /* key has been released */
evKeyRepeat, /* automatically repeated keypress */
evKeyState, /* resynchronize keys state */
evPtrRelative, /* pointer movements reported relative */
evPtrAbsolute, /* pointer movements reported absolute */
evPtrButtonPress, /* pointer button pressed */
evPtrButtonRelease, /* pointer button released */
evPtrState, /* resynchronize pointer state */
evValRelative, /* valuator change (reported relative) */
evValAbsolute, /* valuator change (reported absolute) */
evValState, /* resynchronize valuator state */
evLast /* must be less than 33 */
} ggi_event_type;
______________________________________________________________________
Use the appropriate member from the ggi_event union to access the
event data, depending on the given type. See event.h and keyboard.h
for more details. For example:
______________________________________________________________________
ggi_event ev;
/* ... get the event... */
switch(ev.any.type) {
case evKeyPress:
case evKeyRepeat:
printf("Received key symbol: %x\n", ev.key.sym);
break;
case evKeyRelease:
/* ... ignore ... */ break;
case evPtrRelative:
case evPtrAbsolute:
printf("Moved the mouse! x: %d, y: %d\n",
ev.pmove.x, ev.pmove.y);
/* ... etc ... */
______________________________________________________________________
There is also ggi_event_mask which may be passed to various event
handling functions to indicate which types of events the program is
interested in. See ggi.h for possible masks.
3.10.1. *mask, struct timeval t); ggi_event_mask ggiEvent-
Poll(ggi_visual_t vis, ggi_event_mask
Check if any of the events given in EvMask is available. Using the
struct timeval *t, you can control the timeout, like select(2). Give
NULL to wait indefinitely or a filled out struct to wait for a given
time (which may be 0 to get non-blocking behaviour).
Return: a mask of events which are available (within the limits set by
the mask parameter).
3.10.2. mask); int ggiEventRead(ggi_visual_t vis, ggi_event *ev,
ggi_event_mask
Read an Event from the queue. This call blocks, if there is no such
Event present.
3.10.3. int ggiEventPut(ggi_event_buffer *buf, ggi_event *ev);
3.10.4. void ggiEventSkip(ggi_event_buffer *buf);
3.10.5. int ggiEventCopy(ggi_event_buffer *buf, ggi_event *ev);
These functions manage a simple circular buffer of ggi_events.
Although rarely used, they might be useful to applications requiring a
simple queue of received events. In order to use these, first allocate
and initialize ggi_event_buffer.
ggiEventPut() puts the given event in the buffer. ggiEventCopy()
copies the next event out of the buffer (though it will still be in
the buffer). Use ggiEventSkip() to skip an event.
3.11. Input and Output
LibGGI provides means to do basic graphical input and output.
The input portion consists of convenience keyboard functions based on
ggiEventPoll() and ggiEventRead(). If you need more complex things,
use the event interface directly, described in the previous section.
In graphics modes, ggiPutc() and ggiPuts() are limited to 8x8
character fonts; a future LibGGI2D extension will incorporate more
advanced text rendering.
3.11.1. int ggiKbhit(ggi_visual_t vis);
Checks if a key has been hit on the keyboard. This does not consume
the key. It is basically for easy porting of old DOS applications.
Return: 0 if no key has been received yet, otherwise there is a key to
be consumed.
DO NOT poll like this: do while( ! ggiKbhit(vis) );. On a multitasking
OS you are wasting ressources which could be available to other
processes. If you want to wait for a key, use the ggiGetc() call.
3.11.2. int ggiGetc(ggi_visual_t vis);
Gets a character from the keyboard. Blocks if no key is available.
Return: a 16-bit Unicode character.
As a simple heuristic, you may use 0xff to convert the return code to
ISO-Latin-1 for quickly porting an old application. Please do that
right later using the K_* symbols.
3.11.3. int ggiPutc(ggi_visual_t vis, int x, int y, char c);
Puts a single character on a graphical visual. This is only a very
simple routine using a fixed-width 8x8 font. See the libGGI-2D manual
for enhanced font rendering support.
Return: 0 for OK, otherwise an error code.
3.11.4. int ggiPuts(ggi_visual_t vis, int x, int y, const char *str);
Puts multiple characters at once. No special handling is applied to
control characters like CR or LF. You will see the associated glyph
being displayed, so do this yourself if you need.
Return: 0 for OK, otherwise an error code.
Don't use ggiPutc() repeatedly for multiple characters, as ggiPuts()
might be able to optimize the output of muliple chars at once.
4. LibGGI usage guidelines
4.1. Synchronous and asynchronous drawing modes
Some targets allow different modes with regard to when the screen is
updated and the actual drawing takes place.
The synchronous mode is default because it is what most programmers
expect: When the drawing command returns, it is already or will be
executed very shortly. So the visible effect is that everything is
drawn immediately. (It is not guaranteed in the strict sense that it
is already drawn when the function call returns, but almost.)
The asynchronous mode is (at least on the X) faster, but does not
guarantee that the command is executed immediately. To make sure that
all pending graphics operations are actually done and the screen is
updated, you need to call ggiFlush(myvis);.
(Side note: The screen refresh the X target does every 1/20 s can take
about half the execution time of a program. So syncronous mode can
really slow things down.)
As it stands now, all operations are guaranteed to be performed in the
order given in both modes. Reordering has been discussed but is
currently not done.
So the recommendation for all graphics applications is to set the
asynchronous mode. It will be far more efficient on some platforms and
will never be worse. (If asynchronous mode is not supported by a
target, setting it is still allowed, but has no effect.)
How to set up asynchronous mode?
o ggiSetInfoFlags(myvis,GGIFLAG_ASYNC); switches to asynchronous
mode,
o ggiFlush(myvis); updates the screen,
o ggiSetInfoFlags(myvis,GGIFLAG_SYNC); switches to synchronous mode.
4.2. LibGGI and threads
It is strongly discouraged to draw to one visual from two (or more)
different processes or threads at the same time. While serious damage
is unlikely, corrupted graphics output might happen and a big
performance penalty is almost guaranteed.
If drawing from two threads is needed, the application is required to
synchronise the threads so that not both draw simultanously.
(Side note: Some cards need some sort of paging for accessing their
video ram (banked framebuffer). Two threads accessing different parts
of the screen would require a page change on each process switch,
which could slow them down to a crawl. State information that has to
be reloaded to the accellerator engine after every switch would be
another example.)
4.3. Mixing LibGGI and direct frame buffer access
Some tricky spots have to be taken care of if you mix libggi calls and
direct access to the frame buffer. While direct access is done
immediately, execution of a libggi call might be delayed due to the
accellerator architecture, so framebuffer access immediately after an
accellerated libggi call could actually be executed before or while
the accellerator accesses the frame buffer itself, which worst case
could lock the card and give best case wrong pixels on the screen.
To make sure that all pending accellerator commands are executed, you
need to call ggiFlush(myvis); between a (series of) libGGI call(s) and
direct frame buffer access.
(Side note: ggiFlush(myvis); does more than just waiting for the
accellerator to finish pending jobs, so there will be another call
introduced for only this purpose. Stay tuned.)
5. LibGGI initialization example
A primitive tutorial on how to start using LibGGI, since many people
seem to have trouble with this. For more practical usage, please see
demo.c.
______________________________________________________________________
#include <ggi/libggi.h>
int main(void)
{
ggi_visual_t vis;
ggi_mode mode;
int err;
/* first initialize LibGGI */
ggiInit();
/* Get the default visual to work on */
vis = ggiOpen(NULL);
/* You can also do some drawing to memory, like this:
(don't forget the ending NULL!)
vis = ggiOpen("display-memory", NULL);
*/
if(vis==NULL) {
fprintf(stderr, "No visual!\n");
exit(1);
}
#ifdef AUTO_SELECT_MODE
/* This automatically selects the default mode */
err = ggiSetGraphMode(vis,
GGI_AUTO, GGI_AUTO, /* Visible resolution */
800, 600, /* Virtual: perhaps you'd like a large area to scroll around in */
GGI_AUTO); /* graphtype auto */
#else
/* OR you can select your own */
/* Check for mode: 320x200x8 320x200.
After this call, mode will now have our mode,
OR the suggested mode if our mode cannot be satisfied.
*/
ggiCheckGraphMode(vis, 320, 200, 320, 200, GT_8BIT,
&mode, NULL);
/* Now set the mode */
err = ggiSetMode(vis, &mode);
#endif
if(err) {
fprintf(stderr, "Cannot set mode!\n");
exit(2);
}
/* do some stuff */
ggiClose(vis);
ggiExit();
return 0;
}
______________________________________________________________________
6. API change log
6.1. Pending changes for the next release
o ggiDrawCircle is going to be moved to LibGGI2D. This gives
currently a warning. So relinking with -lggi2d will then be
necessary.
o ggiCrossBlit is most probably going to be moved to LibGGI2D as
well.
6.2. 1.3.0 to 1.4.0
o ggiMapColor takes now a pointer to a ggi_color struct as it's
second argument instead of the struct itself. This breaks binary
compatibility for about every program!
o ggi*Sprite functions, that had never been implemented, are removed.
o The ggi_mode struct members virt and visible now do contain the
number of pixels, not dots (which makes a difference for text mode.
One character is one pixel but e.g. 9x16 dots). It is unclear what
it had to contain before. So it might be a change, it might be a
bugfix.
7. Overview of how to write a LibGGI display target
[This article is NOT a substitute for not looking at the existing
display target code. This (unfinished) article does not explain every
detail (yet).]
Actually, most of the work of displaying and servicing an application
has been done for the display target writer. For example, there is
reasonably optimized code for linear framebuffers of most bit-types,
and stubs exist to do a few non-crucial operations such as ggiPutc()
by calling ggiSetPixel() individually. "Convenient" functions such as
ggi{Set/Check}{Text/Graph}Mode() simply call the "master" function,
ggi{Set/Check}Mode(), with different arguments, and the display target
writer only needs to supply "master" functions for these to work.
When the application requests a visual (by name), LibGGI looks it up
in its configuration file, and loads and initializes the appropriate
library. On initialization, the display target's GGIdlinit() is
called. This function should do whatever set up is necessary to begin
using the target, e.g. initializing Xlib in the display-X target. But
do NOT set the mode here yet. GGIdlinit() should also indicate which
functions it implements by setting the various fields of the operation
structs (the main one being opdisplay). (It does not have to
implement all of the possible functions; if it doesn't then stubs are
used or the function fails.)
The return codes for these functions are the same as if they were
called directly by LibGGI-using applications.
GGIsetmode() is used to set the mode. If it fails, GGIsetmode() should
suggest a mode, although this change may not be propagated back to
ggiSet{Text/Graph}Mode because they don't take a ggi_mode pointer.
GGIsetmode() implementations usually use _GGIdomode(), which sets up
"suggestions". This function is used to load any additional libraries
used by the target, such as the linear framebuffer libraries, and
stubs.
GGIgetmode() is used to check the mode. Usually the display target can
simply grab the mode stored in the given ggi_visual.
GGIcheckmode() is used to check if the mode in question is valid. If
not, the mode passed to it should be changed to the suggested mode.