From 023b9ff0e4b6bae6d55d8130a72dd2ed6b2fee2c Mon Sep 17 00:00:00 2001 From: Kevin Hamacher Date: Thu, 2 Jun 2016 13:22:35 +0200 Subject: [PATCH] Speed enhancements; Write savefiles --- src/cpu.rs | 79 +++++++++++++++++++++++++++++---------------- src/display.rs | 72 +++++++++++++++++++++++++++-------------- src/interconnect.rs | 78 ++++++++++++++++++++++++++------------------ src/serial.rs | 10 +++++- 4 files changed, 154 insertions(+), 85 deletions(-) diff --git a/src/cpu.rs b/src/cpu.rs index c9419bb..a8e8d14 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,5 +1,8 @@ use super::interconnect; +use std::time::{Duration, Instant}; +use std::thread::sleep; + const REG_A: usize = 6; const REG_N_B: usize = 0; @@ -46,7 +49,7 @@ impl CPU { ip: 0, sp: 0xFFFE, interconnect: interconnect, - ime: false, // Is this correct? + ime: false, debug: false, halted: false, } @@ -861,39 +864,58 @@ impl CPU { self.halted = false; } - pub fn run_instruction(&mut self) { - // self.debug = !self.interconnect.is_boot_rom(); - /* - if self.ip == 0x553 { - self.debug = true; - } - */ - // Check for interrupts. - // TODO: Make this right - if self.ime { - // read pending interrupts - let pending = self.interconnect.read_byte(0xFF0F); - let enabled = self.interconnect.read_byte(0xFFFF); - let e_pending = pending & enabled; - - if e_pending & interconnect::INTERRUPT_DISPLAY_VBLANK > 0 { - // println!("Handling vblank interrupt"); - self.handle_interrupt(0x40, interconnect::INTERRUPT_DISPLAY_VBLANK); - } else if e_pending & interconnect::INTERRUPT_DISPLAY_STAT > 0 { - // println!("Handling display stat interrupt"); - self.handle_interrupt(0x48, interconnect::INTERRUPT_DISPLAY_STAT); - } else if e_pending & interconnect::INTERRUPT_TIMER_OVERFLOW > 0{ - println!("Handling timer interrupt"); - self.handle_interrupt(0x50, interconnect::INTERRUPT_TIMER_OVERFLOW); - } else if e_pending > 0 { - panic!("Unknown pending interrupt: {:02X}", e_pending); + pub fn run(&mut self) { + loop { + let mut cycles: i32 = 0; + let start = Instant::now(); + for _ in 0 .. 1000 { + cycles += self.run_instruction() as i32; } + + let gb_dur = cycles * 238; + let our_dur = start.elapsed().subsec_nanos() as i32; + let delta = gb_dur - our_dur; + if delta > (20 * 238) { // We're at least 20 cycles faster. + sleep(Duration::new(0, delta as u32)); + } else if delta < 0 { + print!("-"); + } + } + } + + fn check_interrupts(&mut self) { + // read pending interrupts + let pending = self.interconnect.read_byte(0xFF0F); + let enabled = self.interconnect.read_byte(0xFFFF); + let e_pending = pending & enabled; + + if e_pending & interconnect::INTERRUPT_DISPLAY_VBLANK > 0 { + self.handle_interrupt(0x40, interconnect::INTERRUPT_DISPLAY_VBLANK); + } else if e_pending & interconnect::INTERRUPT_DISPLAY_STAT > 0 { + self.handle_interrupt(0x48, interconnect::INTERRUPT_DISPLAY_STAT); + } else if e_pending & interconnect::INTERRUPT_TIMER_OVERFLOW > 0 { + self.handle_interrupt(0x50, interconnect::INTERRUPT_TIMER_OVERFLOW); + } else if e_pending & interconnect::INTERRUPT_SERIAL > 0 { + self.handle_interrupt(0x58, interconnect::INTERRUPT_SERIAL); + } else if e_pending & interconnect::INTERRUPT_INPUT > 0 { + self.handle_interrupt(0x60, interconnect::INTERRUPT_INPUT); + } else if e_pending > 0 { + panic!("Unknown pending interrupt: {:02X}", e_pending); + } + } + + pub fn run_instruction(&mut self) -> u8 { + // self.debug = !self.interconnect.is_boot_rom(); + // Check for interrupts. + if self.ime { + self.check_interrupts(); } let mut cycles: u8 = 1; + let instruction: u8; if !self.halted { // We need to double-check the flags - let instruction = self.read_byte(self.ip); + instruction = self.read_byte(self.ip); if self.debug { print!("{:#06x}: [SP: {:#04X}] i={:02X}. ", &self.ip, &self.sp, &instruction); for i in 0 .. 6 { @@ -1647,5 +1669,6 @@ impl CPU { }; } self.interconnect.tick(cycles); + cycles } } diff --git a/src/display.rs b/src/display.rs index 1727a13..3950806 100644 --- a/src/display.rs +++ b/src/display.rs @@ -45,12 +45,20 @@ const SPRITE_PALETTE_NO: u8 = 1 << 4; // NonCGB only // const SPRITE_TILE_VRAM_BANK: u8 = 1 << 3; // CGB only // Display color +/* const MONOCHROME_PALETTE: &'static [[f64; 3]; 4] = &[ [0.605, 0.734, 0.059], [0.543, 0.672, 0.059], [0.188, 0.383, 0.188], [0.059, 0.219, 0.059], ]; +*/ +const MONOCHROME_PALETTE: &'static [[u8; 3]; 4] = &[ + [255, 255, 255], + [200, 200, 200], + [125, 125, 12], + [50, 50, 50], +]; #[derive(Debug, Copy, Clone)] enum PixelOrigin { @@ -150,6 +158,9 @@ pub struct Display { stat_interrupt: bool, pixels: [Pixel; GB_PIXELS_X * GB_PIXELS_Y], + + frameskip: u8, + frame_no: u8, } impl Display { @@ -185,6 +196,8 @@ impl Display { vblank_interrupt: false, stat_interrupt: false, pixels: pixels, + frameskip: 0, + frame_no: 0, } } @@ -328,7 +341,9 @@ impl Display { if self.current_ticks > TICKS_END_READMODE { self.current_ticks = 0; self.current_mode = DisplayMode::HBlank; - self.renderscan(); + if self.frameskip < self.frame_no { + self.renderscan(); + } if self.status & STAT_MODE_HBLANK_INT > 0 { self.stat_interrupt = true; } @@ -365,10 +380,15 @@ impl Display { } if self.curline > 153 { self.current_mode = DisplayMode::ReadOAMMemory; // Mode 2, scanline. - self.render_screen(); - self.renderer.present(); - self.renderer.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255)); - self.renderer.clear(); + if self.frameskip < self.frame_no { + self.render_screen(); + self.renderer.present(); + self.renderer.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255)); + self.renderer.clear(); + self.frame_no = 0; + } else { + self.frame_no += 1; + } self.curline = 0; if self.status & STAT_MODE_OAM_INT > 0 { self.stat_interrupt = true; @@ -423,10 +443,11 @@ impl Display { let tile_offset_y: usize = render_y as usize - y_o as usize; let tile_base_addr: usize = sprite.tile as usize * 16; // Should this be twice as wide in wide mode? - let tile_line_1 = self.vram[tile_base_addr + tile_offset_y * 2 + 1]; - let tile_line_2 = self.vram[tile_base_addr + tile_offset_y * 2 + 1]; - let tile_line_3 = self.vram[tile_base_addr + tile_offset_y * 2 + 2]; - let tile_line_4 = self.vram[tile_base_addr + tile_offset_y * 2 + 3]; + let tile_addr = tile_base_addr + tile_offset_y * 2; + let tile_line_1 = self.vram[tile_addr + 0]; + let tile_line_2 = self.vram[tile_addr + 1]; + let tile_line_3 = self.vram[tile_addr + 2]; + let tile_line_4 = self.vram[tile_addr + 3]; // We need to draw this. let wide_mode = self.control & CTRL_BG_SPRITE_SIZE > 0; @@ -488,7 +509,7 @@ impl Display { let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize]; // Draw stuff. We're currently only in monochrome mode - self.set_pixel(x.wrapping_add(x_o), render_y, sdl2::pixels::Color::RGB((entry[0]*255.0) as u8, (entry[1]*255.0) as u8, (entry[2]*255.0) as u8), PixelOrigin::Sprite); + self.set_pixel(x.wrapping_add(x_o), render_y, sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]), PixelOrigin::Sprite); } } } @@ -499,11 +520,11 @@ impl Display { fn get_bg_window_tile_addr(&self, tile_id: u8) -> usize { if (self.control & CTRL_BG_WINDOW_TILE_DATA_SELECT) == CTRL_BG_WINDOW_TILE_DATA_SELECT { let base_addr = 0x0000; - base_addr + (tile_id as usize) * 16 + base_addr + ((tile_id as usize) << 4) } else { let base_addr = 0x0800; let tile_id = (128u8 as i8).wrapping_add(tile_id as i8) as u8; - base_addr + (tile_id as usize) * 16 + base_addr + ((tile_id as usize) << 4) } } @@ -570,19 +591,20 @@ impl Display { let tile_index_y: u8 = render_abs_y >> 3; let tile_offset_y: u8 = render_abs_y & 7; - let vram_offset: usize = background_map + (tile_index_y as usize) * 32 + tile_index_x as usize; + let vram_offset: usize = background_map + ((tile_index_y as usize) * 32) + tile_index_x as usize; // Obtain tile ID in this area let tile_id = self.vram[vram_offset]; // Obtain tile information let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id); - let tile_line_1 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2]; - let tile_line_2 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2 + 1]; + let addr = tile_base_addr + (tile_offset_y as usize) * 2; + let tile_line_1 = self.vram[addr]; + let tile_line_2 = self.vram[addr + 1]; // Get the correct bit - let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0; - let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0; + let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) != 0; + let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) != 0; // Lookup the color let c = (b1 as u8) * 2 + b2 as u8; @@ -598,11 +620,7 @@ impl Display { 0 => PixelOrigin::Empty, // Hack so that objects will be in front of it. _ => PixelOrigin::Background, }; - self.set_pixel(render_x, render_y, sdl2::pixels::Color::RGB((entry[0]*255.0) as u8, (entry[1]*255.0) as u8, (entry[2]*255.0) as u8), origin); - } - - if (self.control & CTRL_BG_SPRITE_ENABLE) == CTRL_BG_SPRITE_ENABLE { - self.render_sprites(&queue); + self.set_pixel(render_x, render_y, sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]), origin); } } @@ -628,8 +646,9 @@ impl Display { // Obtain tile information let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id); - let tile_line_1 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2]; - let tile_line_2 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2 + 1]; + let tile_addr = tile_base_addr + (tile_offset_y as usize) * 2; + let tile_line_1 = self.vram[tile_addr]; + let tile_line_2 = self.vram[tile_addr + 1]; // Get the correct bit let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0; @@ -649,9 +668,12 @@ impl Display { 0 => PixelOrigin::Empty, // Hack so that objects will be in front of it. _ => PixelOrigin::Window, }; - self.set_pixel(render_x, render_y, sdl2::pixels::Color::RGB((entry[0]*255.0) as u8, (entry[1]*255.0) as u8, (entry[2]*255.0) as u8), origin); + self.set_pixel(render_x, render_y, sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]), origin); } } } + if (self.control & CTRL_BG_SPRITE_ENABLE) == CTRL_BG_SPRITE_ENABLE { + self.render_sprites(&queue); + } } } diff --git a/src/interconnect.rs b/src/interconnect.rs index c176fd4..0f64c4c 100644 --- a/src/interconnect.rs +++ b/src/interconnect.rs @@ -2,6 +2,8 @@ const WRAM_SIZE: usize = 0x8000; const HIRAM_SIZE: usize = (0xFFFE - 0xFF80) + 1; +pub const INTERRUPT_INPUT: u8 = 1 << 4; +pub const INTERRUPT_SERIAL: u8 = 1 << 3; pub const INTERRUPT_TIMER_OVERFLOW: u8 = 1 << 2; pub const INTERRUPT_DISPLAY_STAT: u8 = 1 << 1; pub const INTERRUPT_DISPLAY_VBLANK: u8 = 1 << 0; @@ -92,11 +94,13 @@ impl Interconnect { joy_regular_keys: 0x0F, joy_special_keys: 0x0F, joy_switch: 0x30, + + cycles: 0, } } fn press_key(&mut self, key: Key) { - println!("Press key {:?}", &key); + // println!("Press key {:?}", &key); match key { Key::UP => self.joy_regular_keys &= !KEY_UP, Key::DOWN => self.joy_regular_keys &= !KEY_DOWN, @@ -107,10 +111,11 @@ impl Interconnect { Key::A => self.joy_special_keys &= !KEY_A, Key::B => self.joy_special_keys &= !KEY_B, } + self.interrupt_request_flags |= INTERRUPT_INPUT; } fn release_key(&mut self, key: Key) { - println!("Release key {:?}", &key); + // println!("Release key {:?}", &key); match key { Key::UP => self.joy_regular_keys |= KEY_UP, Key::DOWN => self.joy_regular_keys |= KEY_DOWN, @@ -121,6 +126,7 @@ impl Interconnect { Key::A => self.joy_special_keys |= KEY_A, Key::B => self.joy_special_keys |= KEY_B, } + self.interrupt_request_flags |= INTERRUPT_INPUT; } // Somehow we need different timers for this. @@ -140,36 +146,46 @@ impl Interconnect { self.interrupt_request_flags |= INTERRUPT_TIMER_OVERFLOW; } - // Make sure the window is responsive: - loop { - if let Some(event) = self.display.event_pump.poll_event(){ - match event { - Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { - panic!("TODO: Proper shutdown"); - }, - Event::KeyDown { keycode: Some(Keycode::Left), .. } => self.press_key(Key::LEFT), - Event::KeyDown { keycode: Some(Keycode::Down), .. } => self.press_key(Key::DOWN), - Event::KeyDown { keycode: Some(Keycode::Up), .. } => self.press_key(Key::UP), - Event::KeyDown { keycode: Some(Keycode::Right), .. } => self.press_key(Key::RIGHT), - Event::KeyDown { keycode: Some(Keycode::A), .. } => self.press_key(Key::START), - Event::KeyDown { keycode: Some(Keycode::S), .. } => self.press_key(Key::SELECT), - Event::KeyDown { keycode: Some(Keycode::Z), .. } => self.press_key(Key::A), - Event::KeyDown { keycode: Some(Keycode::X), .. } => self.press_key(Key::B), + if self.serial.serial_interrupt() { + self.interrupt_request_flags |= INTERRUPT_SERIAL; + } - Event::KeyUp { keycode: Some(Keycode::Left), .. } => self.release_key(Key::LEFT), - Event::KeyUp { keycode: Some(Keycode::Down), .. } => self.release_key(Key::DOWN), - Event::KeyUp { keycode: Some(Keycode::Up), .. } => self.release_key(Key::UP), - Event::KeyUp { keycode: Some(Keycode::Right), .. } => self.release_key(Key::RIGHT), - Event::KeyUp { keycode: Some(Keycode::A), .. } => self.release_key(Key::START), - Event::KeyUp { keycode: Some(Keycode::S), .. } => self.release_key(Key::SELECT), - Event::KeyUp { keycode: Some(Keycode::Z), .. } => self.release_key(Key::A), - Event::KeyUp { keycode: Some(Keycode::X), .. } => self.release_key(Key::B), - _ => {} - } - } else { - break; - } - } + // Make sure the window is responsive: + if self.cycles > 500 { + loop { + if let Some(event) = self.display.event_pump.poll_event(){ + match event { + Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { + self.cartridge.save(); + panic!("TODO: Proper shutdown"); + }, + Event::KeyDown { keycode: Some(Keycode::Left), .. } => self.press_key(Key::LEFT), + Event::KeyDown { keycode: Some(Keycode::Down), .. } => self.press_key(Key::DOWN), + Event::KeyDown { keycode: Some(Keycode::Up), .. } => self.press_key(Key::UP), + Event::KeyDown { keycode: Some(Keycode::Right), .. } => self.press_key(Key::RIGHT), + Event::KeyDown { keycode: Some(Keycode::A), .. } => self.press_key(Key::START), + Event::KeyDown { keycode: Some(Keycode::S), .. } => self.press_key(Key::SELECT), + Event::KeyDown { keycode: Some(Keycode::Z), .. } => self.press_key(Key::A), + Event::KeyDown { keycode: Some(Keycode::X), .. } => self.press_key(Key::B), + + Event::KeyUp { keycode: Some(Keycode::Left), .. } => self.release_key(Key::LEFT), + Event::KeyUp { keycode: Some(Keycode::Down), .. } => self.release_key(Key::DOWN), + Event::KeyUp { keycode: Some(Keycode::Up), .. } => self.release_key(Key::UP), + Event::KeyUp { keycode: Some(Keycode::Right), .. } => self.release_key(Key::RIGHT), + Event::KeyUp { keycode: Some(Keycode::A), .. } => self.release_key(Key::START), + Event::KeyUp { keycode: Some(Keycode::S), .. } => self.release_key(Key::SELECT), + Event::KeyUp { keycode: Some(Keycode::Z), .. } => self.release_key(Key::A), + Event::KeyUp { keycode: Some(Keycode::X), .. } => self.release_key(Key::B), + _ => {} + } + } else { + break; + } + } + self.cycles = 0; + } else { + self.cycles += cycles as u16; + } } #[inline] diff --git a/src/serial.rs b/src/serial.rs index ab6758b..59dd0b6 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -11,7 +11,10 @@ impl Serial { pub fn write_byte(&mut self, addr: u16, val: u8) { match addr { - 0xFF01 => self.data = val, + 0xFF01 => { + println!("Serial: Write {:02X}", val); + self.data = val; + } 0xFF02 => self.control = val, _ => panic!("Serial: Write {:02X} to {:04X} unsupported", val, addr), } @@ -24,4 +27,9 @@ impl Serial { _ => panic!("Serial: Read from {:04X} unsupported", addr), } } + + pub fn serial_interrupt(&self) -> bool { + // TODO + false + } }