Lean Programming Interfaces for Classic Graphics

This version of the document is dated 2026-02-17.

Notes on developing an application programming interface (API) for classic graphics with as few entry points as possible. As given in my specification for such graphics, classic graphics generally means 2-D or 3-D graphics achieved by video games before the year 2000 and the rise of programmable “shaders”. In general, pre-2000 graphics involve a game screen of 640 × 480 or smaller, up to 12,800 3-D polygons at a time (fewer if the game screen is smaller), and tile- or sprite-based 2-D graphics.

Goal: An Open-Source Engine or API for Classic Graphics

It would be of interest to write a free and open-source graphics engine that implements this specification and renders graphics in software (with a minimum of source code and dependencies),1 or to establish a lean API for this specification. The following are examples:

The graphics engine is intended to run even on computers from around 2005 (and maybe even on older computers), and with low resources, and so to enable video games that run with acceptable performance on those computers.

2-D Graphics

The simplest way to proceed is to give the application a frame buffer, a block of memory consisting of a rectangular array of pixel samples. The nature of each pixel sample depends on the game’s needs; for example, it may be an 8-bit index to a color palette, or a 16-bit color value. In this case, the application must render graphics in software.1

Note: Under the classic-graphics specification, the frame buffer has no more than 307,200 pixels.

A tile- and sprite-based API suggested by the classic-graphics specification is yet to be determined.

The following is a sketch of what could be included in a lean API for copying and stretching 2-D images as well as for geometric drawing.2 (For such an API, antialiasing support is optional, and it’s enough for the API to draw to images stored in system memory, and not also drawing to video memory or directly to the screen.)

A leaner API could provide for the following instead:

The following is not included in either API.

3-D Graphics

Unlike with today’s programmable “shaders”, classic 3-D video-game graphics support only simple, yet admirable capabilities for real-time rendering of three-dimensional scenes, as well as a low scene complexity (fewer than 20,000 triangles per frame).

This section gives suggestions for a lean API supporting 3-D graphics. Any implementation of it should render graphics in software1 and optionally with hardware acceleration. As with 2-D, the 3-D API is allowed to support drawing to images stored in system memory without also supporting drawing to video memory or directly to the screen.

The classic-graphics specification recognizes the following 3-D graphics capabilities as within its spirit:

The PC 99 System Design Guide sections 14.27 to 14.34 (except for the screen resolution, frame rate, and double buffering requirements) are also in scope.

Stencil buffers, bump mapping, environment mapping, and three- or four-texture blending are borderline capabilities.

Suggested C-Language Functions

DrawTrianglesOneTex(State3D *state, float* vertices, uint32_t numvertices,
   uint32_t * indices, uint32_t numindices, Texture *texture);

Draws a sequence of triangles. The vertices array is a rectangular array of numbers organized into “vertex blocks”. The number of floats pointed to must equal the number of floats per vertex block times numvertices. The number of indices (numindices) must be a multiple of 3. float is a number in IEEE 754 binary32 format.

Note: As given in the classic graphics specification, the number of vertices per frame should be no more than 38,400 for a screen resolution of 640 × 480.

There are several possibilities for “vertex blocks”:

state is the 3-D graphics state, to be determined. This state will include the game’s frame buffer, and possibly additional parameters yet to be determined.

texture is information about the texture, to be determined. This information will include the texture’s image data and a blending operation (add, modulate, etc.).

DrawTriangleFanOneTex(State3D *state, float* vertices, uint32_t numvertices, Texture *texture);

Draws a triangle fan. vertices, texture, and numvertices are as in DrawTrianglesOneTex. Moreover, numvertices must be 3 or greater.

DrawTriangleStripOneTex(State3D *state, float* vertices, uint32_t numvertices, Texture *texture);

Draws a triangle strip. vertices, texture, and numvertices are as in DrawTrianglesOneTex. Moreover, numvertices must be 3 or greater.

DrawTrianglesTwoTex(State3D *state, float* vertices, uint32_t numvertices,
   uint32_t * indices, uint32_t numindices, Texture *texture1, Texture *texture2);
DrawTriangleFanTwoTex(State3D *state, float* vertices, uint32_t numvertices,
   Texture *texture1, Texture *texture2);
DrawTriangleStripTwoTex(State3D *state, float* vertices, uint32_t numvertices,
   Texture *texture1, Texture *texture2);

Like the corresponding ...OneTex versions, with the following exceptions. Each vertex block has ten float values: the x-, y-, and z-coordinates; the normal vector’s X, Y, and Z components; the texture coordinates (U and V) for texture1; and the texture coordinates for texture2. These functions are suggested here because some games from the late 1990s rely on so-called light-map textures and two-texture blending rather than in-game lighting calculations.

DrawTriangles(State3D *state, float* vertices, uint32_t numvertices,
   uint32_t * indices, uint32_t numindices);
DrawTriangleFan(State3D *state, float* vertices, uint32_t numvertices);
DrawTriangleStrip(State3D *state, float* vertices, uint32_t numvertices);

