webulator

One browser API for emulated computers

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.

cores
4
CPU families
3
tests passed
48/48

New: Windows XP with 3D hardware acceleration, in the browser

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.

Choose a machine.
Idle

3D acceleration

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.

Two paths to the GPU
The VMware driver (WineD3D or Mesa in the guest, slower), and API forwarding: a replacement 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.
What it needs
WebGL2 in a worker (OffscreenCanvas), SharedArrayBuffer, and a GPU. Without WebGL2 the tab says so and does not start.
Where it stops
The emulated CPU is the limit: about 250 to 280 MIPS, roughly a 300 to 500 MHz Pentium. Cube and volume textures, user clip planes, vertex blending and GLSL are not implemented, and a window overlapping the 3D window is drawn over.
Test (640×480)Software pathForwarded
Quake 3 map, OpenGL158 fps329 fps
Quake 3 map, Direct3D 9n/a498 fps
OpenArena timedemo14.0 fps16.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.

The API

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();
Equal capabilities, nothing optional
A capability enters the contract only once every core implements it and passes its tests. Hardware differences such as screen sizes and mouse buttons are reported as properties.
Snapshots restore to the same pixel
A saved state restores into a fresh worker, and its first frame matches the saved frame exactly.
A virtual clock
The guest sees no time pass while it is paused or stored, so a restored machine behaves like one that never stopped.
Disk fetches never stall the guest
Disk images load lazily over the network. While a chunk is on its way the guest keeps running, so the cursor keeps moving, as on a real machine with a busy disk.

Conformance

Every profile runs the full suite in headless Chromium. These results are from Chromium 153 on an Apple M2.

Test Mini vMacSystem 6.0.8Basilisk IISystem 7.5.3 SheepShaverMac OS 9.0.4v86Windows 98
T1Cold boot to a settled desktop2.7 s4.0 s27.9 s19.1 s
T2Save, destroy, restore into a new worker; first frame identical, 5 times46–85 ms77–140 ms158–217 ms137–170 ms
T3Responsive after restore: a menu opens, an app opens
T4Restored after a 60 s gap, responds like a fresh restore
T5Paused 10 s: guest clock advance, frames drawn0 ms, 00 ms, 00 ms, 00 ms, 0
T6moveTo 20 random points; cursor lands exactly20/2020/2020/2020/20
T7type() all 95 printable ASCII characters, compared with a golden image0 px0 px0 px0 px
T8Disk overlay exported and booted in a new machine16 KB80 KB192 KB2.9 MB
T9Snapshot taken while the guest is busy booting, then restored164 ms269 ms497 ms270 ms
T10No input lost: about 380 key events sent back to back
T11T1 to T3 with no display attached
T12Disk fetches delayed 400 ms each: longest pause in the moving cursor33 ms37 ms59 ms36 ms

Method, golden images and known issues are in docs/conformance.md.

Cores

Each core keeps its own license and ships separately from the MIT-licensed library.

CoreCPUMachine in the demoSnapshotLicenseSource changes
v86x86PC · Windows 9812.4 MBBSD-2-Clausenone
v86 + SVGA3Dx86PC · Windows XP, 3D accelerated39 MBBSD-2-ClauseVMware SVGA II device, WebGL2 renderer
Mini vMac68000Macintosh Plus · System 6.0.80.6 MBGPL-2.0disk driver patch
Basilisk II68040Quadra 650 · System 7.5.32.0 MBGPL-2.0disk driver patch
SheepShaverPowerPCPower Macintosh G3 · Mac OS 9.0.411.1 MBGPL-2.0disk 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.