use crate::bus::BusIo; use crate::cpu::{Cpu as SM83, Register, RegisterPair}; 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, pub config: Configuration, show_flags: bool, show_cpu_info: bool, show_registers: bool, show_int: bool, show_timer: bool, } 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, show_int: false, show_timer: false, } } /// 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: &SM83) { 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: &SM83) { 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; } if ui.button("Interrupts").clicked() { self.show_int = true; } if ui.button("Timer").clicked() { self.show_timer = 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("Timer") .open(&mut self.show_timer) .show(ctx, |ui| { let timer = game_boy.timer(); ui.horizontal(|ui| { ui.label("DIV"); ui.monospace(format!("{:#06X}", timer.divider)); }); ui.horizontal(|ui| { ui.label("TIMA"); ui.monospace(format!("{:#04X}", timer.counter)); }); ui.horizontal(|ui| { ui.label("TMA"); ui.monospace(format!("{:#04X}", timer.modulo)); }); ui.horizontal(|ui| { ui.label("TAC"); ui.monospace(format!("{:?}", timer.ctrl)); }); }); 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"); ui.monospace(format!("{:#04X}", game_boy.register(Register::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())); }); }); egui::Window::new("IRQ Information") .open(&mut self.show_int) .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"); }); }); egui::Window::new("Configuration") .open(&mut self.config.show) .show(ctx, |_ui| {}); } /// 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, } impl Default for Configuration { fn default() -> Self { Self { show: false } } }