diff --git a/src/main.rs b/src/main.rs index c03874f..7a63d97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,7 @@ fn main() -> Result<()> { let ppu = game_boy.get_ppu(); let frame = pixels.get_frame(); - ppu.draw(frame); + ppu.copy_to_gui(frame); if pixels .render() @@ -57,7 +57,7 @@ fn main() -> Result<()> { // Emulation let _cycles = game_boy.step(); - // window.request_redraw(); + window.request_redraw(); } }); } diff --git a/src/ppu.rs b/src/ppu.rs index 28ff611..e53d645 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -17,11 +17,11 @@ pub struct Ppu { impl Ppu { pub fn read_byte(&self, addr: u16) -> u8 { - unimplemented!() + self.vram[addr as usize - 0x8000] } pub fn write_byte(&mut self, addr: u16, byte: u8) { - unimplemented!() + self.vram[addr as usize - 0x8000] = byte; } } @@ -35,6 +35,7 @@ impl Ppu { match self.stat.mode() { Mode::OamScan => { if self.cycles >= 80.into() { + self.cycles %= 80; self.stat.set_mode(Mode::Drawing); } } @@ -46,15 +47,19 @@ impl Ppu { // TODO: This 172 needs to be variable somehow? if self.cycles >= 172.into() { + self.cycles %= 172; + self.stat.set_mode(Mode::HBlank); + self.draw_scanline(); } } Mode::HBlank => { // We've reached the end of a scanline - if self.cycles >= 456.into() { + if self.cycles >= 200.into() { + self.cycles %= 200; self.pos.line_y += 1; - let next_mode = if self.pos.line_y >= 143 { + let next_mode = if self.pos.line_y >= 144 { Mode::VBlank } else { Mode::OamScan @@ -70,7 +75,7 @@ impl Ppu { self.cycles %= 456; self.pos.line_y += 1; - if self.pos.line_y >= 153 { + if self.pos.line_y == 154 { self.stat.set_mode(Mode::OamScan); self.pos.line_y = 0; } @@ -79,7 +84,53 @@ impl Ppu { } } - pub fn draw(&self, frame: &mut [u8]) { + fn draw_scanline(&mut self) { + let mut scanline: [u8; GB_WIDTH * 4] = [0; GB_WIDTH * 4]; + + let scroll_y = self.pos.scroll_y; + let scroll_x = self.pos.scroll_x; + // let window_y = self.pos.window_y; + // let window_x = self.pos.window_x - 7; + + let tile_map_addr = match self.lcd_control.bg_tile_map_addr() { + TileMapAddress::X9800 => 0x9800, + TileMapAddress::X9C00 => 0x9C00, + }; + + let y_pos: usize = scroll_y as usize + self.pos.line_y as usize; + let tile_row: usize = (y_pos as usize / 8) * 32; + + for (i, chunk) in scanline.chunks_mut(4).enumerate() { + let x_pos = i as u8 + scroll_x; + let tile_column = x_pos / 8; + + let tile_addr = tile_map_addr + tile_row as u16 + tile_column as u16; + let tile_number = self.read_byte(tile_addr); + + let tile_data_addr = match self.lcd_control.tile_data_addr() { + TileDataAddress::X8800 => (0x9000 as i32 + (tile_number as i32 * 16)) as u16, + TileDataAddress::X8000 => 0x8000 + (tile_number as u16 * 16), + }; + + // Find the correct vertical line we're on + let line = (y_pos % 8) * 2; // *2 since each vertical line takes up 2 bytes + + let higher = self.read_byte(tile_data_addr + line as u16); + let lower = self.read_byte(tile_data_addr + line as u16 + 1); + // println!("Hi: {:#010b} | Lo: {:#010b}", higher, lower); + + let bit = x_pos % 8; + let colour = ((higher >> bit) & 0x01) << 1 | ((lower >> bit) & 0x01); + let shade: GrayShade = colour.into(); + + chunk.copy_from_slice(&shade.into_rgba()); + } + + let i = (GB_WIDTH * 4) * self.pos.line_y as usize; + self.frame_buf[i..(i + scanline.len())].copy_from_slice(&scanline); + } + + pub fn copy_to_gui(&self, frame: &mut [u8]) { frame.copy_from_slice(&self.frame_buf); } } @@ -186,11 +237,11 @@ bitfield! { pub struct LCDControl(u8); impl Debug; lcd_enabled, set_lcd_enabled: 7; - from into TileMapRegister, win_tile_map_area, set_win_tile_map_area: 6; + from into TileMapAddress, win_tile_map_addr, set_win_tile_map_addr: 6, 6; window_enabled, set_window_enabled: 5; - from into TileDataRegister, tile_data_area, set_tile_data_area: 4; - from into TileMapRegister, gb_tile_map_area, set_gb_tile_map_area: 3; - from into OBJSize, obg_size, set_obj_size: 2; + from into TileDataAddress, tile_data_addr, set_tile_data_addr: 4, 4; + from into TileMapAddress, bg_tile_map_addr, set_bg_tile_map_addr: 3, 3; + from into ObjectSize, obg_size, set_obj_size: 2, 2; obj_enabled, set_obj_enabled: 1; bg_win_enabled, set_bg_win_enabled: 0; } @@ -221,12 +272,12 @@ impl From for u8 { } #[derive(Debug, Clone, Copy)] -enum TileMapRegister { +enum TileMapAddress { X9800 = 0, X9C00 = 1, } -impl From for TileMapRegister { +impl From for TileMapAddress { fn from(byte: u8) -> Self { match byte { 0b00 => Self::X9800, @@ -236,19 +287,25 @@ impl From for TileMapRegister { } } -impl Default for TileMapRegister { +impl From for u8 { + fn from(reg: TileMapAddress) -> Self { + reg as u8 + } +} + +impl Default for TileMapAddress { fn default() -> Self { Self::X9800 } } #[derive(Debug, Clone, Copy)] -enum TileDataRegister { +enum TileDataAddress { X8800 = 0, X8000 = 1, } -impl From for TileDataRegister { +impl From for TileDataAddress { fn from(byte: u8) -> Self { match byte { 0b00 => Self::X8800, @@ -258,19 +315,25 @@ impl From for TileDataRegister { } } -impl Default for TileDataRegister { +impl From for u8 { + fn from(reg: TileDataAddress) -> Self { + reg as u8 + } +} + +impl Default for TileDataAddress { fn default() -> Self { Self::X8800 } } #[derive(Debug, Clone, Copy)] -enum ObjSize { +enum ObjectSize { EightByEight = 0, EightBySixteen = 1, } -impl From for ObjSize { +impl From for ObjectSize { fn from(byte: u8) -> Self { match byte { 0b00 => Self::EightByEight, @@ -280,7 +343,13 @@ impl From for ObjSize { } } -impl Default for ObjSize { +impl From for u8 { + fn from(size: ObjectSize) -> Self { + size as u8 + } +} + +impl Default for ObjectSize { fn default() -> Self { Self::EightByEight } @@ -294,6 +363,17 @@ pub enum GrayShade { Black = 3, } +impl GrayShade { + pub fn into_rgba(self) -> [u8; 4] { + match self { + GrayShade::White => [0xFF, 0xFF, 0xFF, 0xFF], + GrayShade::LightGray => [0xCC, 0xCC, 0xCC, 0xFF], + GrayShade::DarkGray => [0x77, 0x77, 0x77, 0xFF], + GrayShade::Black => [0x00, 0x00, 0x00, 0x00], + } + } +} + impl Default for GrayShade { fn default() -> Self { Self::White @@ -391,3 +471,5 @@ impl From for u8 { palette.0 } } + +struct BackgroundMap([u8; 32]);