# How N64 Graphics Microcode Shaders Are Translated for Modern APIs in Lighthouse

> Discover how Lighthouse translates N64 graphics microcode shaders for modern APIs. Learn about its intermediate representation and GLSL/HLSL shader execution for next-gen GPUs.

- Repository: [Harbour Masters/Lighthouse](https://github.com/HarbourMasters/Lighthouse)
- Tags: internals
- Published: 2026-08-04

---

**Lighthouse translates N64 graphics microcode by parsing display list commands into an intermediate representation, then executing equivalent GLSL/HLSL shaders through libultraship to replicate the original RSP/RDP fixed-function pipeline on modern GPUs.**

Lighthouse is a modern source port that brings classic Nintendo 64 titles to contemporary platforms. A core challenge in this effort involves bridging the gap between the N64's specialized Reality Signal Processor (RSP) microcode and today's graphics APIs. This article examines how the Lighthouse codebase—specifically its interaction with the **libultraship** submodule—accomplishes this translation from low-level microcode to cross-platform shader execution.

## Understanding the N64 Graphics Pipeline Architecture

The Nintendo 64 rendered graphics through a tightly coupled pair of processors: the **RSP** (Reality Signal Processor) and **RDP** (Reality Display Processor). Games submitted work to these processors via **microcode**—small programs that interpreted graphics commands. Common variants include **F3DEX**, **F3DEX2**, and **L3DEX**, each offering different feature sets and performance characteristics.

Microcode operated on **display lists**: sequential command buffers containing opcodes for vertex transformation, triangle rasterization, texture binding, and color combining. These commands used the **Graphics Binary Interface (GBI)**—a standardized set of macros defined in [`lib/ultralib/include/PR/gbi.h`](https://github.com/HarbourMasters/Lighthouse/blob/main/lib/ultralib/include/PR/gbi.h).

Modern GPUs lack native support for this architecture. Lighthouse solves this by emulating the RSP's behavior in software, then mapping the resulting graphics state to shader programs that run on OpenGL, Vulkan, or DirectX.

## Stage 1: Microcode Loading and Task Initialization

The translation process begins when a game prepares to render a frame. In [`src/core1/graphics_thread.c`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/core1/graphics_thread.c), Lighthouse sets up an `OSTask` structure that encapsulates the microcode binary and its associated data.

```c
void thread5_startF3DEXTask(struct ucode_task_data_s *task_data) {
    // Microcode boot pointers are stripped in the port
    sGfxTask.t.data_ptr = (void*)task_data->data_ptr;
    sGfxTask.t.data_size = ((u8 *)task_data->data_ptr_end - 
                           (u8 *)task_data->data_ptr) & ~7;
    
    osSpTaskLoad(&sGfxTask);
    osSpTaskStartGo(&sGfxTask);
}

```

The `OSTask` definition lives in [`lib/ultralib/include/PR/ucode.h`](https://github.com/HarbourMasters/Lighthouse/blob/main/lib/ultralib/include/PR/ucode.h). This structure carries the microcode type, entry points, and stack configuration. Lighthouse supports multiple microcode variants through dedicated entry points:

- `thread5_startF3DEXTask()` — Standard F3DEX
- `thread5_startF3DEX2Task()` — Enhanced F3DEX2
- `thread5_startL3DEXTask()` — Line-drawing variant

Each path validates the task against expected microcode signatures before proceeding to display list processing.

## Stage 2: Display List Parsing and Command Extraction

Once initialized, the microcode's display list must be interpreted. The function `core1_15B30_addF3DEXTaskData()` in [`src/core1/display_list.c`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/core1/display_list.c) records the list boundaries and assigns a task type identifier.

```c
void core1_15B30_addF3DEXTaskData(Gfx *start, Gfx *end, s32 flags) {
    task_data->task_type = UCODE_TASK_TYPE_F3DEX;
    // Store pointers for subsequent traversal
    task_data->data_ptr = start;
    task_data->data_ptr_end = end;
}

```

The `Gfx` type represents individual GBI commands—packed 64-bit structures where high bits encode the opcode and remaining bits hold operands. Common opcodes include:

| Opcode | Purpose |
|--------|---------|
| `gsDPSetCombineMode` | Configure color combiner |
| `gsSPVertex` | Load vertex buffer |
| `gsSPTexture` | Bind and configure texture |
| `gsDPSetRenderMode` | Set blending and z-buffering |
| `gsSPSetGeometryMode` | Enable lighting, fog, culling |

The display list walker decodes these opcodes sequentially, accumulating state changes without immediate execution. This deferred approach allows Lighthouse to batch operations and optimize for modern GPU submission patterns.

## Stage 3: Port-Layer State Conversion

Raw GBI commands are not directly consumable by modern graphics APIs. The **port layer**—centered in [`src/port/Game.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Game.cpp) and [`src/port/Patches/GraphicsPatches.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Patches/GraphicsPatches.cpp)—transforms accumulated state into platform-agnostic rendering primitives.

[`GraphicsPatches.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/GraphicsPatches.cpp) performs several critical conversions:

- **Segment address resolution** — N64 textures referenced via segment-relative addresses are mapped to physical GPU texture objects
- **Vertex format normalization** — RSP vertex data (position, color, texture coordinates) is repacked into VBO-compatible layouts
- **Pipeline state assembly** — Combiner modes, render modes, and geometry flags are consolidated into descriptor structures

```cpp
void GraphicsPatches::applyCombiner(const RenderCommand &cmd) {
    // Map N64 combine mode to shader permutation
    auto shader = shaderManager.getCombinerShader(cmd.combineMode);
    
    // Upload uniforms derived from parsed GBI state
    shader->setUniform("uPrimColor", cmd.primColor);
    shader->setUniform("uEnvColor", cmd.envColor);
    shader->setUniform("uCombineMode", cmd.combineMode);
    
    // Bind vertex buffer and issue draw
    glBindVertexArray(cmd.vao);
    shader->bind();
    glDrawArrays(GL_TRIANGLES, 0, cmd.vertexCount);
}

```

The `RenderCommand` structure serves as the bridge format—decoupled from both GBI specifics and underlying API requirements.

## Stage 4: Shader Execution in libultraship

The actual GPU work is delegated to **libultraship**, a submodule providing cross-platform rendering infrastructure. Its shader system in `src/fast/shaders/` implements N64-equivalent functionality through standard GLSL (and HLSL on DirectX targets).

### Vertex Shader Emulation

The vertex stage replicates RSP transformation, lighting, and texture coordinate generation:

```glsl
// libultraship/src/fast/shaders/vertex.glsl
#version 330 core

uniform mat4 uProjection;
uniform mat4 uModelView;
uniform vec4 uPrimColor;
uniform vec4 uEnvColor;
uniform int uGeometryMode;

in vec3 aPosition;
in vec4 aColor;
in vec2 aTexCoord;

out vec4 vColor;
out vec2 vTexCoord;

void main() {
    // Transform to clip space
    gl_Position = uProjection * uModelView * vec4(aPosition, 1.0);
    
    // Apply lighting if enabled (GEOMETRY_MODE_LIGHTING)
    vec4 litColor = aColor;
    if ((uGeometryMode & 0x00020000) != 0) {
        litColor = calculateLighting(aPosition, aColor);
    }
    
    vColor = litColor;
    vTexCoord = aTexCoord;
}

```

### Fragment Shader Emulation

The fragment stage implements the N64's iconic **color combiner**—a fixed-function unit that blends multiple color sources through configurable operations:

```glsl
// libultraship/src/fast/shaders/fragment.glsl
#version 330 core

uniform int uCombineMode;
uniform sampler2D uTexture0;
uniform sampler2D uTexture1;
uniform vec4 uPrimColor;
uniform vec4 uEnvColor;
uniform vec4 uFogColor;
uniform float uFogMultiplier;
uniform float uFogOffset;

in vec4 vColor;
in vec2 vTexCoord;
in float vFog;

out vec4 fragColor;

void main() {
    vec4 texel0 = texture(uTexture0, vTexCoord);
    vec4 texel1 = texture(uTexture1, vTexCoord);
    
    // Decode combine mode and execute equivalent blend
    vec4 combined;
    switch (uCombineMode) {
        case 0x00000000: // G_CC_MODULATEI
            combined = vColor * texel0;
            break;
        case 0x00000001: // G_CC_MODULATEIA
            combined = vColor * texel0;
            break;
        // ... additional cases for all N64 combiner modes
    }
    
    // Apply fog if enabled
    float fogAlpha = clamp((vFog * uFogMultiplier) + uFogOffset, 0.0, 1.0);
    combined.rgb = mix(combined.rgb, uFogColor.rgb, fogAlpha);
    
    fragColor = combined;
}

```

These shaders are compiled once at startup and cached by combine mode, minimizing runtime overhead.

## Geometry Pipeline Integration

Model data extraction occurs in [`src/port/Resource/Importers/ModelFactory.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Resource/Importers/ModelFactory.cpp), which processes `G_VTX` commands to build efficient vertex arrays:

1. Extract position, normal, color, and texture coordinate data from RSP vertex buffers
2. Dequantize fixed-point values to floating-point
3. Generate index buffers for indexed drawing where possible
4. Upload to GPU-managed VBOs

The resulting geometry feeds directly into the shader pipeline described above.

## Performance Considerations in Microcode Translation

Lighthouse optimizes the translation path through several strategies:

- **Command batching** — Multiple GBI commands with compatible state are merged into single draw calls
- **Texture atlasing** — Small N64 textures are packed into larger GPU textures to reduce binding overhead
- **Uniform buffer objects** — Per-frame constants (projection matrices, lighting parameters) are updated once rather than per-draw
- **Shader permutation caching** — Unique combine modes map to pre-compiled shader variants, avoiding runtime compilation stalls

## Key Source Files Reference

| File | Line Numbers | Responsibility |
|------|--------------|----------------|
| [`src/core1/graphics_thread.c`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/core1/graphics_thread.c) | Task setup functions | Microcode task initialization and RSP emulation kickoff |
| [`src/core1/display_list.c`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/core1/display_list.c) | `core1_15B30_addF3DEXTaskData()` | Display list boundary recording and task type tagging |
| [`src/port/Game.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Game.cpp) | Main loop dispatch | High-level coordination between emulation and rendering |
| [`src/port/Patches/GraphicsPatches.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Patches/GraphicsPatches.cpp) | Pipeline conversion functions | GBI-to-GPU state transformation |
| [`src/port/Resource/Importers/ModelFactory.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/src/port/Resource/Importers/ModelFactory.cpp) | Vertex processing | Geometry extraction and VBO generation |
| [`lib/ultralib/include/PR/gbi.h`](https://github.com/HarbourMasters/Lighthouse/blob/main/lib/ultralib/include/PR/gbi.h) | GBI macro definitions | Opcode constants and command structure layouts |
| [`lib/ultralib/include/PR/ucode.h`](https://github.com/HarbourMasters/Lighthouse/blob/main/lib/ultralib/include/PR/ucode.h) | `OSTask` structure | Microcode task metadata |
| `libultraship/src/fast/shaders/vertex.glsl` | Vertex shader source | RSP transformation and lighting emulation |
| `libultraship/src/fast/shaders/fragment.glsl` | Fragment shader source | Color combiner and fog emulation |

## Summary

Lighthouse translates N64 graphics microcode through a four-stage pipeline:

- **Microcode tasks** are loaded from game code and validated in [`graphics_thread.c`](https://github.com/HarbourMasters/Lighthouse/blob/main/graphics_thread.c)
- **Display lists** are parsed into intermediate commands by [`display_list.c`](https://github.com/HarbourMasters/Lighthouse/blob/main/display_list.c)
- **Port-layer conversion** transforms GBI state to GPU-compatible structures in [`GraphicsPatches.cpp`](https://github.com/HarbourMasters/Lighthouse/blob/main/GraphicsPatches.cpp)
- **Shader execution** via libultraship runs GLSL/HLSL programs that replicate RSP/RDP behavior

This architecture preserves authentic N64 rendering while achieving modern performance through batching, caching, and GPU-efficient data flows.

## Frequently Asked Questions

### What N64 microcode variants does Lighthouse support?

Lighthouse supports **F3DEX**, **F3DEX2**, **L3DEX**, and **S2DEX** through dedicated task entry points in [`graphics_thread.c`](https://github.com/HarbourMasters/Lighthouse/blob/main/graphics_thread.c). Each variant uses specialized parsing logic that accounts for differences in vertex layout, command encoding, and feature availability. The codebase detects microcode type via task metadata and routes to appropriate handlers.

### Why use shaders instead of fixed-function OpenGL?

Modern graphics APIs have **removed fixed-function pipelines** entirely. Vulkan and DirectX 12 require programmable shaders for all rendering. Even OpenGL 3.3+ core profiles deprecate legacy combiner and transformation state. Lighthouse's shader-based approach ensures **cross-platform compatibility** and **future-proofing** while enabling accurate emulation of N64-specific behaviors like the color combiner's complex blend modes.

### How accurate is the translated output compared to original N64 hardware?

The translation achieves **pixel-accurate results** for most content because libultraship shaders implement N64 hardware behaviors mathematically rather than approximating them. The color combiner, lighting equations, and fog calculations match documented RDP specifications. Some edge cases—particularly around texture filter modes and VI (video interface) post-processing—may show minor differences due to hardware-specific behavior not fully captured in public documentation.

### Can the shader system be extended for enhancements like widescreen or higher resolution?

Yes. The modular shader architecture in libultraship allows **custom uniform injection** and **vertex shader modification** without altering the core microcode translation. Lighthouse and related ports commonly use this to implement widescreen aspect ratio correction, internal resolution scaling, and post-processing effects. The separation between GBI parsing and final rendering means enhancements apply transparently across all supported microcode types.