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, HEIGHT, WIDTH}, util::scale_buffer_in_place, }; use keyboard_types::{Code, KeyState}; use nih_plug::prelude::*; use pixels::{Pixels, SurfaceTexture}; use crate::{FrameReceiver, 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 = 3; const S_WIDTH: usize = WIDTH * EXTRA_SCALE; const S_HEIGHT: usize = HEIGHT * EXTRA_SCALE; 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(S_WIDTH as f64, S_HEIGHT 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, scale, 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 { pix: Pixels, scale: usize, scaled_buf: Vec<[u8; 4]>, frame_receiver: Arc, joypad_sender: Arc, } 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 (pix, scale, scaled_buf) = init_pixbuf(info, window); Self { pix, scale, scaled_buf, frame_receiver, joypad_sender, } } } fn init_pixbuf(info: WindowInfo, window: &mut Window) -> (Pixels, usize, Vec<[u8; 4]>) { let physical_size = info.physical_size(); let scale = (physical_size.width as usize / WIDTH).min(physical_size.height as usize / HEIGHT); let scaled_buf = vec![[0, 0, 0, 0xFF]; WIDTH * scale * HEIGHT * scale]; ( pixels::Pixels::new( physical_size.width, physical_size.height, SurfaceTexture::new(physical_size.width, physical_size.height, window), ) .unwrap(), scale, scaled_buf, ) } 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() { if self.scale != 1 { scale_buffer_in_place(buf, &mut self.scaled_buf, WIDTH, HEIGHT, self.scale); } for (pixel, source) in self .pix .get_frame_mut() .chunks_exact_mut(4) .zip(&self.scaled_buf) { pixel.copy_from_slice(source); } self.pix.render().unwrap(); } } } fn on_event(&mut self, window: &mut Window, event: baseview::Event) -> EventStatus { match event { Event::Window(WindowEvent::Resized(info)) => { (self.pix, self.scale, self.scaled_buf) = init_pixbuf(info, 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<(JoypadButtons, bool)>, } impl EmulatorRenderer { #[allow(clippy::type_complexity)] pub(super) fn new() -> (Self, Receiver>, Sender<(JoypadButtons, bool)>) { let (tx, rx) = mpsc::channel::>(); let (keys_tx, keys) = mpsc::channel::<(JoypadButtons, bool)>(); ( Self { tx, joypad: JoypadState::default(), keys, }, rx, keys_tx, ) } } impl Renderer<[u8; 4]> for EmulatorRenderer { fn prepare(&mut self, _width: usize, _height: usize) {} #[allow(unused_must_use)] fn display(&mut self, buffer: &[[u8; 4]]) { 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 } }