chirp-imgui: Refactor menu definitions
This commit is contained in:
parent
7d5718f384
commit
a16d7fe732
192
src/bin/chirp-imgui/emu.rs
Normal file
192
src/bin/chirp-imgui/emu.rs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
//! The emulator's state, including the screen data
|
||||||
|
|
||||||
|
use super::{error, BACKGROUND, FOREGROUND};
|
||||||
|
use chirp::{bus, Bus, Screen, CPU};
|
||||||
|
use pixels::Pixels;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use winit::event::VirtualKeyCode;
|
||||||
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
|
/// The state of the application
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Emulator {
|
||||||
|
screen: Bus,
|
||||||
|
cpu: CPU,
|
||||||
|
pub ipf: usize,
|
||||||
|
pub rom: PathBuf,
|
||||||
|
pub colors: [[u8; 4]; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Emulator {
|
||||||
|
/// Constructs a new CPU, with the provided ROM loaded at 0x0200
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if the provided ROM does not exist
|
||||||
|
pub fn new(ipf: usize, rom: impl AsRef<Path>) -> Self {
|
||||||
|
let screen = bus! {
|
||||||
|
Screen [0x000..0x100],
|
||||||
|
};
|
||||||
|
let mut cpu = CPU::default();
|
||||||
|
cpu.load_program(&rom).expect("Loaded file MUST exist.");
|
||||||
|
Self {
|
||||||
|
cpu,
|
||||||
|
ipf,
|
||||||
|
rom: rom.as_ref().into(),
|
||||||
|
screen,
|
||||||
|
colors: [*FOREGROUND, *BACKGROUND],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Runs a single epoch
|
||||||
|
pub fn update(&mut self) -> Result<(), error::Error> {
|
||||||
|
self.cpu.multistep(&mut self.screen, self.ipf)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Rasterizes the screen into a [Pixels] buffer
|
||||||
|
pub fn draw(&mut self, pixels: &mut Pixels) -> Result<(), error::Error> {
|
||||||
|
if let Some(screen) = self.screen.get_region(Screen) {
|
||||||
|
let len_log2 = screen.len().ilog2() / 2;
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
let (width, height) = (2u32.pow(len_log2 + 2), 2u32.pow(len_log2 + 1));
|
||||||
|
pixels.resize_buffer(width, height)?;
|
||||||
|
for (idx, pixel) in pixels.frame_mut().iter_mut().enumerate() {
|
||||||
|
let (byte, bit, component) = (idx >> 5, (idx >> 2) % 8, idx & 0b11);
|
||||||
|
*pixel = if screen[byte] & (0x80 >> bit) > 0 {
|
||||||
|
self.colors[0][component]
|
||||||
|
} else {
|
||||||
|
self.colors[1][component]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Processes keyboard input for the Emulator
|
||||||
|
pub fn input(&mut self, input: &WinitInputHelper) -> Result<(), error::Error> {
|
||||||
|
const KEYMAP: [VirtualKeyCode; 16] = [
|
||||||
|
VirtualKeyCode::X,
|
||||||
|
VirtualKeyCode::Key1,
|
||||||
|
VirtualKeyCode::Key2,
|
||||||
|
VirtualKeyCode::Key3,
|
||||||
|
VirtualKeyCode::Q,
|
||||||
|
VirtualKeyCode::W,
|
||||||
|
VirtualKeyCode::E,
|
||||||
|
VirtualKeyCode::A,
|
||||||
|
VirtualKeyCode::S,
|
||||||
|
VirtualKeyCode::D,
|
||||||
|
VirtualKeyCode::Z,
|
||||||
|
VirtualKeyCode::C,
|
||||||
|
VirtualKeyCode::Key4,
|
||||||
|
VirtualKeyCode::R,
|
||||||
|
VirtualKeyCode::F,
|
||||||
|
VirtualKeyCode::V,
|
||||||
|
];
|
||||||
|
for (id, &key) in KEYMAP.iter().enumerate() {
|
||||||
|
if input.key_released(key) {
|
||||||
|
self.cpu.release(id)?;
|
||||||
|
}
|
||||||
|
if input.key_pressed(key) {
|
||||||
|
self.cpu.press(id)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quirks(&mut self) -> chirp::Quirks {
|
||||||
|
self.cpu.flags.quirks
|
||||||
|
}
|
||||||
|
pub fn set_quirks(&mut self, quirks: chirp::Quirks) {
|
||||||
|
self.cpu.flags.quirks = quirks
|
||||||
|
}
|
||||||
|
/// Prints the CPU registers and cycle count to stderr
|
||||||
|
pub fn print_registers(&self) {
|
||||||
|
self.cpu.dump()
|
||||||
|
}
|
||||||
|
/// Prints the screen (using the highest resolution available printer) to stdout
|
||||||
|
pub fn print_screen(&self) -> Result<(), error::Error> {
|
||||||
|
self.screen.print_screen()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Dumps the raw screen bytes to a file named `{rom}_{cycle}.bin`,
|
||||||
|
/// or, failing that, `screen_dump.bin`
|
||||||
|
pub fn dump_screen(&self) -> Result<(), error::Error> {
|
||||||
|
let mut path = PathBuf::new().join(format!(
|
||||||
|
"{}_{}.bin",
|
||||||
|
self.rom.file_stem().unwrap_or_default().to_string_lossy(),
|
||||||
|
self.cpu.cycle()
|
||||||
|
));
|
||||||
|
path.set_extension("bin");
|
||||||
|
if std::fs::write(
|
||||||
|
&path,
|
||||||
|
self.screen
|
||||||
|
.get_region(Screen)
|
||||||
|
.expect("Region::Screen should exist"),
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
eprintln!("Saved to {}", &path.display());
|
||||||
|
} else if std::fs::write(
|
||||||
|
"screen_dump.bin",
|
||||||
|
self.screen
|
||||||
|
.get_region(Screen)
|
||||||
|
.expect("Region::Screen should exist"),
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
eprintln!("Saved to screen_dump.bin");
|
||||||
|
} else {
|
||||||
|
eprintln!("Failed to dump screen to file.")
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Sets live disassembly
|
||||||
|
pub fn set_disasm(&mut self, enabled: bool) {
|
||||||
|
self.cpu.flags.debug = enabled;
|
||||||
|
}
|
||||||
|
/// Checks live disassembly
|
||||||
|
pub fn is_disasm(&mut self) {
|
||||||
|
eprintln!(
|
||||||
|
"Live Disassembly {}abled",
|
||||||
|
if self.cpu.flags.debug { "En" } else { "Dis" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// Toggles emulator pause
|
||||||
|
pub fn pause(&mut self) {
|
||||||
|
self.cpu.flags.pause();
|
||||||
|
eprintln!("{}aused", if self.cpu.flags.pause { "P" } else { "Unp" });
|
||||||
|
}
|
||||||
|
/// Single-steps the emulator, pausing afterward
|
||||||
|
pub fn singlestep(&mut self) -> Result<(), error::Error> {
|
||||||
|
self.cpu.singlestep(&mut self.screen)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Sets a breakpoint at the current address
|
||||||
|
pub fn set_break(&mut self) {
|
||||||
|
self.cpu.set_break(self.cpu.pc());
|
||||||
|
eprintln!("Set breakpoint at {}", self.cpu.pc());
|
||||||
|
}
|
||||||
|
/// Unsets a breakpoint at the current address
|
||||||
|
pub fn unset_break(&mut self) {
|
||||||
|
self.cpu.unset_break(self.cpu.pc());
|
||||||
|
eprintln!("Unset breakpoint at {}", self.cpu.pc());
|
||||||
|
}
|
||||||
|
/// Soft-resets the CPU, keeping the program in memory
|
||||||
|
pub fn soft_reset(&mut self) {
|
||||||
|
self.cpu.reset();
|
||||||
|
self.screen.clear_region(Screen);
|
||||||
|
eprintln!("Soft Reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new CPU with the current CPU's flags
|
||||||
|
pub fn hard_reset(&mut self) {
|
||||||
|
self.cpu.reset();
|
||||||
|
self.screen.clear_region(Screen);
|
||||||
|
// keep the flags
|
||||||
|
let flags = self.cpu.flags.clone();
|
||||||
|
// instantiate a completely new CPU, and reload the ROM from disk
|
||||||
|
self.cpu = CPU::default();
|
||||||
|
self.cpu.flags = flags;
|
||||||
|
self.cpu
|
||||||
|
.load_program(&self.rom)
|
||||||
|
.expect("Previously loaded ROM no longer exists (was it moved?)");
|
||||||
|
eprintln!("Hard Reset");
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub(crate) enum Error {
|
pub enum Error {
|
||||||
/// Error originated in [`chirp`]
|
/// Error originated in [`chirp`]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Chirp(#[from] chirp::error::Error),
|
Chirp(#[from] chirp::error::Error),
|
||||||
|
@ -7,9 +7,16 @@
|
|||||||
use pixels::{wgpu, PixelsContext};
|
use pixels::{wgpu, PixelsContext};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
mod about;
|
||||||
mod menubar;
|
mod menubar;
|
||||||
use menubar::Menubar;
|
use menubar::Menubar;
|
||||||
|
|
||||||
|
/// Lays out the imgui widgets for a thing
|
||||||
|
pub trait Drawable {
|
||||||
|
// Lay out the ImGui widgets for this thing
|
||||||
|
fn draw(&mut self, ui: &imgui::Ui);
|
||||||
|
}
|
||||||
|
|
||||||
/// Holds state of GUI
|
/// Holds state of GUI
|
||||||
pub(crate) struct Gui {
|
pub(crate) struct Gui {
|
||||||
imgui: imgui::Context,
|
imgui: imgui::Context,
|
||||||
@ -20,6 +27,17 @@ pub(crate) struct Gui {
|
|||||||
pub menubar: Menubar,
|
pub menubar: Menubar,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Queries the state of the [Gui]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Wants {
|
||||||
|
Quit,
|
||||||
|
Disasm,
|
||||||
|
SoftReset,
|
||||||
|
HardReset,
|
||||||
|
Reset,
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Gui {
|
impl std::fmt::Debug for Gui {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Gui")
|
f.debug_struct("Gui")
|
||||||
@ -43,18 +61,18 @@ impl Gui {
|
|||||||
platform.attach_window(
|
platform.attach_window(
|
||||||
imgui.io_mut(),
|
imgui.io_mut(),
|
||||||
window,
|
window,
|
||||||
imgui_winit_support::HiDpiMode::Default,
|
imgui_winit_support::HiDpiMode::Locked(2.2),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Configure fonts
|
// Configure fonts
|
||||||
// let dpi_scale = window.scale_factor();
|
let dpi_scale = window.scale_factor();
|
||||||
// let font_size = (13.0 * dpi_scale) as f32;
|
let font_size = (13.0 * dpi_scale) as f32;
|
||||||
// imgui.io_mut().font_global_scale = (1.0 / dpi_scale) as f32;
|
imgui.io_mut().font_global_scale = (1.0 / dpi_scale) as f32;
|
||||||
imgui
|
imgui
|
||||||
.fonts()
|
.fonts()
|
||||||
.add_font(&[imgui::FontSource::DefaultFontData {
|
.add_font(&[imgui::FontSource::DefaultFontData {
|
||||||
config: Some(imgui::FontConfig {
|
config: Some(imgui::FontConfig {
|
||||||
size_pixels: 13.0,
|
size_pixels: font_size,
|
||||||
oversample_h: 2,
|
oversample_h: 2,
|
||||||
oversample_v: 2,
|
oversample_v: 2,
|
||||||
pixel_snap_h: true,
|
pixel_snap_h: true,
|
||||||
@ -112,53 +130,12 @@ impl Gui {
|
|||||||
self.platform.prepare_render(ui, window);
|
self.platform.prepare_render(ui, window);
|
||||||
}
|
}
|
||||||
|
|
||||||
let menu = &mut self.menubar;
|
self.menubar.draw(ui);
|
||||||
let settings_menu = || {
|
|
||||||
use chirp::Mode::*;
|
|
||||||
let settings = &mut menu.settings;
|
|
||||||
const MODES: [chirp::Mode; 3] = [Chip8, SChip, XOChip];
|
|
||||||
if ui.combo_simple_string("Mode", &mut settings.mode_index, &MODES) {
|
|
||||||
settings.quirks = MODES[settings.mode_index].into();
|
|
||||||
settings.applied = true;
|
|
||||||
}
|
|
||||||
settings.applied = {
|
|
||||||
ui.input_scalar("IPF", &mut settings.target_ipf)
|
|
||||||
.chars_decimal(true)
|
|
||||||
.build()
|
|
||||||
| ui.checkbox("Bin-ops don't clear vF", &mut settings.quirks.bin_ops)
|
|
||||||
| ui.checkbox("DMA doesn't modify I", &mut settings.quirks.dma_inc)
|
|
||||||
| ui.checkbox("Draw calls are instant", &mut settings.quirks.draw_wait)
|
|
||||||
| ui.checkbox("Screen wraps at edge", &mut settings.quirks.screen_wrap)
|
|
||||||
| ui.checkbox("Shift ops ignore vY", &mut settings.quirks.shift)
|
|
||||||
| ui.checkbox("Jumps behave eratically", &mut settings.quirks.stupid_jumps)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// let file_menu = || {
|
|
||||||
// let file = &mut menu.file;
|
|
||||||
// file.quit = ui.menu_item("Quit");
|
|
||||||
// };
|
|
||||||
let debug_menu = || {
|
|
||||||
menu.debug.reset = ui.menu_item("Reset");
|
|
||||||
ui.checkbox("Live Disassembly", &mut menu.debug.dis);
|
|
||||||
};
|
|
||||||
let help_menu = || {
|
|
||||||
let about = &mut menu.about;
|
|
||||||
about.open = ui.menu_item("About...");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draw windows and GUI elements here
|
// Draw windows and GUI elements here
|
||||||
if menu.active {
|
|
||||||
ui.main_menu_bar(|| {
|
|
||||||
//ui.menu("File", file_menu);
|
|
||||||
ui.menu("Settings", settings_menu);
|
|
||||||
ui.menu("Debug", debug_menu);
|
|
||||||
ui.menu("Help", help_menu);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if menu.file.settings {}
|
if self.menubar.about.about_open {
|
||||||
if self.menubar.about.open {
|
ui.open_popup("About");
|
||||||
ui.show_about_window(&mut self.menubar.about.open);
|
self.menubar.about.about_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render Dear ImGui with WGPU
|
// Render Dear ImGui with WGPU
|
||||||
@ -201,17 +178,17 @@ impl Gui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wants_reset(&mut self) -> bool {
|
/// Query the state of the Gui through a unified interface
|
||||||
|
pub fn wants(&mut self, wants: Wants) -> bool {
|
||||||
|
match wants {
|
||||||
|
Wants::Quit => self.menubar.file.quit,
|
||||||
|
Wants::Disasm => self.menubar.debug.dis,
|
||||||
|
Wants::Reset => {
|
||||||
let reset = self.menubar.debug.reset;
|
let reset = self.menubar.debug.reset;
|
||||||
self.menubar.debug.reset = false;
|
self.menubar.debug.reset = false;
|
||||||
reset
|
reset
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
pub fn wants_disassembly(&self) -> bool {
|
|
||||||
self.menubar.debug.dis
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wants_quit(&self) -> bool {
|
|
||||||
self.menubar.file.quit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
src/bin/chirp-imgui/gui/about.rs
Normal file
28
src/bin/chirp-imgui/gui/about.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//! Information about the crate at compile time
|
||||||
|
|
||||||
|
use imgui::Ui;
|
||||||
|
|
||||||
|
/// Project authors
|
||||||
|
const AUTHORS: Option<&str> = option_env!("CARGO_PKG_AUTHORS");
|
||||||
|
/// Compiled binary name
|
||||||
|
const BIN: Option<&str> = option_env!("CARGO_BIN_NAME");
|
||||||
|
/// Crate description
|
||||||
|
const DESCRIPTION: Option<&str> = option_env!("CARGO_PKG_DESCRIPTION");
|
||||||
|
/// Crate name
|
||||||
|
const NAME: Option<&str> = option_env!("CARGO_PKG_NAME");
|
||||||
|
/// Crate repository
|
||||||
|
const REPO: Option<&str> = option_env!("CARGO_PKG_REPOSITORY");
|
||||||
|
/// Crate version
|
||||||
|
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
pub(crate) fn about(ui: &Ui) {
|
||||||
|
ui.popup("About", || {
|
||||||
|
ui.text(format!(
|
||||||
|
"{} v{}",
|
||||||
|
NAME.unwrap_or("Chirp"),
|
||||||
|
VERSION.unwrap_or("None"),
|
||||||
|
));
|
||||||
|
ui.text(format!("Crafted by: {}", AUTHORS.unwrap_or("some people")));
|
||||||
|
ui.text(REPO.unwrap_or("Repo unavailable"));
|
||||||
|
});
|
||||||
|
}
|
@ -1,12 +1,27 @@
|
|||||||
//! The menubar that shows at the top of the screen
|
//! The menubar that shows at the top of the screen
|
||||||
|
|
||||||
|
use super::Drawable;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Menubar {
|
pub struct Menubar {
|
||||||
pub(super) active: bool,
|
pub(super) active: bool,
|
||||||
pub file: File,
|
pub file: File,
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
pub debug: Debug,
|
pub debug: Debug,
|
||||||
pub about: About,
|
pub about: Help,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drawable for Menubar {
|
||||||
|
fn draw(&mut self, ui: &imgui::Ui) {
|
||||||
|
if self.active {
|
||||||
|
ui.main_menu_bar(|| {
|
||||||
|
self.file.draw(ui);
|
||||||
|
self.settings.draw(ui);
|
||||||
|
self.debug.draw(ui);
|
||||||
|
self.about.draw(ui);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Menubar {
|
impl Default for Menubar {
|
||||||
@ -23,20 +38,52 @@ impl Default for Menubar {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
pub(super) settings: bool,
|
|
||||||
pub(super) reset: bool,
|
pub(super) reset: bool,
|
||||||
pub(super) quit: bool,
|
pub(super) quit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drawable for File {
|
||||||
|
fn draw(&mut self, ui: &imgui::Ui) {
|
||||||
|
ui.menu("File", || {
|
||||||
|
self.reset = ui.menu_item("Reset");
|
||||||
|
self.quit = ui.menu_item("Quit");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Debug {
|
pub struct Debug {
|
||||||
pub(super) reset: bool,
|
pub(super) reset: bool,
|
||||||
pub(super) dis: bool,
|
pub(super) dis: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drawable for Debug {
|
||||||
|
fn draw(&mut self, ui: &imgui::Ui) {
|
||||||
|
ui.menu("Debug", || {
|
||||||
|
self.reset = ui.menu_item("Reset");
|
||||||
|
ui.checkbox("Live Disassembly", &mut self.dis);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug {
|
||||||
|
pub fn reset(&self) -> bool {
|
||||||
|
self.reset
|
||||||
|
}
|
||||||
|
pub fn dis(&self) -> bool {
|
||||||
|
self.dis
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct About {
|
pub struct Help {
|
||||||
pub(super) open: bool,
|
pub(super) about_open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drawable for Help {
|
||||||
|
fn draw(&mut self, ui: &imgui::Ui) {
|
||||||
|
ui.menu("Help", || self.about_open = ui.menu_item("About..."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
@ -48,6 +95,31 @@ pub struct Settings {
|
|||||||
pub(super) applied: bool,
|
pub(super) applied: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drawable for Settings {
|
||||||
|
fn draw(&mut self, ui: &imgui::Ui) {
|
||||||
|
self.applied = false;
|
||||||
|
ui.menu("Settings", || {
|
||||||
|
use chirp::Mode::*;
|
||||||
|
const MODES: [chirp::Mode; 3] = [Chip8, SChip, XOChip];
|
||||||
|
if ui.combo_simple_string("Mode", &mut self.mode_index, &MODES) {
|
||||||
|
self.quirks = MODES[self.mode_index].into();
|
||||||
|
self.applied |= true;
|
||||||
|
}
|
||||||
|
self.applied |= {
|
||||||
|
ui.input_scalar("IPF", &mut self.target_ipf)
|
||||||
|
.chars_decimal(true)
|
||||||
|
.build()
|
||||||
|
| ui.checkbox("Bin-ops don't clear vF", &mut self.quirks.bin_ops)
|
||||||
|
| ui.checkbox("DMA doesn't modify I", &mut self.quirks.dma_inc)
|
||||||
|
| ui.checkbox("Draw calls are instant", &mut self.quirks.draw_wait)
|
||||||
|
| ui.checkbox("Screen wraps at edge", &mut self.quirks.screen_wrap)
|
||||||
|
| ui.checkbox("Shift ops ignore vY", &mut self.quirks.shift)
|
||||||
|
| ui.checkbox("Jumps behave eratically", &mut self.quirks.stupid_jumps)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
pub fn target_ipf(&mut self) -> &mut usize {
|
pub fn target_ipf(&mut self) -> &mut usize {
|
||||||
&mut self.target_ipf
|
&mut self.target_ipf
|
||||||
@ -55,7 +127,6 @@ impl Settings {
|
|||||||
pub fn quirks(&mut self) -> &mut chirp::Quirks {
|
pub fn quirks(&mut self) -> &mut chirp::Quirks {
|
||||||
&mut self.quirks
|
&mut self.quirks
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn applied(&mut self) -> Option<(usize, chirp::Quirks)> {
|
pub fn applied(&mut self) -> Option<(usize, chirp::Quirks)> {
|
||||||
self.applied.then_some((self.target_ipf, self.quirks))
|
self.applied.then_some((self.target_ipf, self.quirks))
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
#![allow(dead_code)] // TODO: finish writing the code
|
#![allow(dead_code)] // TODO: finish writing the code
|
||||||
|
|
||||||
|
mod emu;
|
||||||
|
mod error;
|
||||||
|
mod gui;
|
||||||
|
|
||||||
|
use crate::emu::*;
|
||||||
use crate::gui::*;
|
use crate::gui::*;
|
||||||
use chirp::*;
|
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use pixels::{Pixels, SurfaceTexture};
|
use pixels::{Pixels, SurfaceTexture};
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
@ -12,23 +17,25 @@ use winit::event_loop::{ControlFlow, EventLoop};
|
|||||||
use winit::window::WindowBuilder;
|
use winit::window::WindowBuilder;
|
||||||
use winit_input_helper::WinitInputHelper;
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
mod error;
|
// TODO: Make these configurable in the frontend
|
||||||
mod gui;
|
|
||||||
|
|
||||||
const FOREGROUND: &[u8; 4] = &0xFFFF00FF_u32.to_be_bytes();
|
const FOREGROUND: &[u8; 4] = &0xFFFF00FF_u32.to_be_bytes();
|
||||||
const BACKGROUND: &[u8; 4] = &0x623701FF_u32.to_be_bytes();
|
const BACKGROUND: &[u8; 4] = &0x623701FF_u32.to_be_bytes();
|
||||||
|
const INIT_SPEED: usize = 10;
|
||||||
|
|
||||||
/// The state of the application
|
struct Application {
|
||||||
#[derive(Debug)]
|
|
||||||
struct Emulator {
|
|
||||||
gui: Gui,
|
gui: Gui,
|
||||||
screen: Bus,
|
emu: Emulator,
|
||||||
cpu: CPU,
|
|
||||||
ipf: usize,
|
|
||||||
colors: [[u8; 4]; 2],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), error::Error> {
|
fn main() -> Result<(), error::Error> {
|
||||||
|
let rom_path;
|
||||||
|
if let Some(path) = std::env::args().nth(1) {
|
||||||
|
rom_path = path;
|
||||||
|
} else {
|
||||||
|
panic!("Supply a rom!");
|
||||||
|
}
|
||||||
|
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
let mut input = WinitInputHelper::new();
|
let mut input = WinitInputHelper::new();
|
||||||
|
|
||||||
@ -46,33 +53,32 @@ fn main() -> Result<(), error::Error> {
|
|||||||
Pixels::new(128, 64, surface_texture)?
|
Pixels::new(128, 64, surface_texture)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut emu = Emulator::new(
|
let mut app = Application::new(
|
||||||
|
Emulator::new(INIT_SPEED, rom_path),
|
||||||
Gui::new(&window, &pixels),
|
Gui::new(&window, &pixels),
|
||||||
bus! {
|
|
||||||
Screen [0x000..0x100],
|
|
||||||
},
|
|
||||||
CPU::default(),
|
|
||||||
10,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// set initial parameters
|
// set initial parameters
|
||||||
*emu.gui.menubar.settings.target_ipf() = emu.cpu.flags.monotonic.unwrap_or(10);
|
*app.gui.menubar.settings.target_ipf() = INIT_SPEED;
|
||||||
*emu.gui.menubar.settings.quirks() = emu.cpu.flags.quirks;
|
*app.gui.menubar.settings.quirks() = app.emu.quirks();
|
||||||
if let Some(path) = std::env::args().nth(1) {
|
|
||||||
emu.cpu.load_program(path)?;
|
|
||||||
} else {
|
|
||||||
panic!("Supply a rom!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run event loop
|
// Run event loop
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
let toggle_fullscreen = || {
|
||||||
|
window.set_fullscreen(if window.fullscreen().is_some() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(winit::window::Fullscreen::Borderless(None))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
let redraw = |gui: &mut Gui, pixels: &mut Pixels| -> Result<(), error::Error> {
|
let redraw = |gui: &mut Gui, pixels: &mut Pixels| -> Result<(), error::Error> {
|
||||||
// Prepare gui for redraw
|
// Prepare gui for redraw
|
||||||
gui.prepare(&window)?;
|
gui.prepare(&window)?;
|
||||||
|
|
||||||
// Render everything together
|
// Render everything together
|
||||||
pixels.render_with(|encoder, render_target, context| {
|
pixels.render_with(|encoder, render_target, context| {
|
||||||
// Render the world texture
|
// Render the emulator's screen
|
||||||
context.scaling_renderer.render(encoder, render_target);
|
context.scaling_renderer.render(encoder, render_target);
|
||||||
// Render Dear ImGui
|
// Render Dear ImGui
|
||||||
gui.render(&window, encoder, render_target, context)?;
|
gui.render(&window, encoder, render_target, context)?;
|
||||||
@ -81,7 +87,7 @@ fn main() -> Result<(), error::Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut handle_events = |state: &mut Emulator,
|
let mut handle_events = |state: &mut Application,
|
||||||
pixels: &mut Pixels,
|
pixels: &mut Pixels,
|
||||||
control_flow: &mut ControlFlow|
|
control_flow: &mut ControlFlow|
|
||||||
-> Result<(), error::Error> {
|
-> Result<(), error::Error> {
|
||||||
@ -89,29 +95,30 @@ fn main() -> Result<(), error::Error> {
|
|||||||
if input.update(&event) {
|
if input.update(&event) {
|
||||||
use VirtualKeyCode::*;
|
use VirtualKeyCode::*;
|
||||||
match_pressed!( match input {
|
match_pressed!( match input {
|
||||||
Escape | input.quit() | state.gui.wants_quit() => {
|
Escape | input.quit() | state.gui.wants(Wants::Quit) => {
|
||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Exit;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
F1 => state.cpu.dump(),
|
F1 => state.emu.print_registers(),
|
||||||
F2 => state.screen.print_screen()?,
|
F2 => state.emu.print_screen()?,
|
||||||
F3 => eprintln!("TODO: Dump screen"),
|
F3 => state.emu.dump_screen()?,
|
||||||
F4 => state.cpu.flags.debug(),
|
F4 => state.emu.is_disasm(),
|
||||||
F5 => state.cpu.flags.pause(),
|
F5 => state.emu.pause(),
|
||||||
F6 => state.cpu.singlestep(&mut state.screen)?,
|
F6 => state.emu.singlestep()?,
|
||||||
F7 => state.cpu.set_break(state.cpu.pc()),
|
F7 => state.emu.set_break(),
|
||||||
F8 => state.cpu.unset_break(state.cpu.pc()),
|
F8 => state.emu.unset_break(),
|
||||||
Delete => state.cpu.reset(),
|
Delete => state.emu.soft_reset(),
|
||||||
F11 => window.set_maximized(!window.is_maximized()),
|
Insert => state.emu.hard_reset(),
|
||||||
|
F11 => toggle_fullscreen(),
|
||||||
LAlt => state.gui.show_menubar(None),
|
LAlt => state.gui.show_menubar(None),
|
||||||
});
|
});
|
||||||
state.input(&input)?;
|
state.emu.input(&input)?;
|
||||||
|
|
||||||
// Apply settings
|
// Apply settings
|
||||||
if let Some((ipf, quirks)) = state.gui.menubar.settings.applied() {
|
if let Some((ipf, quirks)) = state.gui.menubar.settings.applied() {
|
||||||
state.ipf = ipf;
|
state.emu.ipf = ipf;
|
||||||
state.cpu.flags.monotonic = Some(ipf);
|
state.emu.set_quirks(quirks);
|
||||||
state.cpu.flags.quirks = quirks;
|
println!("'A");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the scale factor
|
// Update the scale factor
|
||||||
@ -130,14 +137,14 @@ fn main() -> Result<(), error::Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.cpu.flags.debug = state.gui.wants_disassembly();
|
state.emu.set_disasm(state.gui.wants(Wants::Disasm));
|
||||||
if state.gui.wants_reset() {
|
if state.gui.wants(Wants::Reset) {
|
||||||
state.cpu.reset();
|
state.emu.hard_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the game loop
|
// Run the game loop
|
||||||
state.update()?;
|
state.emu.update()?;
|
||||||
state.draw(pixels)?;
|
state.emu.draw(pixels)?;
|
||||||
|
|
||||||
// redraw the window
|
// redraw the window
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
@ -146,81 +153,22 @@ fn main() -> Result<(), error::Error> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Event::RedrawRequested(_) = event {
|
if let Event::RedrawRequested(_) = event {
|
||||||
if let Err(e) = redraw(&mut emu.gui, &mut pixels) {
|
if let Err(e) = redraw(&mut app.gui, &mut pixels) {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = handle_events(&mut emu, &mut pixels, control_flow) {
|
if let Err(e) = handle_events(&mut app, &mut pixels, control_flow) {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
*control_flow = ControlFlow::Exit;
|
*control_flow = ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Emulator {
|
impl Application {
|
||||||
pub fn new(gui: Gui, mem: Bus, cpu: CPU, ipf: usize) -> Self {
|
fn new(emu: Emulator, gui: Gui) -> Self {
|
||||||
Self {
|
Self { gui, emu }
|
||||||
gui,
|
|
||||||
screen: mem,
|
|
||||||
cpu,
|
|
||||||
ipf,
|
|
||||||
colors: [*FOREGROUND, *BACKGROUND],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self) -> Result<(), error::Error> {
|
|
||||||
self.cpu.multistep(&mut self.screen, self.ipf)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(&mut self, pixels: &mut Pixels) -> Result<(), error::Error> {
|
|
||||||
if let Some(screen) = self.screen.get_region(Screen) {
|
|
||||||
let len_log2 = screen.len().ilog2() / 2;
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
let (width, height) = (2u32.pow(len_log2 + 2), 2u32.pow(len_log2 + 1));
|
|
||||||
pixels.resize_buffer(width, height)?;
|
|
||||||
for (idx, pixel) in pixels.frame_mut().iter_mut().enumerate() {
|
|
||||||
let (byte, bit, component) = (idx >> 5, (idx >> 2) % 8, idx & 0b11);
|
|
||||||
*pixel = if screen[byte] & (0x80 >> bit) > 0 {
|
|
||||||
self.colors[0][component]
|
|
||||||
} else {
|
|
||||||
self.colors[1][component]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn input(&mut self, input: &WinitInputHelper) -> Result<(), error::Error> {
|
|
||||||
const KEYMAP: [VirtualKeyCode; 16] = [
|
|
||||||
VirtualKeyCode::X,
|
|
||||||
VirtualKeyCode::Key1,
|
|
||||||
VirtualKeyCode::Key2,
|
|
||||||
VirtualKeyCode::Key3,
|
|
||||||
VirtualKeyCode::Q,
|
|
||||||
VirtualKeyCode::W,
|
|
||||||
VirtualKeyCode::E,
|
|
||||||
VirtualKeyCode::A,
|
|
||||||
VirtualKeyCode::S,
|
|
||||||
VirtualKeyCode::D,
|
|
||||||
VirtualKeyCode::Z,
|
|
||||||
VirtualKeyCode::C,
|
|
||||||
VirtualKeyCode::Key4,
|
|
||||||
VirtualKeyCode::R,
|
|
||||||
VirtualKeyCode::F,
|
|
||||||
VirtualKeyCode::V,
|
|
||||||
];
|
|
||||||
for (id, &key) in KEYMAP.iter().enumerate() {
|
|
||||||
if input.key_released(key) {
|
|
||||||
self.cpu.release(id)?;
|
|
||||||
}
|
|
||||||
if input.key_pressed(key) {
|
|
||||||
self.cpu.press(id)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user