diff --git a/README.md b/README.md index 3d55f45a3648454fdb995aab486e1cf959fbe27c..784a120a2b52f288703db581240b0fc63d66117b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A Game Boy emulator that is written in Rust 🦀. * Game Boy Printer emulation * Support for multiple MBCs: MBC1, MBC2, MBC3, and MBC5 * Variable CPU clock speed -* Accurate PPU - passes [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) tests +* Accurate PPU - passes [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) and [cgb-acid2](https://github.com/mattcurrie/cgb-acid2) tests For the Web front-end... diff --git a/frontends/sdl/res/test/cgb_acid2.png b/frontends/sdl/res/test/cgb_acid2.png new file mode 100644 index 0000000000000000000000000000000000000000..6d23ed4468bb4e3fc45019b1ac6fb7aea4642067 Binary files /dev/null and b/frontends/sdl/res/test/cgb_acid2.png differ diff --git a/frontends/sdl/src/test.rs b/frontends/sdl/src/test.rs index 154a5a34c8b8a7d4588d2ee48451d1f2d0d86765..7fa014f81ad9c1f2ce1eb6f7407d0bd5848157a4 100644 --- a/frontends/sdl/src/test.rs +++ b/frontends/sdl/src/test.rs @@ -26,6 +26,7 @@ pub fn compare_images(source_pixels: &[u8], target_path: &str) -> bool { #[cfg(test)] mod tests { use boytacean::{ + gb::GameBoyMode, ppu::FRAME_BUFFER_SIZE, test::{run_image_test, TestOptions}, }; @@ -54,6 +55,20 @@ mod tests { assert_eq!(image_result, true); } + #[test] + fn test_cgb_acid2() { + let result: [u8; FRAME_BUFFER_SIZE] = run_image_test( + "../../res/roms/test/cgb_acid2.gbc", + Some(50000000), + TestOptions { + mode: Some(GameBoyMode::Cgb), + ..Default::default() + }, + ); + let image_result = compare_images(&result, "res/test/cgb_acid2.png"); + assert_eq!(image_result, true); + } + #[test] fn test_firstwhite() { let result: [u8; FRAME_BUFFER_SIZE] = run_image_test( diff --git a/src/ppu.rs b/src/ppu.rs index 9e20dffeb1bfcdf3821973eb25e004381ca8201c..24fbe4f586997718d8b8fbbb9d2d1cf41ba37ae2 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -285,6 +285,11 @@ pub struct Ppu { /// processed set of pixels ready to be displayed on screen. pub frame_buffer: Box<[u8; FRAME_BUFFER_SIZE]>, + /// The buffer that will control the background to OAM + /// priority, allowing the background to be drawn over + /// the sprites/objects if necessary. + priority_buffer: Box<[bool; COLOR_BUFFER_SIZE]>, + /// Video dedicated memory (VRAM) where both the tiles and /// the sprites/objects are going to be stored. vram: [u8; VRAM_SIZE], @@ -499,6 +504,7 @@ impl Ppu { Self { color_buffer: Box::new([0u8; COLOR_BUFFER_SIZE]), frame_buffer: Box::new([0u8; FRAME_BUFFER_SIZE]), + priority_buffer: Box::new([false; COLOR_BUFFER_SIZE]), vram: [0u8; VRAM_SIZE], hram: [0u8; HRAM_SIZE], oam: [0u8; OAM_SIZE], @@ -555,6 +561,7 @@ impl Ppu { pub fn reset(&mut self) { self.color_buffer = Box::new([0u8; COLOR_BUFFER_SIZE]); self.frame_buffer = Box::new([0u8; FRAME_BUFFER_SIZE]); + self.priority_buffer = Box::new([false; COLOR_BUFFER_SIZE]); self.vram = [0u8; VRAM_SIZE_CGB]; self.hram = [0u8; HRAM_SIZE]; self.vram_bank = 0x0; @@ -1196,6 +1203,10 @@ impl Ppu { let mut xflip = tile_attr.xflip; let mut yflip = tile_attr.yflip; + // obtains the value the BG-to-OAM priority to be used in the computation + // of the final pixel value (CGB only) + let mut priority = tile_attr.priority; + // increments the tile index value by the required offset for the VRAM // bank in which the tile is stored, this is only required for CGB mode tile_index += tile_attr.vram_bank as usize * TILE_COUNT_DMG; @@ -1240,6 +1251,12 @@ impl Ppu { self.frame_buffer[frame_offset + 1] = color[1]; self.frame_buffer[frame_offset + 2] = color[2]; + // updates the priority buffer with the current pixel + // the priority is only set in case the priority of + // the background (over OAM) is set in the attributes + // and the pixel is not transparent + self.priority_buffer[color_offset] = priority && pixel > 0; + // increments the current tile X position in drawing x += 1; @@ -1269,6 +1286,7 @@ impl Ppu { palette = &self.palettes_color_bg[tile_attr.palette as usize]; xflip = tile_attr.xflip; yflip = tile_attr.yflip; + priority = tile_attr.priority; tile_index += tile_attr.vram_bank as usize * TILE_COUNT_DMG; } @@ -1540,7 +1558,13 @@ impl Ppu { // window should be drawn over or if the underlying pixel // is transparent (zero value) meaning there's no background // or window for the provided pixel - let is_visible = obj_over || self.color_buffer[color_offset as usize] == 0; + let mut is_visible = obj_over || self.color_buffer[color_offset as usize] == 0; + + // additionally (in CCG mode) the object is only considered to + // be visible if the priority buffer is not set for the current + // pixel, this means that the background is capturing priority + // by having the BG-to-OAM priority bit set in the bg map attributes + is_visible &= always_over || !self.priority_buffer[color_offset as usize]; // determines if the current pixel has priority over a possible // one that has been drawn by a previous object, this happens diff --git a/src/test.rs b/src/test.rs index c9e0369833c85c1896cf792d17774dfc5dcfa78b..ac1cd49725defdab96b01ecaec1e4aad15eaa0b5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,16 +1,21 @@ -use crate::{devices::buffer::BufferDevice, gb::GameBoy, ppu::FRAME_BUFFER_SIZE}; +use crate::{ + devices::buffer::BufferDevice, + gb::{GameBoy, GameBoyMode}, + ppu::FRAME_BUFFER_SIZE, +}; #[derive(Default)] pub struct TestOptions { - ppu_enabled: Option<bool>, - apu_enabled: Option<bool>, - dma_enabled: Option<bool>, - timer_enabled: Option<bool>, + pub mode: Option<GameBoyMode>, + pub ppu_enabled: Option<bool>, + pub apu_enabled: Option<bool>, + pub dma_enabled: Option<bool>, + pub timer_enabled: Option<bool>, } pub fn build_test(options: TestOptions) -> GameBoy { let device = Box::<BufferDevice>::default(); - let mut game_boy = GameBoy::new(None); + let mut game_boy = GameBoy::new(options.mode); game_boy.set_ppu_enabled(options.ppu_enabled.unwrap_or(true)); game_boy.set_apu_enabled(options.apu_enabled.unwrap_or(true)); game_boy.set_dma_enabled(options.dma_enabled.unwrap_or(true));