Draws triangles without textures. Like the corresponding ...OneTex versions, with the following exceptions. Each “vertex block” has six float values: the x-, y-, and z-coordinates; and a color’s red, green, and blue values, each ranging from 0 through 1. This is for games that render only triangles without textures. These functions are suggested here because some games from the mid- to late 1990s often draw polygons without textures.

This is far from a complete list of useful 3-D drawing functions; there may be others, but the goal is to define a compact set of functions supporting only those 3-D capabilities actually used by video games in the 1990s and earlier.

Further Reading

License

Any copyright to this page is released to the Public Domain. In case this is not possible, this page is also licensed under Creative Commons Zero.

Notes

  1. In this document, “rendering in software” means that the rendering of graphics does not rely on a video card, a graphics accelerator chip, or the operating system’s graphics API (such as GDI, OpenGL, or Direct3D) with the sole exception of sending a finished game screen image to the player’s display (such as through GDI’s StretchDIBits or copying to VGA’s video memory).
    The following are examples of a graphics library that follows the spirit, even if not the letter, of the classic-graphics specification: Tilengine, kit, DOS-like, raylib’s rlsw software renderer. Michal Strehovský published an interesting technique to create small game applications, and so did Jani Peltonen 2 3

  2. It is unclear whether to include any of the following in the lean 2-D graphics API; this will depend on how heavily pre-2000 or pre-1995 games made use of them.

    a. 2-D affine transformations (which keep parallel lines parallel; examples are scalings, shears, and rotations).
    b. Translations, rotations and scalings, but no other 2-D affine transformations.
    c. Translations, rotations, scalings, and reflections, but no other 2-D affine transformations.
    d. So-called “raster operations” (bit-by-bit operations between two images), such as those found in the Windows API (for example, BitBit) and in the “Microsoft C runtime” (for example, _putimage, _setwritemode).
    e. Alpha compositing while drawing 2-D graphics.
    f. Drawing one image part over another (Porter and Duff’s “over” operation), with optional translucency, but no other nontrivial alpha compositing. The images may have translucent (semitransparent) or transparent pixels.
    g. Same as (f), except the images can’t have translucent pixels.
    h. Drawing dashed 2-D paths.
    i. Filling rectangular areas with a repeating image pattern without transparent or translucent pixels.
    j. Filling rectangular areas with a repeating image pattern. The image may have transparent pixels.
    k. Filling arbitrary 2-D paths with a repeating image pattern. The image may have transparent pixels.

    Note on points “a” to “c”: Unlike the Windows NT GDI, the 16-bit GDI did not support arbitrary affine transformations; notably, it did not support rotations or shears (for references, see “Further Reading”).
    Note on point “h”: Dashed 2-D paths are also known as “styled lines”. One-unit-thick outlines are dashed (“styled”) by leaving out pixels (examples include the _getlinestyle method of the “Microsoft C runtime”, or the sophisticated approach in the Windows NT GDI [see “Further Reading”] that depends on the horizontal and vertical spacing between pixels). More general outlines of a 2-D path are dashed by leaving out parts of the path (an example is found in Windows NT’s model).
    Note on points “i” to “k”: Windows’s CreatePatternBrush function for creating repeating image patterns supported only 8 × 8 or smaller images in Windows 95 and Windows 3.x. 

  3. Here, a “unit” means the spacing between an image’s pixels. Thicker outlines can be drawn by approximating the 2-D path with line segments, then drawing filled circles around each segment’s endpoints, then drawing filled rectangles that follow the path of each line segment. Thus, a lean graphics API need not support outlining paths thicker than one unit. See also Ron Gery, “Primitive Cool”, Microsoft Developer Network, Mar. 17, 1992. 

  4. A “monotone-vertical” polygon is one that changes direction along the y-axis exactly twice, whether or not the polygon is self-intersecting. Every convex polygon is monotone-vertical. See chapter 41 of Michael Abrash’s Graphics Programming Black Book Special Edition, 1997. 

  5. Elliptical arcs were not supported in Windows CE version 2.0 and earlier, unlike Windows 95, Windows NT, and Windows 3.x. See Jon Christiansen, “Microsoft Windows CE Graphics Features”. 

  6. Text rendering is essentially the drawing of glyphs of text. A glyph is a writing element of a font; examples are letters, digits, the i’s dot, and the f-f-l combination. Before 2000, there were three kinds of fonts for screen display: raster fonts (the glyphs are images); vector fonts (the glyphs are made of line segments and/or curves; Hershey, Modern, and Script are examples); and outline fonts (the glyphs are filled 2-D paths; TrueType fonts are examples). (Later developments saw (1) the addition of scalable colored graphics, especially emoji, to outline fonts and (2) “subpixel” antialiasing of glyphs, such as the ClearType technology announced in November 1998.) The conversion of text to glyphs and the positioning of such glyphs is often nontrivial.