Device-Independent Engine Features

This section discusses in the capabilities and implementation of Nano-X' core graphics engine in detail. It's purpose is both educational and to allow extending an API by understanding how the engine works.

Graphics Engine Features and Implementation

These routines concern themselves with drawing operations to off-screen or screen surfaces. Each routine starts with Gd... and is passed a pointer to the SCREENDEVICE structure (PSD) as it's first parameter. The PSD parameter specifies the low level display details, like the x, y size of the device and the color model used by the hardware, for example. In addition, the actual routines to perform drawing are function pointers in this structure. All screen coordinates are of type COORD, and specified in device coordinates, that is, offsets from the upper left corner of the screen.

Colors are always specified as an RGB COLORVAL value into the graphics engine. They are then possibly converted to palette indices and sent to the display hardware as PIXELVAL values. In the case of 32bpp truecolor displays, no conversion is required. The color model will be discussed in detail below.

Regions

Regions are used to describe arbitrary sets of pixels on the screen. A simple, square region of pixels can be described by a single rectangle. More complex sets of pixels require more complex data structures. In Nano-X, regions are described by an array of non-overlapping rectangles. Currently, there are two different implementations of regions in Nano-X, the original design for systems with limited memory, and a new design with dynamically allocated sets of rectangles. The original design used a single static array of MWCLIPRECTs to describe complex regions. Any point within any rectangle in the array was considered to be in the region. The array wasn't sorted in any particular order, but was always guaranteed to contain non-overlapping rectangles. Another global variable, clipcount, specified the number of rectangles in the array. This original design had no engine entry points for region management, the entire array was passed to the clipping functions, described below.

In the new design set with #define DYNAMICREGIONS 1, any number of regions can be created, as the regions (MWCLIPREGION *) are stored as dynamically allocated arrays of rectangles. In this implementation, the non-overlapping rectangles are always kept in "y-x" sorted bands, such that each band's y height is the same for all rectangles in the band. This means that only the x position and width of the rectangles in each band varied. Because of this, it is easier to create a set of functions for combining regions, since effectively only a single dimension had to be compared for each region operation. The new region handling routines allow for creating and destroying regions, as well as combining rectangles and regions with regions using Intersection, Union, Subtraction, and Exclusive Or. This model allows regions to be implemented apart from the clipping routines, unlike the first version. Following are the new region routines:

Table 6-2. Region Routines

FunctionDescription
GdAllocRegionCreate a region.
GdAllocRectRegionCreate a rectangular region from left,top,right,bottom.
GdAllocRectRegionIndirectCreate a rectanglular region from a MWRECT.
GdSetRectRegionSet a region to a single rectangle.
GdDestroyRegionDestroy a region.
GdCopyRegionCopy a region.
GdUnionRectWithRegionUnion a rectangle with a region.
GdIntersectRegionCreate a region from the intersection of two regions.
GdSubtractRegionCreate a region from the difference of two regions.
GdUnionRegionCreate a region from the union of two regions.
GdXorRegionCreate a region from the XOR of two regions.

Clipping

Clipping in Nano-X is closely tied to the region management code. At any point in time, the graphics engine has a single clipping region, that is a set of rectangles, defined for any graphics operation. A point is drawn if it is "inside" any of the current set of clip rectangles. Two slightly modified versions of the clipping algorithm are supplied, devclip1.c for the original, static rectangle array, and devclip2.c for the newer dynamically allocated array. A single entry point GdSetClipRects, takes the passed region and specifies it's use for all subsequent graphics operations. All the drawing routines then use the two additional routines to determine whether or not to draw. GdClipPoint takes an x,y point in screen coordinates and returns TRUE if the point can be drawn, that is, the point is within one of the region rectangles. GdClipArea takes an upper left and lower right point, and returns one of the following: CLIP_VISIBLE, if the specified area is completely within the region, CLIP_INVISIBLE, if the area is completely not in the region, which means that no drawing should be performed, or CLIP_PARTIAL, if a part but not the whole area is within the region. A typical graphics primitive will call the screen driver with unmodified passed inputs if CLIP_VISIBLE is returned, or return if CLIP_INIVISIBLE is returned. In the CLIP_PARTIAL case, the primitive must break up the original request into areas that are within the clip region before calling the screen driver. This slows down the operation considerably.

