From cf6a14179d046d44496d27b32a6811e0e66bf34e Mon Sep 17 00:00:00 2001 From: Alex Janka Date: Wed, 22 Feb 2023 15:24:31 +1100 Subject: [PATCH] fully mmio'd gpu! --- src/main.rs | 4 +- src/processor/memory.rs | 78 +++- src/processor/memory/mmio/gpu.rs | 364 ++++++++----------- src/processor/memory/mmio/gpu/addresses.rs | 158 ++++++++ src/processor/memory/mmio/gpu/tile_window.rs | 17 +- src/processor/memory/mmio/gpu/types.rs | 138 ++++++- src/processor/mod.rs | 18 +- src/processor/timer.rs | 1 - 8 files changed, 506 insertions(+), 272 deletions(-) create mode 100644 src/processor/memory/mmio/gpu/addresses.rs diff --git a/src/main.rs b/src/main.rs index 3aae68a..6789ec8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -131,9 +131,7 @@ fn main() { window.topmost(true); let mut cpu = Cpu::new( - Memory::init(bootrom, rom, args.connect_serial), - window, - args.tile_window, + Memory::init(bootrom, rom, window, args.connect_serial, args.tile_window), bootrom_enabled, Gilrs::new().unwrap(), ); diff --git a/src/processor/memory.rs b/src/processor/memory.rs index 0bf73d3..aefe531 100644 --- a/src/processor/memory.rs +++ b/src/processor/memory.rs @@ -1,8 +1,8 @@ -use self::mmio::{Apu, Joypad, Serial}; +use self::mmio::{Apu, Gpu, Joypad, Serial}; pub use self::rom::Rom; use crate::{processor::SplitRegister, util::set_bit, verbose_println, Cpu}; use gilrs::Gilrs; -use minifb::Key; +use minifb::{Key, Window}; pub mod mmio; pub(crate) mod rom; @@ -13,23 +13,28 @@ pub(crate) type Address = u16; pub struct Memory { bootrom: Option>, rom: Rom, - vram: [u8; 8192], ram: [u8; 8192], switchable_ram: [u8; 8192], cpu_ram: [u8; 128], - oam: [u8; 160], interrupts: u8, pub(super) ime: bool, pub(super) ime_scheduled: u8, io: [u8; 76], pub(super) user_mode: bool, joypad: Joypad, + gpu: Gpu, apu: Apu, serial: Serial, } impl Memory { - pub fn init(bootrom: Option>, rom: Rom, connect_serial: bool) -> Self { + pub fn init( + bootrom: Option>, + rom: Rom, + window: Window, + connect_serial: bool, + enable_tile_window: bool, + ) -> Self { let serial = if connect_serial { Serial::default().connected() } else { @@ -38,17 +43,16 @@ impl Memory { Self { bootrom, rom, - vram: [0x0; 8192], ram: [0x0; 8192], switchable_ram: [0x0; 8192], cpu_ram: [0x0; 128], - oam: [0x0; 160], interrupts: 0x0, ime: false, ime_scheduled: 0x0, io: [0xFF; 76], user_mode: false, joypad: Joypad::default(), + gpu: Gpu::new(window, enable_tile_window), apu: Apu::init_default(), serial, } @@ -65,14 +69,14 @@ impl Memory { self.rom.get(address) } } - 0x8000..0xA000 => self.vram[(address - 0x8000) as usize], + 0x8000..0xA000 => self.gpu.vram.get(address), 0xA000..0xC000 => { // cart ram self.rom.get_ram(address) } 0xC000..0xE000 => self.ram[(address - 0xC000) as usize], 0xE000..0xFE00 => self.ram[(address - 0xE000) as usize], - 0xFE00..0xFEA0 => self.oam[(address - 0xFE00) as usize], + 0xFE00..0xFEA0 => self.gpu.oam.get(address), 0xFEA0..0xFF00 => 0xFF, 0xFF00..0xFF4C => self.get_io(address), 0xFF4C..0xFF80 => 0xFF, @@ -87,11 +91,11 @@ impl Memory { // change this with MBC code... self.rom.set(address, data); } - 0x8000..0xA000 => self.vram[(address - 0x8000) as usize] = data, + 0x8000..0xA000 => self.gpu.vram.set(address, data), 0xA000..0xC000 => self.rom.set_ram(address, data), 0xC000..0xE000 => self.ram[(address - 0xC000) as usize] = data, 0xE000..0xFE00 => self.ram[(address - 0xE000) as usize] = data, - 0xFE00..0xFEA0 => self.oam[(address - 0xFE00) as usize] = data, + 0xFE00..0xFEA0 => self.gpu.oam.set(address, data), 0xFEA0..0xFF00 => {} 0xFF00..0xFF4C => self.set_io(address, data), 0xFF50 => self.bootrom = None, @@ -112,6 +116,17 @@ impl Memory { 0xFF01 => self.serial.get_queued(), 0xFF02 => self.serial.get_control(), 0xFF10..0xFF40 => self.apu.get_register(address), + 0xFF40 => self.gpu.get_lcdc(), + 0xFF41 => self.gpu.get_lcd_status(), + 0xFF42 => self.gpu.get_scy(), + 0xFF43 => self.gpu.get_scx(), + 0xFF44 => self.gpu.get_ly(), + 0xFF45 => self.gpu.get_lyc(), + 0xFF47 => self.gpu.get_bg_palette(), + 0xFF48 => self.gpu.get_obj_palette_0(), + 0xFF49 => self.gpu.get_obj_palette_1(), + 0xFF4A => self.gpu.get_wy(), + 0xFF4B => self.gpu.get_wx(), _ => self.io[(address - 0xFF00) as usize], } } @@ -133,14 +148,11 @@ impl Memory { 0xFF07 => self.masked_io(addr_l, data, 0b111), 0xFF0F => self.masked_io(addr_l, data, 0b11111), 0xFF10..0xFF40 => self.apu.mmio_write(address, data), - 0xFF41 => { - // mixed read/write - self.masked_io(addr_l, data, 0b01111000); - } - 0xFF03 | 0xFF08..0xFF0F | 0xFF44 => { - // read-only addresses - println!("BANNED write: {data:#X} to {address:#X}"); - } + 0xFF40 => self.gpu.update_lcdc(data), + 0xFF41 => self.gpu.update_lcd_status(data), + 0xFF42 => self.gpu.update_scy(data), + 0xFF43 => self.gpu.update_scx(data), + 0xFF45 => self.gpu.update_lyc(data), 0xFF46 => { if data > 0xDF { panic!("dma transfer out of bounds: {data:#X}"); @@ -150,9 +162,18 @@ impl Memory { addr.set_high(data); for l in 0x0..0xA0 { addr.set_low(l); - self.oam[l as usize] = self.get(addr); + self.gpu.oam.data[l as usize] = self.get(addr); } } + 0xFF47 => self.gpu.update_bg_palette(data), + 0xFF48 => self.gpu.update_obj_palette_0(data), + 0xFF49 => self.gpu.update_obj_palette_1(data), + 0xFF4A => self.gpu.update_wy(data), + 0xFF4B => self.gpu.update_wx(data), + 0xFF03 | 0xFF08..0xFF0F | 0xFF44 => { + // read-only addresses + println!("BANNED write: {data:#X} to {address:#X}"); + } 0x0..0xFF00 | 0xFF4C..=u16::MAX => panic!("passed wrong address to set_io"), _ => self.io[addr_l] = data, } @@ -204,6 +225,23 @@ impl Cpu { if self.memory.serial.tick(steps) { self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 3)); } + + let gpu_interrupts = self.memory.gpu.tick(steps); + + if gpu_interrupts.vblank { + self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 0)); + + if self + .memory + .update_pressed_keys(self.memory.gpu.window.get_keys(), &mut self.gamepad_handler) + { + self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 4)); + } + } + + if gpu_interrupts.lcd_stat { + self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 1)); + } } } diff --git a/src/processor/memory/mmio/gpu.rs b/src/processor/memory/mmio/gpu.rs index cf7ba1d..c07ac50 100644 --- a/src/processor/memory/mmio/gpu.rs +++ b/src/processor/memory/mmio/gpu.rs @@ -1,17 +1,19 @@ use self::{ tile_window::TileWindow, types::{ - Colour, DrawMode, Lcdc, ObjSize, Object, ObjectFlags, Palette, TiledataArea, TilemapArea, + Colour, DrawMode, GpuInterrupts, Lcdc, Oam, ObjPalette, ObjSize, Object, ObjectFlags, + Palette, Stat, TiledataArea, TilemapArea, Vram, }, }; use crate::{ - processor::{Cpu, SplitRegister}, - util::{clear_bit, get_bit, set_bit, set_or_clear_bit}, + processor::SplitRegister, + util::{clear_bit, get_bit}, FACTOR, HEIGHT, WIDTH, }; use minifb::{Window, WindowOptions}; use once_cell::sync::OnceCell; +mod addresses; mod tile_window; mod types; @@ -31,17 +33,29 @@ pub fn init_statics() { pub struct Gpu { pub buffer: Vec, + pub vram: Vram, + pub oam: Oam, + pub window: Window, scaled_buffer: Vec, - mode: DrawMode, + lcdc: Lcdc, + stat: Stat, mode_clock: usize, scanline: u8, + lyc: u8, tile_window: Option, window_lc: u8, has_window_been_enabled: bool, + bg_palette: Palette, + obj_palette_0: Palette, + obj_palette_1: Palette, + scx: u8, + scy: u8, + wx: u8, + wy: u8, } impl Gpu { - pub fn new(enable_tile_window: bool) -> Self { + pub fn new(window: Window, enable_tile_window: bool) -> Self { let tile_window = if enable_tile_window { let mut window = Window::new( "Tiles", @@ -62,41 +76,52 @@ impl Gpu { Self { buffer: vec![0; WIDTH * HEIGHT], + vram: Vram::default(), + oam: Oam::default(), + window, scaled_buffer: vec![0; WIDTH * HEIGHT * 4], - mode: DrawMode::Mode2, + lcdc: Lcdc::default(), + stat: Stat::default(), mode_clock: 0, scanline: 0, + lyc: 0xFF, tile_window, window_lc: 0, has_window_been_enabled: false, + bg_palette: Palette::from_byte(0xFC), + obj_palette_0: Palette::from_byte(0xFF), + obj_palette_1: Palette::from_byte(0xFF), + scx: 0, + scy: 0, + wx: 0, + wy: 0, } } -} -impl Cpu { - pub fn advance_gpu_clock(&mut self, steps: usize) { - let lcdc = self.get_lcdc(); - if lcdc.enable { - self.gpu.mode_clock += steps; - match self.gpu.mode { + pub fn tick(&mut self, steps: usize) -> GpuInterrupts { + let mut interrupts = GpuInterrupts::default(); + if self.lcdc.enable { + self.mode_clock += steps; + match self.stat.mode { DrawMode::HBlank => { // mode 0: hblank - if self.gpu.mode_clock >= 204 { - self.gpu.mode_clock = 0; - self.gpu.scanline += 1; - if self.gpu.scanline == 143 { + if self.mode_clock >= 204 { + self.mode_clock = 0; + self.scanline += 1; + if self.scanline == 143 { self.enter_vblank(); + interrupts.vblank = true; } else { - self.gpu.mode = DrawMode::Mode2; + self.stat.mode = DrawMode::Mode2; } } } DrawMode::VBlank => { // mode 1: vblank - if self.gpu.mode_clock >= 456 { - self.gpu.mode_clock = 0; - self.gpu.scanline += 1; - if self.gpu.scanline == 153 { + if self.mode_clock >= 456 { + self.mode_clock = 0; + self.scanline += 1; + if self.scanline == 153 { self.exit_vblank(); } } @@ -104,206 +129,124 @@ impl Cpu { DrawMode::Mode2 => { // search oam for sprites on this line // we dont really have to emulate this - if self.gpu.mode_clock >= 80 { - self.gpu.mode_clock = 0; - self.gpu.mode = DrawMode::Mode3; + if self.mode_clock >= 80 { + self.mode_clock = 0; + self.stat.mode = DrawMode::Mode3; } } DrawMode::Mode3 => { // generate scanline - if self.gpu.mode_clock >= 172 { - self.gpu.mode_clock = 0; - self.enter_hblank(&lcdc); + if self.mode_clock >= 172 { + self.mode_clock = 0; + self.enter_hblank(); } } } } else { - self.gpu.mode_clock = 0; - self.gpu.mode = DrawMode::VBlank; - self.gpu.scanline = 0; + self.mode_clock = 0; + self.stat.mode = DrawMode::VBlank; + self.scanline = 0; } - self.set_lcd_status(); + + interrupts.lcd_stat = (self.stat.lyc_eq_ly_interrupt_enabled + && (self.lyc == self.scanline)) + || (self.stat.mode_2_interrupt_enabled && (self.stat.mode == DrawMode::Mode2)) + || (self.stat.vblank_interrupt_enabled && (self.stat.mode == DrawMode::VBlank)) + || (self.stat.hblank_interrupt_enabled && (self.stat.mode == DrawMode::HBlank)); + + interrupts } - fn get_lcdc(&self) -> Lcdc { - let reg = self.memory.get(0xFF40); - Lcdc { - enable: get_bit(reg, 7), - window_tilemap: if get_bit(reg, 6) { - TilemapArea::T9C00 - } else { - TilemapArea::T9800 - }, - window_enable: get_bit(reg, 5), - tile_area: if get_bit(reg, 4) { - TiledataArea::D8000 - } else { - TiledataArea::D9000 - }, - bg_tilemap: if get_bit(reg, 3) { - TilemapArea::T9C00 - } else { - TilemapArea::T9800 - }, - obj_size: if get_bit(reg, 2) { - ObjSize::S8x16 - } else { - ObjSize::S8x8 - }, - obj_enable: get_bit(reg, 1), - bg_window_enable: get_bit(reg, 0), - } - } - - fn enter_hblank(&mut self, lcdc: &Lcdc) { - self.gpu.mode = DrawMode::HBlank; - self.render_scanline(self.gpu.scanline, lcdc); + fn enter_hblank(&mut self) { + self.stat.mode = DrawMode::HBlank; + self.render_scanline(self.scanline); } fn enter_vblank(&mut self) { - if self - .memory - .update_pressed_keys(self.window.get_keys(), &mut self.gamepad_handler) - { - self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 4)); - } - self.gpu.mode = DrawMode::VBlank; + self.stat.mode = DrawMode::VBlank; self.render_window(); - self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 0)); } fn exit_vblank(&mut self) { - self.gpu.mode = DrawMode::Mode2; - self.gpu.scanline = 0; - self.gpu.window_lc = 0; - self.gpu.has_window_been_enabled = false; - if let Some(tile_window) = &mut self.gpu.tile_window { - tile_window.draw_sprite_window(byte_to_palette(self.memory.get(0xFF47)), &self.memory); + self.stat.mode = DrawMode::Mode2; + self.scanline = 0; + self.window_lc = 0; + self.has_window_been_enabled = false; + if let Some(tile_window) = &mut self.tile_window { + tile_window.draw_sprite_window(self.bg_palette, &self.vram); } } - fn set_lcd_status(&mut self) { - let mut stat = self.memory.get(0xFF41); - - let lyc_eq_ly_enabled = get_bit(stat, 6); - let mode_2_enabled = get_bit(stat, 5); - let mode_1_vblank_enabled = get_bit(stat, 4); - let mode_0_hblank_enabled = get_bit(stat, 3); - - let lyc_eq_ly = self.gpu.scanline == self.memory.get(0xFF45); - let mode_2 = self.gpu.mode == DrawMode::Mode2; - let mode_1_vblank = self.gpu.mode == DrawMode::VBlank; - let mode_0_hblank = self.gpu.mode == DrawMode::HBlank; - - if (lyc_eq_ly_enabled && lyc_eq_ly) - || (mode_2_enabled && mode_2) - || (mode_1_vblank_enabled && mode_1_vblank) - || (mode_0_hblank_enabled && mode_0_hblank) - { - self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 1)); - } else { - self.memory - .set(0xFF0F, clear_bit(self.memory.get(0xFF0F), 1)); - } - - stat = set_or_clear_bit(stat, 2, lyc_eq_ly); - stat = set_or_clear_bit( - stat, - 1, - (self.gpu.mode == DrawMode::Mode2) || (self.gpu.mode == DrawMode::Mode3), - ); - stat = set_or_clear_bit( - stat, - 0, - (self.gpu.mode == DrawMode::VBlank) || (self.gpu.mode == DrawMode::Mode3), - ); - self.memory.set(0xFF41, stat); - self.memory.set(0xFF44, self.gpu.scanline); - } - - fn render_scanline(&mut self, scanline: u8, lcdc: &Lcdc) { + fn render_scanline(&mut self, scanline: u8) { for x in 0..WIDTH { - self.gpu.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 0, 255); + self.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 0, 255); } - let bg_palette = byte_to_palette(self.memory.get(0xFF47)); - if lcdc.bg_window_enable { - self.render_scanline_bg(scanline, lcdc, bg_palette); - if lcdc.window_enable { - if !self.gpu.has_window_been_enabled { - self.gpu.window_lc = scanline; + if self.lcdc.bg_window_enable { + self.render_scanline_bg(scanline); + if self.lcdc.window_enable { + if !self.has_window_been_enabled { + self.window_lc = scanline; } - self.render_scanline_window(scanline, lcdc, bg_palette); - self.gpu.has_window_been_enabled = true; + self.render_scanline_window(scanline); + self.has_window_been_enabled = true; } } else { for x in 0..WIDTH { - self.gpu.buffer[(scanline as usize * WIDTH) + x] = - Colour::from_u8_rgb(255, 255, 255); + self.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 255, 255); } } - if lcdc.obj_enable { - self.render_scanline_obj(scanline, lcdc, bg_palette); + if self.lcdc.obj_enable { + self.render_scanline_obj(scanline); } } - fn render_scanline_bg(&mut self, scanline: u8, lcdc: &Lcdc, palette: Palette) { - let scroll_y = 0_u8.wrapping_sub(self.memory.get(0xFF42)); - let scroll_x = 0_u8.wrapping_sub(self.memory.get(0xFF43)); + fn render_scanline_bg(&mut self, scanline: u8) { self.render_tiles( scanline, scanline, - &lcdc.bg_tilemap, - &lcdc.tile_area, - palette, - scroll_x, - scroll_y, + self.lcdc.bg_tilemap, + 0_u8.wrapping_sub(self.scx), + 0_u8.wrapping_sub(self.scy), true, ); } - fn render_scanline_window(&mut self, scanline: u8, lcdc: &Lcdc, palette: Palette) { - let pos_y = self.memory.get(0xFF4A); - // subtracting 7 to get the Real Number... - let pos_x = self.memory.get(0xFF4B); - - if pos_y < 143 && pos_x < 166 { + fn render_scanline_window(&mut self, scanline: u8) { + if self.wy < 143 && self.wx < 166 { // window is on screen self.render_tiles( scanline, - self.gpu.window_lc, - &lcdc.window_tilemap, - &lcdc.tile_area, - palette, - pos_x.wrapping_sub(7), - pos_y, + self.window_lc, + self.lcdc.window_tilemap, + self.wx.wrapping_sub(7), + self.wy, false, ); - self.gpu.window_lc = self.gpu.window_lc.wrapping_add(1); - self.gpu.has_window_been_enabled = true; + self.window_lc = self.window_lc.wrapping_add(1); + self.has_window_been_enabled = true; } } - fn render_scanline_obj(&mut self, scanline: u8, lcdc: &Lcdc, bg_palette: Palette) { - let objs = self.parse_oam(scanline, &lcdc.obj_size); + fn render_scanline_obj(&mut self, scanline: u8) { + let objs = self.parse_oam(scanline); for object in objs { - self.render_object(scanline, object, &lcdc.obj_size, bg_palette); + self.render_object(scanline, object); } } - fn parse_oam(&mut self, scanline: u8, obj_size: &ObjSize) -> Vec { + fn parse_oam(&mut self, scanline: u8) -> Vec { let mut objs = vec![]; for i in (0xFE00..0xFE9F).step_by(4) { - let y_pos = self.memory.get(i).wrapping_sub(16); - if y_pos <= scanline && (y_pos + obj_size.get_height()) > scanline { + let y_pos = self.oam.get(i).wrapping_sub(16); + if y_pos <= scanline && (y_pos + self.lcdc.obj_size.get_height()) > scanline { // sprite is on line - let x_pos = self.memory.get(i + 1); - let mut tile_index = self.memory.get(i + 2); - if *obj_size == ObjSize::S8x16 { + let x_pos = self.oam.get(i + 1); + let mut tile_index = self.oam.get(i + 2); + if self.lcdc.obj_size == ObjSize::S8x16 { tile_index = clear_bit(tile_index, 0); } - let flags = self.memory.get(i + 3); - let palette_addr = if get_bit(flags, 4) { 0xFF49 } else { 0xFF48 }; + let flags = self.oam.get(i + 3); objs.push(Object { x: x_pos, y: y_pos, @@ -312,7 +255,11 @@ impl Cpu { behind_bg_and_window: get_bit(flags, 7), y_flip: get_bit(flags, 6), x_flip: get_bit(flags, 5), - palette: byte_to_palette(self.memory.get(palette_addr)), + palette: if get_bit(flags, 4) { + ObjPalette::One + } else { + ObjPalette::Zero + }, }, oam_location: (i - 0xFE00) as u8, }); @@ -329,29 +276,33 @@ impl Cpu { objs } - fn render_object( - &mut self, - scanline: u8, - object: Object, - obj_size: &ObjSize, - bg_palette: Palette, - ) { + fn render_object(&mut self, scanline: u8, object: Object) { let mut object_row = scanline - object.y; if object.flags.y_flip { - object_row = obj_size.get_height() - (object_row + 1); + object_row = self.lcdc.obj_size.get_height() - (object_row + 1); } let tile_row = object_row % 8; let tile_addr = TiledataArea::D8000 .get_addr(object.tile_index + if object_row >= 8 { 1 } else { 0 }) + (tile_row as u16 * 2); - let lsbs = self.memory.get(tile_addr); - let msbs = self.memory.get(tile_addr + 1); + let lsbs = self.vram.get(tile_addr); + let msbs = self.vram.get(tile_addr + 1); for px_x in 0..8 { let x_addr = if object.flags.x_flip { px_x } else { 7 - px_x }; let lsb = get_bit(lsbs, x_addr); let msb = get_bit(msbs, x_addr); - let colour = bits_to_mapped_colour(lsb, msb, object.flags.palette); - if colour == object.flags.palette.zero { + let colour = match object.flags.palette { + ObjPalette::Zero => self.obj_palette_0, + ObjPalette::One => self.obj_palette_1, + } + .map_bits(lsb, msb); + if colour + == match object.flags.palette { + ObjPalette::Zero => self.obj_palette_0, + ObjPalette::One => self.obj_palette_1, + } + .zero + { continue; } let x_coord_uncorrected = (object.x as usize) + (px_x as usize); @@ -362,9 +313,9 @@ impl Cpu { if x_coord < WIDTH { let buffer_index = (scanline as usize * WIDTH) + x_coord; if !object.flags.behind_bg_and_window - || self.gpu.buffer[buffer_index] == bg_palette.zero.as_rgb() + || self.buffer[buffer_index] == self.bg_palette.zero.as_rgb() { - self.gpu.buffer[buffer_index] = colour.as_rgb(); + self.buffer[buffer_index] = colour.as_rgb(); } } } @@ -375,9 +326,7 @@ impl Cpu { &mut self, scanline: u8, draw_from: u8, - tilemap: &TilemapArea, - tiledata: &TiledataArea, - palette: Palette, + tilemap: TilemapArea, offset_x: u8, offset_y: u8, wrap: bool, @@ -399,27 +348,27 @@ impl Cpu { let tilemap_column = (tile_line_x / 8) as u16; let tile_px_x = tile_line_x % 8; - let tile_addr = tiledata.get_addr( - self.memory - .get(tilemap.get_addr(row_addr + (tilemap_column))), - ) + tiledata_offset as u16; + let tile_addr = self + .lcdc + .tile_area + .get_addr(self.vram.get(tilemap.get_addr(row_addr + (tilemap_column)))) + + tiledata_offset as u16; - let lsbs = self.memory.get(tile_addr); - let msbs = self.memory.get(tile_addr + 1); + let lsbs = self.vram.get(tile_addr); + let msbs = self.vram.get(tile_addr + 1); let lsb = get_bit(lsbs, 7 - tile_px_x); let msb = get_bit(msbs, 7 - tile_px_x); - let colour = bits_to_mapped_colour(lsb, msb, palette); + let colour = self.bg_palette.map_bits(lsb, msb); - self.gpu.buffer[(scanline as usize * WIDTH) + x] = colour.as_rgb(); + self.buffer[(scanline as usize * WIDTH) + x] = colour.as_rgb(); } } fn render_window(&mut self) { - self.gpu.scaled_buffer = - scale_buffer(&self.gpu.buffer, WIDTH, HEIGHT, *FACTOR.get().unwrap()); + self.scaled_buffer = scale_buffer(&self.buffer, WIDTH, HEIGHT, *FACTOR.get().unwrap()); self.window .update_with_buffer( - &self.gpu.scaled_buffer, + &self.scaled_buffer, WIDTH * FACTOR.get().unwrap(), HEIGHT * FACTOR.get().unwrap(), ) @@ -440,30 +389,3 @@ fn scale_buffer(buffer: &[u32], width: usize, height: usize, factor: usize) -> V } v } - -fn bits_to_mapped_colour(lsb: bool, msb: bool, palette: Palette) -> Colour { - match (lsb, msb) { - (true, true) => palette.three, - (true, false) => palette.one, - (false, true) => palette.two, - (false, false) => palette.zero, - } -} - -fn byte_to_palette(byte: u8) -> Palette { - Palette { - zero: bits_to_colour(get_bit(byte, 0), get_bit(byte, 1)), - one: bits_to_colour(get_bit(byte, 2), get_bit(byte, 3)), - two: bits_to_colour(get_bit(byte, 4), get_bit(byte, 5)), - three: bits_to_colour(get_bit(byte, 6), get_bit(byte, 7)), - } -} - -fn bits_to_colour(first: bool, second: bool) -> Colour { - match (first, second) { - (true, true) => Colour::Black, - (true, false) => Colour::LightGray, - (false, true) => Colour::DarkGray, - (false, false) => Colour::White, - } -} diff --git a/src/processor/memory/mmio/gpu/addresses.rs b/src/processor/memory/mmio/gpu/addresses.rs new file mode 100644 index 0000000..72258de --- /dev/null +++ b/src/processor/memory/mmio/gpu/addresses.rs @@ -0,0 +1,158 @@ +use crate::util::{get_bit, set_or_clear_bit}; + +use super::{ + types::{DrawMode, ObjSize, Palette, TiledataArea, TilemapArea}, + Gpu, +}; + +impl Gpu { + pub fn update_lcdc(&mut self, data: u8) { + self.lcdc.enable = get_bit(data, 7); + self.lcdc.window_tilemap = if get_bit(data, 6) { + TilemapArea::T9C00 + } else { + TilemapArea::T9800 + }; + self.lcdc.window_enable = get_bit(data, 5); + self.lcdc.tile_area = if get_bit(data, 4) { + TiledataArea::D8000 + } else { + TiledataArea::D9000 + }; + self.lcdc.bg_tilemap = if get_bit(data, 3) { + TilemapArea::T9C00 + } else { + TilemapArea::T9800 + }; + self.lcdc.obj_size = if get_bit(data, 2) { + ObjSize::S8x16 + } else { + ObjSize::S8x8 + }; + self.lcdc.obj_enable = get_bit(data, 1); + self.lcdc.bg_window_enable = get_bit(data, 0); + } + + pub fn get_lcdc(&self) -> u8 { + (if self.lcdc.enable { 1 } else { 0 }) << 7 + | (match self.lcdc.window_tilemap { + TilemapArea::T9800 => 0, + TilemapArea::T9C00 => 1, + }) << 6 + | (if self.lcdc.window_enable { 1 } else { 0 }) << 5 + | (match self.lcdc.tile_area { + TiledataArea::D8000 => 1, + TiledataArea::D9000 => 0, + }) << 4 + | (match self.lcdc.bg_tilemap { + TilemapArea::T9800 => 0, + TilemapArea::T9C00 => 1, + }) << 3 + | (match self.lcdc.obj_size { + ObjSize::S8x8 => 0, + ObjSize::S8x16 => 1, + }) << 2 + | (if self.lcdc.obj_enable { 1 } else { 0 }) << 1 + | (if self.lcdc.bg_window_enable { 1 } else { 0 }) + } + + pub fn update_lcd_status(&mut self, data: u8) { + self.stat.lyc_eq_ly_interrupt_enabled = get_bit(data, 6); + self.stat.mode_2_interrupt_enabled = get_bit(data, 5); + self.stat.vblank_interrupt_enabled = get_bit(data, 4); + self.stat.hblank_interrupt_enabled = get_bit(data, 3); + } + + pub fn get_lcd_status(&self) -> u8 { + let mut reg = 0xFF; + + reg = set_or_clear_bit(reg, 6, self.stat.lyc_eq_ly_interrupt_enabled); + reg = set_or_clear_bit(reg, 5, self.stat.mode_2_interrupt_enabled); + reg = set_or_clear_bit(reg, 4, self.stat.vblank_interrupt_enabled); + reg = set_or_clear_bit(reg, 3, self.stat.hblank_interrupt_enabled); + + let lyc_eq_ly = self.scanline == self.lyc; + reg = set_or_clear_bit(reg, 2, lyc_eq_ly); + + reg = set_or_clear_bit( + reg, + 1, + (self.stat.mode == DrawMode::Mode2) || (self.stat.mode == DrawMode::Mode3), + ); + reg = set_or_clear_bit( + reg, + 0, + (self.stat.mode == DrawMode::VBlank) || (self.stat.mode == DrawMode::Mode3), + ); + + reg + } + + pub fn update_lyc(&mut self, data: u8) { + self.lyc = data; + } + + pub fn get_lyc(&self) -> u8 { + self.lyc + } + + pub fn get_ly(&self) -> u8 { + self.scanline + } + + pub fn update_bg_palette(&mut self, data: u8) { + self.bg_palette = Palette::from_byte(data); + } + + pub fn get_bg_palette(&self) -> u8 { + self.bg_palette.as_byte() + } + + pub fn update_obj_palette_0(&mut self, data: u8) { + self.obj_palette_0 = Palette::from_byte(data); + } + + pub fn get_obj_palette_0(&self) -> u8 { + self.obj_palette_0.as_byte() + } + + pub fn update_obj_palette_1(&mut self, data: u8) { + self.obj_palette_1 = Palette::from_byte(data); + } + + pub fn get_obj_palette_1(&self) -> u8 { + self.obj_palette_1.as_byte() + } + + pub fn update_scx(&mut self, data: u8) { + self.scx = data; + } + + pub fn update_scy(&mut self, data: u8) { + self.scy = data; + } + + pub fn get_scx(&self) -> u8 { + self.scx + } + + pub fn get_scy(&self) -> u8 { + self.scy + } + + pub fn update_wx(&mut self, data: u8) { + self.wx = data; + } + + pub fn update_wy(&mut self, data: u8) { + self.wy = data; + } + + pub fn get_wx(&self) -> u8 { + self.wx + } + + pub fn get_wy(&self) -> u8 { + self.wy + } +} diff --git a/src/processor/memory/mmio/gpu/tile_window.rs b/src/processor/memory/mmio/gpu/tile_window.rs index 636a396..8966f57 100644 --- a/src/processor/memory/mmio/gpu/tile_window.rs +++ b/src/processor/memory/mmio/gpu/tile_window.rs @@ -3,17 +3,16 @@ use minifb::Window; use crate::{ processor::{ get_bit, - memory::{ - mmio::gpu::{ - bits_to_mapped_colour, scale_buffer, Palette, TiledataArea, TILE_WINDOW_HEIGHT, - TILE_WINDOW_HEIGHT_SCALED, TILE_WINDOW_WIDTH, TILE_WINDOW_WIDTH_SCALED, - }, - Memory, + memory::mmio::gpu::{ + scale_buffer, Palette, TiledataArea, TILE_WINDOW_HEIGHT, TILE_WINDOW_HEIGHT_SCALED, + TILE_WINDOW_WIDTH, TILE_WINDOW_WIDTH_SCALED, }, }, FACTOR, }; +use super::types::Vram; + pub(super) struct TileWindow { sprite_buffer: Vec, sprite_buffer_scaled: Vec, @@ -35,7 +34,7 @@ impl TileWindow { } impl TileWindow { - pub(super) fn draw_sprite_window(&mut self, palette: Palette, memory: &Memory) { + pub(super) fn draw_sprite_window(&mut self, palette: Palette, memory: &Vram) { for tile_y in 0..16 { self.draw_row( tile_y, @@ -76,7 +75,7 @@ impl TileWindow { display_y: usize, area: TiledataArea, palette: Palette, - memory: &Memory, + memory: &Vram, ) { for tile_x in 0..16 { let tile_num = (tile_y * 16) + tile_x; @@ -89,7 +88,7 @@ impl TileWindow { let real_px_x = (tile_x as usize * 8) + px_x as usize; let lsb = get_bit(lsbs, 7 - px_x); let msb = get_bit(msbs, 7 - px_x); - let colour = bits_to_mapped_colour(lsb, msb, palette); + let colour = palette.map_bits(lsb, msb); self.sprite_buffer[real_px_x + (real_px_y * TILE_WINDOW_WIDTH)] = colour.as_rgb(); diff --git a/src/processor/memory/mmio/gpu/types.rs b/src/processor/memory/mmio/gpu/types.rs index 6ece7d0..b8ad570 100644 --- a/src/processor/memory/mmio/gpu/types.rs +++ b/src/processor/memory/mmio/gpu/types.rs @@ -1,4 +1,7 @@ -use crate::util::as_signed; +use crate::{ + processor::memory::Address, + util::{as_signed, get_bit}, +}; #[derive(Debug, PartialEq, Clone, Copy)] pub(super) enum DrawMode { @@ -65,6 +68,21 @@ pub(super) struct Lcdc { pub(super) bg_window_enable: bool, } +impl Default for Lcdc { + fn default() -> Self { + Self { + enable: true, + window_tilemap: TilemapArea::T9800, + window_enable: false, + tile_area: TiledataArea::D8000, + bg_tilemap: TilemapArea::T9800, + obj_size: ObjSize::S8x8, + obj_enable: false, + bg_window_enable: true, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq)] pub(super) enum Colour { White, @@ -87,6 +105,24 @@ impl Colour { let (r, g, b) = (r as u32, g as u32, b as u32); (r << 16) | (g << 8) | b } + + pub(super) fn from_bits(first: bool, second: bool) -> Colour { + match (first, second) { + (true, true) => Colour::Black, + (true, false) => Colour::LightGray, + (false, true) => Colour::DarkGray, + (false, false) => Colour::White, + } + } + + fn as_bits(&self) -> u8 { + match self { + Colour::White => 0b00, + Colour::LightGray => 0b10, + Colour::DarkGray => 0b01, + Colour::Black => 0b11, + } + } } #[derive(Clone, Copy)] @@ -97,11 +133,43 @@ pub(super) struct Palette { pub(super) three: Colour, } +impl Palette { + pub(super) fn from_byte(byte: u8) -> Palette { + Palette { + zero: Colour::from_bits(get_bit(byte, 0), get_bit(byte, 1)), + one: Colour::from_bits(get_bit(byte, 2), get_bit(byte, 3)), + two: Colour::from_bits(get_bit(byte, 4), get_bit(byte, 5)), + three: Colour::from_bits(get_bit(byte, 6), get_bit(byte, 7)), + } + } + + pub(super) fn as_byte(&self) -> u8 { + self.zero.as_bits() + | (self.one.as_bits() << 2) + | (self.two.as_bits() << 4) + | (self.three.as_bits() << 6) + } + + pub(super) fn map_bits(&self, lsb: bool, msb: bool) -> Colour { + match (lsb, msb) { + (true, true) => self.three, + (true, false) => self.one, + (false, true) => self.two, + (false, false) => self.zero, + } + } +} + pub(super) struct ObjectFlags { pub(super) behind_bg_and_window: bool, pub(super) y_flip: bool, pub(super) x_flip: bool, - pub(super) palette: Palette, + pub(super) palette: ObjPalette, +} + +pub(super) enum ObjPalette { + Zero, + One, } pub(super) struct Object { @@ -111,3 +179,69 @@ pub(super) struct Object { pub(super) flags: ObjectFlags, pub(super) oam_location: u8, } + +pub(super) struct Stat { + pub(super) lyc_eq_ly_interrupt_enabled: bool, + pub(super) mode_2_interrupt_enabled: bool, + pub(super) vblank_interrupt_enabled: bool, + pub(super) hblank_interrupt_enabled: bool, + pub(super) mode: DrawMode, +} + +impl Default for Stat { + fn default() -> Self { + Self { + lyc_eq_ly_interrupt_enabled: false, + mode_2_interrupt_enabled: false, + vblank_interrupt_enabled: false, + hblank_interrupt_enabled: false, + mode: DrawMode::Mode2, + } + } +} + +pub struct Vram { + data: [u8; 8192], +} + +impl Vram { + pub fn get(&self, address: Address) -> u8 { + self.data[(address - 0x8000) as usize] + } + + pub fn set(&mut self, address: Address, data: u8) { + self.data[(address - 0x8000) as usize] = data; + } +} + +impl Default for Vram { + fn default() -> Self { + Self { data: [0x0; 8192] } + } +} + +pub struct Oam { + pub data: [u8; 160], +} + +impl Oam { + pub fn get(&self, address: Address) -> u8 { + self.data[(address - 0xFE00) as usize] + } + + pub fn set(&mut self, address: Address, data: u8) { + self.data[(address - 0xFE00) as usize] = data; + } +} + +impl Default for Oam { + fn default() -> Self { + Self { data: [0x0; 160] } + } +} + +#[derive(Default)] +pub struct GpuInterrupts { + pub lcd_stat: bool, + pub vblank: bool, +} diff --git a/src/processor/mod.rs b/src/processor/mod.rs index a5533c7..6278c4d 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -1,15 +1,11 @@ use std::time::Instant; -use self::{ - memory::{mmio::Gpu, Memory}, - timer::Timers, -}; +use self::{memory::Memory, timer::Timers}; use crate::{ util::{clear_bit, get_bit}, verbose_println, }; use gilrs::Gilrs; -use minifb::Window; mod instructions; pub mod memory; @@ -34,8 +30,6 @@ pub struct Cpu { pub reg: Registers, pub last_instruction: u8, last_instruction_addr: u16, - window: Window, - gpu: Gpu, halted: bool, timers: Timers, gamepad_handler: Gilrs, @@ -43,13 +37,7 @@ pub struct Cpu { } impl Cpu { - pub fn new( - mut memory: Memory, - window: Window, - enable_tile_window: bool, - run_bootrom: bool, - gamepad_handler: Gilrs, - ) -> Self { + pub fn new(mut memory: Memory, run_bootrom: bool, gamepad_handler: Gilrs) -> Self { if !run_bootrom { memory.cpu_ram_init(); } @@ -58,8 +46,6 @@ impl Cpu { reg: Registers::init(run_bootrom), last_instruction: 0x0, last_instruction_addr: 0x0, - window, - gpu: Gpu::new(enable_tile_window), halted: false, timers: Timers::init(), gamepad_handler, diff --git a/src/processor/timer.rs b/src/processor/timer.rs index d0d510a..020d72c 100644 --- a/src/processor/timer.rs +++ b/src/processor/timer.rs @@ -27,7 +27,6 @@ impl Cpu { pub(super) fn increment_timers(&mut self, machine_cycles: u8) { let clock_cycles = (machine_cycles as usize) * 4; - self.advance_gpu_clock(clock_cycles); self.advance_mmio_clocks(clock_cycles); self.timers.div_counter += clock_cycles;