From 7fa6857ac3a574587f9011eacda365f36387ad67 Mon Sep 17 00:00:00 2001 From: Franco Colmenarez Date: Mon, 15 Nov 2021 11:25:30 -0500 Subject: [PATCH] Refactor ppu and vram (currently crashing) --- src/bus.rs | 23 +++++++++++++----- src/emulator.rs | 20 +++++++++------- src/ppu.rs | 62 ++++++++++++++++++++++++++++--------------------- 3 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/bus.rs b/src/bus.rs index febf1a7..dccc470 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -8,6 +8,7 @@ use crate::utils::{ }; use crate::rom::ROM; use crate::ppu::{ + PPU, LCD_STATUS_ADDRESS, LCD_CONTROL_ADDRESS, LCD_Y_ADDRESS, @@ -35,12 +36,13 @@ pub const INTERRUPT_FLAG_ADDRESS: u16 = 0xFF0F; pub struct Bus { game_rom: ROM, data: [u8; 0x10000], + ppu: Rc>, joypad: Rc>, timer: Rc>, } impl Bus { - pub fn new(joypad: Rc>, timer: Rc>) -> Self { + pub fn new(ppu: Rc>, joypad: Rc>, timer: Rc>) -> Self { let args: Vec = std::env::args().collect(); if args.len() < 2 { println!("Please, specify a ROM file"); @@ -80,6 +82,7 @@ impl Bus { Self { data, game_rom, + ppu, joypad, timer, } @@ -90,6 +93,10 @@ impl Bus { return self.game_rom.read(address); } else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS { return 0b11100000 | self.data[address as usize]; + } else if VIDEO_RAM.contains(&address) { + return self.ppu.borrow().read_vram(address); + } else if SPRITE_ATTRIBUTE_TABLE.contains(&address) { + return self.ppu.borrow().read_oam(address); } else if address == JOYPAD_ADDRESS { return self.joypad.borrow().read(self.data[address as usize]); } else if address == TIMER_DIVIDER_REGISTER_ADDRESS { @@ -140,14 +147,18 @@ 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 VIDEO_RAM.contains(&address) { + return self.ppu.borrow_mut().write_vram(address, data); + } else if SPRITE_ATTRIBUTE_TABLE.contains(&address) { + return self.ppu.borrow_mut().write_oam(address, data); } 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.min().unwrap() as usize; + let source = (data as u16) * 0x100; + let mut count: u16 = 0; + let oam_addr = SPRITE_ATTRIBUTE_TABLE.min().unwrap(); + let mut ppu = self.ppu.borrow_mut(); while count < 160 { - self.data[oam_addr + count] = self.data[source + count]; + ppu.write_oam(oam_addr + count, self.data[(source + count) as usize]); count += 1; } } else { diff --git a/src/emulator.rs b/src/emulator.rs index 3d6336b..7bc6f79 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -11,9 +11,9 @@ use crate::timer::Timer; use crate::joypad::{Joypad, Button}; pub struct Emulator { - cpu: CPU, - ppu: PPU, bus: Bus, + cpu: CPU, + ppu: Rc>, timer: Rc>, joypad: Rc>, } @@ -21,11 +21,12 @@ pub struct Emulator { impl Emulator { pub fn new() -> Self { let joypad = Rc::new(RefCell::new(Joypad::new())); - let timer = Rc::new(RefCell::new(Timer::new())); + let timer = Rc::new(RefCell::new(Timer::new())); + let ppu = Rc::new(RefCell::new(PPU::new())); Self { + bus: Bus::new(Rc::clone(&ppu), Rc::clone(&joypad), Rc::clone(&timer)), cpu: CPU::new(), - ppu: PPU::new(), - bus: Bus::new(Rc::clone(&joypad), Rc::clone(&timer)), + ppu, timer, joypad, } @@ -106,10 +107,13 @@ impl Emulator { pub fn run(&mut self, cpu_cycles: Cycles, frame_buffer: &mut [u8]) { self.cpu.reset_cycles(); + let mut ppu = self.ppu.borrow_mut(); + let mut timer = self.timer.borrow_mut(); while self.cpu.get_cycles().to_t().0 <= cpu_cycles.0 { self.cpu.run(&mut self.bus); - self.ppu.do_cycles(&mut self.bus, self.cpu.get_last_op_cycles().to_t(), frame_buffer); - self.timer.borrow_mut().do_cycles(&mut self.bus, self.cpu.get_last_op_cycles().to_t()); + let cycles = self.cpu.get_last_op_cycles().to_t(); + ppu.do_cycles(&mut self.bus, cycles, frame_buffer); + timer.do_cycles(&mut self.bus, cycles); // 1 CPU cycle = 238.42ns // thread::sleep(time::Duration::from_nanos((self.cpu.get_last_op_cycles().0 * 238).try_into().unwrap())); @@ -122,7 +126,7 @@ impl Emulator { let mut frame: [u8; 144 * 160 * 4] = [0; 144 * 160 * 4]; while !exit { self.cpu.run(&mut self.bus); - self.ppu.do_cycles(&mut self.bus, self.cpu.get_last_op_cycles().to_t(), &mut frame); + self.ppu.borrow_mut().do_cycles(&mut self.bus, self.cpu.get_last_op_cycles().to_t(), &mut frame); self.timer.borrow_mut().do_cycles(&mut self.bus, self.cpu.get_last_op_cycles().to_t()); // exit = self.cpu.get_exec_calls_count() >= 1258895; // log 1 diff --git a/src/ppu.rs b/src/ppu.rs index c6c1125..34182e1 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -3,7 +3,7 @@ use crate::utils::{ get_bit, set_bit, }; -use crate::bus::{Bus, SPRITE_ATTRIBUTE_TABLE}; +use crate::bus::{Bus, VIDEO_RAM, SPRITE_ATTRIBUTE_TABLE}; use crate::cpu::{Cycles, Interrupt}; pub const LCD_WIDTH: u32 = 160; @@ -142,7 +142,7 @@ impl Sprite { self.x } - pub fn get_pixel(&mut self, lcd_x: u8, lcd_y: u8, bus: &Bus, last_bg_index: u8) -> Option<(Pixel, bool)> { + pub fn get_pixel(&mut self, lcd_x: u8, lcd_y: u8, vram: &[u8], last_bg_index: u8) -> Option<(Pixel, bool)> { if lcd_x < self.x.saturating_sub(8) || lcd_x >= self.x { return None; } @@ -184,8 +184,8 @@ impl Sprite { let tile_line = y.rem_euclid(height) * 2; let addr = 0x8000 + (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 tile_byte_1 = vram[addr as usize - 0x2000]; + let tile_byte_2 = vram[addr as usize - 0x2000 + 1]; let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2); self.bit_pixels = Some(bit_pixels_array); @@ -216,6 +216,8 @@ pub struct PPU { scroll_y: u8, window_x: u8, window_y: u8, + vram: [u8; 0x2000], + oam: [u8; 0xA0], } impl PPU { @@ -235,9 +237,27 @@ impl PPU { scroll_y: 0, window_x: 0, window_y: 0, + vram: [0; 0x2000], + oam: [0; 0xA0], } } + pub fn read_vram(&self, address: u16) -> u8 { + self.vram[(address - VIDEO_RAM.min().unwrap()) as usize] + } + + pub fn write_vram(&mut self, address: u16, data: u8) { + self.vram[(address - VIDEO_RAM.min().unwrap()) as usize] = data; + } + + pub fn read_oam(&self, address: u16) -> u8 { + self.oam[(address - SPRITE_ATTRIBUTE_TABLE.min().unwrap()) as usize] + } + + pub fn write_oam(&mut self, address: u16, data: u8) { + self.oam[(address - SPRITE_ATTRIBUTE_TABLE.min().unwrap()) as usize] = data; + } + pub fn reset_cycles(&mut self) { self.cycles.0 = 0; } @@ -294,7 +314,6 @@ impl PPU { self.window_y_counter = 0; } bus.force_write(LCD_Y_ADDRESS, self.lcd_y); - // self.check_lyc(bus); self.stat_interrupt(bus); } } @@ -323,15 +342,6 @@ impl PPU { } } - fn check_lyc(&mut self, bus: &mut Bus) { - let lyc_compare = self.lcd_y == bus.read(LCD_Y_COMPARE_ADDRESS); - PPU::set_lcd_status(bus, LCDStatus::LYCFlag, lyc_compare); - if !self.state && lyc_compare && PPU::get_lcd_status(bus, LCDStatus::LYCInterrupt) { - bus.set_interrupt_flag(Interrupt::LCDSTAT, true); - self.state = true; - } - } - fn oam_search(&mut self, bus: &Bus) { if !self.get_lcd_control(bus, LCDControl::ObjectEnable) { return; @@ -348,8 +358,8 @@ impl PPU { // todo!("Make a setting for the 10 sprites per scanline"); break; } - let y = bus.read(addr); - let x = bus.read(addr + 1); + let y = self.read_oam(addr); + let x = self.read_oam(addr + 1); if x == 0 { addr += 4; @@ -369,8 +379,8 @@ impl PPU { } - let tile_number = bus.read(addr + 2); - let attributes = bus.read(addr + 3); + let tile_number = self.read_oam(addr + 2); + let attributes = self.read_oam(addr + 3); let palette_zero = !get_bit(attributes, BitIndex::I4); self.sprite_buffer.push(Sprite { @@ -394,10 +404,10 @@ impl PPU { self.sprite_buffer.sort_by(|a, b| a.x().cmp(&b.x())); } - fn find_sprite_pixel(&mut self, lcd_x: u8, bus: &Bus) -> Option<(Pixel, bool)> { + fn find_sprite_pixel(&mut self, lcd_x: u8) -> Option<(Pixel, bool)> { let lcd_y = self.lcd_y; for sprite in &mut self.sprite_buffer { - if let Some(pixel) = sprite.get_pixel(lcd_x, lcd_y, bus, self.last_bg_index) { + if let Some(pixel) = sprite.get_pixel(lcd_x, lcd_y, &self.vram, self.last_bg_index) { return Some(pixel); } } @@ -452,12 +462,12 @@ impl PPU { bus.force_write(LCD_STATUS_ADDRESS, byte); } - fn get_tile_bytes(x: u8, y: u8, tilemap_area: u16, default_method: bool, bus: &Bus) -> (u8, u8) { + fn get_tile_bytes(&self, x: u8, y: u8, tilemap_area: u16, default_method: bool) -> (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 = bus.read(tilemap_area + index as u16) as u16; + let tile_number = self.read_vram(tilemap_area + index as u16) as u16; let addr = if default_method { 0x8000 + tile_line as u16 + (tile_number * 16) } else { @@ -467,7 +477,7 @@ impl PPU { (base + tile_line + (tile_number * 16)) as u16 }; - (bus.read(addr), bus.read(addr + 1)) + (self.read_vram(addr), self.read_vram(addr + 1)) } fn get_window_pixel(&mut self, lcd_x: u8, bus: &Bus) -> Option { @@ -506,7 +516,7 @@ impl PPU { true => 0x9C00, false => 0x9800, }; - let (tile_byte_1, tile_byte_2) = PPU::get_tile_bytes(x, y, tilemap_area, default_mode, bus); + 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); @@ -542,7 +552,7 @@ impl PPU { true => 0x9C00, false => 0x9800, }; - let (tile_byte_1, tile_byte_2) = PPU::get_tile_bytes(x, y, tilemap_area, default_mode, bus); + 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); @@ -581,7 +591,7 @@ impl PPU { frame_buffer[idx + 2] = rgba[2]; } if self.get_lcd_control(bus, LCDControl::ObjectEnable) { - if let Some((sprite_pixel, palette_zero)) = self.find_sprite_pixel(lcd_x, bus) { + if let Some((sprite_pixel, palette_zero)) = self.find_sprite_pixel(lcd_x) { let rgba = PPU::get_rgba(sprite_pixel, match palette_zero { true => SPRITE_0_COLORS, false => SPRITE_1_COLORS,