| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A browser has no file descriptors, so the wasm build's link port was inert.
Give it a pair of ring buffers instead: the guest's transmitted bytes pile up
in one, the host pushes received bytes into the other, and the external-clock
RX path pops from it (staying pending when empty, exactly like a quiet link).
gbw_serial_drain / gbw_serial_push hand them to JS through a fixed staging
buffer, so nothing allocates per frame.
That is enough to be a real link. SLIP-framed bytes are IP packets; unframed
bytes are console I/O - the same split tools/gbhub makes natively, which is
what lets a page type into the link port and reach the shell's stdin.
One machine per wasm instance, so the queues are plain statics: several Game
Boys on a page are several instantiations of the one compiled module, each
with its own linear memory.
The native link is untouched: the fd paths sit in the #else and, preprocessed
for a native target, gb.c comes out byte-identical to the previous commit
(verified with gcc -E). blargg cpu_instrs still passes and the golden-frame
tests are still pixel-exact.
|
|
|
`make wasm` builds the core to WebAssembly (~44KB stripped) and web/ is a
front-end for it: drag-drop a ROM, pixelated canvas at any zoom, keyboard
mapped like the native build, battery saves in localStorage.
Built with zig cc, which carries its own wasm backend, linker and
wasi-libc - so no emscripten dependency, just zig on PATH. Only the
portable core goes in; render/kitty/control/main are terminal- and
POSIX-bound and stay out. src/wasm.c is the browser's whole host layer:
no terminal, no sockets, no files, and no frame pacing (the page's
requestAnimationFrame owns that). The machine is static, the RGB888 ->
RGBA conversion for putImageData happens in wasm, and nothing allocates
per frame.
The entire host interface is four WASI fd_* imports, pulled in by the
cart info banner's fprintf; the page shims them to console.log in ~20
lines. Cartridge RAM *is* the save file here, so it's exported for the
page to persist per cart title (every 5s and on unload).
Because both builds now advance on the same frame boundary, the port is
checked byte-for-byte against the native binary rather than by eyeballing
a canvas - tools/wasm_check.mjs runs N frames in node and diffs the
framebuffer against a --shot PPM:
cgb-acid2 @60, dmg-acid2 @60, cpu_instrs @600, mem_timing-2 @700,
pky.gbc @400 with an imported .sav, cgb-acid2 @120 through the full
CGB BIOS boot animation -> all pixel-identical
Also smoke-tested in headless chromium over CDP: core instantiates, ROM
loads through the file input, canvas animates (7 distinct frames over
5.6s), keyboard reaches the joypad, 60fps, and a 32KB save lands in
localStorage.
No audio: the APU is still stubbed.
|