From 3deb9f4f49302d0665cbf5d72f78371488b97d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pinheiro?= Date: Sat, 23 Mar 2024 14:07:42 +0000 Subject: [PATCH] slow mode --- fpt-egui/src/main.rs | 73 +++++++++++++++++++++++++++++++++++--------- fpt/src/lib.rs | 16 ++++++++-- fpt/src/lr35902.rs | 4 +-- 3 files changed, 75 insertions(+), 18 deletions(-) diff --git a/fpt-egui/src/main.rs b/fpt-egui/src/main.rs index ca6cf0d..d285f5c 100644 --- a/fpt-egui/src/main.rs +++ b/fpt-egui/src/main.rs @@ -12,7 +12,9 @@ use fpt::ppu::tile::Tile; use fpt_cli::debugger::Debugger; use log::info; -const GB_FRAME_IN_SECONDS: f64 = 0.016666666667; +// TODO: the gameboy doesn't run at exactly 60fps +const SIXTY_FPS_FRAMETIME: f64 = 0.016666666667; +const T_CYCLE: f64 = 0.0000002384185791015625; const TEXTURE_SCALE_FACTOR: f32 = 3.0; @@ -83,8 +85,12 @@ pub struct FPT { bg_map_texture: Option, gb: Rc>, - db: Debugger, + dbg: Debugger, paused: bool, + slow_factor_s: String, + slow_factor: f64, + cycles_since_last_frame: u32, + total_cycles: u64, } impl Default for FPT { @@ -101,8 +107,12 @@ impl Default for FPT { bg_map: egui::ColorImage::new([BMV_X_SIZE, BMV_Y_SIZE], Color32::TRANSPARENT), bg_map_texture: None, gb: gameboy.clone(), - db: Debugger::with_gameboy(gameboy), + dbg: Debugger::with_gameboy(gameboy), paused: false, + slow_factor_s: "1".to_string(), + slow_factor: 1.0, + cycles_since_last_frame: 0, + total_cycles: 0, } } } @@ -138,18 +148,50 @@ impl FPT { } fn emulator(&mut self, ui: &mut Ui) { + self.egui_frame_count += 1; + let mut frame: Option = None; let delta_time = ui.input(|i| i.unstable_dt) as f64; self.accum_time += delta_time; - self.egui_frame_count += 1; - if self.accum_time >= GB_FRAME_IN_SECONDS { + + // if self.slow_factor != 1.0 { + if true { + let cycles = self.accum_time.div_euclid(T_CYCLE * self.slow_factor) as u32; + self.accum_time -= cycles as f64 * T_CYCLE * self.slow_factor; + for _ in 0..cycles { + // TODO: care for double speed mode + self.gb.borrow_mut().cpu_mut().t_cycle(); + self.gb.borrow_mut().ppu_mut().step(1); + self.cycles_since_last_frame += 1; + if self.cycles_since_last_frame == self.gb.borrow().cycles_in_one_frame() { + frame = { + let mut ppu_frame_copy: fpt::ppu::Frame = [0; 23040]; // should be optimized away? + ppu_frame_copy.copy_from_slice(self.gb.borrow_mut().get_frame()); + Option::from(ppu_frame_copy) + }; + self.gb_frame_count += 1; + self.cycles_since_last_frame = 0; + } + } + self.total_cycles += cycles as u64; + if let Some(frame) = frame { + for z in 0..(WIDTH * HEIGHT) { + let x = z % WIDTH; + let y = z / WIDTH; + self.image[(x, y)] = PALETTE[frame[z] as usize]; + } + } + } else if self.accum_time >= SIXTY_FPS_FRAMETIME { + self.accum_time -= SIXTY_FPS_FRAMETIME; self.gb_frame_count += 1; - self.accum_time -= GB_FRAME_IN_SECONDS; + self.cycles_since_last_frame = 0; + self.total_cycles += 70224; + self.gb.borrow_mut().frame(); // I didn't manage to work with a reference from self.gb.borrow().frame() // because that borrows self immutably, // and then `self.image[(x, y)] = ... frame[z] ...` borrows self mutably and reads frame let frame = { let mut ppu_frame_copy: fpt::ppu::Frame = [0; 23040]; // should be optimized away? - ppu_frame_copy.copy_from_slice(self.gb.borrow_mut().frame()); + ppu_frame_copy.copy_from_slice(self.gb.borrow_mut().get_frame()); ppu_frame_copy }; for z in 0..(WIDTH * HEIGHT) { @@ -176,7 +218,7 @@ impl FPT { if _ccc { info!("time_taken2 {:.8}", time_taken); } - let sleep_time = GB_FRAME_IN_SECONDS - time_taken; + let sleep_time = SIXTY_FPS_FRAMETIME - time_taken; info!("sleep_time {:.8}", sleep_time); if sleep_time < 0.0 { ctx.request_repaint(); @@ -202,7 +244,7 @@ impl FPT { stat!("time" : "{:.8}", time); stat!("dt" : "{:.8}", delta_time); stat!("accum. time" : "{:.8}", self.accum_time); - stat!("Ideal count" : "{:.3}", time / GB_FRAME_IN_SECONDS); + stat!("Ideal count" : "{:.3}", time / SIXTY_FPS_FRAMETIME); stat!("Frame count" : "{}" , self.gb_frame_count); stat!("UI updates" : "{}" , self.egui_frame_count); }); @@ -225,11 +267,14 @@ impl FPT { egui::ScrollArea::vertical() .id_source("debug_panel") .show(ui, |ui| { + ui.heading("VRAM"); ui.horizontal(|ui| { - ui.heading("VRAM"); - ui.with_layout(Layout::right_to_left(Align::Max), |ui| { + // ui.with_layout(Layout::right_to_left(Align::Max), |ui| { + ui.monospace("Slow factor:"); + ui.add(egui::TextEdit::singleline(&mut self.slow_factor_s).desired_width(16.0)); + self.slow_factor = self.slow_factor_s.parse().unwrap_or(1.0); ui.checkbox(&mut self.paused, "Paused") - }); + // }); }); ui.separator(); ui.horizontal_wrapped(|ui| { @@ -410,12 +455,12 @@ impl FPT { self.paused = false; } ui.with_layout(Layout::right_to_left(Align::Max), |ui| { - ui.monospace(self.db.pc().to_string()); + ui.monospace(self.dbg.pc().to_string()); ui.label("PC: "); }); }); ui.separator(); - let breakpoints_string = self.db.list_breakpoints(); + let breakpoints_string = self.dbg.list_breakpoints(); if breakpoints_string.is_empty() { ui.centered_and_justified(|ui| ui.label("No breakpoints (WIP)")); } else { diff --git a/fpt/src/lib.rs b/fpt/src/lib.rs index 2284033..8d05165 100644 --- a/fpt/src/lib.rs +++ b/fpt/src/lib.rs @@ -45,6 +45,13 @@ impl Gameboy { &mut self.cpu } + pub fn ppu(&self) -> &Ppu { + &self.ppu + } + pub fn ppu_mut(&mut self) -> &mut Ppu { + &mut self.ppu + } + pub fn instruction(&mut self) -> u32 { let cycles = self.cpu.instruction() as u32; // TODO: care for double speed mode (need to run half as much dots) @@ -54,8 +61,8 @@ impl Gameboy { pub fn frame(&mut self) -> &Frame { for _ in 0..DOTS_IN_ONE_FRAME { - // TODO: care for double speed mode (need to run two cpu cycles) - self.cpu.cycle(); + // TODO: care for double speed mode (need to run two cpu t_cycles) + self.cpu.t_cycle(); self.ppu.step(1); } self.ppu.get_frame() @@ -64,4 +71,9 @@ impl Gameboy { pub fn get_frame(&self) -> &Frame { self.ppu.get_frame() } + + pub fn cycles_in_one_frame(&self) -> u32 { + // TODO: care for double speed mode + DOTS_IN_ONE_FRAME + } } diff --git a/fpt/src/lr35902.rs b/fpt/src/lr35902.rs index d6a12de..36e1ae4 100644 --- a/fpt/src/lr35902.rs +++ b/fpt/src/lr35902.rs @@ -555,7 +555,7 @@ impl LR35902 { } /// Run one t-cycle - from actual crystal @ 4 or 8 MHz (double speed mode) - pub fn cycle(&mut self) { + pub fn t_cycle(&mut self) { let instruction = self.decode(); self.set_inst_cycle_count(self.inst_cycle_count() + 1); // Only actually mutate CPU state on the last t-cycle of the instruction @@ -583,7 +583,7 @@ impl LR35902 { pub fn instruction(&mut self) -> u8 { let instruction = self.decode(); for _ in 0..instruction.cycles { - self.cycle(); + self.t_cycle(); } instruction.cycles }