Digest: what changed after release 1.6

Revision range pureikyubu-16 (17 August 2023) → development head (11 September 2026) · 168 commits · pull requests #302–#349

This digest stops at the 1.8 release. For what 1.9 added after it — the integrated Game Boy Advance and Game Boy, the software GFX pipeline, the new debugger, the MCP server, the headless build, the DSP recompiler and the security review — see the 1.9 release notes.

Release 1.6 was a snapshot of a Win32-only emulator with a fixed-function OpenGL backend and almost no automated tests. Everything below happened after it: the emulator gained a second user interface, a shader-based graphics pipeline, a unit-test rig that runs the real hardware pipeline, a Linux boot with the Broadband Adapter, hardware-checked audio, and a long list of fixes that turned several black screens into playable pictures.

At a glance

Graphics

GLSL instead of fixed function

XF and TEV are emulated by two static shaders that receive the whole register state as uniforms. All eight texture maps, mip levels and the corrected sampler modes came with it.

Tests

340 GFX + 177 DSP tests

Both subsystems now have CppUnitTest suites that link the emulator sources directly, plus an HTML report with 262 rendered reference pictures.

Emulation

Hardware-documented behaviour

ARAM and AI follow the documentation; GQR and paired-single are decoded the way the ISA manual numbers the bits; the decrementer is modelled like the hardware.

Platforms

SDL build matured

The SDL front end got audio, a real game selector, message boxes and file browsing, and it builds and runs on Windows and Linux.

Timeline

The graphics pipeline: XF and TEV as shaders

The Flipper GFX backend no longer uses the fixed-function OpenGL pipeline. Its two programmable stages are mapped onto the two programmable stages of OpenGL 3.3 core:

Both shaders are static and take the register state as uniforms, so a game can reconfigure the GFX registers without triggering a shader recompile. Along the way the texture unit was rebuilt: all eight texture maps are decoded and bound independently (there used to be only one), the sampler wrap and filter modes were corrected (magnification and minification were swapped) and mip levels are generated. The rasterizer accumulates vertices into a VBO and draws with glDrawArrays/glDrawElements, because core-profile OpenGL has no GL_QUADS.

A second refactoring turned the Command Processor into what the hardware describes: the CP no longer reaches into the graphics blocks. It pushes its command words into the XF (CPRegLoadBegin/CPRegLoadData, SU bypass words and the vertex rows of a draw), polls the XF readiness response and receives register read-backs through CP_XF_DATAL/CP_XF_DATAH. The pipeline is now strictly CP → XF → SU → RAS, and register loads are word-wise, so a partial load can no longer halt the emulator.

Luigi's Mansion rendered with the XF/TEV shader pipeline
Luigi's Mansion on the shader pipeline.The lighting and texture combine stages that used to be hard-coded in the OpenGL backend now run in the emulated XF and TEV shaders.

A test rig for the whole GFX pipeline

The graphics subsystem used to have no automated coverage at all — the only way to check it was to boot the emulator and look at the picture. The new rig creates a window with a real OpenGL context, drives the emulated CP through the PI register window (so display lists are parsed for real), reads the EFB back and compares pixels. Vertex-stage behaviour is probed through transform feedback, and Report::Section/Report::Image publish an HTML report next to the test DLL.

Coverage spans CP, XF, SU/RAS, TX, TEV and PE — the command stream and vertex formats, matrices, projection, viewport, lighting, texgen and dual transform, primitives, culling, scissor, line and point size, TREF bindings, every texel format and palette, filters, wrap modes, mip selection, colour and alpha combines, K constants, fog, the Z environment and indirect/bump texturing, blend factors, logic ops, depth compares, write masks and the copy clear. 340 tests pass, and the report contains 262 rendered pictures in 43 sections.

The tests immediately paid for themselves. Among the bugs they found and drove out:

Audio: the DSP measured against the hardware

The DSP core was checked against vectors captured from the DSP itself. The suite contains unit tests for every non-parallel instruction, an independent reference model of the C V Z N E U flag rules, 1822 golden ALU vectors, 435 golden vectors for the circular buffers, tests of the packed/parallel word forms and the addressing modes, the CPU↔DSP mailbox protocol, and decode coverage of the whole IROM together with its published disassembly (testing/DspIrom.md).

Both the tests and the disassembly analysis uncovered real bugs:

The hardware-documentation pass renamed the ARAM controller registers (ACRS/ACWE/…), fixed the 32-byte block transfer and the address masks, renamed AIDCR to CDCR, applied the AIVR streaming volume to the DVD-audio channel (sample × volume / 256, 0x00 mutes, 0xFF is full scale) and latched the packed memory half's operand in the DSP core. The ARAM DMA is now performed to completion inside the AMBL low-word write, like the SI, EXI and DVD engines, instead of being sliced by a worker thread — that slicing dropped Metroid Prime's audio DMA requests and hung the game right after the intro movie.

