Speed enhancements; Write savefiles
This commit is contained in:
parent
be4f9da380
commit
023b9ff0e4
79
src/cpu.rs
79
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user