diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs index 965230e0d64150d2b87907dd290b1cbab3501b61..a6133ff775e3da1bb1e2dbb4f8e530c4171a409f 100644 --- a/examples/sdl/src/main.rs +++ b/examples/sdl/src/main.rs @@ -87,7 +87,7 @@ fn main() { //game_boy.load_rom_file("../../res/roms.prop/alleyway.gb"); //game_boy.load_rom_file("../../res/roms/firstwhite.gb"); - //game_boy.load_rom_file("../../res/roms/opus5.gb"); + game_boy.load_rom_file("../../res/roms/opus5.gb"); //game_boy.load_rom_file("../../res/roms/paradius/cpu/01-special.gb"); // PASSED //game_boy.load_rom_file("../../res/roms/paradius/cpu/02-interrupts.gb"); // NO FINISH diff --git a/examples/web/debug.css b/examples/web/debug.css index 97a8a6a149b80b43597595730dd47f9b9bdec6f6..e4b0114329f1ddc881d99223109f87837910388c 100644 --- a/examples/web/debug.css +++ b/examples/web/debug.css @@ -1,4 +1,4 @@ .debug > .canvas-tiles { background-color: #000000; - width: 480px; + width: 256px; } diff --git a/examples/web/index.html b/examples/web/index.html index c948d327089ff391f82280e1d73ef322b21a5a6c..521cf0df8970077317fb0151a51f42de4fc56718 100644 --- a/examples/web/index.html +++ b/examples/web/index.html @@ -69,7 +69,7 @@ <div id="separator-keyboard" class="separator" style="display: none;"></div> <div id="section-debug" class="section" style="display: none;"> <div id="debug" class="debug"> - <canvas id="canvas-tiles" class="canvas-tiles" width="512" height="512"></canvas> + <canvas id="canvas-tiles" class="canvas-tiles" width="128" height="128"></canvas> </div> </div> <div id="separator-debug" class="separator" style="display: none;"></div> diff --git a/examples/web/index.ts b/examples/web/index.ts index eefb20faad9675f298b1e9f20c3e6a99457b13f0..c2b983a88ba011715b67405f5f8841d93932e7be 100644 --- a/examples/web/index.ts +++ b/examples/web/index.ts @@ -301,7 +301,7 @@ const start = async ({ // resets the Game Boy engine to restore it into // a valid state ready to be used - //state.gameBoy.reset_hard(); @todo + state.gameBoy.reset(); state.gameBoy.load_boot_static(); state.gameBoy.load_rom(romData); @@ -533,6 +533,43 @@ const registerButtons = () => { sectionNarrative.style.display = "none"; separatorNarrative.style.display = "none"; buttonDebug.classList.add("enabled"); + + const canvasTiles = document.getElementById("canvas-tiles") as HTMLCanvasElement; + const canvasTilesCtx = canvasTiles.getContext("2d"); + + const canvasImage = canvasTilesCtx.createImageData(canvasTiles.width, canvasTiles.height); + const videoBuff = new DataView(canvasImage.data.buffer); + + const drawSprite = (index: number, format: PixelFormat = PixelFormat.RGB) => { + const pixels = state.gameBoy.get_tile_buffer(index); + const line = Math.floor(index / 16); + const column = index % 16; + console.info(`${canvasTiles.width}`); + let offset = ((line * canvasTiles.width * 8) + (column * 8)) * PixelFormat.RGBA; + console.info(`${offset}`); + let counter = 0; + for (let index = 0; index < pixels.length; index += format) { + const color = + (pixels[index] << 24) | + (pixels[index + 1] << 16) | + (pixels[index + 2] << 8) | + (format == PixelFormat.RGBA ? pixels[index + 3] : 0xff); + videoBuff.setUint32(offset, color); + + counter++; + if (counter == 8) { + counter = 0; + offset += (canvasTiles.width - 7) * PixelFormat.RGBA; + } else { + offset += PixelFormat.RGBA; + } + } + canvasTilesCtx.putImageData(canvasImage, 0, 0); + } + + for (let index = 0; index < 256; index++) { + drawSprite(index); + } } }); @@ -698,7 +735,7 @@ const initCanvas = async () => { state.videoBuff = new DataView(state.image.data.buffer); }; -const updateCanvas = (pixels: Uint8Array, format: PixelFormat) => { +const updateCanvas = (pixels: Uint8Array, format: PixelFormat = PixelFormat.RGB) => { let offset = 0; for (let index = 0; index < pixels.length; index += format) { const color = @@ -726,7 +763,7 @@ const clearCanvas = async ( ); // in case an image was requested then uses that to load - // an image at the center of the screen + // an image at the center of the screen properly scaled if (image) { const img = await new Promise<HTMLImageElement>((resolve) => { const img = new Image(); diff --git a/src/cpu.rs b/src/cpu.rs index e7f879abecfe0e23e1bcc8957e331248584f0528..f4f874e9845475ebd1cca115371b199b8728c3e7 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -77,6 +77,25 @@ impl Cpu { } } + pub fn reset(&mut self) { + self.pc = 0x0; + self.sp = 0x0; + self.a = 0x0; + self.b = 0x0; + self.c = 0x0; + self.d = 0x0; + self.e = 0x0; + self.h = 0x0; + self.l = 0x0; + self.ime = false; + self.zero = false; + self.sub = false; + self.half_carry = false; + self.carry = false; + self.halted = false; + self.ticks = 0; + } + pub fn clock(&mut self) -> u8 { if self.halted { return 4; diff --git a/src/gb.rs b/src/gb.rs index b6eedd77fb24c9b387384c70da1ccd374bfc4899..4ef33166161da136d1fe657f1d25d227fb14c793 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -29,6 +29,12 @@ impl GameBoy { Self { cpu: cpu } } + pub fn reset(&mut self) { + self.ppu().reset(); + self.mmu().reset(); + self.cpu.reset(); + } + pub fn pc(&self) -> u16 { self.cpu.pc() } @@ -112,6 +118,11 @@ impl GameBoy { pub fn get_tile(&mut self, index: usize) -> Tile { self.ppu().tiles()[index] } + + pub fn get_tile_buffer(&mut self, index: usize) -> Vec<u8> { + let tile = self.get_tile(index); + tile.palette_buffer(self.ppu().palette()) + } } impl GameBoy { diff --git a/src/mmu.rs b/src/mmu.rs index 770a651b261de52a1a2fdd5f0e0a5040c76ab934..4a67cda4da0ab7d0a60b29f11da193354a4ddfb1 100644 --- a/src/mmu.rs +++ b/src/mmu.rs @@ -28,6 +28,14 @@ impl Mmu { } } + pub fn reset(&mut self) { + self.boot_active = true; + self.boot = [0u8; BIOS_SIZE]; + self.rom = [0u8; ROM_SIZE]; + self.ram = [0u8; RAM_SIZE]; + self.eram = [0u8; ERAM_SIZE]; + } + pub fn ppu(&mut self) -> &mut Ppu { &mut self.ppu } @@ -159,7 +167,7 @@ impl Mmu { println!("GOING TO START DMA transfer to 0x{:x}00", value); let data = self.read_many((value as u16) << 8, 160); self.write_many(0xfe00, &data); - println!("FINISHED DMA transfer") + println!("FINISHED DMA transfer"); } _ => self.ppu.write(addr, value), } diff --git a/src/ppu.rs b/src/ppu.rs index 17639f5d5507e4e7ddf3f2c648de5622b4b85ccb..8159ad45a56187d2e79aa0d20eab1e5c4400e146 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -26,6 +26,10 @@ pub const FRAME_BUFFER_SIZE: usize = DISPLAY_WIDTH * DISPLAY_HEIGHT * RGB_SIZE; // with the size of RGB (3 bytes). pub type Pixel = [u8; RGB_SIZE]; +/// Defines a type that represents a color palette +/// within the Game Boy context. +pub type Palette = [Pixel; PALETTE_SIZE]; + /// Represents a tile within the Game Boy context, /// should contain the pixel buffer of the tile. #[cfg_attr(feature = "wasm", wasm_bindgen)] @@ -34,6 +38,7 @@ pub struct Tile { buffer: [u8; 64], } +#[cfg_attr(feature = "wasm", wasm_bindgen)] impl Tile { pub fn get(&self, x: usize, y: usize) -> u8 { self.buffer[y * 8 + x] @@ -48,6 +53,15 @@ impl Tile { } } +impl Tile { + pub fn palette_buffer(&self, palette: Palette) -> Vec<u8> { + self.buffer + .iter() + .flat_map(|p| palette[*p as usize]) + .collect() + } +} + impl Display for Tile { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut buffer = String::new(); @@ -87,11 +101,11 @@ pub struct Ppu { tiles: [Tile; TILE_COUNT], /// The palette of colors that is currently loaded in Game Boy /// and used for background (tiles). - palette: [Pixel; PALETTE_SIZE], + palette: Palette, // The palette that is going to be used for sprites/objects #0. - palette_obj_0: [Pixel; PALETTE_SIZE], + palette_obj_0: Palette, // The palette that is going to be used for sprites/objects #1. - palette_obj_1: [Pixel; PALETTE_SIZE], + palette_obj_1: Palette, /// The scroll Y register that controls the Y offset /// of the background. scy: u8, @@ -181,6 +195,35 @@ impl Ppu { } } + pub fn reset(&mut self) { + self.frame_buffer = Box::new([0u8; DISPLAY_WIDTH * DISPLAY_HEIGHT * RGB_SIZE]); + self.vram = [0u8; VRAM_SIZE]; + self.hram = [0u8; HRAM_SIZE]; + self.tiles = [Tile { buffer: [0u8; 64] }; TILE_COUNT]; + self.palette = [[0u8; RGB_SIZE]; PALETTE_SIZE]; + self.palette_obj_0 = [[0u8; RGB_SIZE]; PALETTE_SIZE]; + self.palette_obj_1 = [[0u8; RGB_SIZE]; PALETTE_SIZE]; + self.scy = 0x0; + self.scx = 0x0; + self.ly = 0x0; + self.lyc = 0x0; + self.mode = PpuMode::OamRead; + self.mode_clock = 0; + self.switch_bg = false; + self.switch_obj = false; + self.obj_size = false; + self.bg_map = false; + self.bg_tile = false; + self.switch_window = false; + self.window_map = false; + self.switch_lcd = false; + self.stat_hblank = false; + self.stat_vblank = false; + self.stat_oam = false; + self.stat_lyc = false; + self.int_vblank = false; + } + pub fn clock(&mut self, cycles: u8) { if !self.switch_lcd { return; @@ -342,6 +385,18 @@ impl Ppu { self.tiles } + pub fn palette(&self) -> Palette { + self.palette + } + + pub fn palette_obj_0(&self) -> Palette { + self.palette_obj_0 + } + + pub fn palette_obj_1(&self) -> Palette { + self.palette_obj_1 + } + pub fn int_vblank(&self) -> bool { self.int_vblank }