use std::sync::{ mpsc::{self, Receiver, Sender}, Arc, }; use baseview::{ Event, EventStatus, Size, Window, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, }; use gb_emu_lib::{ connect::{JoypadButtons, JoypadState, Renderer, ResolutionData, HEIGHT, WIDTH}, renderer::{RendererBackend, RendererBackendManager, WindowOptions}, }; use keyboard_types::{Code, KeyState}; use nih_plug::prelude::*; use raw_window_handle::HasRawDisplayHandle; use crate::{Frame, FrameReceiver, JoypadInfo, JoypadSender}; pub struct Emulator { frame_receiver: Arc, joypad_sender: Arc, } impl Emulator { pub fn new(frame_receiver: Arc, joypad_sender: Arc) -> Self { Self { frame_receiver, joypad_sender, } } } const EXTRA_SCALE: usize = 4; impl Editor for Emulator { fn spawn( &self, parent: ParentWindowHandle, _context: Arc, ) -> Box { let fr_cloned = self.frame_receiver.clone(); let js_cloned = self.joypad_sender.clone(); // let (size, scale) = if cfg!(target_os = "macos") { // ( // Size::new((WIDTH * EXTRA_SCALE) as f64, (HEIGHT * EXTRA_SCALE) as f64), // baseview::WindowScalePolicy::SystemScaleFactor, // ) // } else { // ( // Size::new(WIDTH as f64, HEIGHT as f64), // baseview::WindowScalePolicy::ScaleFactor(EXTRA_SCALE as f64), // ) // }; Window::open_parented( &parent, WindowOpenOptions { title: String::from("gb-emu"), size: Size::new((WIDTH * EXTRA_SCALE) as f64, (HEIGHT * EXTRA_SCALE) as f64), scale: baseview::WindowScalePolicy::SystemScaleFactor, gl_config: None, }, |w| EmulatorWindow::new(w, fr_cloned, js_cloned), ); Box::new(Self::new( self.frame_receiver.clone(), self.joypad_sender.clone(), )) } fn size(&self) -> (u32, u32) { ((WIDTH * EXTRA_SCALE) as u32, (HEIGHT * EXTRA_SCALE) as u32) } fn set_scale_factor(&self, _factor: f32) -> bool { true } fn param_value_changed(&self, _id: &str, _normalized_value: f32) {} fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {} fn param_values_changed(&self) {} } pub struct EmulatorWindow { renderer: RendererBackend, manager: Arc, frame_receiver: Arc, joypad_sender: Arc, current_resolution: ResolutionData, } impl EmulatorWindow { fn new( window: &mut Window, frame_receiver: Arc, joypad_sender: Arc, ) -> Self { let info = WindowInfo::from_logical_size( Size::new(WIDTH as f64, HEIGHT as f64), EXTRA_SCALE as f64, ); let physical_size = info.physical_size(); let current_resolution = ResolutionData { real_width: physical_size.width, real_height: physical_size.height, scaled_width: WIDTH as u32, scaled_height: HEIGHT as u32, }; let manager = Arc::new(RendererBackendManager::new(window.raw_display_handle())); let renderer = RendererBackend::new(current_resolution, window, WindowOptions { shader_path: Some(std::path::PathBuf::from( "./test-roms/shaders/slang-shaders/handheld/console-border/gbc-lcd-grid-v2.slangp", )), }, manager.clone()); Self { renderer, manager, frame_receiver, joypad_sender, current_resolution, } } } impl WindowHandler for EmulatorWindow { fn on_frame(&mut self, _window: &mut Window) { if let Some(ref mut receiver) = *self.frame_receiver.lock().expect("failed to lock mutex") { if let Some(ref buf) = receiver.try_iter().last() { self.renderer.new_frame(buf); } } self.renderer.render(self.current_resolution, &self.manager); } fn on_event(&mut self, window: &mut Window, event: baseview::Event) -> EventStatus { match event { Event::Window(WindowEvent::Resized(info)) => { let physical_size = info.physical_size(); self.current_resolution = ResolutionData { real_width: physical_size.width, real_height: physical_size.height, scaled_width: WIDTH as u32, scaled_height: HEIGHT as u32, }; self.renderer.resize(self.current_resolution, window); EventStatus::Captured } Event::Keyboard(event) => { let status = event.state == KeyState::Down; if let Some(button) = match event.code { Code::Equal => Some(JoypadButtons::Start), Code::Minus => Some(JoypadButtons::Select), Code::Quote => Some(JoypadButtons::A), Code::Semicolon => Some(JoypadButtons::B), Code::KeyW | Code::ArrowUp => Some(JoypadButtons::Up), Code::KeyA | Code::ArrowLeft => Some(JoypadButtons::Left), Code::KeyS | Code::ArrowDown => Some(JoypadButtons::Down), Code::KeyD | Code::ArrowRight => Some(JoypadButtons::Right), _ => None, } { if let Some(ref mut sender) = *self.joypad_sender.lock().expect("failed to lock mutex") { sender.send((button, status)).unwrap(); } EventStatus::Captured } else { EventStatus::Ignored } } _ => EventStatus::Ignored, } } } pub struct EmulatorRenderer { tx: Sender, joypad: JoypadState, keys: Receiver, } impl EmulatorRenderer { pub(super) fn new() -> (Self, Receiver, Sender) { let (tx, rx) = mpsc::channel::(); let (keys_tx, keys) = mpsc::channel::(); ( Self { tx, joypad: JoypadState::default(), keys, }, rx, keys_tx, ) } } impl Renderer<[u8; 4]> for EmulatorRenderer { fn prepare(&mut self, _width: usize, _height: usize) {} fn display(&mut self, buffer: &[[u8; 4]]) { let _ = self.tx.send(buffer.to_vec()); } fn latest_joypad_state(&mut self) -> JoypadState { while let Ok((key, state)) = self.keys.try_recv() { match key { JoypadButtons::Down => self.joypad.down = state, JoypadButtons::Up => self.joypad.up = state, JoypadButtons::Left => self.joypad.left = state, JoypadButtons::Right => self.joypad.right = state, JoypadButtons::Start => self.joypad.start = state, JoypadButtons::Select => self.joypad.select = state, JoypadButtons::B => self.joypad.b = state, JoypadButtons::A => self.joypad.a = state, } } self.joypad } }