diff --git a/src/bus.rs b/src/bus.rs index c1255e4..11a590f 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -18,12 +18,12 @@ pub struct Bus { pub ppu: Ppu, work_ram: WorkRam, var_ram: VariableWorkRam, - timer: Timer, + pub(crate) timer: Timer, int: Interrupt, snd: Sound, high_ram: HighRam, serial: Serial, - pub joypad: Joypad, + pub(crate) joypad: Joypad, } impl Default for Bus { @@ -79,10 +79,6 @@ impl Bus { self.oam_write_byte(dest_addr, byte); } } - - pub(crate) fn timer(&self) -> Timer { - self.timer - } } impl Bus { diff --git a/src/cpu.rs b/src/cpu.rs index 87f3489..f062ce6 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -1,7 +1,9 @@ use crate::bus::{Bus, BusIo}; use crate::instruction::{Cycle, Instruction}; use crate::interrupt::{InterruptEnable, InterruptFlag}; +use crate::joypad::Joypad; use crate::ppu::Ppu; +use crate::timer::Timer; use bitfield::bitfield; use std::fmt::{Display, Formatter, Result as FmtResult}; @@ -62,12 +64,6 @@ impl Cpu { self.halted } - #[cfg(feature = "debug")] - pub(crate) fn inc_pc(&mut self) { - self.reg.pc += 1; - } - - #[cfg(not(feature = "debug"))] fn inc_pc(&mut self) { self.reg.pc += 1; } @@ -82,22 +78,10 @@ impl Cpu { } impl Cpu { - #[cfg(feature = "debug")] - pub(crate) fn fetch(&self) -> u8 { - self.bus.read_byte(self.reg.pc) - } - - #[cfg(not(feature = "debug"))] fn fetch(&self) -> u8 { self.bus.read_byte(self.reg.pc) } - #[cfg(feature = "debug")] - pub(crate) fn decode(&mut self, opcode: u8) -> Instruction { - Instruction::from_byte(self, opcode) - } - - #[cfg(not(feature = "debug"))] pub(crate) fn decode(&mut self, opcode: u8) -> Instruction { Instruction::from_byte(self, opcode) } @@ -175,8 +159,16 @@ impl Cpu { } impl Cpu { - pub fn get_ppu(&mut self) -> &mut Ppu { - &mut self.bus.ppu + pub fn ppu(&mut self) -> &Ppu { + &self.bus.ppu + } + + pub(crate) fn joypad_mut(&mut self) -> &mut Joypad { + &mut self.bus.joypad + } + + pub(crate) fn timer(&self) -> &Timer { + &self.bus.timer } fn check_ime(&mut self) { @@ -308,21 +300,6 @@ impl Cpu { } } - #[cfg(feature = "debug")] - pub fn register_pair(&self, pair: RegisterPair) -> u16 { - use RegisterPair::*; - - match pair { - AF => (self.reg.a as u16) << 8 | u8::from(self.flags) as u16, - BC => (self.reg.b as u16) << 8 | self.reg.c as u16, - DE => (self.reg.d as u16) << 8 | self.reg.e as u16, - HL => (self.reg.h as u16) << 8 | self.reg.l as u16, - SP => self.reg.sp, - PC => self.reg.pc, - } - } - - #[cfg(not(feature = "debug"))] pub(crate) fn register_pair(&self, pair: RegisterPair) -> u16 { use RegisterPair::*; @@ -407,18 +384,6 @@ pub(crate) enum Register { Flag, } -#[cfg(feature = "debug")] -#[derive(Debug, Copy, Clone)] -pub enum RegisterPair { - AF, - BC, - DE, - HL, - SP, - PC, -} - -#[cfg(not(feature = "debug"))] #[derive(Debug, Copy, Clone)] pub(crate) enum RegisterPair { AF, diff --git a/src/emu.rs b/src/emu.rs new file mode 100644 index 0000000..84d1493 --- /dev/null +++ b/src/emu.rs @@ -0,0 +1,48 @@ +use crate::cpu::Cpu as SM83; +use crate::instruction::Cycle; +use crate::joypad; +use crate::ppu::Ppu; +use anyhow::Result; +use gilrs::Gilrs; +use std::time::Duration; + +pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED); +pub const CYCLES_IN_FRAME: Cycle = Cycle::new(456 * 154); // 456 Cycles times 154 scanlines +const SM83_CLOCK_SPEED: u64 = 0x400_000; // Hz which is 4.194304Mhz +const DEFAULT_TITLE: &'static str = "DMG-01 Emulator"; + +pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result { + let mut cpu = match boot_path { + Some(path) => SM83::boot_new(path)?, + None => SM83::new(), + }; + + cpu.load_cartridge(rom_path)?; + Ok(cpu) +} + +pub fn rom_title(game_boy: &SM83) -> &str { + game_boy.rom_title().unwrap_or(DEFAULT_TITLE) +} + +pub fn run(game_boy: &mut SM83, gamepad: &mut Gilrs, pending: Cycle) -> Cycle { + let mut elapsed = Cycle::new(0); + + while elapsed < pending { + elapsed += run_unsynced(game_boy, gamepad); + } + + elapsed +} + +pub fn run_unsynced(game_boy: &mut SM83, gamepad: &mut Gilrs) -> Cycle { + if let Some(event) = gamepad.next_event() { + joypad::handle_gamepad_input(&mut game_boy.joypad_mut(), event); + } + + game_boy.step() +} + +pub fn draw(ppu: &Ppu, frame: &mut [u8]) { + ppu.copy_to_gui(frame); +} diff --git a/src/gui.rs b/src/gui.rs index 55911bc..dc9babb 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,7 +1,5 @@ use crate::bus::BusIo; -use crate::cpu::Register; -use crate::cpu::RegisterPair; -use crate::LR35902; +use crate::cpu::{Cpu as SM83, Register, RegisterPair}; use egui::{ClippedMesh, FontDefinitions}; use egui_wgpu_backend::{RenderPass, ScreenDescriptor}; use egui_winit_platform::{Platform, PlatformDescriptor}; @@ -27,11 +25,6 @@ pub struct Egui { show_registers: bool, show_int: bool, show_timer: bool, - - #[cfg(feature = "debug")] - show_disasm: bool, - #[cfg(feature = "debug")] - pub break_point: Option, } impl Egui { @@ -65,10 +58,6 @@ impl Egui { show_registers: false, show_int: false, show_timer: false, - #[cfg(feature = "debug")] - show_disasm: false, - #[cfg(feature = "debug")] - break_point: None, } } @@ -89,7 +78,7 @@ impl Egui { } /// Prepare egui. - pub fn prepare(&mut self, game_boy: &LR35902) { + pub fn prepare(&mut self, game_boy: &SM83) { self.platform .update_time(self.start_time.elapsed().as_secs_f64()); @@ -105,7 +94,7 @@ impl Egui { } /// Create the UI using egui. - fn ui(&mut self, ctx: &egui::CtxRef, game_boy: &LR35902) { + fn ui(&mut self, ctx: &egui::CtxRef, game_boy: &SM83) { egui::TopPanel::top("menubar_container").show(ctx, |ui| { egui::menu::bar(ui, |ui| { egui::menu::menu(ui, "File", |ui| { @@ -135,11 +124,6 @@ impl Egui { self.show_timer = true; } }); - - #[cfg(feature = "debug")] - if ui.button("Disassembly").clicked() { - self.show_disasm = true; - } }); }); @@ -159,7 +143,7 @@ impl Egui { egui::Window::new("Timer") .open(&mut self.show_timer) .show(ctx, |ui| { - let timer = game_boy.bus.timer(); + let timer = game_boy.timer(); ui.horizontal(|ui| { ui.label("DIV"); @@ -262,43 +246,6 @@ impl Egui { }); }); - #[cfg(feature = "debug")] - { - let mut show_disasm = self.show_disasm; - egui::Window::new("Disassembler") - .open(&mut show_disasm) - .show(ctx, |ui| { - // FIXME: This can't be efficient at all - // To fix this, maybe we should make Instruction::from_byte not mutate the state of the Cpu (which we SHOULD have done to begin with) - let mut emu_clone = game_boy.clone(); - - for i in 0..10 { - let pc = emu_clone.register_pair(RegisterPair::PC); - let opcode = emu_clone.fetch(); - emu_clone.inc_pc(); - - let instr = emu_clone.decode(opcode); - let res = ui.selectable_label(i == 0, format!("{:#06X}: {:?}", pc, instr)); - - if res.clicked() { - self.break_point = Some(pc); - } - } - - ui.horizontal(|ui| { - if ui.button("Reset Breakpoint").clicked() { - self.break_point = None; - } - - ui.selectable_label( - self.break_point.is_some(), - format!("{:04X?}", self.break_point), - ) - }); - }); - self.show_disasm = show_disasm; - } - egui::Window::new("IRQ Information") .open(&mut self.show_int) .show(ctx, |ui| { @@ -324,22 +271,9 @@ impl Egui { }); }); - #[cfg(feature = "debug")] - let mut spacebar_step = self.config.spacebar_step; egui::Window::new("Configuration") .open(&mut self.config.show) - .show(ctx, |_ui| { - #[cfg(feature = "debug")] - _ui.horizontal(|ui| { - ui.label("Spacebar Steps"); - ui.add(egui::Slider::u16(&mut spacebar_step, 0..=std::u16::MAX)); - }); - }); - - #[cfg(feature = "debug")] - { - self.config.spacebar_step = spacebar_step; - } + .show(ctx, |_ui| {}); } /// Render egui. @@ -378,19 +312,10 @@ impl Egui { pub struct Configuration { /// Show Configuration egui menu show: bool, - - /// How many [`LR35902`] .step() do we want to do at once - /// when pressing the spacebar key? - #[cfg(feature = "debug")] - pub spacebar_step: u16, } impl Default for Configuration { fn default() -> Self { - Self { - show: false, - #[cfg(feature = "debug")] - spacebar_step: 1, - } + Self { show: false } } } diff --git a/src/lib.rs b/src/lib.rs index 4605309..53aecf3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,13 @@ -pub use cpu::Cpu as LR35902; pub use gui::Egui; pub use instruction::Cycle; -pub use joypad::handle_gamepad_input; - -#[cfg(feature = "debug")] -pub use cpu::RegisterPair; pub const GB_WIDTH: usize = 160; pub const GB_HEIGHT: usize = 144; -pub const LR35902_CLOCK_SPEED: u32 = 0x400000; // Hz | 4.194304Mhz mod bus; mod cartridge; mod cpu; +pub mod emu; mod gui; mod high_ram; mod instruction; diff --git a/src/main.rs b/src/main.rs index c489ba9..5969ed2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,17 @@ use anyhow::{anyhow, Result}; use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; -use gb::Egui; -use gb::LR35902_CLOCK_SPEED; -use gb::{handle_gamepad_input, Cycle, LR35902}; +use gb::{Cycle, Egui, GB_HEIGHT, GB_WIDTH}; use gilrs::Gilrs; use pixels::{Pixels, SurfaceTexture}; -use std::time::{Duration, Instant}; +use std::time::Instant; use winit::dpi::LogicalSize; use winit::event::{Event, VirtualKeyCode}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::{Window, WindowBuilder}; use winit_input_helper::WinitInputHelper; -#[cfg(feature = "debug")] -use gb::RegisterPair; - -// 160 x 144 -const GB_WIDTH: u32 = 160; -const GB_HEIGHT: u32 = 144; const SCALE: f64 = 5.0; -const LR35902_CYCLE_TIME: f64 = 1.0f64 / LR35902_CLOCK_SPEED as f64; -const CYCLES_IN_FRAME: Cycle = Cycle::new(70224); - -#[cfg(feature = "debug")] -const STEP_MODE_BY_DEFAULT: bool = true; - fn main() -> Result<()> { let app = App::new(crate_name!()) .version(crate_version!()) @@ -51,26 +37,16 @@ fn main() -> Result<()> { ) .get_matches(); - let mut game_boy = match m.value_of("boot") { - Some(path) => LR35902::boot_new(path).expect("Failed to load boot ROM"), - None => LR35902::new(), - }; + // `rom` is a required value in every situation so this will + // always exist. + let rom_path = m.value_of("rom").unwrap(); - // This is a required value so if we program gets here, - // a string **will** have been provided to the rom argument - let rom_path = m - .value_of("rom") - .expect("ROM Path not provided despite it being a required argument"); - - game_boy - .load_cartridge(rom_path) - .expect("Failed to load ROM"); - - let default_title = "DMG-01 Emulator"; - let cartridge_title = game_boy.rom_title().unwrap_or(default_title); + let mut game_boy = + gb::emu::init(m.value_of("boot"), rom_path).expect("Failed to initialize DMG-01 Emulator"); + let cartridge_title = gb::emu::rom_title(&game_boy); // Initialize Gamepad Support - let mut gilrs = Gilrs::new().expect("Failed to initialize Gilrs"); + let mut gamepad = Gilrs::new().expect("Failed to initialize Gilrs"); // Initialize GUI let event_loop = EventLoop::new(); @@ -81,17 +57,14 @@ fn main() -> Result<()> { let size = window.inner_size(); let scale_factor = window.scale_factor(); let surface_texture = SurfaceTexture::new(size.width, size.height, &window); - let pixels = Pixels::new(GB_WIDTH, GB_HEIGHT, surface_texture)?; + let pixels = Pixels::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)?; let egui = Egui::new(size.width, size.height, scale_factor, pixels.context()); (pixels, egui) }; let mut now = Instant::now(); - let mut cycles_in_frame: Cycle = Default::default(); - - #[cfg(feature = "debug")] - let mut step_mode = STEP_MODE_BY_DEFAULT; + let mut cycle_count: Cycle = Default::default(); event_loop.run(move |event, _, control_flow| { // Update egui @@ -125,11 +98,6 @@ fn main() -> Result<()> { return; } - #[cfg(feature = "debug")] - if input.key_pressed(VirtualKeyCode::S) { - step_mode = !step_mode; - } - if let Some(scale_factor) = input.scale_factor() { egui.scale_factor(scale_factor); } @@ -139,60 +107,17 @@ fn main() -> Result<()> { egui.resize(size.width, size.height); } - // Emulate Game Boy - let mut elapsed_cycles: Cycle = Default::default(); let delta = now.elapsed().subsec_nanos(); now = Instant::now(); - #[cfg(feature = "debug")] - if step_mode { - if input.key_pressed(VirtualKeyCode::Space) { - if let Some(event) = gilrs.next_event() { - handle_gamepad_input(&mut game_boy.bus.joypad, event); - } + let pending = Cycle::new(delta / gb::emu::SM83_CYCLE_TIME.subsec_nanos()); + cycle_count += gb::emu::run(&mut game_boy, &mut gamepad, pending); - for _ in 0..egui.config.spacebar_step { - elapsed_cycles += game_boy.step(); - } + if cycle_count >= gb::emu::CYCLES_IN_FRAME { + // Draw Frame + cycle_count = Cycle::new(0); - cycles_in_frame %= CYCLES_IN_FRAME; - } - - game_boy.get_ppu().copy_to_gui(pixels.get_frame()); - window.request_redraw(); - return; - } - - let cycle_time = Duration::from_secs_f64(LR35902_CYCLE_TIME).subsec_nanos(); - let pending_cycles = Cycle::new(delta / cycle_time); - - while elapsed_cycles <= pending_cycles { - if let Some(event) = gilrs.next_event() { - handle_gamepad_input(&mut game_boy.bus.joypad, event); - } - - elapsed_cycles += game_boy.step(); - - #[cfg(feature = "debug")] - { - let pc = game_boy.register_pair(RegisterPair::PC); - - if let Some(break_point) = egui.break_point { - if pc == break_point { - step_mode = true; - break; - } - } - } - } - - cycles_in_frame += elapsed_cycles; - - if cycles_in_frame >= CYCLES_IN_FRAME { - // Redraw - cycles_in_frame = Default::default(); - - game_boy.get_ppu().copy_to_gui(pixels.get_frame()); + gb::emu::draw(game_boy.ppu(), pixels.get_frame()); window.request_redraw(); } } @@ -205,5 +130,8 @@ fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result { .with_title(title) .with_inner_size(size) .with_min_inner_size(size) + .with_resizable(true) + .with_decorations(true) + .with_transparent(false) .build(event_loop)?) } diff --git a/src/ppu.rs b/src/ppu.rs index d3f6e23..c34f87a 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -1,5 +1,5 @@ use crate::bus::BusIo; -use crate::Cycle; +use crate::instruction::Cycle; use crate::GB_HEIGHT; use crate::GB_WIDTH; use dma::DirectMemoryAccess; diff --git a/src/timer.rs b/src/timer.rs index 433cb0f..3c7cfa4 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,4 +1,3 @@ -use crate::Cycle; use bitfield::bitfield; #[derive(Debug, Clone, Copy)]