From 75ce14f29fc5c463f33e6a6937d475eb031dfc49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Mon, 30 Oct 2023 00:45:12 +0000 Subject: [PATCH] chore: initial implementation of the raw buffer --- frontends/sdl/src/main.rs | 2 +- src/gb.rs | 8 ++++ src/ppu.rs | 82 ++++++++++++++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs index 96835116..5f82603b 100644 --- a/frontends/sdl/src/main.rs +++ b/frontends/sdl/src/main.rs @@ -312,7 +312,7 @@ impl Emulator { fn save_image(&mut self, file_path: &str) { let width = self.system.display_width() as u32; let height = self.system.display_height() as u32; - let pixels = self.system.frame_buffer(); + let pixels = self.system.frame_buffer_raw(); let mut image_buffer: ImageBuffer<Rgb<u8>, Vec<u8>> = ImageBuffer::new(width, height); diff --git a/src/gb.rs b/src/gb.rs index 000ff555..c7f32be2 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -589,6 +589,10 @@ impl GameBoy { self.frame_buffer().to_vec() } + pub fn frame_buffer_raw_eager(&mut self) -> Vec<u8> { + self.frame_buffer_raw().to_vec() + } + pub fn audio_buffer_eager(&mut self, clear: bool) -> Vec<u8> { let buffer = Vec::from(self.audio_buffer().clone()); if clear { @@ -989,6 +993,10 @@ impl GameBoy { self.ppu().frame_buffer_rgb565_u16() } + pub fn frame_buffer_raw(&mut self) -> [u8; FRAME_BUFFER_SIZE] { + self.ppu().frame_buffer_raw() + } + pub fn audio_buffer(&mut self) -> &VecDeque<u8> { self.apu().audio_buffer() } diff --git a/src/ppu.rs b/src/ppu.rs index 61516362..0febb408 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -61,6 +61,12 @@ pub const DISPLAY_SIZE: usize = DISPLAY_WIDTH * DISPLAY_HEIGHT; /// range from 0 to 3. pub const COLOR_BUFFER_SIZE: usize = DISPLAY_SIZE; +/// The size of the buffer that will hold the palette +/// index for each of the pixel in the screen, so that +/// (together with the color buffer) it is possible to +/// rebuild the pixel buffer from scratch. +pub const PALETTE_BUFFER_SIZE: usize = DISPLAY_SIZE; + /// The size of the RGB frame buffer in bytes. pub const FRAME_BUFFER_SIZE: usize = DISPLAY_SIZE * RGB_SIZE; @@ -80,6 +86,8 @@ pub const FRAME_BUFFER_RGB565_SIZE: usize = DISPLAY_SIZE * RGB565_SIZE; /// custom palettes of the Game Boy. pub const PALETTE_COLORS: Palette = [[255, 255, 255], [192, 192, 192], [96, 96, 96], [0, 0, 0]]; +/// Default tile data to be used in the DMG compatibility +/// mode of tile processing (avoids algorithmic forking). pub const DEFAULT_TILE_ATTR: TileData = TileData { palette: 0, vram_bank: 0, @@ -88,6 +96,15 @@ pub const DEFAULT_TILE_ATTR: TileData = TileData { priority: false, }; +/// A basic palette for DMG with the typical high contrast +/// color characteristic of the Game Boy. +pub const BASIC_PALETTE: Palette = [ + [0xff, 0xff, 0xff], + [0xc0, 0xc0, 0xc0], + [0x60, 0x60, 0x60], + [0x00, 0x00, 0x00], +]; + /// Defines the Game Boy pixel type as a buffer /// with the size of RGB (3 bytes). pub type Pixel = [u8; RGB_SIZE]; @@ -311,6 +328,10 @@ pub struct Ppu { /// (from 0 to 3) for all the pixels in the screen. pub color_buffer: Box<[u8; COLOR_BUFFER_SIZE]>, + /// The palette buffer that is going to store the palette + /// index for all the pixels in the screen. + pub palette_buffer: Box<[u8; PALETTE_BUFFER_SIZE]>, + /// The 8 bit based RGB frame buffer with the /// processed set of pixels ready to be displayed on screen. pub frame_buffer: Box<[u8; FRAME_BUFFER_SIZE]>, @@ -533,6 +554,7 @@ impl Ppu { pub fn new(mode: GameBoyMode, gbc: Rc<RefCell<GameBoyConfig>>) -> Self { Self { color_buffer: Box::new([0u8; COLOR_BUFFER_SIZE]), + palette_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], @@ -590,6 +612,7 @@ impl Ppu { pub fn reset(&mut self) { self.color_buffer = Box::new([0u8; COLOR_BUFFER_SIZE]); + self.palette_buffer = Box::new([0u8; PALETTE_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]; @@ -1012,6 +1035,38 @@ impl Ppu { buffer } + /// Obtains the "raw" version of the frame buffer any custom + /// color palette operation applied to it. This is can be an + /// extremely slow operation (in DMG devices) and because of + /// that should be used carefully. + pub fn frame_buffer_raw(&self) -> [u8; FRAME_BUFFER_SIZE] { + if self.gb_mode == GameBoyMode::Dmg { + let mut palettes_color: [Palette; 3] = [ + [[0u8; RGB_SIZE]; PALETTE_SIZE], + [[0u8; RGB_SIZE]; PALETTE_SIZE], + [[0u8; RGB_SIZE]; PALETTE_SIZE], + ]; + for (index, palette_color) in palettes_color.iter_mut().enumerate() { + Self::compute_palette(palette_color, &BASIC_PALETTE, self.palettes[index]); + } + + let mut buffer = [0u8; FRAME_BUFFER_SIZE]; + for (index, pixel) in self.color_buffer.iter().enumerate() { + let palette_index = self.palette_buffer[index]; + let palette = palettes_color[palette_index as usize]; + let color = palette[*pixel as usize]; + + let frame_offset = (index * 3) as usize; + buffer[frame_offset] = color[0]; + buffer[frame_offset + 1] = color[1]; + buffer[frame_offset + 2] = color[2]; + } + buffer + } else { + *self.frame_buffer.clone() + } + } + pub fn vram(&self) -> &[u8; VRAM_SIZE] { &self.vram } @@ -1149,10 +1204,11 @@ impl Ppu { } /// Fills the frame buffer with pixels of the provided color, - /// this method must represent the fastest way of achieving + /// this method should represent the fastest way of achieving /// the fill background with color operation. pub fn fill_frame_buffer(&mut self, color: Pixel) { self.color_buffer.fill(0); + self.palette_buffer.fill(0); for index in (0..self.frame_buffer.len()).step_by(RGB_SIZE) { self.frame_buffer[index] = color[0]; self.frame_buffer[index + 1] = color[1]; @@ -1435,8 +1491,10 @@ impl Ppu { let color = &palette[pixel as usize]; // updates the pixel in the color buffer, which stores - // the raw pixel color information (unmapped) + // the raw pixel color information (unmapped) and then + // updates the palette buffer with the palette index self.color_buffer[color_offset] = pixel; + self.palette_buffer[color_offset] = 0; // set the color pixel in the frame buffer self.frame_buffer[frame_offset] = color[0]; @@ -1561,8 +1619,10 @@ impl Ppu { let color = &self.palette_bg[pixel as usize]; // updates the pixel in the color buffer, which stores - // the raw pixel color information (unmapped) + // the raw pixel color information (unmapped) and then + // updates the palette buffer with the palette index self.color_buffer[color_offset] = pixel; + self.palette_buffer[color_offset] = 0; // set the color pixel in the frame buffer self.frame_buffer[frame_offset] = color[0]; @@ -1661,22 +1721,22 @@ impl Ppu { continue; } - let palette = if self.gb_mode == GameBoyMode::Cgb { + let (palette, palette_index) = if self.gb_mode == GameBoyMode::Cgb { if self.dmg_compat { if obj.palette == 0 { - &self.palette_obj_0 + (&self.palette_obj_0, 1_u8) } else if obj.palette == 1 { - &self.palette_obj_1 + (&self.palette_obj_1, 2_u8) } else { panic!("Invalid object palette: {:02x}", obj.palette); } } else { - &self.palettes_color_obj[obj.palette_cgb as usize] + (&self.palettes_color_obj[obj.palette_cgb as usize], 255_u8) } } else if obj.palette == 0 { - &self.palette_obj_0 + (&self.palette_obj_0, 1_u8) } else if obj.palette == 1 { - &self.palette_obj_1 + (&self.palette_obj_1, 2_u8) } else { panic!("Invalid object palette: {:02x}", obj.palette); }; @@ -1781,8 +1841,10 @@ impl Ppu { let color = palette[pixel as usize]; // updates the pixel in the color buffer, which stores - // the raw pixel color information (unmapped) + // the raw pixel color information (unmapped) and then + // updates the palette buffer with the palette index self.color_buffer[color_offset as usize] = pixel; + self.palette_buffer[color_offset as usize] = palette_index; // sets the color pixel in the frame buffer self.frame_buffer[frame_offset as usize] = color[0]; -- GitLab