Webulator runs WebAssembly emulators in a worker and puts them behind one contract: snapshots, exact pointer input, a virtual clock and copy-on-write disks. A core counts as supported only when it passes the same twelve conformance tests as every other core.
A virtual VMware SVGA II card inside the emulator turns the guest's Direct3D and OpenGL calls into WebGL2 on your GPU. Open the 3D tab to see OpenArena running on an emulated Pentium.
Windows XP · 3D accelerated. Needs a desktop browser with WebGL2 and SharedArrayBuffer (cross-origin isolation, set up by this page's service worker) and a GPU. First load downloads a 39 MB saved state of the XP desktop; the page then starts OpenArena 0.8.8 in the guest by typing into the Run dialog, and the guest streams about 130 MB of game data from disk as it loads (roughly a minute). It plays back a recorded match through OpenGL 1.x, forwarded to WebGL2, and loops. The counter at the top right of the game window is the real frame rate of the emulated machine: expect single digits to about fifteen frames per second, because the emulated Pentium, not the GPU, is the limit. The saved state is the desktop because the host-side 3D state is not part of a snapshot.
v86 has no 3D hardware. Webulator's v86 build adds a VMware SVGA II card with the SVGA3D protocol, so the guest's own VMware display driver gets a Direct3D-capable GPU. A WebGL2 renderer in the worker executes the commands, including fixed-function transform and lighting and D3D9 shaders translated to GLSL.
d3d9.dll and opengl32.dll hand each call to the host, which reads vertex and texture data
straight from guest memory. Direct3D 8 runs through d3d8to9.| Test (640×480) | Software path | Forwarded |
|---|---|---|
| Quake 3 map, OpenGL | 158 fps | 329 fps |
| Quake 3 map, Direct3D 9 | n/a | 498 fps |
| OpenArena timedemo | 14.0 fps | 16.9 fps |
Measured in headless Chromium on an Apple M2. The software path is Mesa's SVGA3D OpenGL driver in the guest. The live demo shows lower numbers while the page also draws the screen.
A profile names the core, the machine model and the disk images. Everything after
Machine.create is the same for every core.
import { Machine, loadProfile } from "webulator"; const m = await Machine.create({ profile: await loadProfile("sheepshaver-g3bw-macos904.json"), snapshot: { url: "finder.webusnap" }, // skip the boot display: canvas, }); m.input.pointer.moveTo(20, 8); // guest pixels, lands exactly m.input.pointer.button(0, true); await m.input.type("Hello"); await m.pause(); // the guest clock stops too const state = await m.saveState(); // CPU, RAM, devices, disk writes const writes = await m.disks.get("hd0").exportOverlay();
Every profile runs the full suite in headless Chromium. These results are from Chromium 153 on an Apple M2.
| Test | Mini vMacSystem 6.0.8 | Basilisk IISystem 7.5.3 | SheepShaverMac OS 9.0.4 | v86Windows 98 | |
|---|---|---|---|---|---|
| T1 | Cold boot to a settled desktop | 2.7 s | 4.0 s | 27.9 s | 19.1 s |
| T2 | Save, destroy, restore into a new worker; first frame identical, 5 times | 46–85 ms | 77–140 ms | 158–217 ms | 137–170 ms |
| T3 | Responsive after restore: a menu opens, an app opens | ||||
| T4 | Restored after a 60 s gap, responds like a fresh restore | ||||
| T5 | Paused 10 s: guest clock advance, frames drawn | 0 ms, 0 | 0 ms, 0 | 0 ms, 0 | 0 ms, 0 |
| T6 | moveTo 20 random points; cursor lands exactly | 20/20 | 20/20 | 20/20 | 20/20 |
| T7 | type() all 95 printable ASCII characters, compared with a golden image | 0 px | 0 px | 0 px | 0 px |
| T8 | Disk overlay exported and booted in a new machine | 16 KB | 80 KB | 192 KB | 2.9 MB |
| T9 | Snapshot taken while the guest is busy booting, then restored | 164 ms | 269 ms | 497 ms | 270 ms |
| T10 | No input lost: about 380 key events sent back to back | ||||
| T11 | T1 to T3 with no display attached | ||||
| T12 | Disk fetches delayed 400 ms each: longest pause in the moving cursor | 33 ms | 37 ms | 59 ms | 36 ms |
Method, golden images and known issues are in docs/conformance.md.
Each core keeps its own license and ships separately from the MIT-licensed library.
| Core | CPU | Machine in the demo | Snapshot | License | Source changes |
|---|---|---|---|---|---|
| v86 | x86 | PC · Windows 98 | 12.4 MB | BSD-2-Clause | none |
| v86 + SVGA3D | x86 | PC · Windows XP, 3D accelerated | 39 MB | BSD-2-Clause | VMware SVGA II device, WebGL2 renderer |
| Mini vMac | 68000 | Macintosh Plus · System 6.0.8 | 0.6 MB | GPL-2.0 | disk driver patch |
| Basilisk II | 68040 | Quadra 650 · System 7.5.3 | 2.0 MB | GPL-2.0 | disk driver patch |
| SheepShaver | PowerPC | Power Macintosh G3 · Mac OS 9.0.4 | 11.1 MB | GPL-2.0 | disk driver patch |
Snapshot sizes are gzip-compressed. The Mac cores are Infinite Mac's builds, rebuilt with Asyncify enabled and one small patch: the disk driver reports "busy" instead of blocking while data is fetched.