Gekko, MMU and the memory interface

The GQR and paired-single extensions were decoded with the wrong bit numbering: the register fields are numbered from the most significant bit in the ISA manual (LD_SCALE 24:29, LD_TYPE 16:18, ST_SCALE 8:13, ST_TYPE 0:2), so every non-zero GQR was misinterpreted. The conversion rules were corrected as well (loads scale by 2−S, stores compute clamp(trunc(F × 2S)), NaN stores as positive saturation, type-0 denormal stores as 0.0), together with the HID2 gating: indexed psq_* forms need only HID2[PSE], non-indexed ones need PSE and LSQE. The conversion math now lives in the pure header src/gqr.h and has its own tests.

The MMU hash-table walk used to read page table entries straight from memory while the CPU writes them through the write-back data cache, so freshly created PTEs were invisible and the kernel looped on the same DSI fault; ReadHashPte/WriteHashPte make the walk coherent with the cache, as the hardware is. mtcrf assigned its operand fields in the wrong order (the RS field was used as the CR mask and the CRM field as the register number, reading gpr[128] out of bounds), and lswi/lswx only stored the last accumulated word when the byte count was not a multiple of four, which broke GCC's struct copies.

The decrementer is now modelled the way the hardware does: the exception is caused by an underflow, it is generated once per underflow, the request is latched so it survives a period with MSR[EE] cleared, a positive counter value clears a pending request, and the counter is negative at reset. That fixed both the GC-Linux timer tick and a livelock in the IPL/Bootrom, where a purely level-sensitive request re-entered the 0x900 vector forever. Finally, writes to the SI input buffer registers are accepted instead of halting the emulation, because the Linux gcn-si driver resets them during initialisation.

Booting GC-Linux

GC-Linux kernel booting to the gcnfb console
GC-Linux on pureikyubu.A 2004 kernel (2.6.10-rc2-mm4-isobel-nbdc7) boots to the gcnfb console; the Broadband Adapter driver is registered and all four SI ports are detected.

Booting the 2004 GC-Linux build was the test that found most of the CPU and interface bugs above: every defect stopped the kernel at a different stage. With the mtcrf fix, the cache-coherent MMU walk, the latched decrementer, the corrected lswi and the permissive SI registers, the kernel brings up the gcnfb console and prints a clean dmesg. The Broadband Adapter driver initialises and the frame buffer console runs at 640×480×16 with the NTSC/PAL60 480i mode.

User interface and debugger

The SDL build is no longer a stub. It has its own ImGui-based UI (uisdl.cpp/cuisdl.cpp), an integrated file browser, message boxes and a separate video-output window whose events are filtered so the covered selector does not react to clicks meant for the game. The Win32 game selector was ported to it:

The pureikyubu game selector with a library of GameCube games
The game selector.Titles, banners, Game IDs, sizes and comments are collected from the disc images; jump-to-letter navigation works on the Title column, which is what the user actually sees.

The debug user interface gained a JDI (JSON Debugger Interface) server and its own view, the Flipper instance is passed through the debug tree so every block can be inspected in place, and the debugger can boot the IPL menu directly for sound debugging.

Compatibility

Metroid Prime

The intro FMV used to stay on a black screen while its audio played. The movie player never calls GXDrawDone; it draws, copies the frame to the XFB with the copy engine and waits for the retrace. A whole-frame display copy is now a frame boundary of its own, partial copies still wait for PE_FINISH, and a frame only presents when it holds content that has not been displayed yet. The game then reaches its title screen.

The Legend of Zelda: The Wind Waker

Gets past its boot. The XF matrix RAM and colour registers now start from the state GXInit establishes (verified against a real bootrom run), including the identity matrices at GX_IDENTITY and GX_DTTIDENTITY that the game selects by index without loading them; without it every texture coordinate collapsed to (0, 0).

Ikaruga

The THP title movie decoded into a checkerboard of float fragments and rendered pink. Fixed by the GQR field numbering, by separating the Rev-B K constants from the colour registers (they share ids but not storage) and by grouping the single-channel K selectors by channel.

Luigi's Mansion

Runs on the shader pipeline and is used as a smoke test for lighting and texture combines.

GameCube IPL menu

A new --ipl option boots the console menu without a disc image, and --no-disc starts the emulation with the DVD lid open. This made sound debugging much faster.

PONG and homebrew

The DOL/ELF loader was fixed and PONG runs again both directly and from the game selector.

The GameCube IPL menu rendered by pureikyubu
The IPL menu.Booted with --ipl; the spinning cube is rendered by the XF/TEV shaders.

Tooling and build system

Documentation and repository

What is still missing

This digest is compiled from the git history between the pureikyubu-16 tag and the development head. Commit messages, PR descriptions and the wiki pages are the sources.