chore: refactor gui code
This commit is contained in:
parent
41bc4f7afe
commit
77af5453f0
457
src/gui.rs
457
src/gui.rs
|
@ -1,18 +1,19 @@
|
|||
use egui::{ClippedPrimitive, Context, TextureId};
|
||||
use egui_wgpu_backend::{BackendError, RenderPass, ScreenDescriptor};
|
||||
use egui::{Context, TextureId};
|
||||
use egui_wgpu_backend::{RenderPass, ScreenDescriptor};
|
||||
use egui_winit_platform::Platform;
|
||||
use wgpu::{
|
||||
Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError,
|
||||
Surface, SurfaceConfiguration, SurfaceTexture, Texture, TextureFormat, TextureUsages,
|
||||
TextureView,
|
||||
Adapter, Backends, Color, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue,
|
||||
RequestDeviceError, Surface, SurfaceConfiguration, Texture, TextureFormat, TextureUsages,
|
||||
TextureView, TextureViewDescriptor,
|
||||
};
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::error::OsError;
|
||||
use winit::event::{ElementState, KeyboardInput};
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::event::{ElementState, Event, KeyboardInput};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::Window;
|
||||
|
||||
use crate::cpu::Cpu;
|
||||
use crate::{GB_HEIGHT, GB_WIDTH};
|
||||
use crate::{emu, GB_HEIGHT, GB_WIDTH};
|
||||
|
||||
const EGUI_DIMENSIONS: (usize, usize) = (1280, 720);
|
||||
const FILTER_MODE: FilterMode = FilterMode::Nearest;
|
||||
|
@ -20,23 +21,272 @@ const WINDOW_TITLE: &str = "DMG-01 Emulator";
|
|||
|
||||
const SCALE: f32 = 3.0;
|
||||
|
||||
/// Holds GUI State
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GuiState {
|
||||
/// When true, egui winit should exit the application
|
||||
pub quit: bool,
|
||||
pub struct Gui {
|
||||
pub should_quit: bool,
|
||||
pub title: String,
|
||||
pub mode: EmuMode,
|
||||
|
||||
window: Window,
|
||||
platform: Platform,
|
||||
|
||||
surface: Surface,
|
||||
device: Device,
|
||||
queue: Queue,
|
||||
surface_config: SurfaceConfiguration,
|
||||
render_pass: RenderPass,
|
||||
|
||||
texture: Texture,
|
||||
texture_size: Extent3d,
|
||||
texture_id: TextureId,
|
||||
}
|
||||
|
||||
impl GuiState {
|
||||
pub fn new(title: String) -> Self {
|
||||
impl Gui {
|
||||
pub fn new<T>(title: String, event_loop: &EventLoop<T>, cpu: &Cpu) -> Self {
|
||||
let window = build_window(event_loop).expect("build window");
|
||||
|
||||
let instance = Instance::new(Backends::PRIMARY);
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
|
||||
let adapter = request_adapter(&instance, &surface).expect("request adaptor");
|
||||
let (device, queue) = request_device(&adapter).expect("request device");
|
||||
let texture_format = surface.get_supported_formats(&adapter)[0]; // First is preferred
|
||||
|
||||
let surface_config = surface_config(&window, texture_format);
|
||||
surface.configure(&device, &surface_config);
|
||||
let platform = platform(&window);
|
||||
let mut render_pass = RenderPass::new(&device, texture_format, 1);
|
||||
|
||||
let texture_size = texture_size();
|
||||
let texture = create_texture(&device, texture_size);
|
||||
write_to_texture(&queue, &texture, emu::pixel_buf(&cpu), texture_size);
|
||||
|
||||
let view = texture.create_view(&TextureViewDescriptor::default());
|
||||
let texture_id = expose_texture_to_egui(&mut render_pass, &device, &view);
|
||||
|
||||
Self {
|
||||
should_quit: Default::default(),
|
||||
title,
|
||||
quit: Default::default(),
|
||||
mode: EmuMode::Running,
|
||||
|
||||
window,
|
||||
platform,
|
||||
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
surface_config,
|
||||
render_pass,
|
||||
|
||||
texture,
|
||||
texture_size,
|
||||
texture_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_quit(self: &Self, cpu: &Cpu, control_flow: &mut ControlFlow) {
|
||||
if self.should_quit {
|
||||
emu::save_and_exit(cpu, control_flow);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_redraw(self: &Self) {
|
||||
self.window.request_redraw();
|
||||
}
|
||||
|
||||
pub fn handle_event<T>(self: &mut Self, event: &Event<T>) {
|
||||
self.platform.handle_event(event);
|
||||
}
|
||||
|
||||
pub fn update_time(self: &mut Self, elapsed_seconds: f64) {
|
||||
self.platform.update_time(elapsed_seconds);
|
||||
}
|
||||
|
||||
pub fn resize(self: &mut Self, size: PhysicalSize<u32>) {
|
||||
// See: https://github.com/rust-windowing/winit/issues/208
|
||||
if size.width > 0 && size.height > 0 {
|
||||
self.surface_config.width = size.width;
|
||||
self.surface_config.height = size.height;
|
||||
self.surface.configure(&self.device, &self.surface_config);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint(self: &mut Self, cpu: &Cpu) {
|
||||
use wgpu::SurfaceError;
|
||||
|
||||
let data = emu::pixel_buf(cpu);
|
||||
write_to_texture(&self.queue, &self.texture, data, self.texture_size);
|
||||
|
||||
let output_frame = match self.surface.get_current_texture() {
|
||||
Ok(frame) => frame,
|
||||
Err(SurfaceError::Outdated) => return, // Occurs on minimization on Windows
|
||||
Err(e) => {
|
||||
eprintln!("Dropped frame with error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let output_view = output_frame
|
||||
.texture
|
||||
.create_view(&TextureViewDescriptor::default());
|
||||
|
||||
// Begin to draw Egui components
|
||||
self.platform.begin_frame();
|
||||
self.draw_egui(cpu, &self.platform.context(), self.texture_id);
|
||||
|
||||
// End the UI frame. We could now handle the output and draw the UI with the backend.
|
||||
let full_output = self.platform.end_frame(Some(&self.window));
|
||||
let paint_jobs = self.platform.context().tessellate(full_output.shapes);
|
||||
|
||||
let mut encoder = create_command_encoder(&self.device);
|
||||
let screen_descriptor = create_screen_descriptor(&self.window, &self.surface_config);
|
||||
let tdelta = full_output.textures_delta;
|
||||
// Upload all resources for the GPU.
|
||||
self.render_pass
|
||||
.add_textures(&self.device, &self.queue, &tdelta)
|
||||
.expect("add texture ok");
|
||||
self.render_pass
|
||||
.update_buffers(&self.device, &self.queue, &paint_jobs, &screen_descriptor);
|
||||
|
||||
// Record all render passes.
|
||||
self.render_pass
|
||||
.execute(
|
||||
&mut encoder,
|
||||
&output_view,
|
||||
&paint_jobs,
|
||||
&screen_descriptor,
|
||||
Some(Color::BLACK),
|
||||
)
|
||||
.expect("execute render pass");
|
||||
|
||||
// Submit the commands.
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
// Redraw egui
|
||||
output_frame.present();
|
||||
|
||||
self.render_pass
|
||||
.remove_textures(tdelta)
|
||||
.expect("remove texture delta");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn draw_egui(self: &mut Self, cpu: &Cpu, ctx: &Context, texture_id: TextureId) {
|
||||
use crate::{cpu, instruction, ppu};
|
||||
|
||||
fn selectable_text(ui: &mut egui::Ui, mut text: &str) -> egui::Response {
|
||||
ui.add(egui::TextEdit::multiline(&mut text).code_editor())
|
||||
}
|
||||
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Quit").clicked() {
|
||||
self.should_quit = true;
|
||||
}
|
||||
});
|
||||
|
||||
egui::Window::new(&self.title).show(ctx, |ui| {
|
||||
ui.image(
|
||||
texture_id,
|
||||
[GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE],
|
||||
);
|
||||
});
|
||||
|
||||
egui::Window::new("Disassembly").show(ctx, |ui| {
|
||||
selectable_text(ui, &instruction::dbg::tmp_disasm(cpu, 20));
|
||||
});
|
||||
|
||||
egui::Window::new("Settings").show(ctx, |ui| {
|
||||
egui::ComboBox::from_label("Emulation Mode")
|
||||
.selected_text(format!("{:?}", self.mode))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut self.mode, EmuMode::Running, "Running");
|
||||
ui.selectable_value(&mut self.mode, EmuMode::Stopped, "Stopped");
|
||||
ui.selectable_value(&mut self.mode, EmuMode::StepFrame, "Step Frame");
|
||||
ui.selectable_value(&mut self.mode, EmuMode::Step, "Step");
|
||||
})
|
||||
});
|
||||
|
||||
egui::Window::new("GB Info").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("CPU");
|
||||
|
||||
ui.monospace(format!("AF: {:#06X}", cpu::dbg::af(cpu)));
|
||||
ui.monospace(format!("BC: {:#06X}", cpu::dbg::bc(cpu)));
|
||||
ui.monospace(format!("DE: {:#06X}", cpu::dbg::de(cpu)));
|
||||
ui.monospace(format!("HL: {:#06X}", cpu::dbg::hl(cpu)));
|
||||
ui.add_space(10.0);
|
||||
ui.monospace(format!("SP: {:#06X}", cpu::dbg::sp(cpu)));
|
||||
ui.monospace(format!("PC: {:#06X}", cpu::dbg::pc(cpu)));
|
||||
});
|
||||
|
||||
ui.vertical(|ui| {
|
||||
let ppu = &cpu.bus.ppu;
|
||||
|
||||
ui.heading("PPU");
|
||||
|
||||
ui.monospace(format!("LY: {}", ppu::dbg::ly(ppu)));
|
||||
ui.horizontal(|ui| {
|
||||
ui.monospace(format!("SCX: {}", ppu::dbg::scx(ppu)));
|
||||
ui.monospace(format!("SCY: {}", ppu::dbg::scy(ppu)));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.monospace(format!("WX: {}", ppu::dbg::wx(ppu)));
|
||||
ui.monospace(format!("WY: {}", ppu::dbg::wy(ppu)));
|
||||
});
|
||||
|
||||
ui.monospace(format!(
|
||||
"Mode: {:?} {}",
|
||||
ppu::dbg::mode(ppu),
|
||||
ppu::dbg::dot(ppu)
|
||||
))
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
let _ = ui.selectable_label(cpu::dbg::ime(cpu), "IME");
|
||||
ui.horizontal(|ui| {
|
||||
let irq = cpu.int_request();
|
||||
|
||||
ui.label("IRQ:");
|
||||
let _ = ui.selectable_label(irq & 0b01 == 0x01, "VBlank");
|
||||
let _ = ui.selectable_label(irq >> 1 & 0x01 == 0x01, "LCD STAT");
|
||||
let _ = ui.selectable_label(irq >> 2 & 0x01 == 0x01, "Timer");
|
||||
let _ = ui.selectable_label(irq >> 3 & 0x01 == 0x01, "Serial");
|
||||
let _ = ui.selectable_label(irq >> 4 & 0x01 == 0x01, "Joypad");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let ie = cpu.int_enable();
|
||||
|
||||
// TODO: Reimplement this
|
||||
// let r_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'R');
|
||||
// let e_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'E');
|
||||
// let q_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'Q');
|
||||
|
||||
ui.label("IE:");
|
||||
// ui.add_space(q_len - (e_len - r_len));
|
||||
let _ = ui.selectable_label(ie & 0b01 == 0x01, "VBlank");
|
||||
let _ = ui.selectable_label(ie >> 1 & 0x01 == 0x01, "LCD STAT");
|
||||
let _ = ui.selectable_label(ie >> 2 & 0x01 == 0x01, "Timer");
|
||||
let _ = ui.selectable_label(ie >> 3 & 0x01 == 0x01, "Serial");
|
||||
let _ = ui.selectable_label(ie >> 4 & 0x01 == 0x01, "Joypad");
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let flags = cpu::dbg::flags(cpu);
|
||||
|
||||
let _ = ui.selectable_label((flags >> 7 & 0x01) == 0x01, "Zero");
|
||||
let _ = ui.selectable_label((flags >> 6 & 0x01) == 0x01, "Negative");
|
||||
let _ = ui.selectable_label((flags >> 5 & 0x01) == 0x01, "Half-Carry");
|
||||
let _ = ui.selectable_label((flags >> 4 & 0x01) == 0x01, "Carry");
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -61,8 +311,7 @@ pub fn unused_key() -> KeyboardInput {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> {
|
||||
use winit::dpi::PhysicalSize;
|
||||
fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> {
|
||||
use winit::window::WindowBuilder;
|
||||
|
||||
WindowBuilder::new()
|
||||
|
@ -77,38 +326,30 @@ pub fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> {
|
|||
.build(event_loop)
|
||||
}
|
||||
|
||||
pub fn create_surface(window: &Window) -> (Instance, Surface) {
|
||||
use wgpu::Backends;
|
||||
|
||||
let instance = Instance::new(Backends::PRIMARY);
|
||||
let surface = unsafe { instance.create_surface(window) };
|
||||
(instance, surface)
|
||||
}
|
||||
|
||||
pub fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> {
|
||||
fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> {
|
||||
use wgpu::{PowerPreference, RequestAdapterOptions};
|
||||
|
||||
pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
|
||||
power_preference: PowerPreference::HighPerformance,
|
||||
force_fallback_adapter: false, // TODO: What do I want to do with this?
|
||||
compatible_surface: Some(surface),
|
||||
force_fallback_adapter: false, // TODO: What do I want to do with this?
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> {
|
||||
fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> {
|
||||
use wgpu::{DeviceDescriptor, Features, Limits};
|
||||
|
||||
pollster::block_on(adapter.request_device(
|
||||
&DeviceDescriptor {
|
||||
label: None,
|
||||
features: Features::default(),
|
||||
limits: Limits::default(),
|
||||
label: None,
|
||||
},
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn surface_config(window: &Window, format: TextureFormat) -> SurfaceConfiguration {
|
||||
fn surface_config(window: &Window, format: TextureFormat) -> SurfaceConfiguration {
|
||||
use wgpu::PresentMode;
|
||||
|
||||
let size = window.inner_size();
|
||||
|
@ -121,7 +362,7 @@ pub fn surface_config(window: &Window, format: TextureFormat) -> SurfaceConfigur
|
|||
}
|
||||
}
|
||||
|
||||
pub fn platform_desc(window: &Window) -> Platform {
|
||||
fn platform(window: &Window) -> Platform {
|
||||
use egui::FontDefinitions;
|
||||
use egui_winit_platform::PlatformDescriptor;
|
||||
|
||||
|
@ -135,7 +376,7 @@ pub fn platform_desc(window: &Window) -> Platform {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn texture_size() -> Extent3d {
|
||||
fn texture_size() -> Extent3d {
|
||||
Extent3d {
|
||||
width: GB_WIDTH as u32,
|
||||
height: GB_HEIGHT as u32,
|
||||
|
@ -143,7 +384,7 @@ pub fn texture_size() -> Extent3d {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn create_texture(device: &Device, size: Extent3d) -> Texture {
|
||||
fn create_texture(device: &Device, size: Extent3d) -> Texture {
|
||||
use wgpu::{TextureDescriptor, TextureDimension};
|
||||
|
||||
device.create_texture(&TextureDescriptor {
|
||||
|
@ -158,7 +399,7 @@ pub fn create_texture(device: &Device, size: Extent3d) -> Texture {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_to_texture(
|
||||
fn write_to_texture(
|
||||
queue: &Queue,
|
||||
texture: &Texture,
|
||||
data: &[u8; GB_WIDTH * 4 * GB_HEIGHT],
|
||||
|
@ -184,7 +425,7 @@ pub fn write_to_texture(
|
|||
);
|
||||
}
|
||||
|
||||
pub fn expose_texture_to_egui(
|
||||
fn expose_texture_to_egui(
|
||||
render_pass: &mut RenderPass,
|
||||
device: &Device,
|
||||
view: &TextureView,
|
||||
|
@ -193,14 +434,7 @@ pub fn expose_texture_to_egui(
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_view(frame: &SurfaceTexture) -> TextureView {
|
||||
use wgpu::TextureViewDescriptor;
|
||||
|
||||
frame.texture.create_view(&TextureViewDescriptor::default())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_command_encoder(device: &Device) -> CommandEncoder {
|
||||
fn create_command_encoder(device: &Device) -> CommandEncoder {
|
||||
use wgpu::CommandEncoderDescriptor;
|
||||
|
||||
device.create_command_encoder(&CommandEncoderDescriptor {
|
||||
|
@ -209,10 +443,7 @@ pub fn create_command_encoder(device: &Device) -> CommandEncoder {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn create_screen_descriptor(
|
||||
window: &Window,
|
||||
config: &SurfaceConfiguration,
|
||||
) -> ScreenDescriptor {
|
||||
fn create_screen_descriptor(window: &Window, config: &SurfaceConfiguration) -> ScreenDescriptor {
|
||||
ScreenDescriptor {
|
||||
physical_width: config.width,
|
||||
physical_height: config.height,
|
||||
|
@ -220,136 +451,6 @@ pub fn create_screen_descriptor(
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn execute_render_pass(
|
||||
render_pass: &mut RenderPass,
|
||||
encoder: &mut CommandEncoder,
|
||||
view: &TextureView,
|
||||
jobs: Vec<ClippedPrimitive>,
|
||||
descriptor: &ScreenDescriptor,
|
||||
) -> Result<(), BackendError> {
|
||||
render_pass.execute(encoder, view, &jobs, descriptor, Some(wgpu::Color::BLACK))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn draw_egui(cpu: &Cpu, app: &mut GuiState, ctx: &Context, texture_id: TextureId) {
|
||||
use crate::{cpu, instruction, ppu};
|
||||
|
||||
fn selectable_text(ui: &mut egui::Ui, mut text: &str) -> egui::Response {
|
||||
ui.add(egui::TextEdit::multiline(&mut text).code_editor())
|
||||
}
|
||||
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Quit").clicked() {
|
||||
app.quit = true;
|
||||
}
|
||||
});
|
||||
|
||||
egui::Window::new(&app.title).show(ctx, |ui| {
|
||||
ui.image(
|
||||
texture_id,
|
||||
[GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE],
|
||||
);
|
||||
});
|
||||
|
||||
egui::Window::new("Disassembly").show(ctx, |ui| {
|
||||
selectable_text(ui, &instruction::dbg::tmp_disasm(cpu, 20));
|
||||
});
|
||||
|
||||
egui::Window::new("Settings").show(ctx, |ui| {
|
||||
egui::ComboBox::from_label("Emulation Mode")
|
||||
.selected_text(format!("{:?}", app.mode))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut app.mode, EmuMode::Running, "Running");
|
||||
ui.selectable_value(&mut app.mode, EmuMode::Stopped, "Stopped");
|
||||
ui.selectable_value(&mut app.mode, EmuMode::StepFrame, "Step Frame");
|
||||
ui.selectable_value(&mut app.mode, EmuMode::Step, "Step");
|
||||
})
|
||||
});
|
||||
|
||||
egui::Window::new("GB Info").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("CPU");
|
||||
|
||||
ui.monospace(format!("AF: {:#06X}", cpu::dbg::af(cpu)));
|
||||
ui.monospace(format!("BC: {:#06X}", cpu::dbg::bc(cpu)));
|
||||
ui.monospace(format!("DE: {:#06X}", cpu::dbg::de(cpu)));
|
||||
ui.monospace(format!("HL: {:#06X}", cpu::dbg::hl(cpu)));
|
||||
ui.add_space(10.0);
|
||||
ui.monospace(format!("SP: {:#06X}", cpu::dbg::sp(cpu)));
|
||||
ui.monospace(format!("PC: {:#06X}", cpu::dbg::pc(cpu)));
|
||||
});
|
||||
|
||||
ui.vertical(|ui| {
|
||||
let ppu = &cpu.bus.ppu;
|
||||
|
||||
ui.heading("PPU");
|
||||
|
||||
ui.monospace(format!("LY: {}", ppu::dbg::ly(ppu)));
|
||||
ui.horizontal(|ui| {
|
||||
ui.monospace(format!("SCX: {}", ppu::dbg::scx(ppu)));
|
||||
ui.monospace(format!("SCY: {}", ppu::dbg::scy(ppu)));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.monospace(format!("WX: {}", ppu::dbg::wx(ppu)));
|
||||
ui.monospace(format!("WY: {}", ppu::dbg::wy(ppu)));
|
||||
});
|
||||
|
||||
ui.monospace(format!(
|
||||
"Mode: {:?} {}",
|
||||
ppu::dbg::mode(ppu),
|
||||
ppu::dbg::dot(ppu)
|
||||
))
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
let _ = ui.selectable_label(cpu::dbg::ime(cpu), "IME");
|
||||
ui.horizontal(|ui| {
|
||||
let irq = cpu.int_request();
|
||||
|
||||
ui.label("IRQ:");
|
||||
let _ = ui.selectable_label(irq & 0b01 == 0x01, "VBlank");
|
||||
let _ = ui.selectable_label(irq >> 1 & 0x01 == 0x01, "LCD STAT");
|
||||
let _ = ui.selectable_label(irq >> 2 & 0x01 == 0x01, "Timer");
|
||||
let _ = ui.selectable_label(irq >> 3 & 0x01 == 0x01, "Serial");
|
||||
let _ = ui.selectable_label(irq >> 4 & 0x01 == 0x01, "Joypad");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let ie = cpu.int_enable();
|
||||
|
||||
// TODO: Reimplement this
|
||||
// let r_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'R');
|
||||
// let e_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'E');
|
||||
// let q_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'Q');
|
||||
|
||||
ui.label("IE:");
|
||||
// ui.add_space(q_len - (e_len - r_len));
|
||||
let _ = ui.selectable_label(ie & 0b01 == 0x01, "VBlank");
|
||||
let _ = ui.selectable_label(ie >> 1 & 0x01 == 0x01, "LCD STAT");
|
||||
let _ = ui.selectable_label(ie >> 2 & 0x01 == 0x01, "Timer");
|
||||
let _ = ui.selectable_label(ie >> 3 & 0x01 == 0x01, "Serial");
|
||||
let _ = ui.selectable_label(ie >> 4 & 0x01 == 0x01, "Joypad");
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let flags = cpu::dbg::flags(cpu);
|
||||
|
||||
let _ = ui.selectable_label((flags >> 7 & 0x01) == 0x01, "Zero");
|
||||
let _ = ui.selectable_label((flags >> 6 & 0x01) == 0x01, "Negative");
|
||||
let _ = ui.selectable_label((flags >> 5 & 0x01) == 0x01, "Half-Carry");
|
||||
let _ = ui.selectable_label((flags >> 4 & 0x01) == 0x01, "Carry");
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub mod kbd {
|
||||
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
|
||||
|
||||
|
|
102
src/main.rs
102
src/main.rs
|
@ -1,14 +1,11 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use clap::{crate_authors, crate_description, crate_name, crate_version, Arg, Command};
|
||||
use egui_wgpu_backend::RenderPass;
|
||||
use gb::gui::EmuMode;
|
||||
use gb::gui::{EmuMode, Gui};
|
||||
use gb::{emu, gui};
|
||||
use gilrs::Gilrs;
|
||||
use gui::GuiState;
|
||||
use rodio::{OutputStream, Sink};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use wgpu::TextureViewDescriptor;
|
||||
use winit::event::{Event, WindowEvent};
|
||||
use winit::event_loop::{EventLoop, EventLoopBuilder};
|
||||
|
||||
|
@ -47,22 +44,7 @@ fn main() {
|
|||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
// --Here lies a lot of winit + wgpu Boilerplate--
|
||||
let event_loop: EventLoop<Event<()>> = EventLoopBuilder::with_user_event().build();
|
||||
let window = gui::build_window(&event_loop).expect("build window");
|
||||
|
||||
let (instance, surface) = gui::create_surface(&window);
|
||||
let adapter = gui::request_adapter(&instance, &surface).expect("request adaptor");
|
||||
let (device, queue) = gui::request_device(&adapter).expect("request device");
|
||||
let format = surface.get_supported_formats(&adapter)[0]; // First is preferred
|
||||
|
||||
let mut config = gui::surface_config(&window, format);
|
||||
surface.configure(&device, &config);
|
||||
let mut platform = gui::platform_desc(&window);
|
||||
let mut render_pass = RenderPass::new(&device, format, 1);
|
||||
|
||||
// We interrupt your boiler plate to initialize the emulator so that
|
||||
// we can copy it's empty pixel buffer to the GPU
|
||||
// Init CPU
|
||||
let mut cpu = match m.value_of("boot") {
|
||||
Some(path) => {
|
||||
tracing::info!("User-provided boot ROM");
|
||||
|
@ -74,22 +56,12 @@ fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
// Set up the wgpu (and then EGUI) texture we'll be working with.
|
||||
let texture_size = gui::texture_size();
|
||||
let texture = gui::create_texture(&device, texture_size);
|
||||
gui::write_to_texture(&queue, &texture, emu::pixel_buf(&cpu), texture_size);
|
||||
|
||||
let view = texture.create_view(&TextureViewDescriptor::default());
|
||||
let texture_id = gui::expose_texture_to_egui(&mut render_pass, &device, &view);
|
||||
|
||||
// Load ROM if filepath was provided
|
||||
if let Some(path) = m.value_of("rom") {
|
||||
tracing::info!("User-provided cartridge ROM");
|
||||
emu::read_game_rom(&mut cpu, path).expect("read game rom from path");
|
||||
}
|
||||
|
||||
emu::load_save(&mut cpu);
|
||||
|
||||
let rom_title = emu::rom_title(&cpu).to_string();
|
||||
|
||||
tracing::info!("Initialize Gamepad");
|
||||
|
@ -116,20 +88,20 @@ fn main() {
|
|||
}
|
||||
|
||||
// Set up state for the Immediate-mode GUI
|
||||
let mut app = GuiState::new(rom_title);
|
||||
let mut last_key = gui::unused_key();
|
||||
let event_loop: EventLoop<Event<()>> = EventLoopBuilder::with_user_event().build();
|
||||
|
||||
let mut app = Gui::new(rom_title, &event_loop, &cpu);
|
||||
let mut last_key = gui::unused_key(); // TODO: Fix this awful impl
|
||||
|
||||
// used for egui animations
|
||||
let start_time = Instant::now();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
platform.handle_event(&event);
|
||||
app.handle_event(&event);
|
||||
|
||||
match event {
|
||||
Event::MainEventsCleared => {
|
||||
if app.quit {
|
||||
emu::save_and_exit(&cpu, control_flow);
|
||||
}
|
||||
app.maybe_quit(&cpu, control_flow);
|
||||
|
||||
match app.mode {
|
||||
EmuMode::Running => emu::run_frame(&mut cpu, &mut gamepad, last_key),
|
||||
|
@ -145,64 +117,14 @@ fn main() {
|
|||
// Input has been consumed, reset it
|
||||
last_key = gui::unused_key();
|
||||
|
||||
window.request_redraw();
|
||||
app.request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(..) => {
|
||||
platform.update_time(start_time.elapsed().as_secs_f64());
|
||||
|
||||
let data = emu::pixel_buf(&cpu);
|
||||
gui::write_to_texture(&queue, &texture, data, texture_size);
|
||||
|
||||
let output_frame = match surface.get_current_texture() {
|
||||
Ok(frame) => frame,
|
||||
Err(e) => {
|
||||
eprintln!("Dropped frame with error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let output_view = gui::create_view(&output_frame);
|
||||
|
||||
// Begin to draw Egui components
|
||||
platform.begin_frame();
|
||||
gui::draw_egui(&cpu, &mut app, &platform.context(), texture_id);
|
||||
// End the UI frame. We could now handle the output and draw the UI with the backend.
|
||||
let full_output = platform.end_frame(Some(&window));
|
||||
let paint_jobs = platform.context().tessellate(full_output.shapes);
|
||||
|
||||
let mut encoder = gui::create_command_encoder(&device);
|
||||
let screen_descriptor = gui::create_screen_descriptor(&window, &config);
|
||||
let tdelta = full_output.textures_delta;
|
||||
// Upload all resources for the GPU.
|
||||
render_pass
|
||||
.add_textures(&device, &queue, &tdelta)
|
||||
.expect("add texture ok");
|
||||
render_pass.update_buffers(&device, &queue, &paint_jobs, &screen_descriptor);
|
||||
|
||||
// Record all render passes.
|
||||
gui::execute_render_pass(
|
||||
&mut render_pass,
|
||||
&mut encoder,
|
||||
&output_view,
|
||||
paint_jobs,
|
||||
&screen_descriptor,
|
||||
)
|
||||
.expect("record render passes");
|
||||
|
||||
// Submit the commands.
|
||||
queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
// Redraw egui
|
||||
output_frame.present();
|
||||
app.update_time(start_time.elapsed().as_secs_f64());
|
||||
}
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::Resized(size) => {
|
||||
config.width = size.width;
|
||||
config.height = size.height;
|
||||
surface.configure(&device, &config);
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
emu::save_and_exit(&cpu, control_flow);
|
||||
}
|
||||
WindowEvent::Resized(size) => app.resize(size),
|
||||
WindowEvent::CloseRequested => emu::save_and_exit(&cpu, control_flow),
|
||||
WindowEvent::KeyboardInput { input, .. } => last_key = input,
|
||||
_ => {}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue