Digest: what changed after release 1.6
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.
Contents
- At a glance
- Timeline
- The graphics pipeline: XF and TEV as shaders
- A test rig for the whole GFX pipeline
- Audio: the DSP measured against the hardware
- Gekko, MMU and the memory interface
- Booting GC-Linux
- User interface and debugger
- Compatibility
- Tooling and build system
- Documentation and repository
- What is still missing
At a glance
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.
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.
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.
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
-
August 2023 · right after 1.6
SDL user interface and graphics refactoring.
The UI was moved to
uisdl.cpp/cuisdl.cpp, the ImGui file browser was added, message boxes and a render-target path appeared, the Linux build was repaired, and the pad stick and the PI FIFO wrap bit were fixed. -
2024
The build catches up.
A conflicting copy of
fmtwas removed, compiler warnings were cleared, dedicated Debug SDL / Release SDL configurations and a properWinMainappeared. -
February–March 2026
Visual Studio 2026 and the Flipper refactoring (PR #326).
Every Flipper block received a typed register view, the Command Processor became a standalone
Flipper entity, PI FIFO, memory, VI and GFX were refactored, 8-bit hardware access was removed,
memory reset was introduced, and the emulator got SDL audio. The
imgstorefolder moved intowiki. - July 2026 Debugger interface and repository work. The JDI (JSON Debugger Interface) and its UI were moved into place, a Flipper instance is now passed through the debug tree, the DOL/ELF loader was fixed (PONG runs again) and the JSON tables were embedded into the source (PR #332, #335).
- September 2026 The big push: shaders, tests and compatibility. XF/TEV shaders (#336), the DSP test suite (#337), UI letter navigation (#341), CP → XF (#342), the game selector in the SDL build (#343), the GFX test suite (#344), JAudio/DSP/AI (#345), the GC-Linux boot fixes (#346), the Ikaruga THP colours (#348) and the Metroid Prime black-screen fixes (#349).
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:
-
the Transform Unit is emulated by a vertex shader (
src/xf.cpp): geometry and texture matrix multiplies, the projection combine, per-vertex lighting of the two colour channels (N·L diffuse, cosine and distance attenuation) and texture coordinate generation (regular, colour and dual transform); -
the Texture Environment Unit is emulated by a fragment shader
(
src/tev.cpp): up to 16 combine stages with the full operand, bias, sub, clamp and shift datapath, Rev-B K constants, fog and the final alpha function.
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.
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:
- Black bootrom screen:
PE_COPY_CMDperformed its EFB clear when the command arrived, although the frame is swapped later onPE_FINISH— the clear erased the frame that was still to be displayed. The clear is now recorded by the copy command and executed at the frame begin, with the write masks forced while it runs. - CMPR decoding: the colour table was never initialised and only one of four sub-blocks set the endpoint alpha, so results depended on the stack; the fourth colour of the 3-colour mode was written as an average instead of the defined transparent texel.
- Texture maps 4–7 could never be programmed, because the I4–I7 register block was decoded as a second copy of I0–I3.
- Multi-texture: the texture unit of a map was selected after the map was decoded, uploaded and configured, so the work of map i landed on the unit of map i−1.
- The scissor was compiled out behind
NO_VIEWPORT, and registers that were decoded but never applied (TexMode0.lodbias,TexMode1.minlod/maxlod,SU_LPSIZE,SU_SSIZE/SU_TSIZE,RAS1_SS0/SS1,GEN_MODE.flat_en) now take effect. - TEV fog F-select 1 and 3 had no law, and the PE colour/alpha update bits are applied as the registers describe.
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:
divtook its carry through a wrongly sized mask, so the low quotient bit was always zero, andnorm/divwere unimplemented stubs;lsf/asfshifted the wrong way in every register form — the count is the signed low byte of the source, positive shifts left and negative shifts right;ModifyFlagsdid not mask its inputs to 40 bits;neg preported the wrong borrow; unsigned multiplies, logic-family flags, accumulator and immediate rules,clr,neg/negc,addp,lsl16,tst pand the multiply/accumulate family were corrected;trapdid not advance the PC, soretire-trapped forever, and the interrupt vectors ignored the program base;- circular addressing wrapped at the modifier length instead of the 2n aligned block, the decoder accepted reserved words, and the mailbox could tear a message pair.
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
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:
- executables (DOL/ELF) and disk images (GCM/ISO) are collected from the
PATHuser variable, with the sameFILTERmasks per extension; - disk banners (
opening.bnr) are converted from the 4×4 tiled RGB5A3 format into an SDL texture with an alpha channel, the Game ID is read from the DiskID and Japanese banners are converted from SJIS; - the table has Icon/Title/Size/Game ID/Comment columns and the Win32 sort rules (Default, Filename, Title, Size, Game ID, Comment, Unsorted);
- the cursor moves with a click, with Up/Down, or by typing the first letter of a title, and a game starts with Enter or a double click;
- Options → Selector holds Enable Selector, Refresh, Small Icons, Sort by,
File Filter and Add Directory; the selection is remembered in
LASTFILEacross runs, and the rescan is skipped while a game is running (reading banners mounts disks).
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.
--ipl; the spinning cube is
rendered by the XF/TEV shaders.Tooling and build system
- Visual Studio 2026. The project files were migrated and now build with MSVC
v145; the solution is
scripts/VS2026/pureikyubu.slnand the test project isscripts/VS2026/pureikyubu_test.slnx(a legacy VS2022 project is kept in the tree for reference). - Unit tests. The test project references the emulator sources instead of copying them, so the tests always exercise exactly the code the emulator is built from. Suites: DSP core, flags, golden ALU, parallel words, mailbox, IROM, GQR, and the whole GFX pipeline.
- Debug facilities.
EMU_LOG=<file>dumpsDebug::Reportmessages to a file andGFX_DUMPdumps every n-th rendered frame to a BMP (with R and B in the right order now). - SDL audio. A new
audiosdl.cppbackend gives the SDL build sound, with the CMake and Visual Studio projects updated. - Data and configuration. The emulator checks that the
Datadirectory exists, the JSON tables were embedded into the source so no external files are needed (PR #332), andMemRstintroduced a proper memory reset path.
Documentation and repository
- The in-repository wiki was extended and reorganised:
wiki/gfx.mdnow documents the GFX pipeline in detail (238 lines), and Flipper, Gekko, main and Debug UI pages were updated. Theimgstorefolder moved intowiki. testing/Readme.mddescribes the test layout and how to run it, andtesting/DspIrom.mdpublishes the DSP IROM disassembly and analysis.- The project adopted Karpathy-style behavioural guidelines in
.clinerulesfor AI-assisted changes.
What is still missing
- GFX: bump mapping, indirect texturing, the Z-texture environment and Cpu2Efb are not
emulated yet; PE dither and a few
SUflag fields are deliberately unimplemented and documented as such inwiki/gfx.md. - Audio: full JAudio microcode support (issue #71) is the goal the recent DSP/ARAM/AI work
builds towards; some DSP divergences from the hardware are still recorded and asserted in
testing/Readme.md. - Linux: sound and input are still missing in the Linux build.
- The Metroid Prime fixes are on a development branch at the time of writing.
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.