deniz.in

Markets

Weather

Loading weather

· via Hacker News – Front Page (native)

PS2 games statically recompiled to WebAssembly run at full speed in the browser

A Hacker News project recompiles PlayStation 2 games ahead of time into WebAssembly plus a WebGPU hardware re-implementation, running Gran Turismo 4 and God of War II at full speed in Chrome without an emulator.

PS2 games statically recompiled to WebAssembly run at full speed in the browser

A project called ps2-web-recomp, surfaced on Hacker News, translates PlayStation 2 games into standalone web pages that run at full speed in Chrome without an emulator. Rather than interpreting the console's CPU at runtime, the toolchain converts a game's Emotion Engine machine code into C++ ahead of time, compiles that to WebAssembly, and links it against a re-implementation of the PS2's operating system and hardware that renders through WebGPU. It is the sibling of an earlier PSP project using the same method, and it ships no game data: you supply a disc image of a game you own, and the repository's scripts build the page locally.

Two games, no game-specific code

According to the project's GitHub repository, the first title brought up this way was Gran Turismo 4, chosen as one of the most demanding games on the system: it leans on both vector units, depends on exact floating point behaviour for its replays and licence tests, and reads rendered images back for use as palettes. It plays from boot through the opening movie, menus and races, with music and sound effects, and saves to a memory card kept in the browser. A PAL race runs at its full 50 frames per second in Chrome on a laptop's integrated GPU at twice the PS2's resolution.

God of War II was then built into a page with only generic changes. It reached combat and holds 60 fps in heavy combat at native resolution, or 48 to 60 fps at double resolution, where the integrated GPU becomes the bottleneck. Getting there exposed two bugs the racing game never triggered: a recompiler flaw in which a branch on the last instruction of a 16 KiB unit resumed at the delay slot instead of after it, and vector unit timing that made clip flags visible an instruction too late for this game's microcode. An unimplemented hardware timer on the input/output processor had also left the game silent.

How the recompilation works

The core tool, eerecomp, follows the executable's control flow and emits C++ in 16 KiB units of guest code. The output preserves the R5900 CPU's quirks — 128-bit registers and their multimedia instructions, branch delay slots, and a floating point unit with no infinities or denormals that rounds differently from IEEE. Jumps between units become tail calls, and a thread switch unwinds the host stack and resumes at the guest address, so the generated code needs no per-thread stack.

The main CPU side of the PS2 kernel is answered with high-level emulation: threads, semaphores, DMA and vertical blank interrupt handlers, alarms, and the SIF channel to the I/O processor. The I/O processor runs the game's own IRX drivers for sound, disc streaming, controllers and memory cards on an R3000 interpreter, while the IOP kernel and its libraries are re-implemented in C++. The disc is streamed over HTTP Range requests, so only the executable downloads up front.

Vector units and the Graphics Synthesizer

Most 3D work arrives as microprograms uploaded to vector unit 1, which runs them with bit-exact PS2 floating point and modelled pipeline delays for the MAC, status and clip flags, since games schedule their code around those latencies. Microprograms seen during a test run are compiled ahead of time into SIMD C++; anything new is translated into a WebAssembly module on the spot in a few milliseconds.

The Graphics Synthesizer's 4 MB of video memory lives on the GPU in its native swizzled layouts, and WebGPU compute shaders bin triangles into tiles and rasterize them with the GS's own rules for texturing, palettes, alpha blending, depth and destination alpha tests. Because everything stays inside GS memory, effects that render into a texture or reinterpret a framebuffer as a palette behave as on real hardware. Internal resolution scales from 1x to 3x, and a software rasterizer remains the reference the GPU output is compared against.

The performance work was browser-side

The first race reportedly ran at 3 fps natively with a software rasterizer and a VU1 interpreter; the WebGPU renderer and compiled microprograms lifted that to roughly 70. The remaining gains came from browser-specific tuning. The first GPU renderer issued around 650 compute dispatches per frame, and hoisting texture uploads ahead of the triangles that do not depend on them cut that to about 80. Gran Turismo 4 uploads the same 3 MB of textures every frame, and a 16 MB GPU-side cache of recent uploads reduced per-frame data by about 60 percent. Fixing divergent per-triangle setup loads and writing each frame's data in a single piece brought 3x resolution from 23 fps to 48-50, and moving emulation into a worker while yielding through a MessageChannel instead of clamped setTimeout calls steadied races at a constant 50.

Why it matters

Static recompilation treats console games like any other legacy binary: translate once, then run the artifact anywhere WebAssembly and WebGPU do — here, a plain browser tab. The God of War II port needed only generic fixes after two core bugs were found, which suggests the method generalises beyond a single hand-tuned game. The write-up also doubles as one of the more detailed public case studies in WebGPU performance — batch reduction, upload caching, shader divergence and worker scheduling — lessons that apply to any demanding web application, not just emulation.

  • #webassembly
  • #webgpu
  • #emulation
  • #playstation-2
  • #static-recompilation