diff --git a/snes-frontend/src/emu_state/debug_options.rs b/snes-frontend/src/emu_state/debug_options.rs index c017612..e549d1d 100644 --- a/snes-frontend/src/emu_state/debug_options.rs +++ b/snes-frontend/src/emu_state/debug_options.rs @@ -1,3 +1,23 @@ +pub struct DebugOptions { + pub enable_debugging: bool, + pub show_debug_options_window: bool, + pub memory_map_conrtrol_options: MemoryMapControlOptions, + pub cpu_debug_control_options: CPUDebugControlOptions, + pub ppu_debug_control_options: PPUDebugControlOptions, +} + +impl DebugOptions { + pub fn new() -> Self { + Self { + enable_debugging: true, + show_debug_options_window: true, + memory_map_conrtrol_options: MemoryMapControlOptions::new(), + cpu_debug_control_options: CPUDebugControlOptions::new(), + ppu_debug_control_options: PPUDebugControlOptions::new(), + } + } +} + pub struct MemoryMapControlOptions { pub is_enabled: bool, pub inputs: MemoryMapInputs, @@ -44,20 +64,34 @@ impl CPUDebugControlOptions { } } -pub struct DebugOptions { - pub enable_debugging: bool, - pub show_debug_options_window: bool, - pub memory_map_conrtrol_options: MemoryMapControlOptions, - pub cpu_debug_control_options: CPUDebugControlOptions, +pub struct PPUDebugControlOptions { + pub is_enabled: bool, + pub show_registers: bool, + pub show_vram: bool, + pub vram_inputs: VramInputs, } -impl DebugOptions { +impl PPUDebugControlOptions { pub fn new() -> Self { Self { - enable_debugging: true, - show_debug_options_window: true, - memory_map_conrtrol_options: MemoryMapControlOptions::new(), - cpu_debug_control_options: CPUDebugControlOptions::new(), + is_enabled: true, + show_registers: true, + show_vram: true, + vram_inputs: VramInputs::new(), + } + } +} + +pub struct VramInputs { + pub address_start: String, + pub address_end: String, +} + +impl VramInputs { + pub fn new() -> Self { + Self { + address_start: String::from("0000"), + address_end: String::from("01FF"), } } } diff --git a/snes-frontend/src/emu_ui/debug/common.rs b/snes-frontend/src/emu_ui/debug/common.rs new file mode 100644 index 0000000..90ddd9e --- /dev/null +++ b/snes-frontend/src/emu_ui/debug/common.rs @@ -0,0 +1,13 @@ +use regex::Regex; + +pub fn sanitize_input(input: &mut String, is_page: bool) { + let re = Regex::new(r"^(0x)?[0-9a-fA-F]{2,4}$").unwrap(); + if !re.is_match(input) { + *input = String::from("0x00"); + } + let trimmed_input = input.trim_start_matches("0x"); + *input = match is_page { + true => format!("{:02X}", u8::from_str_radix(&trimmed_input, 16).unwrap()), + false => format!("{:04X}", u16::from_str_radix(&trimmed_input, 16).unwrap()), + } +} diff --git a/snes-frontend/src/emu_ui/debug/debug.rs b/snes-frontend/src/emu_ui/debug/debug.rs index 82a3042..25f1d9f 100644 --- a/snes-frontend/src/emu_ui/debug/debug.rs +++ b/snes-frontend/src/emu_ui/debug/debug.rs @@ -6,6 +6,7 @@ use crate::emu_state::emulation::EmulationState; use super::memory_map::build_memory_map_window; use super::cpu::build_cpu_debug_controls; +use super::ppu::build_ppu_debug_controls; pub fn build_all_debug_options(ctx: &egui::Context, debug_options: &mut DebugOptions, emulation_state: &mut EmulationState, emulator: &mut Emulator) { @@ -17,6 +18,7 @@ pub fn build_all_debug_options(ctx: &egui::Context, debug_options: &mut DebugOpt build_memory_map_window(ctx, &mut debug_options.memory_map_conrtrol_options, emulator); build_cpu_debug_controls(ctx, &mut debug_options.cpu_debug_control_options, emulation_state, emulator); + build_ppu_debug_controls(ctx, &mut debug_options.ppu_debug_control_options, &emulator.bus.ppu.registers); } @@ -41,5 +43,11 @@ fn build_debug_options_window(ctx: &egui::Context, debug_options: &mut DebugOpti ).clicked() { debug_options.cpu_debug_control_options.is_enabled = !debug_options.cpu_debug_control_options.is_enabled; } + if ui.selectable_label( + debug_options.ppu_debug_control_options.is_enabled, + "Show PPU Debug Controls" + ).clicked() { + debug_options.ppu_debug_control_options.is_enabled = !debug_options.ppu_debug_control_options.is_enabled; + } }); } diff --git a/snes-frontend/src/emu_ui/debug/memory_map.rs b/snes-frontend/src/emu_ui/debug/memory_map.rs index 724e3c3..295d780 100644 --- a/snes-frontend/src/emu_ui/debug/memory_map.rs +++ b/snes-frontend/src/emu_ui/debug/memory_map.rs @@ -1,6 +1,6 @@ use eframe::egui; -use regex::Regex; use snes_core::emulator::Emulator; +use crate::emu_ui::debug::common::sanitize_input; use crate::emu_state::debug_options::{MemoryMapInputs, MemoryMapControlOptions}; @@ -51,18 +51,6 @@ fn build_inputs(ui: &mut egui::Ui, input_values: &mut MemoryMapInputs) { } } -fn sanitize_input(input: &mut String, is_page: bool) { - let re = Regex::new(r"^(0x)?[0-9a-fA-F]{2,4}$").unwrap(); - if !re.is_match(input) { - *input = String::from("0x00"); - } - let trimmed_input = input.trim_start_matches("0x"); - *input = match is_page { - true => format!("{:02X}", u8::from_str_radix(&trimmed_input, 16).unwrap()), - false => format!("{:04X}", u16::from_str_radix(&trimmed_input, 16).unwrap()), - } -} - fn build_memory_map_text(ui: &mut egui::Ui, input_values: &MemoryMapInputs, emulator: &Emulator) { let page_start = u8::from_str_radix(&input_values.page_start, 16).unwrap(); let page_end = u8::from_str_radix(&input_values.page_end, 16).unwrap(); diff --git a/snes-frontend/src/emu_ui/debug/mod.rs b/snes-frontend/src/emu_ui/debug/mod.rs index f734507..d967947 100644 --- a/snes-frontend/src/emu_ui/debug/mod.rs +++ b/snes-frontend/src/emu_ui/debug/mod.rs @@ -1,5 +1,7 @@ pub mod debug; pub mod memory_map; pub mod cpu; +pub mod ppu; +pub mod common; pub use debug::build_all_debug_options; \ No newline at end of file diff --git a/snes-frontend/src/emu_ui/debug/ppu.rs b/snes-frontend/src/emu_ui/debug/ppu.rs new file mode 100644 index 0000000..d0ed3b3 --- /dev/null +++ b/snes-frontend/src/emu_ui/debug/ppu.rs @@ -0,0 +1,187 @@ +use eframe::egui; +use snes_core::ppu::registers::PPURegisters; + +use crate::emu_state::debug_options::PPUDebugControlOptions; +use crate::emu_ui::debug::common::sanitize_input; + + +pub fn build_ppu_debug_controls(ctx: &egui::Context, ppu_debug_options: &mut PPUDebugControlOptions, ppu_registers: &PPURegisters) { + if !ppu_debug_options.is_enabled { + return + } + + egui::Window::new("PPU Debug Controls") + .auto_sized() + .max_width(125.0) + .open(&mut ppu_debug_options.is_enabled) + .show(ctx, |ui| { + ui.monospace("Controls:"); + ui.horizontal(|ui| { + if ui.selectable_label( + ppu_debug_options.show_registers, + "Show PPU Registers" + ).clicked() { + ppu_debug_options.show_registers = !ppu_debug_options.show_registers; + } + if ui.selectable_label( + ppu_debug_options.show_vram, + "Show VRAM" + ).clicked() { + ppu_debug_options.show_vram = !ppu_debug_options.show_vram; + } + }); + ui.monospace("Backgrounds:"); + ui.horizontal(|ui| { + }); + }); + + build_ppu_registers_window(ctx, ppu_debug_options, ppu_registers); + build_vram_window(ctx, ppu_debug_options, ppu_registers); +} + +fn build_ppu_registers_window(ctx: &egui::Context, ppu_debug_options: &mut PPUDebugControlOptions, ppu_registers: &PPURegisters) { + if !ppu_debug_options.show_registers { + return; + } + egui::Window::new("PPU Registers") + .max_width(180.0) + .open(&mut ppu_debug_options.show_registers) + .show(ctx, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + ui.monospace(format!( + "H count: {} V count: {}", + ppu_registers.h_count, + ppu_registers.v_count, + )); + ui.monospace(format!("VBlanking: {}", ppu_registers.is_vblanking())); + ui.monospace(format!("NMI Flag: {}", ppu_registers.vblank_nmi)); + ui.separator(); + ui.monospace("Registers:"); + for (index, register_value) in ppu_registers.registers().iter().enumerate() { + let register = (index as u16) + 0x2100; + let register_name = get_register_name(register); + ui.monospace(format!("{:04X} {}: {:02X} {:08b}", register, register_name, register_value, register_value)); + } + }); + }); +} + +fn get_register_name(register: u16) -> &'static str { + match register { + snes_core::ppu::registers::INIDISP => "INIDISP", + snes_core::ppu::registers::OBSEL => "OBSEL ", + snes_core::ppu::registers::OAMADDL => "OAMADDL", + snes_core::ppu::registers::OAMADDH => "OAMADDH", + snes_core::ppu::registers::OAMDATA => "OAMDATA", + snes_core::ppu::registers::BGMODE => "BGMODE ", + snes_core::ppu::registers::MOSAIC => "MOSAIC ", + snes_core::ppu::registers::BG1SC => "BG1SC ", + snes_core::ppu::registers::BG2SC => "BG2SC ", + snes_core::ppu::registers::BG3SC => "BG3SC ", + snes_core::ppu::registers::BG4SC => "BG4SC ", + snes_core::ppu::registers::BG12NBA => "BG12NBA", + snes_core::ppu::registers::BG34NBA => "BG34NBA", + snes_core::ppu::registers::BG1HOFS => "BG1HOFS", + snes_core::ppu::registers::BG1VOFS => "BG1VOFS", + snes_core::ppu::registers::BG2HOFS => "BG2HOFS", + snes_core::ppu::registers::BG2VOFS => "BG2VOFS", + snes_core::ppu::registers::BG3HOFS => "BG3HOFS", + snes_core::ppu::registers::BG3VOFS => "BG3VOFS", + snes_core::ppu::registers::BG4HOFS => "BG4HOFS", + snes_core::ppu::registers::BG4VOFS => "BG4VOFS", + snes_core::ppu::registers::VMAIN => "VMAIN ", + snes_core::ppu::registers::VMADDL => "VMADDL ", + snes_core::ppu::registers::VMADDH => "VMADDH ", + snes_core::ppu::registers::VMDATAL => "VMDATAL", + snes_core::ppu::registers::VMDATAH => "VMDATAH", + snes_core::ppu::registers::M7SEL => "M7SEL ", + snes_core::ppu::registers::M7A => "M7A ", + snes_core::ppu::registers::M7B => "M7B ", + snes_core::ppu::registers::M7C => "M7C ", + snes_core::ppu::registers::M7D => "M7D ", + snes_core::ppu::registers::M7X => "M7X ", + snes_core::ppu::registers::M7Y => "M7Y ", + snes_core::ppu::registers::CGADD => "CGADD ", + snes_core::ppu::registers::CGDATA => "CGDATA ", + snes_core::ppu::registers::W12SEL => "W12SEL ", + snes_core::ppu::registers::W34SEL => "W34SEL ", + snes_core::ppu::registers::WOBJSEL => "WOBJSEL", + snes_core::ppu::registers::WH0 => "WH0 ", + snes_core::ppu::registers::WH1 => "WH1 ", + snes_core::ppu::registers::WH2 => "WH2 ", + snes_core::ppu::registers::WH3 => "WH3 ", + snes_core::ppu::registers::WBGLOG => "WBGLOG ", + snes_core::ppu::registers::WOBJLOG => "WOBJLOG", + snes_core::ppu::registers::TM => "TM ", + snes_core::ppu::registers::TS => "TS ", + snes_core::ppu::registers::TMW => "TMW ", + snes_core::ppu::registers::TSW => "TSW ", + snes_core::ppu::registers::CGWSEL => "CGWSEL ", + snes_core::ppu::registers::CGADSUB => "CGADSUB", + snes_core::ppu::registers::COLDATA => "COLDATA", + snes_core::ppu::registers::SETINI => "SETINI ", + snes_core::ppu::registers::MPYL => "MPYL ", + snes_core::ppu::registers::MPYM => "MPYM ", + snes_core::ppu::registers::MPYH => "MPYH ", + snes_core::ppu::registers::SLHV => "SLHV ", + snes_core::ppu::registers::RDOAM => "RDOAM ", + snes_core::ppu::registers::RDVRAML => "RDVRAML", + snes_core::ppu::registers::RDVRAMH => "RDVRAMH", + snes_core::ppu::registers::RDCGRAM => "RDCGRAM", + snes_core::ppu::registers::OPHCT => "OPHCT ", + snes_core::ppu::registers::OPVCT => "OPVCT ", + snes_core::ppu::registers::STAT77 => "STAT77 ", + snes_core::ppu::registers::STAT78 => "STAT78 ", + _ => "N/A ", + } +} + +fn build_vram_window(ctx: &egui::Context, ppu_debug_options: &mut PPUDebugControlOptions, ppu_registers: &PPURegisters) { + if !ppu_debug_options.show_vram { + return; + } + egui::Window::new("VRAM Viewer") + .open(&mut ppu_debug_options.show_vram) + .default_width(610.0) + .show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label("Address Start: "); + ui.text_edit_singleline(&mut ppu_debug_options.vram_inputs.address_start); + }); + ui.horizontal(|ui| { + ui.label("Address End: "); + ui.text_edit_singleline(&mut ppu_debug_options.vram_inputs.address_end); + }); + + if ui.button("Search").clicked() { + sanitize_input(&mut ppu_debug_options.vram_inputs.address_start, false); + sanitize_input(&mut ppu_debug_options.vram_inputs.address_end, false); + } + + ui.separator(); + + egui::ScrollArea::both().show(ui, |ui| { + let address_start = u16::from_str_radix(&ppu_debug_options.vram_inputs.address_start, 16).unwrap(); + let address_end = u16::from_str_radix(&ppu_debug_options.vram_inputs.address_end, 16).unwrap(); + let mut header = String::from(" | "); + for page in 0x00..=0x0F { + header = format!("{} {:02X} ", header, page); + } + ui.monospace(header); + let mut divider = String::from("-----|-"); + for _ in 0x00..=0x0F { + divider = format!("{}-----", divider); + } + ui.monospace(divider); + let vector = (address_start..=address_end).collect::>(); + let chunks = vector.chunks(0x10); + for row in chunks { + let mut address_row = format!("{:04X} | ", row[0]); + for address in row { + address_row = format!("{}{:04X} ", address_row, ppu_registers.vram()[((*address) & 0x7FFF) as usize]); + } + ui.monospace(address_row); + } + }); + }); +}