LibGGI Steve Cheng, elmert@ipoline.com $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 (,) is assigned a pixel number according to the following formula: = ( + )* + + In any case both and must not be negative, must be less than and must be less than . For top-left-origin screen coordinates, and will both be positive. For bottom-left-origin screen coordinates, and 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 of bits is stored per pixel. Bit of the pixel value for pixel number is stored in bit number * + of the buffer. Bit of byte in the buffer corresponds to bit *8 + 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() in is set if accesses with width have to be aligned. Read operations should be performed using the buffer and write operations should be performed using the 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() in is set if this accesses wit 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, being the value being read, and 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 and of either read or write buffers with != / / may be very expensive compared to successive accesses with == / /. 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 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=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 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.