How Framebuffer Configuration Impacts N64 GPU Performance and Memory Bandwidth in Pyrite64

Framebuffer width, height, and pixel format directly determine memory bandwidth consumption on the N64, where RGBA32 doubles the data load compared to RGBA16 and larger resolutions linearly increase fill-rate pressure against the RDP's ~15 MiB/s limit.

The Pyrite64 engine exposes these hardware-sensitive parameters through the SceneConf struct, allowing developers to balance visual fidelity against the Nintendo 64's constrained 4 MiB VRAM budget. Understanding how fbWidth, fbHeight, and fbFormat propagate from editor settings to runtime surface allocation is essential for maintaining stable frame times.

Where Framebuffer Settings Are Defined in the Source

The configuration originates in the project layer and flows through the build system into the engine runtime.

In src/project/scene/scene.h, the SceneConf struct stores the user-defined parameters:

// src/project/scene/scene.h (lines 30-34)
struct SceneConf {
  uint16_t fbWidth = 320;
  uint16_t fbHeight = 240;
  uint8_t fbFormat = 0;  // 0 = RGBA16, 1 = RGBA32
  // ...
};

The src/build/sceneBuilder.cpp module serializes these values into the binary scene file consumed by the N64 executable:

// src/build/sceneBuilder.cpp (lines 37-44)
fileScene.write<uint16_t>(sc->conf.fbWidth);
fileScene.write<uint16_t>(sc->conf.fbHeight);
fileScene.write<uint8_t>(sc->conf.fbFormat);

At runtime, n64/engine/include/scene/scene.h defines the FLAG_SCR_32BIT constant, which the render pipelines query to determine surface allocation:

// n64/engine/include/scene/scene.h (lines 45-47)
enum SceneFlags {
  FLAG_SCR_32BIT = 1 << 0,
  // ...
};

How Resolution Impacts Memory Bandwidth

The product of fbWidth and fbHeight determines the total pixel count the RDP must process each frame. On the N64, memory bandwidth scales linearly with this pixel count because every operation—clearing, rendering, and VI output—must touch every byte.

For a standard 320×240 resolution:

  • RGBA16: 320 × 240 × 2 bytes = ~150 KB per framebuffer
  • RGBA32: 320 × 240 × 4 bytes = ~300 KB per framebuffer

With double-buffering (standard for tear-free rendering), these values double, consuming 300 KB or 600 KB of the 4 MiB VRAM respectively. The remaining memory must accommodate textures, depth buffers, and audio buffers, leaving minimal headroom for high-resolution framebuffers.

The RDP's empirical fill-rate ceiling of approximately 15 MiB/s means that increasing framebuffer dimensions directly reduces the maximum sustainable frame rate. A 640×480 RGBA16 buffer requires ~600 KB per frame, consuming 4 MiB of bandwidth at 60 Hz—approaching the hardware limit before any geometry or texture fetches are considered.

How Pixel Format Impacts GPU Performance

The fbFormat parameter selects between RGBA16 (2 bytes/pixel) and RGBA32 (4 bytes/pixel). This choice has multiplicative effects on memory bandwidth and cache efficiency.

In n64/engine/src/renderer/pipelineDefault.cpp, the engine checks the FLAG_SCR_32BIT flag to determine which surface format to allocate:

// n64/engine/src/renderer/pipelineDefault.cpp (lines 30-34)
if (scene.getConf().flags & P64::SceneConf::FLAG_SCR_32BIT) {
  fmt = FMT_RGBA32;
} else {
  fmt = FMT_RGBA16;
}

Switching to RGBA32 doubles the memory traffic for every framebuffer operation:

  • Clear operations must write twice as many bytes
  • RDP rendering transfers double the data across the RDRAM bus
  • VI scanning reads twice the data to output to the display

The N64's RDRAM cache (8 KB) sees increased pressure with RGBA32, as fewer pixels fit into cache lines, causing more frequent evictions and memory stalls.

