A Voodoo of our own
2026-10-23, the first piece of the wrapper.

WPOD3DFX.EXE, the 3dfx build, in a race on Beltane at 1280 × 960 (twice 640 × 480), drawn by our own glide.dll. Shown here at 960 pixels wide.
The plan puts a frame around the original game: run every build unchanged, and give each one modern rendering. The 3dfx build was the obvious start. It hands every triangle and texture to one library, so replacing the library is enough to change how the game is drawn, as nGlide and dgVoodoo do for other Glide games.
What the game asks of the card
First the list. WPOD3DFX.EXE imports 47 functions, from a file called glide.dll. It does not use glide2x.dll, the name later Glide 2 games use. Their names carry the __stdcall decoration (_grGlideInit@0). They are the early Glide 2 calls: the frame buffer is opened with grLfbBegin and grLfbGetWritePtr, and grSstOpen takes no window, because a Voodoo drew full screen over the desktop.
A scan of all 360 call sites read the constant pushed before each call. That gave the game's whole use of the card:
- 60-byte vertices and one texture unit;
- RGB565 and 8-bit palettised textures;
- a W depth buffer and the general colour combiner, with the light carried in the vertex alpha;
- table fog, a black chroma key and alpha blending;
- gamma 1.4 in the menus and 1.0 in the race.
2D pictures are written straight into the frame buffer, 1024 pixels to a row. The lens flare reads the depth buffer back: the sun shows only where nothing was drawn. The details, with an address for each, are in 3D cards.
The library
The first decision was the language. A DLL in C, built with zig, needed no downloads, but it would have meant writing the texture decoding again. A DLL in Rust could use the project's gpu-1997 crate, and it needed Rust's 32-bit Windows target (about 30 MB). The VM also needed 32-bit OpenGL for Wine (Mesa, about 100 MB). Both downloads were asked for and approved.
wrapper/glide is written in Rust, built for 32-bit Windows, and linked by zig, so no MinGW is needed. That took four tries:
- zig refused rustc's GCC-only linker arguments;
- it had no
msvcrtto link; - then the unwinding symbols were missing;
- then rustc's startup object wanted
__register_frame_info.
A small linker script now filters all of that. Rust also exported the names without their decoration (grGlideInit, not _grGlideInit@0), so the script swaps in our own export list. The DLL exports exactly those 47 names (observed, its export table).
The game calls Glide from more than one thread (the loading screen draws behind a semaphore), and an OpenGL context belongs to one thread. So every call only records a command, and one render thread replays the commands in order:
- the Voodoo's pixel pipeline is one fragment shader: the combiners, the chroma key, and fog looked up by w the way the card did;
- textures are decoded with the project's
gpu-1997crate; - the frame buffer is drawn at the output size, two colour buffers and one depth buffer, as Glide has.
Size is a setting: GLIDE_SCALE=2, 4, or any size with GLIDE_SIZE.

The main menu at twice the size. It is a 2D picture the game writes into the frame buffer itself. Gamma 1.4 is applied on the way out, as the Voodoo's output table did.
Getting it to run
The first run stopped at once. Each stop, in order:
- The Cyrix test again. The 3dfx build crashed at once with "privileged instruction at 004897ed". It is the same Cyrix CPU test that stopped the software build, which talks to the processor through I/O ports. Windows 95 allowed that, and Wine does not. This time the EXE stays unpatched. The library installs an exception handler when it loads, which steps over those instructions and answers 0xff, as a PC without a Cyrix chip would.
- No window yet. The game opens Glide before it shows its window, so the first
grSstOpenfound nothing and the library made a window of its own. The game's window ("Fenetre Pod") came later and was hidden behind it. Now the library accepts the game's window even while it is hidden, and moves its drawing there when the window appears. - The sound card. The VM has none, and the game asks, as the software build does. "Ignorer" carries on without sound effects.
- A black screen while the game hands the screen back to Windows (
grSstPassthruMode(1)) for its videos and dialogs. It passes. - The mode comes after the pointer. The game takes the frame-buffer pointer first and says whether it will write 16-bit or 32-bit pixels afterwards. So the library hands out a buffer filled with a marker value, and reads back whatever changed when the game is done.
Then the menus worked: the main menu, the one-player menu, the track choice and the car screen, all at 1280 × 960 (measured, the captures below).

