Demo egui app, displaying CPU Registers in a window

This commit is contained in:
2022-02-07 15:26:38 -05:00
parent c421f3cf7e
commit 48ebe8536f
12 changed files with 1533 additions and 20 deletions

1398
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,3 +6,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
eframe = "0.16.0"

View File

@@ -1,3 +1,7 @@
fn main() {
use rultra64::gui::EmulatorApp;
fn main() {
let app = EmulatorApp::default();
let native_options = eframe::NativeOptions::default();
eframe::run_native(Box::new(app), native_options);
}

View File

@@ -99,6 +99,10 @@ impl CPU {
}
}
pub fn registers(&self) -> &CPURegisters {
&self.registers
}
pub fn fetch_opcode(address: i64, mmu: &MMU) -> u32 {
let data = mmu.read_virtual(address, 4);
let opcode = ((data[0] as u32) << 24) | ((data[1] as u32) << 16) | ((data[2] as u32) << 8) | ((data[3] as u32) << 8);

View File

@@ -24,4 +24,8 @@ impl Emulator {
pub fn tick(&mut self, _framebuffer: &mut [u8]) {
self.cpu.fetch_and_exec_opcode(&mut self.mmu);
}
pub fn cpu(&self) -> &CPU {
&self.cpu
}
}

93
src/gui.rs Normal file
View File

@@ -0,0 +1,93 @@
use eframe::{egui, epi};
use crate::emulator::Emulator;
pub struct EmulatorApp {
core: Emulator,
}
impl Default for EmulatorApp {
fn default() -> Self {
Self {
core: Emulator::new(),
}
}
}
impl epi::App for EmulatorApp {
fn name(&self) -> &str {
"Rultra64"
}
/// Called once before the first frame.
fn setup(
&mut self,
_ctx: &egui::CtxRef,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
) {
// Load previous app state (if any).
// Note that you must enable the `persistence` feature for this to work.
#[cfg(feature = "persistence")]
if let Some(storage) = _storage {
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default()
}
}
/// Called by the frame work to save state before shutdown.
/// Note that you must enable the `persistence` feature for this to work.
#[cfg(feature = "persistence")]
fn save(&mut self, storage: &mut dyn epi::Storage) {
epi::set_value(storage, epi::APP_KEY, self);
}
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
let Self { core: emulator_core } = self;
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Load ROM").clicked() {
}
if ui.button("Quit").clicked() {
frame.quit();
}
});
});
});
build_cpu_registers_window(ctx, &emulator_core);
}
}
fn build_cpu_registers_window(ctx: &egui::CtxRef, emulator_core: &Emulator) {
egui::Window::new("CPU Registers").vscroll(true).show(ctx, |ui| {
egui::SidePanel::left("left_panel")
// .resizable(true)
.default_width(10.0)
.show_inside(ui, |ui| {
ui.vertical_centered(|ui| {
ui.heading("# | Name");
});
for (index, name) in crate::registers::CPU_REGISTER_NAMES.into_iter().enumerate() {
ui.label(format!("r{} | {}", index, name));
}
});
egui::SidePanel::right("right_panel")
.resizable(true)
// .default_width(50.0)
.show_inside(ui, |ui| {
ui.vertical_centered(|ui| {
ui.heading("Value");
});
for (_, name) in crate::registers::CPU_REGISTER_NAMES.into_iter().enumerate() {
let val = emulator_core.cpu().registers().get_by_name(name);
ui.label(format!("{}", val));
}
});
});
}

View File

@@ -5,3 +5,5 @@ pub mod rom;
pub mod rdram;
pub mod emulator;
pub mod rcp;
pub mod utils;
pub mod gui;

View File

@@ -45,23 +45,10 @@ pub struct MMU {
impl MMU {
pub fn new() -> Self {
let args: Vec<String> = std::env::args().collect();
#[cfg(not(test))]
if args.len() < 2 {
eprintln!("Please, specify a ROM file");
std::process::exit(1);
}
let rom = match ROM::load_file(&args.get(1).unwrap_or(&"".to_string())) {
Ok(rom) => rom,
Err(err) => {
eprintln!("Could not read ROM: {}", err);
std::process::exit(1);
},
};
Self {
rdram: RDRAM::new(),
rcp: RCP::new(),
rom,
rom: ROM::new(),
}
}

View File

@@ -1,12 +1,13 @@
use crate::rdram::RDRAM;
use crate::utils::box_array;
pub struct VideoInterface {
registers: [u8; 0x100000],
registers: Box<[u8; 0x100000]>,
}
impl VideoInterface {
pub fn new() -> Self {
let mut registers = [0; 0x100000];
let mut registers = box_array![0; 0x100000];
// Initialize VI_V_INTR 0x0440 000C: https://n64brew.dev/wiki/Video_Interface#0x0440_000C_-_VI_V_INTR
registers[0x0440000C - 0x04400000] = 0xFF;
registers[0x0440000B - 0x04400000] = 0x03;

View File

@@ -1,3 +1,5 @@
use crate::utils::box_array;
#[derive(Copy, Clone)]
pub struct Byte {
data: u16,
@@ -28,13 +30,13 @@ impl Byte {
}
pub struct RDRAM {
data: [Byte; 0x400000],
data: Box<[Byte; 0x400000]>,
}
impl RDRAM {
pub fn new() -> Self {
Self {
data: [Byte::new(); 0x400000],
data: box_array![Byte::new(); 0x400000],
}
}

View File

@@ -17,7 +17,7 @@ impl ROM {
}
}
pub fn load_file(filename: &str) -> std::io::Result<Self> {
pub fn new_from_filename(filename: &str) -> std::io::Result<Self> {
let mut file = File::open(filename)?;
let mut data = vec![];
file.read_to_end(&mut data)?;

17
src/utils.rs Normal file
View File

@@ -0,0 +1,17 @@
#[macro_export]
macro_rules! box_array {
($val:expr ; $len:expr) => {{
// Use a generic function so that the pointer cast remains type-safe
fn vec_to_boxed_array<T>(vec: Vec<T>) -> Box<[T; $len]> {
let boxed_slice = vec.into_boxed_slice();
let ptr = ::std::boxed::Box::into_raw(boxed_slice) as *mut [T; $len];
unsafe { Box::from_raw(ptr) }
}
vec_to_boxed_array(vec![$val; $len])
}};
}
pub(crate) use box_array;