However, not all pipelines support RGBA32. The n64/engine/src/renderer/pipelineHDRBloom.cpp and pipelineBigTex.cpp modules contain static assertions that enforce RGBA16 exclusivity:

// n64/engine/src/renderer/pipelineHDRBloom.cpp (lines 36-38)
assert(!(scene.getConf().flags & P64::SceneConf::FLAG_SCR_32BIT) && 
       "HDR-Bloom pipeline only supports RGBA16");

These restrictions exist because the microcode and surface allocation logic for these effects are optimized specifically for 16-bit pixel operations. Attempting to use RGBA32 with these pipelines triggers a runtime abort, protecting developers from undefined behavior or severe performance degradation.

Practical Configuration Examples

Configuring via JSON Scene Files

Developers can specify framebuffer parameters directly in the scene JSON:

{
  "fbWidth": 320,
  "fbHeight": 240,
  "fbFormat": 0,
  "renderPipeline": 0
}

The fbFormat value maps to the TexFormat enum defined in src/utils/textureFormats.h:

// src/utils/textureFormats.h (lines 10-13)
enum TexFormat {
  TEX_FMT_RGBA16 = 0,
  TEX_FMT_RGBA32 = 1,
  // ...
};

Using the Editor Interface

The src/editor/pages/parts/sceneInspector.cpp module exposes these settings through an ImGui interface:

// src/editor/pages/parts/sceneInspector.cpp (lines 58-60)
constexpr const char* const FORMATS[] = {"RGBA16","RGBA32"};
ImTable::addComboBox("Format", scene->conf.fbFormat, FORMATS, 2);

The editor automatically disables the RGBA32 option when the HDR-Bloom or BigTex pipelines are selected, preventing invalid configurations.

Runtime Flag Checking

Render pipelines access the configuration through the scene header flags:

// Runtime check pattern used across pipelines
if (scene.getConf().flags & P64::SceneConf::FLAG_SCR_32BIT) {
    // Execute RGBA32-specific path
} else {
    // Execute RGBA16 path
}

This pattern appears in pipelineDefault.cpp during surface initialization and determines which RDP surface format constant to pass to the graphics microcode.

Summary

  • Framebuffer dimensions (fbWidth × fbHeight) linearly scale memory bandwidth requirements and RDP fill-rate consumption on the N64.
  • Pixel format selection (fbFormat) doubles memory traffic when switching from RGBA16 to RGBA32, impacting cache efficiency and VI output bandwidth.
  • VRAM constraints mean that high-resolution RGBA32 framebuffers can consume over 600 KB of the 4 MiB budget, leaving insufficient space for textures and depth buffers.
  • Pipeline compatibility restricts RGBA32 usage to the Default pipeline only; HDR-Bloom and BigTex pipelines enforce RGBA16 through runtime assertions.
  • Configuration flow propagates from SceneConf in src/project/scene/scene.h through the scene builder to runtime flags checked by pipelineDefault.cpp.

Frequently Asked Questions

What is the default framebuffer configuration in Pyrite64?

The default configuration uses a resolution of 320×240 pixels with the RGBA16 format (2 bytes per pixel). This is defined in the SceneConf struct in src/project/scene/scene.h and provides a balance between visual quality and memory bandwidth suitable for the N64's hardware constraints.

Why does switching to RGBA32 halve the frame rate on N64?

RGBA32 uses 4 bytes per pixel compared to RGBA16's 2 bytes, doubling the memory bandwidth required for every framebuffer operation including clearing, RDP rendering, and VI scanning. Given the N64's RDRAM bandwidth limitations and RDP fill-rate ceiling of approximately 15 MiB/s, this doubling often pushes the system past sustainable performance thresholds.

Which render pipelines support RGBA32 framebuffer format?

Only the Default pipeline (RenderPipelineDefault) supports RGBA32. The HDR-Bloom and BigTex pipelines contain static assertions in pipelineHDRBloom.cpp and pipelineBigTex.cpp that abort execution if FLAG_SCR_32BIT is set, as their microcode and surface allocation logic are optimized exclusively for 16-bit pixel operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →