This file is gl.doc, which documents the interfaces between gl.c and the gl-idev-* and gl-odev-* modules, and the gl-md-asm.h interface used by routines often done in assembly for speed. THE IDEV INTERFACE The gl-idev-* modules are input devices (keyboards). The interface here is the "struct idev" and some manifest constants defined by gl-int.h. The struct contains name and description strings and six function pointers. The name is a short name, the string that needs to be passed to -idev to explicitly specify this input device. The description is a long string, suitable for human printing. ("long" is relative; it should be no more than about 50 characters long.) The six function pointers are: int (*probe)(void); This function is called to see if the device exists and can be used, when no device was explicitly specified by the user. It should never print anything for errors that can normally result from the device simply not being there, or being the wrong type. (Errors for other causes, such as "can't malloc", it is reasonable for the idev module to complain about.) The return value must be zero if the device wasn't found or can't be used for a nonfatal reason, or nonzero if the device was found and is ready for use. This function is also expected to set the TICKS_PER_SEC global, though the idev module is not responsible for generating the clock ticks; this is arguably an interface botch. int (*setup)(void); This function is similar to probe, above, but is used when the user explicitly specified this device. Thus, for example, more complaints are reasonable, and there is no need to consider other possible modules that may want to run instead. It is legitimate (though not always desirable) to use the same function for probe and setup. void (*shutdown)(void); This function is called once, before exiting. None of the other functions are called before probe/setup or after shutdown. If the keyboard has multiple modes, for example, and it has to be in some unusual mode, the probe or setup function will have to set that mode; this is the place where it should be unset. This is also the place to close file descriptors, unmap memory buffers, or similar tasks, though they will often be unnecessary because the game is about to exit anyway, which usually does such things automatically. void (*setselect)(fd_set *, fd_set *, fd_set *, struct timeval *, struct timeval *); This function is called when the main game is about to do a select(). The first three arguments are the read, write, and execptional fd_sets that will be passed to select(); the fourth argument is the current time; the fifth is the timeout that is going to be passed to select(). This function is expected to add zero or more file descriptors to the fd_set arguments and/or decrease the timeout. It must not modify the current time value, increase the timeout, or remove any file descriptors from the fd_sets. int (*doselect)(fd_set *, fd_set *, fd_set *); This function is called after select() returns, when the game wishes to give the input device module a chance to accept input. The arguments are the read, write, and exceptional file descriptor masks from select(); the function is expected to check to see if input is available. If not, it should return 0 and do nothing else; if so, it should generate one or more input events (see below for how) and return nonzero. const char *(*keyname)(int); This function is passed a key number, in the encoding used by input events, and must return a short string describing the key, for use in text messages. (Examples of reasonable strings might be "Left shift" or "Control".) If the doselect function wants to generate an input event, it should do so by calling makeqentry(): void makeqentry(long int dev, short int val); dev is the GL device on which input is being generated. This will generally be either KEYBD or the specific key value (eg, SPACEKEY, F9KEY). The doselect function should decide which by testing TSTBIT(devqueued,KEYBD); if this returns true, dev should be KEYBD and val should be the ASCII character generated (such calls should be made only on keypresses; key releases should not generate KEYBD events). If the TSTBIT call returns false, dev should be one of the *KEY constants, and val should be 1 for a press, 0 for a release. It is acceptable to occasionally return 1 without generating any events, but this should be done only in situations such as keystrokes occurring but their not being of interest, since if it happens often the main loop will run inefficiently (and if it always happens, the main loop will not work correctly). If the event has a timestamp associated with it, the doselect function should call q_timers() before any calls to makeqentry(): void q_timers(struct timeval *t); The struct timeval indicates the time corresponding to the following makeqentry() call(s), if any. This should be done only if a more precise timestamp is available than "sometime after the last (*doselect)() call and before the current time". THE ODEV INTERFACE The gl-odev-* modules are output devices (screens). The interface here is the "struct odev" and some manifest constants defined by gl-int.h. The struct contains name and description strings and seven function pointers. The name and description strings are, as for the idev modules, short names and long names respectively. The function pointers are: int (*probe)(void); int (*setup)(void); void (*shutdown)(void); These have the same basic meaning as for the idev interface: probe and setup probe the framebuffer, returning nonzero if it's ready to go, zero if it isn't there, with the same distinction between them as for the idev probe and setup functions; shutdown shuts down anything that needs to be (for example, if the probe/setup function needs to install a specific colormap, it should save the previous one, and the shutdown function should retore the saved colormap). The probe/setup routine is expected to set three globals: XMAXSCREEN, YMAXSCREEN, and PIXELSCALE. The first two give the size of the screen in pixels (not, contrary to the implication of the names, the maximum on-screen coordinate value); the third gives an approximate scale value, based on the provided values NOMINAL_XSIZE and NOMINAL_YSIZE. (If the screen is NOMINAL_XSIZE by NOMINAL_YSIZE, PIXELSCALE should be set to 1; if the screen is smaller, PIXELSIZE should be less than 1; if larger, greater than 1. If the aspect ratio of the screen differs from the aspect ratio of the NOMINAL_* values by too much (where the exact definition of "too much" is an implementation judgement call), the odev module should display on only part of the screen, or rotate the display 90 degrees, or take some such compensatory measure. void (*clear)(void); This routine clears the drawing area to all-black. (See the (*swap)() function for more.) void (*swap)(void); This call indicates that the drawing of a frame is complete and that it should be displayed. To get less flicker, most framebuffer modules should draw into a memory shadow and copy it to the framebuffer only when this call is done. (Using a memory shadow may be unnecessary for multi-plane displays with modifiable colormaps, or displays with other forms of double-buffering; such displays should flip the colormap, or poke the double-buffering facility, here.) void (*line)(int, int, int, int); This draws a line. The arguments are x0,y0,x1,y1, and the line is between (x0,y0) and (x1,y1) inclusive. The coordinate system is expected to have (0,0) at the upper left corner, with X increasing to the right and Y increasing downward. If this is not the way the hardware works, the odev module is expected to compensate. void (*point)(int, int); This draws a single-pixel point. The arguments are X,Y, in the same coordinate system used by the (*line)() function. The odev module will usually call on the gl-md-asm.h interface. THE gl-md-asm.h INTERFACE Some routines used in the display modules will normally be written in assembly, for speed. A generic implementation of the interface is provided in gl-generic.c, for initial implementation testing; the Makefile will use this version if no assembly version is found for the machine. Not all machines will necessarily need all these routines, though if an assembly version is provided of any of them, assembly versions are expected for all that could possibly be used on that platform. This interface is not considered `cast in stone'; if a new port needs a new routine akin to these, there is no reason to not simply add it. The defined data interfaces consist of two structures, struct gl_fb1 and struct gl_fb8, describing 1-bit and 8-bit memory-mapped framebuffers, respectively (or memory shadow areas). These structures, which are functionally identical at present, consist of the following fields: unsigned char *vram; Pointer to the memory (mapped device memory, or shadow memory). If the module is using only part of the hardware's displayable area, this may not point to the beginning of the hardware's pixels. unsigned int stride; Number of bytes between the beginning of one scanline and the beginning of the next. (No, this is not always the smallest number of bytes needed to store xsize pixels; when using only part of the hardware's screen, xsize can be reduced below what the hardware supports, and for the sake of easy hardware design, some framebuffers have an "invisible" area, for which memory may or may not exist, after the end of each scanline and the beginning of the next.) unsigned int xsize; unsigned int ysize; The functional sizes of the screen, in pixels. unsigned int size; The total size of the framebuffer, in bytes. The functions are: extern void gl_clear_fb1(struct gl_fb1 *); This clears the framebuffer to all-0-pixels. extern void gl_copy_fb1_fb1_inverted(struct gl_fb1 *, struct gl_fb1 *); This copies from one fb1 to another, inverting all pixels as it goes. It compensates for differing stride values, using the smaller of the two framebuffer's sizes in each dimension. The first argument is the "from" framebuffer, the second the "to" framebuffer. (There is no non-inverting version of this at present, simply because no port has yet been done to one-bit hardware that displays 0 bits as black.) extern void gl_line_fb1(struct gl_fb1 *, int, int, int, int); This draws a line in the framebuffer. The last four arguments are as passed to the (*line)() odev method, and may fall outside the framebuffer. This routine is expected to do any necessary clipping of the line. extern void gl_point_fb1(struct gl_fb1 *, int, int); Draws a point in the framebuffer. The last two arguments are as passed to the (*point)() odev method, and may fall outside the framebuffer; if they do, nothing should be drawn. extern void gl_copy_fb1_fb8_bit(struct gl_fb1 *, struct gl_fb8 *, unsigned char); This copies from a one-bit framebuffer (usually a memory shadow) to an 8-bit framebuffer, affecting only one bit in each 8-bit destination pixel; the third argument specifies which bit to affect. extern void gl_copy_fb1_fb8(struct gl_fb1 *, struct gl_fb8 *); This copies from a one-bit framebuffer (usually a memory shadow) to an 8-bit framebuffer, expanding single-bit source pixels to eight-bit destination pixels (0 -> 0, 1 -> 255).