From e95e13df1e018a0dcf477edababfc01314aa5781 Mon Sep 17 00:00:00 2001 From: lifning <> Date: Thu, 4 Nov 2021 11:23:02 -0700 Subject: [PATCH] add checks for sequencing, fix audio sync in paraLLEl-n64 --- ferretro_base/Cargo.toml | 16 - ferretro_base/examples/sdl2_emulator.rs | 453 ------------------ ferretro_base/src/retro/loading.rs | 11 +- .../examples/multifunction_emulator.rs | 2 +- .../src/provided/sdl2/audio.rs | 11 +- 5 files changed, 20 insertions(+), 473 deletions(-) delete mode 100644 ferretro_base/examples/sdl2_emulator.rs diff --git a/ferretro_base/Cargo.toml b/ferretro_base/Cargo.toml index 930e0a0..6672807 100644 --- a/ferretro_base/Cargo.toml +++ b/ferretro_base/Cargo.toml @@ -11,19 +11,3 @@ cc = "^1" libretro-sys = "0.1" libloading = "0.5" num_enum = "0.4" - -# examples (both) -structopt = { version = "0.3", optional = true } -# example: sdl2_emulator -sdl2 = { version = "0.32", optional = true } -crossbeam-channel = { version = "0.4", optional = true } -# example: ffmpeg_recorder -ffmpeg-next = { version = "4.3.8", optional = true } - -[features] -example_sdl2 = ["sdl2", "crossbeam-channel", "structopt"] -example_ffmpeg = ["ffmpeg-next", "structopt"] - -[[example]] -name = "sdl2_emulator" -required-features = ["example_sdl2"] diff --git a/ferretro_base/examples/sdl2_emulator.rs b/ferretro_base/examples/sdl2_emulator.rs deleted file mode 100644 index 906c075..0000000 --- a/ferretro_base/examples/sdl2_emulator.rs +++ /dev/null @@ -1,453 +0,0 @@ -extern crate crossbeam_channel; -extern crate ferretro_base; -extern crate sdl2; - -use ferretro_base::retro; -use ferretro_base::prelude::*; - -use core::pin::Pin; - -use std::ffi::CStr; -use std::io::Read; -use std::path::{Path, PathBuf}; -use std::time::{Duration, Instant}; - -use structopt::StructOpt; - -use sdl2::audio::{AudioCallback, AudioFormat, AudioSpec, AudioSpecDesired, AudioDevice}; -use sdl2::controller::{GameController, Button, Axis}; -use sdl2::event::Event; -use sdl2::keyboard::Keycode; -use sdl2::rect::Rect; -use sdl2::render::WindowCanvas; - -struct MyEmulator { - retro: retro::wrapper::LibretroWrapper, - core_path: PathBuf, - sys_path: Option, - - preferred_pad: Option, - - sys_info: SystemInfo, - av_info: SystemAvInfo, - - sdl_context: sdl2::Sdl, - - // video bits - canvas: WindowCanvas, - pixel_format: sdl2::pixels::PixelFormatEnum, - - // audio bits - audio_buffer: Vec, - audio_spec: AudioSpec, - audio_device: AudioDevice, - audio_sender: crossbeam_channel::Sender>, - - // input bits - gamepad_subsys: sdl2::GameControllerSubsystem, - gamepads: Vec, - pressed_keys: Vec, -} - -impl MyEmulator { - pub fn new(core_path: impl AsRef, sys_path: &Option>) -> Pin> { - let core_path = PathBuf::from(core_path.as_ref()); - let lib = libloading::Library::new(&core_path).unwrap(); - let raw_retro = retro::loading::LibretroApi::from_library(lib).unwrap(); - let retro = retro::wrapper::LibretroWrapper::from(raw_retro); - - let sys_info = retro.get_system_info(); - let title = format!( - "{} - rust libretro", - unsafe { CStr::from_ptr(sys_info.library_name) }.to_string_lossy() - ); - - let mut av_info = retro.get_system_av_info(); - let pixel_format = sdl2::pixels::PixelFormatEnum::ARGB1555; - - // HACK: some cores don't report this 'til we get an environ call to set_system_av_info... - // which is too late for this constructor to pass along to SDL. - if av_info.timing.sample_rate == 0.0 { - av_info.timing.sample_rate = 32040.0; - } - - let sdl_context = sdl2::init().unwrap(); - - let window = sdl_context - .video() - .unwrap() - .window(title.as_str(), av_info.geometry.base_width, av_info.geometry.base_height) - .build() - .unwrap(); - - let canvas = window.into_canvas().build().unwrap(); - - let (audio_sender, audio_receiver) = crossbeam_channel::bounded(2); - - let audio = sdl_context.audio().unwrap(); - let desired_spec = AudioSpecDesired { - freq: Some(av_info.timing.sample_rate.round() as i32), - channels: Some(2), - samples: None, - }; - let mut audio_spec = None; - let audio_device = audio - .open_playback(None, &desired_spec, |spec| { - if spec.format != AudioFormat::S16LSB { - println!("unsupported audio format {:?}", spec.format); - } - audio_spec = Some(spec.clone()); - MySdlAudio { - audio_spec: spec, - audio_receiver, - } - }) - .unwrap(); - - let gamepad_subsys = sdl_context.game_controller().unwrap(); - let mut gamepads = Vec::new(); - for i in 0..gamepad_subsys.num_joysticks().unwrap() { - gamepads.extend(gamepad_subsys.open(i).into_iter()); - } - - let pressed_keys = Vec::new(); - - let emu = MyEmulator { - retro, - core_path, - sys_path: sys_path.as_ref().map(|p| p.as_ref().to_path_buf()), - preferred_pad: None, - av_info, - sys_info, - sdl_context, - canvas, - pixel_format, - audio_buffer: Default::default(), - audio_spec: audio_spec.unwrap(), - audio_device, - audio_sender, - gamepad_subsys, - gamepads, - pressed_keys, - }; - let mut pin_emu = Box::pin(emu); - retro::wrapper::set_handler(pin_emu.as_mut()); - pin_emu.retro.init(); - pin_emu - } - - pub fn run_loop(&mut self) { - self.audio_device.resume(); - let mut event_pump = self.sdl_context.event_pump().unwrap(); - 'running: loop { - let frame_begin = Instant::now(); - - for event in event_pump.poll_iter() { - match event { - Event::Quit { .. } - | Event::KeyDown { - keycode: Some(Keycode::Escape), - .. - } => break 'running, - _ => {} - } - } - - self.update_key_state(&event_pump.keyboard_state()); - - // The rest of the game loop goes here... - self.retro.run(); - self.canvas.present(); - - // similar hack to the sample rate, make sure we don't divide by zero. - let mut spf = 1.0 / self.av_info.timing.fps; - if spf.is_nan() || spf.is_infinite() { - spf = 1.0 / 60.0; - } - Duration::from_secs_f64(spf) - .checked_sub(frame_begin.elapsed()) - .map(std::thread::sleep); - } - } - - pub fn load_game(&mut self, rom: impl AsRef) { - let path = rom.as_ref(); - let mut data = None; - let mut v = Vec::new(); - if !self.sys_info.need_fullpath { - if let Ok(mut f) = std::fs::File::open(path) { - if f.read_to_end(&mut v).is_ok() { - data = Some(v.as_ref()); - } - } - } - self.retro - .load_game(Some(path), data, None) - .unwrap(); - if let Some(device) = self.preferred_pad { - for port in 0..self.gamepads.len() as u32 { - self.retro.set_controller_port_device(port, device); - } - } - } - - pub fn update_key_state<'a>(&mut self, keyboard_state: &sdl2::keyboard::KeyboardState<'a>){ - let keys: Vec = keyboard_state.pressed_scancodes().filter_map(Keycode::from_scancode).collect(); - self.pressed_keys = keys; - } - - fn send_audio_samples(&mut self) { - let stereo_samples = self.audio_spec.samples as usize * 2; - while self.audio_buffer.len() >= stereo_samples { - let remainder = self.audio_buffer.split_off(stereo_samples); - let msg = std::mem::replace(&mut self.audio_buffer, remainder); - let _ = self.audio_sender.try_send(msg); - } - } - - fn input_state_gamepad(&mut self, port: u32, device: &InputDeviceId, index: &InputIndex) -> i16 { - match self.gamepads.get(port as usize) { - Some(gamepad) => { - match device { - InputDeviceId::Joypad(button) => { - match button_map(&button) { - Some(x) => gamepad.button(x) as i16, - None => match button { - JoypadButton::L2 => gamepad.axis(Axis::TriggerLeft), - JoypadButton::R2 => gamepad.axis(Axis::TriggerRight), - _ => 0, - } - } - } - InputDeviceId::Analog(axis) => gamepad.axis(axis_map(index, axis)), - _ => 0, - } - } - None => 0, - } - } - - fn input_state_keyboard(&mut self, port: u32, device: &InputDeviceId, _index: &InputIndex) -> i16 { - if port != 0 { // Keyboard only controls the first port. - return 0; - } - - match device { - InputDeviceId::Joypad(button) => { - match keyboard_map(&button) { - Some(x) => return if self.pressed_keys.contains(&x) {1} else {0}, - None => 0 - } - }, - _ => 0 - } - } -} - -impl Drop for MyEmulator { - fn drop(&mut self) { - retro::wrapper::unset_handler(); - } -} - -impl retro::wrapper::LibretroWrapperAccess for MyEmulator { - fn libretro_core(&mut self) -> &mut LibretroWrapper { - &mut self.retro - } -} - -impl retro::wrapper::RetroCallbacks for MyEmulator { - fn video_refresh(&mut self, frame: &VideoFrame) { - match frame { - VideoFrame::XRGB1555 { width, height, .. } - | VideoFrame::RGB565 { width, height, .. } - | VideoFrame::XRGB8888 { width, height, .. } => { - let rect = Rect::new(0, 0, *width, *height); - let (pixel_data, pitch) = frame.data_pitch_as_bytes().unwrap(); - - if let Ok(mut tex) = - self.canvas - .texture_creator() - .create_texture_static(self.pixel_format, *width, *height) - { - if tex.update(rect, pixel_data, pitch).is_ok() { - self.canvas.clear(); - self.canvas.copy(&tex, None, None).unwrap(); - } - } - } - _ => {} - } - } - - fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize { - self.audio_buffer.extend(stereo_pcm); - self.send_audio_samples(); - stereo_pcm.len() / 2 - } - - fn input_poll(&mut self) { - self.gamepad_subsys.update(); - } - - fn input_state(&mut self, port: u32, device: InputDeviceId, index: InputIndex) -> i16 { - let gamepad_state = self.input_state_gamepad(port, &device, &index); - if gamepad_state != 0 { - return gamepad_state; - } - - return self.input_state_keyboard(port, &device, &index); - } - - fn get_system_directory(&mut self) -> Option { - self.sys_path.clone() - } - - fn set_pixel_format(&mut self, pix_fmt: retro::ffi::PixelFormat) -> Option { - self.pixel_format = match pix_fmt { - retro::ffi::PixelFormat::ARGB1555 => sdl2::pixels::PixelFormatEnum::RGB555, - retro::ffi::PixelFormat::ARGB8888 => sdl2::pixels::PixelFormatEnum::ARGB8888, - retro::ffi::PixelFormat::RGB565 => sdl2::pixels::PixelFormatEnum::RGB565, - }; - Some(true) - } - - fn get_variable(&mut self, key: &str) -> Option { - match key { - "beetle_saturn_analog_stick_deadzone" => Some("15%".to_string()), - "parallel-n64-gfxplugin" => Some("angrylion".to_string()), - "parallel-n64-astick-deadzone" => Some("15%".to_string()), - _ => None, - } - } - - fn get_libretro_path(&mut self) -> Option { - Some(self.core_path.clone()) - } - - fn get_input_device_capabilities(&mut self) -> Option { - let bits = (1 << (DeviceType::Joypad as u32)) | (1 << (DeviceType::Analog as u32)); - Some(bits as u64) - } - - fn get_save_directory(&mut self) -> Option { - Some(std::env::temp_dir()) - } - - fn set_system_av_info(&mut self, av_info: &SystemAvInfo) -> Option { - self.set_geometry(&av_info.geometry); - self.av_info = av_info.clone(); - Some(true) - } - - fn set_controller_info(&mut self, controller_info: &[ControllerDescription2]) -> Option { - for ci in controller_info { - // so we can have analog support in beetle/mednafen saturn - if ci.name.as_str() == "3D Control Pad" { - self.preferred_pad = Some(ci.device_id()); - break; - } - } - Some(true) - } - - fn set_geometry(&mut self, geom: &GameGeometry) -> Option { - let _ = self.canvas.window_mut().set_size(geom.base_width, geom.base_height); - let _ = self.canvas.set_logical_size(geom.base_width, geom.base_height); - self.av_info.geometry = geom.clone(); - Some(true) - } - - fn log_print(&mut self, level: retro::ffi::LogLevel, msg: &str) { - eprint!("[{:?}] {}", level, msg); - } -} - -struct MySdlAudio { - audio_spec: AudioSpec, - audio_receiver: crossbeam_channel::Receiver>, -} - -impl AudioCallback for MySdlAudio { - type Channel = i16; - - fn callback(&mut self, out: &mut [Self::Channel]) { - if self.audio_spec.format == AudioFormat::S16LSB { - if let Ok(samples) = self.audio_receiver.recv_timeout(Duration::from_millis(500)) { - out.copy_from_slice(&samples[..out.len()]); - } - } - } -} - -pub fn main() { - let opt: Opt = Opt::from_args(); - let mut emu = MyEmulator::new(&opt.core, &opt.system); - emu.load_game(&opt.rom); - emu.run_loop(); -} - -#[derive(StructOpt)] -struct Opt { - /// Core module to use. - #[structopt(short, long, parse(from_os_str))] - core: PathBuf, - /// ROM to load using the core. - #[structopt(short, long, parse(from_os_str))] - rom: PathBuf, - /// System directory, often containing BIOS files - #[structopt(short, long, parse(from_os_str))] - system: Option, -} - -fn button_map(retro_button: &JoypadButton) -> Option