Posted March 20, 2026 by Novus Idea
#Dos #VGA #Retroengine #3d #ModeX
Since the last devlog, CyberVGA has moved beyond being a renderer prototype and is starting to resemble a complete engine.
The largest addition is a built-in editor for cell-and-portal worlds, but a substantial amount of work has also gone into lighting, mesh formats, rasterization, input handling, and the fixed-point support code underneath the entire pipeline.
More than 12,000 new lines of C89 have been added since the winter update. As expected, the bug surface has expanded with it.
The main addition in this update is CyberEdit (CEDIT.C), a built-in world editor running entirely inside VGA Mode X at 320×240.
It renders its own 2D editing views through planar VGA memory, uses the same rendering backend as the engine itself, and makes it possible to build worlds without relying on external tooling or direct data editing.
The editor currently provides a top-down wireframe view of the portal/cell world with mouse-driven interaction. Vertices can be selected and dragged with snapping, faces can be selected for portal linking, cells can be created and deleted, and the viewport can be zoomed and panned. There is also a toggleable info window and an exit confirmation dialog, added for the usual reason.
Worlds are serialized to a binary .CEL format and can be saved to or loaded from disk. Point lights can be placed, adjusted, and removed directly in the editor on a per-cell basis. Because the editor shares the runtime rendering pipeline, authored worlds can be previewed in textured 3D immediately.
At roughly 2,500 lines, CyberEdit is the single largest feature addition to the codebase so far.
CyberVGA now supports point lights.
Lights belong to cells, store position and emit level, and are saved as part of the world data. Lighting is currently evaluated per vertex with distance attenuation, producing an intensity term that modulates indexed texture colors during rasterization.
Because the renderer targets an 8-bit palette, shading immediately exposed the expected banding problems. To reduce this, I added a 4×4 Bayer dithering matrix to the shading path. In practice, this breaks broad color bands into a regular stipple pattern that reads as smoother gradation at Mode X resolution.
That result is much closer to the intended visual target for the engine: limited precision used deliberately, not disguised.
The older CVG1 mesh format has now been retired and replaced with CMS (CyberMesh).
CVG1 was useful as a first test format, but CMS is a proper binary mesh container designed around the current renderer and toolchain. It stores positions, normals, UV coordinates, triangle indices, and submesh definitions, all in Q16.16 fixed-point.
Export from Blender is handled by THIRD/export_cms/export_cms.py, which performs axis remapping, coordinate conversion, and fixed-point encoding during export.
The old CVG1 loader has been removed entirely, along with the assets that depended on it. There is no compatibility layer and no migration path. On a project at this stage, that is an advantage.
A large share of this update went into renderer correctness and throughput.
Quad tessellation now uses bilinear interpolation when subdividing quads into triangle fans. UVs are interpolated correctly across the generated geometry, which removes the worst texture warping artifacts on large oblique surfaces.
This matters because affine texture mapping only remains convincing when the polygons are kept under control. Once surfaces become too large, the distortion becomes obvious quickly. Subdividing the geometry is the simplest practical correction within the constraints of this renderer.
Perspective correction has also been optimized by replacing inner-loop divisions with multiplications against a reciprocal lookup table.
On a 486-class target, division is costly enough that removing it from hot paths produces an immediate improvement. The current reciprocal table covers the useful denominator range for the renderer and has noticeably improved scanline throughput.
One long-standing seam bug was finally identified and fixed. A -1 bias on clip_maxy in all three rasterizers, triangle, quad, and wall, was introducing single-pixel gaps between adjacent polygons.
The fix was a one-line change in each file. The bug had been present since the first version of the renderer.
The dedicated wall renderer has now been removed, and its responsibilities have been absorbed into the quad pipeline.
This simplifies the renderer architecture and removes a distinction that was no longer useful. Related to that, the old Wall structure has been replaced by Texture, which better reflects what it actually represents.
The Camera struct now includes principal point attributes (cx, cy), so the projection center no longer has to be hardcoded to the viewport midpoint.
This was necessary for the editor’s split-view rendering and opens the way for more flexible projection setups later.
The fixed-point math layer has also expanded substantially. The library now includes ceiling and floor for Q16.16 values, 2D and 3D vector length and distance functions, improved trigonometric table generation, and more robust multiply/divide support in the NASM layer.
The type system was cleaned up as well. The older sint and uint aliases are gone, replaced consistently with i32 and u32 across the codebase.
The input system was rewritten to support multiple mouse modes cleanly, including camera look, editor interaction, and UI control.
Keyboard handling was also simplified by removing older DOS-specific logic in favor of explicit state tracking. Mouse sensitivity is now scaled against delta time, so camera movement remains consistent across varying frame rates.
On the audio side, Sound Blaster initialization is now handled with configurable base port, IRQ, DMA channel, and sample rate. DSP setup is more robust, and initialization failures are now reported instead of failing silently.
The HRMNAT palette has been revised into a structured 256-color layout organized as an 8×32 ramp system, with dedicated regions for grays, warm tones, cool tones, and accent colors.
I also implemented palette remapping support for textures, allowing a texture to be shifted into different palette regions without duplicating the source data. That makes controlled variation much cheaper, which fits naturally with indexed-color rendering.
CyberVGA is approaching the point where it can represent a complete small environment: rooms connected by portals, lit by authored point lights, textured with mapped geometry, and populated with mesh objects exported from Blender.
The editor changes that process from something barely manageable into something I can iterate with directly.
The next major areas of work are likely to be tighter portal clipping, more robust world-space mesh rendering, continued renderer cleanup, and eventually something moving on screen that is not just the camera.
CyberVGA remains a 32-bit DOS engine written in C89 and NASM, targeting VGA hardware through DPMI. It avoids floating point in hot paths and continues to be built around fixed-point arithmetic, explicit memory management, and hardware-era constraints.
It is not practical.
That is still not the point. :)
Abrash, Michael. Michael Abrash's Graphics Programming Black Book, Special Edition. The Coriolis Group, 1997.
LaMothe, André. Tricks of the Game Programming Gurus. Sams Publishing, 1994.
DJGPP — DJ Delorie’s GCC port for DOS
NASM — The Netwide Assembler