feat: integrate eui and pixels-rs for debug info
This commit is contained in:
47
src/cpu.rs
47
src/cpu.rs
@@ -11,6 +11,7 @@ pub struct Cpu {
|
||||
reg: Registers,
|
||||
flags: Flags,
|
||||
ime: ImeState,
|
||||
// TODO: Merge halted and state properties
|
||||
halted: Option<HaltState>,
|
||||
state: State,
|
||||
}
|
||||
@@ -89,7 +90,10 @@ impl Cpu {
|
||||
|
||||
pub fn step(&mut self) -> Cycle {
|
||||
// if !self.bus.boot_enabled() {
|
||||
// self.log_state().unwrap();
|
||||
// let out = std::io::stdout();
|
||||
// let handle = out.lock();
|
||||
|
||||
// self.log_state(handle).unwrap();
|
||||
// }
|
||||
|
||||
let cycles = match self.halted() {
|
||||
@@ -123,13 +127,13 @@ impl Cpu {
|
||||
|
||||
impl Cpu {
|
||||
pub fn read_imm_byte(&mut self, addr: u16) -> u8 {
|
||||
self.inc_pc();
|
||||
self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 1 after this function call
|
||||
self.bus.read_byte(addr)
|
||||
}
|
||||
|
||||
pub fn read_imm_word(&mut self, addr: u16) -> u16 {
|
||||
self.inc_pc();
|
||||
self.inc_pc();
|
||||
self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 2 after this function call
|
||||
self.bus.read_word(addr)
|
||||
}
|
||||
|
||||
@@ -335,27 +339,22 @@ impl Cpu {
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
fn log_state(&self) -> std::io::Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
let out = std::io::stdout();
|
||||
let mut handle = out.lock();
|
||||
|
||||
write!(handle, "A: {:02X} ", self.reg.a)?;
|
||||
write!(handle, "F: {:02X} ", u8::from(self.flags))?;
|
||||
write!(handle, "B: {:02X} ", self.reg.b)?;
|
||||
write!(handle, "C: {:02X} ", self.reg.c)?;
|
||||
write!(handle, "D: {:02X} ", self.reg.d)?;
|
||||
write!(handle, "E: {:02X} ", self.reg.e)?;
|
||||
write!(handle, "H: {:02X} ", self.reg.h)?;
|
||||
write!(handle, "L: {:02X} ", self.reg.l)?;
|
||||
write!(handle, "SP: {:04X} ", self.reg.sp)?;
|
||||
write!(handle, "PC: 00:{:04X} ", self.reg.pc)?;
|
||||
write!(handle, "({:02X} ", self.read_byte(self.reg.pc))?;
|
||||
write!(handle, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
|
||||
write!(handle, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
|
||||
writeln!(handle, "{:02X})", self.read_byte(self.reg.pc + 3))?;
|
||||
handle.flush()?;
|
||||
pub fn log_state(&self, mut writer: impl std::io::Write) -> std::io::Result<()> {
|
||||
write!(writer, "A: {:02X} ", self.reg.a)?;
|
||||
write!(writer, "F: {:02X} ", u8::from(self.flags))?;
|
||||
write!(writer, "B: {:02X} ", self.reg.b)?;
|
||||
write!(writer, "C: {:02X} ", self.reg.c)?;
|
||||
write!(writer, "D: {:02X} ", self.reg.d)?;
|
||||
write!(writer, "E: {:02X} ", self.reg.e)?;
|
||||
write!(writer, "H: {:02X} ", self.reg.h)?;
|
||||
write!(writer, "L: {:02X} ", self.reg.l)?;
|
||||
write!(writer, "SP: {:04X} ", self.reg.sp)?;
|
||||
write!(writer, "PC: 00:{:04X} ", self.reg.pc)?;
|
||||
write!(writer, "({:02X} ", self.read_byte(self.reg.pc))?;
|
||||
write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
|
||||
write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
|
||||
writeln!(writer, "{:02X})", self.read_byte(self.reg.pc + 3))?;
|
||||
writer.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
356
src/gui.rs
Normal file
356
src/gui.rs
Normal file
@@ -0,0 +1,356 @@
|
||||
use crate::cpu::Register;
|
||||
use crate::cpu::RegisterPair;
|
||||
use crate::LR35902;
|
||||
use egui::{ClippedMesh, FontDefinitions};
|
||||
use egui_wgpu_backend::{RenderPass, ScreenDescriptor};
|
||||
use egui_winit_platform::{Platform, PlatformDescriptor};
|
||||
use pixels::{wgpu, PixelsContext};
|
||||
use std::time::Instant;
|
||||
use wgpu::TextureFormat::Bgra8UnormSrgb;
|
||||
|
||||
// Boilerplate code from: https://github.com/parasyte/pixels/blob/0.3.0/examples/egui-winit/src/gui.rs
|
||||
|
||||
/// Manages all state required for rendering egui over `Pixels`.
|
||||
pub struct Egui {
|
||||
// State for egui.
|
||||
start_time: Instant,
|
||||
platform: Platform,
|
||||
screen_desc: ScreenDescriptor,
|
||||
render_pass: RenderPass,
|
||||
paint_jobs: Vec<ClippedMesh>,
|
||||
|
||||
pub config: Configuration,
|
||||
|
||||
show_flags: bool,
|
||||
show_cpu_info: bool,
|
||||
show_registers: bool,
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
show_disasm: bool,
|
||||
#[cfg(feature = "debug")]
|
||||
pub break_point: Option<u16>,
|
||||
}
|
||||
|
||||
impl Egui {
|
||||
/// Create egui.
|
||||
pub fn new(width: u32, height: u32, scale_factor: f64, context: &PixelsContext) -> Self {
|
||||
let platform = Platform::new(PlatformDescriptor {
|
||||
physical_width: width,
|
||||
physical_height: height,
|
||||
scale_factor,
|
||||
font_definitions: FontDefinitions::default(),
|
||||
style: Default::default(),
|
||||
});
|
||||
|
||||
let screen_desc = ScreenDescriptor {
|
||||
physical_width: width,
|
||||
physical_height: height,
|
||||
scale_factor: scale_factor as f32,
|
||||
};
|
||||
|
||||
let render_pass = RenderPass::new(&context.device, Bgra8UnormSrgb);
|
||||
|
||||
Self {
|
||||
start_time: Instant::now(),
|
||||
platform,
|
||||
screen_desc,
|
||||
render_pass,
|
||||
paint_jobs: Vec::new(),
|
||||
config: Default::default(),
|
||||
show_flags: false,
|
||||
show_cpu_info: false,
|
||||
show_registers: false,
|
||||
#[cfg(feature = "debug")]
|
||||
show_disasm: false,
|
||||
#[cfg(feature = "debug")]
|
||||
break_point: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle input events from the window manager.
|
||||
pub fn handle_event(&mut self, event: &winit::event::Event<'_, ()>) {
|
||||
self.platform.handle_event(event);
|
||||
}
|
||||
|
||||
/// Resize egui.
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.screen_desc.physical_width = width;
|
||||
self.screen_desc.physical_height = height;
|
||||
}
|
||||
|
||||
/// Update scaling factor.
|
||||
pub fn scale_factor(&mut self, scale_factor: f64) {
|
||||
self.screen_desc.scale_factor = scale_factor as f32;
|
||||
}
|
||||
|
||||
/// Prepare egui.
|
||||
pub fn prepare(&mut self, game_boy: &LR35902) {
|
||||
self.platform
|
||||
.update_time(self.start_time.elapsed().as_secs_f64());
|
||||
|
||||
// Begin the egui frame.
|
||||
self.platform.begin_frame();
|
||||
|
||||
// Draw the demo application.
|
||||
self.ui(&self.platform.context(), game_boy);
|
||||
|
||||
// End the egui frame and create all paint jobs to prepare for rendering.
|
||||
let (_output, paint_commands) = self.platform.end_frame();
|
||||
self.paint_jobs = self.platform.context().tessellate(paint_commands);
|
||||
}
|
||||
|
||||
/// Create the UI using egui.
|
||||
fn ui(&mut self, ctx: &egui::CtxRef, game_boy: &LR35902) {
|
||||
egui::TopPanel::top("menubar_container").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
egui::menu::menu(ui, "File", |ui| {
|
||||
if ui.button("Configuration").clicked() {
|
||||
self.config.show = true;
|
||||
}
|
||||
});
|
||||
|
||||
egui::menu::menu(ui, "Status", |ui| {
|
||||
if ui.button("Flags").clicked() {
|
||||
self.show_flags = true;
|
||||
}
|
||||
|
||||
if ui.button("CPU Information").clicked() {
|
||||
self.show_cpu_info = true;
|
||||
}
|
||||
|
||||
if ui.button("Registers").clicked() {
|
||||
self.show_registers = true;
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
if ui.button("Disassembly").clicked() {
|
||||
self.show_disasm = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
egui::Window::new("Cpu Flags")
|
||||
.open(&mut self.show_flags)
|
||||
.show(ctx, |ui| {
|
||||
let flags = game_boy.flags();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let _ = ui.selectable_label(flags.z(), "Zero");
|
||||
let _ = ui.selectable_label(flags.n(), "Negative");
|
||||
let _ = ui.selectable_label(flags.h(), "Half-Carry");
|
||||
let _ = ui.selectable_label(flags.c(), "Carry");
|
||||
});
|
||||
});
|
||||
|
||||
egui::Window::new("Registers")
|
||||
.open(&mut self.show_registers)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("A");
|
||||
ui.monospace(format!("{:#04X}", game_boy.register(Register::A)));
|
||||
|
||||
ui.label("F");
|
||||
let flag: u8 = game_boy.register(Register::Flag).into();
|
||||
ui.monospace(format!("{:#04X}", flag));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("B");
|
||||
ui.monospace(format!("{:#04X}", game_boy.register(Register::B)));
|
||||
|
||||
ui.label("C");
|
||||
ui.monospace(format!("{:#04X}", game_boy.register(Register::C)));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("D");
|
||||
ui.monospace(format!("{:#04X}", game_boy.register(Register::D)));
|
||||
|
||||
ui.label("E");
|
||||
ui.monospace(format!("{:#04X}", game_boy.register(Register::E)));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("H");
|
||||
ui.monospace(format!("{:#04X}", game_boy.register(Register::H)));
|
||||
|
||||
ui.label("L");
|
||||
ui.monospace(format!("{:#04X}", game_boy.register(Register::L)));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("AF");
|
||||
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::AF)));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("BC");
|
||||
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::BC)));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("DE");
|
||||
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::DE)));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("HL");
|
||||
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::HL)));
|
||||
});
|
||||
});
|
||||
|
||||
egui::Window::new("Cpu Information")
|
||||
.open(&mut self.show_cpu_info)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("PC");
|
||||
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::PC)));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("SP");
|
||||
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::SP)));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("IME");
|
||||
ui.label(format!("{:?}", game_boy.ime()));
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("HALT");
|
||||
ui.label(format!("{:?}", game_boy.halted()));
|
||||
});
|
||||
});
|
||||
|
||||
#[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").show(ctx, |ui| {
|
||||
let req = game_boy.read_byte(0xFF0F);
|
||||
let enabled = game_boy.read_byte(0xFFFF);
|
||||
|
||||
ui.heading("Interrupt Requests");
|
||||
ui.horizontal(|ui| {
|
||||
let _ = ui.selectable_label(req & 0x01 == 0x01, "VBLANK");
|
||||
let _ = ui.selectable_label((req >> 1) & 0x01 == 0x01, "LCD STAT");
|
||||
let _ = ui.selectable_label((req >> 2) & 0x01 == 0x01, "TIMER");
|
||||
let _ = ui.selectable_label((req >> 3) & 0x01 == 0x01, "SERIAL");
|
||||
let _ = ui.selectable_label((req >> 4) & 0x01 == 0x01, "JOYPAD");
|
||||
});
|
||||
|
||||
ui.heading("Interrupt Enable");
|
||||
ui.horizontal(|ui| {
|
||||
let _ = ui.selectable_label(enabled & 0x01 == 0x01, "VBLANK");
|
||||
let _ = ui.selectable_label((enabled >> 1) & 0x01 == 0x01, "LCD STAT");
|
||||
let _ = ui.selectable_label((enabled >> 2) & 0x01 == 0x01, "TIMER");
|
||||
let _ = ui.selectable_label((enabled >> 3) & 0x01 == 0x01, "SERIAL");
|
||||
let _ = ui.selectable_label((enabled >> 4) & 0x01 == 0x01, "JOYPAD");
|
||||
});
|
||||
});
|
||||
|
||||
#[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;
|
||||
}
|
||||
}
|
||||
|
||||
/// Render egui.
|
||||
pub fn render(
|
||||
&mut self,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
render_target: &wgpu::TextureView,
|
||||
context: &PixelsContext,
|
||||
) {
|
||||
// Upload all resources to the GPU.
|
||||
self.render_pass.update_texture(
|
||||
&context.device,
|
||||
&context.queue,
|
||||
&self.platform.context().texture(),
|
||||
);
|
||||
self.render_pass
|
||||
.update_user_textures(&context.device, &context.queue);
|
||||
self.render_pass.update_buffers(
|
||||
&context.device,
|
||||
&context.queue,
|
||||
&self.paint_jobs,
|
||||
&self.screen_desc,
|
||||
);
|
||||
|
||||
// Record all render passes.
|
||||
self.render_pass.execute(
|
||||
encoder,
|
||||
render_target,
|
||||
&self.paint_jobs,
|
||||
&self.screen_desc,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
use super::cpu::{Cpu, Flags, HaltState, ImeState, Register, RegisterPair};
|
||||
use std::{convert::TryFrom, fmt::Debug};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum Instruction {
|
||||
NOP,
|
||||
@@ -93,7 +93,7 @@ pub enum InstrRegisterPair {
|
||||
DecrementHL,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum InstrRegister {
|
||||
A,
|
||||
B,
|
||||
@@ -105,7 +105,7 @@ pub enum InstrRegister {
|
||||
IndirectHL, // (HL)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum JumpCondition {
|
||||
NotZero,
|
||||
Zero,
|
||||
@@ -2017,15 +2017,15 @@ impl std::fmt::Debug for JPTarget {
|
||||
impl std::fmt::Debug for LDTarget {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
LDTarget::IndirectC => f.write_str("IndirectC"),
|
||||
LDTarget::IndirectC => f.write_str("(0xFF00 + C)"),
|
||||
LDTarget::Register(reg) => write!(f, "{:?}", reg),
|
||||
LDTarget::IndirectRegister(pair) => write!(f, "[{:?}]", pair),
|
||||
LDTarget::ByteAtAddress(addr) => write!(f, "[{:#06X}]", addr),
|
||||
LDTarget::IndirectRegister(pair) => write!(f, "({:?})", pair),
|
||||
LDTarget::ByteAtAddress(addr) => write!(f, "({:#06X})", addr),
|
||||
LDTarget::ImmediateWord(word) => write!(f, "{:#06X}", word),
|
||||
LDTarget::ImmediateByte(byte) => write!(f, "{:#04X}", byte),
|
||||
LDTarget::RegisterPair(pair) => write!(f, "{:?}", pair),
|
||||
LDTarget::ByteAtAddressWithOffset(byte) => {
|
||||
write!(f, "[0xFF00 + {:#04X}]", byte)
|
||||
write!(f, "(0xFF00 + {:#04X})", byte)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2065,6 +2065,90 @@ impl std::fmt::Debug for Registers {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Instruction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use Instruction::*;
|
||||
|
||||
match *self {
|
||||
NOP => f.write_str("NOP"),
|
||||
LD(left, right) => write!(f, "LD {:?}, {:?}", left, right),
|
||||
STOP => f.write_str("STOP"),
|
||||
JR(cond, dist) => write!(f, "JR {:?}, {:?}", cond, dist),
|
||||
ADD(left, right) => write!(f, "ADD {:?}, {:?}", left, right),
|
||||
INC(register) => write!(f, "INC {:?}", register),
|
||||
DEC(register) => write!(f, "DEC {:?}", register),
|
||||
RLCA => f.write_str("RLCA"),
|
||||
RRCA => f.write_str("RRCA"),
|
||||
RLA => f.write_str("RLA"),
|
||||
RRA => f.write_str("RRA"),
|
||||
DAA => f.write_str("DAA"),
|
||||
CPL => f.write_str("CPL"),
|
||||
SCF => f.write_str("SCF"),
|
||||
CCF => f.write_str("CCF"),
|
||||
HALT => f.write_str("HALT"),
|
||||
ADC(target) => write!(f, "ADC {:?}", target),
|
||||
SUB(target) => write!(f, "SUB {:?}", target),
|
||||
SBC(target) => write!(f, "SBC {:?}", target),
|
||||
AND(target) => write!(f, "AND {:?}", target),
|
||||
XOR(target) => write!(f, "XOR {:?}", target),
|
||||
OR(target) => write!(f, "OR {:?}", target),
|
||||
CP(target) => write!(f, "CP {:?}", target),
|
||||
RET(cond) => write!(f, "RET {:?}", cond),
|
||||
LDHL(value) => write!(f, "LDHL {:?}", value),
|
||||
POP(pair) => write!(f, "POP {:?}", pair),
|
||||
RETI => f.write_str("RETI"),
|
||||
JP(cond, target) => write!(f, "JP {:?}, {:?}", cond, target),
|
||||
DI => f.write_str("DI"),
|
||||
EI => f.write_str("EI"),
|
||||
CALL(cond, addr) => write!(f, "CALL {:?}, {:?}", cond, addr),
|
||||
PUSH(pair) => write!(f, "PUSH {:?}", pair),
|
||||
RST(vector) => write!(f, "RST {:?}", vector),
|
||||
RLC(register) => write!(f, "RLC {:?}", register),
|
||||
RRC(register) => write!(f, "RRC {:?}", register),
|
||||
RL(register) => write!(f, "RL {:?}", register),
|
||||
RR(register) => write!(f, "RR {:?}", register),
|
||||
SLA(register) => write!(f, "SLA {:?}", register),
|
||||
SRA(register) => write!(f, "SRA {:?}", register),
|
||||
SWAP(register) => write!(f, "SWAP {:?}", register),
|
||||
SRL(register) => write!(f, "SRL {:?}", register),
|
||||
BIT(bit, register) => write!(f, "BIT {:?}, {:?}", bit, register),
|
||||
RES(bit, register) => write!(f, "RES {:?}, {:?}", bit, register),
|
||||
SET(bit, register) => write!(f, "SET {:?}, {:?}", bit, register),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for JumpCondition {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use JumpCondition::*;
|
||||
|
||||
match *self {
|
||||
NotZero => f.write_str("NZ"),
|
||||
Zero => f.write_str("Z"),
|
||||
NotCarry => f.write_str("NC"),
|
||||
Carry => f.write_str("C"),
|
||||
Always => f.write_str(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for InstrRegister {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use InstrRegister::*;
|
||||
|
||||
match *self {
|
||||
A => f.write_str("A"),
|
||||
B => f.write_str("B"),
|
||||
C => f.write_str("C"),
|
||||
D => f.write_str("D"),
|
||||
E => f.write_str("E"),
|
||||
H => f.write_str("H"),
|
||||
L => f.write_str("L"),
|
||||
IndirectHL => f.write_str("(HL)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Cycle {
|
||||
pub const fn new(num: u32) -> Self {
|
||||
Self(num)
|
||||
|
@@ -1,7 +1,11 @@
|
||||
pub use cpu::Cpu as LR35902;
|
||||
pub use gui::Egui;
|
||||
pub use instruction::Cycle;
|
||||
pub use joypad::ButtonState;
|
||||
|
||||
#[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
|
||||
@@ -9,6 +13,7 @@ pub const LR35902_CLOCK_SPEED: u32 = 0x400000; // Hz | 4.194304Mhz
|
||||
mod bus;
|
||||
mod cartridge;
|
||||
mod cpu;
|
||||
mod gui;
|
||||
mod high_ram;
|
||||
mod instruction;
|
||||
mod interrupt;
|
||||
|
105
src/main.rs
105
src/main.rs
@@ -1,8 +1,9 @@
|
||||
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::{ButtonState, Cycle, LR35902};
|
||||
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType, Gamepad, Gilrs};
|
||||
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType, Gilrs};
|
||||
use pixels::{Pixels, SurfaceTexture};
|
||||
use std::time::{Duration, Instant};
|
||||
use winit::dpi::LogicalSize;
|
||||
@@ -11,6 +12,9 @@ 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;
|
||||
@@ -19,6 +23,9 @@ 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!())
|
||||
@@ -64,20 +71,46 @@ fn main() -> Result<()> {
|
||||
|
||||
// Initialize Gamepad Support
|
||||
let mut gilrs = Gilrs::new().expect("Failed to initialize Gilrs");
|
||||
let mut active_gamepad = None;
|
||||
|
||||
// Initialize GUI
|
||||
let event_loop = EventLoop::new();
|
||||
let mut input = WinitInputHelper::new();
|
||||
let window = create_window(&event_loop, cartridge_title)?;
|
||||
let mut pixels = create_pixels(&window)?;
|
||||
|
||||
let (mut pixels, mut egui) = {
|
||||
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 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;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
// Update egui
|
||||
egui.handle_event(&event);
|
||||
|
||||
if let Event::RedrawRequested(_) = event {
|
||||
if pixels
|
||||
.render()
|
||||
// Prepare egui
|
||||
egui.prepare(&game_boy);
|
||||
|
||||
// Render everything together
|
||||
let render_result = pixels.render_with(|encoder, target, ctx| {
|
||||
// Render the texture
|
||||
ctx.scaling_renderer.render(encoder, target);
|
||||
|
||||
// Render egui
|
||||
egui.render(encoder, target, ctx);
|
||||
});
|
||||
|
||||
if render_result
|
||||
.map_err(|e| anyhow!("pixels.render() failed: {}", e))
|
||||
.is_err()
|
||||
{
|
||||
@@ -92,39 +125,75 @@ fn main() -> Result<()> {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(size) = input.window_resized() {
|
||||
pixels.resize_surface(size.width, size.height);
|
||||
#[cfg(feature = "debug")]
|
||||
if input.key_pressed(VirtualKeyCode::S) {
|
||||
step_mode = !step_mode;
|
||||
}
|
||||
|
||||
if active_gamepad.is_none() {
|
||||
active_gamepad = gilrs.next_event().map(|e| e.id);
|
||||
if let Some(scale_factor) = input.scale_factor() {
|
||||
egui.scale_factor(scale_factor);
|
||||
}
|
||||
|
||||
if let Some(size) = input.window_resized() {
|
||||
pixels.resize_surface(size.width, size.height);
|
||||
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, event);
|
||||
}
|
||||
|
||||
for _ in 0..egui.config.spacebar_step {
|
||||
elapsed_cycles += game_boy.step();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let mut elapsed_cycles: Cycle = Default::default();
|
||||
while elapsed_cycles <= pending_cycles {
|
||||
if let Some(event) = gilrs.next_event() {
|
||||
handle_gamepad_input(&mut game_boy, 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 {
|
||||
let ppu = game_boy.get_ppu();
|
||||
let frame = pixels.get_frame();
|
||||
ppu.copy_to_gui(frame);
|
||||
window.request_redraw();
|
||||
// Redraw
|
||||
cycles_in_frame = Default::default();
|
||||
|
||||
cycles_in_frame = Default::default()
|
||||
game_boy.get_ppu().copy_to_gui(pixels.get_frame());
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -139,12 +208,6 @@ fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
|
||||
.build(&event_loop)?)
|
||||
}
|
||||
|
||||
fn create_pixels(window: &Window) -> Result<Pixels> {
|
||||
let window_size = window.inner_size();
|
||||
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, window);
|
||||
Ok(Pixels::new(GB_WIDTH, GB_HEIGHT, surface_texture)?)
|
||||
}
|
||||
|
||||
fn handle_gamepad_input(game_boy: &mut LR35902, event: GamepadEvent) {
|
||||
use GamepadEventType::*;
|
||||
let joypad_status = &mut game_boy.bus.joypad.status;
|
||||
|
Reference in New Issue
Block a user