From 1747addcd3e9c3f37579550b20b1701fe2f2cce2 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Thu, 11 Jul 2019 18:17:28 +0300 Subject: [PATCH] Start modeling the Lcd Display Former-commit-id: 544f185c6f9eead870032170292b1cc8afc724bf --- src/debugger/command.rs | 38 +++++++ src/gba.rs | 64 ++++++++---- src/lcd.rs | 220 +++++++++++++++++++++++++++++++++++----- 3 files changed, 277 insertions(+), 45 deletions(-) diff --git a/src/debugger/command.rs b/src/debugger/command.rs index 659ba33..1c1a82a 100644 --- a/src/debugger/command.rs +++ b/src/debugger/command.rs @@ -1,6 +1,8 @@ use crate::arm7tdmi::bus::Bus; use crate::arm7tdmi::{Addr, CpuState}; use crate::disass::Disassembler; +use crate::ioregs::consts::*; +use crate::lcd::*; use crate::GBAError; use super::palette_view::create_palette_view; @@ -20,8 +22,10 @@ pub enum DisassMode { #[derive(Debug, PartialEq, Clone)] pub enum Command { Info, + DisplayInfo, Step(usize), Continue, + Frame(usize), HexDump(Addr, usize), Disass(DisassMode, Addr, usize), AddBreakpoint(Addr), @@ -38,6 +42,21 @@ impl Command { use Command::*; match *self { Info => println!("{}", debugger.gba.cpu), + DisplayInfo => { + println!("{:?}", debugger.gba.lcd); + println!( + "DISPCNT: {:#?}", + DisplayControl::from(debugger.gba.sysbus.ioregs.read_reg(REG_DISPCNT)) + ); + println!( + "DISPSTAT: {:#?}", + DisplayStatus::from(debugger.gba.sysbus.ioregs.read_reg(REG_DISPSTAT)) + ); + println!( + "VCOUNT: {:?}", + debugger.gba.sysbus.ioregs.read_reg(REG_VCOUNT) + ); + } Step(count) => { for _ in 0..count { if let Some(bp) = debugger.check_breakpoint() { @@ -95,6 +114,12 @@ impl Command { _ => (), }; }, + Frame(count) => { + for _ in 0..count { + debugger.gba.frame(); + print!(".") + } + } HexDump(addr, nbytes) => { let bytes = debugger.gba.sysbus.get_bytes(addr); hexdump::hexdump(&bytes[0..nbytes]); @@ -189,6 +214,7 @@ impl Debugger { match command.as_ref() { "i" | "info" => Ok(Command::Info), + "dispinfo" => Ok(Command::DisplayInfo), "s" | "step" => { let count = match args.len() { 0 => 1, @@ -202,6 +228,18 @@ impl Debugger { Ok(Command::Step(count as usize)) } "c" | "continue" => Ok(Command::Continue), + "f" | "frame" => { + let count = match args.len() { + 0 => 1, + 1 => self.val_number(&args[0])?, + _ => { + return Err(DebuggerError::InvalidCommandFormat( + "frame [count]".to_string(), + )) + } + }; + Ok(Command::Frame(count as usize)) + } "x" | "hexdump" => { let (addr, n) = match args.len() { 2 => { diff --git a/src/gba.rs b/src/gba.rs index 9de9dc3..9fafbc4 100644 --- a/src/gba.rs +++ b/src/gba.rs @@ -15,11 +15,11 @@ pub struct GameBoyAdvance { pub sysbus: SysBus, // io devices - lcd: Lcd, - dma0: DmaChannel, - dma1: DmaChannel, - dma2: DmaChannel, - dma3: DmaChannel, + pub lcd: Lcd, + pub dma0: DmaChannel, + pub dma1: DmaChannel, + pub dma2: DmaChannel, + pub dma3: DmaChannel, post_bool_flags: bool, } @@ -42,29 +42,51 @@ impl GameBoyAdvance { } } + fn run_cpu_for_n_cycles(&mut self, n: usize) { + let previous_cycles = self.cpu.cycles; + loop { + self.cpu.step_one(&mut self.sysbus).unwrap(); + if n > self.cpu.cycles - previous_cycles { + break; + } + } + } + + pub fn frame(&mut self) { + for _ in 0..Lcd::DISPLAY_HEIGHT { + self.run_cpu_for_n_cycles(Lcd::CYCLES_HDRAW); + let _irq = self.lcd.set_hblank(&mut self.sysbus); + self.run_cpu_for_n_cycles(Lcd::CYCLES_HBLANK); + } + let _irq = self.lcd.set_vblank(&mut self.sysbus); + self.run_cpu_for_n_cycles(Lcd::CYCLES_VBLANK); + self.lcd.render(&mut self.sysbus); // Currently not implemented + self.lcd.set_hdraw(); + } + pub fn step(&mut self) -> GBAResult { let previous_cycles = self.cpu.cycles; - let decoded = self.cpu.step_one(&mut self.sysbus)?; + let executed_insn = self.cpu.step_one(&mut self.sysbus)?; - // drop interrupts at the moment - let cycles = self.cpu.cycles - previous_cycles; - let (dma_cycles, _) = self.dma0.step(cycles, &mut self.sysbus); - let cycles = cycles + dma_cycles; + let mut cycles = self.cpu.cycles - previous_cycles; - let cycles = self.cpu.cycles - previous_cycles; - let (dma_cycles, _) = self.dma1.step(cycles, &mut self.sysbus); - let cycles = cycles + dma_cycles; + // // drop interrupts at the moment - let cycles = self.cpu.cycles - previous_cycles; - let (dma_cycles, _) = self.dma2.step(cycles, &mut self.sysbus); - let cycles = cycles + dma_cycles; + // let (dma_cycles, _) = self.dma0.step(cycles, &mut self.sysbus); + // cycles += dma_cycles; - let cycles = self.cpu.cycles - previous_cycles; - let (dma_cycles, _) = self.dma3.step(cycles, &mut self.sysbus); - let cycles = cycles + dma_cycles; + // let (dma_cycles, _) = self.dma1.step(cycles, &mut self.sysbus); + // cycles += dma_cycles; - let (lcd_cycles, _) = self.lcd.step(cycles, &mut self.sysbus); + // let (dma_cycles, _) = self.dma2.step(cycles, &mut self.sysbus); + // cycles += dma_cycles; - Ok(decoded) // return the decoded instruction for the debugger + // let (dma_cycles, _) = self.dma3.step(cycles, &mut self.sysbus); + // cycles += dma_cycles; + + // let (lcd_cycles, _) = self.lcd.step(cycles, &mut self.sysbus); + // cycles += lcd_cycles; + + Ok(executed_insn) } } diff --git a/src/lcd.rs b/src/lcd.rs index 7cc843a..a817bb9 100644 --- a/src/lcd.rs +++ b/src/lcd.rs @@ -1,4 +1,6 @@ +use super::arm7tdmi::Bus; use super::ioregs::consts::*; +use super::palette::{Palette, Rgb15}; use super::*; use crate::bit::BitIndex; @@ -6,12 +8,12 @@ use crate::num::FromPrimitive; #[derive(Debug, Primitive)] enum BGMode { - Mode0 = 0, - Mode1 = 1, - Mode2 = 2, - Mode3 = 3, - Mode4 = 4, - Mode5 = 5, + BGMode0 = 0, + BGMode1 = 1, + BGMode2 = 2, + BGMode3 = 3, + BGMode4 = 4, + BGMode5 = 5, } #[derive(Debug)] @@ -21,10 +23,7 @@ pub struct DisplayControl { hblank_interval_free: bool, obj_character_vram_mapping: bool, // true - 1 dimentional, false - 2 dimentional forced_blank: bool, - disp_bg0: bool, - disp_bg1: bool, - disp_bg2: bool, - disp_bg3: bool, + disp_bg: [bool; 4], disp_obj: bool, disp_window0: bool, disp_window1: bool, @@ -40,10 +39,7 @@ impl From for DisplayControl { hblank_interval_free: v.bit(5), obj_character_vram_mapping: v.bit(6), forced_blank: v.bit(7), - disp_bg0: v.bit(8), - disp_bg1: v.bit(9), - disp_bg2: v.bit(10), - disp_bg3: v.bit(11), + disp_bg: [v.bit(8), v.bit(9), v.bit(10), v.bit(11)], disp_obj: v.bit(12), disp_window0: v.bit(13), disp_window1: v.bit(14), @@ -61,6 +57,7 @@ pub struct DisplayStatus { hblank_irq_enable: bool, vcount_irq_enable: bool, vcount_setting: u8, + raw_value: u16, } impl From for DisplayStatus { @@ -74,35 +71,210 @@ impl From for DisplayStatus { vcount_irq_enable: v.bit(5), // bits 6-7 are unused in GBA vcount_setting: v.bit_range(8..15) as u8, + raw_value: v, } } } +#[derive(Debug)] +pub struct BgControl { + bg_priority: u8, + character_base_block: u8, + moasic: bool, + colors_palettes: bool, // 0=16/16, 1=256/1) + screen_base_block: u8, + wraparound: bool, + screen_width: usize, + screen_height: usize, +} + +impl From for BgControl { + fn from(v: u16) -> Self { + let (width, height) = match v.bit_range(14..15) { + 0 => (256, 256), + 1 => (512, 256), + 2 => (256, 512), + 3 => (512, 512), + _ => unreachable!(), + }; + BgControl { + bg_priority: v.bit_range(0..1) as u8, + character_base_block: v.bit_range(2..3) as u8, + moasic: v.bit(6), + colors_palettes: v.bit(7), // 0=16/16, 1=256/1) + screen_base_block: v.bit_range(8..12) as u8, + wraparound: v.bit(13), + screen_width: width, + screen_height: height, + } + } +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum LcdState { + HDraw = 0, + HBlank, + VBlank, +} +impl Default for LcdState { + fn default() -> LcdState { + LcdState::HDraw + } +} +use LcdState::*; + #[derive(Debug, Default)] pub struct Lcd { cycles: usize, + state: LcdState, current_scanline: usize, // VCOUNT } impl Lcd { - const DISPLAY_WIDTH: usize = 240; - const DISPLAY_HEIGHT: usize = 160; + pub const DISPLAY_WIDTH: usize = 240; + pub const DISPLAY_HEIGHT: usize = 160; + + pub const CYCLES_PIXEL: usize = 4; + pub const CYCLES_HDRAW: usize = 960; + pub const CYCLES_HBLANK: usize = 272; + pub const CYCLES_SCANLINE: usize = 1232; + pub const CYCLES_VDRAW: usize = 197120; + pub const CYCLES_VBLANK: usize = 83776; pub fn new() -> Lcd { Lcd { + state: HDraw, ..Default::default() } } -} -impl EmuIoDev for Lcd { - fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option) { - self.cycles += cycles; + fn palette(&self, sysbus: &SysBus) -> Palette { + Palette::from(sysbus.get_bytes(0x0500_0000)) + } - // let mut dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT)); - // let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT)); + fn update_regs(&self, dispstat: DisplayStatus, sysbus: &mut SysBus) { + let mut v = dispstat.raw_value; + v.set_bit(0, dispstat.vblank_flag); + v.set_bit(1, dispstat.hblank_flag); + v.set_bit(2, dispstat.vcount_flag); + sysbus.ioregs.write_reg(REG_DISPSTAT, v); + } - // TODO - (0, None) + pub fn set_hblank(&mut self, sysbus: &mut SysBus) -> Option { + let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT)); + let mut v = dispstat.raw_value; + v.set_bit(1, true); + self.state = HBlank; + sysbus.ioregs.write_reg(REG_DISPSTAT, v); + + if dispstat.hblank_irq_enable { + Some(Interrupt::LCD_HBlank) + } else { + None + } + } + + pub fn set_vblank(&mut self, sysbus: &mut SysBus) -> Option { + let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT)); + let mut v = dispstat.raw_value; + v.set_bit(1, false); + v.set_bit(0, true); + self.state = HBlank; + sysbus.ioregs.write_reg(REG_DISPSTAT, v); + + if dispstat.vblank_irq_enable { + Some(Interrupt::LCD_VBlank) + } else { + None + } + } + + pub fn set_hdraw(&mut self) { + self.state = HDraw; + } + + fn bgcnt(&self, sysbus: &SysBus, bg: u32) -> BgControl { + BgControl::from(sysbus.ioregs.read_reg(REG_BG0CNT + 2 * bg)) + } + + pub fn render(&mut self, sysbus: &SysBus) { + // let dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT)); + // let dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT)); + + // TODO - redner } } + +// *TODO* Running the Lcd step by step causes a massive performance impact, so for now not treat it as an emulated IO device. +// +// impl EmuIoDev for Lcd { +// fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option) { +// self.cycles += cycles; + +// sysbus +// .ioregs +// .write_reg(REG_VCOUNT, self.current_scanline as u16); +// let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT)); + +// dispstat.vcount_flag = dispstat.vcount_setting as usize == self.current_scanline; +// if dispstat.vcount_irq_enable { +// panic!("VCOUNT IRQ NOT IMPL"); +// } + +// match self.state { +// HDraw => { +// if self.cycles > Lcd::CYCLES_HDRAW { +// self.current_scanline += 1; +// self.cycles -= Lcd::CYCLES_HDRAW; + +// let (new_state, irq) = if self.current_scanline < Lcd::DISPLAY_HEIGHT { +// // HBlank +// dispstat.hblank_flag = true; +// let irq = if dispstat.hblank_irq_enable { +// Some(Interrupt::LCD_HBlank) +// } else { +// None +// }; +// (HBlank, irq) +// } else { +// dispstat.vblank_flag = true; +// let irq = if dispstat.vblank_irq_enable { +// Some(Interrupt::LCD_HBlank) +// } else { +// None +// }; +// (VBlank, irq) +// }; +// self.state = new_state; +// self.update_regs(dispstat, sysbus); +// return (0, irq); +// } +// } +// HBlank => { +// if self.cycles > Lcd::CYCLES_HBLANK { +// self.cycles -= Lcd::CYCLES_HBLANK; +// self.state = HDraw; +// dispstat.hblank_flag = false; +// self.update_regs(dispstat, sysbus); +// return (0, None); +// } +// } +// VBlank => { +// if self.cycles > Lcd::CYCLES_VBLANK { +// self.cycles -= Lcd::CYCLES_VBLANK; +// self.state = HDraw; +// dispstat.vblank_flag = false; +// self.current_scanline = 0; +// self.update_regs(dispstat, sysbus); +// return (0, None); +// } +// } +// } + +// // let mut dispcnt = DisplayControl::from(sysbus.ioregs.read_reg(REG_DISPCNT)); +// // let mut dispstat = DisplayStatus::from(sysbus.ioregs.read_reg(REG_DISPSTAT)); + +// // TODO +// (0, None) +// } +// }