Because the clipping code is called constantly before drawing operations, Nano-X keeps a global cache rectangle of the last rectangle checked with GdClipArea, for speed and also to allow the mid level to quickly calculate how partial drawing lengths.

Line Drawing

Line drawing is the simplest of graphics operations. Nano-X supports GdPoint to draw a point, and GdLine to draw a horizontal, vertical or diagonal (using Bresenham algorithm) line. Just before any call to the screen driver, a call to GdCheckCursor assures that the software cursor is removed prior to drawing. GdFixCursor restores the cursor if previously visible.

There is a tricky part to line drawing that had to be added during the support for multiple API's. This has to do with whether or not the last point in specified line segment is drawn or not. There are two schools of thought on this, and to make it short, Nano-X supports both of them. The last parameter to GdLine specifies whether or not to draw the last point. The Nano-X API doesn't draw the last point, but the Nano-X API does.

Most drawing functions, including line drawing draw using the "current" foreground color, specified using GdSetForeground. In addition a drawing mode, currently either MODE_SET or MODE_XOR can be specified using GdSetMode.

Rectangles, Circles, Ellipses

Rectangles, circles and ellipses are drawn using the GdRect and GdEllipse routines. A circle is an ellipse with the same x and y radius. As with lines, rectangles and ellipses are drawn using the current foreground color and mode.

Polygons

Nano-X supports polygon drawing by specifying an array of x,y points. The points are then connected using the GdLine function. The current foreground color, drawing mode, and clip region is used during output.

Area Fills

Nano-X supports filled rectangular areas using the GdFillRect function. The rectangle's outline and contents are filled using the current foreground color. Filled circles and ellipses are performed with GdFillEllipse, and polygon fills with GdFillPoly. Area filling is implemented through successive calls to the DrawHorzLine in the screen driver, and are much faster if fully not clipped.

Fonts

Both fixed pitch and proportional fonts are supported in Nano-X. Because of potentially large differences in display hardware, the actual font format is known only to the screen driver, although a set of standard functions are supplied for dealing with converted .bdf fonts and Microsoft Windows fonts, if you have a license. The engine function GdSetFont specifies a font number that is passed to the driver and used to index a static array of linked in fonts. Screen driver entry points GetTextSize return the font height and width for a passed string, and GetTextBits returns an individual character bitmap. The engine layer uses these values to calculate a clipping region for text drawing, as well as to draw the character as a monochrome bitmap.

The screen drivers currently supplied implement both fixed pitch PC ROM based fonts, as well as a proportional font format that is linked into the screen driver. A few conversion programs allow conversion of fonts from different formats to the driver format. Bdftobogl converts X Window System .bdf files to Nano-X format. Convfnt32 converts raster and truetype Microsoft Windows fonts, if you have a license, to Nano-X format. Convrom converts PC ROM bios fonts.

A number of free fonts are supplied with the system, a heavier proportional 14x16 system font, and a sans-serif 11x13 font for title bar and edit box displays. Any number of fonts can be linked into the system, and it's fairly easy to dynamically load fonts if one writes the routines for it.

Text Drawing

Text output is performed by first selecting the desired font with GdSetFont, and then calling the GdText function. Full text clipping is performed, although currently there is no "fast" text output entry point in the screen driver, so each character bitmap is grabbed using the GetTextBits entrypoint and then drawn using Drawpixel. While this will have to remain the same for partially clipped text, a screen driver entry point to draw fast text will probably be required shortly.

Text is drawn using the current foreground color. The background is drawn if the current "use background" mode set via GdUseBackground is TRUE. In this case the background is drawn using the current background color set via GdSetBackground. The GdText function also takes a bottomAlign parameter that specifies whether the text is to be bottom or top aligned, to help with differing API's.

Color model and palettes

