mirror of
https://github.com/FranLMSP/rmg-001.git
synced 2024-11-23 10:12:11 +00:00
Refactor PPU interrupt requests and fix Scroll X
This commit is contained in:
parent
60fd55abca
commit
c5ff92c0ab
16
src/bus.rs
16
src/bus.rs
@ -59,7 +59,7 @@ pub struct Bus {
|
|||||||
|
|
||||||
impl Bus {
|
impl Bus {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let game_rom = match ROM::load_file("ignore/tetris.gb".to_string()) {
|
let game_rom = match ROM::load_file("ignore/m3_scy_change.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.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/01-special.gb".to_string()) {
|
||||||
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/02-interrupts.gb".to_string()) {
|
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/02-interrupts.gb".to_string()) {
|
||||||
@ -111,9 +111,6 @@ impl Bus {
|
|||||||
if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) {
|
if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) {
|
||||||
return self.game_rom.read(address);
|
return self.game_rom.read(address);
|
||||||
}
|
}
|
||||||
if address == JOYPAD_ADDRESS {
|
|
||||||
println!("{:08b}", self.data[address as usize]);
|
|
||||||
}
|
|
||||||
self.data[address as usize]
|
self.data[address as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,11 +139,14 @@ impl Bus {
|
|||||||
} else if address == LCD_CONTROL_ADDRESS && get_bit(data, BitIndex::I7) {
|
} else if address == LCD_CONTROL_ADDRESS && get_bit(data, BitIndex::I7) {
|
||||||
self.data[address as usize] = data;
|
self.data[address as usize] = data;
|
||||||
self.data[LCD_Y_ADDRESS as usize] = 0x00;
|
self.data[LCD_Y_ADDRESS as usize] = 0x00;
|
||||||
} else if address == JOYPAD_ADDRESS {
|
|
||||||
let byte = self.data[JOYPAD_ADDRESS as usize];
|
|
||||||
self.data[JOYPAD_ADDRESS as usize] = (data & 0b11110000) | (byte & 0b00001111);
|
|
||||||
} else if address == LCD_Y_ADDRESS {
|
} else if address == LCD_Y_ADDRESS {
|
||||||
println!("Write to LCD_Y not allowed");
|
// println!("Write to LCD_Y not allowed");
|
||||||
|
} else if address == LCD_STATUS_ADDRESS {
|
||||||
|
let byte = self.data[address as usize];
|
||||||
|
self.data[address as usize] = (data & 0b11111000) | (byte & 0b00000111);
|
||||||
|
} else if address == JOYPAD_ADDRESS {
|
||||||
|
let byte = self.data[address as usize];
|
||||||
|
self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111);
|
||||||
} else {
|
} else {
|
||||||
self.data[address as usize] = data;
|
self.data[address as usize] = data;
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,6 @@ impl Emulator {
|
|||||||
self.joypad.release(Button::Select);
|
self.joypad.release(Button::Select);
|
||||||
}
|
}
|
||||||
if change {
|
if change {
|
||||||
self.joypad.update(&mut self.bus);
|
|
||||||
self.bus.set_interrupt_flag(Interrupt::Joypad, true);
|
self.bus.set_interrupt_flag(Interrupt::Joypad, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ impl Joypad {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&self, bus: &mut Bus) {
|
pub fn read(&self, bus: &mut Bus) -> u8 {
|
||||||
let byte = bus.read(JOYPAD_ADDRESS);
|
let byte = bus.read(JOYPAD_ADDRESS);
|
||||||
let direction = !get_bit(byte, BitIndex::I4);
|
let direction = !get_bit(byte, BitIndex::I4);
|
||||||
let action = !get_bit(byte, BitIndex::I5);
|
let action = !get_bit(byte, BitIndex::I5);
|
||||||
@ -83,7 +83,6 @@ impl Joypad {
|
|||||||
) | (
|
) | (
|
||||||
(!((direction && self.right) || (action && self.a)) as u8)
|
(!((direction && self.right) || (action && self.a)) as u8)
|
||||||
);
|
);
|
||||||
println!("New joypad write: {:08b}", data);
|
return data;
|
||||||
bus.force_write(JOYPAD_ADDRESS, data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
73
src/ppu.rs
73
src/ppu.rs
@ -91,6 +91,7 @@ pub enum LCDStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct PPU {
|
pub struct PPU {
|
||||||
|
prev_state: bool,
|
||||||
cycles: Cycles,
|
cycles: Cycles,
|
||||||
rgba_frame: [[u8; 4]; FRAME_BUFFER_LENGTH as usize],
|
rgba_frame: [[u8; 4]; FRAME_BUFFER_LENGTH as usize],
|
||||||
}
|
}
|
||||||
@ -103,6 +104,7 @@ enum TileNumber {
|
|||||||
impl PPU {
|
impl PPU {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
prev_state: false,
|
||||||
cycles: Cycles(0),
|
cycles: Cycles(0),
|
||||||
rgba_frame: [[0xFF, 0xFF, 0xFF, 0]; FRAME_BUFFER_LENGTH as usize],
|
rgba_frame: [[0xFF, 0xFF, 0xFF, 0]; FRAME_BUFFER_LENGTH as usize],
|
||||||
}
|
}
|
||||||
@ -134,9 +136,7 @@ impl PPU {
|
|||||||
if self.cycles.0 == 0 {
|
if self.cycles.0 == 0 {
|
||||||
// Mode 2 OAM scan
|
// Mode 2 OAM scan
|
||||||
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM), true);
|
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM), true);
|
||||||
if PPU::get_lcd_status(bus, LCDStatus::Mode2OAMInterrupt) {
|
self.stat_interrupt(bus);
|
||||||
PPU::request_interrupt(bus, Interrupt::LCDSTAT);
|
|
||||||
}
|
|
||||||
} else if self.cycles.0 == 80 + 1 {
|
} else if self.cycles.0 == 80 + 1 {
|
||||||
// Mode 3 drawing pixel line. This could also last 289 cycles
|
// Mode 3 drawing pixel line. This could also last 289 cycles
|
||||||
self.draw_line(bus, frame_buffer);
|
self.draw_line(bus, frame_buffer);
|
||||||
@ -144,16 +144,13 @@ impl PPU {
|
|||||||
} else if self.cycles.0 == 80 + 172 + 1 {
|
} else if self.cycles.0 == 80 + 172 + 1 {
|
||||||
// Mode 0 Horizontal blank. This could last 87 or 204 cycles depending on the mode 3
|
// Mode 0 Horizontal blank. This could last 87 or 204 cycles depending on the mode 3
|
||||||
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank), true);
|
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank), true);
|
||||||
if PPU::get_lcd_status(bus, LCDStatus::Mode0HBlankInterrupt) {
|
self.stat_interrupt(bus);
|
||||||
PPU::request_interrupt(bus, Interrupt::LCDSTAT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if PPU::get_lcd_y(bus) == 144 && self.cycles.0 == 0 {
|
} else if PPU::get_lcd_y(bus) == 144 && self.cycles.0 == 0 {
|
||||||
// Mode 1 Vertical blank
|
// Mode 1 Vertical blank
|
||||||
|
bus.set_interrupt_flag(Interrupt::VBlank, true);
|
||||||
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank), true);
|
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank), true);
|
||||||
if PPU::get_lcd_status(bus, LCDStatus::Mode1VBlankInterrupt) {
|
self.stat_interrupt(bus);
|
||||||
PPU::request_interrupt(bus, Interrupt::VBlank);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.increment_cycles(Cycles(1));
|
self.increment_cycles(Cycles(1));
|
||||||
@ -167,21 +164,28 @@ impl PPU {
|
|||||||
if PPU::get_lcd_y(bus) > 153 {
|
if PPU::get_lcd_y(bus) > 153 {
|
||||||
PPU::set_lcd_y(bus, 0);
|
PPU::set_lcd_y(bus, 0);
|
||||||
}
|
}
|
||||||
PPU::check_lyc(bus);
|
self.check_lyc(bus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_lyc(bus: &mut Bus) {
|
fn stat_interrupt(&mut self, bus: &mut Bus) {
|
||||||
|
let state = self.prev_state;
|
||||||
|
self.prev_state = (
|
||||||
|
(PPU::get_lcd_status(bus, LCDStatus::Mode2OAMInterrupt) && PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM))) ||
|
||||||
|
(PPU::get_lcd_status(bus, LCDStatus::Mode0HBlankInterrupt) && PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank))) ||
|
||||||
|
(PPU::get_lcd_status(bus, LCDStatus::Mode1VBlankInterrupt) && PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank)))
|
||||||
|
);
|
||||||
|
if self.prev_state && !state {
|
||||||
|
bus.set_interrupt_flag(Interrupt::LCDSTAT, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_lyc(&mut self, bus: &mut Bus) {
|
||||||
let lyc_compare = PPU::get_lcd_y(bus) == bus.read(LCD_Y_COMPARE_ADDRESS);
|
let lyc_compare = PPU::get_lcd_y(bus) == bus.read(LCD_Y_COMPARE_ADDRESS);
|
||||||
if PPU::get_lcd_status(bus, LCDStatus::LYCInterrupt) && lyc_compare {
|
|
||||||
PPU::set_lcd_status(bus, LCDStatus::LYCFlag, lyc_compare);
|
PPU::set_lcd_status(bus, LCDStatus::LYCFlag, lyc_compare);
|
||||||
PPU::request_interrupt(bus, Interrupt::LCDSTAT);
|
if lyc_compare && PPU::get_lcd_status(bus, LCDStatus::LYCInterrupt) {
|
||||||
}
|
bus.set_interrupt_flag(Interrupt::LCDSTAT, true);
|
||||||
}
|
self.prev_state = true;
|
||||||
|
|
||||||
fn request_interrupt(bus: &mut Bus, interrupt: Interrupt) {
|
|
||||||
if PPU::get_lcd_control(bus, LCDControl::LCDEnable) {
|
|
||||||
bus.set_interrupt_flag(interrupt, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +220,7 @@ impl PPU {
|
|||||||
|
|
||||||
fn set_lcd_control(bus: &mut Bus, control: LCDControl, val: bool) {
|
fn set_lcd_control(bus: &mut Bus, control: LCDControl, val: bool) {
|
||||||
let mut byte = bus.read(LCD_CONTROL_ADDRESS);
|
let mut byte = bus.read(LCD_CONTROL_ADDRESS);
|
||||||
bus.write(LCD_CONTROL_ADDRESS, control.set(byte, val));
|
bus.force_write(LCD_CONTROL_ADDRESS, control.set(byte, val));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_lcd_status(bus: &Bus, status: LCDStatus) -> bool {
|
pub fn get_lcd_status(bus: &Bus, status: LCDStatus) -> bool {
|
||||||
@ -251,7 +255,7 @@ impl PPU {
|
|||||||
LCDStatusModeFlag::TransferringToLCD => (byte & 0b11111100) | 3,
|
LCDStatusModeFlag::TransferringToLCD => (byte & 0b11111100) | 3,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
bus.write(LCD_STATUS_ADDRESS, byte);
|
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, tile_number_type: TileNumber, default_method: bool, bus: &Bus) -> (u8, u8) {
|
||||||
@ -277,15 +281,15 @@ impl PPU {
|
|||||||
|
|
||||||
fn get_window_pixel(lcd_x: u8, bus: &Bus) -> Option<Pixel> {
|
fn get_window_pixel(lcd_x: u8, bus: &Bus) -> Option<Pixel> {
|
||||||
let lcd_y = PPU::get_lcd_y(bus);
|
let lcd_y = PPU::get_lcd_y(bus);
|
||||||
let window_x = (PPU::get_window_x(bus) as i8 - 7) as u8;
|
let window_x = PPU::get_window_x(bus);
|
||||||
let window_y = PPU::get_window_y(bus);
|
let window_y = PPU::get_window_y(bus);
|
||||||
|
|
||||||
if !PPU::get_lcd_control(bus, LCDControl::WindowEnable) || window_x != lcd_x || window_y != lcd_y {
|
if !PPU::get_lcd_control(bus, LCDControl::WindowEnable) || lcd_x < (window_x.saturating_sub(7)) || window_y != lcd_y {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let x = lcd_x - window_x;
|
let x = lcd_x.wrapping_sub(window_x.saturating_sub(7));
|
||||||
let y = lcd_y - window_x;
|
let y = lcd_y.wrapping_sub(window_y);
|
||||||
|
|
||||||
let default_mode = PPU::get_lcd_control(bus, LCDControl::TileAddressMode);
|
let default_mode = PPU::get_lcd_control(bus, LCDControl::TileAddressMode);
|
||||||
let tilemap_area = match PPU::get_lcd_control(bus, LCDControl::WindowTileMapAddress) {
|
let tilemap_area = match PPU::get_lcd_control(bus, LCDControl::WindowTileMapAddress) {
|
||||||
@ -320,23 +324,36 @@ impl PPU {
|
|||||||
|
|
||||||
let bg_pixels = PPU::get_byte_pixels(tile_byte_1, tile_byte_2, palette);
|
let bg_pixels = PPU::get_byte_pixels(tile_byte_1, tile_byte_2, palette);
|
||||||
|
|
||||||
for pixel in bg_pixels {
|
|
||||||
let idx = (lcd_x as usize + (lcd_y as usize * LCD_WIDTH as usize)) * 4;
|
let idx = (lcd_x as usize + (lcd_y as usize * LCD_WIDTH as usize)) * 4;
|
||||||
|
let pixel = bg_pixels[x.rem_euclid(8) as usize];
|
||||||
let rgba = PPU::get_rgba(pixel);
|
let rgba = PPU::get_rgba(pixel);
|
||||||
frame_buffer[idx] = rgba[0];
|
frame_buffer[idx] = rgba[0];
|
||||||
frame_buffer[idx + 1] = rgba[1];
|
frame_buffer[idx + 1] = rgba[1];
|
||||||
frame_buffer[idx + 2] = rgba[2];
|
frame_buffer[idx + 2] = rgba[2];
|
||||||
frame_buffer[idx + 3] = rgba[3];
|
|
||||||
if let Some(window_pixel) = PPU::get_window_pixel(lcd_x, bus) {
|
if let Some(window_pixel) = PPU::get_window_pixel(lcd_x, bus) {
|
||||||
let rgba = PPU::get_rgba(pixel);
|
let rgba = PPU::get_rgba(pixel);
|
||||||
frame_buffer[idx] = rgba[0];
|
frame_buffer[idx] = rgba[0];
|
||||||
frame_buffer[idx + 1] = rgba[1];
|
frame_buffer[idx + 1] = rgba[1];
|
||||||
frame_buffer[idx + 2] = rgba[2];
|
frame_buffer[idx + 2] = rgba[2];
|
||||||
frame_buffer[idx + 3] = rgba[3];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lcd_x += 1;
|
lcd_x += 1;
|
||||||
|
|
||||||
|
/* for pixel in bg_pixels {
|
||||||
|
let idx = (lcd_x as usize + (lcd_y as usize * LCD_WIDTH as usize)) * 4;
|
||||||
|
let rgba = PPU::get_rgba(pixel);
|
||||||
|
frame_buffer[idx] = rgba[0];
|
||||||
|
frame_buffer[idx + 1] = rgba[1];
|
||||||
|
frame_buffer[idx + 2] = rgba[2];
|
||||||
|
if let Some(window_pixel) = PPU::get_window_pixel(lcd_x, bus) {
|
||||||
|
let rgba = PPU::get_rgba(pixel);
|
||||||
|
frame_buffer[idx] = rgba[0];
|
||||||
|
frame_buffer[idx + 1] = rgba[1];
|
||||||
|
frame_buffer[idx + 2] = rgba[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lcd_x += 1;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ pub fn start_eventloop() {
|
|||||||
emulator.run(Cycles(70224), pixels.get_frame());
|
emulator.run(Cycles(70224), pixels.get_frame());
|
||||||
// emulator.draw(pixels.get_frame());
|
// emulator.draw(pixels.get_frame());
|
||||||
|
|
||||||
// thread::sleep(time::Duration::from_millis(14));
|
// thread::sleep(time::Duration::from_millis(1000));
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
},
|
},
|
||||||
Event::RedrawRequested(_) => {
|
Event::RedrawRequested(_) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user