diff --git a/ferretro_components/examples/multifunction_emulator.rs b/ferretro_components/examples/multifunction_emulator.rs index 811640e..4e252e3 100644 --- a/ferretro_components/examples/multifunction_emulator.rs +++ b/ferretro_components/examples/multifunction_emulator.rs @@ -10,11 +10,10 @@ use ferretro_components::prelude::*; use ferretro_components::provided::{ ffmpeg::FfmpegComponent, - sdl2::Sdl2Component, - stdlib::{PathBufComponent, StderrLogComponent}, + sdl2::*, + stdlib::{PathBufComponent, StderrLogComponent, SleepFramerateLimitComponent}, }; use ferretro_components::base::ControlFlow; -use ferretro_components::provided::stdlib::StderrSysInfoLogComponent; #[derive(StructOpt)] struct Opt { @@ -37,30 +36,46 @@ struct Opt { pub fn main() { let opt: Opt = Opt::from_args(); + let mut emu = RetroComponentBase::new(&opt.core); - let sdl2_comp = Sdl2Component::new(emu.libretro_core()); - emu.register_component(sdl2_comp); + + let mut sdl_context = sdl2::init().unwrap(); + + let sdl2_canvas = SimpleSdl2CanvasComponent::new(&mut sdl_context, emu.libretro_core()); + emu.register_component(sdl2_canvas); + + let sdl2_audio = SimpleSdl2AudioComponent::new(&mut sdl_context, emu.libretro_core()); + emu.register_component(sdl2_audio); + + emu.register_component(SimpleSdl2GamepadComponent::new(&mut sdl_context)); + + let sleep_fps = SleepFramerateLimitComponent::new(emu.libretro_core()); + emu.register_component(sleep_fps); + emu.register_component(StderrLogComponent::default()); - emu.register_component(StderrSysInfoLogComponent::default()); emu.register_component(PathBufComponent { sys_path: opt.system.clone(), libretro_path: Some(opt.core.to_path_buf()), core_assets_path: None, save_path: Some(std::env::temp_dir()), }); + if let Some(video) = opt.video { ffmpeg::log::set_level(ffmpeg::log::Level::Info); ffmpeg::init().unwrap(); let ffmpeg_comp = FfmpegComponent::new(emu.libretro_core(), video); emu.register_component(ffmpeg_comp); } + emu.load_game(&opt.rom).unwrap(); if let Some(state) = opt.state { emu.unserialize_path(state).unwrap(); } + let mut frame = 0; while let ControlFlow::Continue = emu.run() { frame += 1; } + eprintln!("Ran for {} frames.", frame); } diff --git a/ferretro_components/src/provided/ffmpeg.rs b/ferretro_components/src/provided/ffmpeg.rs index c682168..8eaebc4 100644 --- a/ferretro_components/src/provided/ffmpeg.rs +++ b/ferretro_components/src/provided/ffmpeg.rs @@ -429,9 +429,7 @@ impl RetroCallbacks for FfmpegComponent { 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, } } diff --git a/ferretro_components/src/provided/sdl2.rs b/ferretro_components/src/provided/sdl2.rs index 16b0a8a..1fb8cf1 100644 --- a/ferretro_components/src/provided/sdl2.rs +++ b/ferretro_components/src/provided/sdl2.rs @@ -1,323 +1,7 @@ -use std::error::Error; -use std::ffi::CStr; -use std::path::Path; -use std::time::{Duration, Instant}; +mod canvas; +mod audio; +mod gamepad; -use crate::prelude::*; -use crate::base::ControlFlow; - -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; - -// TODO: split up between video/audio/input! -pub struct Sdl2Component { - preferred_pad: Option, - - 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, - - // timing and events - frame_begin: Instant, - event_pump: sdl2::EventPump, -} - -impl RetroComponent for Sdl2Component { - fn pre_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { - self.frame_begin = Instant::now(); - - for event in self.event_pump.poll_iter() { - match event { - Event::Quit { .. } - | Event::KeyDown { - keycode: Some(Keycode::Escape), - .. - } => return ControlFlow::Break, - _ => {} - } - } - - ControlFlow::Continue - } - - fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { - // The rest of the game loop goes here... - 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(self.frame_begin.elapsed()) - .map(std::thread::sleep); - - - ControlFlow::Continue - } - - fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> Result<(), Box> { - if let Some(device) = self.preferred_pad { - for port in 0..self.gamepads.len() as u32 { - retro.set_controller_port_device(port, device); - } - } - self.audio_device.resume(); - Ok(()) - } -} - -impl Sdl2Component { - pub fn new(retro: &LibretroWrapper) -> Self { - let sys_info = retro.get_system_info(); - let title = format!( - "{} - ferretro", - 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) - .opengl() - .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 { - eprintln!("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 event_pump = sdl_context.event_pump().unwrap(); - - Sdl2Component { - preferred_pad: None, - av_info, - _sdl_context: sdl_context, - canvas, - pixel_format, - audio_buffer: Default::default(), - audio_spec: audio_spec.unwrap(), - audio_device, - audio_sender, - gamepad_subsys, - gamepads, - frame_begin: Instant::now(), - event_pump, - } - } - - 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); - } - } -} - -impl RetroCallbacks for Sdl2Component { - fn video_refresh(&mut self, data: &[u8], width: u32, height: u32, pitch: u32) { - let rect = Rect::new(0, 0, width, height); - - if let Ok(mut tex) = - self.canvas - .texture_creator() - .create_texture_static(self.pixel_format, width, height) - { - if tex.update(rect, data, pitch as usize).is_ok() { - self.canvas.clear(); - self.canvas.copy(&tex, None, None).unwrap(); - } - } - } - - fn audio_sample(&mut self, left: i16, right: i16) { - self.audio_buffer.push(left); - self.audio_buffer.push(right); - self.send_audio_samples() - } - - fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { - self.audio_buffer.extend(stereo_pcm); - self.send_audio_samples(); - stereo_pcm.len() - } - - fn input_poll(&mut self) { - self.gamepad_subsys.update(); - } - - fn input_state(&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 set_pixel_format(&mut self, pix_fmt: PixelFormat) -> Option { - self.pixel_format = match pix_fmt { - PixelFormat::ARGB1555 => sdl2::pixels::PixelFormatEnum::RGB555, - PixelFormat::ARGB8888 => sdl2::pixels::PixelFormatEnum::ARGB8888, - 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_input_device_capabilities(&mut self) -> Option { - let bits = (1 << (DeviceType::Joypad as u32)) | (1 << (DeviceType::Analog as u32)); - Some(bits as u64) - } - - 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: &Vec) -> 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) - } -} - -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()]); - } - } - } -} - -fn button_map(retro_button: &JoypadButton) -> Option