add checks for sequencing, fix audio sync in paraLLEl-n64
This commit is contained in:
		
							parent
							
								
									41e3352927
								
							
						
					
					
						commit
						e95e13df1e
					
				
					 5 changed files with 20 additions and 473 deletions
				
			
		| 
						 | 
				
			
			@ -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"]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<PathBuf>,
 | 
			
		||||
 | 
			
		||||
    preferred_pad: Option<u32>,
 | 
			
		||||
 | 
			
		||||
    sys_info: SystemInfo,
 | 
			
		||||
    av_info: SystemAvInfo,
 | 
			
		||||
 | 
			
		||||
    sdl_context: sdl2::Sdl,
 | 
			
		||||
 | 
			
		||||
    // video bits
 | 
			
		||||
    canvas: WindowCanvas,
 | 
			
		||||
    pixel_format: sdl2::pixels::PixelFormatEnum,
 | 
			
		||||
 | 
			
		||||
    // audio bits
 | 
			
		||||
    audio_buffer: Vec<i16>,
 | 
			
		||||
    audio_spec: AudioSpec,
 | 
			
		||||
    audio_device: AudioDevice<MySdlAudio>,
 | 
			
		||||
    audio_sender: crossbeam_channel::Sender<Vec<i16>>,
 | 
			
		||||
 | 
			
		||||
    // input bits
 | 
			
		||||
    gamepad_subsys: sdl2::GameControllerSubsystem,
 | 
			
		||||
    gamepads: Vec<GameController>,
 | 
			
		||||
    pressed_keys: Vec<Keycode>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MyEmulator {
 | 
			
		||||
    pub fn new(core_path: impl AsRef<Path>, sys_path: &Option<impl AsRef<Path>>) -> Pin<Box<Self>> {
 | 
			
		||||
        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<Path>) {
 | 
			
		||||
        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<Keycode> = 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<PathBuf> {
 | 
			
		||||
        self.sys_path.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_pixel_format(&mut self, pix_fmt: retro::ffi::PixelFormat) -> Option<bool> {
 | 
			
		||||
        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<String> {
 | 
			
		||||
        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<PathBuf> {
 | 
			
		||||
        Some(self.core_path.clone())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_input_device_capabilities(&mut self) -> Option<u64> {
 | 
			
		||||
        let bits = (1 << (DeviceType::Joypad as u32)) | (1 << (DeviceType::Analog as u32));
 | 
			
		||||
        Some(bits as u64)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_save_directory(&mut self) -> Option<PathBuf> {
 | 
			
		||||
        Some(std::env::temp_dir())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_system_av_info(&mut self, av_info: &SystemAvInfo) -> Option<bool> {
 | 
			
		||||
        self.set_geometry(&av_info.geometry);
 | 
			
		||||
        self.av_info = av_info.clone();
 | 
			
		||||
        Some(true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_controller_info(&mut self, controller_info: &[ControllerDescription2]) -> Option<bool> {
 | 
			
		||||
        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<bool> {
 | 
			
		||||
        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<Vec<i16>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<PathBuf>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn button_map(retro_button: &JoypadButton) -> Option<Button> {
 | 
			
		||||
    match retro_button {
 | 
			
		||||
        JoypadButton::B => Some(Button::A),
 | 
			
		||||
        JoypadButton::Y => Some(Button::X),
 | 
			
		||||
        JoypadButton::Select => Some(Button::Back),
 | 
			
		||||
        JoypadButton::Start => Some(Button::Start),
 | 
			
		||||
        JoypadButton::Up => Some(Button::DPadUp),
 | 
			
		||||
        JoypadButton::Down => Some(Button::DPadDown),
 | 
			
		||||
        JoypadButton::Left => Some(Button::DPadLeft),
 | 
			
		||||
        JoypadButton::Right => Some(Button::DPadRight),
 | 
			
		||||
        JoypadButton::A => Some(Button::B),
 | 
			
		||||
        JoypadButton::X => Some(Button::Y),
 | 
			
		||||
        JoypadButton::L => Some(Button::LeftShoulder),
 | 
			
		||||
        JoypadButton::R => Some(Button::RightShoulder),
 | 
			
		||||
         // SDL2 controller API doesn't have L2/R2 as buttons, they're considered axes
 | 
			
		||||
        JoypadButton::L2 => None,
 | 
			
		||||
        JoypadButton::R2 => None,
 | 
			
		||||
        JoypadButton::L3 => Some(Button::LeftStick),
 | 
			
		||||
        JoypadButton::R3 => Some(Button::RightStick),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn keyboard_map(retro_button: &JoypadButton) -> Option<Keycode> {
 | 
			
		||||
    match retro_button {
 | 
			
		||||
        JoypadButton::B => Some(Keycode::K),
 | 
			
		||||
        JoypadButton::Y => Some(Keycode::J),
 | 
			
		||||
        JoypadButton::Select => Some(Keycode::Num5),
 | 
			
		||||
        JoypadButton::Start => Some(Keycode::Num6),
 | 
			
		||||
        JoypadButton::Up => Some(Keycode::W),
 | 
			
		||||
        JoypadButton::Down => Some(Keycode::S),
 | 
			
		||||
        JoypadButton::Left => Some(Keycode::A),
 | 
			
		||||
        JoypadButton::Right => Some(Keycode::D),
 | 
			
		||||
        JoypadButton::A => Some(Keycode::L),
 | 
			
		||||
        JoypadButton::X => Some(Keycode::I),
 | 
			
		||||
        JoypadButton::L => Some(Keycode::Num1),
 | 
			
		||||
        JoypadButton::R => Some(Keycode::Num0),
 | 
			
		||||
        JoypadButton::L2 => None,
 | 
			
		||||
        JoypadButton::R2 => None,
 | 
			
		||||
        JoypadButton::L3 => None,
 | 
			
		||||
        JoypadButton::R3 => None,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn axis_map(index: &InputIndex, axis: &AnalogAxis) -> Axis {
 | 
			
		||||
    match (index, axis) {
 | 
			
		||||
        (InputIndex::Left, AnalogAxis::X) => Axis::LeftX,
 | 
			
		||||
        (InputIndex::Left, AnalogAxis::Y) => Axis::LeftY,
 | 
			
		||||
        (InputIndex::Right, AnalogAxis::X) => Axis::RightX,
 | 
			
		||||
        (InputIndex::Right, AnalogAxis::Y) => Axis::RightY,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,8 @@ pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
 | 
			
		|||
pub struct LibretroApi {
 | 
			
		||||
    _lib: libloading::Library, // for tying our lifetime to its own
 | 
			
		||||
    pub core_api: CoreAPI,
 | 
			
		||||
    loaded_game: bool,
 | 
			
		||||
    environment_set: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl LibretroApi {
 | 
			
		||||
| 
						 | 
				
			
			@ -44,11 +46,12 @@ impl LibretroApi {
 | 
			
		|||
                retro_get_memory_data: *lib.get(b"retro_get_memory_data")?,
 | 
			
		||||
                retro_get_memory_size: *lib.get(b"retro_get_memory_size")?,
 | 
			
		||||
            };
 | 
			
		||||
            Ok(LibretroApi { _lib: lib, core_api })
 | 
			
		||||
            Ok(LibretroApi { _lib: lib, core_api, loaded_game: false, environment_set: false })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /// set_environment() must be called before init().
 | 
			
		||||
    pub fn set_environment(&mut self, cb: EnvironmentFn) {
 | 
			
		||||
        self.environment_set = true;
 | 
			
		||||
        unsafe { (&self.core_api.retro_set_environment)(cb) }
 | 
			
		||||
    }
 | 
			
		||||
    /// set_video_refresh() must be called before run().
 | 
			
		||||
| 
						 | 
				
			
			@ -69,10 +72,11 @@ impl LibretroApi {
 | 
			
		|||
    }
 | 
			
		||||
    /// set_environment() must be called before init().
 | 
			
		||||
    pub fn init(&mut self) {
 | 
			
		||||
        // TODO assert!(called retro_set_environment);
 | 
			
		||||
        assert!(self.environment_set, "Must call set_environment(EnvironmentFn) before init()");
 | 
			
		||||
        unsafe { (&self.core_api.retro_init)() }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn deinit(&mut self) {
 | 
			
		||||
        assert!(!self.loaded_game, "Must call unload_game() before deinit()");
 | 
			
		||||
        unsafe { (&self.core_api.retro_deinit)() }
 | 
			
		||||
    }
 | 
			
		||||
    /// Must return RETRO_API_VERSION. Used to validate ABI compatibility when the API is revised.
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +97,7 @@ impl LibretroApi {
 | 
			
		|||
    /// E.g. geom.aspect_ratio might not be initialized if core doesn't
 | 
			
		||||
    /// desire a particular aspect ratio.
 | 
			
		||||
    pub fn get_system_av_info(&self) -> SystemAvInfo {
 | 
			
		||||
        assert!(self.loaded_game, "Must call load_game(..) before get_system_av_info()");
 | 
			
		||||
        unsafe {
 | 
			
		||||
            let mut av_info = ::std::mem::zeroed::<SystemAvInfo>();
 | 
			
		||||
            (&self.core_api.retro_get_system_av_info)(&mut av_info);
 | 
			
		||||
| 
						 | 
				
			
			@ -190,6 +195,7 @@ impl LibretroApi {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        if unsafe { (&self.core_api.retro_load_game)(&game) } {
 | 
			
		||||
            self.loaded_game = true;
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err("Failed to load game".into())
 | 
			
		||||
| 
						 | 
				
			
			@ -197,6 +203,7 @@ impl LibretroApi {
 | 
			
		|||
    }
 | 
			
		||||
    /// Unloads the currently loaded game. Called before deinit().
 | 
			
		||||
    pub fn unload_game(&mut self) {
 | 
			
		||||
        self.loaded_game = false;
 | 
			
		||||
        unsafe { (&self.core_api.retro_unload_game)() }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn get_region(&self) -> Region {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ use ferretro_components::prelude::*;
 | 
			
		|||
use ferretro_components::provided::{
 | 
			
		||||
    ffmpeg::FfmpegComponent,
 | 
			
		||||
    sdl2::*,
 | 
			
		||||
    stdlib::{PathBufComponent, StderrLogComponent, SleepFramerateLimitComponent},
 | 
			
		||||
    stdlib::*,
 | 
			
		||||
};
 | 
			
		||||
use ferretro_components::base::ControlFlow;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ pub struct SimpleSdl2AudioComponent {
 | 
			
		|||
    src_freq: f64,
 | 
			
		||||
    audio_buffer: Vec<i16>,
 | 
			
		||||
    queue: AudioQueue<i16>,
 | 
			
		||||
    started: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Work around an idiosyncrasy of the sdl2 crate's AudioCVT API.
 | 
			
		||||
| 
						 | 
				
			
			@ -38,8 +39,14 @@ pub(crate) fn resample<C: AudioFormatNum>(converter: &AudioCVT, mut samples: Vec
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
impl RetroCallbacks for SimpleSdl2AudioComponent {
 | 
			
		||||
    fn video_refresh(&mut self, _frame: &VideoFrame) {
 | 
			
		||||
        self.started = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize {
 | 
			
		||||
        if self.started {
 | 
			
		||||
            self.audio_buffer.extend(stereo_pcm);
 | 
			
		||||
        }
 | 
			
		||||
        stereo_pcm.len() / 2
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -57,6 +64,7 @@ impl RetroComponent for SimpleSdl2AudioComponent {
 | 
			
		|||
    fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> Result<(), Box<dyn Error>> {
 | 
			
		||||
        self.src_freq = retro.get_system_av_info().timing.sample_rate;
 | 
			
		||||
        self.queue.resume();
 | 
			
		||||
        self.started = false;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +100,7 @@ impl SimpleSdl2AudioComponent {
 | 
			
		|||
            src_freq,
 | 
			
		||||
            audio_buffer: Default::default(),
 | 
			
		||||
            queue,
 | 
			
		||||
            started: false,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue