How Half-Life’s BSP Renderer Was Reworked for the Dreamcast
At a glance, the Dreamcast port of Half-Life still renders the world the same way as the PC build: determine the current BSP leaf, decompress its PVS, walk the visible BSP nodes, reject geometry outside the frustum, collect visible surfaces, and draw them grouped by texture.
Underneath that familiar structure, however, the Dreamcast renderer is substantially different. The port trades the PC renderer’s generous memory layouts and OpenGL immediate-mode drawing for compact structures, shared data, explicit batching, native texture formats, and carefully managed rendering passes.
The result is not a new renderer so much as a Dreamcast-specific interpretation of GoldSrc’s renderer.
The BSP algorithm survived; its data did not
The high-level world-rendering sequence remains recognizable:
- Mod_PointInLeaf finds the leaf containing the camera.
- R_MarkLeaves obtains that leaf’s potentially visible set.
- Visible leaves mark their parent nodes.
- R_RecursiveWorldNode walks only the marked part of the BSP.
- Node bounds are tested against the view frustum.
- Visible surfaces are linked into texture chains.
- The texture chains are submitted in batches.
- Decals and lightmaps are rendered as later passes.
The PC build follows almost the same logical sequence. The major difference is that the Dreamcast port aggressively changes the runtime representation of the BSP.
The original PC structures use full-width integers for values such as contents and visframe. On Dreamcast, these become signed bytes. Surface frame markers, dynamic-light frame markers, lightmap coordinates, surface edge counts, style indices, and several flags are packed into byte or short fields.
The Dreamcast node and leaf layouts are also deliberately arranged so their common header occupies the same offsets. A leaf can therefore be traversed as an mnode_t while walking back through its parents without conversion or an extra abstraction layer.
This matters on a machine with 16 MB of main RAM. BSP data is touched constantly by visibility, collision, lighting, entity linking, and rendering. Reducing its working set improves more than the amount of free memory: it also reduces the pressure placed on the SH-4 cache and memory bus.
The plane-normal table
One of the most distinctive Dreamcast changes is the global plane-normal table.
In the PC build, every runtime BSP plane is an mplane_t containing:
- A three-float normal vector.
- A float distance.
- Plane type and sign bits.
- Padding.
That is 20 bytes per plane. The PC loader even reserves space for twice the BSP plane count.