The Nano-X graphics engine requires all colors to be specified as either 24-bit RGB color values, or in rare cases, as palette indices for speed. The palette index method will only work on systems that have hardware palettes, so it's not recommended. All of the upper-level color parameters are specified to the engine routines using a COLORVAL value, which is a long containing the desired RGB color, created using the RGB() macro. The engine then converts the COLORVAL to a PIXELVAL value, which is normally a long also, but on some smaller systems can be compiled as an unsigned char. The PIXELVAL value is the actual value passed to any screen driver entry point requiring a color. So the mid level routines all work with RGB COLORVALs, while the device driver routines all work with PIXELVALs. The graphics engine converts these values using two routines, GdFindColor and GdFindNearestColor, described below.

GdFindColor takes a hardware independent RGB value and converts it to a hardware dependent PIXELVAL pixel value. In the case of 32bpp display drivers, no conversion is required. Otherwise for truecolor systems, Nano-X converts the RGB value to a 5/5/5 15-bit or 5/6/5 16 bit truecolor value. For 8bpp truecolor displays, the RGB value is converted to 3/3/2. For palletized displays, the GdFindNearestColor function is called to convert the RGB color to the nearest palette index in the current system palette. GdFindNearestColor uses a weighted distance-cubed method to find the palette value nearest to the requested color, and returns it. Standard palettes for 1, 2, 4 and 8bpp are included in the files devpal1.c, devpal2.c, devpal4.c and devpal8.c. These palettes associate an RGB value with an index, but may be overwritten.

The GdSetPalette function determines whether there are any free entries in the system palette (discussed shortly) and if so, adds entries to the system palette, and calls the screen driver SetPalette entry point to set the hardware palette. There is a single global variable, gr_firstuserpalentry, that contains the index of the next available system palette entry. Initially, this is set to 24. Thus, systems with less than 24 total palette entries will never have an available palette entry to remap. On systems that do, like 256 color systems, then images requiring more color entries keep calling GdSetPalette until the system palette is full. To reset marker, the function GdResetPalette is called. This allows upper level API's to distinguish between different images and force the system palette to be rewritten.

Image Drawing

Nano-X supports two styles of images, monochrome and palettized. Monochrome images are specified with an IMAGEBITS structure, which is an array of words with 1 bits specifying the foreground color and 0 bits the background. The IMAGEBITS bits are short-word padded to the width of the bitmap. The GdBitmap routine draws monochrome bitmaps, similar to GdText, by drawing all the 1 bits in the foreground color, and the 0 bits in the background color if the "use background" set by GdUseBackground is TRUE.

Color bitmaps are specified using a 1, 4 or 8bpp image palette, and an array of indices into this palette, all stuffed into an IMAGEHDR structure, and drawn via GdDrawImage. First, the system creates a conversion palette by calling GdMakePaletteConversionTable, which converts the images' palette entries into system indices. At the same time, the system attempts to increase the system palette if necessary by calling the GdSetPalette function described above. At the end of this operation, the image has a converted palette which necessarily corresponds to the system palette. In the case of truecolor hardware, the image's palette entries are converted to hardware truecolor pixel values, and output directly.

After converting the image color entries the GdDrawImage determines whether the image is clipped, and outputs the image, pixel by pixel. In the future, a blitting routine could be used for faster image drawing.

Blitting

Blitting functionality is required in the screen driver for offscreen drawing capability, discussed earlier in the screen drivers section. The engine function GdBlit allows upper level APIs to implement copy operations from offscreen memory to the display, or vice versa. The blit format is driver specific, and generally only works for memory images created by the screen driver during runtime. The upper level APIs implement this by allocating a new SCREENDRIVER structure, copying an existing SCREENDRIVER structure into it, replacing the address field with a malloc()'d value, and setting the PSF_MEMORY bit, which indicates to the display driver that this is now an offscreen surface. Any subsequent calls to the engine routines then draw onto this surface. When it is desired to copy the offscreen surface back to the physical display, the GdBlit routine is called. Currently, only SRCCOPY operations are performed, but future plans will add blitting opcodes.

The function GdCalcMemGCAlloc calculates the byte size and line length (pitch) of an offscreen memory area given the passed bpp and planes parameters. This is before calling the screen driver to allocate an offscreen screen device.