From 544990c58f528d130189db622448427b266b4088 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Mon, 11 Nov 2024 00:50:57 -0500 Subject: [PATCH 01/21] Support running two sims at once --- shrooms-vb-core | 2 +- src/app.rs | 57 +++++++----- src/app/game.rs | 78 ++++++++-------- src/app/input.rs | 51 ++++++----- src/controller.rs | 38 ++++---- src/emulator.rs | 122 +++++++++++++++++++------- src/{ => emulator}/shrooms_vb_core.rs | 56 ++++++++---- src/input.rs | 81 ++++++++++------- src/main.rs | 1 - 9 files changed, 302 insertions(+), 184 deletions(-) rename src/{ => emulator}/shrooms_vb_core.rs (83%) diff --git a/shrooms-vb-core b/shrooms-vb-core index ae22c95..b4b9131 160000 --- a/shrooms-vb-core +++ b/shrooms-vb-core @@ -1 +1 @@ -Subproject commit ae22c95dbee3d0b338168bfdf98143e6eddc6c70 +Subproject commit b4b9131f3976388b096343725fce1bdf35f4e3df diff --git a/src/app.rs b/src/app.rs index a5d62ac..91b9381 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,6 +5,7 @@ use std::{ }; use game::GameWindow; +use input::InputWindow; use winit::{ application::ApplicationHandler, event::{Event, WindowEvent}, @@ -14,7 +15,7 @@ use winit::{ use crate::{ controller::ControllerState, - emulator::{EmulatorClient, EmulatorCommand}, + emulator::{EmulatorClient, EmulatorCommand, SimId}, input::InputMapper, }; @@ -28,6 +29,7 @@ pub struct App { input_mapper: Arc>, controller: ControllerState, proxy: EventLoopProxy, + player_2_window: Option, } impl App { @@ -40,19 +42,19 @@ impl App { input_mapper, controller, proxy, + player_2_window: None, } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let mut window = GameWindow::new( + let window = GameWindow::new( event_loop, + SimId::Player1, self.client.clone(), - self.input_mapper.clone(), self.proxy.clone(), ); - window.init(); self.windows.insert(window.id(), Box::new(window)); } @@ -63,9 +65,9 @@ impl ApplicationHandler for App { event: WindowEvent, ) { if let WindowEvent::KeyboardInput { event, .. } = &event { - if self.controller.key_event(event) { + if let Some((sim_id, pressed)) = self.controller.key_event(event) { self.client - .send_command(EmulatorCommand::SetKeys(self.controller.pressed())); + .send_command(EmulatorCommand::SetKeys(sim_id, pressed)); } } let Some(window) = self.windows.get_mut(&window_id) else { @@ -74,13 +76,30 @@ impl ApplicationHandler for App { window.handle_event(event_loop, &Event::WindowEvent { window_id, event }); } - fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) { + fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { match event { - UserEvent::OpenWindow(mut window) => { - window.init(); - self.windows.insert(window.id(), window); + UserEvent::OpenInputWindow => { + let window = + InputWindow::new(event_loop, self.input_mapper.clone(), self.proxy.clone()); + self.windows.insert(window.id(), Box::new(window)); } - UserEvent::CloseWindow(window_id) => { + UserEvent::OpenPlayer2Window => { + if self.player_2_window.is_some() { + return; + } + let window = GameWindow::new( + event_loop, + SimId::Player2, + self.client.clone(), + self.proxy.clone(), + ); + self.player_2_window = Some(window.id()); + self.windows.insert(window.id(), Box::new(window)); + } + UserEvent::Close(window_id) => { + if self.player_2_window == Some(window_id) { + self.player_2_window.take(); + } self.windows.remove(&window_id); } } @@ -112,20 +131,12 @@ impl ApplicationHandler for App { pub trait AppWindow { fn id(&self) -> WindowId; - fn init(&mut self); fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event); } +#[derive(Debug)] pub enum UserEvent { - OpenWindow(Box), - CloseWindow(WindowId), -} - -impl Debug for UserEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::OpenWindow(window) => f.debug_tuple("OpenWindow").field(&window.id()).finish(), - Self::CloseWindow(window_id) => f.debug_tuple("CloseWindow").field(window_id).finish(), - } - } + OpenInputWindow, + OpenPlayer2Window, + Close(WindowId), } diff --git a/src/app/game.rs b/src/app/game.rs index efbeae0..ad3f6af 100644 --- a/src/app/game.rs +++ b/src/app/game.rs @@ -1,7 +1,4 @@ -use std::{ - sync::{Arc, RwLock}, - time::Instant, -}; +use std::{sync::Arc, time::Instant}; use wgpu::util::DeviceExt as _; use winit::{ dpi::LogicalSize, @@ -11,24 +8,22 @@ use winit::{ }; use crate::{ - emulator::{EmulatorClient, EmulatorCommand}, - input::InputMapper, + emulator::{EmulatorClient, EmulatorCommand, SimId}, renderer::GameRenderer, }; use super::{ common::{ImguiState, WindowState, WindowStateBuilder}, - input::InputWindow, AppWindow, UserEvent, }; pub struct GameWindow { window: WindowState, - imgui: Option, + imgui: ImguiState, pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, + sim_id: SimId, client: EmulatorClient, - input_mapper: Arc>, proxy: EventLoopProxy, paused_due_to_minimize: bool, } @@ -36,21 +31,29 @@ pub struct GameWindow { impl GameWindow { pub fn new( event_loop: &ActiveEventLoop, + sim_id: SimId, client: EmulatorClient, - input_mapper: Arc>, proxy: EventLoopProxy, ) -> Self { + let title = if sim_id == SimId::Player2 { + "Shrooms VB (Player 2)" + } else { + "Shrooms VB" + }; let window = WindowStateBuilder::new(event_loop) - .with_title("Shrooms VB") + .with_title(title) .with_inner_size(LogicalSize::new(384, 244)) .build(); let device = &window.device; let eyes = Arc::new(GameRenderer::create_texture(device, "eye")); - client.send_command(EmulatorCommand::SetRenderer(GameRenderer { - queue: window.queue.clone(), - eyes: eyes.clone(), - })); + client.send_command(EmulatorCommand::SetRenderer( + sim_id, + GameRenderer { + queue: window.queue.clone(), + eyes: eyes.clone(), + }, + )); let eyes = eyes.create_view(&wgpu::TextureViewDescriptor::default()); let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); let colors = Colors { @@ -158,13 +161,15 @@ impl GameWindow { cache: None, }); + let imgui = ImguiState::new(&window); + Self { window, - imgui: None, + imgui, pipeline: render_pipeline, bind_group, + sim_id, client, - input_mapper, proxy, paused_due_to_minimize: false, } @@ -172,7 +177,7 @@ impl GameWindow { fn draw(&mut self, event_loop: &ActiveEventLoop) { let window = &mut self.window; - let imgui = self.imgui.as_mut().unwrap(); + let imgui = &mut self.imgui; let mut context = imgui.context.lock().unwrap(); let mut new_size = None; @@ -204,7 +209,8 @@ impl GameWindow { .show_open_single_file() .unwrap(); if let Some(path) = rom { - self.client.send_command(EmulatorCommand::LoadGame(path)); + self.client + .send_command(EmulatorCommand::LoadGame(self.sim_id, path)); } } if ui.menu_item("Quit") { @@ -240,14 +246,14 @@ impl GameWindow { }); ui.menu("Input", || { if ui.menu_item("Bind Inputs") { - let input_window = Box::new(InputWindow::new( - event_loop, - self.input_mapper.clone(), - self.proxy.clone(), - )); - self.proxy - .send_event(UserEvent::OpenWindow(input_window)) - .unwrap(); + self.proxy.send_event(UserEvent::OpenInputWindow).unwrap(); + } + }); + ui.menu("Multiplayer", || { + if self.sim_id == SimId::Player1 && ui.menu_item("Open Player 2") { + self.client + .send_command(EmulatorCommand::StartSecondSim(None)); + self.proxy.send_event(UserEvent::OpenPlayer2Window).unwrap(); } }); }); @@ -321,11 +327,6 @@ impl AppWindow for GameWindow { self.window.window.id() } - fn init(&mut self) { - self.imgui = Some(ImguiState::new(&self.window)); - self.window.window.request_redraw(); - } - fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event) { match event { Event::WindowEvent { event, .. } => match event { @@ -341,7 +342,14 @@ impl AppWindow for GameWindow { self.paused_due_to_minimize = false; } } - WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::CloseRequested => { + if self.sim_id == SimId::Player2 { + self.client.send_command(EmulatorCommand::StopSecondSim); + self.proxy.send_event(UserEvent::Close(self.id())).unwrap(); + } else { + event_loop.exit(); + } + } WindowEvent::RedrawRequested => self.draw(event_loop), _ => (), }, @@ -351,9 +359,7 @@ impl AppWindow for GameWindow { _ => (), } let window = &self.window; - let Some(imgui) = self.imgui.as_mut() else { - return; - }; + let imgui = &mut self.imgui; let mut context = imgui.context.lock().unwrap(); imgui .platform diff --git a/src/app/input.rs b/src/app/input.rs index d5ffe7e..b39f8b1 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -7,10 +7,12 @@ use winit::{ dpi::LogicalSize, event::{Event, KeyEvent, WindowEvent}, event_loop::{ActiveEventLoop, EventLoopProxy}, - platform::modifier_supplement::KeyEventExtModifierSupplement, }; -use crate::{input::InputMapper, shrooms_vb_core::VBKey}; +use crate::{ + emulator::{SimId, VBKey}, + input::InputMapper, +}; use super::{ common::{ImguiState, UiExt, WindowState, WindowStateBuilder}, @@ -19,10 +21,10 @@ use super::{ pub struct InputWindow { window: WindowState, - imgui: Option, + imgui: ImguiState, input_mapper: Arc>, proxy: EventLoopProxy, - now_binding: Option, + now_binding: Option<(SimId, VBKey)>, } const KEY_NAMES: [(VBKey, &str); 14] = [ @@ -52,9 +54,10 @@ impl InputWindow { .with_title("Bind Inputs") .with_inner_size(LogicalSize::new(600, 400)) .build(); + let imgui = ImguiState::new(&window); Self { window, - imgui: None, + imgui, input_mapper, now_binding: None, proxy, @@ -63,7 +66,7 @@ impl InputWindow { fn draw(&mut self) { let window = &mut self.window; - let imgui = self.imgui.as_mut().unwrap(); + let imgui = &mut self.imgui; let mut context = imgui.context.lock().unwrap(); let now = Instant::now(); @@ -85,11 +88,11 @@ impl InputWindow { .expect("Failed to prepare frame"); let ui = context.new_frame(); - let mut render_key_bindings = || { + let mut render_key_bindings = |sim_id: SimId| { if let Some(table) = ui.begin_table("controls", 2) { let binding_names = { let mapper = self.input_mapper.read().unwrap(); - mapper.binding_names() + mapper.binding_names(&sim_id) }; ui.table_next_row(); @@ -100,20 +103,20 @@ impl InputWindow { ui.group(|| { ui.right_align_text(name, space * 0.20); ui.same_line(); - let label_text = if self.now_binding == Some(key) { + let label_text = if self.now_binding == Some((sim_id, key)) { "Press any input" } else { binding.unwrap_or("") }; let label = format!("{}##{}", label_text, name); if ui.button_with_size(label, [space * 0.60, 0.0]) { - self.now_binding = Some(key); + self.now_binding = Some((sim_id, key)); } }); ui.same_line(); if ui.button(format!("Clear##{name}")) { let mut mapper = self.input_mapper.write().unwrap(); - mapper.clear_binding(key); + mapper.clear_binding(sim_id, key); } } @@ -124,7 +127,11 @@ impl InputWindow { if let Some(window) = ui.fullscreen_window() { if let Some(tabs) = ui.tab_bar("tabs") { if let Some(tab) = ui.tab_item("Player 1") { - render_key_bindings(); + render_key_bindings(SimId::Player1); + tab.end(); + } + if let Some(tab) = ui.tab_item("Player 2") { + render_key_bindings(SimId::Player2); tab.end(); } tabs.end(); @@ -175,11 +182,11 @@ impl InputWindow { if !event.state.is_pressed() { return; } - let Some(vb) = self.now_binding.take() else { + let Some((sim_id, vb)) = self.now_binding.take() else { return; }; let mut mapper = self.input_mapper.write().unwrap(); - mapper.bind_key(vb, event.key_without_modifiers()); + mapper.bind_key(sim_id, vb, event.physical_key); } } @@ -188,19 +195,13 @@ impl AppWindow for InputWindow { self.window.window.id() } - fn init(&mut self) { - self.imgui = Some(ImguiState::new(&self.window)); - self.window.window.request_redraw(); - } - fn handle_event(&mut self, _: &ActiveEventLoop, event: &Event) { match event { Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(size) => self.window.handle_resize(size), - WindowEvent::CloseRequested => self - .proxy - .send_event(UserEvent::CloseWindow(self.id())) - .unwrap(), + WindowEvent::CloseRequested => { + self.proxy.send_event(UserEvent::Close(self.id())).unwrap() + } WindowEvent::KeyboardInput { event, .. } => self.try_bind_key(event), WindowEvent::RedrawRequested => self.draw(), _ => (), @@ -212,9 +213,7 @@ impl AppWindow for InputWindow { } let window = &self.window; - let Some(imgui) = self.imgui.as_mut() else { - return; - }; + let imgui = &mut self.imgui; let mut context = imgui.context.lock().unwrap(); imgui .platform diff --git a/src/controller.rs b/src/controller.rs index 70861ab..d10a1ab 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -2,48 +2,46 @@ use std::sync::{Arc, RwLock}; use winit::event::{ElementState, KeyEvent}; -use crate::{input::InputMapper, shrooms_vb_core::VBKey}; +use crate::{ + emulator::{SimId, VBKey}, + input::InputMapper, +}; pub struct ControllerState { input_mapper: Arc>, - pressed: VBKey, + pressed: Vec, } impl ControllerState { pub fn new(input_mapper: Arc>) -> Self { Self { input_mapper, - pressed: VBKey::SGN, + pressed: vec![VBKey::SGN; 2], } } - pub fn pressed(&self) -> VBKey { - self.pressed - } - - pub fn key_event(&mut self, event: &KeyEvent) -> bool { - let Some(input) = self.key_event_to_input(event) else { - return false; - }; + pub fn key_event(&mut self, event: &KeyEvent) -> Option<(SimId, VBKey)> { + let (sim_id, input) = self.key_event_to_input(event)?; + let pressed = &mut self.pressed[sim_id.to_index()]; match event.state { ElementState::Pressed => { - if self.pressed.contains(input) { - return false; + if pressed.contains(input) { + return None; } - self.pressed.insert(input); - true + pressed.insert(input); + Some((sim_id, *pressed)) } ElementState::Released => { - if !self.pressed.contains(input) { - return false; + if !pressed.contains(input) { + return None; } - self.pressed.remove(input); - true + pressed.remove(input); + Some((sim_id, *pressed)) } } } - fn key_event_to_input(&self, event: &KeyEvent) -> Option { + fn key_event_to_input(&self, event: &KeyEvent) -> Option<(SimId, VBKey)> { let mapper = self.input_mapper.read().unwrap(); mapper.key_event(event) } diff --git a/src/emulator.rs b/src/emulator.rs index 38fdab6..4bb8386 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, fs, path::{Path, PathBuf}, sync::{ @@ -10,11 +11,11 @@ use std::{ use anyhow::Result; -use crate::{ - audio::Audio, - renderer::GameRenderer, - shrooms_vb_core::{CoreVB, VBKey}, -}; +use crate::{audio::Audio, renderer::GameRenderer}; +use shrooms_vb_core::Sim; +pub use shrooms_vb_core::VBKey; + +mod shrooms_vb_core; pub struct EmulatorBuilder { rom: Option, @@ -23,6 +24,20 @@ pub struct EmulatorBuilder { has_game: Arc, } +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] +pub enum SimId { + Player1, + Player2, +} +impl SimId { + pub fn to_index(self) -> usize { + match self { + Self::Player1 => 0, + Self::Player2 => 1, + } + } +} + impl EmulatorBuilder { pub fn new() -> (Self, EmulatorClient) { let (queue, commands) = mpsc::channel(); @@ -50,17 +65,17 @@ impl EmulatorBuilder { pub fn build(self) -> Result { let mut emulator = Emulator::new(self.commands, self.running, self.has_game)?; if let Some(path) = self.rom { - emulator.load_rom(&path)?; + emulator.load_rom_from_file(SimId::Player1, &path)?; } Ok(emulator) } } pub struct Emulator { - sim: CoreVB, + sims: Vec, audio: Audio, commands: mpsc::Receiver, - renderer: Option, + renderers: HashMap, running: Arc, has_game: Arc, } @@ -72,24 +87,50 @@ impl Emulator { has_game: Arc, ) -> Result { Ok(Self { - sim: CoreVB::new(), + sims: vec![], audio: Audio::init()?, commands, - renderer: None, + renderers: HashMap::new(), running, has_game, }) } - pub fn load_rom(&mut self, path: &Path) -> Result<()> { + pub fn load_rom_from_file(&mut self, sim_id: SimId, path: &Path) -> Result<()> { let bytes = fs::read(path)?; - self.sim.reset(); - self.sim.load_rom(bytes)?; + self.load_rom(sim_id, bytes)?; + Ok(()) + } + + pub fn start_second_sim(&mut self, rom: Option) -> Result<()> { + let bytes = if let Some(path) = rom { + fs::read(path)? + } else if let Some(rom) = self.sims.first().and_then(|s| s.clone_rom()) { + rom + } else { + return Ok(()); + }; + self.load_rom(SimId::Player2, bytes)?; + Ok(()) + } + + fn load_rom(&mut self, sim_id: SimId, bytes: Vec) -> Result<()> { + while self.sims.len() <= sim_id.to_index() { + self.sims.push(Sim::new()); + } + let sim = &mut self.sims[sim_id.to_index()]; + sim.reset(); + sim.load_rom(bytes)?; self.has_game.store(true, Ordering::Release); self.running.store(true, Ordering::Release); Ok(()) } + pub fn stop_second_sim(&mut self) { + self.renderers.remove(&SimId::Player2); + self.sims.truncate(1); + } + pub fn run(&mut self) { let mut eye_contents = vec![0u8; 384 * 224 * 2]; let mut audio_samples = vec![]; @@ -97,15 +138,20 @@ impl Emulator { let mut idle = true; if self.running.load(Ordering::Acquire) { idle = false; - self.sim.emulate_frame(); + Sim::emulate_many(&mut self.sims); } - if let Some(renderer) = &mut self.renderer { - if self.sim.read_pixels(&mut eye_contents) { - idle = false; - renderer.render(&eye_contents); + for (sim_id, renderer) in self.renderers.iter_mut() { + if let Some(sim) = self.sims.get_mut(sim_id.to_index()) { + if sim.read_pixels(&mut eye_contents) { + idle = false; + renderer.render(&eye_contents); + } } } - self.sim.read_samples(&mut audio_samples); + let weight = 1.0 / self.sims.len() as f32; + for sim in self.sims.iter_mut() { + sim.read_samples(&mut audio_samples, weight); + } if !audio_samples.is_empty() { idle = false; self.audio.update(&audio_samples); @@ -137,14 +183,22 @@ impl Emulator { fn handle_command(&mut self, command: EmulatorCommand) { match command { - EmulatorCommand::SetRenderer(renderer) => { - self.renderer = Some(renderer); + EmulatorCommand::SetRenderer(sim_id, renderer) => { + self.renderers.insert(sim_id, renderer); } - EmulatorCommand::LoadGame(path) => { - if let Err(error) = self.load_rom(&path) { + EmulatorCommand::LoadGame(sim_id, path) => { + if let Err(error) = self.load_rom_from_file(sim_id, &path) { eprintln!("error loading rom: {}", error); } } + EmulatorCommand::StartSecondSim(path) => { + if let Err(error) = self.start_second_sim(path) { + eprintln!("error starting second sim: {}", error); + } + } + EmulatorCommand::StopSecondSim => { + self.stop_second_sim(); + } EmulatorCommand::Pause => { self.running.store(false, Ordering::Release); } @@ -154,11 +208,17 @@ impl Emulator { } } EmulatorCommand::Reset => { - self.sim.reset(); - self.running.store(true, Ordering::Release); + for sim in self.sims.iter_mut() { + sim.reset(); + } + if !self.sims.is_empty() { + self.running.store(true, Ordering::Release); + } } - EmulatorCommand::SetKeys(keys) => { - self.sim.set_keys(keys); + EmulatorCommand::SetKeys(sim_id, keys) => { + if let Some(sim) = self.sims.get_mut(sim_id.to_index()) { + sim.set_keys(keys); + } } } } @@ -166,12 +226,14 @@ impl Emulator { #[derive(Debug)] pub enum EmulatorCommand { - SetRenderer(GameRenderer), - LoadGame(PathBuf), + SetRenderer(SimId, GameRenderer), + LoadGame(SimId, PathBuf), + StartSecondSim(Option), + StopSecondSim, Pause, Resume, Reset, - SetKeys(VBKey), + SetKeys(SimId, VBKey), } #[derive(Clone)] diff --git a/src/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs similarity index 83% rename from src/shrooms_vb_core.rs rename to src/emulator/shrooms_vb_core.rs index 324689d..507085e 100644 --- a/src/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -25,6 +25,12 @@ enum VBDataType { F32 = 5, } +#[repr(i32)] +#[derive(FromPrimitive, ToPrimitive)] +enum VBOption { + PseudoHalt = 0, +} + bitflags! { #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -52,8 +58,8 @@ type OnFrame = extern "C" fn(sim: *mut VB) -> c_int; #[link(name = "vb")] extern "C" { - #[link_name = "vbEmulate"] - fn vb_emulate(sim: *mut VB, cycles: *mut u32) -> c_int; + #[link_name = "vbEmulateEx"] + fn vb_emulate_ex(sims: *mut *mut VB, count: c_uint, cycles: *mut u32) -> c_int; #[link_name = "vbGetCartROM"] fn vb_get_cart_rom(sim: *mut VB, size: *mut u32) -> *mut c_void; #[link_name = "vbGetPixels"] @@ -81,10 +87,12 @@ extern "C" { fn vb_reset(sim: *mut VB); #[link_name = "vbSetCartROM"] fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int; - #[link_name = "vbSetKeys"] - fn vb_set_keys(sim: *mut VB, keys: u16) -> u16; #[link_name = "vbSetFrameCallback"] fn vb_set_frame_callback(sim: *mut VB, on_frame: OnFrame); + #[link_name = "vbSetKeys"] + fn vb_set_keys(sim: *mut VB, keys: u16) -> u16; + #[link_name = "vbSetOption"] + fn vb_set_option(sim: *mut VB, key: VBOption, value: c_int); #[link_name = "vbSetSamples"] fn vb_set_samples( sim: *mut VB, @@ -113,14 +121,15 @@ struct VBState { frame_seen: bool, } -pub struct CoreVB { +#[repr(transparent)] +pub struct Sim { sim: *mut VB, } // SAFETY: the memory pointed to by sim is valid -unsafe impl Send for CoreVB {} +unsafe impl Send for Sim {} -impl CoreVB { +impl Sim { pub fn new() -> Self { // init the VB instance itself let size = unsafe { vb_size_of() }; @@ -128,6 +137,7 @@ impl CoreVB { let memory = vec![0u64; size.div_ceil(4)]; let sim: *mut VB = Box::into_raw(memory.into_boxed_slice()).cast(); unsafe { vb_init(sim) }; + unsafe { vb_set_option(sim, VBOption::PseudoHalt, 1) }; unsafe { vb_reset(sim) }; // set up userdata @@ -140,7 +150,7 @@ impl CoreVB { let samples: *mut c_void = Box::into_raw(audio_buffer.into_boxed_slice()).cast(); unsafe { vb_set_samples(sim, samples, VBDataType::F32, AUDIO_CAPACITY_SAMPLES as u32) }; - CoreVB { sim } + Sim { sim } } pub fn reset(&mut self) { @@ -162,6 +172,17 @@ impl CoreVB { } } + pub fn clone_rom(&self) -> Option> { + let mut size = 0; + let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) }; + if rom.is_null() { + return None; + } + // SAFETY: rom definitely points to a valid array of `size` bytes + let slice: &[u8] = unsafe { slice::from_raw_parts(rom.cast(), size as usize) }; + Some(slice.to_vec()) + } + fn unload_rom(&mut self) -> Option> { let mut size = 0; let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) }; @@ -173,9 +194,11 @@ impl CoreVB { Some(vec) } - pub fn emulate_frame(&mut self) { + pub fn emulate_many(sims: &mut [Sim]) { let mut cycles = 20_000_000; - unsafe { vb_emulate(self.sim, &mut cycles) }; + let count = sims.len() as c_uint; + let sims = sims.as_mut_ptr().cast(); + unsafe { vb_emulate_ex(sims, count, &mut cycles) }; } pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool { @@ -203,14 +226,17 @@ impl CoreVB { true } - pub fn read_samples(&mut self, samples: &mut Vec) { + pub fn read_samples(&mut self, samples: &mut Vec, weight: f32) { let mut position = 0; let ptr = unsafe { vb_get_samples(self.sim, ptr::null_mut(), ptr::null_mut(), &mut position) }; // SAFETY: position is an offset in a buffer of (f32, f32). so, position * 2 is an offset in a buffer of f32. - let read_samples: &mut [f32] = - unsafe { slice::from_raw_parts_mut(ptr.cast(), position as usize * 2) }; - samples.extend_from_slice(read_samples); + let read_samples: &[f32] = + unsafe { slice::from_raw_parts(ptr.cast(), position as usize * 2) }; + samples.resize(read_samples.len(), 0.0); + for (index, sample) in read_samples.iter().enumerate() { + samples[index] += sample * weight; + } unsafe { vb_set_samples( @@ -227,7 +253,7 @@ impl CoreVB { } } -impl Drop for CoreVB { +impl Drop for Sim { fn drop(&mut self) { let ptr = unsafe { vb_get_samples(self.sim, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) }; diff --git a/src/input.rs b/src/input.rs index 5367d60..44e626e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -2,15 +2,14 @@ use std::collections::HashMap; use winit::{ event::KeyEvent, - keyboard::{Key, NamedKey}, - platform::modifier_supplement::KeyEventExtModifierSupplement, + keyboard::{KeyCode, PhysicalKey}, }; -use crate::shrooms_vb_core::VBKey; +use crate::emulator::{SimId, VBKey}; pub struct InputMapper { - vb_bindings: HashMap, - key_bindings: HashMap, + vb_bindings: HashMap>, + key_bindings: HashMap, } impl InputMapper { @@ -19,30 +18,48 @@ impl InputMapper { vb_bindings: HashMap::new(), key_bindings: HashMap::new(), }; - mapper.bind_key(VBKey::SEL, Key::Character("a".into())); - mapper.bind_key(VBKey::STA, Key::Character("s".into())); - mapper.bind_key(VBKey::B, Key::Character("d".into())); - mapper.bind_key(VBKey::A, Key::Character("f".into())); - mapper.bind_key(VBKey::LT, Key::Character("e".into())); - mapper.bind_key(VBKey::RT, Key::Character("r".into())); - mapper.bind_key(VBKey::RU, Key::Character("i".into())); - mapper.bind_key(VBKey::RL, Key::Character("j".into())); - mapper.bind_key(VBKey::RD, Key::Character("k".into())); - mapper.bind_key(VBKey::RR, Key::Character("l".into())); - mapper.bind_key(VBKey::LU, Key::Named(NamedKey::ArrowUp)); - mapper.bind_key(VBKey::LL, Key::Named(NamedKey::ArrowLeft)); - mapper.bind_key(VBKey::LD, Key::Named(NamedKey::ArrowDown)); - mapper.bind_key(VBKey::LR, Key::Named(NamedKey::ArrowRight)); + mapper.bind_key(SimId::Player1, VBKey::SEL, PhysicalKey::Code(KeyCode::KeyA)); + mapper.bind_key(SimId::Player1, VBKey::STA, PhysicalKey::Code(KeyCode::KeyS)); + mapper.bind_key(SimId::Player1, VBKey::B, PhysicalKey::Code(KeyCode::KeyD)); + mapper.bind_key(SimId::Player1, VBKey::A, PhysicalKey::Code(KeyCode::KeyF)); + mapper.bind_key(SimId::Player1, VBKey::LT, PhysicalKey::Code(KeyCode::KeyE)); + mapper.bind_key(SimId::Player1, VBKey::RT, PhysicalKey::Code(KeyCode::KeyR)); + mapper.bind_key(SimId::Player1, VBKey::RU, PhysicalKey::Code(KeyCode::KeyI)); + mapper.bind_key(SimId::Player1, VBKey::RL, PhysicalKey::Code(KeyCode::KeyJ)); + mapper.bind_key(SimId::Player1, VBKey::RD, PhysicalKey::Code(KeyCode::KeyK)); + mapper.bind_key(SimId::Player1, VBKey::RR, PhysicalKey::Code(KeyCode::KeyL)); + mapper.bind_key( + SimId::Player1, + VBKey::LU, + PhysicalKey::Code(KeyCode::ArrowUp), + ); + mapper.bind_key( + SimId::Player1, + VBKey::LL, + PhysicalKey::Code(KeyCode::ArrowLeft), + ); + mapper.bind_key( + SimId::Player1, + VBKey::LD, + PhysicalKey::Code(KeyCode::ArrowDown), + ); + mapper.bind_key( + SimId::Player1, + VBKey::LR, + PhysicalKey::Code(KeyCode::ArrowRight), + ); mapper } - pub fn binding_names(&self) -> HashMap { - self.vb_bindings + pub fn binding_names(&self, sim_id: &SimId) -> HashMap { + let Some(bindings) = self.vb_bindings.get(sim_id) else { + return HashMap::new(); + }; + bindings .iter() .map(|(k, v)| { let name = match v { - Key::Character(char) => char.to_string(), - Key::Named(key) => format!("{:?}", key), + PhysicalKey::Code(code) => format!("{code:?}"), k => format!("{:?}", k), }; (*k, name) @@ -50,22 +67,22 @@ impl InputMapper { .collect() } - pub fn bind_key(&mut self, vb: VBKey, key: Key) { - if let Some(old) = self.vb_bindings.insert(vb, key.clone()) { + pub fn bind_key(&mut self, sim_id: SimId, vb: VBKey, key: PhysicalKey) { + let vb_bindings = self.vb_bindings.entry(sim_id).or_default(); + if let Some(old) = vb_bindings.insert(vb, key) { self.key_bindings.remove(&old); } - self.key_bindings.insert(key, vb); + self.key_bindings.insert(key, (sim_id, vb)); } - pub fn clear_binding(&mut self, vb: VBKey) { - if let Some(old) = self.vb_bindings.remove(&vb) { + pub fn clear_binding(&mut self, sim_id: SimId, vb: VBKey) { + let vb_bindings = self.vb_bindings.entry(sim_id).or_default(); + if let Some(old) = vb_bindings.remove(&vb) { self.key_bindings.remove(&old); } } - pub fn key_event(&self, event: &KeyEvent) -> Option { - self.key_bindings - .get(&event.key_without_modifiers()) - .cloned() + pub fn key_event(&self, event: &KeyEvent) -> Option<(SimId, VBKey)> { + self.key_bindings.get(&event.physical_key).copied() } } diff --git a/src/main.rs b/src/main.rs index b8de1cf..9297ee4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ mod controller; mod emulator; mod input; mod renderer; -mod shrooms_vb_core; #[derive(Parser)] struct Args { From e2c38cd03a48f4354f89953a508accdf748f99ed Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Mon, 11 Nov 2024 18:14:11 -0500 Subject: [PATCH 02/21] Turn off LTO so we can use clang --- build.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/build.rs b/build.rs index 17bd565..697e46e 100644 --- a/build.rs +++ b/build.rs @@ -5,7 +5,6 @@ fn main() { cc::Build::new() .include(Path::new("shrooms-vb-core/core")) .opt_level(2) - .flag_if_supported("-flto") .flag_if_supported("-fno-strict-aliasing") .file(Path::new("shrooms-vb-core/core/vb.c")) .compile("vb"); From f22a74b036e7c09628a366c80b86274684e2a36c Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Mon, 11 Nov 2024 18:46:25 -0500 Subject: [PATCH 03/21] Sleep instead of just spin-looping --- src/audio.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/audio.rs b/src/audio.rs index 2486830..2d97949 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use anyhow::{bail, Result}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use itertools::Itertools; @@ -95,7 +97,7 @@ impl Audio { } while self.sample_sink.slots() < self.sampler.output_frames_max() * 2 { - std::hint::spin_loop(); + std::thread::sleep(Duration::from_micros(500)); } } } From d306f19297b3021915760e8b55663aef6a45cc52 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Mon, 11 Nov 2024 19:27:23 -0500 Subject: [PATCH 04/21] Write textures in separate thread; fixes perf --- src/app/game.rs | 17 ++---- src/emulator.rs | 27 ++++++--- src/graphics.rs | 143 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 +- src/renderer.rs | 53 ------------------ 5 files changed, 167 insertions(+), 75 deletions(-) create mode 100644 src/graphics.rs delete mode 100644 src/renderer.rs diff --git a/src/app/game.rs b/src/app/game.rs index ad3f6af..9dbaa47 100644 --- a/src/app/game.rs +++ b/src/app/game.rs @@ -1,4 +1,4 @@ -use std::{sync::Arc, time::Instant}; +use std::time::Instant; use wgpu::util::DeviceExt as _; use winit::{ dpi::LogicalSize, @@ -9,7 +9,7 @@ use winit::{ use crate::{ emulator::{EmulatorClient, EmulatorCommand, SimId}, - renderer::GameRenderer, + graphics::TextureSink, }; use super::{ @@ -46,15 +46,8 @@ impl GameWindow { .build(); let device = &window.device; - let eyes = Arc::new(GameRenderer::create_texture(device, "eye")); - client.send_command(EmulatorCommand::SetRenderer( - sim_id, - GameRenderer { - queue: window.queue.clone(), - eyes: eyes.clone(), - }, - )); - let eyes = eyes.create_view(&wgpu::TextureViewDescriptor::default()); + let (sink, texture_view) = TextureSink::new(device, window.queue.clone()); + client.send_command(EmulatorCommand::SetRenderer(sim_id, sink)); let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); let colors = Colors { left: [1.0, 0.0, 0.0, 1.0], @@ -103,7 +96,7 @@ impl GameWindow { entries: &[ wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::TextureView(&eyes), + resource: wgpu::BindingResource::TextureView(&texture_view), }, wgpu::BindGroupEntry { binding: 1, diff --git a/src/emulator.rs b/src/emulator.rs index 4bb8386..7339c45 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -11,7 +11,7 @@ use std::{ use anyhow::Result; -use crate::{audio::Audio, renderer::GameRenderer}; +use crate::{audio::Audio, graphics::TextureSink}; use shrooms_vb_core::Sim; pub use shrooms_vb_core::VBKey; @@ -30,7 +30,10 @@ pub enum SimId { Player2, } impl SimId { - pub fn to_index(self) -> usize { + pub const fn values() -> [Self; 2] { + [Self::Player1, Self::Player2] + } + pub const fn to_index(self) -> usize { match self { Self::Player1 => 0, Self::Player2 => 1, @@ -75,7 +78,7 @@ pub struct Emulator { sims: Vec, audio: Audio, commands: mpsc::Receiver, - renderers: HashMap, + renderers: HashMap, running: Arc, has_game: Arc, } @@ -140,11 +143,17 @@ impl Emulator { idle = false; Sim::emulate_many(&mut self.sims); } - for (sim_id, renderer) in self.renderers.iter_mut() { - if let Some(sim) = self.sims.get_mut(sim_id.to_index()) { - if sim.read_pixels(&mut eye_contents) { - idle = false; - renderer.render(&eye_contents); + for sim_id in SimId::values() { + let Some(renderer) = self.renderers.get_mut(&sim_id) else { + continue; + }; + let Some(sim) = self.sims.get_mut(sim_id.to_index()) else { + continue; + }; + if sim.read_pixels(&mut eye_contents) { + idle = false; + if renderer.queue_render(&eye_contents).is_err() { + self.renderers.remove(&sim_id); } } } @@ -226,7 +235,7 @@ impl Emulator { #[derive(Debug)] pub enum EmulatorCommand { - SetRenderer(SimId, GameRenderer), + SetRenderer(SimId, TextureSink), LoadGame(SimId, PathBuf), StartSecondSim(Option), StopSecondSim, diff --git a/src/graphics.rs b/src/graphics.rs new file mode 100644 index 0000000..4c0f75f --- /dev/null +++ b/src/graphics.rs @@ -0,0 +1,143 @@ +use std::{ + sync::{ + atomic::{AtomicU64, Ordering}, + mpsc, Arc, Mutex, MutexGuard, + }, + thread, +}; + +use anyhow::{bail, Result}; +use itertools::Itertools as _; +use wgpu::{ + Device, Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, Queue, Texture, + TextureDescriptor, TextureFormat, TextureUsages, TextureView, TextureViewDescriptor, +}; + +#[derive(Debug)] +pub struct TextureSink { + buffers: Arc, + sink: mpsc::Sender, +} + +impl TextureSink { + pub fn new(device: &Device, queue: Arc) -> (Self, TextureView) { + let texture = Self::create_texture(device); + let view = texture.create_view(&TextureViewDescriptor::default()); + let buffers = Arc::new(BufferPool::new()); + let (sink, source) = mpsc::channel(); + let bufs = buffers.clone(); + thread::spawn(move || { + let mut local_buf = vec![0; 384 * 224 * 2]; + while let Ok(id) = source.recv() { + { + let Some(bytes) = bufs.read(id) else { + continue; + }; + local_buf.copy_from_slice(bytes.as_slice()); + } + Self::write_texture(&queue, &texture, local_buf.as_slice()); + } + }); + let sink = Self { buffers, sink }; + (sink, view) + } + + pub fn queue_render(&mut self, bytes: &[u8]) -> Result<()> { + let id = { + let (mut buf, id) = self.buffers.write()?; + buf.copy_from_slice(bytes); + id + }; + self.sink.send(id)?; + Ok(()) + } + + fn create_texture(device: &Device) -> Texture { + let desc = TextureDescriptor { + label: Some("eyes"), + size: Extent3d { + width: 384, + height: 224, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: TextureFormat::Rg8Unorm, + usage: TextureUsages::COPY_SRC + | TextureUsages::COPY_DST + | TextureUsages::TEXTURE_BINDING, + view_formats: &[TextureFormat::Rg8Unorm], + }; + device.create_texture(&desc) + } + + fn write_texture(queue: &Queue, texture: &Texture, bytes: &[u8]) { + let texture = ImageCopyTexture { + texture, + mip_level: 0, + origin: Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }; + let size = Extent3d { + width: 384, + height: 224, + depth_or_array_layers: 1, + }; + let data_layout = ImageDataLayout { + offset: 0, + bytes_per_row: Some(384 * 2), + rows_per_image: Some(224), + }; + queue.write_texture(texture, bytes, data_layout, size); + } +} + +#[derive(Debug)] +struct BufferPool { + buffers: [Buffer; 3], +} +impl BufferPool { + fn new() -> Self { + Self { + buffers: std::array::from_fn(|i| Buffer::new(i as u64)), + } + } + + fn read(&self, id: u64) -> Option>> { + let buf = self + .buffers + .iter() + .find(|buf| buf.id.load(Ordering::Acquire) == id)?; + buf.data.lock().ok() + } + + fn write(&self) -> Result<(MutexGuard<'_, Vec>, u64)> { + let (min, max) = self + .buffers + .iter() + .minmax_by_key(|buf| buf.id.load(Ordering::Acquire)) + .into_option() + .unwrap(); + let Ok(lock) = min.data.lock() else { + bail!("lock was poisoned") + }; + let id = max.id.load(Ordering::Acquire) + 1; + min.id.store(id, Ordering::Release); + Ok((lock, id)) + } +} + +#[derive(Debug)] +struct Buffer { + data: Mutex>, + id: AtomicU64, +} +impl Buffer { + fn new(id: u64) -> Self { + Self { + data: Mutex::new(vec![0; 384 * 224 * 2]), + id: AtomicU64::new(id), + } + } +} diff --git a/src/main.rs b/src/main.rs index 9297ee4..c0fc4b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,8 @@ mod app; mod audio; mod controller; mod emulator; +mod graphics; mod input; -mod renderer; #[derive(Parser)] struct Args { diff --git a/src/renderer.rs b/src/renderer.rs deleted file mode 100644 index a97e9e7..0000000 --- a/src/renderer.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::sync::Arc; - -use wgpu::{ - Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, Queue, Texture, TextureDescriptor, - TextureFormat, TextureUsages, -}; - -#[derive(Debug)] -pub struct GameRenderer { - pub queue: Arc, - pub eyes: Arc, -} - -impl GameRenderer { - pub fn render(&self, buffer: &[u8]) { - let texture = ImageCopyTexture { - texture: &self.eyes, - mip_level: 0, - origin: Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }; - let size = Extent3d { - width: 384, - height: 224, - depth_or_array_layers: 1, - }; - let data_layout = ImageDataLayout { - offset: 0, - bytes_per_row: Some(384 * 2), - rows_per_image: Some(224), - }; - self.queue.write_texture(texture, buffer, data_layout, size); - } - pub fn create_texture(device: &wgpu::Device, name: &str) -> Texture { - let desc = TextureDescriptor { - label: Some(name), - size: Extent3d { - width: 384, - height: 224, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: TextureFormat::Rg8Unorm, - usage: TextureUsages::COPY_SRC - | TextureUsages::COPY_DST - | TextureUsages::TEXTURE_BINDING, - view_formats: &[TextureFormat::Rg8Unorm], - }; - device.create_texture(&desc) - } -} From c23fc6e9dfbb37e6c6fa02bdb798e65c680f3057 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Thu, 14 Nov 2024 21:02:56 -0500 Subject: [PATCH 05/21] Small performance improvements --- build.rs | 3 +++ shrooms-vb-core | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 697e46e..ab23bf9 100644 --- a/build.rs +++ b/build.rs @@ -6,6 +6,9 @@ fn main() { .include(Path::new("shrooms-vb-core/core")) .opt_level(2) .flag_if_supported("-fno-strict-aliasing") + .define("VB_LITTLE_ENDIAN", None) + .define("VB_SIGNED_PROPAGATE", None) + .define("VB_DIV_GENERIC", None) .file(Path::new("shrooms-vb-core/core/vb.c")) .compile("vb"); } diff --git a/shrooms-vb-core b/shrooms-vb-core index b4b9131..7e31fbd 160000 --- a/shrooms-vb-core +++ b/shrooms-vb-core @@ -1 +1 @@ -Subproject commit b4b9131f3976388b096343725fce1bdf35f4e3df +Subproject commit 7e31fbd5824d612e26a176aa2946497cfb2aa02e From eef8f834d6d5841370c5ff31d9fa90b13e2603d2 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Thu, 14 Nov 2024 21:54:13 -0500 Subject: [PATCH 06/21] Actually implement multiplayer --- src/emulator.rs | 2 ++ src/emulator/shrooms_vb_core.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/emulator.rs b/src/emulator.rs index 7339c45..027df7b 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -114,6 +114,8 @@ impl Emulator { return Ok(()); }; self.load_rom(SimId::Player2, bytes)?; + let (first, second) = self.sims.split_at_mut(1); + first[0].link(&mut second[0]); Ok(()) } diff --git a/src/emulator/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs index 507085e..8a96ffe 100644 --- a/src/emulator/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -62,6 +62,8 @@ extern "C" { fn vb_emulate_ex(sims: *mut *mut VB, count: c_uint, cycles: *mut u32) -> c_int; #[link_name = "vbGetCartROM"] fn vb_get_cart_rom(sim: *mut VB, size: *mut u32) -> *mut c_void; + #[link_name = "vbGetPeer"] + fn vb_get_peer(sim: *mut VB) -> *mut VB; #[link_name = "vbGetPixels"] fn vb_get_pixels( sim: *mut VB, @@ -93,6 +95,8 @@ extern "C" { fn vb_set_keys(sim: *mut VB, keys: u16) -> u16; #[link_name = "vbSetOption"] fn vb_set_option(sim: *mut VB, key: VBOption, value: c_int); + #[link_name = "vbSetPeer"] + fn vb_set_peer(sim: *mut VB, peer: *mut VB); #[link_name = "vbSetSamples"] fn vb_set_samples( sim: *mut VB, @@ -194,6 +198,10 @@ impl Sim { Some(vec) } + pub fn link(&mut self, peer: &mut Sim) { + unsafe { vb_set_peer(self.sim, peer.sim) }; + } + pub fn emulate_many(sims: &mut [Sim]) { let mut cycles = 20_000_000; let count = sims.len() as c_uint; @@ -269,6 +277,12 @@ impl Drop for Sim { // SAFETY: we made this pointer ourselves, we can for sure free it unsafe { drop(Box::from_raw(ptr)) }; + // If we're linked to another sim, unlink them from ourselves. + let peer = unsafe { vb_get_peer(self.sim) }; + if !peer.is_null() { + unsafe { vb_set_peer(peer, ptr::null_mut()) }; + } + let len = unsafe { vb_size_of() }.div_ceil(4); // SAFETY: the sim's memory originally came from a Vec let bytes: Vec = unsafe { Vec::from_raw_parts(self.sim.cast(), len, len) }; From 8f62ec1c5226d478b0d97d3870012c773eb3d7e0 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Fri, 15 Nov 2024 00:23:49 -0500 Subject: [PATCH 07/21] Update wgpu for faster release builds --- Cargo.lock | 288 ++++++++++++++++++++++-------------------------- Cargo.toml | 4 +- src/app/game.rs | 4 +- 3 files changed, 133 insertions(+), 163 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a61b2f2..619a697 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,9 +42,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "alsa" @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -155,9 +155,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arrayref" @@ -219,23 +219,23 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.87", + "syn", ] [[package]] name = "bit-set" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -287,7 +287,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -324,9 +324,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.34" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -385,9 +385,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -395,9 +395,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -414,14 +414,14 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "cocoa" @@ -469,37 +469,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" -[[package]] -name = "com" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" -dependencies = [ - "com_macros", -] - -[[package]] -name = "com_macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" -dependencies = [ - "com_macros_support", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "com_macros_support" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "combine" version = "4.6.7" @@ -614,17 +583,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" -[[package]] -name = "d3d12" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" -dependencies = [ - "bitflags 2.6.0", - "libloading", - "winapi", -] - [[package]] name = "dasp_sample" version = "0.11.0" @@ -728,7 +686,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -777,9 +735,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "glow" -version = "0.13.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" dependencies = [ "js-sys", "slotmap", @@ -817,15 +775,14 @@ dependencies = [ [[package]] name = "gpu-allocator" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ "log", "presser", "thiserror", - "winapi", - "windows 0.52.0", + "windows 0.58.0", ] [[package]] @@ -860,24 +817,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" - -[[package]] -name = "hassle-rs" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" -dependencies = [ - "bitflags 2.6.0", - "com", - "libc", - "libloading", - "thiserror", - "widestring", - "winapi", -] +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "heck" @@ -934,7 +876,7 @@ dependencies = [ [[package]] name = "imgui-wgpu" version = "0.25.0" -source = "git+https://github.com/Yatekii/imgui-wgpu-rs?rev=2edd348#2edd348a0fc11e9e72f19060c34a6e45c760b116" +source = "git+https://github.com/SupernaviX/imgui-wgpu-rs?rev=5bb8673#5bb8673e256b6b5ad497a3baa2dc151545025f12" dependencies = [ "bytemuck", "imgui", @@ -960,7 +902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.1", ] [[package]] @@ -1046,9 +988,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "libloading" @@ -1161,9 +1103,9 @@ checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" [[package]] name = "naga" -version = "22.1.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" +checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e" dependencies = [ "arrayvec", "bit-set", @@ -1283,7 +1225,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1322,7 +1264,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1656,7 +1598,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1673,9 +1615,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", @@ -1819,9 +1761,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1881,9 +1823,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.38" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno", @@ -1934,22 +1876,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -2074,17 +2016,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.87" @@ -2107,22 +2038,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -2297,7 +2228,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn", "wasm-bindgen-shared", ] @@ -2331,7 +2262,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2483,9 +2414,9 @@ dependencies = [ [[package]] name = "wgpu" -version = "22.1.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" +checksum = "76ab52f2d3d18b70d5ab8dd270a1cff3ebe6dbe4a7d13c1cc2557138a9777fdc" dependencies = [ "arrayvec", "cfg_aliases 0.1.1", @@ -2508,9 +2439,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "22.1.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" +checksum = "0e0c68e7b6322a03ee5b83fcd92caeac5c2a932f6457818179f4652ad2a9c065" dependencies = [ "arrayvec", "bit-vec", @@ -2533,9 +2464,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "22.0.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" +checksum = "de6e7266b869de56c7e3ed72a954899f71d14fec6cc81c102b7530b92947601b" dependencies = [ "android_system_properties", "arrayvec", @@ -2543,15 +2474,14 @@ dependencies = [ "bit-set", "bitflags 2.6.0", "block", + "bytemuck", "cfg_aliases 0.1.1", "core-graphics-types", - "d3d12", "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", - "hassle-rs", "js-sys", "khronos-egl", "libc", @@ -2573,14 +2503,15 @@ dependencies = [ "wasm-bindgen", "web-sys", "wgpu-types", - "winapi", + "windows 0.58.0", + "windows-core 0.58.0", ] [[package]] name = "wgpu-types" -version = "22.0.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" +checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" dependencies = [ "bitflags 2.6.0", "js-sys", @@ -2599,12 +2530,6 @@ dependencies = [ "rustix", ] -[[package]] -name = "widestring" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" - [[package]] name = "winapi" version = "0.3.9" @@ -2636,16 +2561,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.54.0" @@ -2657,11 +2572,12 @@ dependencies = [ ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ + "windows-core 0.58.0", "windows-targets 0.52.6", ] @@ -2671,10 +2587,45 @@ version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" dependencies = [ - "windows-result", + "windows-result 0.1.2", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -2684,6 +2635,25 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3009,9 +2979,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" +checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f" [[package]] name = "zerocopy" @@ -3030,5 +3000,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 73f97d5..0b86a83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ bytemuck = { version = "1", features = ["derive"] } clap = { version = "4", features = ["derive"] } cpal = "0.15" imgui = { version = "0.12", features = ["tables-api"] } -imgui-wgpu = { git = "https://github.com/Yatekii/imgui-wgpu-rs", rev = "2edd348" } +imgui-wgpu = { git = "https://github.com/SupernaviX/imgui-wgpu-rs", rev = "5bb8673" } imgui-winit-support = "0.13" itertools = "0.13" native-dialog = "0.7" @@ -20,7 +20,7 @@ pollster = "0.4" rtrb = "0.3" rubato = "0.16" thread-priority = "1" -wgpu = "22.1" +wgpu = "23.0" winit = "0.30" [build-dependencies] diff --git a/src/app/game.rs b/src/app/game.rs index 9dbaa47..aad68ba 100644 --- a/src/app/game.rs +++ b/src/app/game.rs @@ -121,13 +121,13 @@ impl GameWindow { layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: &shader, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: wgpu::TextureFormat::Bgra8UnormSrgb, blend: Some(wgpu::BlendState::REPLACE), From 09e39b37f5b55dbc5aac8aaa55623e384de0e1ed Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Fri, 15 Nov 2024 21:50:21 -0500 Subject: [PATCH 08/21] Let sims link/unlink/reset independently --- src/app/game.rs | 23 ++++- src/emulator.rs | 163 +++++++++++++++++++++++--------- src/emulator/shrooms_vb_core.rs | 15 +++ 3 files changed, 149 insertions(+), 52 deletions(-) diff --git a/src/app/game.rs b/src/app/game.rs index aad68ba..8960450 100644 --- a/src/app/game.rs +++ b/src/app/game.rs @@ -211,8 +211,8 @@ impl GameWindow { } }); ui.menu("Emulation", || { - let has_game = self.client.has_game(); - if self.client.is_running() { + let has_game = self.client.has_game(self.sim_id); + if self.client.is_running(self.sim_id) { if ui.menu_item_config("Pause").enabled(has_game).build() { self.client.send_command(EmulatorCommand::Pause); } @@ -220,7 +220,8 @@ impl GameWindow { self.client.send_command(EmulatorCommand::Resume); } if ui.menu_item_config("Reset").enabled(has_game).build() { - self.client.send_command(EmulatorCommand::Reset); + self.client + .send_command(EmulatorCommand::Reset(self.sim_id)); } }); ui.menu("Video", || { @@ -243,11 +244,23 @@ impl GameWindow { } }); ui.menu("Multiplayer", || { - if self.sim_id == SimId::Player1 && ui.menu_item("Open Player 2") { + if self.sim_id == SimId::Player1 + && !self.client.has_player_2() + && ui.menu_item("Open Player 2") + { self.client .send_command(EmulatorCommand::StartSecondSim(None)); self.proxy.send_event(UserEvent::OpenPlayer2Window).unwrap(); } + if self.client.has_player_2() { + let linked = self.client.are_sims_linked(); + if linked && ui.menu_item("Unlink") { + self.client.send_command(EmulatorCommand::Unlink); + } + if !linked && ui.menu_item("Link") { + self.client.send_command(EmulatorCommand::Link); + } + } }); }); @@ -326,7 +339,7 @@ impl AppWindow for GameWindow { WindowEvent::Resized(size) => { self.window.handle_resize(size); if self.window.minimized { - if self.client.is_running() { + if self.client.is_running(self.sim_id) { self.client.send_command(EmulatorCommand::Pause); self.paused_due_to_minimize = true; } diff --git a/src/emulator.rs b/src/emulator.rs index 027df7b..c0638a8 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -3,7 +3,7 @@ use std::{ fs, path::{Path, PathBuf}, sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicUsize, Ordering}, mpsc::{self, RecvError, TryRecvError}, Arc, }, @@ -20,8 +20,10 @@ mod shrooms_vb_core; pub struct EmulatorBuilder { rom: Option, commands: mpsc::Receiver, - running: Arc, - has_game: Arc, + sim_count: Arc, + running: Arc<[AtomicBool; 2]>, + has_game: Arc<[AtomicBool; 2]>, + linked: Arc, } #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] @@ -47,13 +49,17 @@ impl EmulatorBuilder { let builder = Self { rom: None, commands, - running: Arc::new(AtomicBool::new(false)), - has_game: Arc::new(AtomicBool::new(false)), + sim_count: Arc::new(AtomicUsize::new(0)), + running: Arc::new([AtomicBool::new(false), AtomicBool::new(false)]), + has_game: Arc::new([AtomicBool::new(false), AtomicBool::new(false)]), + linked: Arc::new(AtomicBool::new(false)), }; let client = EmulatorClient { queue, + sim_count: builder.sim_count.clone(), running: builder.running.clone(), has_game: builder.has_game.clone(), + linked: builder.linked.clone(), }; (builder, client) } @@ -66,9 +72,15 @@ impl EmulatorBuilder { } pub fn build(self) -> Result { - let mut emulator = Emulator::new(self.commands, self.running, self.has_game)?; + let mut emulator = Emulator::new( + self.commands, + self.sim_count, + self.running, + self.has_game, + self.linked, + )?; if let Some(path) = self.rom { - emulator.load_rom_from_file(SimId::Player1, &path)?; + emulator.load_rom(SimId::Player1, &path)?; } Ok(emulator) } @@ -78,73 +90,112 @@ pub struct Emulator { sims: Vec, audio: Audio, commands: mpsc::Receiver, + sim_count: Arc, + running: Arc<[AtomicBool; 2]>, + has_game: Arc<[AtomicBool; 2]>, + linked: Arc, renderers: HashMap, - running: Arc, - has_game: Arc, } impl Emulator { fn new( commands: mpsc::Receiver, - running: Arc, - has_game: Arc, + sim_count: Arc, + running: Arc<[AtomicBool; 2]>, + has_game: Arc<[AtomicBool; 2]>, + linked: Arc, ) -> Result { Ok(Self { sims: vec![], audio: Audio::init()?, commands, - renderers: HashMap::new(), + sim_count, running, has_game, + linked, + renderers: HashMap::new(), }) } - pub fn load_rom_from_file(&mut self, sim_id: SimId, path: &Path) -> Result<()> { + pub fn load_rom(&mut self, sim_id: SimId, path: &Path) -> Result<()> { let bytes = fs::read(path)?; - self.load_rom(sim_id, bytes)?; + self.reset_sim(sim_id, Some(bytes))?; Ok(()) } pub fn start_second_sim(&mut self, rom: Option) -> Result<()> { let bytes = if let Some(path) = rom { - fs::read(path)? - } else if let Some(rom) = self.sims.first().and_then(|s| s.clone_rom()) { - rom + Some(fs::read(path)?) } else { - return Ok(()); + self.sims.first().and_then(|s| s.clone_rom()) }; - self.load_rom(SimId::Player2, bytes)?; - let (first, second) = self.sims.split_at_mut(1); - first[0].link(&mut second[0]); + self.reset_sim(SimId::Player2, bytes)?; + self.link_sims(); Ok(()) } - fn load_rom(&mut self, sim_id: SimId, bytes: Vec) -> Result<()> { - while self.sims.len() <= sim_id.to_index() { + fn reset_sim(&mut self, sim_id: SimId, new_rom: Option>) -> Result<()> { + let index = sim_id.to_index(); + while self.sims.len() <= index { self.sims.push(Sim::new()); } - let sim = &mut self.sims[sim_id.to_index()]; + self.sim_count.store(self.sims.len(), Ordering::Relaxed); + let sim = &mut self.sims[index]; sim.reset(); - sim.load_rom(bytes)?; - self.has_game.store(true, Ordering::Release); - self.running.store(true, Ordering::Release); + if let Some(bytes) = new_rom { + sim.load_rom(bytes)?; + self.has_game[index].store(true, Ordering::Release); + } + if self.has_game[index].load(Ordering::Acquire) { + self.running[index].store(true, Ordering::Release); + } Ok(()) } + fn link_sims(&mut self) { + let (first, second) = self.sims.split_at_mut(1); + let Some(first) = first.first_mut() else { + return; + }; + let Some(second) = second.first_mut() else { + return; + }; + first.link(second); + self.linked.store(true, Ordering::Release); + } + + fn unlink_sims(&mut self) { + let Some(first) = self.sims.first_mut() else { + return; + }; + first.unlink(); + self.linked.store(false, Ordering::Release); + } + pub fn stop_second_sim(&mut self) { self.renderers.remove(&SimId::Player2); self.sims.truncate(1); + self.sim_count.store(self.sims.len(), Ordering::Relaxed); + self.running[SimId::Player2.to_index()].store(false, Ordering::Release); + self.has_game[SimId::Player2.to_index()].store(false, Ordering::Release); + self.linked.store(false, Ordering::Release); } pub fn run(&mut self) { let mut eye_contents = vec![0u8; 384 * 224 * 2]; let mut audio_samples = vec![]; loop { - let mut idle = true; - if self.running.load(Ordering::Acquire) { - idle = false; + let p1_running = self.running[SimId::Player1.to_index()].load(Ordering::Acquire); + let p2_running = self.running[SimId::Player2.to_index()].load(Ordering::Acquire); + let mut idle = p1_running || p2_running; + if p1_running && p2_running { Sim::emulate_many(&mut self.sims); + } else if p1_running { + self.sims[SimId::Player1.to_index()].emulate(); + } else if p2_running { + self.sims[SimId::Player2.to_index()].emulate(); } + for sim_id in SimId::values() { let Some(renderer) = self.renderers.get_mut(&sim_id) else { continue; @@ -198,7 +249,7 @@ impl Emulator { self.renderers.insert(sim_id, renderer); } EmulatorCommand::LoadGame(sim_id, path) => { - if let Err(error) = self.load_rom_from_file(sim_id, &path) { + if let Err(error) = self.load_rom(sim_id, &path) { eprintln!("error loading rom: {}", error); } } @@ -211,19 +262,27 @@ impl Emulator { self.stop_second_sim(); } EmulatorCommand::Pause => { - self.running.store(false, Ordering::Release); + for sim in SimId::values() { + self.running[sim.to_index()].store(false, Ordering::Release); + } } EmulatorCommand::Resume => { - if self.has_game.load(Ordering::Acquire) { - self.running.store(true, Ordering::Relaxed); + for sim_id in SimId::values() { + let index = sim_id.to_index(); + if self.has_game[index].load(Ordering::Acquire) { + self.running[index].store(true, Ordering::Relaxed); + } } } - EmulatorCommand::Reset => { - for sim in self.sims.iter_mut() { - sim.reset(); - } - if !self.sims.is_empty() { - self.running.store(true, Ordering::Release); + EmulatorCommand::Link => { + self.link_sims(); + } + EmulatorCommand::Unlink => { + self.unlink_sims(); + } + EmulatorCommand::Reset(sim_id) => { + if let Err(error) = self.reset_sim(sim_id, None) { + eprintln!("error resetting sim: {}", error); } } EmulatorCommand::SetKeys(sim_id, keys) => { @@ -243,23 +302,33 @@ pub enum EmulatorCommand { StopSecondSim, Pause, Resume, - Reset, + Link, + Unlink, + Reset(SimId), SetKeys(SimId, VBKey), } #[derive(Clone)] pub struct EmulatorClient { queue: mpsc::Sender, - running: Arc, - has_game: Arc, + sim_count: Arc, + running: Arc<[AtomicBool; 2]>, + has_game: Arc<[AtomicBool; 2]>, + linked: Arc, } impl EmulatorClient { - pub fn is_running(&self) -> bool { - self.running.load(Ordering::Acquire) + pub fn has_player_2(&self) -> bool { + self.sim_count.load(Ordering::Acquire) == 2 } - pub fn has_game(&self) -> bool { - self.has_game.load(Ordering::Acquire) + pub fn is_running(&self, sim_id: SimId) -> bool { + self.running[sim_id.to_index()].load(Ordering::Acquire) + } + pub fn has_game(&self, sim_id: SimId) -> bool { + self.has_game[sim_id.to_index()].load(Ordering::Acquire) + } + pub fn are_sims_linked(&self) -> bool { + self.linked.load(Ordering::Acquire) } pub fn send_command(&self, command: EmulatorCommand) { if let Err(err) = self.queue.send(command) { diff --git a/src/emulator/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs index 8a96ffe..0d7593b 100644 --- a/src/emulator/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -58,6 +58,8 @@ type OnFrame = extern "C" fn(sim: *mut VB) -> c_int; #[link(name = "vb")] extern "C" { + #[link_name = "vbEmulate"] + fn vb_emulate(sim: *mut VB, cycles: *mut u32) -> c_int; #[link_name = "vbEmulateEx"] fn vb_emulate_ex(sims: *mut *mut VB, count: c_uint, cycles: *mut u32) -> c_int; #[link_name = "vbGetCartROM"] @@ -202,6 +204,19 @@ impl Sim { unsafe { vb_set_peer(self.sim, peer.sim) }; } + pub fn unlink(&mut self) { + let peer = unsafe { vb_get_peer(self.sim) }; + if !peer.is_null() { + unsafe { vb_set_peer(peer, ptr::null_mut()) }; + unsafe { vb_set_peer(self.sim, ptr::null_mut()) }; + } + } + + pub fn emulate(&mut self) { + let mut cycles = 20_000_000; + unsafe { vb_emulate(self.sim, &mut cycles) }; + } + pub fn emulate_many(sims: &mut [Sim]) { let mut cycles = 20_000_000; let count = sims.len() as c_uint; From 63cb7a48354b09da6946496639067e1ac8d7cba3 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sun, 17 Nov 2024 14:00:08 -0500 Subject: [PATCH 09/21] Update core --- shrooms-vb-core | 2 +- src/emulator/shrooms_vb_core.rs | 15 +++------------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/shrooms-vb-core b/shrooms-vb-core index 7e31fbd..bc86464 160000 --- a/shrooms-vb-core +++ b/shrooms-vb-core @@ -1 +1 @@ -Subproject commit 7e31fbd5824d612e26a176aa2946497cfb2aa02e +Subproject commit bc864644f791e17d0060a60ebf1eea938bcf80af diff --git a/src/emulator/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs index 0d7593b..a241772 100644 --- a/src/emulator/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -64,8 +64,6 @@ extern "C" { fn vb_emulate_ex(sims: *mut *mut VB, count: c_uint, cycles: *mut u32) -> c_int; #[link_name = "vbGetCartROM"] fn vb_get_cart_rom(sim: *mut VB, size: *mut u32) -> *mut c_void; - #[link_name = "vbGetPeer"] - fn vb_get_peer(sim: *mut VB) -> *mut VB; #[link_name = "vbGetPixels"] fn vb_get_pixels( sim: *mut VB, @@ -205,11 +203,7 @@ impl Sim { } pub fn unlink(&mut self) { - let peer = unsafe { vb_get_peer(self.sim) }; - if !peer.is_null() { - unsafe { vb_set_peer(peer, ptr::null_mut()) }; - unsafe { vb_set_peer(self.sim, ptr::null_mut()) }; - } + unsafe { vb_set_peer(self.sim, ptr::null_mut()) }; } pub fn emulate(&mut self) { @@ -292,11 +286,8 @@ impl Drop for Sim { // SAFETY: we made this pointer ourselves, we can for sure free it unsafe { drop(Box::from_raw(ptr)) }; - // If we're linked to another sim, unlink them from ourselves. - let peer = unsafe { vb_get_peer(self.sim) }; - if !peer.is_null() { - unsafe { vb_set_peer(peer, ptr::null_mut()) }; - } + // If we're linked to another sim, unlink from them. + unsafe { vb_set_peer(self.sim, ptr::null_mut()) }; let len = unsafe { vb_size_of() }.div_ceil(4); // SAFETY: the sim's memory originally came from a Vec From c84531e70ecca195761ebb8e580027f12d807c51 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sun, 17 Nov 2024 20:57:43 -0500 Subject: [PATCH 10/21] Set process priority to high on windows --- Cargo.lock | 1 + Cargo.toml | 3 +++ src/main.rs | 12 ++++++++++++ 3 files changed, 16 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 619a697..0ada172 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1922,6 +1922,7 @@ dependencies = [ "rubato", "thread-priority", "wgpu", + "windows 0.58.0", "winit", ] diff --git a/Cargo.toml b/Cargo.toml index 0b86a83..5b62f02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ thread-priority = "1" wgpu = "23.0" winit = "0.30" +[target.'cfg(windows)'.dependencies] +windows = { version = "0.58", features = ["Win32_System_Threading"] } + [build-dependencies] cc = "1" diff --git a/src/main.rs b/src/main.rs index c0fc4b9..8c47f3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,19 @@ struct Args { rom: Option, } +#[cfg(windows)] +fn set_process_priority_to_high() -> Result<()> { + use windows::Win32::{Foundation, System::Threading}; + let process = unsafe { Threading::GetCurrentProcess() }; + unsafe { Threading::SetPriorityClass(process, Threading::HIGH_PRIORITY_CLASS)? }; + unsafe { Foundation::CloseHandle(process)? }; + Ok(()) +} + fn main() -> Result<()> { + #[cfg(windows)] + set_process_priority_to_high()?; + let args = Args::parse(); let (mut builder, client) = EmulatorBuilder::new(); From f60b10ce1de6987229b7c8a4976ba3a989d07c1e Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Mon, 18 Nov 2024 22:49:56 -0500 Subject: [PATCH 11/21] Implement gamepad input --- Cargo.lock | 127 +++++++++++++++++++++++++-- Cargo.toml | 1 + src/app.rs | 60 +++++++++---- src/app/input.rs | 26 +++--- src/controller.rs | 144 +++++++++++++++++++++++------- src/input.rs | 217 +++++++++++++++++++++++++++++++--------------- 6 files changed, 435 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ada172..b47fc70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,7 +432,7 @@ dependencies = [ "bitflags 1.3.2", "block", "cocoa-foundation", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "foreign-types", "libc", @@ -447,7 +447,7 @@ checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "libc", "objc", @@ -498,6 +498,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -511,7 +521,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "foreign-types", "libc", @@ -524,7 +534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] @@ -668,6 +678,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.5.0" @@ -716,6 +732,40 @@ dependencies = [ "wasi", ] +[[package]] +name = "gilrs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f" +dependencies = [ + "fnv", + "gilrs-core", + "log", + "uuid", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495af945e45efd6386227613cd9fb7bd7c43d3c095040e30c5304c489e6abed5" +dependencies = [ + "core-foundation 0.10.0", + "inotify", + "io-kit-sys", + "js-sys", + "libc", + "libudev-sys", + "log", + "nix", + "uuid", + "vec_map", + "wasm-bindgen", + "web-sys", + "windows 0.58.0", +] + [[package]] name = "gl_generator" version = "0.14.0" @@ -905,6 +955,36 @@ dependencies = [ "hashbrown 0.15.1", ] +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.6.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1013,6 +1093,16 @@ dependencies = [ "redox_syscall 0.5.7", ] +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1131,7 +1221,7 @@ dependencies = [ "ascii", "block", "cocoa", - "core-foundation", + "core-foundation 0.9.4", "dirs-next", "objc", "objc-foundation", @@ -1198,6 +1288,18 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1910,6 +2012,7 @@ dependencies = [ "cc", "clap", "cpal", + "gilrs", "imgui", "imgui-wgpu", "imgui-winit-support", @@ -2175,6 +2278,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.5" @@ -2875,7 +2990,7 @@ dependencies = [ "calloop", "cfg_aliases 0.2.1", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", diff --git a/Cargo.toml b/Cargo.toml index 5b62f02..50b667c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ bitflags = "2" bytemuck = { version = "1", features = ["derive"] } clap = { version = "4", features = ["derive"] } cpal = "0.15" +gilrs = "0.11" imgui = { version = "0.12", features = ["tables-api"] } imgui-wgpu = { git = "https://github.com/SupernaviX/imgui-wgpu-rs", rev = "5bb8673" } imgui-winit-support = "0.13" diff --git a/src/app.rs b/src/app.rs index 91b9381..5213243 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,10 +1,7 @@ -use std::{ - collections::HashMap, - fmt::Debug, - sync::{Arc, RwLock}, -}; +use std::{collections::HashMap, fmt::Debug, thread}; use game::GameWindow; +use gilrs::{EventType, Gilrs}; use input::InputWindow; use winit::{ application::ApplicationHandler, @@ -14,9 +11,9 @@ use winit::{ }; use crate::{ - controller::ControllerState, - emulator::{EmulatorClient, EmulatorCommand, SimId}, - input::InputMapper, + controller::ControllerManager, + emulator::{EmulatorClient, SimId}, + input::MappingProvider, }; mod common; @@ -26,21 +23,26 @@ mod input; pub struct App { windows: HashMap>, client: EmulatorClient, - input_mapper: Arc>, - controller: ControllerState, + mappings: MappingProvider, + controllers: ControllerManager, proxy: EventLoopProxy, player_2_window: Option, } impl App { pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { - let input_mapper = Arc::new(RwLock::new(InputMapper::new())); - let controller = ControllerState::new(input_mapper.clone()); + let mappings = MappingProvider::new(); + let controllers = ControllerManager::new(client.clone(), &mappings); + { + let mappings = mappings.clone(); + let proxy = proxy.clone(); + thread::spawn(|| process_gamepad_input(mappings, proxy)); + } Self { windows: HashMap::new(), client, - input_mapper, - controller, + mappings, + controllers, proxy, player_2_window: None, } @@ -65,10 +67,7 @@ impl ApplicationHandler for App { event: WindowEvent, ) { if let WindowEvent::KeyboardInput { event, .. } = &event { - if let Some((sim_id, pressed)) = self.controller.key_event(event) { - self.client - .send_command(EmulatorCommand::SetKeys(sim_id, pressed)); - } + self.controllers.handle_key_event(event); } let Some(window) = self.windows.get_mut(&window_id) else { return; @@ -80,7 +79,7 @@ impl ApplicationHandler for App { match event { UserEvent::OpenInputWindow => { let window = - InputWindow::new(event_loop, self.input_mapper.clone(), self.proxy.clone()); + InputWindow::new(event_loop, self.mappings.clone(), self.proxy.clone()); self.windows.insert(window.id(), Box::new(window)); } UserEvent::OpenPlayer2Window => { @@ -102,6 +101,9 @@ impl ApplicationHandler for App { } self.windows.remove(&window_id); } + UserEvent::GamepadEvent(event) => { + self.controllers.handle_gamepad_event(&event); + } } } @@ -139,4 +141,24 @@ pub enum UserEvent { OpenInputWindow, OpenPlayer2Window, Close(WindowId), + GamepadEvent(gilrs::Event), +} + +fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy) { + let Ok(mut gilrs) = Gilrs::new() else { + eprintln!("could not connect gamepad listener"); + return; + }; + while let Some(event) = gilrs.next_event_blocking(None) { + if event.event == EventType::Connected { + let Some(gamepad) = gilrs.connected_gamepad(event.id) else { + continue; + }; + mappings.map_gamepad(SimId::Player1, &gamepad); + } + if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() { + // main thread has closed! we done + return; + } + } } diff --git a/src/app/input.rs b/src/app/input.rs index b39f8b1..8147f5c 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -1,7 +1,4 @@ -use std::{ - sync::{Arc, RwLock}, - time::Instant, -}; +use std::time::Instant; use winit::{ dpi::LogicalSize, @@ -11,7 +8,7 @@ use winit::{ use crate::{ emulator::{SimId, VBKey}, - input::InputMapper, + input::MappingProvider, }; use super::{ @@ -22,7 +19,7 @@ use super::{ pub struct InputWindow { window: WindowState, imgui: ImguiState, - input_mapper: Arc>, + mappings: MappingProvider, proxy: EventLoopProxy, now_binding: Option<(SimId, VBKey)>, } @@ -47,7 +44,7 @@ const KEY_NAMES: [(VBKey, &str); 14] = [ impl InputWindow { pub fn new( event_loop: &ActiveEventLoop, - input_mapper: Arc>, + mappings: MappingProvider, proxy: EventLoopProxy, ) -> Self { let window = WindowStateBuilder::new(event_loop) @@ -58,7 +55,7 @@ impl InputWindow { Self { window, imgui, - input_mapper, + mappings, now_binding: None, proxy, } @@ -89,10 +86,11 @@ impl InputWindow { let ui = context.new_frame(); let mut render_key_bindings = |sim_id: SimId| { + let mappings = self.mappings.for_sim(sim_id); if let Some(table) = ui.begin_table("controls", 2) { let binding_names = { - let mapper = self.input_mapper.read().unwrap(); - mapper.binding_names(&sim_id) + let mapping = mappings.read().unwrap(); + mapping.keyboard_mapping_names() }; ui.table_next_row(); @@ -115,8 +113,8 @@ impl InputWindow { }); ui.same_line(); if ui.button(format!("Clear##{name}")) { - let mut mapper = self.input_mapper.write().unwrap(); - mapper.clear_binding(sim_id, key); + let mut mapping = mappings.write().unwrap(); + mapping.clear_keyboard_mappings(key); } } @@ -185,8 +183,8 @@ impl InputWindow { let Some((sim_id, vb)) = self.now_binding.take() else { return; }; - let mut mapper = self.input_mapper.write().unwrap(); - mapper.bind_key(sim_id, vb, event.physical_key); + let mut mappings = self.mappings.for_sim(sim_id).write().unwrap(); + mappings.add_keyboard_mapping(vb, event.physical_key); } } diff --git a/src/controller.rs b/src/controller.rs index d10a1ab..fe2ec14 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,48 +1,128 @@ use std::sync::{Arc, RwLock}; -use winit::event::{ElementState, KeyEvent}; - -use crate::{ - emulator::{SimId, VBKey}, - input::InputMapper, +use gilrs::{ev::Code, Event as GamepadEvent, EventType, GamepadId}; +use winit::{ + event::{ElementState, KeyEvent}, + keyboard::PhysicalKey, }; -pub struct ControllerState { - input_mapper: Arc>, - pressed: Vec, +use crate::{ + emulator::{EmulatorClient, EmulatorCommand, SimId, VBKey}, + input::{InputMapping, MappingProvider}, +}; + +pub struct Controller { + pub sim_id: SimId, + state: VBKey, + mapping: Arc>, } -impl ControllerState { - pub fn new(input_mapper: Arc>) -> Self { +impl Controller { + pub fn new(sim_id: SimId, mappings: &MappingProvider) -> Self { Self { - input_mapper, - pressed: vec![VBKey::SGN; 2], + sim_id, + state: VBKey::SGN, + mapping: mappings.for_sim(sim_id).clone(), } } - pub fn key_event(&mut self, event: &KeyEvent) -> Option<(SimId, VBKey)> { - let (sim_id, input) = self.key_event_to_input(event)?; - let pressed = &mut self.pressed[sim_id.to_index()]; + pub fn key_event(&mut self, event: &KeyEvent) -> Option { + let keys = self.map_keys(&event.physical_key)?; match event.state { - ElementState::Pressed => { - if pressed.contains(input) { - return None; - } - pressed.insert(input); - Some((sim_id, *pressed)) - } - ElementState::Released => { - if !pressed.contains(input) { - return None; - } - pressed.remove(input); - Some((sim_id, *pressed)) - } + ElementState::Pressed => self.update_state(keys, VBKey::empty()), + ElementState::Released => self.update_state(VBKey::empty(), keys), } } - fn key_event_to_input(&self, event: &KeyEvent) -> Option<(SimId, VBKey)> { - let mapper = self.input_mapper.read().unwrap(); - mapper.key_event(event) + pub fn gamepad_event(&mut self, event: &GamepadEvent) -> Option { + let (pressed, released) = match event.event { + EventType::ButtonPressed(_, code) => { + let mappings = self.map_button(&event.id, &code)?; + (mappings, VBKey::empty()) + } + EventType::ButtonReleased(_, code) => { + let mappings = self.map_button(&event.id, &code)?; + (VBKey::empty(), mappings) + } + EventType::AxisChanged(_, value, code) => { + let (neg, pos) = self.map_axis(&event.id, &code)?; + let mut pressed = VBKey::empty(); + let mut released = VBKey::empty(); + if value < -0.75 { + pressed = pressed.union(neg); + } + if value > 0.75 { + pressed = pressed.union(pos); + } + if value > -0.65 { + released = released.union(neg); + } + if value < 0.65 { + released = released.union(pos); + } + (pressed, released) + } + _ => { + return None; + } + }; + self.update_state(pressed, released) + } + + fn update_state(&mut self, pressed: VBKey, released: VBKey) -> Option { + let old_state = self.state; + self.state = self.state.union(pressed).difference(released); + if self.state != old_state { + Some(self.state) + } else { + None + } + } + + fn map_keys(&self, key: &PhysicalKey) -> Option { + self.mapping.read().unwrap().map_keyboard(key) + } + + fn map_button(&self, id: &GamepadId, code: &Code) -> Option { + self.mapping.read().unwrap().map_button(id, code) + } + + fn map_axis(&self, id: &GamepadId, code: &Code) -> Option<(VBKey, VBKey)> { + self.mapping.read().unwrap().map_axis(id, code) + } +} + +pub struct ControllerManager { + client: EmulatorClient, + controllers: [Controller; 2], +} + +impl ControllerManager { + pub fn new(client: EmulatorClient, mappings: &MappingProvider) -> Self { + Self { + client, + controllers: [ + Controller::new(SimId::Player1, mappings), + Controller::new(SimId::Player2, mappings), + ], + } + } + + pub fn handle_key_event(&mut self, event: &KeyEvent) { + for controller in &mut self.controllers { + if let Some(pressed) = controller.key_event(event) { + self.client + .send_command(EmulatorCommand::SetKeys(controller.sim_id, pressed)); + } + } + } + + pub fn handle_gamepad_event(&mut self, event: &GamepadEvent) { + for controller in &mut self.controllers { + if let Some(pressed) = controller.gamepad_event(event) { + self.client + .send_command(EmulatorCommand::SetKeys(controller.sim_id, pressed)); + } + } } } diff --git a/src/input.rs b/src/input.rs index 44e626e..a5364a5 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,88 +1,167 @@ -use std::collections::HashMap; - -use winit::{ - event::KeyEvent, - keyboard::{KeyCode, PhysicalKey}, +use std::{ + collections::{hash_map::Entry, HashMap}, + sync::{Arc, RwLock}, }; +use gilrs::{ev::Code, Axis, Button, Gamepad, GamepadId}; +use winit::keyboard::{KeyCode, PhysicalKey}; + use crate::emulator::{SimId, VBKey}; -pub struct InputMapper { - vb_bindings: HashMap>, - key_bindings: HashMap, +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +struct DeviceId(u16, u16); + +pub struct GamepadMapping { + buttons: HashMap, + axes: HashMap, } -impl InputMapper { - pub fn new() -> Self { - let mut mapper = Self { - vb_bindings: HashMap::new(), - key_bindings: HashMap::new(), +impl GamepadMapping { + fn for_gamepad(gamepad: &Gamepad) -> Self { + let mut buttons = HashMap::new(); + let mut default_button = |btn: Button, key: VBKey| { + if let Some(code) = gamepad.button_code(btn) { + buttons.insert(code, key); + } }; - mapper.bind_key(SimId::Player1, VBKey::SEL, PhysicalKey::Code(KeyCode::KeyA)); - mapper.bind_key(SimId::Player1, VBKey::STA, PhysicalKey::Code(KeyCode::KeyS)); - mapper.bind_key(SimId::Player1, VBKey::B, PhysicalKey::Code(KeyCode::KeyD)); - mapper.bind_key(SimId::Player1, VBKey::A, PhysicalKey::Code(KeyCode::KeyF)); - mapper.bind_key(SimId::Player1, VBKey::LT, PhysicalKey::Code(KeyCode::KeyE)); - mapper.bind_key(SimId::Player1, VBKey::RT, PhysicalKey::Code(KeyCode::KeyR)); - mapper.bind_key(SimId::Player1, VBKey::RU, PhysicalKey::Code(KeyCode::KeyI)); - mapper.bind_key(SimId::Player1, VBKey::RL, PhysicalKey::Code(KeyCode::KeyJ)); - mapper.bind_key(SimId::Player1, VBKey::RD, PhysicalKey::Code(KeyCode::KeyK)); - mapper.bind_key(SimId::Player1, VBKey::RR, PhysicalKey::Code(KeyCode::KeyL)); - mapper.bind_key( - SimId::Player1, - VBKey::LU, - PhysicalKey::Code(KeyCode::ArrowUp), - ); - mapper.bind_key( - SimId::Player1, - VBKey::LL, - PhysicalKey::Code(KeyCode::ArrowLeft), - ); - mapper.bind_key( - SimId::Player1, - VBKey::LD, - PhysicalKey::Code(KeyCode::ArrowDown), - ); - mapper.bind_key( - SimId::Player1, - VBKey::LR, - PhysicalKey::Code(KeyCode::ArrowRight), - ); - mapper + default_button(Button::South, VBKey::A); + default_button(Button::West, VBKey::B); + default_button(Button::RightTrigger, VBKey::RT); + default_button(Button::LeftTrigger, VBKey::LT); + default_button(Button::Start, VBKey::STA); + default_button(Button::Select, VBKey::SEL); + + let mut axes = HashMap::new(); + let mut default_axis = |axis: Axis, neg: VBKey, pos: VBKey| { + if let Some(code) = gamepad.axis_code(axis) { + axes.insert(code, (neg, pos)); + } + }; + default_axis(Axis::LeftStickX, VBKey::LL, VBKey::LR); + default_axis(Axis::LeftStickY, VBKey::LD, VBKey::LU); + default_axis(Axis::RightStickX, VBKey::RL, VBKey::RR); + default_axis(Axis::RightStickY, VBKey::RD, VBKey::RU); + default_axis(Axis::DPadX, VBKey::LL, VBKey::LR); + default_axis(Axis::DPadY, VBKey::LD, VBKey::LU); + + Self { buttons, axes } + } +} + +#[derive(Default)] +pub struct InputMapping { + keys: HashMap, + gamepads: HashMap>>, +} + +impl InputMapping { + pub fn map_keyboard(&self, key: &PhysicalKey) -> Option { + self.keys.get(key).copied() } - pub fn binding_names(&self, sim_id: &SimId) -> HashMap { - let Some(bindings) = self.vb_bindings.get(sim_id) else { - return HashMap::new(); - }; - bindings - .iter() - .map(|(k, v)| { - let name = match v { - PhysicalKey::Code(code) => format!("{code:?}"), - k => format!("{:?}", k), - }; - (*k, name) + pub fn map_button(&self, id: &GamepadId, code: &Code) -> Option { + let mappings = self.gamepads.get(id)?.read().unwrap(); + mappings.buttons.get(code).copied() + } + + pub fn map_axis(&self, id: &GamepadId, code: &Code) -> Option<(VBKey, VBKey)> { + let mappings = self.gamepads.get(id)?.read().unwrap(); + mappings.axes.get(code).copied() + } + + pub fn add_keyboard_mapping(&mut self, key: VBKey, keyboard_key: PhysicalKey) { + let entry = self.keys.entry(keyboard_key).or_insert(VBKey::empty()); + *entry = entry.union(key); + } + + pub fn clear_keyboard_mappings(&mut self, key: VBKey) { + self.keys.retain(|_, keys| { + *keys = keys.difference(key); + *keys != VBKey::empty() + }); + } + + pub fn keyboard_mapping_names(&self) -> HashMap { + let mut results: HashMap> = HashMap::new(); + for (keyboard_key, keys) in &self.keys { + let name = match keyboard_key { + PhysicalKey::Code(code) => format!("{code:?}"), + k => format!("{:?}", k), + }; + for key in keys.iter() { + results.entry(key).or_default().push(name.clone()); + } + } + results + .into_iter() + .map(|(k, mut v)| { + v.sort(); + (k, v.join(", ")) }) .collect() } +} - pub fn bind_key(&mut self, sim_id: SimId, vb: VBKey, key: PhysicalKey) { - let vb_bindings = self.vb_bindings.entry(sim_id).or_default(); - if let Some(old) = vb_bindings.insert(vb, key) { - self.key_bindings.remove(&old); - } - self.key_bindings.insert(key, (sim_id, vb)); - } +#[derive(Clone)] +pub struct MappingProvider { + device_mappings: Arc>>>>, + sim_mappings: HashMap>>, +} - pub fn clear_binding(&mut self, sim_id: SimId, vb: VBKey) { - let vb_bindings = self.vb_bindings.entry(sim_id).or_default(); - if let Some(old) = vb_bindings.remove(&vb) { - self.key_bindings.remove(&old); +impl MappingProvider { + pub fn new() -> Self { + let mut mappings = HashMap::new(); + + let mut p1_mappings = InputMapping::default(); + let p2_mappings = InputMapping::default(); + let mut default_key = |code, key| { + p1_mappings.add_keyboard_mapping(key, PhysicalKey::Code(code)); + }; + default_key(KeyCode::KeyA, VBKey::SEL); + default_key(KeyCode::KeyS, VBKey::STA); + default_key(KeyCode::KeyD, VBKey::B); + default_key(KeyCode::KeyF, VBKey::A); + default_key(KeyCode::KeyE, VBKey::LT); + default_key(KeyCode::KeyR, VBKey::RT); + default_key(KeyCode::KeyI, VBKey::RU); + default_key(KeyCode::KeyJ, VBKey::RL); + default_key(KeyCode::KeyK, VBKey::RD); + default_key(KeyCode::KeyL, VBKey::RR); + default_key(KeyCode::ArrowUp, VBKey::LU); + default_key(KeyCode::ArrowLeft, VBKey::LL); + default_key(KeyCode::ArrowDown, VBKey::LD); + default_key(KeyCode::ArrowRight, VBKey::LR); + + mappings.insert(SimId::Player1, Arc::new(RwLock::new(p1_mappings))); + mappings.insert(SimId::Player2, Arc::new(RwLock::new(p2_mappings))); + Self { + device_mappings: Arc::new(RwLock::new(HashMap::new())), + sim_mappings: mappings, } } - pub fn key_event(&self, event: &KeyEvent) -> Option<(SimId, VBKey)> { - self.key_bindings.get(&event.physical_key).copied() + pub fn for_sim(&self, sim_id: SimId) -> &Arc> { + self.sim_mappings.get(&sim_id).unwrap() + } + + pub fn map_gamepad(&self, sim_id: SimId, gamepad: &Gamepad) { + let device_id = DeviceId( + gamepad.vendor_id().unwrap_or_default(), + gamepad.product_id().unwrap_or_default(), + ); + let mut lock = self.device_mappings.write().unwrap(); + let gamepad_mapping = match lock.entry(device_id) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => { + let mappings = GamepadMapping::for_gamepad(gamepad); + entry.insert(Arc::new(RwLock::new(mappings))).clone() + } + }; + drop(lock); + self.for_sim(sim_id) + .write() + .unwrap() + .gamepads + .insert(gamepad.id(), gamepad_mapping); } } From 167acbde15d5782144f85975a94e49f8862fee59 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Wed, 20 Nov 2024 00:02:31 -0500 Subject: [PATCH 12/21] VIP performance improvements --- shrooms-vb-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shrooms-vb-core b/shrooms-vb-core index bc86464..f45636a 160000 --- a/shrooms-vb-core +++ b/shrooms-vb-core @@ -1 +1 @@ -Subproject commit bc864644f791e17d0060a60ebf1eea938bcf80af +Subproject commit f45636a491a50c5847b92a51912e6e19f21af98f From 0f536fdd8817c14c1de810ec32c248722a10aac5 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sat, 23 Nov 2024 17:36:24 -0500 Subject: [PATCH 13/21] Use CPAL fork which supports device switching --- Cargo.lock | 33 ++++++++++++++++++++++++++++----- Cargo.toml | 2 +- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b47fc70..c4ac34f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,7 +213,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.11.0", "proc-macro2", "quote", "regex", @@ -561,8 +561,7 @@ dependencies = [ [[package]] name = "cpal" version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +source = "git+https://github.com/sidit77/cpal.git?rev=66ed6be#66ed6bec97f25ec7f02a82f50d1aa9aef733a58e" dependencies = [ "alsa", "core-foundation-sys", @@ -2684,6 +2683,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" dependencies = [ "windows-core 0.54.0", + "windows-implement 0.53.0", + "windows-interface 0.53.0", "windows-targets 0.52.6", ] @@ -2713,13 +2714,24 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.58.0", + "windows-interface 0.58.0", "windows-result 0.2.0", "windows-strings", "windows-targets 0.52.6", ] +[[package]] +name = "windows-implement" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -2731,6 +2743,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-interface" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-interface" version = "0.58.0" diff --git a/Cargo.toml b/Cargo.toml index 50b667c..775d3a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ anyhow = "1" bitflags = "2" bytemuck = { version = "1", features = ["derive"] } clap = { version = "4", features = ["derive"] } -cpal = "0.15" +cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" } gilrs = "0.11" imgui = { version = "0.12", features = ["tables-api"] } imgui-wgpu = { git = "https://github.com/SupernaviX/imgui-wgpu-rs", rev = "5bb8673" } From 4b34d138acbc6c5f07f3e56e47b14f83abba9e67 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sat, 23 Nov 2024 20:17:28 -0500 Subject: [PATCH 14/21] Support muting sims --- src/app/game.rs | 12 +++++++++ src/emulator.rs | 43 +++++++++++++++++++++++++++------ src/emulator/shrooms_vb_core.rs | 1 + 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/app/game.rs b/src/app/game.rs index 8960450..432b677 100644 --- a/src/app/game.rs +++ b/src/app/game.rs @@ -238,6 +238,18 @@ impl GameWindow { } } }); + ui.menu("Audio", || { + let p1_enabled = self.client.is_audio_enabled(SimId::Player1); + let p2_enabled = self.client.is_audio_enabled(SimId::Player2); + if ui.menu_item_config("Player 1").selected(p1_enabled).build() { + self.client + .send_command(EmulatorCommand::SetAudioEnabled(!p1_enabled, p2_enabled)); + } + if ui.menu_item_config("Player 2").selected(p2_enabled).build() { + self.client + .send_command(EmulatorCommand::SetAudioEnabled(p1_enabled, !p2_enabled)); + } + }); ui.menu("Input", || { if ui.menu_item("Bind Inputs") { self.proxy.send_event(UserEvent::OpenInputWindow).unwrap(); diff --git a/src/emulator.rs b/src/emulator.rs index c0638a8..fd04520 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -12,8 +12,8 @@ use std::{ use anyhow::Result; use crate::{audio::Audio, graphics::TextureSink}; -use shrooms_vb_core::Sim; pub use shrooms_vb_core::VBKey; +use shrooms_vb_core::{Sim, EXPECTED_FRAME_SIZE}; mod shrooms_vb_core; @@ -23,6 +23,7 @@ pub struct EmulatorBuilder { sim_count: Arc, running: Arc<[AtomicBool; 2]>, has_game: Arc<[AtomicBool; 2]>, + audio_on: Arc<[AtomicBool; 2]>, linked: Arc, } @@ -52,6 +53,7 @@ impl EmulatorBuilder { sim_count: Arc::new(AtomicUsize::new(0)), running: Arc::new([AtomicBool::new(false), AtomicBool::new(false)]), has_game: Arc::new([AtomicBool::new(false), AtomicBool::new(false)]), + audio_on: Arc::new([AtomicBool::new(true), AtomicBool::new(true)]), linked: Arc::new(AtomicBool::new(false)), }; let client = EmulatorClient { @@ -59,6 +61,7 @@ impl EmulatorBuilder { sim_count: builder.sim_count.clone(), running: builder.running.clone(), has_game: builder.has_game.clone(), + audio_on: builder.audio_on.clone(), linked: builder.linked.clone(), }; (builder, client) @@ -77,6 +80,7 @@ impl EmulatorBuilder { self.sim_count, self.running, self.has_game, + self.audio_on, self.linked, )?; if let Some(path) = self.rom { @@ -93,6 +97,7 @@ pub struct Emulator { sim_count: Arc, running: Arc<[AtomicBool; 2]>, has_game: Arc<[AtomicBool; 2]>, + audio_on: Arc<[AtomicBool; 2]>, linked: Arc, renderers: HashMap, } @@ -103,6 +108,7 @@ impl Emulator { sim_count: Arc, running: Arc<[AtomicBool; 2]>, has_game: Arc<[AtomicBool; 2]>, + audio_on: Arc<[AtomicBool; 2]>, linked: Arc, ) -> Result { Ok(Self { @@ -112,6 +118,7 @@ impl Emulator { sim_count, running, has_game, + audio_on, linked, renderers: HashMap::new(), }) @@ -210,15 +217,28 @@ impl Emulator { } } } - let weight = 1.0 / self.sims.len() as f32; - for sim in self.sims.iter_mut() { - sim.read_samples(&mut audio_samples, weight); + let p1_audio = + p1_running && self.audio_on[SimId::Player1.to_index()].load(Ordering::Acquire); + let p2_audio = + p2_running && self.audio_on[SimId::Player2.to_index()].load(Ordering::Acquire); + let weight = if p1_audio && p2_audio { 0.5 } else { 1.0 }; + if p1_audio { + if let Some(sim) = self.sims.get_mut(SimId::Player1.to_index()) { + sim.read_samples(&mut audio_samples, weight); + } } - if !audio_samples.is_empty() { + if p2_audio { + if let Some(sim) = self.sims.get_mut(SimId::Player2.to_index()) { + sim.read_samples(&mut audio_samples, weight); + } + } + if audio_samples.is_empty() { + audio_samples.resize(EXPECTED_FRAME_SIZE, 0.0); + } else { idle = false; - self.audio.update(&audio_samples); - audio_samples.clear(); } + self.audio.update(&audio_samples); + audio_samples.clear(); if idle { // The game is paused, and we have output all the video/audio we have. // Block the thread until a new command comes in. @@ -274,6 +294,10 @@ impl Emulator { } } } + EmulatorCommand::SetAudioEnabled(p1, p2) => { + self.audio_on[SimId::Player1.to_index()].store(p1, Ordering::Release); + self.audio_on[SimId::Player2.to_index()].store(p2, Ordering::Release); + } EmulatorCommand::Link => { self.link_sims(); } @@ -302,6 +326,7 @@ pub enum EmulatorCommand { StopSecondSim, Pause, Resume, + SetAudioEnabled(bool, bool), Link, Unlink, Reset(SimId), @@ -314,6 +339,7 @@ pub struct EmulatorClient { sim_count: Arc, running: Arc<[AtomicBool; 2]>, has_game: Arc<[AtomicBool; 2]>, + audio_on: Arc<[AtomicBool; 2]>, linked: Arc, } @@ -330,6 +356,9 @@ impl EmulatorClient { pub fn are_sims_linked(&self) -> bool { self.linked.load(Ordering::Acquire) } + pub fn is_audio_enabled(&self, sim_id: SimId) -> bool { + self.audio_on[sim_id.to_index()].load(Ordering::Acquire) + } pub fn send_command(&self, command: EmulatorCommand) { if let Err(err) = self.queue.send(command) { eprintln!( diff --git a/src/emulator/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs index a241772..fe2fa27 100644 --- a/src/emulator/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -120,6 +120,7 @@ extern "C" fn on_frame(sim: *mut VB) -> i32 { const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4; const AUDIO_CAPACITY_FLOATS: usize = AUDIO_CAPACITY_SAMPLES * 2; +pub const EXPECTED_FRAME_SIZE: usize = 834 * 2; struct VBState { frame_seen: bool, From 08680b27ae8dfbb3fe91b277225adc64a76adbcf Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Tue, 26 Nov 2024 00:38:03 -0500 Subject: [PATCH 15/21] Out with dear imgui, in with egui --- Cargo.lock | 1492 +++++++++++++++++++++++++++++++------ Cargo.toml | 11 +- src/app.rs | 164 ---- src/app/common.rs | 264 ------- src/app/game.rs | 408 ---------- src/app/input.rs | 220 ------ src/application.rs | 315 ++++++++ src/main.rs | 7 +- src/window.rs | 157 ++++ src/window/game.rs | 174 +++++ src/window/game_screen.rs | 194 +++++ src/window/input.rs | 133 ++++ 12 files changed, 2241 insertions(+), 1298 deletions(-) delete mode 100644 src/app.rs delete mode 100644 src/app/common.rs delete mode 100644 src/app/game.rs delete mode 100644 src/app/input.rs create mode 100644 src/application.rs create mode 100644 src/window.rs create mode 100644 src/window/game.rs create mode 100644 src/window/game_screen.rs create mode 100644 src/window/input.rs diff --git a/Cargo.lock b/Cargo.lock index c4ac34f..4990382 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,21 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +[[package]] +name = "arboard" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +dependencies = [ + "clipboard-win", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "x11rb", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -177,12 +192,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - [[package]] name = "ash" version = "0.38.0+1.3.281" @@ -192,6 +201,182 @@ dependencies = [ "libloading", ] +[[package]] +name = "ashpd" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -213,29 +398,29 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.87", ] [[package]] name = "bit-set" -version = "0.8.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.8.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" [[package]] name = "bitflags" @@ -264,6 +449,19 @@ dependencies = [ "objc2", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -287,9 +485,15 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.8.0" @@ -366,12 +570,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chlorine" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e10e7569f6ca78ef7664d7d651115172d4875c4410c050306bccde856a99a49" - [[package]] name = "clang-sys" version = "1.8.1" @@ -414,7 +612,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -424,33 +622,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] -name = "cocoa" -version = "0.25.0" +name = "clipboard-win" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation 0.9.4", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation 0.9.4", - "core-graphics-types", - "libc", - "objc", + "error-code", ] [[package]] @@ -469,6 +646,37 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "combine" version = "4.6.7" @@ -592,39 +800,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "d3d12" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" +dependencies = [ + "bitflags 2.6.0", + "libloading", + "winapi", +] + [[package]] name = "dasp_sample" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "dlib" version = "0.5.2" @@ -655,12 +864,164 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +[[package]] +name = "ecolor" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "egui" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" +dependencies = [ + "ahash", + "emath", + "epaint", + "log", + "nohash-hasher", +] + +[[package]] +name = "egui-wgpu" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00fd5d06d8405397e64a928fa0ef3934b3c30273ea7603e3dc4627b1f7a1a82" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "thiserror", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a9c430f4f816340e8e8c1b20eec274186b1be6bc4c7dfc467ed50d57abc36c6" +dependencies = [ + "ahash", + "arboard", + "egui", + "log", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_extras" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3c1f5cd8dfe2ade470a218696c66cf556fcfd701e7830fa2e9f4428292a2a1" +dependencies = [ + "ahash", + "egui", + "enum-map", + "log", + "mime_guess2", +] + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "emath" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "epaint" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a32af8da821bd4f43f2c137e295459ee2e1661d87ca8779dfa0eaf45d870e20f" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483440db0b7993cf77a20314f08311dbe95675092405518c0677aa08c151a3ea" + [[package]] name = "equivalent" version = "1.0.1" @@ -677,6 +1038,39 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + [[package]] name = "fnv" version = "1.0.7" @@ -701,7 +1095,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -710,6 +1104,89 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -784,9 +1261,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "glow" -version = "0.14.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" dependencies = [ "js-sys", "slotmap", @@ -824,14 +1301,15 @@ dependencies = [ [[package]] name = "gpu-allocator" -version = "0.27.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" dependencies = [ "log", "presser", "thiserror", - "windows 0.58.0", + "winapi", + "windows 0.52.0", ] [[package]] @@ -870,6 +1348,21 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.6.0", + "com", + "libc", + "libloading", + "thiserror", + "widestring", + "winapi", +] + [[package]] name = "heck" version = "0.5.0" @@ -882,6 +1375,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hexf-parse" version = "0.2.1" @@ -898,50 +1397,142 @@ dependencies = [ ] [[package]] -name = "imgui" -version = "0.12.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8addafa5cecf0515812226e806913814e02ce38d10215778082af5174abe5669" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "imgui-sys", - "mint", - "parking_lot", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "imgui-sys" -version = "0.12.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead193f9f4b60398e8b8f4ab1483e2321640d87aeebdaa3e5f44c55633ccd804" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "cc", - "cfg-if", - "chlorine", - "mint", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "imgui-wgpu" -version = "0.25.0" -source = "git+https://github.com/SupernaviX/imgui-wgpu-rs?rev=5bb8673#5bb8673e256b6b5ad497a3baa2dc151545025f12" +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "bytemuck", - "imgui", - "log", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", "smallvec", - "wgpu", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "imgui-winit-support" -version = "0.13.0" +name = "icu_normalizer_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7fcccfa9efab56c94274c0fec9939bb14149342b49e6a425883a5b7dda6a3f" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "imgui", - "winit", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -990,15 +1581,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -1108,6 +1690,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "litrs" version = "0.4.1" @@ -1163,6 +1751,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.29.0" @@ -1178,23 +1775,33 @@ dependencies = [ "paste", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess2" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a3333bb1609500601edc766a39b4c1772874a4ce26022f4d866854dc020c41" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "mint" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" - [[package]] name = "naga" -version = "23.0.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ "arrayvec", "bit-set", @@ -1211,29 +1818,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "native-dialog" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e7038885d2aeab236bd60da9e159a5967b47cde3292da3b15ff1bec27c039f" -dependencies = [ - "ascii", - "block", - "cocoa", - "core-foundation 0.9.4", - "dirs-next", - "objc", - "objc-foundation", - "objc_id", - "once_cell", - "raw-window-handle 0.5.2", - "thiserror", - "versions", - "wfd", - "which", - "winapi", -] - [[package]] name = "ndk" version = "0.8.0" @@ -1259,7 +1843,7 @@ dependencies = [ "log", "ndk-sys 0.6.0+11769913", "num_enum", - "raw-window-handle 0.6.2", + "raw-window-handle", "thiserror", ] @@ -1297,8 +1881,15 @@ dependencies = [ "cfg-if", "cfg_aliases 0.2.1", "libc", + "memoffset", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -1326,7 +1917,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1365,7 +1956,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1377,17 +1968,6 @@ dependencies = [ "malloc_buf", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -1591,15 +2171,6 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "oboe" version = "0.6.1" @@ -1638,6 +2209,16 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "owned_ttf_parser" version = "0.25.0" @@ -1647,6 +2228,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1699,7 +2286,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -1708,6 +2295,23 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -1729,12 +2333,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "pollster" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "presser" version = "0.3.1" @@ -1792,18 +2411,42 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - [[package]] name = "raw-window-handle" version = "0.6.2" @@ -1837,17 +2480,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" version = "1.11.1" @@ -1883,6 +2515,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rfd" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f6f80a9b882647d9014673ca9925d30ffc9750f2eed2b4490e189eaebd01e8" +dependencies = [ + "ashpd", + "block2", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "pollster 0.3.0", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.48.0", +] + [[package]] name = "rtrb" version = "0.3.1" @@ -1992,7 +2646,18 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] @@ -2011,15 +2676,16 @@ dependencies = [ "cc", "clap", "cpal", + "egui", + "egui-wgpu", + "egui-winit", + "egui_extras", "gilrs", - "imgui", - "imgui-wgpu", - "imgui-winit-support", - "itertools 0.13.0", - "native-dialog", + "itertools", "num-derive", "num-traits", - "pollster", + "pollster 0.4.0", + "rfd", "rtrb", "rubato", "thread-priority", @@ -2028,6 +2694,15 @@ dependencies = [ "winit", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -2077,6 +2752,17 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + [[package]] name = "smol_str" version = "0.2.2" @@ -2095,6 +2781,12 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2119,6 +2811,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.87" @@ -2130,6 +2833,30 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -2156,7 +2883,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2198,6 +2925,16 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "toml_datetime" version = "0.6.8" @@ -2222,14 +2959,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] [[package]] name = "transpose" @@ -2247,6 +2999,32 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + [[package]] name = "unicode-ident" version = "1.0.13" @@ -2271,6 +3049,36 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2295,16 +3103,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "versions" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c73a36bc44e3039f51fbee93e39f41225f6b17b380eb70cc2aab942df06b34dd" -dependencies = [ - "itertools 0.11.0", - "nom", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -2343,7 +3141,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -2377,7 +3175,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2518,20 +3316,28 @@ dependencies = [ ] [[package]] -name = "wfd" -version = "0.1.7" +name = "webbrowser" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e713040b67aae5bf1a0ae3e1ebba8cc29ab2b90da9aa1bff6e09031a8a41d7a8" +checksum = "2e5f07fb9bc8de2ddfe6b24a71a75430673fd679e568c48b52716cef1cfae923" dependencies = [ - "libc", - "winapi", + "block2", + "core-foundation 0.10.0", + "home", + "jni", + "log", + "ndk-context", + "objc2", + "objc2-foundation", + "url", + "web-sys", ] [[package]] name = "wgpu" -version = "23.0.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ab52f2d3d18b70d5ab8dd270a1cff3ebe6dbe4a7d13c1cc2557138a9777fdc" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" dependencies = [ "arrayvec", "cfg_aliases 0.1.1", @@ -2541,7 +3347,7 @@ dependencies = [ "naga", "parking_lot", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "smallvec", "static_assertions", "wasm-bindgen", @@ -2554,9 +3360,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "23.0.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c68e7b6322a03ee5b83fcd92caeac5c2a932f6457818179f4652ad2a9c065" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", "bit-vec", @@ -2569,7 +3375,7 @@ dependencies = [ "once_cell", "parking_lot", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "rustc-hash", "smallvec", "thiserror", @@ -2579,9 +3385,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "23.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de6e7266b869de56c7e3ed72a954899f71d14fec6cc81c102b7530b92947601b" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" dependencies = [ "android_system_properties", "arrayvec", @@ -2589,14 +3395,15 @@ dependencies = [ "bit-set", "bitflags 2.6.0", "block", - "bytemuck", "cfg_aliases 0.1.1", "core-graphics-types", + "d3d12", "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", + "hassle-rs", "js-sys", "khronos-egl", "libc", @@ -2610,7 +3417,7 @@ dependencies = [ "parking_lot", "profiling", "range-alloc", - "raw-window-handle 0.6.2", + "raw-window-handle", "renderdoc-sys", "rustc-hash", "smallvec", @@ -2618,15 +3425,14 @@ dependencies = [ "wasm-bindgen", "web-sys", "wgpu-types", - "windows 0.58.0", - "windows-core 0.58.0", + "winapi", ] [[package]] name = "wgpu-types" -version = "23.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" dependencies = [ "bitflags 2.6.0", "js-sys", @@ -2634,16 +3440,10 @@ dependencies = [ ] [[package]] -name = "which" -version = "4.4.2" +name = "widestring" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -2676,6 +3476,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.54.0" @@ -2698,6 +3508,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.54.0" @@ -2729,7 +3548,7 @@ checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2740,7 +3559,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2751,7 +3570,7 @@ checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2762,7 +3581,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", ] [[package]] @@ -2802,6 +3621,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -3028,7 +3856,7 @@ dependencies = [ "orbclient", "percent-encoding", "pin-project", - "raw-window-handle 0.6.2", + "raw-window-handle", "redox_syscall 0.4.1", "rustix", "sctk-adwaita", @@ -3059,6 +3887,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "x11-dl" version = "2.21.0" @@ -3097,6 +3937,16 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "xkbcommon-dl" version = "0.4.2" @@ -3122,12 +3972,100 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1162094dc63b1629fcc44150bcceeaa80798cd28bcbe7fa987b65a034c258608" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-util", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd2dcdce3e2727f7d74b7e33b5a89539b3cc31049562137faf7ae4eb86cd16d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.87", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -3139,5 +4077,91 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "zvariant" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1200ee6ac32f1e5a312e455a949a4794855515d34f9909f4a3e082d14e1a56f" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "url", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687e3b97fae6c9104fbbd36c73d27d149abf04fb874e2efbd84838763daa8916" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.87", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d1d011a38f12360e5fcccceeff5e2c42a8eb7f27f0dcba97a0862ede05c9c6" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn 2.0.87", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 775d3a4..2db1fb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,19 +9,20 @@ bitflags = "2" bytemuck = { version = "1", features = ["derive"] } clap = { version = "4", features = ["derive"] } cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" } +egui = "0.29" +egui_extras = "0.29" +egui-winit = "0.29" +egui-wgpu = { version = "0.29", features = ["winit"] } gilrs = "0.11" -imgui = { version = "0.12", features = ["tables-api"] } -imgui-wgpu = { git = "https://github.com/SupernaviX/imgui-wgpu-rs", rev = "5bb8673" } -imgui-winit-support = "0.13" itertools = "0.13" -native-dialog = "0.7" num-derive = "0.4" num-traits = "0.2" pollster = "0.4" +rfd = "0.15" rtrb = "0.3" rubato = "0.16" thread-priority = "1" -wgpu = "23.0" +wgpu = "22.1" winit = "0.30" [target.'cfg(windows)'.dependencies] diff --git a/src/app.rs b/src/app.rs deleted file mode 100644 index 5213243..0000000 --- a/src/app.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::{collections::HashMap, fmt::Debug, thread}; - -use game::GameWindow; -use gilrs::{EventType, Gilrs}; -use input::InputWindow; -use winit::{ - application::ApplicationHandler, - event::{Event, WindowEvent}, - event_loop::{ActiveEventLoop, EventLoopProxy}, - window::WindowId, -}; - -use crate::{ - controller::ControllerManager, - emulator::{EmulatorClient, SimId}, - input::MappingProvider, -}; - -mod common; -mod game; -mod input; - -pub struct App { - windows: HashMap>, - client: EmulatorClient, - mappings: MappingProvider, - controllers: ControllerManager, - proxy: EventLoopProxy, - player_2_window: Option, -} - -impl App { - pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { - let mappings = MappingProvider::new(); - let controllers = ControllerManager::new(client.clone(), &mappings); - { - let mappings = mappings.clone(); - let proxy = proxy.clone(); - thread::spawn(|| process_gamepad_input(mappings, proxy)); - } - Self { - windows: HashMap::new(), - client, - mappings, - controllers, - proxy, - player_2_window: None, - } - } -} - -impl ApplicationHandler for App { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let window = GameWindow::new( - event_loop, - SimId::Player1, - self.client.clone(), - self.proxy.clone(), - ); - self.windows.insert(window.id(), Box::new(window)); - } - - fn window_event( - &mut self, - event_loop: &ActiveEventLoop, - window_id: WindowId, - event: WindowEvent, - ) { - if let WindowEvent::KeyboardInput { event, .. } = &event { - self.controllers.handle_key_event(event); - } - let Some(window) = self.windows.get_mut(&window_id) else { - return; - }; - window.handle_event(event_loop, &Event::WindowEvent { window_id, event }); - } - - fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { - match event { - UserEvent::OpenInputWindow => { - let window = - InputWindow::new(event_loop, self.mappings.clone(), self.proxy.clone()); - self.windows.insert(window.id(), Box::new(window)); - } - UserEvent::OpenPlayer2Window => { - if self.player_2_window.is_some() { - return; - } - let window = GameWindow::new( - event_loop, - SimId::Player2, - self.client.clone(), - self.proxy.clone(), - ); - self.player_2_window = Some(window.id()); - self.windows.insert(window.id(), Box::new(window)); - } - UserEvent::Close(window_id) => { - if self.player_2_window == Some(window_id) { - self.player_2_window.take(); - } - self.windows.remove(&window_id); - } - UserEvent::GamepadEvent(event) => { - self.controllers.handle_gamepad_event(&event); - } - } - } - - fn device_event( - &mut self, - event_loop: &ActiveEventLoop, - device_id: winit::event::DeviceId, - event: winit::event::DeviceEvent, - ) { - for window in self.windows.values_mut() { - window.handle_event( - event_loop, - &Event::DeviceEvent { - device_id, - event: event.clone(), - }, - ); - } - } - - fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - for window in self.windows.values_mut() { - window.handle_event(event_loop, &Event::AboutToWait); - } - } -} - -pub trait AppWindow { - fn id(&self) -> WindowId; - fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event); -} - -#[derive(Debug)] -pub enum UserEvent { - OpenInputWindow, - OpenPlayer2Window, - Close(WindowId), - GamepadEvent(gilrs::Event), -} - -fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy) { - let Ok(mut gilrs) = Gilrs::new() else { - eprintln!("could not connect gamepad listener"); - return; - }; - while let Some(event) = gilrs.next_event_blocking(None) { - if event.event == EventType::Connected { - let Some(gamepad) = gilrs.connected_gamepad(event.id) else { - continue; - }; - mappings.map_gamepad(SimId::Player1, &gamepad); - } - if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() { - // main thread has closed! we done - return; - } - } -} diff --git a/src/app/common.rs b/src/app/common.rs deleted file mode 100644 index 396acb2..0000000 --- a/src/app/common.rs +++ /dev/null @@ -1,264 +0,0 @@ -use std::{ - ops::{Deref, DerefMut}, - sync::Arc, - time::Instant, -}; - -use imgui::{FontSource, MouseCursor, SuspendedContext, WindowToken}; -use imgui_wgpu::{Renderer, RendererConfig}; -use imgui_winit_support::WinitPlatform; -use pollster::block_on; -#[cfg(target_os = "windows")] -use winit::platform::windows::{CornerPreference, WindowAttributesExtWindows as _}; -use winit::{ - dpi::{LogicalSize, PhysicalSize, Size}, - event_loop::ActiveEventLoop, - window::{Window, WindowAttributes}, -}; - -pub struct WindowStateBuilder<'a> { - event_loop: &'a ActiveEventLoop, - attributes: WindowAttributes, -} -impl<'a> WindowStateBuilder<'a> { - pub fn new(event_loop: &'a ActiveEventLoop) -> Self { - let attributes = Window::default_attributes(); - #[cfg(target_os = "windows")] - let attributes = attributes.with_corner_preference(CornerPreference::DoNotRound); - Self { - event_loop, - attributes, - } - } - - pub fn with_title>(self, title: T) -> Self { - Self { - attributes: self.attributes.with_title(title), - ..self - } - } - - pub fn with_inner_size>(self, size: S) -> Self { - Self { - attributes: self.attributes.with_inner_size(size), - ..self - } - } - - pub fn build(self) -> WindowState { - WindowState::new(self.event_loop, self.attributes) - } -} - -#[derive(Debug)] -pub struct WindowState { - pub device: wgpu::Device, - pub queue: Arc, - pub window: Arc, - pub surface_desc: wgpu::SurfaceConfiguration, - pub surface: wgpu::Surface<'static>, - pub hidpi_factor: f64, - pub minimized: bool, -} - -impl WindowState { - fn new(event_loop: &ActiveEventLoop, attributes: WindowAttributes) -> Self { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - let window = Arc::new(event_loop.create_window(attributes).unwrap()); - - let size = window.inner_size(); - let hidpi_factor = window.scale_factor(); - let surface = instance.create_surface(window.clone()).unwrap(); - - let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .unwrap(); - - let (device, queue) = - block_on(adapter.request_device(&wgpu::DeviceDescriptor::default(), None)).unwrap(); - let queue = Arc::new(queue); - - // Set up swap chain - let surface_desc = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Fifo, - desired_maximum_frame_latency: 2, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![wgpu::TextureFormat::Bgra8Unorm], - }; - - surface.configure(&device, &surface_desc); - - Self { - device, - queue, - window, - surface_desc, - surface, - hidpi_factor, - minimized: false, - } - } - - pub fn logical_size(&self) -> LogicalSize { - PhysicalSize::new(self.surface_desc.width, self.surface_desc.height) - .to_logical(self.hidpi_factor) - } - - pub fn handle_resize(&mut self, size: &PhysicalSize) { - if size.width > 0 && size.height > 0 { - self.minimized = false; - self.surface_desc.width = size.width; - self.surface_desc.height = size.height; - self.surface.configure(&self.device, &self.surface_desc); - } else { - self.minimized = true; - } - } -} - -pub struct ImguiState { - pub context: ContextGuard, - pub platform: WinitPlatform, - pub renderer: Renderer, - pub clear_color: wgpu::Color, - pub last_frame: Instant, - pub last_cursor: Option, -} -impl ImguiState { - pub fn new(window: &WindowState) -> Self { - let mut context_guard = ContextGuard::new(); - let mut context = context_guard.lock().unwrap(); - - let mut platform = imgui_winit_support::WinitPlatform::new(&mut context); - platform.attach_window( - context.io_mut(), - &window.window, - imgui_winit_support::HiDpiMode::Default, - ); - context.set_ini_filename(None); - - let font_size = (16.0 * window.hidpi_factor) as f32; - context.io_mut().font_global_scale = (1.0 / window.hidpi_factor) as f32; - - context.fonts().add_font(&[FontSource::TtfData { - data: include_bytes!("../../assets/selawk.ttf"), - size_pixels: font_size, - config: Some(imgui::FontConfig { - oversample_h: 1, - pixel_snap_h: true, - size_pixels: font_size, - ..Default::default() - }), - }]); - - let style = context.style_mut(); - style.use_light_colors(); - - // - // Set up dear imgui wgpu renderer - // - let renderer_config = RendererConfig { - texture_format: window.surface_desc.format, - ..Default::default() - }; - - let renderer = Renderer::new(&mut context, &window.device, &window.queue, renderer_config); - - let last_frame = Instant::now(); - let last_cursor = None; - - drop(context); - Self { - context: context_guard, - platform, - renderer, - clear_color: wgpu::Color::BLACK, - last_frame, - last_cursor, - } - } -} - -pub struct ContextGuard { - value: Option, -} - -impl ContextGuard { - fn new() -> Self { - Self { - value: Some(SuspendedContext::create()), - } - } - - pub fn lock(&mut self) -> Option> { - let sus = self.value.take()?; - match sus.activate() { - Ok(ctx) => Some(ContextLock { - ctx: Some(ctx), - holder: self, - }), - Err(sus) => { - self.value = Some(sus); - None - } - } - } -} - -pub struct ContextLock<'a> { - ctx: Option, - holder: &'a mut ContextGuard, -} - -impl<'a> Deref for ContextLock<'a> { - type Target = imgui::Context; - fn deref(&self) -> &Self::Target { - self.ctx.as_ref().unwrap() - } -} - -impl<'a> DerefMut for ContextLock<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.ctx.as_mut().unwrap() - } -} - -impl<'a> Drop for ContextLock<'a> { - fn drop(&mut self) { - self.holder.value = self.ctx.take().map(|c| c.suspend()) - } -} - -pub trait UiExt { - fn fullscreen_window(&self) -> Option>; - fn right_align_text>(&self, text: T, space: f32); -} - -impl UiExt for imgui::Ui { - fn fullscreen_window(&self) -> Option> { - self.window("fullscreen") - .position([0.0, 0.0], imgui::Condition::Always) - .size(self.io().display_size, imgui::Condition::Always) - .flags(imgui::WindowFlags::NO_DECORATION) - .begin() - } - - fn right_align_text>(&self, text: T, space: f32) { - let width = self.calc_text_size(text.as_ref())[0]; - let [left, y] = self.cursor_pos(); - let right = left + space; - self.set_cursor_pos([right - width, y]); - self.text(text); - } -} diff --git a/src/app/game.rs b/src/app/game.rs deleted file mode 100644 index 432b677..0000000 --- a/src/app/game.rs +++ /dev/null @@ -1,408 +0,0 @@ -use std::time::Instant; -use wgpu::util::DeviceExt as _; -use winit::{ - dpi::LogicalSize, - event::{Event, WindowEvent}, - event_loop::{ActiveEventLoop, EventLoopProxy}, - window::WindowId, -}; - -use crate::{ - emulator::{EmulatorClient, EmulatorCommand, SimId}, - graphics::TextureSink, -}; - -use super::{ - common::{ImguiState, WindowState, WindowStateBuilder}, - AppWindow, UserEvent, -}; - -pub struct GameWindow { - window: WindowState, - imgui: ImguiState, - pipeline: wgpu::RenderPipeline, - bind_group: wgpu::BindGroup, - sim_id: SimId, - client: EmulatorClient, - proxy: EventLoopProxy, - paused_due_to_minimize: bool, -} - -impl GameWindow { - pub fn new( - event_loop: &ActiveEventLoop, - sim_id: SimId, - client: EmulatorClient, - proxy: EventLoopProxy, - ) -> Self { - let title = if sim_id == SimId::Player2 { - "Shrooms VB (Player 2)" - } else { - "Shrooms VB" - }; - let window = WindowStateBuilder::new(event_loop) - .with_title(title) - .with_inner_size(LogicalSize::new(384, 244)) - .build(); - let device = &window.device; - - let (sink, texture_view) = TextureSink::new(device, window.queue.clone()); - client.send_command(EmulatorCommand::SetRenderer(sim_id, sink)); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); - let colors = Colors { - left: [1.0, 0.0, 0.0, 1.0], - right: [0.0, 0.7734375, 0.9375, 1.0], - }; - let color_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("colors"), - contents: bytemuck::bytes_of(&colors), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }); - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("texture bind group layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("bind group"), - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture_view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&sampler), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: color_buf.as_entire_binding(), - }, - ], - }); - - let shader = device.create_shader_module(wgpu::include_wgsl!("../anaglyph.wgsl")); - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("render pipeline layout"), - bind_group_layouts: &[&texture_bind_group_layout], - push_constant_ranges: &[], - }); - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("render pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format: wgpu::TextureFormat::Bgra8UnormSrgb, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let imgui = ImguiState::new(&window); - - Self { - window, - imgui, - pipeline: render_pipeline, - bind_group, - sim_id, - client, - proxy, - paused_due_to_minimize: false, - } - } - - fn draw(&mut self, event_loop: &ActiveEventLoop) { - let window = &mut self.window; - let imgui = &mut self.imgui; - let mut context = imgui.context.lock().unwrap(); - let mut new_size = None; - - let now = Instant::now(); - context.io_mut().update_delta_time(now - imgui.last_frame); - imgui.last_frame = now; - - let frame = match window.surface.get_current_texture() { - Ok(frame) => frame, - Err(e) => { - if !self.window.minimized { - eprintln!("dropped frame: {e:?}"); - } - return; - } - }; - imgui - .platform - .prepare_frame(context.io_mut(), &window.window) - .expect("Failed to prepare frame"); - let ui = context.new_frame(); - let mut menu_height = 0.0; - ui.main_menu_bar(|| { - menu_height = ui.window_size()[1]; - ui.menu("ROM", || { - if ui.menu_item("Open ROM") { - let rom = native_dialog::FileDialog::new() - .add_filter("Virtual Boy ROMs", &["vb", "vbrom"]) - .show_open_single_file() - .unwrap(); - if let Some(path) = rom { - self.client - .send_command(EmulatorCommand::LoadGame(self.sim_id, path)); - } - } - if ui.menu_item("Quit") { - event_loop.exit(); - } - }); - ui.menu("Emulation", || { - let has_game = self.client.has_game(self.sim_id); - if self.client.is_running(self.sim_id) { - if ui.menu_item_config("Pause").enabled(has_game).build() { - self.client.send_command(EmulatorCommand::Pause); - } - } else if ui.menu_item_config("Resume").enabled(has_game).build() { - self.client.send_command(EmulatorCommand::Resume); - } - if ui.menu_item_config("Reset").enabled(has_game).build() { - self.client - .send_command(EmulatorCommand::Reset(self.sim_id)); - } - }); - ui.menu("Video", || { - let current_dims = window.logical_size(); - for scale in 1..=4 { - let label = format!("x{scale}"); - let dims = LogicalSize::new(384 * scale, 224 * scale + 20); - let selected = dims == current_dims; - if ui.menu_item_config(label).selected(selected).build() { - if let Some(size) = window.window.request_inner_size(dims) { - window.handle_resize(&size); - new_size = Some(size); - } - } - } - }); - ui.menu("Audio", || { - let p1_enabled = self.client.is_audio_enabled(SimId::Player1); - let p2_enabled = self.client.is_audio_enabled(SimId::Player2); - if ui.menu_item_config("Player 1").selected(p1_enabled).build() { - self.client - .send_command(EmulatorCommand::SetAudioEnabled(!p1_enabled, p2_enabled)); - } - if ui.menu_item_config("Player 2").selected(p2_enabled).build() { - self.client - .send_command(EmulatorCommand::SetAudioEnabled(p1_enabled, !p2_enabled)); - } - }); - ui.menu("Input", || { - if ui.menu_item("Bind Inputs") { - self.proxy.send_event(UserEvent::OpenInputWindow).unwrap(); - } - }); - ui.menu("Multiplayer", || { - if self.sim_id == SimId::Player1 - && !self.client.has_player_2() - && ui.menu_item("Open Player 2") - { - self.client - .send_command(EmulatorCommand::StartSecondSim(None)); - self.proxy.send_event(UserEvent::OpenPlayer2Window).unwrap(); - } - if self.client.has_player_2() { - let linked = self.client.are_sims_linked(); - if linked && ui.menu_item("Unlink") { - self.client.send_command(EmulatorCommand::Unlink); - } - if !linked && ui.menu_item("Link") { - self.client.send_command(EmulatorCommand::Link); - } - } - }); - }); - - let mut encoder: wgpu::CommandEncoder = window - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - if imgui.last_cursor != ui.mouse_cursor() { - imgui.last_cursor = ui.mouse_cursor(); - imgui.platform.prepare_render(ui, &window.window); - } - - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(imgui.clear_color), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - // Draw the game - rpass.set_pipeline(&self.pipeline); - let window_width = window.surface_desc.width as f32; - let window_height = window.surface_desc.height as f32; - let menu_height = menu_height * window.hidpi_factor as f32; - let ((x, y), (width, height)) = - compute_game_bounds(window_width, window_height, menu_height); - rpass.set_viewport(x, y, width, height, 0.0, 1.0); - rpass.set_bind_group(0, &self.bind_group, &[]); - rpass.draw(0..6, 0..1); - - // Draw the menu on top of the game - rpass.set_viewport(0.0, 0.0, window_width, window_height, 0.0, 1.0); - imgui - .renderer - .render(context.render(), &window.queue, &window.device, &mut rpass) - .expect("Rendering failed"); - - drop(rpass); - - if let Some(size) = new_size { - imgui.platform.handle_event::( - context.io_mut(), - &window.window, - &Event::WindowEvent { - window_id: window.window.id(), - event: WindowEvent::Resized(size), - }, - ); - } - - window.queue.submit(Some(encoder.finish())); - - frame.present(); - } -} - -impl AppWindow for GameWindow { - fn id(&self) -> WindowId { - self.window.window.id() - } - - fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event) { - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::Resized(size) => { - self.window.handle_resize(size); - if self.window.minimized { - if self.client.is_running(self.sim_id) { - self.client.send_command(EmulatorCommand::Pause); - self.paused_due_to_minimize = true; - } - } else if self.paused_due_to_minimize { - self.client.send_command(EmulatorCommand::Resume); - self.paused_due_to_minimize = false; - } - } - WindowEvent::CloseRequested => { - if self.sim_id == SimId::Player2 { - self.client.send_command(EmulatorCommand::StopSecondSim); - self.proxy.send_event(UserEvent::Close(self.id())).unwrap(); - } else { - event_loop.exit(); - } - } - WindowEvent::RedrawRequested => self.draw(event_loop), - _ => (), - }, - Event::AboutToWait => { - self.window.window.request_redraw(); - } - _ => (), - } - let window = &self.window; - let imgui = &mut self.imgui; - let mut context = imgui.context.lock().unwrap(); - imgui - .platform - .handle_event(context.io_mut(), &window.window, event); - } -} - -fn compute_game_bounds( - window_width: f32, - window_height: f32, - menu_height: f32, -) -> ((f32, f32), (f32, f32)) { - let available_width = window_width; - let available_height = window_height - menu_height; - - let width = available_width.min(available_height * 384.0 / 224.0); - let height = available_height.min(available_width * 224.0 / 384.0); - let x = (available_width - width) / 2.0; - let y = menu_height + (available_height - height) / 2.0; - ((x, y), (width, height)) -} - -#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -struct Colors { - left: [f32; 4], - right: [f32; 4], -} diff --git a/src/app/input.rs b/src/app/input.rs deleted file mode 100644 index 8147f5c..0000000 --- a/src/app/input.rs +++ /dev/null @@ -1,220 +0,0 @@ -use std::time::Instant; - -use winit::{ - dpi::LogicalSize, - event::{Event, KeyEvent, WindowEvent}, - event_loop::{ActiveEventLoop, EventLoopProxy}, -}; - -use crate::{ - emulator::{SimId, VBKey}, - input::MappingProvider, -}; - -use super::{ - common::{ImguiState, UiExt, WindowState, WindowStateBuilder}, - AppWindow, UserEvent, -}; - -pub struct InputWindow { - window: WindowState, - imgui: ImguiState, - mappings: MappingProvider, - proxy: EventLoopProxy, - now_binding: Option<(SimId, VBKey)>, -} - -const KEY_NAMES: [(VBKey, &str); 14] = [ - (VBKey::LU, "Up"), - (VBKey::LD, "Down"), - (VBKey::LL, "Left"), - (VBKey::LR, "Right"), - (VBKey::SEL, "Select"), - (VBKey::STA, "Start"), - (VBKey::B, "B"), - (VBKey::A, "A"), - (VBKey::LT, "L-Trigger"), - (VBKey::RT, "R-Trigger"), - (VBKey::RU, "R-Up"), - (VBKey::RD, "R-Down"), - (VBKey::RL, "R-Left"), - (VBKey::RR, "R-Right"), -]; - -impl InputWindow { - pub fn new( - event_loop: &ActiveEventLoop, - mappings: MappingProvider, - proxy: EventLoopProxy, - ) -> Self { - let window = WindowStateBuilder::new(event_loop) - .with_title("Bind Inputs") - .with_inner_size(LogicalSize::new(600, 400)) - .build(); - let imgui = ImguiState::new(&window); - Self { - window, - imgui, - mappings, - now_binding: None, - proxy, - } - } - - fn draw(&mut self) { - let window = &mut self.window; - let imgui = &mut self.imgui; - let mut context = imgui.context.lock().unwrap(); - - let now = Instant::now(); - context.io_mut().update_delta_time(now - imgui.last_frame); - imgui.last_frame = now; - - let frame = match window.surface.get_current_texture() { - Ok(frame) => frame, - Err(e) => { - if !self.window.minimized { - eprintln!("dropped frame: {e:?}"); - } - return; - } - }; - imgui - .platform - .prepare_frame(context.io_mut(), &window.window) - .expect("Failed to prepare frame"); - let ui = context.new_frame(); - - let mut render_key_bindings = |sim_id: SimId| { - let mappings = self.mappings.for_sim(sim_id); - if let Some(table) = ui.begin_table("controls", 2) { - let binding_names = { - let mapping = mappings.read().unwrap(); - mapping.keyboard_mapping_names() - }; - ui.table_next_row(); - - for (key, name) in KEY_NAMES { - let binding = binding_names.get(&key).map(|s| s.as_str()); - ui.table_next_column(); - let [space, _] = ui.content_region_avail(); - ui.group(|| { - ui.right_align_text(name, space * 0.20); - ui.same_line(); - let label_text = if self.now_binding == Some((sim_id, key)) { - "Press any input" - } else { - binding.unwrap_or("") - }; - let label = format!("{}##{}", label_text, name); - if ui.button_with_size(label, [space * 0.60, 0.0]) { - self.now_binding = Some((sim_id, key)); - } - }); - ui.same_line(); - if ui.button(format!("Clear##{name}")) { - let mut mapping = mappings.write().unwrap(); - mapping.clear_keyboard_mappings(key); - } - } - - table.end(); - } - }; - - if let Some(window) = ui.fullscreen_window() { - if let Some(tabs) = ui.tab_bar("tabs") { - if let Some(tab) = ui.tab_item("Player 1") { - render_key_bindings(SimId::Player1); - tab.end(); - } - if let Some(tab) = ui.tab_item("Player 2") { - render_key_bindings(SimId::Player2); - tab.end(); - } - tabs.end(); - } - window.end(); - } - let mut encoder: wgpu::CommandEncoder = window - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - if imgui.last_cursor != ui.mouse_cursor() { - imgui.last_cursor = ui.mouse_cursor(); - imgui.platform.prepare_render(ui, &window.window); - } - - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(imgui.clear_color), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - // Draw the game - imgui - .renderer - .render(context.render(), &window.queue, &window.device, &mut rpass) - .expect("Rendering failed"); - - drop(rpass); - - window.queue.submit(Some(encoder.finish())); - - frame.present(); - } - - fn try_bind_key(&mut self, event: &KeyEvent) { - if !event.state.is_pressed() { - return; - } - let Some((sim_id, vb)) = self.now_binding.take() else { - return; - }; - let mut mappings = self.mappings.for_sim(sim_id).write().unwrap(); - mappings.add_keyboard_mapping(vb, event.physical_key); - } -} - -impl AppWindow for InputWindow { - fn id(&self) -> winit::window::WindowId { - self.window.window.id() - } - - fn handle_event(&mut self, _: &ActiveEventLoop, event: &Event) { - match event { - Event::WindowEvent { event, .. } => match event { - WindowEvent::Resized(size) => self.window.handle_resize(size), - WindowEvent::CloseRequested => { - self.proxy.send_event(UserEvent::Close(self.id())).unwrap() - } - WindowEvent::KeyboardInput { event, .. } => self.try_bind_key(event), - WindowEvent::RedrawRequested => self.draw(), - _ => (), - }, - Event::AboutToWait => { - self.window.window.request_redraw(); - } - _ => (), - } - - let window = &self.window; - let imgui = &mut self.imgui; - let mut context = imgui.context.lock().unwrap(); - imgui - .platform - .handle_event(context.io_mut(), &window.window, event); - } -} diff --git a/src/application.rs b/src/application.rs new file mode 100644 index 0000000..024caaa --- /dev/null +++ b/src/application.rs @@ -0,0 +1,315 @@ +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + num::NonZero, + sync::Arc, +}; + +use winit::event_loop::EventLoopProxy; + +use crate::{emulator::EmulatorClient, window::WindowManager}; + +pub struct Application { + client: EmulatorClient, + proxy: EventLoopProxy, + state: Option, +} + +impl Application { + pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { + Self { + client, + proxy, + state: None, + } + } +} + +impl winit::application::ApplicationHandler for Application { + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + self.state = Some(AppState::new( + self.client.clone(), + self.proxy.clone(), + event_loop, + )); + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + window_id: winit::window::WindowId, + event: winit::event::WindowEvent, + ) { + self.state + .as_mut() + .unwrap() + .window_event(event_loop, window_id, event); + } + + fn device_event( + &mut self, + _event_loop: &winit::event_loop::ActiveEventLoop, + _device_id: winit::event::DeviceId, + event: winit::event::DeviceEvent, + ) { + self.state.as_mut().unwrap().device_event(event) + } + + fn user_event(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, event: UserEvent) { + self.state.as_mut().unwrap().user_event(event); + } +} + +struct AppState { + painter: egui_wgpu::winit::Painter, + ctx: egui::Context, + viewports: HashMap, + viewports_by_window: HashMap, + focused: Option, + screen: WindowManager, +} +impl AppState { + fn new( + client: EmulatorClient, + proxy: EventLoopProxy, + event_loop: &winit::event_loop::ActiveEventLoop, + ) -> Self { + let mut painter = egui_wgpu::winit::Painter::new( + egui_wgpu::WgpuConfiguration::default(), + 1, + None, + false, + true, + ); + let ctx = egui::Context::default(); + ctx.style_mut(|s| { + s.wrap_mode = Some(egui::TextWrapMode::Extend); + s.visuals.menu_rounding = Default::default(); + }); + ctx.set_embed_viewports(false); + { + let proxy = proxy.clone(); + ctx.set_request_repaint_callback(move |info| { + proxy.send_event(UserEvent::RepaintRequested(info)).unwrap(); + }); + } + + let mut screen = WindowManager::new(client, proxy); + let root_viewport = ViewportState::new( + egui::ViewportId::ROOT, + &ctx, + event_loop, + screen.initial_viewport(), + &mut painter, + ); + screen.init_renderer(painter.render_state().as_ref().unwrap()); + + let mut viewports_by_window = HashMap::new(); + viewports_by_window.insert(root_viewport.window.id(), egui::ViewportId::ROOT); + let mut viewports = HashMap::new(); + viewports.insert(egui::ViewportId::ROOT, root_viewport); + + Self { + painter, + ctx, + viewports, + viewports_by_window, + focused: Some(egui::ViewportId::ROOT), + screen, + } + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + window_id: winit::window::WindowId, + event: winit::event::WindowEvent, + ) { + let Some(&viewport_id) = self.viewports_by_window.get(&window_id) else { + return; + }; + let Some(viewport) = self.viewports.get_mut(&viewport_id) else { + panic!("Unrecognized viewport"); + }; + viewport.on_window_event(&event); + + match event { + winit::event::WindowEvent::KeyboardInput { event, .. } => { + self.screen.handle_key_event(event); + } + winit::event::WindowEvent::RedrawRequested => { + pollster::block_on( + self.painter + .set_window(viewport_id, Some(viewport.window.clone())), + ) + .unwrap(); + let cb = viewport.viewport_ui_cb.clone(); + let mut input = viewport.state.take_egui_input(&viewport.window); + input.viewports = self + .viewports + .iter() + .map(|(k, v)| (*k, v.info.clone())) + .collect(); + let output = self.ctx.run(input, |ctx| { + if let Some(cb) = cb.as_deref() { + cb(ctx) + } else { + self.screen.show(ctx) + } + }); + let clipped_primitives = + self.ctx.tessellate(output.shapes, output.pixels_per_point); + self.painter.paint_and_update_textures( + viewport_id, + output.pixels_per_point, + [0.0, 0.0, 0.0, 0.0], + &clipped_primitives, + &output.textures_delta, + false, + ); + + let mut live_viewports = HashSet::default(); + for (id, output) in output.viewport_output { + live_viewports.insert(id); + let viewport = match self.viewports.entry(id) { + Entry::Occupied(e) => e.into_mut(), + Entry::Vacant(e) => { + let mut v = ViewportState::new( + id, + &self.ctx, + event_loop, + output.builder, + &mut self.painter, + ); + v.viewport_ui_cb = output.viewport_ui_cb; + self.viewports_by_window.insert(v.window.id(), id); + e.insert(v) + } + }; + egui_winit::process_viewport_commands( + &self.ctx, + &mut viewport.info, + output.commands, + &viewport.window, + &mut HashSet::default(), + ); + if viewport.info.close_requested() { + live_viewports.remove(&id); + } + } + self.viewports.retain(|k, v| { + if live_viewports.contains(k) { + return true; + } + self.viewports_by_window.remove(&v.window.id()); + false + }); + self.painter.gc_viewports(&live_viewports); + if !self.viewports.contains_key(&egui::ViewportId::ROOT) { + event_loop.exit(); + } + } + winit::event::WindowEvent::Resized(size) => { + let (Some(width), Some(height)) = + (NonZero::new(size.width), NonZero::new(size.height)) + else { + return; + }; + self.painter.on_window_resized(viewport_id, width, height); + } + winit::event::WindowEvent::Focused(new_focused) => { + self.focused = new_focused.then_some(viewport_id); + } + winit::event::WindowEvent::CloseRequested => { + if viewport_id == egui::ViewportId::ROOT { + event_loop.exit(); + } else if let Some(viewport) = self.viewports.get_mut(&viewport_id) { + viewport.info.events.push(egui::ViewportEvent::Close); + self.ctx.request_repaint_of(viewport_id); + self.ctx.request_repaint_of(egui::ViewportId::ROOT); + } + } + _ => {} + } + } + + fn device_event(&mut self, event: winit::event::DeviceEvent) { + if let winit::event::DeviceEvent::MouseMotion { delta } = event { + let Some(viewport) = self + .focused + .as_ref() + .and_then(|id| self.viewports.get_mut(id)) + else { + return; + }; + viewport.state.on_mouse_motion(delta); + } + } + + fn user_event(&mut self, event: UserEvent) { + match event { + UserEvent::GamepadEvent(event) => self.screen.handle_gamepad_event(event), + UserEvent::RepaintRequested(info) => { + let Some(viewport) = self.viewports.get(&info.viewport_id) else { + return; + }; + viewport.window.request_redraw(); + } + } + } +} + +struct ViewportState { + info: egui::ViewportInfo, + state: egui_winit::State, + viewport_ui_cb: Option>, + window: Arc, +} + +impl ViewportState { + fn new( + id: egui::ViewportId, + ctx: &egui::Context, + event_loop: &winit::event_loop::ActiveEventLoop, + viewport: egui::ViewportBuilder, + painter: &mut egui_wgpu::winit::Painter, + ) -> Self { + let mut info = egui::ViewportInfo::default(); + let window = Arc::new(egui_winit::create_window(ctx, event_loop, &viewport).unwrap()); + egui_winit::update_viewport_info(&mut info, ctx, &window, true); + + pollster::block_on(painter.set_window(id, Some(window.clone()))).unwrap(); + let state = egui_winit::State::new( + ctx.clone(), + id, + event_loop, + Some(window.scale_factor() as f32), + event_loop.system_theme(), + painter.max_texture_side(), + ); + Self { + info, + state, + viewport_ui_cb: None, + window, + } + } + + fn on_window_event(&mut self, event: &winit::event::WindowEvent) { + let response = self.state.on_window_event(&self.window, event); + if response.repaint { + self.window.request_redraw(); + } + egui_winit::update_viewport_info( + &mut self.info, + self.state.egui_ctx(), + &self.window, + false, + ); + } +} + +#[derive(Debug)] +pub enum UserEvent { + GamepadEvent(gilrs::Event), + RepaintRequested(egui::RequestRepaintInfo), +} diff --git a/src/main.rs b/src/main.rs index 8c47f3b..411c0f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,19 @@ use std::{path::PathBuf, process}; use anyhow::Result; -use app::App; +use application::Application; use clap::Parser; use emulator::EmulatorBuilder; use thread_priority::{ThreadBuilder, ThreadPriority}; use winit::event_loop::{ControlFlow, EventLoop}; -mod app; +mod application; mod audio; mod controller; mod emulator; mod graphics; mod input; +mod window; #[derive(Parser)] struct Args { @@ -56,6 +57,6 @@ fn main() -> Result<()> { let event_loop = EventLoop::with_user_event().build().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); let proxy = event_loop.create_proxy(); - event_loop.run_app(&mut App::new(client, proxy))?; + event_loop.run_app(&mut Application::new(client, proxy))?; Ok(()) } diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..53dd01c --- /dev/null +++ b/src/window.rs @@ -0,0 +1,157 @@ +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, + thread, +}; + +use egui::{Context, ViewportBuilder, ViewportId}; +use game::GameWindow; +use game_screen::GameScreen; +use gilrs::{EventType, Gilrs}; +use input::InputWindow; +use winit::{event::KeyEvent, event_loop::EventLoopProxy}; + +use crate::{ + application::UserEvent, + controller::ControllerManager, + emulator::{EmulatorClient, EmulatorCommand, SimId}, + input::MappingProvider, +}; + +mod game; +mod game_screen; +mod input; + +pub struct WindowManager { + p1: GameWindow, + p2: Arc>, + client: EmulatorClient, + input: ChildWindow, + controllers: ControllerManager, +} +impl WindowManager { + pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { + let mappings = MappingProvider::new(); + let controllers = ControllerManager::new(client.clone(), &mappings); + { + let mappings = mappings.clone(); + thread::spawn(|| process_gamepad_input(mappings, proxy)); + } + let input = ChildWindow::new(InputWindow::new(mappings)); + let p1 = GameWindow::new(client.clone(), SimId::Player1, input.open.clone()); + let p2 = GameWindow::new(client.clone(), SimId::Player2, input.open.clone()); + Self { + p1, + p2: Arc::new(Mutex::new(p2)), + client, + input, + controllers, + } + } + + pub fn init_renderer(&mut self, render_state: &egui_wgpu::RenderState) { + GameScreen::init_pipeline(render_state); + self.p2.lock().unwrap().init_renderer(render_state); + self.p1.init_renderer(render_state); + } + + pub fn initial_viewport(&self) -> ViewportBuilder { + self.p1.initial_viewport() + } + + pub fn show(&mut self, ctx: &Context) { + self.p1.show(ctx); + self.input.show(ctx); + if self.client.has_player_2() { + let (viewport_id, viewport) = { + let p2 = self.p2.lock().unwrap(); + (p2.viewport_id(), p2.initial_viewport()) + }; + let client = self.client.clone(); + let p2 = self.p2.clone(); + ctx.show_viewport_deferred(viewport_id, viewport, move |ctx, _| { + p2.lock().unwrap().show(ctx); + if ctx.input(|i| i.viewport().close_requested()) { + client.send_command(EmulatorCommand::StopSecondSim); + } + }); + } + } + + pub fn handle_key_event(&mut self, event: winit::event::KeyEvent) { + self.controllers.handle_key_event(&event); + self.input.handle_key_event(&event); + } + + pub fn handle_gamepad_event(&mut self, event: gilrs::Event) { + self.controllers.handle_gamepad_event(&event); + } +} + +fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy) { + let Ok(mut gilrs) = Gilrs::new() else { + eprintln!("could not connect gamepad listener"); + return; + }; + while let Some(event) = gilrs.next_event_blocking(None) { + if event.event == EventType::Connected { + let Some(gamepad) = gilrs.connected_gamepad(event.id) else { + continue; + }; + mappings.map_gamepad(SimId::Player1, &gamepad); + } + if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() { + // main thread has closed! we done + return; + } + } +} + +trait AppWindow { + fn viewport_id(&self) -> ViewportId; + fn initial_viewport(&self) -> ViewportBuilder; + fn show(&mut self, ctx: &Context); + fn handle_key_event(&mut self, event: &KeyEvent) { + let _ = event; + } +} + +struct ChildWindow { + pub open: Arc, + window: Arc>, +} +impl ChildWindow { + fn new(window: T) -> Self { + Self { + open: Arc::new(AtomicBool::new(false)), + window: Arc::new(Mutex::new(window)), + } + } + + fn show(&self, ctx: &Context) { + if !self.open.load(Ordering::Relaxed) { + return; + } + let (viewport_id, viewport) = { + let window = self.window.lock().unwrap(); + (window.viewport_id(), window.initial_viewport()) + }; + let open = self.open.clone(); + let window = self.window.clone(); + ctx.show_viewport_deferred(viewport_id, viewport, move |ctx, _| { + window.lock().unwrap().show(ctx); + if ctx.input(|i| i.viewport().close_requested()) { + open.store(false, Ordering::Relaxed); + ctx.request_repaint(); + } + }); + } + + fn handle_key_event(&self, event: &KeyEvent) { + if self.open.load(Ordering::Relaxed) { + self.window.lock().unwrap().handle_key_event(event); + } + } +} diff --git a/src/window/game.rs b/src/window/game.rs new file mode 100644 index 0000000..b3c3b7d --- /dev/null +++ b/src/window/game.rs @@ -0,0 +1,174 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +use crate::emulator::{EmulatorClient, EmulatorCommand, SimId}; +use egui::{ + menu, Button, CentralPanel, Color32, Context, Response, TopBottomPanel, Ui, ViewportBuilder, + ViewportCommand, ViewportId, WidgetText, +}; + +use super::{game_screen::GameScreen, AppWindow}; + +pub struct GameWindow { + client: EmulatorClient, + sim_id: SimId, + input_window_open: Arc, + screen: Option, +} + +impl GameWindow { + pub fn new(client: EmulatorClient, sim_id: SimId, input_window_open: Arc) -> Self { + Self { + client, + sim_id, + input_window_open, + screen: None, + } + } + + pub fn init_renderer(&mut self, render_state: &egui_wgpu::RenderState) { + let (screen, sink) = GameScreen::init(render_state); + self.client + .send_command(EmulatorCommand::SetRenderer(self.sim_id, sink)); + self.screen = Some(screen) + } + + fn show_menu(&mut self, ctx: &Context, ui: &mut Ui) { + ui.menu_button("ROM", |ui| { + if ui.button("Open ROM").clicked() { + let rom = rfd::FileDialog::new() + .add_filter("Virtual Boy ROMs", &["vb", "vbrom"]) + .pick_file(); + if let Some(path) = rom { + self.client + .send_command(EmulatorCommand::LoadGame(SimId::Player1, path)); + } + ui.close_menu(); + } + if ui.button("Quit").clicked() { + ctx.send_viewport_cmd(ViewportCommand::Close); + } + }); + ui.menu_button("Emulation", |ui| { + let has_game = self.client.has_game(self.sim_id); + if self.client.is_running(self.sim_id) { + if ui.add_enabled(has_game, Button::new("Pause")).clicked() { + self.client.send_command(EmulatorCommand::Pause); + ui.close_menu(); + } + } else if ui.add_enabled(has_game, Button::new("Resume")).clicked() { + self.client.send_command(EmulatorCommand::Resume); + ui.close_menu(); + } + if ui.add_enabled(has_game, Button::new("Reset")).clicked() { + self.client + .send_command(EmulatorCommand::Reset(self.sim_id)); + ui.close_menu(); + } + }); + ui.menu_button("Video", |ui| { + let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap()); + let current_dims = current_dims.max - current_dims.min; + + for scale in 1..=4 { + let label = format!("x{scale}"); + let scale = scale as f32; + let dims = (384.0 * scale, 224.0 * scale + 20.0).into(); + if ui + .selectable_button((current_dims - dims).length() < 1.0, label) + .clicked() + { + ctx.send_viewport_cmd(ViewportCommand::InnerSize(dims)); + ui.close_menu(); + } + } + }); + ui.menu_button("Audio", |ui| { + let p1_enabled = self.client.is_audio_enabled(SimId::Player1); + let p2_enabled = self.client.is_audio_enabled(SimId::Player2); + if ui.selectable_button(p1_enabled, "Player 1").clicked() { + self.client + .send_command(EmulatorCommand::SetAudioEnabled(!p1_enabled, p2_enabled)); + ui.close_menu(); + } + if ui.selectable_button(p2_enabled, "Player 2").clicked() { + self.client + .send_command(EmulatorCommand::SetAudioEnabled(p1_enabled, !p2_enabled)); + ui.close_menu(); + } + }); + ui.menu_button("Input", |ui| { + if ui.button("Bind Inputs").clicked() { + self.input_window_open.store(true, Ordering::Relaxed); + ui.close_menu(); + } + }); + ui.menu_button("Multiplayer", |ui| { + if self.sim_id == SimId::Player1 + && !self.client.has_player_2() + && ui.button("Open Player 2").clicked() + { + self.client + .send_command(EmulatorCommand::StartSecondSim(None)); + ui.close_menu(); + } + if self.client.has_player_2() { + let linked = self.client.are_sims_linked(); + if linked && ui.button("Unlink").clicked() { + self.client.send_command(EmulatorCommand::Unlink); + ui.close_menu(); + } + if !linked && ui.button("Link").clicked() { + self.client.send_command(EmulatorCommand::Link); + ui.close_menu(); + } + } + }); + } +} + +impl AppWindow for GameWindow { + fn viewport_id(&self) -> ViewportId { + match self.sim_id { + SimId::Player1 => ViewportId::ROOT, + SimId::Player2 => ViewportId::from_hash_of("Player2"), + } + } + + fn initial_viewport(&self) -> ViewportBuilder { + ViewportBuilder::default() + .with_title("Shrooms VB") + .with_inner_size((384.0, 244.0)) + } + + fn show(&mut self, ctx: &Context) { + TopBottomPanel::top("menubar") + .exact_height(20.0) + .show(ctx, |ui| { + menu::bar(ui, |ui| { + self.show_menu(ctx, ui); + }); + }); + CentralPanel::default().show(ctx, |ui| { + if let Some(screen) = self.screen.as_ref() { + ui.add(screen); + } + }); + } +} + +trait UiExt { + fn selectable_button(&mut self, selected: bool, text: impl Into) -> Response; +} + +impl UiExt for Ui { + fn selectable_button(&mut self, selected: bool, text: impl Into) -> Response { + self.style_mut().visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT; + self.style_mut().visuals.widgets.hovered.bg_fill = Color32::TRANSPARENT; + self.style_mut().visuals.widgets.active.bg_fill = Color32::TRANSPARENT; + let mut selected = selected; + self.checkbox(&mut selected, text) + } +} diff --git a/src/window/game_screen.rs b/src/window/game_screen.rs new file mode 100644 index 0000000..2a00520 --- /dev/null +++ b/src/window/game_screen.rs @@ -0,0 +1,194 @@ +use std::sync::Arc; + +use egui::Widget; +use wgpu::{util::DeviceExt as _, BindGroup, BindGroupLayout, RenderPipeline}; + +use crate::graphics::TextureSink; + +pub struct GameScreen { + bind_group: Arc, +} + +impl GameScreen { + pub fn init_pipeline(render_state: &egui_wgpu::RenderState) { + let device = &render_state.device; + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("texture bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let shader = device.create_shader_module(wgpu::include_wgsl!("../anaglyph.wgsl")); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("render pipeline layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("render pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Bgra8Unorm, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + render_state + .renderer + .write() + .callback_resources + .insert(SharedGameScreenResources { + pipeline: render_pipeline, + bind_group_layout, + }); + } + + pub fn init(render_state: &egui_wgpu::RenderState) -> (Self, TextureSink) { + let device = &render_state.device; + let queue = &render_state.queue; + + let (sink, texture_view) = TextureSink::new(device, queue.clone()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); + let colors = Colors { + left: [1.0, 0.0, 0.0, 1.0], + right: [0.0, 0.7734375, 0.9375, 1.0], + }; + + let color_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("colors"), + contents: bytemuck::bytes_of(&colors), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let renderer = render_state.renderer.read(); + let resources: &SharedGameScreenResources = renderer.callback_resources.get().unwrap(); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("bind group"), + layout: &resources.bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: color_buf.as_entire_binding(), + }, + ], + }); + + ( + Self { + bind_group: Arc::new(bind_group), + }, + sink, + ) + } +} + +impl Widget for &GameScreen { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + let response = ui.allocate_rect(ui.clip_rect(), egui::Sense::hover()); + let callback = egui_wgpu::Callback::new_paint_callback( + response.rect, + GameScreenCallback { + bind_group: self.bind_group.clone(), + }, + ); + ui.painter().add(callback); + response + } +} + +struct GameScreenCallback { + bind_group: Arc, +} + +impl egui_wgpu::CallbackTrait for GameScreenCallback { + fn paint( + &self, + _info: egui::PaintCallbackInfo, + render_pass: &mut wgpu::RenderPass<'static>, + callback_resources: &egui_wgpu::CallbackResources, + ) { + let resources: &SharedGameScreenResources = callback_resources.get().unwrap(); + // TODO: maintain aspect ratio + render_pass.set_pipeline(&resources.pipeline); + render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.draw(0..6, 0..1); + } +} + +struct SharedGameScreenResources { + pipeline: RenderPipeline, + bind_group_layout: BindGroupLayout, +} + +#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +struct Colors { + left: [f32; 4], + right: [f32; 4], +} diff --git a/src/window/input.rs b/src/window/input.rs new file mode 100644 index 0000000..b2bc956 --- /dev/null +++ b/src/window/input.rs @@ -0,0 +1,133 @@ +use egui::{ + Button, CentralPanel, Context, Label, Layout, TopBottomPanel, Ui, ViewportBuilder, ViewportId, +}; +use egui_extras::{Column, TableBuilder}; + +use crate::{ + emulator::{SimId, VBKey}, + input::MappingProvider, +}; + +use super::AppWindow; + +pub struct InputWindow { + mappings: MappingProvider, + now_binding: Option<(SimId, VBKey)>, + active_tab: InputTab, +} + +const KEY_NAMES: [(VBKey, &str); 14] = [ + (VBKey::LU, "Up"), + (VBKey::LD, "Down"), + (VBKey::LL, "Left"), + (VBKey::LR, "Right"), + (VBKey::SEL, "Select"), + (VBKey::STA, "Start"), + (VBKey::B, "B"), + (VBKey::A, "A"), + (VBKey::LT, "L-Trigger"), + (VBKey::RT, "R-Trigger"), + (VBKey::RU, "R-Up"), + (VBKey::RD, "R-Down"), + (VBKey::RL, "R-Left"), + (VBKey::RR, "R-Right"), +]; + +impl InputWindow { + pub fn new(mappings: MappingProvider) -> Self { + Self { + mappings, + now_binding: None, + active_tab: InputTab::Player1, + } + } + + fn render_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { + let mappings = self.mappings.for_sim(sim_id); + let binding_names = { + let mapping = mappings.read().unwrap(); + mapping.keyboard_mapping_names() + }; + TableBuilder::new(ui) + .column(Column::remainder()) + .column(Column::remainder()) + .cell_layout(Layout::left_to_right(egui::Align::Center)) + .body(|mut body| { + for keys in KEY_NAMES.chunks_exact(2) { + body.row(20.0, |mut row| { + for (key, name) in keys { + let binding = binding_names.get(key).map(|s| s.as_str()); + row.col(|ui| { + let size = ui.available_size_before_wrap(); + let width = size.x; + let height = size.y; + ui.add_sized((width * 0.2, height), Label::new(*name)); + let label_text = if self.now_binding == Some((sim_id, *key)) { + "Press any input" + } else { + binding.unwrap_or("") + }; + if ui + .add_sized((width * 0.6, height), Button::new(label_text)) + .clicked() + { + self.now_binding = Some((sim_id, *key)) + } + if ui + .add_sized(ui.available_size(), Button::new("Clear")) + .clicked() + { + let mut mapping = mappings.write().unwrap(); + mapping.clear_keyboard_mappings(*key); + } + }); + } + }); + } + }); + } +} + +impl AppWindow for InputWindow { + fn viewport_id(&self) -> ViewportId { + ViewportId::from_hash_of("input") + } + + fn initial_viewport(&self) -> ViewportBuilder { + ViewportBuilder::default() + .with_title("Bind Inputs") + .with_inner_size((600.0, 400.0)) + } + + fn show(&mut self, ctx: &Context) { + TopBottomPanel::top("options").show(ctx, |ui| { + ui.horizontal(|ui| { + ui.selectable_value(&mut self.active_tab, InputTab::Player1, "Player 1"); + ui.selectable_value(&mut self.active_tab, InputTab::Player2, "Player 2"); + }); + }); + CentralPanel::default().show(ctx, |ui| { + match self.active_tab { + InputTab::Player1 => self.render_key_bindings(ui, SimId::Player1), + InputTab::Player2 => self.render_key_bindings(ui, SimId::Player2), + }; + }); + } + + fn handle_key_event(&mut self, event: &winit::event::KeyEvent) { + if !event.state.is_pressed() { + return; + } + let Some((sim_id, vb)) = self.now_binding.take() else { + return; + }; + let mut mappings = self.mappings.for_sim(sim_id).write().unwrap(); + mappings.add_keyboard_mapping(vb, event.physical_key); + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum InputTab { + Player1, + Player2, +} From 83377ff5fa3991ca78b70ea1abb87b672f8315ed Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Thu, 28 Nov 2024 10:27:18 -0500 Subject: [PATCH 16/21] Stop using nested viewports --- src/app.rs | 329 ++++++++++++++++++++++++++++++++++++++ src/application.rs | 315 ------------------------------------ src/main.rs | 4 +- src/window.rs | 152 +----------------- src/window/game.rs | 38 +++-- src/window/game_screen.rs | 4 +- 6 files changed, 364 insertions(+), 478 deletions(-) create mode 100644 src/app.rs delete mode 100644 src/application.rs diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..4f21223 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,329 @@ +use std::{collections::HashSet, num::NonZero, sync::Arc, thread}; + +use egui::{ + ahash::{HashMap, HashMapExt}, + Context, TextWrapMode, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo, +}; +use gilrs::{EventType, Gilrs}; +use winit::{ + application::ApplicationHandler, + event::{KeyEvent, WindowEvent}, + event_loop::{ActiveEventLoop, EventLoopProxy}, + window::Window, +}; + +use crate::{ + controller::ControllerManager, + emulator::{EmulatorClient, SimId}, + input::MappingProvider, + window::{AppWindow, GameWindow, InputWindow}, +}; + +pub struct Application { + client: EmulatorClient, + proxy: EventLoopProxy, + mappings: MappingProvider, + controllers: ControllerManager, + viewports: HashMap, + focused: Option, +} + +impl Application { + pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { + let mappings = MappingProvider::new(); + let controllers = ControllerManager::new(client.clone(), &mappings); + { + let mappings = mappings.clone(); + let proxy = proxy.clone(); + thread::spawn(|| process_gamepad_input(mappings, proxy)); + } + Self { + client, + proxy, + mappings, + controllers, + viewports: HashMap::new(), + focused: None, + } + } + + fn open(&mut self, event_loop: &ActiveEventLoop, window: Box) { + let viewport_id = window.viewport_id(); + if self.viewports.contains_key(&viewport_id) { + return; + } + self.viewports + .insert(viewport_id, Viewport::new(event_loop, window)); + } +} + +impl ApplicationHandler for Application { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let app = GameWindow::new(self.client.clone(), self.proxy.clone(), SimId::Player1); + let wrapper = Viewport::new(event_loop, Box::new(app)); + self.focused = Some(wrapper.id()); + self.viewports.insert(wrapper.id(), wrapper); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: winit::window::WindowId, + event: WindowEvent, + ) { + let Some(viewport) = self + .viewports + .values_mut() + .find(|v| v.window.id() == window_id) + else { + return; + }; + let viewport_id = viewport.id(); + match &event { + WindowEvent::KeyboardInput { event, .. } => { + self.controllers.handle_key_event(event); + viewport.handle_key_event(event); + } + WindowEvent::Focused(new_focused) => { + self.focused = new_focused.then_some(viewport_id); + } + _ => {} + } + match viewport.on_window_event(event) { + Some(Action::Redraw) => { + for viewport in self.viewports.values_mut() { + viewport.redraw(event_loop); + } + } + Some(Action::Close) => { + self.viewports.remove(&viewport_id); + if viewport_id == ViewportId::ROOT { + event_loop.exit(); + } + } + None => {} + } + } + + fn device_event( + &mut self, + _event_loop: &ActiveEventLoop, + _device_id: winit::event::DeviceId, + event: winit::event::DeviceEvent, + ) { + if let winit::event::DeviceEvent::MouseMotion { delta } = event { + let Some(viewport) = self + .focused + .as_ref() + .and_then(|id| self.viewports.get_mut(id)) + else { + return; + }; + viewport.state.on_mouse_motion(delta); + } + } + + fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { + match event { + UserEvent::GamepadEvent(event) => self.controllers.handle_gamepad_event(&event), + UserEvent::OpenInput => { + let input = InputWindow::new(self.mappings.clone()); + self.open(event_loop, Box::new(input)); + } + UserEvent::OpenPlayer2 => { + let p2 = GameWindow::new(self.client.clone(), self.proxy.clone(), SimId::Player2); + self.open(event_loop, Box::new(p2)); + } + } + } + + fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { + if let Some(viewport) = self.viewports.get(&ViewportId::ROOT) { + viewport.window.request_redraw(); + } + } +} + +struct Viewport { + painter: egui_wgpu::winit::Painter, + ctx: Context, + info: ViewportInfo, + commands: Vec, + builder: ViewportBuilder, + window: Arc, + state: egui_winit::State, + app: Box, +} +impl Viewport { + pub fn new(event_loop: &ActiveEventLoop, mut app: Box) -> Self { + let mut painter = egui_wgpu::winit::Painter::new( + egui_wgpu::WgpuConfiguration::default(), + 1, + None, + false, + true, + ); + + let ctx = Context::default(); + ctx.style_mut(|s| { + s.wrap_mode = Some(TextWrapMode::Extend); + s.visuals.menu_rounding = Default::default(); + }); + + let mut info = ViewportInfo::default(); + let builder = app.initial_viewport(); + let (window, state) = create_window_and_state(&ctx, event_loop, &builder, &mut painter); + egui_winit::update_viewport_info(&mut info, &ctx, &window, true); + + app.on_init(painter.render_state().as_ref().unwrap()); + Self { + painter, + ctx, + info, + commands: vec![], + builder, + window, + state, + app, + } + } + + pub fn id(&self) -> ViewportId { + self.app.viewport_id() + } + + pub fn on_window_event(&mut self, event: WindowEvent) -> Option { + let response = self.state.on_window_event(&self.window, &event); + egui_winit::update_viewport_info( + &mut self.info, + self.state.egui_ctx(), + &self.window, + false, + ); + + match event { + WindowEvent::RedrawRequested => Some(Action::Redraw), + WindowEvent::CloseRequested => Some(Action::Close), + WindowEvent::Resized(size) => { + let (Some(width), Some(height)) = + (NonZero::new(size.width), NonZero::new(size.height)) + else { + return None; + }; + self.painter + .on_window_resized(ViewportId::ROOT, width, height); + None + } + _ if response.repaint => Some(Action::Redraw), + _ => None, + } + } + + pub fn handle_key_event(&mut self, event: &KeyEvent) { + self.app.handle_key_event(event); + } + + fn redraw(&mut self, event_loop: &ActiveEventLoop) -> Option { + let mut input = self.state.take_egui_input(&self.window); + input.viewports = std::iter::once((ViewportId::ROOT, self.info.clone())).collect(); + let mut output = self.ctx.run(input, |ctx| { + self.app.show(ctx); + }); + let clipped_primitives = self.ctx.tessellate(output.shapes, output.pixels_per_point); + self.painter.paint_and_update_textures( + ViewportId::ROOT, + output.pixels_per_point, + [0.0, 0.0, 0.0, 0.0], + &clipped_primitives, + &output.textures_delta, + false, + ); + + self.state + .handle_platform_output(&self.window, output.platform_output); + + let Some(viewport_output) = output.viewport_output.remove(&ViewportId::ROOT) else { + return Some(Action::Close); + }; + + let (mut deferred_commands, recreate) = self.builder.patch(viewport_output.builder); + if recreate { + let (window, state) = + create_window_and_state(&self.ctx, event_loop, &self.builder, &mut self.painter); + egui_winit::update_viewport_info(&mut self.info, &self.ctx, &window, true); + self.window = window; + self.state = state; + } + self.commands.append(&mut deferred_commands); + egui_winit::process_viewport_commands( + &self.ctx, + &mut self.info, + std::mem::take(&mut self.commands), + &self.window, + &mut HashSet::default(), + ); + + if self.info.close_requested() { + Some(Action::Close) + } else { + Some(Action::Redraw) + } + } +} + +impl Drop for Viewport { + fn drop(&mut self) { + self.app.on_destroy(); + } +} + +#[derive(Debug)] +pub enum UserEvent { + GamepadEvent(gilrs::Event), + OpenInput, + OpenPlayer2, +} + +pub enum Action { + Redraw, + Close, +} + +fn create_window_and_state( + ctx: &Context, + event_loop: &ActiveEventLoop, + builder: &ViewportBuilder, + painter: &mut egui_wgpu::winit::Painter, +) -> (Arc, egui_winit::State) { + pollster::block_on(painter.set_window(ViewportId::ROOT, None)).unwrap(); + let window = Arc::new(egui_winit::create_window(ctx, event_loop, builder).unwrap()); + pollster::block_on(painter.set_window(ViewportId::ROOT, Some(window.clone()))).unwrap(); + let state = egui_winit::State::new( + ctx.clone(), + ViewportId::ROOT, + event_loop, + Some(window.scale_factor() as f32), + event_loop.system_theme(), + painter.max_texture_side(), + ); + (window, state) +} + +fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy) { + let Ok(mut gilrs) = Gilrs::new() else { + eprintln!("could not connect gamepad listener"); + return; + }; + while let Some(event) = gilrs.next_event_blocking(None) { + if event.event == EventType::Connected { + let Some(gamepad) = gilrs.connected_gamepad(event.id) else { + continue; + }; + mappings.map_gamepad(SimId::Player1, &gamepad); + } + if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() { + // main thread has closed! we done + return; + } + } +} diff --git a/src/application.rs b/src/application.rs deleted file mode 100644 index 024caaa..0000000 --- a/src/application.rs +++ /dev/null @@ -1,315 +0,0 @@ -use std::{ - collections::{hash_map::Entry, HashMap, HashSet}, - num::NonZero, - sync::Arc, -}; - -use winit::event_loop::EventLoopProxy; - -use crate::{emulator::EmulatorClient, window::WindowManager}; - -pub struct Application { - client: EmulatorClient, - proxy: EventLoopProxy, - state: Option, -} - -impl Application { - pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { - Self { - client, - proxy, - state: None, - } - } -} - -impl winit::application::ApplicationHandler for Application { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - self.state = Some(AppState::new( - self.client.clone(), - self.proxy.clone(), - event_loop, - )); - } - - fn window_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - window_id: winit::window::WindowId, - event: winit::event::WindowEvent, - ) { - self.state - .as_mut() - .unwrap() - .window_event(event_loop, window_id, event); - } - - fn device_event( - &mut self, - _event_loop: &winit::event_loop::ActiveEventLoop, - _device_id: winit::event::DeviceId, - event: winit::event::DeviceEvent, - ) { - self.state.as_mut().unwrap().device_event(event) - } - - fn user_event(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, event: UserEvent) { - self.state.as_mut().unwrap().user_event(event); - } -} - -struct AppState { - painter: egui_wgpu::winit::Painter, - ctx: egui::Context, - viewports: HashMap, - viewports_by_window: HashMap, - focused: Option, - screen: WindowManager, -} -impl AppState { - fn new( - client: EmulatorClient, - proxy: EventLoopProxy, - event_loop: &winit::event_loop::ActiveEventLoop, - ) -> Self { - let mut painter = egui_wgpu::winit::Painter::new( - egui_wgpu::WgpuConfiguration::default(), - 1, - None, - false, - true, - ); - let ctx = egui::Context::default(); - ctx.style_mut(|s| { - s.wrap_mode = Some(egui::TextWrapMode::Extend); - s.visuals.menu_rounding = Default::default(); - }); - ctx.set_embed_viewports(false); - { - let proxy = proxy.clone(); - ctx.set_request_repaint_callback(move |info| { - proxy.send_event(UserEvent::RepaintRequested(info)).unwrap(); - }); - } - - let mut screen = WindowManager::new(client, proxy); - let root_viewport = ViewportState::new( - egui::ViewportId::ROOT, - &ctx, - event_loop, - screen.initial_viewport(), - &mut painter, - ); - screen.init_renderer(painter.render_state().as_ref().unwrap()); - - let mut viewports_by_window = HashMap::new(); - viewports_by_window.insert(root_viewport.window.id(), egui::ViewportId::ROOT); - let mut viewports = HashMap::new(); - viewports.insert(egui::ViewportId::ROOT, root_viewport); - - Self { - painter, - ctx, - viewports, - viewports_by_window, - focused: Some(egui::ViewportId::ROOT), - screen, - } - } - - fn window_event( - &mut self, - event_loop: &winit::event_loop::ActiveEventLoop, - window_id: winit::window::WindowId, - event: winit::event::WindowEvent, - ) { - let Some(&viewport_id) = self.viewports_by_window.get(&window_id) else { - return; - }; - let Some(viewport) = self.viewports.get_mut(&viewport_id) else { - panic!("Unrecognized viewport"); - }; - viewport.on_window_event(&event); - - match event { - winit::event::WindowEvent::KeyboardInput { event, .. } => { - self.screen.handle_key_event(event); - } - winit::event::WindowEvent::RedrawRequested => { - pollster::block_on( - self.painter - .set_window(viewport_id, Some(viewport.window.clone())), - ) - .unwrap(); - let cb = viewport.viewport_ui_cb.clone(); - let mut input = viewport.state.take_egui_input(&viewport.window); - input.viewports = self - .viewports - .iter() - .map(|(k, v)| (*k, v.info.clone())) - .collect(); - let output = self.ctx.run(input, |ctx| { - if let Some(cb) = cb.as_deref() { - cb(ctx) - } else { - self.screen.show(ctx) - } - }); - let clipped_primitives = - self.ctx.tessellate(output.shapes, output.pixels_per_point); - self.painter.paint_and_update_textures( - viewport_id, - output.pixels_per_point, - [0.0, 0.0, 0.0, 0.0], - &clipped_primitives, - &output.textures_delta, - false, - ); - - let mut live_viewports = HashSet::default(); - for (id, output) in output.viewport_output { - live_viewports.insert(id); - let viewport = match self.viewports.entry(id) { - Entry::Occupied(e) => e.into_mut(), - Entry::Vacant(e) => { - let mut v = ViewportState::new( - id, - &self.ctx, - event_loop, - output.builder, - &mut self.painter, - ); - v.viewport_ui_cb = output.viewport_ui_cb; - self.viewports_by_window.insert(v.window.id(), id); - e.insert(v) - } - }; - egui_winit::process_viewport_commands( - &self.ctx, - &mut viewport.info, - output.commands, - &viewport.window, - &mut HashSet::default(), - ); - if viewport.info.close_requested() { - live_viewports.remove(&id); - } - } - self.viewports.retain(|k, v| { - if live_viewports.contains(k) { - return true; - } - self.viewports_by_window.remove(&v.window.id()); - false - }); - self.painter.gc_viewports(&live_viewports); - if !self.viewports.contains_key(&egui::ViewportId::ROOT) { - event_loop.exit(); - } - } - winit::event::WindowEvent::Resized(size) => { - let (Some(width), Some(height)) = - (NonZero::new(size.width), NonZero::new(size.height)) - else { - return; - }; - self.painter.on_window_resized(viewport_id, width, height); - } - winit::event::WindowEvent::Focused(new_focused) => { - self.focused = new_focused.then_some(viewport_id); - } - winit::event::WindowEvent::CloseRequested => { - if viewport_id == egui::ViewportId::ROOT { - event_loop.exit(); - } else if let Some(viewport) = self.viewports.get_mut(&viewport_id) { - viewport.info.events.push(egui::ViewportEvent::Close); - self.ctx.request_repaint_of(viewport_id); - self.ctx.request_repaint_of(egui::ViewportId::ROOT); - } - } - _ => {} - } - } - - fn device_event(&mut self, event: winit::event::DeviceEvent) { - if let winit::event::DeviceEvent::MouseMotion { delta } = event { - let Some(viewport) = self - .focused - .as_ref() - .and_then(|id| self.viewports.get_mut(id)) - else { - return; - }; - viewport.state.on_mouse_motion(delta); - } - } - - fn user_event(&mut self, event: UserEvent) { - match event { - UserEvent::GamepadEvent(event) => self.screen.handle_gamepad_event(event), - UserEvent::RepaintRequested(info) => { - let Some(viewport) = self.viewports.get(&info.viewport_id) else { - return; - }; - viewport.window.request_redraw(); - } - } - } -} - -struct ViewportState { - info: egui::ViewportInfo, - state: egui_winit::State, - viewport_ui_cb: Option>, - window: Arc, -} - -impl ViewportState { - fn new( - id: egui::ViewportId, - ctx: &egui::Context, - event_loop: &winit::event_loop::ActiveEventLoop, - viewport: egui::ViewportBuilder, - painter: &mut egui_wgpu::winit::Painter, - ) -> Self { - let mut info = egui::ViewportInfo::default(); - let window = Arc::new(egui_winit::create_window(ctx, event_loop, &viewport).unwrap()); - egui_winit::update_viewport_info(&mut info, ctx, &window, true); - - pollster::block_on(painter.set_window(id, Some(window.clone()))).unwrap(); - let state = egui_winit::State::new( - ctx.clone(), - id, - event_loop, - Some(window.scale_factor() as f32), - event_loop.system_theme(), - painter.max_texture_side(), - ); - Self { - info, - state, - viewport_ui_cb: None, - window, - } - } - - fn on_window_event(&mut self, event: &winit::event::WindowEvent) { - let response = self.state.on_window_event(&self.window, event); - if response.repaint { - self.window.request_redraw(); - } - egui_winit::update_viewport_info( - &mut self.info, - self.state.egui_ctx(), - &self.window, - false, - ); - } -} - -#[derive(Debug)] -pub enum UserEvent { - GamepadEvent(gilrs::Event), - RepaintRequested(egui::RequestRepaintInfo), -} diff --git a/src/main.rs b/src/main.rs index 411c0f5..f747fca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ use std::{path::PathBuf, process}; use anyhow::Result; -use application::Application; +use app::Application; use clap::Parser; use emulator::EmulatorBuilder; use thread_priority::{ThreadBuilder, ThreadPriority}; use winit::event_loop::{ControlFlow, EventLoop}; -mod application; +mod app; mod audio; mod controller; mod emulator; diff --git a/src/window.rs b/src/window.rs index 53dd01c..8f0f179 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,157 +1,21 @@ -use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, - }, - thread, -}; - use egui::{Context, ViewportBuilder, ViewportId}; -use game::GameWindow; -use game_screen::GameScreen; -use gilrs::{EventType, Gilrs}; -use input::InputWindow; -use winit::{event::KeyEvent, event_loop::EventLoopProxy}; - -use crate::{ - application::UserEvent, - controller::ControllerManager, - emulator::{EmulatorClient, EmulatorCommand, SimId}, - input::MappingProvider, -}; +pub use game::GameWindow; +pub use input::InputWindow; +use winit::event::KeyEvent; mod game; mod game_screen; mod input; -pub struct WindowManager { - p1: GameWindow, - p2: Arc>, - client: EmulatorClient, - input: ChildWindow, - controllers: ControllerManager, -} -impl WindowManager { - pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { - let mappings = MappingProvider::new(); - let controllers = ControllerManager::new(client.clone(), &mappings); - { - let mappings = mappings.clone(); - thread::spawn(|| process_gamepad_input(mappings, proxy)); - } - let input = ChildWindow::new(InputWindow::new(mappings)); - let p1 = GameWindow::new(client.clone(), SimId::Player1, input.open.clone()); - let p2 = GameWindow::new(client.clone(), SimId::Player2, input.open.clone()); - Self { - p1, - p2: Arc::new(Mutex::new(p2)), - client, - input, - controllers, - } - } - - pub fn init_renderer(&mut self, render_state: &egui_wgpu::RenderState) { - GameScreen::init_pipeline(render_state); - self.p2.lock().unwrap().init_renderer(render_state); - self.p1.init_renderer(render_state); - } - - pub fn initial_viewport(&self) -> ViewportBuilder { - self.p1.initial_viewport() - } - - pub fn show(&mut self, ctx: &Context) { - self.p1.show(ctx); - self.input.show(ctx); - if self.client.has_player_2() { - let (viewport_id, viewport) = { - let p2 = self.p2.lock().unwrap(); - (p2.viewport_id(), p2.initial_viewport()) - }; - let client = self.client.clone(); - let p2 = self.p2.clone(); - ctx.show_viewport_deferred(viewport_id, viewport, move |ctx, _| { - p2.lock().unwrap().show(ctx); - if ctx.input(|i| i.viewport().close_requested()) { - client.send_command(EmulatorCommand::StopSecondSim); - } - }); - } - } - - pub fn handle_key_event(&mut self, event: winit::event::KeyEvent) { - self.controllers.handle_key_event(&event); - self.input.handle_key_event(&event); - } - - pub fn handle_gamepad_event(&mut self, event: gilrs::Event) { - self.controllers.handle_gamepad_event(&event); - } -} - -fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy) { - let Ok(mut gilrs) = Gilrs::new() else { - eprintln!("could not connect gamepad listener"); - return; - }; - while let Some(event) = gilrs.next_event_blocking(None) { - if event.event == EventType::Connected { - let Some(gamepad) = gilrs.connected_gamepad(event.id) else { - continue; - }; - mappings.map_gamepad(SimId::Player1, &gamepad); - } - if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() { - // main thread has closed! we done - return; - } - } -} - -trait AppWindow { +pub trait AppWindow { fn viewport_id(&self) -> ViewportId; fn initial_viewport(&self) -> ViewportBuilder; fn show(&mut self, ctx: &Context); + fn on_init(&mut self, render_state: &egui_wgpu::RenderState) { + let _ = render_state; + } + fn on_destroy(&mut self) {} fn handle_key_event(&mut self, event: &KeyEvent) { let _ = event; } } - -struct ChildWindow { - pub open: Arc, - window: Arc>, -} -impl ChildWindow { - fn new(window: T) -> Self { - Self { - open: Arc::new(AtomicBool::new(false)), - window: Arc::new(Mutex::new(window)), - } - } - - fn show(&self, ctx: &Context) { - if !self.open.load(Ordering::Relaxed) { - return; - } - let (viewport_id, viewport) = { - let window = self.window.lock().unwrap(); - (window.viewport_id(), window.initial_viewport()) - }; - let open = self.open.clone(); - let window = self.window.clone(); - ctx.show_viewport_deferred(viewport_id, viewport, move |ctx, _| { - window.lock().unwrap().show(ctx); - if ctx.input(|i| i.viewport().close_requested()) { - open.store(false, Ordering::Relaxed); - ctx.request_repaint(); - } - }); - } - - fn handle_key_event(&self, event: &KeyEvent) { - if self.open.load(Ordering::Relaxed) { - self.window.lock().unwrap().handle_key_event(event); - } - } -} diff --git a/src/window/game.rs b/src/window/game.rs index b3c3b7d..23d4542 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -1,40 +1,32 @@ -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, +use crate::{ + app::UserEvent, + emulator::{EmulatorClient, EmulatorCommand, SimId}, }; - -use crate::emulator::{EmulatorClient, EmulatorCommand, SimId}; use egui::{ menu, Button, CentralPanel, Color32, Context, Response, TopBottomPanel, Ui, ViewportBuilder, ViewportCommand, ViewportId, WidgetText, }; +use winit::event_loop::EventLoopProxy; use super::{game_screen::GameScreen, AppWindow}; pub struct GameWindow { client: EmulatorClient, + proxy: EventLoopProxy, sim_id: SimId, - input_window_open: Arc, screen: Option, } impl GameWindow { - pub fn new(client: EmulatorClient, sim_id: SimId, input_window_open: Arc) -> Self { + pub fn new(client: EmulatorClient, proxy: EventLoopProxy, sim_id: SimId) -> Self { Self { client, + proxy, sim_id, - input_window_open, screen: None, } } - pub fn init_renderer(&mut self, render_state: &egui_wgpu::RenderState) { - let (screen, sink) = GameScreen::init(render_state); - self.client - .send_command(EmulatorCommand::SetRenderer(self.sim_id, sink)); - self.screen = Some(screen) - } - fn show_menu(&mut self, ctx: &Context, ui: &mut Ui) { ui.menu_button("ROM", |ui| { if ui.button("Open ROM").clicked() { @@ -101,7 +93,7 @@ impl GameWindow { }); ui.menu_button("Input", |ui| { if ui.button("Bind Inputs").clicked() { - self.input_window_open.store(true, Ordering::Relaxed); + self.proxy.send_event(UserEvent::OpenInput).unwrap(); ui.close_menu(); } }); @@ -112,6 +104,7 @@ impl GameWindow { { self.client .send_command(EmulatorCommand::StartSecondSim(None)); + self.proxy.send_event(UserEvent::OpenPlayer2).unwrap(); ui.close_menu(); } if self.client.has_player_2() { @@ -157,6 +150,19 @@ impl AppWindow for GameWindow { } }); } + + fn on_init(&mut self, render_state: &egui_wgpu::RenderState) { + let (screen, sink) = GameScreen::init(render_state); + self.client + .send_command(EmulatorCommand::SetRenderer(self.sim_id, sink)); + self.screen = Some(screen) + } + + fn on_destroy(&mut self) { + if self.sim_id == SimId::Player2 { + self.client.send_command(EmulatorCommand::StopSecondSim); + } + } } trait UiExt { diff --git a/src/window/game_screen.rs b/src/window/game_screen.rs index 2a00520..e1ee7dd 100644 --- a/src/window/game_screen.rs +++ b/src/window/game_screen.rs @@ -10,7 +10,7 @@ pub struct GameScreen { } impl GameScreen { - pub fn init_pipeline(render_state: &egui_wgpu::RenderState) { + fn init_pipeline(render_state: &egui_wgpu::RenderState) { let device = &render_state.device; let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -101,6 +101,8 @@ impl GameScreen { } pub fn init(render_state: &egui_wgpu::RenderState) -> (Self, TextureSink) { + Self::init_pipeline(render_state); + let device = &render_state.device; let queue = &render_state.queue; From 9ff62af310505bf90f1b9a7c9396941023c9a2c5 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Thu, 28 Nov 2024 12:39:08 -0500 Subject: [PATCH 17/21] Graphical fixes --- assets/{selawk.ttf => selawik.ttf} | Bin src/app.rs | 44 ++++++++++++++++++++++++----- src/window/game.rs | 13 +++++---- src/window/game_screen.rs | 14 +++++++-- 4 files changed, 56 insertions(+), 15 deletions(-) rename assets/{selawk.ttf => selawik.ttf} (100%) diff --git a/assets/selawk.ttf b/assets/selawik.ttf similarity index 100% rename from assets/selawk.ttf rename to assets/selawik.ttf diff --git a/src/app.rs b/src/app.rs index 4f21223..0196b15 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,8 @@ use std::{collections::HashSet, num::NonZero, sync::Arc, thread}; use egui::{ ahash::{HashMap, HashMapExt}, - Context, TextWrapMode, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo, + Context, FontData, FontDefinitions, FontFamily, TextWrapMode, ViewportBuilder, ViewportCommand, + ViewportId, ViewportInfo, }; use gilrs::{EventType, Gilrs}; use winit::{ @@ -89,20 +90,37 @@ impl ApplicationHandler for Application { } _ => {} } + let mut queue_redraw = false; + let mut inactive_viewports = HashSet::new(); match viewport.on_window_event(event) { Some(Action::Redraw) => { for viewport in self.viewports.values_mut() { - viewport.redraw(event_loop); + match viewport.redraw(event_loop) { + Some(Action::Redraw) => { + queue_redraw = true; + } + Some(Action::Close) => { + inactive_viewports.insert(viewport.id()); + } + None => {} + } } } Some(Action::Close) => { - self.viewports.remove(&viewport_id); - if viewport_id == ViewportId::ROOT { - event_loop.exit(); - } + inactive_viewports.insert(viewport_id); } None => {} } + self.viewports + .retain(|k, _| !inactive_viewports.contains(k)); + match self.viewports.get(&ViewportId::ROOT) { + Some(viewport) => { + if queue_redraw { + viewport.window.request_redraw(); + } + } + None => event_loop.exit(), + } } fn device_event( @@ -165,6 +183,17 @@ impl Viewport { ); let ctx = Context::default(); + let mut fonts = FontDefinitions::empty(); + fonts.font_data.insert( + "Selawik".into(), + FontData::from_static(include_bytes!("../assets/selawik.ttf")), + ); + fonts + .families + .get_mut(&FontFamily::Proportional) + .unwrap() + .insert(0, "Selawik".into()); + ctx.set_fonts(fonts); ctx.style_mut(|s| { s.wrap_mode = Some(TextWrapMode::Extend); s.visuals.menu_rounding = Default::default(); @@ -242,7 +271,7 @@ impl Viewport { self.state .handle_platform_output(&self.window, output.platform_output); - let Some(viewport_output) = output.viewport_output.remove(&ViewportId::ROOT) else { + let Some(mut viewport_output) = output.viewport_output.remove(&ViewportId::ROOT) else { return Some(Action::Close); }; @@ -255,6 +284,7 @@ impl Viewport { self.state = state; } self.commands.append(&mut deferred_commands); + self.commands.append(&mut viewport_output.commands); egui_winit::process_viewport_commands( &self.ctx, &mut self.info, diff --git a/src/window/game.rs b/src/window/game.rs index 23d4542..0ec7885 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -3,8 +3,8 @@ use crate::{ emulator::{EmulatorClient, EmulatorCommand, SimId}, }; use egui::{ - menu, Button, CentralPanel, Color32, Context, Response, TopBottomPanel, Ui, ViewportBuilder, - ViewportCommand, ViewportId, WidgetText, + menu, Button, CentralPanel, Color32, Context, Frame, Response, TopBottomPanel, Ui, + ViewportBuilder, ViewportCommand, ViewportId, WidgetText, }; use winit::event_loop::EventLoopProxy; @@ -67,7 +67,7 @@ impl GameWindow { for scale in 1..=4 { let label = format!("x{scale}"); let scale = scale as f32; - let dims = (384.0 * scale, 224.0 * scale + 20.0).into(); + let dims = (384.0 * scale, 224.0 * scale + 22.0).into(); if ui .selectable_button((current_dims - dims).length() < 1.0, label) .clicked() @@ -133,18 +133,19 @@ impl AppWindow for GameWindow { fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title("Shrooms VB") - .with_inner_size((384.0, 244.0)) + .with_inner_size((384.0, 246.0)) } fn show(&mut self, ctx: &Context) { TopBottomPanel::top("menubar") - .exact_height(20.0) + .exact_height(22.0) .show(ctx, |ui| { menu::bar(ui, |ui| { self.show_menu(ctx, ui); }); }); - CentralPanel::default().show(ctx, |ui| { + let frame = Frame::central_panel(&ctx.style()).fill(Color32::BLACK); + CentralPanel::default().frame(frame).show(ctx, |ui| { if let Some(screen) = self.screen.as_ref() { ui.add(screen); } diff --git a/src/window/game_screen.rs b/src/window/game_screen.rs index e1ee7dd..c70c9bc 100644 --- a/src/window/game_screen.rs +++ b/src/window/game_screen.rs @@ -171,14 +171,24 @@ struct GameScreenCallback { impl egui_wgpu::CallbackTrait for GameScreenCallback { fn paint( &self, - _info: egui::PaintCallbackInfo, + info: egui::PaintCallbackInfo, render_pass: &mut wgpu::RenderPass<'static>, callback_resources: &egui_wgpu::CallbackResources, ) { let resources: &SharedGameScreenResources = callback_resources.get().unwrap(); - // TODO: maintain aspect ratio + let viewport = info.viewport_in_pixels(); + let left = viewport.left_px as f32; + let top = viewport.top_px as f32; + let width = viewport.width_px as f32; + let height = viewport.height_px as f32; + let aspect_ratio = 384.0 / 224.0; + let w = width.min(height * aspect_ratio); + let h = height.min(width / aspect_ratio); + let x = left + (width - w) / 2.0; + let y = top + (height - h) / 2.0; render_pass.set_pipeline(&resources.pipeline); render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.set_viewport(x, y, w, h, 0.0, 1.0); render_pass.draw(0..6, 0..1); } } From 24474fabd0672139a561e06724ed14703e07283d Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Fri, 29 Nov 2024 14:36:46 -0500 Subject: [PATCH 18/21] Support choosing which player uses a controller --- src/app.rs | 5 ++- src/input.rs | 99 ++++++++++++++++++++++++++++++++++++++++++--- src/window/input.rs | 40 ++++++++++++++++-- 3 files changed, 134 insertions(+), 10 deletions(-) diff --git a/src/app.rs b/src/app.rs index 0196b15..3ef13fa 100644 --- a/src/app.rs +++ b/src/app.rs @@ -349,7 +349,10 @@ fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy, +} + pub struct GamepadMapping { buttons: HashMap, axes: HashMap, @@ -106,6 +114,7 @@ impl InputMapping { pub struct MappingProvider { device_mappings: Arc>>>>, sim_mappings: HashMap>>, + gamepad_info: Arc>>, } impl MappingProvider { @@ -136,6 +145,7 @@ impl MappingProvider { mappings.insert(SimId::Player2, Arc::new(RwLock::new(p2_mappings))); Self { device_mappings: Arc::new(RwLock::new(HashMap::new())), + gamepad_info: Arc::new(RwLock::new(HashMap::new())), sim_mappings: mappings, } } @@ -144,24 +154,101 @@ impl MappingProvider { self.sim_mappings.get(&sim_id).unwrap() } - pub fn map_gamepad(&self, sim_id: SimId, gamepad: &Gamepad) { + pub fn handle_gamepad_connect(&self, gamepad: &Gamepad) { let device_id = DeviceId( gamepad.vendor_id().unwrap_or_default(), gamepad.product_id().unwrap_or_default(), ); let mut lock = self.device_mappings.write().unwrap(); - let gamepad_mapping = match lock.entry(device_id) { - Entry::Occupied(entry) => entry.get().clone(), + let mappings = match lock.entry(device_id) { Entry::Vacant(entry) => { let mappings = GamepadMapping::for_gamepad(gamepad); - entry.insert(Arc::new(RwLock::new(mappings))).clone() + entry.insert(Arc::new(RwLock::new(mappings))) } - }; + Entry::Occupied(entry) => entry.into_mut(), + } + .clone(); drop(lock); + let mut lock = self.gamepad_info.write().unwrap(); + let bound_to = SimId::values() + .into_iter() + .find(|sim_id| lock.values().all(|info| info.bound_to != Some(*sim_id))); + if let Entry::Vacant(entry) = lock.entry(gamepad.id()) { + let info = GamepadInfo { + id: *entry.key(), + name: gamepad.name().to_string(), + device_id, + bound_to, + }; + entry.insert(info); + } + drop(lock); + if let Some(sim_id) = bound_to { + self.for_sim(sim_id) + .write() + .unwrap() + .gamepads + .insert(gamepad.id(), mappings); + } + } + + pub fn handle_gamepad_disconnect(&self, gamepad_id: GamepadId) { + let mut lock = self.gamepad_info.write().unwrap(); + let Some(info) = lock.remove(&gamepad_id) else { + return; + }; + if let Some(sim_id) = info.bound_to { + self.for_sim(sim_id) + .write() + .unwrap() + .gamepads + .remove(&gamepad_id); + } + } + + pub fn assign_gamepad(&self, gamepad_id: GamepadId, sim_id: SimId) { + self.unassign_gamepad(gamepad_id); + let mut lock = self.gamepad_info.write().unwrap(); + let Some(info) = lock.get_mut(&gamepad_id) else { + return; + }; + info.bound_to = Some(sim_id); + let device_id = info.device_id; + drop(lock); + let Some(device_mappings) = self + .device_mappings + .write() + .unwrap() + .get(&device_id) + .cloned() + else { + return; + }; self.for_sim(sim_id) .write() .unwrap() .gamepads - .insert(gamepad.id(), gamepad_mapping); + .insert(gamepad_id, device_mappings); + } + + pub fn unassign_gamepad(&self, gamepad_id: GamepadId) { + let mut lock = self.gamepad_info.write().unwrap(); + let Some(info) = lock.get_mut(&gamepad_id) else { + return; + }; + if let Some(sim_id) = info.bound_to { + let mut sim_mapping = self.for_sim(sim_id).write().unwrap(); + sim_mapping.gamepads.remove(&gamepad_id); + } + info.bound_to = None; + } + + pub fn gamepad_info(&self) -> Vec { + self.gamepad_info + .read() + .unwrap() + .values() + .cloned() + .collect() } } diff --git a/src/window/input.rs b/src/window/input.rs index b2bc956..fe7ded5 100644 --- a/src/window/input.rs +++ b/src/window/input.rs @@ -42,7 +42,7 @@ impl InputWindow { } } - fn render_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { + fn show_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { let mappings = self.mappings.for_sim(sim_id); let binding_names = { let mapping = mappings.read().unwrap(); @@ -86,6 +86,37 @@ impl InputWindow { } }); } + + fn show_gamepads(&mut self, ui: &mut Ui) { + let mut gamepads = self.mappings.gamepad_info(); + gamepads.sort_by_key(|g| usize::from(g.id)); + + if gamepads.is_empty() { + ui.label("No gamepads connected."); + return; + } + + for (index, gamepad) in gamepads.into_iter().enumerate() { + ui.horizontal(|ui| { + ui.label(format!("Gamepad {index}: {}", gamepad.name)); + let mut bound_to = gamepad.bound_to; + let mut rebind = false; + rebind |= ui + .selectable_value(&mut bound_to, Some(SimId::Player1), "Player 1") + .changed(); + rebind |= ui + .selectable_value(&mut bound_to, Some(SimId::Player2), "Player 2") + .changed(); + rebind |= ui.selectable_value(&mut bound_to, None, "Nobody").changed(); + if rebind { + match bound_to { + Some(sim_id) => self.mappings.assign_gamepad(gamepad.id, sim_id), + None => self.mappings.unassign_gamepad(gamepad.id), + } + } + }); + } + } } impl AppWindow for InputWindow { @@ -104,12 +135,14 @@ impl AppWindow for InputWindow { ui.horizontal(|ui| { ui.selectable_value(&mut self.active_tab, InputTab::Player1, "Player 1"); ui.selectable_value(&mut self.active_tab, InputTab::Player2, "Player 2"); + ui.selectable_value(&mut self.active_tab, InputTab::Gamepads, "Gamepads"); }); }); CentralPanel::default().show(ctx, |ui| { match self.active_tab { - InputTab::Player1 => self.render_key_bindings(ui, SimId::Player1), - InputTab::Player2 => self.render_key_bindings(ui, SimId::Player2), + InputTab::Player1 => self.show_key_bindings(ui, SimId::Player1), + InputTab::Player2 => self.show_key_bindings(ui, SimId::Player2), + InputTab::Gamepads => self.show_gamepads(ui), }; }); } @@ -130,4 +163,5 @@ impl AppWindow for InputWindow { enum InputTab { Player1, Player2, + Gamepads, } From ce7ba71ea048a05b9762b6759567276ae07ce8a9 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Fri, 29 Nov 2024 18:54:26 -0500 Subject: [PATCH 19/21] Support rebinding gamepad inputs --- src/app.rs | 20 ++++++--- src/input.rs | 90 +++++++++++++++++++++++++++++++------ src/window.rs | 3 ++ src/window/input.rs | 105 ++++++++++++++++++++++++++++++++++++++------ 4 files changed, 184 insertions(+), 34 deletions(-) diff --git a/src/app.rs b/src/app.rs index 3ef13fa..b7c0fb0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,7 +8,7 @@ use egui::{ use gilrs::{EventType, Gilrs}; use winit::{ application::ApplicationHandler, - event::{KeyEvent, WindowEvent}, + event::WindowEvent, event_loop::{ActiveEventLoop, EventLoopProxy}, window::Window, }; @@ -83,7 +83,7 @@ impl ApplicationHandler for Application { match &event { WindowEvent::KeyboardInput { event, .. } => { self.controllers.handle_key_event(event); - viewport.handle_key_event(event); + viewport.app.handle_key_event(event); } WindowEvent::Focused(new_focused) => { self.focused = new_focused.then_some(viewport_id); @@ -143,7 +143,17 @@ impl ApplicationHandler for Application { fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { match event { - UserEvent::GamepadEvent(event) => self.controllers.handle_gamepad_event(&event), + UserEvent::GamepadEvent(event) => { + self.controllers.handle_gamepad_event(&event); + let Some(viewport) = self + .focused + .as_ref() + .and_then(|id| self.viewports.get_mut(id)) + else { + return; + }; + viewport.app.handle_gamepad_event(&event); + } UserEvent::OpenInput => { let input = InputWindow::new(self.mappings.clone()); self.open(event_loop, Box::new(input)); @@ -248,10 +258,6 @@ impl Viewport { } } - pub fn handle_key_event(&mut self, event: &KeyEvent) { - self.app.handle_key_event(event); - } - fn redraw(&mut self, event_loop: &ActiveEventLoop) -> Option { let mut input = self.state.take_egui_input(&self.window); input.viewports = std::iter::once((ViewportId::ROOT, self.info.clone())).collect(); diff --git a/src/input.rs b/src/input.rs index c604d4b..e818521 100644 --- a/src/input.rs +++ b/src/input.rs @@ -19,6 +19,11 @@ pub struct GamepadInfo { pub bound_to: Option, } +pub trait Mappings { + fn mapping_names(&self) -> HashMap>; + fn clear_mappings(&mut self, key: VBKey); +} + pub struct GamepadMapping { buttons: HashMap, axes: HashMap, @@ -54,6 +59,59 @@ impl GamepadMapping { Self { buttons, axes } } + + pub fn add_button_mapping(&mut self, key: VBKey, code: Code) { + let entry = self.buttons.entry(code).or_insert(VBKey::empty()); + *entry = entry.union(key); + } + + pub fn add_axis_neg_mapping(&mut self, key: VBKey, code: Code) { + let entry = self + .axes + .entry(code) + .or_insert((VBKey::empty(), VBKey::empty())); + entry.0 = entry.0.union(key); + } + + pub fn add_axis_pos_mapping(&mut self, key: VBKey, code: Code) { + let entry = self + .axes + .entry(code) + .or_insert((VBKey::empty(), VBKey::empty())); + entry.1 = entry.1.union(key); + } +} + +impl Mappings for GamepadMapping { + fn mapping_names(&self) -> HashMap> { + let mut results: HashMap> = HashMap::new(); + for (axis, (left_keys, right_keys)) in &self.axes { + for key in left_keys.iter() { + results.entry(key).or_default().push(format!("-{axis}")); + } + for key in right_keys.iter() { + results.entry(key).or_default().push(format!("+{axis}")); + } + } + for (button, keys) in &self.buttons { + for key in keys.iter() { + results.entry(key).or_default().push(format!("{button}")); + } + } + results + } + + fn clear_mappings(&mut self, key: VBKey) { + self.axes.retain(|_, (left, right)| { + *left = left.difference(key); + *right = right.difference(key); + !(left.is_empty() && right.is_empty()) + }); + self.buttons.retain(|_, keys| { + *keys = keys.difference(key); + !keys.is_empty() + }); + } } #[derive(Default)] @@ -81,15 +139,10 @@ impl InputMapping { let entry = self.keys.entry(keyboard_key).or_insert(VBKey::empty()); *entry = entry.union(key); } +} - pub fn clear_keyboard_mappings(&mut self, key: VBKey) { - self.keys.retain(|_, keys| { - *keys = keys.difference(key); - *keys != VBKey::empty() - }); - } - - pub fn keyboard_mapping_names(&self) -> HashMap { +impl Mappings for InputMapping { + fn mapping_names(&self) -> HashMap> { let mut results: HashMap> = HashMap::new(); for (keyboard_key, keys) in &self.keys { let name = match keyboard_key { @@ -101,12 +154,13 @@ impl InputMapping { } } results - .into_iter() - .map(|(k, mut v)| { - v.sort(); - (k, v.join(", ")) - }) - .collect() + } + + fn clear_mappings(&mut self, key: VBKey) { + self.keys.retain(|_, keys| { + *keys = keys.difference(key); + !keys.is_empty() + }); } } @@ -154,6 +208,14 @@ impl MappingProvider { self.sim_mappings.get(&sim_id).unwrap() } + pub fn for_gamepad(&self, gamepad_id: GamepadId) -> Option>> { + let lock = self.gamepad_info.read().unwrap(); + let device_id = lock.get(&gamepad_id)?.device_id; + drop(lock); + let lock = self.device_mappings.read().unwrap(); + lock.get(&device_id).cloned() + } + pub fn handle_gamepad_connect(&self, gamepad: &Gamepad) { let device_id = DeviceId( gamepad.vendor_id().unwrap_or_default(), diff --git a/src/window.rs b/src/window.rs index 8f0f179..fdc95d3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -18,4 +18,7 @@ pub trait AppWindow { fn handle_key_event(&mut self, event: &KeyEvent) { let _ = event; } + fn handle_gamepad_event(&mut self, event: &gilrs::Event) { + let _ = event; + } } diff --git a/src/window/input.rs b/src/window/input.rs index fe7ded5..2999e8b 100644 --- a/src/window/input.rs +++ b/src/window/input.rs @@ -2,17 +2,19 @@ use egui::{ Button, CentralPanel, Context, Label, Layout, TopBottomPanel, Ui, ViewportBuilder, ViewportId, }; use egui_extras::{Column, TableBuilder}; +use gilrs::{EventType, GamepadId}; +use std::sync::RwLock; use crate::{ emulator::{SimId, VBKey}, - input::MappingProvider, + input::{MappingProvider, Mappings}, }; use super::AppWindow; pub struct InputWindow { mappings: MappingProvider, - now_binding: Option<(SimId, VBKey)>, + now_binding: Option, active_tab: InputTab, } @@ -42,11 +44,15 @@ impl InputWindow { } } - fn show_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { - let mappings = self.mappings.for_sim(sim_id); - let binding_names = { + fn show_bindings( + &mut self, + ui: &mut Ui, + mappings: &RwLock, + bind_message: &str, + ) { + let mut names = { let mapping = mappings.read().unwrap(); - mapping.keyboard_mapping_names() + mapping.mapping_names() }; TableBuilder::new(ui) .column(Column::remainder()) @@ -56,29 +62,33 @@ impl InputWindow { for keys in KEY_NAMES.chunks_exact(2) { body.row(20.0, |mut row| { for (key, name) in keys { - let binding = binding_names.get(key).map(|s| s.as_str()); + let binding = names.remove(key).map(|mut s| { + s.sort(); + s.join(", ") + }); row.col(|ui| { let size = ui.available_size_before_wrap(); let width = size.x; let height = size.y; ui.add_sized((width * 0.2, height), Label::new(*name)); - let label_text = if self.now_binding == Some((sim_id, *key)) { - "Press any input" + let label_text = if self.now_binding == Some(*key) { + bind_message } else { - binding.unwrap_or("") + binding.as_deref().unwrap_or("") }; if ui .add_sized((width * 0.6, height), Button::new(label_text)) .clicked() { - self.now_binding = Some((sim_id, *key)) + self.now_binding = Some(*key); } if ui .add_sized(ui.available_size(), Button::new("Clear")) .clicked() { let mut mapping = mappings.write().unwrap(); - mapping.clear_keyboard_mappings(*key); + mapping.clear_mappings(*key); + self.now_binding = None; } }); } @@ -87,6 +97,11 @@ impl InputWindow { }); } + fn show_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) { + let mappings = self.mappings.for_sim(sim_id).clone(); + self.show_bindings(ui, &mappings, "Press any key"); + } + fn show_gamepads(&mut self, ui: &mut Ui) { let mut gamepads = self.mappings.gamepad_info(); gamepads.sort_by_key(|g| usize::from(g.id)); @@ -114,9 +129,21 @@ impl InputWindow { None => self.mappings.unassign_gamepad(gamepad.id), } } + ui.separator(); + if ui.button("Rebind").clicked() { + self.active_tab = InputTab::RebindGamepad(gamepad.id); + } }); } } + + fn show_gamepad_bindings(&mut self, ui: &mut Ui, gamepad_id: GamepadId) { + let Some(mappings) = self.mappings.for_gamepad(gamepad_id) else { + self.active_tab = InputTab::Gamepads; + return; + }; + self.show_bindings(ui, &mappings, "Press any input"); + } } impl AppWindow for InputWindow { @@ -133,9 +160,17 @@ impl AppWindow for InputWindow { fn show(&mut self, ctx: &Context) { TopBottomPanel::top("options").show(ctx, |ui| { ui.horizontal(|ui| { + let old_active_tab = self.active_tab; ui.selectable_value(&mut self.active_tab, InputTab::Player1, "Player 1"); ui.selectable_value(&mut self.active_tab, InputTab::Player2, "Player 2"); ui.selectable_value(&mut self.active_tab, InputTab::Gamepads, "Gamepads"); + if matches!(self.active_tab, InputTab::RebindGamepad(_)) { + let tab = self.active_tab; + ui.selectable_value(&mut self.active_tab, tab, "Rebind Gamepad"); + } + if old_active_tab != self.active_tab { + self.now_binding = None; + } }); }); CentralPanel::default().show(ctx, |ui| { @@ -143,6 +178,7 @@ impl AppWindow for InputWindow { InputTab::Player1 => self.show_key_bindings(ui, SimId::Player1), InputTab::Player2 => self.show_key_bindings(ui, SimId::Player2), InputTab::Gamepads => self.show_gamepads(ui), + InputTab::RebindGamepad(id) => self.show_gamepad_bindings(ui, id), }; }); } @@ -151,12 +187,54 @@ impl AppWindow for InputWindow { if !event.state.is_pressed() { return; } - let Some((sim_id, vb)) = self.now_binding.take() else { + let sim_id = match self.active_tab { + InputTab::Player1 => SimId::Player1, + InputTab::Player2 => SimId::Player2, + _ => { + return; + } + }; + let Some(vb) = self.now_binding.take() else { return; }; let mut mappings = self.mappings.for_sim(sim_id).write().unwrap(); mappings.add_keyboard_mapping(vb, event.physical_key); } + + fn handle_gamepad_event(&mut self, event: &gilrs::Event) { + let InputTab::RebindGamepad(gamepad_id) = self.active_tab else { + return; + }; + if gamepad_id != event.id { + return; + } + let Some(mappings) = self.mappings.for_gamepad(gamepad_id) else { + return; + }; + let Some(vb) = self.now_binding else { + return; + }; + match event.event { + EventType::ButtonPressed(_, code) => { + let mut mapping = mappings.write().unwrap(); + mapping.add_button_mapping(vb, code); + self.now_binding.take(); + } + EventType::AxisChanged(_, value, code) => { + if value < -0.75 { + let mut mapping = mappings.write().unwrap(); + mapping.add_axis_neg_mapping(vb, code); + self.now_binding.take(); + } + if value > 0.75 { + let mut mapping = mappings.write().unwrap(); + mapping.add_axis_pos_mapping(vb, code); + self.now_binding.take(); + } + } + _ => {} + } + } } #[derive(Clone, Copy, PartialEq, Eq)] @@ -164,4 +242,5 @@ enum InputTab { Player1, Player2, Gamepads, + RebindGamepad(GamepadId), } From 0c35a1e234133c55ac7f8d2f13414d7ad459322d Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Fri, 29 Nov 2024 19:09:00 -0500 Subject: [PATCH 20/21] Make it easy to clear/reset mappings --- src/input.rs | 66 ++++++++++++++++++++++++++++++++------------- src/window/input.rs | 11 ++++++++ 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/input.rs b/src/input.rs index e818521..057d616 100644 --- a/src/input.rs +++ b/src/input.rs @@ -22,19 +22,23 @@ pub struct GamepadInfo { pub trait Mappings { fn mapping_names(&self) -> HashMap>; fn clear_mappings(&mut self, key: VBKey); + fn clear_all_mappings(&mut self); + fn use_default_mappings(&mut self); } pub struct GamepadMapping { buttons: HashMap, axes: HashMap, + default_buttons: HashMap, + default_axes: HashMap, } impl GamepadMapping { fn for_gamepad(gamepad: &Gamepad) -> Self { - let mut buttons = HashMap::new(); + let mut default_buttons = HashMap::new(); let mut default_button = |btn: Button, key: VBKey| { if let Some(code) = gamepad.button_code(btn) { - buttons.insert(code, key); + default_buttons.insert(code, key); } }; default_button(Button::South, VBKey::A); @@ -44,10 +48,10 @@ impl GamepadMapping { default_button(Button::Start, VBKey::STA); default_button(Button::Select, VBKey::SEL); - let mut axes = HashMap::new(); + let mut default_axes = HashMap::new(); let mut default_axis = |axis: Axis, neg: VBKey, pos: VBKey| { if let Some(code) = gamepad.axis_code(axis) { - axes.insert(code, (neg, pos)); + default_axes.insert(code, (neg, pos)); } }; default_axis(Axis::LeftStickX, VBKey::LL, VBKey::LR); @@ -57,7 +61,12 @@ impl GamepadMapping { default_axis(Axis::DPadX, VBKey::LL, VBKey::LR); default_axis(Axis::DPadY, VBKey::LD, VBKey::LU); - Self { buttons, axes } + Self { + buttons: default_buttons.clone(), + axes: default_axes.clone(), + default_buttons, + default_axes, + } } pub fn add_button_mapping(&mut self, key: VBKey, code: Code) { @@ -112,6 +121,16 @@ impl Mappings for GamepadMapping { !keys.is_empty() }); } + + fn clear_all_mappings(&mut self) { + self.axes.clear(); + self.buttons.clear(); + } + + fn use_default_mappings(&mut self) { + self.axes = self.default_axes.clone(); + self.buttons = self.default_buttons.clone(); + } } #[derive(Default)] @@ -162,23 +181,15 @@ impl Mappings for InputMapping { !keys.is_empty() }); } -} -#[derive(Clone)] -pub struct MappingProvider { - device_mappings: Arc>>>>, - sim_mappings: HashMap>>, - gamepad_info: Arc>>, -} + fn clear_all_mappings(&mut self) { + self.keys.clear(); + } -impl MappingProvider { - pub fn new() -> Self { - let mut mappings = HashMap::new(); - - let mut p1_mappings = InputMapping::default(); - let p2_mappings = InputMapping::default(); + fn use_default_mappings(&mut self) { + self.keys.clear(); let mut default_key = |code, key| { - p1_mappings.add_keyboard_mapping(key, PhysicalKey::Code(code)); + self.keys.insert(PhysicalKey::Code(code), key); }; default_key(KeyCode::KeyA, VBKey::SEL); default_key(KeyCode::KeyS, VBKey::STA); @@ -194,6 +205,23 @@ impl MappingProvider { default_key(KeyCode::ArrowLeft, VBKey::LL); default_key(KeyCode::ArrowDown, VBKey::LD); default_key(KeyCode::ArrowRight, VBKey::LR); + } +} + +#[derive(Clone)] +pub struct MappingProvider { + device_mappings: Arc>>>>, + sim_mappings: HashMap>>, + gamepad_info: Arc>>, +} + +impl MappingProvider { + pub fn new() -> Self { + let mut mappings = HashMap::new(); + + let mut p1_mappings = InputMapping::default(); + p1_mappings.use_default_mappings(); + let p2_mappings = InputMapping::default(); mappings.insert(SimId::Player1, Arc::new(RwLock::new(p1_mappings))); mappings.insert(SimId::Player2, Arc::new(RwLock::new(p2_mappings))); diff --git a/src/window/input.rs b/src/window/input.rs index 2999e8b..c93c6e4 100644 --- a/src/window/input.rs +++ b/src/window/input.rs @@ -50,6 +50,17 @@ impl InputWindow { mappings: &RwLock, bind_message: &str, ) { + ui.horizontal(|ui| { + if ui.button("Use defaults").clicked() { + mappings.write().unwrap().use_default_mappings(); + self.now_binding = None; + } + if ui.button("Clear all").clicked() { + mappings.write().unwrap().clear_all_mappings(); + self.now_binding = None; + } + }); + ui.separator(); let mut names = { let mapping = mappings.read().unwrap(); mapping.mapping_names() From 3eb6b9cd3ec4742d032093e06556a0f96e15c210 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Fri, 29 Nov 2024 19:16:08 -0500 Subject: [PATCH 21/21] Update core for good luck --- shrooms-vb-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shrooms-vb-core b/shrooms-vb-core index f45636a..18b2c58 160000 --- a/shrooms-vb-core +++ b/shrooms-vb-core @@ -1 +1 @@ -Subproject commit f45636a491a50c5847b92a51912e6e19f21af98f +Subproject commit 18b2c589e6cacec5a0bd0f450cedf2f8fe3a2bc8