diff --git a/src/bus.rs b/src/bus.rs index 0dca296..fc44f7d 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -11,7 +11,8 @@ use crate::ppu::{ LCDStatusModeFlag, LCD_STATUS_ADDRESS, LCD_CONTROL_ADDRESS, - LCD_Y_ADDRESS + LCD_Y_ADDRESS, + DMA_ADDRESS, }; use crate::cpu::{Interrupt}; use crate::timer::{TIMER_DIVIDER_REGISTER_ADDRESS}; @@ -55,11 +56,12 @@ pub struct Bus { game_rom: ROM, data: [u8; 0x10000], pub reset_timer: bool, + pub joypad: Joypad, } impl Bus { pub fn new() -> Self { - let game_rom = match ROM::load_file("ignore/mooneye/acceptance/if_ie_registers.gb".to_string()) { + let game_rom = match ROM::load_file("ignore/tetris.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/01-special.gb".to_string()) { // let game_rom = match ROM::load_file("roms/cpu_instrs_individual/02-interrupts.gb".to_string()) { @@ -104,6 +106,7 @@ impl Bus { data, game_rom, reset_timer: false, + joypad: Joypad::new(), } } @@ -113,6 +116,8 @@ impl Bus { } else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS { return 0b11100000 | self.data[address as usize]; + } else if address == JOYPAD_ADDRESS { + return self.joypad.read(self.data[address as usize]); } self.data[address as usize] } @@ -150,6 +155,16 @@ impl Bus { } else if address == JOYPAD_ADDRESS { let byte = self.data[address as usize]; self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111); + } else if address == DMA_ADDRESS { + // the idea is: when something gets written to $FF46, multiply it by 0x100, then copy 160 bytes starting from that memory location into OAM + self.data[address as usize] = data; + let source = (data as usize) * 0x100; + let mut count = 0; + let oam_addr = SPRITE_ATTRIBUTE_TABLE.begin() as usize; + while count < 160 { + self.data[oam_addr + count] = self.data[source + count]; + count += 1; + } } else { self.data[address as usize] = data; } diff --git a/src/cpu.rs b/src/cpu.rs index b598e05..b4b51f1 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -911,7 +911,7 @@ impl CPU { } pub fn handle_interrupt(&mut self, bus: &mut Bus, interrupt: Interrupt) { - println!("Interrupt: {:?}", interrupt); + // println!("Interrupt: {:?}", interrupt); bus.set_interrupt_flag(interrupt, false); self.ime = false; self.registers.decrement(Register::PC, 3); diff --git a/src/emulator.rs b/src/emulator.rs index 74f4b56..ee5b91d 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -13,18 +13,15 @@ pub struct Emulator { ppu: PPU, bus: Bus, timer: Timer, - joypad: Joypad, } impl Emulator { pub fn new() -> Self { - let mut joypad: Joypad = Joypad::new(); Self { cpu: CPU::new(), ppu: PPU::new(), bus: Bus::new(), timer: Timer::new(), - joypad: Joypad::new(), } } @@ -32,68 +29,68 @@ impl Emulator { let mut change = false; if input.key_pressed(VirtualKeyCode::K) { change = true; - self.joypad.press(Button::A); + self.bus.joypad.press(Button::A); } if input.key_pressed(VirtualKeyCode::J) { change = true; - self.joypad.press(Button::B); + self.bus.joypad.press(Button::B); } if input.key_pressed(VirtualKeyCode::W) { change = true; - self.joypad.press(Button::Up); + self.bus.joypad.press(Button::Up); } if input.key_pressed(VirtualKeyCode::S) { change = true; - self.joypad.press(Button::Down); + self.bus.joypad.press(Button::Down); } if input.key_pressed(VirtualKeyCode::A) { change = true; - self.joypad.press(Button::Left); + self.bus.joypad.press(Button::Left); } if input.key_pressed(VirtualKeyCode::D) { change = true; - self.joypad.press(Button::Right); + self.bus.joypad.press(Button::Right); } if input.key_pressed(VirtualKeyCode::N) { change = true; - self.joypad.press(Button::Start); + self.bus.joypad.press(Button::Start); } if input.key_pressed(VirtualKeyCode::B) { change = true; - self.joypad.press(Button::Select); + self.bus.joypad.press(Button::Select); } if input.key_released(VirtualKeyCode::K) { change = true; - self.joypad.release(Button::A); + self.bus.joypad.release(Button::A); } if input.key_released(VirtualKeyCode::J) { change = true; - self.joypad.release(Button::B); + self.bus.joypad.release(Button::B); } if input.key_released(VirtualKeyCode::W) { change = true; - self.joypad.release(Button::Up); + self.bus.joypad.release(Button::Up); } if input.key_released(VirtualKeyCode::S) { change = true; - self.joypad.release(Button::Down); + self.bus.joypad.release(Button::Down); } if input.key_released(VirtualKeyCode::A) { change = true; - self.joypad.release(Button::Left); + self.bus.joypad.release(Button::Left); } if input.key_released(VirtualKeyCode::D) { change = true; - self.joypad.release(Button::Right); + self.bus.joypad.release(Button::Right); } if input.key_released(VirtualKeyCode::N) { change = true; - self.joypad.release(Button::Start); + self.bus.joypad.release(Button::Start); } if input.key_released(VirtualKeyCode::B) { change = true; - self.joypad.release(Button::Select); + self.bus.joypad.release(Button::Select); } if change { self.bus.set_interrupt_flag(Interrupt::Joypad, true); diff --git a/src/joypad.rs b/src/joypad.rs index 2327f8d..ebef6e2 100644 --- a/src/joypad.rs +++ b/src/joypad.rs @@ -41,7 +41,6 @@ impl Joypad { } pub fn press(&mut self, button: Button) { - println!("{:?} pressed", button); match button { Button::A => self.a = true, Button::B => self.b = true, @@ -55,7 +54,6 @@ impl Joypad { } pub fn release(&mut self, button: Button) { - println!("{:?} released", button); match button { Button::A => self.a = false, Button::B => self.b = false, @@ -68,8 +66,7 @@ impl Joypad { }; } - pub fn read(&self, bus: &mut Bus) -> u8 { - let byte = bus.read(JOYPAD_ADDRESS); + pub fn read(&self, byte: u8) -> u8 { let direction = !get_bit(byte, BitIndex::I4); let action = !get_bit(byte, BitIndex::I5); diff --git a/src/ppu.rs b/src/ppu.rs index d6ce0d4..e449b40 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -4,7 +4,7 @@ use crate::utils::{ set_bit, to_bit_index, }; -use crate::bus::{Bus, AddressRange, BANK_ZERO, VIDEO_RAM}; +use crate::bus::{Bus, AddressRange, BANK_ZERO, VIDEO_RAM, SPRITE_ATTRIBUTE_TABLE}; use crate::cpu::{Cycles, Interrupt}; pub const LCD_WIDTH: u32 = 160; @@ -94,11 +94,60 @@ pub struct PPU { prev_state: bool, cycles: Cycles, rgba_frame: [[u8; 4]; FRAME_BUFFER_LENGTH as usize], + sprite_buffer: Vec, } -enum TileNumber { - Base(u16), - Absolute(u8), +struct Sprite { + x: u8, + y: u8, + tile_number: u8, + x_flip: bool, + y_flip: bool, + over_bg: bool, + palette_one: bool, + is_long: bool, +} + +impl Sprite { + pub fn x(&self) -> u8 { + self.x + } + + pub fn get_pixel(&self, lcd_x: u8, lcd_y: u8, bus: &Bus) -> Option { + todo!("Implement sprite flipping"); + if lcd_x < self.x.saturating_sub(8) || lcd_x >= self.x { + return None; + } + + let height: u8 = match self.is_long { + true => 16, + false => 8, + }; + + let x = lcd_x.saturating_sub(self.x.saturating_sub(8)); + let y = lcd_y.saturating_sub(self.y .saturating_sub(16)); + + let tile_line = y.rem_euclid(height) * 2; + let addr = 0x8000 + (self.tile_number as u16 * 16) + tile_line as u16; + + let tile_byte_1 = bus.read(addr); + let tile_byte_2 = bus.read(addr + 1); + + let pixel_index = (x as usize).rem_euclid(8); + + if PPU::get_two_bit_byte_pixels(tile_byte_1, tile_byte_2)[pixel_index] == 0 { + return None; + } + + let palette = match self.palette_one { + true => bus.read(OBJECT_PALETTE_1_ADDRESS), + false => bus.read(OBJECT_PALETTE_0_ADDRESS), + }; + let pixels = PPU::get_byte_pixels(tile_byte_1, tile_byte_2, palette); + + Some(pixels[pixel_index]) + + } } impl PPU { @@ -107,6 +156,7 @@ impl PPU { prev_state: false, cycles: Cycles(0), rgba_frame: [[0xFF, 0xFF, 0xFF, 0]; FRAME_BUFFER_LENGTH as usize], + sprite_buffer: Vec::new(), } } @@ -137,6 +187,7 @@ impl PPU { // Mode 2 OAM scan PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM), true); self.stat_interrupt(bus); + self.oam_search(bus); } else if self.cycles.0 == 80 + 1 { // Mode 3 drawing pixel line. This could also last 289 cycles self.draw_line(bus, frame_buffer); @@ -189,6 +240,71 @@ impl PPU { } } + fn oam_search(&mut self, bus: &Bus) { + self.sprite_buffer = Vec::new(); + if !PPU::get_lcd_control(bus, LCDControl::ObjectEnable) { + return; + } + let long_sprites = PPU::get_lcd_control(bus, LCDControl::ObjectSize); + let mut addr = SPRITE_ATTRIBUTE_TABLE.begin(); + while addr <= SPRITE_ATTRIBUTE_TABLE.end() { + // The gameboy only supports 10 sprites per line, + // but since we are on an emulator we can avoud that limitation + if self.sprite_buffer.len() >= 10 { + todo!("Make a setting for the 10 sprites per scanline"); + // break; + } + let y = bus.read(addr); + let x = bus.read(addr + 1); + + if x == 0 { + addr += 4; + continue; + } + + let sprite_height: u8 = match long_sprites { + true => 16, + false => 8, + }; + + let lcd_y = PPU::get_lcd_y(bus).saturating_add(16); + + if lcd_y < y || lcd_y > (y + sprite_height) { + addr += 4; + continue; + } + + + let tile_number = bus.read(addr + 2); + let attributes = bus.read(addr + 3); + + self.sprite_buffer.push(Sprite { + x, + y, + tile_number, + is_long: long_sprites, + palette_one: get_bit(attributes, BitIndex::I4), + x_flip: get_bit(attributes, BitIndex::I5), + y_flip: get_bit(attributes, BitIndex::I6), + over_bg: get_bit(attributes, BitIndex::I7), + }); + + addr += 4; + } + self.sprite_buffer.sort_by(|a, b| a.x().cmp(&b.x())); + } + + fn find_sprite_pixel(&self, lcd_x: u8, bus: &Bus) -> Option { + let lcd_y = PPU::get_lcd_y(bus); + for sprite in &self.sprite_buffer { + if let Some(pixel) = sprite.get_pixel(lcd_x, lcd_y, bus) { + return Some(pixel); + } + } + + return None; + } + fn get_lcd_y(bus: &Bus) -> u8 { bus.read(LCD_Y_ADDRESS) } @@ -258,15 +374,12 @@ impl PPU { bus.force_write(LCD_STATUS_ADDRESS, byte); } - fn get_tile_bytes(x: u8, y: u8, tile_number_type: TileNumber, default_method: bool, bus: &Bus) -> (u8, u8) { + fn get_tile_bytes(x: u8, y: u8, tilemap_area: u16, default_method: bool, bus: &Bus) -> (u8, u8) { 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 = match tile_number_type { - TileNumber::Base(base) => bus.read(base + index as u16), - TileNumber::Absolute(num) => bus.read(0x8000 + num as u16), - } as u16; + let tile_number = bus.read(tilemap_area + index as u16) as u16; let addr = if default_method { 0x8000 + tile_line as u16 + (tile_number * 16) } else { @@ -296,7 +409,7 @@ impl PPU { true => 0x9C00, false => 0x9800, }; - let (tile_byte_1, tile_byte_2) = PPU::get_tile_bytes(x, y, TileNumber::Base(tilemap_area), default_mode, bus); + let (tile_byte_1, tile_byte_2) = PPU::get_tile_bytes(x, y, tilemap_area, default_mode, bus); let palette = bus.read(BACKGROUND_PALETTE_ADDRESS); let pixels = PPU::get_byte_pixels(tile_byte_1, tile_byte_2, palette); @@ -320,7 +433,7 @@ impl PPU { true => 0x9C00, false => 0x9800, }; - let (tile_byte_1, tile_byte_2) = PPU::get_tile_bytes(x, y, TileNumber::Base(tilemap_area), default_mode, bus); + let (tile_byte_1, tile_byte_2) = PPU::get_tile_bytes(x, y, tilemap_area, default_mode, bus); let bg_pixels = PPU::get_byte_pixels(tile_byte_1, tile_byte_2, palette); @@ -337,6 +450,13 @@ impl PPU { frame_buffer[idx + 2] = rgba[2]; } + if let Some(sprite_pixel) = self.find_sprite_pixel(lcd_x, bus) { + let rgba = PPU::get_rgba(sprite_pixel); + frame_buffer[idx] = rgba[0]; + frame_buffer[idx + 1] = rgba[1]; + frame_buffer[idx + 2] = rgba[2]; + } + lcd_x += 1; /* for pixel in bg_pixels { @@ -386,6 +506,19 @@ impl PPU { } } + fn get_two_bit_byte_pixels(byte1: u8, byte2: u8) -> [u8; 8] { + [ + ((byte1 >> 7) & 0b01) | ((byte2 >> 6) & 0b10), + ((byte1 >> 6) & 0b01) | ((byte2 >> 5) & 0b10), + ((byte1 >> 5) & 0b01) | ((byte2 >> 4) & 0b10), + ((byte1 >> 4) & 0b01) | ((byte2 >> 3) & 0b10), + ((byte1 >> 3) & 0b01) | ((byte2 >> 2) & 0b10), + ((byte1 >> 2) & 0b01) | ((byte2 >> 1) & 0b10), + ((byte1 >> 1) & 0b01) | (byte2 & 0b10), + (byte1 & 0b01) | ((byte2 << 1) & 0b10), + ] + } + fn get_byte_pixels(byte1: u8, byte2: u8, palette: u8) -> [Pixel; 8] { [ PPU::get_pixel(PPU::get_palette(((byte1 >> 7) & 0b01) | ((byte2 >> 6) & 0b10), palette)), diff --git a/src/render.rs b/src/render.rs index 08bd622..f71f994 100644 --- a/src/render.rs +++ b/src/render.rs @@ -67,7 +67,7 @@ pub fn start_eventloop() { emulator.run(Cycles(70224), pixels.get_frame()); // emulator.draw(pixels.get_frame()); - // thread::sleep(time::Duration::from_millis(500)); + thread::sleep(time::Duration::from_millis(10)); window.request_redraw(); }, Event::RedrawRequested(_) => {