SlideShare a Scribd company logo
Build Gameboy Emulator in Rust and
WebAssembly
Author: Yodalee
<lc85301@gmail.com>
Outline
1. Inside Gameboy
2. Gameboy Emulator in Rust
3. Migrate Rust to WebAssembly
Star Me on Github!
Source Code
https://github.com/yodalee/ruGameboy
Note: (Traditional Chinese)
https://yodalee.me/tags/gameboy/
Inside Gameboy
Gameboy
4MHz 8-bit Sharp LR35902
32KB Cartridge
8KB SRAM
8KB Video RAM
160x144 LCD screen
CPU Register
D15..D8 D7..D0
A F
B C
D E
H L
D15..D0
SP (stack pointer)
PC (program counter)
Flag Register
Z N H C 0 0 0 0
Z - Zero Flag
N - Subtract Flag
H - Half Carry Flag
C - Carry Flag
0 - Not used, always zero
CPU Instruction
4MHz CPU based on Intel 8080 and Z80.
245 instructions and another 256 extended (CB) instructions
Memory Layout
Start Size Description
0x0000 32 KB Cartridge ROM
0x8000 6 KB GPU Tile Content
0x9800 2 KB GPU Background Index
0xC000 8KB RAM
0xFE00 160 Bytes GPU Foreground Index
GPU Tile
1 tile = 8x8 = 64 pixels.
256
256
2 bits/pixel
1 tile = 16 bytes
Virtual Screen = 1024 Tiles
GPU Background
...
VRAM 0x8000-0xA000 (8K)
1. 0x8000-0x9000 or 0x8800-0x9800 => 4KB / 16B = 256 Tiles
2. 0x9800-0x9C00 or 0x9C00-A000 => 1KB = Tiles index
0x9800 - 0x9C00
Tile Index Table
From 0xFE00 to 0xFE9F = 160 bytes
4 bytes per Sprite => 40 Sprite
Sprite Size = 1 Tile = 8x8
GPU Sprite Foreground
Byte 0 Offset X
Byte 1 Offset Y
Byte 2 Tile Index
Byte 3 Flags: x flip, y flip, etc.
Note the width:
8 pixels
Line 0
Line 1
Line 2
Line 143
HBlank
VBlank
GPU Timing
160
144
Mode Cycle
Scanline Sprite 80
Scanline BG 172
HBlank 204
VBlank 4560 (10 lines)
Total 70224
4 MHz clock
70224 cycles ~= 0.0176s ~= 60 Hz
Gameboy Emulator in Rust
Emulator Architecture
GPU Cartridge RAM ….
Bus Register
VM
CPU
Display Buffer
Implement Register
F register All Register Interface
pub struct
FlagRegister {
pub zero: bool,
pub subtract: bool,
pub half_carry: bool,
pub carry: bool
}
pub struct Register {
pub a: u8,
pub b: u8,
pub c: u8,
pub d: u8,
pub e: u8,
pub f: FlagRegister,
pub h: u8,
pub l: u8,
}
impl Register {
fn get_hl() -> u16 {
(self.h as u16) << 8 | self.l as u16
}
fn set_hl(&mut self, value: u16) {
self.h = ((value >> 8) & 0xff) as u8;
self.l = (value & 0xff) as u8;
}
//...
}
Encode Instruction to Enum
Register X,Y Move Register X to Y Byte to Instruction
enum Target
{
A,
B,
C,
...
}
enum Instruction {
NOP
LDRR(Target, Target)
ADD(Target)
...
}
impl Instruction {
fn from_byte(u8) -> Instruction {
0x00 => Instruction::NOP,
0x01 => //…
0x02 => //…
//…
}
}
Implement CPU
CPU Step
pub struct Cpu {
regs: Register,
sp: u16,
pub pc: u16,
pub bus: Bus,
}
let byte = self.load(self.pc);
self.pc + 1;
let inst = Instruction::from_byte(byte);
match inst {
Instruction::JPHL => self.pc = self.regs.get_hl(),
Instruction::LDRR(source, target) => {
match (&source, &target) {
(&Target::C, &Target::B) => self.regs.b = self.regs.c,
//...
Implement Bus
Device Load/Store
pub trait Device {
fn load(&self, addr: u16) ->
Result<u8, ()>;
fn store(&mut self, addr: u16,
value: u8) -> Result<(), ()>;
}
impl Bus {
fn load(&self, addr: u16) -> Result<u8, ()> {
match addr {
CATRIDGE_START ..= CATRIDGE_END =>
self.cartridge.load(addr),
VRAM_START ..= VRAM_END =>
self.gpu.load(addr),
RAM_START ..= RAM_END =>
self.ram.load(addr),
//...
Implement Gpu
Sprite Gpu Render
struct Sprite {
tile_idx: u8,
x: isize,
y: isize,
// flag...
}
struct Gpu {
sprite: [Sprite:40]
vram: Vec<u8>,
oam: Vec<u8>,
//...
}
impl Device for Gpu {
//...
fn build_background(&mut self, buffer: &mut
Vec<u32>) {
for row in 0..HEIGHT {
for col in 0..WIDTH {
let tile_addr = row * 32 + col;
let tile_idx = self.vram[tile_addr];
let pixels = self.get_tile(tile_idx);
buffer.splice(start..end, pixels.iter()...);
VM
Screen built with minifb.
Loop:
1. run CPU until VBlank
2. Render Screen.
Let GPU fill display buffer Vec<u32>
Migrate Rust to WebAssembly
Rust x WebAssembly
https://rustwasm.github.io/book/
Tool needed:
1. rustup install
wasm32-unknown-unknown
2. wasm-pack
3. cargo-generate
4. npm
Migration Step
1. cargo generate --git https://github.com/rustwasm/wasm-pack-template
2. Expose Interface to Javascript
3. wasm-pack build
4. Import WebAssembly from Javascript
Done
https://github.com/yodalee/
wasm_gameboy
Expose Interface to Javascript
Magic Word
#[wasm_bindgen]
wasm_bindgen
// rust
#[wasm_bindgen]
pub struct Gameboy {
cartridge: Vec<u8>,
vm: Option<Vm>
}
// javascript
import Gameboy from "wasmgb"
Gameboy.new()
wasm_bindgen
// rust
#[wasm_bindgen]
impl Gameboy {
pub fn get_buffer(&self) -> *const u32 {
self.vm.buffer.as_ptr()
}
}
// javascript
import memory from "wasmgb_bg"
const buffer = gameboy.get_buffer();
const pixels = new
Uint32Array(memory.buffer, buffer,
width*height);
Import WebAssembly from Javascript
<body>
<input type="file" id="file-uploader"/>
<canvas id="gameboy-canvas"></canvas>
<pre id="gameboy-log"></pre>
<script src="./bootstrap.js"></script>
</body>
Import WebAssembly from Javascript
reader.onload = function() {
const cartridge = gameboy.get_cartridge();
var bytes = new Uint8Array(reader.result);
const m_cartridge = new Uint8Array(memory.buffer, cartridge, 0x8000);
// set cartridge
for (let idx = 0; idx < 0x8000; idx++) {
m_cartridge[idx] = bytes[idx];
}
gameboy.set_cartridge();
Import WebAssembly from Javascript
const renderLoop = () => {
drawPixels();
gameboy.step();
}
const drawPixels = () => {
const buffer = gameboy.get_buffer();
const pixels = new Uint32Array(memory.buffer, buffer, width * height);
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
if (pixels[row * width + col] == WHITE) { //...
Done
Conclusion
Future Work
Features to implement:
● Right now only Tetris can work (゚д゚;)
● Implement Sound
Problem to solve:
● How to emulate program in correct timing?
● How to debug efficiently?
Conclusion
1. We learn how to build emulator by building it.
2. A Rust program can be migrated to WebAssembly really quick.
Thanks for Listening

More Related Content

PDF
Android HAL Introduction: libhardware and its legacy
PPT
Virtualização
PDF
Apostila Html completa e simples
PPTX
AtoM's Command Line Tasks - An Introduction
PDF
Admincenter
PPTX
Aula 2 - Sistemas operacionais - Windows
PPTX
Hardware e redes de computadores (Componente, tipos de redes e topologias)
PPT
Working with WebSPHINX Web Crawler
Android HAL Introduction: libhardware and its legacy
Virtualização
Apostila Html completa e simples
AtoM's Command Line Tasks - An Introduction
Admincenter
Aula 2 - Sistemas operacionais - Windows
Hardware e redes de computadores (Componente, tipos de redes e topologias)
Working with WebSPHINX Web Crawler

Similar to Gameboy emulator in rust and web assembly (20)

PDF
JavaScript on the GPU
PDF
Qemu JIT Code Generator and System Emulation
PDF
Introduction to CUDA
PPTX
Introduction to ARM
KEY
Emulating With JavaScript
PDF
Skiron - Experiments in CPU Design in D
PDF
Embedded Rust and more
PDF
Study on Android Emulator
PDF
Tft touch screen manufacturers
PPT
CO_Chapter2.ppt
PDF
How to control physical devices with mruby
PDF
Reading php terminal-gameboy-emulator
DOCX
2.1 ### uVision Project, (C) Keil Software .docx
PDF
OptimizingARM
PDF
mRuby - Powerful Software for Embedded System Development
PDF
Embedded system Design introduction _ Karakola
PDF
Automatic Vectorization in ART (Android RunTime) - SFO17-216
PDF
Creating an Embedded System Lab
PDF
How old consoles work!
PDF
There is more to C
JavaScript on the GPU
Qemu JIT Code Generator and System Emulation
Introduction to CUDA
Introduction to ARM
Emulating With JavaScript
Skiron - Experiments in CPU Design in D
Embedded Rust and more
Study on Android Emulator
Tft touch screen manufacturers
CO_Chapter2.ppt
How to control physical devices with mruby
Reading php terminal-gameboy-emulator
2.1 ### uVision Project, (C) Keil Software .docx
OptimizingARM
mRuby - Powerful Software for Embedded System Development
Embedded system Design introduction _ Karakola
Automatic Vectorization in ART (Android RunTime) - SFO17-216
Creating an Embedded System Lab
How old consoles work!
There is more to C
Ad

More from Yodalee (8)

PDF
COSCUP2023 RSA256 Verilator.pdf
PDF
rrxv6 Build a Riscv xv6 Kernel in Rust.pdf
PDF
Make A Shoot ‘Em Up Game with Amethyst Framework
PDF
Build Yourself a Nixie Tube Clock
PDF
Use PEG to Write a Programming Language Parser
PDF
Introduction to nand2 tetris
PDF
Office word skills
PDF
Git: basic to advanced
COSCUP2023 RSA256 Verilator.pdf
rrxv6 Build a Riscv xv6 Kernel in Rust.pdf
Make A Shoot ‘Em Up Game with Amethyst Framework
Build Yourself a Nixie Tube Clock
Use PEG to Write a Programming Language Parser
Introduction to nand2 tetris
Office word skills
Git: basic to advanced
Ad

Recently uploaded (20)

PDF
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
PPTX
IOT PPTs Week 10 Lecture Material.pptx of NPTEL Smart Cities contd
PDF
Evaluating the Democratization of the Turkish Armed Forces from a Normative P...
PDF
R24 SURVEYING LAB MANUAL for civil enggi
PDF
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks
PDF
Model Code of Practice - Construction Work - 21102022 .pdf
PDF
composite construction of structures.pdf
PDF
SM_6th-Sem__Cse_Internet-of-Things.pdf IOT
PPTX
UNIT-1 - COAL BASED THERMAL POWER PLANTS
PPTX
bas. eng. economics group 4 presentation 1.pptx
PDF
Digital Logic Computer Design lecture notes
PDF
The CXO Playbook 2025 – Future-Ready Strategies for C-Suite Leaders Cerebrai...
PDF
Embodied AI: Ushering in the Next Era of Intelligent Systems
PDF
TFEC-4-2020-Design-Guide-for-Timber-Roof-Trusses.pdf
PPTX
MCN 401 KTU-2019-PPE KITS-MODULE 2.pptx
PPTX
Welding lecture in detail for understanding
PDF
July 2025 - Top 10 Read Articles in International Journal of Software Enginee...
PPTX
web development for engineering and engineering
PDF
BMEC211 - INTRODUCTION TO MECHATRONICS-1.pdf
PPTX
CH1 Production IntroductoryConcepts.pptx
Mitigating Risks through Effective Management for Enhancing Organizational Pe...
IOT PPTs Week 10 Lecture Material.pptx of NPTEL Smart Cities contd
Evaluating the Democratization of the Turkish Armed Forces from a Normative P...
R24 SURVEYING LAB MANUAL for civil enggi
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks
Model Code of Practice - Construction Work - 21102022 .pdf
composite construction of structures.pdf
SM_6th-Sem__Cse_Internet-of-Things.pdf IOT
UNIT-1 - COAL BASED THERMAL POWER PLANTS
bas. eng. economics group 4 presentation 1.pptx
Digital Logic Computer Design lecture notes
The CXO Playbook 2025 – Future-Ready Strategies for C-Suite Leaders Cerebrai...
Embodied AI: Ushering in the Next Era of Intelligent Systems
TFEC-4-2020-Design-Guide-for-Timber-Roof-Trusses.pdf
MCN 401 KTU-2019-PPE KITS-MODULE 2.pptx
Welding lecture in detail for understanding
July 2025 - Top 10 Read Articles in International Journal of Software Enginee...
web development for engineering and engineering
BMEC211 - INTRODUCTION TO MECHATRONICS-1.pdf
CH1 Production IntroductoryConcepts.pptx

Gameboy emulator in rust and web assembly

  • 1. Build Gameboy Emulator in Rust and WebAssembly Author: Yodalee <lc85301@gmail.com>
  • 2. Outline 1. Inside Gameboy 2. Gameboy Emulator in Rust 3. Migrate Rust to WebAssembly
  • 3. Star Me on Github! Source Code https://github.com/yodalee/ruGameboy Note: (Traditional Chinese) https://yodalee.me/tags/gameboy/
  • 5. Gameboy 4MHz 8-bit Sharp LR35902 32KB Cartridge 8KB SRAM 8KB Video RAM 160x144 LCD screen
  • 6. CPU Register D15..D8 D7..D0 A F B C D E H L D15..D0 SP (stack pointer) PC (program counter) Flag Register Z N H C 0 0 0 0 Z - Zero Flag N - Subtract Flag H - Half Carry Flag C - Carry Flag 0 - Not used, always zero
  • 7. CPU Instruction 4MHz CPU based on Intel 8080 and Z80. 245 instructions and another 256 extended (CB) instructions
  • 8. Memory Layout Start Size Description 0x0000 32 KB Cartridge ROM 0x8000 6 KB GPU Tile Content 0x9800 2 KB GPU Background Index 0xC000 8KB RAM 0xFE00 160 Bytes GPU Foreground Index
  • 9. GPU Tile 1 tile = 8x8 = 64 pixels. 256 256 2 bits/pixel 1 tile = 16 bytes Virtual Screen = 1024 Tiles
  • 10. GPU Background ... VRAM 0x8000-0xA000 (8K) 1. 0x8000-0x9000 or 0x8800-0x9800 => 4KB / 16B = 256 Tiles 2. 0x9800-0x9C00 or 0x9C00-A000 => 1KB = Tiles index 0x9800 - 0x9C00 Tile Index Table
  • 11. From 0xFE00 to 0xFE9F = 160 bytes 4 bytes per Sprite => 40 Sprite Sprite Size = 1 Tile = 8x8 GPU Sprite Foreground Byte 0 Offset X Byte 1 Offset Y Byte 2 Tile Index Byte 3 Flags: x flip, y flip, etc. Note the width: 8 pixels
  • 12. Line 0 Line 1 Line 2 Line 143 HBlank VBlank GPU Timing 160 144 Mode Cycle Scanline Sprite 80 Scanline BG 172 HBlank 204 VBlank 4560 (10 lines) Total 70224 4 MHz clock 70224 cycles ~= 0.0176s ~= 60 Hz
  • 14. Emulator Architecture GPU Cartridge RAM …. Bus Register VM CPU Display Buffer
  • 15. Implement Register F register All Register Interface pub struct FlagRegister { pub zero: bool, pub subtract: bool, pub half_carry: bool, pub carry: bool } pub struct Register { pub a: u8, pub b: u8, pub c: u8, pub d: u8, pub e: u8, pub f: FlagRegister, pub h: u8, pub l: u8, } impl Register { fn get_hl() -> u16 { (self.h as u16) << 8 | self.l as u16 } fn set_hl(&mut self, value: u16) { self.h = ((value >> 8) & 0xff) as u8; self.l = (value & 0xff) as u8; } //... }
  • 16. Encode Instruction to Enum Register X,Y Move Register X to Y Byte to Instruction enum Target { A, B, C, ... } enum Instruction { NOP LDRR(Target, Target) ADD(Target) ... } impl Instruction { fn from_byte(u8) -> Instruction { 0x00 => Instruction::NOP, 0x01 => //… 0x02 => //… //… } }
  • 17. Implement CPU CPU Step pub struct Cpu { regs: Register, sp: u16, pub pc: u16, pub bus: Bus, } let byte = self.load(self.pc); self.pc + 1; let inst = Instruction::from_byte(byte); match inst { Instruction::JPHL => self.pc = self.regs.get_hl(), Instruction::LDRR(source, target) => { match (&source, &target) { (&Target::C, &Target::B) => self.regs.b = self.regs.c, //...
  • 18. Implement Bus Device Load/Store pub trait Device { fn load(&self, addr: u16) -> Result<u8, ()>; fn store(&mut self, addr: u16, value: u8) -> Result<(), ()>; } impl Bus { fn load(&self, addr: u16) -> Result<u8, ()> { match addr { CATRIDGE_START ..= CATRIDGE_END => self.cartridge.load(addr), VRAM_START ..= VRAM_END => self.gpu.load(addr), RAM_START ..= RAM_END => self.ram.load(addr), //...
  • 19. Implement Gpu Sprite Gpu Render struct Sprite { tile_idx: u8, x: isize, y: isize, // flag... } struct Gpu { sprite: [Sprite:40] vram: Vec<u8>, oam: Vec<u8>, //... } impl Device for Gpu { //... fn build_background(&mut self, buffer: &mut Vec<u32>) { for row in 0..HEIGHT { for col in 0..WIDTH { let tile_addr = row * 32 + col; let tile_idx = self.vram[tile_addr]; let pixels = self.get_tile(tile_idx); buffer.splice(start..end, pixels.iter()...);
  • 20. VM Screen built with minifb. Loop: 1. run CPU until VBlank 2. Render Screen. Let GPU fill display buffer Vec<u32>
  • 21. Migrate Rust to WebAssembly
  • 22. Rust x WebAssembly https://rustwasm.github.io/book/ Tool needed: 1. rustup install wasm32-unknown-unknown 2. wasm-pack 3. cargo-generate 4. npm
  • 23. Migration Step 1. cargo generate --git https://github.com/rustwasm/wasm-pack-template 2. Expose Interface to Javascript 3. wasm-pack build 4. Import WebAssembly from Javascript Done https://github.com/yodalee/ wasm_gameboy
  • 24. Expose Interface to Javascript Magic Word #[wasm_bindgen]
  • 25. wasm_bindgen // rust #[wasm_bindgen] pub struct Gameboy { cartridge: Vec<u8>, vm: Option<Vm> } // javascript import Gameboy from "wasmgb" Gameboy.new()
  • 26. wasm_bindgen // rust #[wasm_bindgen] impl Gameboy { pub fn get_buffer(&self) -> *const u32 { self.vm.buffer.as_ptr() } } // javascript import memory from "wasmgb_bg" const buffer = gameboy.get_buffer(); const pixels = new Uint32Array(memory.buffer, buffer, width*height);
  • 27. Import WebAssembly from Javascript <body> <input type="file" id="file-uploader"/> <canvas id="gameboy-canvas"></canvas> <pre id="gameboy-log"></pre> <script src="./bootstrap.js"></script> </body>
  • 28. Import WebAssembly from Javascript reader.onload = function() { const cartridge = gameboy.get_cartridge(); var bytes = new Uint8Array(reader.result); const m_cartridge = new Uint8Array(memory.buffer, cartridge, 0x8000); // set cartridge for (let idx = 0; idx < 0x8000; idx++) { m_cartridge[idx] = bytes[idx]; } gameboy.set_cartridge();
  • 29. Import WebAssembly from Javascript const renderLoop = () => { drawPixels(); gameboy.step(); } const drawPixels = () => { const buffer = gameboy.get_buffer(); const pixels = new Uint32Array(memory.buffer, buffer, width * height); for (let row = 0; row < height; row++) { for (let col = 0; col < width; col++) { if (pixels[row * width + col] == WHITE) { //...
  • 30. Done
  • 32. Future Work Features to implement: ● Right now only Tetris can work (゚д゚;) ● Implement Sound Problem to solve: ● How to emulate program in correct timing? ● How to debug efficiently?
  • 33. Conclusion 1. We learn how to build emulator by building it. 2. A Rust program can be migrated to WebAssembly really quick.