Compare commits
No commits in common. "d9a3a7b0dde1486efb04da9d9f7725269df6f16e" and "31abd6dc5c09e36eb3d104c0c47707f985801bbd" have entirely different histories.
d9a3a7b0dd
...
31abd6dc5c
650
Cargo.lock
generated
650
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -11,9 +11,12 @@ anyhow = "^1.0"
|
||||
bitfield = "^0.13"
|
||||
clap = "^2.33"
|
||||
gilrs = "^0.8"
|
||||
pixels = "^0.5"
|
||||
winit = "^0.25"
|
||||
winit_input_helper = "^0.10"
|
||||
pixels = "^0.3"
|
||||
winit = "^0.24"
|
||||
winit_input_helper = "^0.9"
|
||||
egui = "^0.10"
|
||||
egui_wgpu_backend = { git="https://github.com/hasenbanck/egui_wgpu_backend.git", rev="9d03ad345d15d1e44165849b242d3562fdf3e859" }
|
||||
egui_winit_platform = { git="https://github.com/hasenbanck/egui_winit_platform.git", rev="17298250e9721e8bf2c1d4a17b3e22777f8cb2e8" }
|
||||
rodio = "^0.14"
|
||||
crossbeam-channel = "^0.5"
|
||||
|
||||
|
321
src/gui.rs
Normal file
321
src/gui.rs
Normal file
@ -0,0 +1,321 @@
|
||||
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<ClippedMesh>,
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
pub use apu::gen::AudioMPSC;
|
||||
pub use gui::Egui;
|
||||
pub use instruction::Cycle;
|
||||
|
||||
pub const GB_WIDTH: usize = 160;
|
||||
@ -9,6 +10,7 @@ mod bus;
|
||||
mod cartridge;
|
||||
mod cpu;
|
||||
pub mod emu;
|
||||
mod gui;
|
||||
mod high_ram;
|
||||
mod instruction;
|
||||
mod interrupt;
|
||||
|
35
src/main.rs
35
src/main.rs
@ -1,6 +1,6 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
|
||||
use gb::{AudioMPSC, Cycle, GB_HEIGHT, GB_WIDTH};
|
||||
use gb::{AudioMPSC, Cycle, Egui, GB_HEIGHT, GB_WIDTH};
|
||||
use gilrs::Gilrs;
|
||||
use pixels::{PixelsBuilder, SurfaceTexture};
|
||||
use rodio::OutputStream;
|
||||
@ -56,13 +56,17 @@ fn main() -> Result<()> {
|
||||
let mut input = WinitInputHelper::new();
|
||||
let window = create_window(&event_loop, cartridge_title)?;
|
||||
|
||||
let mut pixels = {
|
||||
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);
|
||||
|
||||
PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
|
||||
let pixels = PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
|
||||
.enable_vsync(false)
|
||||
.build()?
|
||||
.build()?;
|
||||
let egui = Egui::new(size.width, size.height, scale_factor, pixels.context());
|
||||
|
||||
(pixels, egui)
|
||||
};
|
||||
|
||||
let (send, recv) = AudioMPSC::init();
|
||||
@ -81,9 +85,23 @@ fn main() -> Result<()> {
|
||||
let mut cycle_count: Cycle = Default::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()
|
||||
{
|
||||
@ -98,8 +116,13 @@ fn main() -> Result<()> {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let delta = now.elapsed().subsec_nanos();
|
||||
|
Loading…
x
Reference in New Issue
Block a user