From 3f2824714f9ac6319750c682c22655e69d472da5 Mon Sep 17 00:00:00 2001 From: Franco Colmenarez Date: Thu, 23 Dec 2021 19:40:25 -0500 Subject: [PATCH] WIP PPU CGB support --- src/bus.rs | 6 +-- src/ppu.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 105 insertions(+), 20 deletions(-) diff --git a/src/bus.rs b/src/bus.rs index caa1766..241b9ad 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -62,7 +62,7 @@ impl Bus { true => Box::new(CGBRAM::new()), false => Box::new(DMGRAM::new()), }, - ppu: PPU::new(), + ppu: PPU::new(cgb_mode), joypad: Joypad::new(), timer: Timer::new(), sound: Sound::new(), @@ -105,7 +105,7 @@ impl Bus { } else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS { return self.interrupts.read(address); } else if VIDEO_RAM.contains(&address) { - return self.ppu.read_vram(address); + return self.ppu.read_vram_external(address); } else if SPRITE_ATTRIBUTE_TABLE.contains(&address) { return self.ppu.read_oam(address); } else if PPU::is_io_register(address) { @@ -147,7 +147,7 @@ impl Bus { let byte = self.data[address as usize]; self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111); } else if VIDEO_RAM.contains(&address) { - return self.ppu.write_vram(address, data); + return self.ppu.write_vram_external(address, data); } else if SPRITE_ATTRIBUTE_TABLE.contains(&address) { return self.ppu.write_oam(address, data); } else if address == DMA_ADDRESS { diff --git a/src/ppu.rs b/src/ppu.rs index 5625afb..d10611d 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -26,6 +26,7 @@ pub const OBJECT_PALETTE_0_ADDRESS: u16 = 0xFF48; pub const OBJECT_PALETTE_1_ADDRESS: u16 = 0xFF49; pub const WINDOW_Y_ADDRESS: u16 = 0xFF4A; pub const WINDOW_X_ADDRESS: u16 = 0xFF4B; +pub const VRAM_BANK_SELECT_ADDRESS: u16 = 0xFF4F; pub const TILE_MAP_ADDRESS: u16 = 0x9800; @@ -126,6 +127,26 @@ pub enum LCDStatus { ModeFlag(LCDStatusModeFlag), } +struct BgAttributes { + bg_to_oam_priority: bool, + vertical_flip: bool, + horizontal_flip: bool, + vram_bank: u8, + palette_number: u8, +} + +impl BgAttributes { + pub fn new(byte: u8) -> Self { + Self { + bg_to_oam_priority: get_bit(byte, BitIndex::I7), + vertical_flip: get_bit(byte, BitIndex::I6), + horizontal_flip: get_bit(byte, BitIndex::I5), + vram_bank: get_bit(byte, BitIndex::I3) as u8, + palette_number: byte & 0b111, + } + } +} + struct Sprite { x: u8, y: u8, @@ -136,6 +157,8 @@ struct Sprite { y_flip: bool, over_bg: bool, bit_pixels: Option<[u8; 8]>, + vram_bank: u8, + palette_number: u8, } impl Sprite { @@ -229,13 +252,15 @@ pub struct PPU { scroll_y: u8, window_x: u8, window_y: u8, - io_registers: [u8; 12], - vram: [u8; 0x2000], + io_registers: [u8; 16], + vram: [u8; 0x2000 * 2], oam: [u8; 0xA0], + vram_bank: u8, + cgb_mode: bool, } impl PPU { - pub fn new() -> Self { + pub fn new(cgb_mode: bool) -> Self { Self { state: false, background_priority: false, @@ -256,21 +281,41 @@ impl PPU { scroll_y: 0, window_x: 0, window_y: 0, - io_registers: [0; 12], - vram: [0; 0x2000], + io_registers: [0; 16], + vram: [0; 0x2000 * 2], oam: [0; 0xA0], + vram_bank: 0, + cgb_mode, } } - pub fn is_io_register(address: u16) -> bool { - address >= 0xFF40 && address <= 0xFF4B + pub fn set_vram_bank(&mut self, bank: u8) { + if self.cgb_mode { + self.vram_bank = bank & 1; + } } - pub fn read_vram(&self, address: u16) -> u8 { + pub fn get_vram_bank(&self) -> u8 { + self.vram_bank | 0xFE + } + + pub fn is_io_register(address: u16) -> bool { + address >= 0xFF40 && address <= 0xFF4F + } + + pub fn read_vram_external(&self, address: u16) -> u8 { + self.vram[((address + (0x2000 * self.vram_bank as u16)) - 0x8000) as usize] + } + + pub fn write_vram_external(&mut self, address: u16, data: u8) { + self.vram[((address + (0x2000 * self.vram_bank as u16)) - 0x8000) as usize] = data; + } + + fn read_vram(&self, address: u16) -> u8 { self.vram[(address - 0x8000) as usize] } - pub fn write_vram(&mut self, address: u16, data: u8) { + fn write_vram(&mut self, address: u16, data: u8) { self.vram[(address - 0x8000) as usize] = data; } @@ -283,7 +328,9 @@ impl PPU { } pub fn get_register(&self, address: u16) -> u8 { - if address == LCD_CONTROL_ADDRESS { + if address == VRAM_BANK_SELECT_ADDRESS { + return self.get_vram_bank(); + } else if address == LCD_CONTROL_ADDRESS { return self.lcd_control; } else if address == LCD_Y_ADDRESS { return self.lcd_y; @@ -292,7 +339,9 @@ impl PPU { } pub fn set_register(&mut self, address: u16, data: u8) { - if address == LCD_Y_ADDRESS { + if address == VRAM_BANK_SELECT_ADDRESS { + return self.set_vram_bank(data); + } else if address == LCD_Y_ADDRESS { return; } else if address == LCD_CONTROL_ADDRESS { self.lcd_control = data; @@ -465,11 +514,18 @@ impl PPU { y_flip: get_bit(attributes, BitIndex::I6), over_bg: get_bit(attributes, BitIndex::I7), bit_pixels: None, + vram_bank: match self.cgb_mode && get_bit(attributes, BitIndex::I3) { + true => 1, + false => 0, + }, + palette_number: attributes & 0b111, }); addr += 4; } - self.sprite_buffer.sort_by(|a, b| a.x().cmp(&b.x())); + if !self.cgb_mode { + self.sprite_buffer.sort_by(|a, b| a.x().cmp(&b.x())); + } } fn find_sprite_pixel(&mut self) -> Option<(Pixel, bool)> { @@ -522,12 +578,17 @@ impl PPU { self.force_set_register(LCD_STATUS_ADDRESS, byte); } - fn get_tile_bytes(&self, x: u8, y: u8, tilemap_area: u16, default_method: bool) -> (u8, u8) { + fn get_tile_bytes(&self, x: u8, y: u8, tilemap_area: u16, default_method: bool) -> (u8, u8, BgAttributes) { let index_x = x as u16 / 8; let index_y = (y as u16 / 8) * 32; let index = index_x + index_y; - let tile_line = (y).rem_euclid(8) * 2; let tile_number = self.read_vram(tilemap_area + index as u16) as u16; + let mut tile_line = (y).rem_euclid(8); + let attributes = BgAttributes::new(self.read_vram(tilemap_area + 0x2000 + index as u16)); + if self.cgb_mode && attributes.vertical_flip { + tile_line = 7 - tile_line; + } + tile_line = tile_line * 2; let addr = if default_method { 0x8000 + tile_line as u16 + (tile_number * 16) } else { @@ -537,7 +598,31 @@ impl PPU { (base + tile_line + (tile_number * 16)) as u16 }; - (self.read_vram(addr), self.read_vram(addr + 1)) + if !self.cgb_mode { + return (self.read_vram(addr), self.read_vram(addr + 1), attributes); + } + let byte1 = self.read_vram(addr + (0x2000 * (attributes.vram_bank as u16))); + let byte2 = self.read_vram(addr + (0x2000 * (attributes.vram_bank as u16)) + 1); + if attributes.horizontal_flip { + let byte1 = ((byte1 >> 7) & 0b1) | + ((byte1 >> 5) & 0b10) | + ((byte1 >> 3) & 0b100) | + ((byte1 >> 1) & 0b1000) | + ((byte1 << 1) & 0b10000) | + ((byte1 << 3) & 0b100000) | + ((byte1 << 5) & 0b1000000) | + ((byte1 << 7) & 0b10000000); + let byte2 = ((byte2 >> 7) & 0b1) | + ((byte2 >> 5) & 0b10) | + ((byte2 >> 3) & 0b100) | + ((byte2 >> 1) & 0b1000) | + ((byte2 << 1) & 0b10000) | + ((byte2 << 3) & 0b100000) | + ((byte2 << 5) & 0b1000000) | + ((byte2 << 7) & 0b10000000); + return (byte1, byte2, attributes); + } + return (byte1, byte2, attributes); } fn get_window_pixel(&mut self) -> Option { @@ -577,7 +662,7 @@ impl PPU { true => 0x9C00, false => 0x9800, }; - let (tile_byte_1, tile_byte_2) = self.get_tile_bytes(x, y, tilemap_area, default_mode); + let (tile_byte_1, tile_byte_2, _) = self.get_tile_bytes(x, y, tilemap_area, default_mode); let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2); self.current_window_pixels = Some(bit_pixels_array); @@ -614,7 +699,7 @@ impl PPU { true => 0x9C00, false => 0x9800, }; - let (tile_byte_1, tile_byte_2) = self.get_tile_bytes(x, y, tilemap_area, default_mode); + let (tile_byte_1, tile_byte_2, _) = self.get_tile_bytes(x, y, tilemap_area, default_mode); let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2); self.current_background_pixels = Some(bit_pixels_array);