The track choice at 1280 × 960. The map is a 2D picture; the car screen below draws its car in 3D through the library.

Driving the menus to a race
Reaching the race took longer than drawing it.
- Keys fell between frames. In the VM the menus ran at 1.6 frames a second, and a key pressed for 0.08 s was gone before the game looked. Holding each key for half a second, then two seconds, fixed the menus.
- Enter does nothing on the car screen. The OK button is lit, but Enter does not press it, and the "up" presses meant for the race then moved the focus: one run picked the SABER car and stayed there. The script now moves the game's own cursor onto OK and clicks. The game reads the mouse as relative motion through DirectInput, so the move had to be found by trying: at 1.6 frames a second one unit of motion moved the cursor about two pixels.

A scripted run that never left the car screen: Enter did nothing, and the presses that followed moved the focus between buttons.
- The race at 2×. With the click, the race on Beltane started at 1280 × 960 (measured: the capture at the top of this entry). The race ran at one to two frames a second.

Later in the same race: the car (with "up" held and no steering) against the wall, the race order and lap timer in the corners.
Speed, and a shared machine
The library logs frames per second and where the time goes. At the menu it was busy all of the time: about 200 ms per frame for the 2D picture and 310 ms for showing the frame, and nothing for triangles (measured). The cost is Mesa's software OpenGL running on an emulated CPU. Three changes helped: llvmpipe's 128-bit code instead of its 256-bit (AVX2) code, which QEMU emulates slowly; a plain copy instead of a shader pass when gamma is 1; and the 2D picture updated in place. One measurement showed twice the speed, but the next one did not. The numbers were noisy, because the software build was running on the same machine at 130–150 % CPU for another session.
The machine was shared in a worse way too. Twice the game died in the middle of a run with "X connection to :2 broken". Another session's stop script ran a bare pkill Xvfb, which ends every X display in the VM. That script now stops only its own display (:1). The library's runs start their X server under a name of their own, which no pkill Xvfb matches.
Four times
At 4× the menus ran at 0.4 frames a second, and the game's cursor barely moved: one run stopped on the car screen with the cursor half-way to OK. The game opens the card again for each race, so a setting read at each opening (GLIDE_SCALE_FILE) lets the menus run at 1× and the race open at 4×. In the one try, the cursor stopped on the button above OK and would not move on, so the 4× race was not reached.

The menus at 1× in the corner of a display sized for 4×, waiting for the race to open large. The race did not start.

The same part of the car screen at 1×, 2× and 4×, each shown at the same size (the car turns between captures). The 1× picture is blown up from its own pixels; the 2× and 4× ones are drawn with more pixels, so the car's edges and textures are finer.

The main menu at the original 640 × 480, through the same library.
How it is known
- The list of calls and their arguments: observed in the import table and at the call sites.
- That it draws the menus, the car and a race: measured, with the captures above, in the emulated PC (Debian 13, Wine 10, Mesa's software OpenGL). The race at 2×; the menus and the car screen at 1×, 2× and 4×.
- The frame rates and where the time goes: measured, the library's own log, but noisy (a second game shared the CPU).
- That the game's window appears after its first
grSstOpen, and that Enter does nothing on the car screen: observed in runs. - That a PC without a Cyrix chip reads 0xff on ports 0x22 and 0x23: inferred.
- Whether every combiner and fog setting matches a real Voodoo: open. No frame has been compared yet with a real card or with another Glide implementation.
What is next
- Speed. In the emulated PC, OpenGL runs in software (llvmpipe) on an emulated CPU. The menus run at about 5 frames a second at 1×, 2 at 2× and 0.4 at 4×, and the race at 1 to 2 at 2× (measured, the library's own log). On a host with a real GPU none of this would be the limit.
- A race at 4×: the cursor needs a dependable way onto OK at low frame rates.
- Comparing frames with a reference Glide for the combiner, fog and chroma key.
- The same back end for the ATI and S3 builds.