CAMLBOY is a Game Boy emulator that runs in the browser. It is written in OCaml and compiled to JavaScript using js_of_ocaml.
Try it out in our demo page!
- English: https://linoscope.github.io/writing-a-game-boy-emulator-in-ocaml/
- 日本語: https://qiita.com/linoscope/items/244d931aaae07df2c27e
- Playable in the browser of middle-tier/high-tier mobile devices
- Readable/maintainable code that follows OCaml's best practices
- Achieve stable 60fps in low-tier mobile devices
- Serve as a benchmark target for various compile backends, similar to optcarrot in the Ruby world. Would be especially interesting if we can compare the performance of various JS/wasm backends, such as js_of_ocaml, Rescript, Melange, and ocaml-wasm.
- Run all games with high accuracy
- Optimize performance to the limit at the expense of code readability
- Runs with "playable" FPS in middle-tier mobile devices. (It runs at 60FPS for most games in my Galaxy S9, a smartphone released in 2018)
- Runs with ~1000FPS, with UI, in native
- Supports headless benchmarking mode, both for native and web, that runs without UI
- Passes various test ROMs such as Blargg's
cpu_insrts.gb
andinstr_timing.gb
(tests using Blargg's test ROMs can be found here, and tests using Mooneye's test ROMs can be found here).
We ran the first 1500 frames of Tobu Tobu Girl in headless mode (i.e., without UI) for ten times each and calculated the average FPS. The error bars represent the standard deviation. See benchmark.md
for details about the environment/commands used for the benchmark.1
Here is a rough sketch of the various modules and their relationship. You can find details in the accompanied blog post.
lib
- Main emulator codebin
- UI codeweb
- Websdl2
- SDL2
test
unit_tests
- Unit testsrom_tests
- Integration tests that use test roms
resource
games
- Game romstest_roms
- Test roms used inrom_tests
- Cartridge based save
- Audio Processing Unit (APU)
- Rescript backend
- MBC5
- Game Boy Color mode
Install opam, OCaml's package manager, if you haven't yet.
# Clone repository
git clone https://github.com/linoscope/CAMLBOY.git
# cd into repository
cd CAMLBOY
# Create local switch for the repository
opam switch create . ocaml-base-compiler.4.13.1
eval $(opam env)
# Install system packages required by opam packages (SDL, etc)
opam pin add camlboy.dev . --no-action
opam depext camlboy
# Install opam dependencies
opam install . --deps-only --with-test
# Build
$ dune build
# Usage: main.exe [--mode {default|withtrace|no-throttle}] <rom_path>
# For example:
$ dune exec bin/sdl2/main.exe -- resource/games/tobu.gb
# Build
$ dune build
# Serve `_build/default/bin/web` using some server. For example, run the following with python:
$ python -m http.server 8000 --directory _build/default/bin/web
# Now open `localhost:8000` in the browser
# Usage: bench.exe [--frames <frames>] <rom_path>
# For example:
$ dune exec bin/sdl2/bench.exe -- resource/games/tobu.gb --frames 1500
ROM path: resource/games/tobu.gb
Frames: 1500
Duration: 1.453315
FPS: 1032.123098
First, follow the steps in "How to run with UI - js_of_ocaml frontend" above. Now open http://localhost:8000/bench.html?frames=<frames>&rom_path=<rom_path>
. For example, if you open http://localhost:8000/bench.html?frames=1500&rom_path=./tobu.gb you should see something like this:
# Run all tests:
$ dune runtest
# Run unit tests only:
$ dune runtest test/unit_tests/
# Run integration tests (tests that use test ROMs):
$ dune runtest test/rom_tests/
- The Bouncing Ball
- Tobu Tobu Girl
- Retroid
- Into The Blue
- Wishing Sarah
- Rocket Man Demo
- SHEET IT UP
- Cavern
Footnotes
-
Note that we can not use this benchmark to compare the FPS with other Game Boy emulators. This is because the performance of an emulator depends significantly on how accurate it is and how much functionality it has. For example, CALMBOY does not implement the APU (Audio Processing Unit), so there is no point in comparing its FPS with emulators with APU support. ↩