No description
  • Shell 32.2%
  • JavaScript 26.7%
  • C 22.4%
  • C++ 9.3%
  • HTML 5.4%
  • Other 4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
zclaude 1e83fc1f97 README: measured browser timings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:28:08 -04:00
guest guest+web: glxgears waits for the xterm's commands; FPS parsed from whole lines 2026-10-26 12:17:34 -04:00
qemu-wasm qemu-wasm: browser QEMU x86_64 (wasm JIT) with the rv100 device 2026-10-26 11:20:28 -04:00
scripts web: the demo page, COOP/COEP service worker, site build and e2e test 2026-10-26 11:24:34 -04:00
tests web: start glxgears sooner, keep FPS lines out of the serial terminal 2026-10-26 12:05:31 -04:00
web guest+web: glxgears waits for the xterm's commands; FPS parsed from whole lines 2026-10-26 12:17:34 -04:00
.gitignore Initial commit 2026-10-26 10:42:01 -04:00
README.md README: measured browser timings 2026-10-26 12:28:08 -04:00

rv100-web

QEMU in the browser (x86_64, wasm JIT) with rv100, the emulated ATI Radeon 7000 / RV100, as its only display, booting Arch Linux into Xorg. On the emulated card:

  • glxgears, hardware-rendered through Mesa Amber's classic R100 driver (radeon_dri.so) and the stock radeon kernel driver;
  • an xterm that runs glxinfo | grep -i opengl, lspci -k and fastfetch.

Live: https://apps.wizardhat.info/rv100-web/

The page shows the card's scanout on a canvas, a boot checklist, glxgears' FPS, and a root shell on the serial console. It is slow: the x86 guest runs roughly 15-20x slower than native QEMU TCG. Measured in headless Chrome on a Ryzen 7 3700X: kernel at ~90 s, radeon bound at ~165 s, X at ~240 s, the xterm's three commands done at ~520 s, glxgears drawing at ~560 s, at 0.3-2.5 FPS. (Native TCG: gears at 18.6 s and 29 FPS; KVM: 63-65 FPS, vsync.)

Layout

guest/       the Arch guest: build-guest.sh (kernel + initramfs from Arch packages,
             fetched with pacman -Sp and unpacked, never installed),
             build-mesa-amber.sh (Mesa 21.3.9 Amber, r100 only), rootfs/ (init, X config,
             the demo terminal script), build-qemu.sh + run-native.py (native QEMU proof),
             evidence/ (native screenshots and serial log)
qemu-wasm/   QEMU for the browser: build.sh builds ktock/qemu-wasm (pinned, QEMU 8.2 base)
             with the rv100 device in rootless podman; device/ is rv100's QEMU device
             ported to 8.2 plus a frame-grab export; web/rv100web.js is the JS API;
             test/ is the emulator-only smoke test
web/         the page: index.html, app.js, coi-sw.js (adds COOP/COEP, see below)
scripts/build-site.sh   assembles build/site from build/qemu-wasm, build/guest and web/
tests/site_smoke.mjs    the whole site in headless Chromium, served WITHOUT COOP/COEP

Build

guest/build-guest.sh          # -> build/guest/{vmlinuz,initramfs.cpio.gz}
qemu-wasm/build.sh            # -> build/qemu-wasm/ (podman; first run builds the toolchain image)
scripts/build-site.sh         # -> build/site
PLAYWRIGHT_CORE=<dir with node_modules/playwright-core> node tests/site_smoke.mjs

Notes

  • Cross-origin isolation. qemu-wasm runs QEMU's threads as Web Workers sharing one memory, which needs SharedArrayBuffer, which needs the page to be served with COOP same-origin and COEP require-corp. The host serves every app through one nginx config without them, so web/coi-sw.js, a service worker, adds them; the page installs it and reloads once.
  • The X cursor is not drawn: neither the model nor the frame grab composites the hardware cursor, and the page does not forward mouse or keyboard to the guest's X server. Typing goes to the serial console.
  • Memory. The QEMU build has a fixed 2300 MB wasm memory; the guest gets 512 MB (it uses about 220 MB with X and glxgears running). -m 1536 does not fit alongside the 500 MB translation cache.
  • The rv100 repo's frontend/qemu/qemu-v11.1.0.patch has a wrong hunk count in its last hunk; guest/build-qemu.sh corrects it on the fly.