audio support
This commit is contained in:
parent
e183627018
commit
043806c713
|
@ -10,10 +10,11 @@ failure = "^0.1"
|
||||||
libloading = "^0.5"
|
libloading = "^0.5"
|
||||||
num_enum = "^0.4"
|
num_enum = "^0.4"
|
||||||
structopt = "^0.3"
|
structopt = "^0.3"
|
||||||
sdl2 = { version = "*", optional = true, features = ["unsafe_textures"] }
|
sdl2 = { version = "^0.32", optional = true }
|
||||||
|
crossbeam-channel = { version = "^0.4", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
with-sdl2 = ["sdl2"]
|
with-sdl2 = ["sdl2", "crossbeam-channel"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "example"
|
name = "example"
|
||||||
|
|
|
@ -1,31 +1,41 @@
|
||||||
|
extern crate crossbeam_channel;
|
||||||
extern crate rustro;
|
extern crate rustro;
|
||||||
extern crate sdl2;
|
extern crate sdl2;
|
||||||
|
|
||||||
use rustro::retro;
|
use rustro::retro;
|
||||||
use rustro::retro::ffi::GameGeometry;
|
use rustro::retro::ffi::{GameGeometry, SystemAvInfo};
|
||||||
use rustro::retro::ffi::SystemAvInfo;
|
|
||||||
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::io::Write;
|
use std::io::Read;
|
||||||
use std::ops::Deref;
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::{PathBuf, Path};
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::thread::sleep;
|
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use sdl2::render::{WindowCanvas, Texture, TextureCreator};
|
use sdl2::audio::{AudioCallback, AudioFormat, AudioSpec, AudioSpecDesired, AudioDevice};
|
||||||
use sdl2::rect::Rect;
|
|
||||||
use sdl2::event::Event;
|
use sdl2::event::Event;
|
||||||
use sdl2::keyboard::Keycode;
|
use sdl2::keyboard::Keycode;
|
||||||
|
use sdl2::rect::Rect;
|
||||||
|
use sdl2::render::{TextureCreator, WindowCanvas};
|
||||||
use sdl2::video::WindowContext;
|
use sdl2::video::WindowContext;
|
||||||
|
|
||||||
struct MyEmulator {
|
struct MyEmulator {
|
||||||
retro: retro::wrapper::LibretroWrapper,
|
retro: retro::wrapper::LibretroWrapper,
|
||||||
av_info: SystemAvInfo,
|
av_info: SystemAvInfo,
|
||||||
|
|
||||||
|
sdl_context: sdl2::Sdl,
|
||||||
|
|
||||||
|
// video bits
|
||||||
|
canvas: WindowCanvas,
|
||||||
pixel_format: sdl2::pixels::PixelFormatEnum,
|
pixel_format: sdl2::pixels::PixelFormatEnum,
|
||||||
texture: Option<Texture>,
|
texture_creator: TextureCreator<WindowContext>,
|
||||||
|
|
||||||
|
// audio bits
|
||||||
|
audio_buffer: Vec<i16>,
|
||||||
|
audio_spec: AudioSpec,
|
||||||
|
audio_device: AudioDevice<MySdlAudio>,
|
||||||
|
audio_sender: crossbeam_channel::Sender<Vec<i16>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyEmulator {
|
impl MyEmulator {
|
||||||
|
@ -34,40 +44,143 @@ impl MyEmulator {
|
||||||
let raw_retro = retro::loading::LibretroApi::from_library(lib).unwrap();
|
let raw_retro = retro::loading::LibretroApi::from_library(lib).unwrap();
|
||||||
let retro = retro::wrapper::LibretroWrapper::from(raw_retro);
|
let retro = retro::wrapper::LibretroWrapper::from(raw_retro);
|
||||||
println!("api version: {}", retro.as_ref().api_version());
|
println!("api version: {}", retro.as_ref().api_version());
|
||||||
println!("name: {}", unsafe { CStr::from_ptr(retro.as_ref().get_system_info().library_name) }.to_string_lossy());
|
println!(
|
||||||
|
"name: {}",
|
||||||
|
unsafe { CStr::from_ptr(retro.as_ref().get_system_info().library_name) }
|
||||||
|
.to_string_lossy()
|
||||||
|
);
|
||||||
|
|
||||||
let av_info = retro.as_ref().get_system_av_info();
|
let av_info = retro.as_ref().get_system_av_info();
|
||||||
|
|
||||||
let pixel_format = sdl2::pixels::PixelFormatEnum::ABGR1555;
|
let pixel_format = sdl2::pixels::PixelFormatEnum::ABGR1555;
|
||||||
let mut emu = MyEmulator { retro, av_info, pixel_format, texture: None };
|
|
||||||
let mut pin_emu = Box::pin(emu);
|
|
||||||
|
|
||||||
|
let sdl_context = sdl2::init().unwrap();
|
||||||
|
|
||||||
|
let (width, height) = (av_info.geometry.base_width, av_info.geometry.base_height);
|
||||||
|
let window = sdl_context
|
||||||
|
.video()
|
||||||
|
.unwrap()
|
||||||
|
.window("rust libretro", width, height)
|
||||||
|
.opengl()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let canvas = window.into_canvas().build().unwrap();
|
||||||
|
let texture_creator = canvas.texture_creator();
|
||||||
|
|
||||||
|
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| {
|
||||||
|
println!("audio format {:?}", spec.format);
|
||||||
|
audio_spec = Some(spec.clone());
|
||||||
|
MySdlAudio {
|
||||||
|
audio_spec: spec,
|
||||||
|
audio_receiver,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let emu = MyEmulator {
|
||||||
|
retro,
|
||||||
|
av_info,
|
||||||
|
sdl_context,
|
||||||
|
canvas,
|
||||||
|
pixel_format,
|
||||||
|
texture_creator,
|
||||||
|
audio_buffer: Default::default(),
|
||||||
|
audio_spec: audio_spec.unwrap(),
|
||||||
|
audio_device,
|
||||||
|
audio_sender,
|
||||||
|
};
|
||||||
|
let mut pin_emu = Box::pin(emu);
|
||||||
retro::wrapper::register_handler(pin_emu.as_mut());
|
retro::wrapper::register_handler(pin_emu.as_mut());
|
||||||
pin_emu.retro.as_ref().init();
|
pin_emu.retro.as_ref().init();
|
||||||
pin_emu
|
pin_emu
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self) {
|
pub fn run(&mut self) {
|
||||||
self.retro.as_ref().run();
|
self.audio_device.resume();
|
||||||
}
|
let mut event_pump = self.sdl_context.event_pump().unwrap();
|
||||||
|
'running: loop {
|
||||||
|
let frame_begin = Instant::now();
|
||||||
|
|
||||||
pub fn base_dimensions(&self) -> (u32, u32) {
|
for event in event_pump.poll_iter() {
|
||||||
(self.av_info.geometry.base_width, self.av_info.geometry.base_height)
|
match event {
|
||||||
|
Event::Quit { .. }
|
||||||
|
| Event::KeyDown {
|
||||||
|
keycode: Some(Keycode::Escape),
|
||||||
|
..
|
||||||
|
} => break 'running,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest of the game loop goes here...
|
||||||
|
self.retro.as_ref().run();
|
||||||
|
self.canvas.present();
|
||||||
|
|
||||||
|
Duration::from_secs_f64(1.0 / self.av_info.timing.fps)
|
||||||
|
.checked_sub(frame_begin.elapsed())
|
||||||
|
.map(std::thread::sleep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_game(&self, rom: impl AsRef<Path>) {
|
pub fn load_game(&self, rom: impl AsRef<Path>) {
|
||||||
self.retro.as_ref().load_game(Some(rom.as_ref()), None, None);
|
let mut data = None;
|
||||||
|
let mut v = Vec::new();
|
||||||
|
if let Ok(mut f) = std::fs::File::open(rom.as_ref()) {
|
||||||
|
if f.read_to_end(&mut v).is_ok() {
|
||||||
|
data = Some(v.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.retro
|
||||||
|
.as_ref()
|
||||||
|
.load_game(Some(rom.as_ref()), data, None)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 retro::wrapper::Handler for MyEmulator {
|
||||||
impl retro::convert::Handler for MyEmulator {
|
|
||||||
fn video_refresh(&mut self, data: &[u8], width: u32, height: u32, pitch: u32) {
|
fn video_refresh(&mut self, data: &[u8], width: u32, height: u32, pitch: u32) {
|
||||||
let rect = Rect::new(0, 0, width, height);
|
let rect = Rect::new(0, 0, width, height);
|
||||||
if let Some(tex) = self.texture.as_mut() {
|
|
||||||
tex.update(rect, data, pitch as usize).unwrap();
|
if let Ok(mut tex) =
|
||||||
|
self.texture_creator
|
||||||
|
.create_texture_static(self.pixel_format, width, height)
|
||||||
|
{
|
||||||
|
if tex.update(rect, data, pitch as usize).is_ok() {
|
||||||
|
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 set_pixel_format(&mut self, pix_fmt: retro::ffi::PixelFormat) -> bool {
|
fn set_pixel_format(&mut self, pix_fmt: retro::ffi::PixelFormat) -> bool {
|
||||||
self.pixel_format = match pix_fmt {
|
self.pixel_format = match pix_fmt {
|
||||||
retro::ffi::PixelFormat::ARGB1555 => sdl2::pixels::PixelFormatEnum::RGB555,
|
retro::ffi::PixelFormat::ARGB1555 => sdl2::pixels::PixelFormatEnum::RGB555,
|
||||||
|
@ -76,12 +189,34 @@ impl retro::convert::Handler for MyEmulator {
|
||||||
};
|
};
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_system_av_info(&mut self, av_info: SystemAvInfo) -> bool {
|
||||||
|
self.av_info = av_info;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_geometry(&mut self, geom: GameGeometry) -> bool {
|
||||||
|
self.av_info.geometry = geom;
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for MyEmulator {
|
struct MySdlAudio {
|
||||||
fn drop(&mut self) {
|
audio_spec: AudioSpec,
|
||||||
if let Some(x) = self.texture.take() {
|
audio_receiver: crossbeam_channel::Receiver<Vec<i16>>,
|
||||||
unsafe { x.destroy(); }
|
}
|
||||||
|
|
||||||
|
impl AudioCallback for MySdlAudio {
|
||||||
|
type Channel = i16;
|
||||||
|
|
||||||
|
fn callback(&mut self, out: &mut [Self::Channel]) {
|
||||||
|
match self.audio_spec.format {
|
||||||
|
AudioFormat::S16LSB | AudioFormat::U16LSB => {
|
||||||
|
if let Ok(samples) = self.audio_receiver.recv() {
|
||||||
|
out.copy_from_slice(&samples[..out.len()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,43 +225,8 @@ pub fn main() -> failure::Fallible<()> {
|
||||||
let opt: Opt = Opt::from_args();
|
let opt: Opt = Opt::from_args();
|
||||||
let mut emu = MyEmulator::with_core(&opt.core);
|
let mut emu = MyEmulator::with_core(&opt.core);
|
||||||
emu.load_game(&opt.rom);
|
emu.load_game(&opt.rom);
|
||||||
|
emu.run();
|
||||||
|
|
||||||
let sdl_context = sdl2::init().map_err(failure::err_msg)?;
|
|
||||||
let video_subsystem = sdl_context.video().map_err(failure::err_msg)?;
|
|
||||||
|
|
||||||
let (width, height) = emu.base_dimensions();
|
|
||||||
let window = video_subsystem.window("rust libretro", width, height)
|
|
||||||
.opengl()
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let mut canvas = window.into_canvas().build()?;
|
|
||||||
let texture_creator = canvas.texture_creator();
|
|
||||||
emu.texture = texture_creator.create_texture_streaming(emu.pixel_format, width, height).ok();
|
|
||||||
|
|
||||||
let target_frame_time = Duration::from_secs_f64(1.0 / emu.av_info.timing.fps);
|
|
||||||
|
|
||||||
let mut event_pump = sdl_context.event_pump().map_err(failure::err_msg)?;
|
|
||||||
'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
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The rest of the game loop goes here...
|
|
||||||
emu.run();
|
|
||||||
canvas.clear();
|
|
||||||
canvas.copy(emu.texture.as_ref().unwrap(), None, Some(Rect::new(0, 0, width, height))).map_err(failure::err_msg)?;
|
|
||||||
canvas.present();
|
|
||||||
|
|
||||||
target_frame_time.checked_sub(frame_begin.elapsed()).map(sleep);
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,3 +239,4 @@ struct Opt {
|
||||||
#[structopt(short, long, parse(from_os_str))]
|
#[structopt(short, long, parse(from_os_str))]
|
||||||
rom: PathBuf,
|
rom: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::os::raw::c_uint;
|
use std::os::raw::c_uint;
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use num_enum::{TryFromPrimitive, IntoPrimitive};
|
use num_enum::{TryFromPrimitive, IntoPrimitive};
|
||||||
|
|
||||||
|
@ -188,52 +187,3 @@ pub enum EnvCmd {
|
||||||
GetLanguage = ENVIRONMENT_GET_LANGUAGE,
|
GetLanguage = ENVIRONMENT_GET_LANGUAGE,
|
||||||
// SetSerializationQuirks = ENVIRONMENT_SET_SERIALIZATION_QUIRKS,
|
// SetSerializationQuirks = ENVIRONMENT_SET_SERIALIZATION_QUIRKS,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Handler: Unpin + 'static {
|
|
||||||
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint) {}
|
|
||||||
fn audio_sample(&mut self, left: i16, right: i16) {}
|
|
||||||
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { stereo_pcm.len() }
|
|
||||||
fn input_poll(&mut self) {}
|
|
||||||
fn input_state(&mut self, port: u32, device: Input, index: DeviceIndex) -> i16 { 0 }
|
|
||||||
|
|
||||||
// -- environment callbacks --
|
|
||||||
fn set_rotation(&mut self, rotation: EnvRotation) -> bool { false }
|
|
||||||
fn get_overscan(&mut self) -> Option<bool> { None }
|
|
||||||
fn get_can_dupe(&mut self) -> Option<bool> { None }
|
|
||||||
fn set_message(&mut self, message: Message) -> bool { false }
|
|
||||||
fn shutdown(&mut self) -> bool { false }
|
|
||||||
fn set_performance_level(&mut self, level: c_uint) -> bool { false }
|
|
||||||
fn get_system_directory(&mut self) -> Option<PathBuf> { None }
|
|
||||||
fn set_pixel_format(&mut self, format: PixelFormat) -> bool { false }
|
|
||||||
fn set_input_descriptors(&mut self, input_descriptors: &[InputDescriptor]) -> bool { false }
|
|
||||||
fn set_keyboard_callback(&mut self, cb: KeyboardCallback) -> bool { false }
|
|
||||||
fn set_disk_control_interface(&mut self, cb: DiskControlCallback) -> bool { false }
|
|
||||||
fn set_hw_render(&mut self, hw_render_callback: HwRenderCallback) -> bool { false }
|
|
||||||
fn get_variable(&mut self) -> Option<EnvVariable> { None }
|
|
||||||
fn set_variables(&mut self, variables: &[EnvVariable]) -> bool { false }
|
|
||||||
fn get_variable_update(&mut self) -> Option<bool> { None }
|
|
||||||
fn set_support_no_game(&mut self, supports_no_game: bool) -> bool { false }
|
|
||||||
fn get_libretro_path(&mut self) -> Option<PathBuf> { None }
|
|
||||||
fn set_frame_time_callback(&mut self, cb: FrameTimeCallback) -> bool { false }
|
|
||||||
fn set_audio_callback(&mut self, audio_callback: AudioCallback) -> bool { false }
|
|
||||||
fn get_rumble_interface(&mut self) -> Option<RumbleInterface> { None }
|
|
||||||
fn get_input_device_capabilities(&mut self) -> Option<u64> { None }
|
|
||||||
fn get_sensor_interface(&mut self) -> Option<SensorInterface> { None }
|
|
||||||
fn get_camera_interface(&mut self) -> Option<CameraCallback> { None }
|
|
||||||
fn get_log_interface(&mut self) -> Option<LogCallback> { None }
|
|
||||||
fn get_perf_interface(&mut self) -> Option<PerfCallback> { None }
|
|
||||||
fn get_location_interface(&mut self) -> Option<LocationCallback> { None }
|
|
||||||
fn get_core_assets_directory(&mut self) -> Option<PathBuf> { None }
|
|
||||||
fn get_save_directory(&mut self) -> Option<PathBuf> { None }
|
|
||||||
fn set_system_av_info(&mut self, system_av_info: SystemAvInfo) -> bool { false }
|
|
||||||
fn set_proc_address_callback(&mut self, cb: GetProcAddressInterface) -> bool { false }
|
|
||||||
fn set_subsystem_info(&mut self, subsystem_info: SubsystemInfo) -> bool { false }
|
|
||||||
fn set_controller_info(&mut self, controller_info: ControllerInfo) -> bool { false }
|
|
||||||
fn set_memory_maps(&mut self, memory_map: MemoryMap) -> bool { false }
|
|
||||||
fn set_geometry(&mut self, game_geometry: GameGeometry) -> bool { false }
|
|
||||||
fn get_username(&mut self) -> Option<String> { None }
|
|
||||||
fn get_language(&mut self) -> Option<Language> { None }
|
|
||||||
// fn set_serialization_quirks(&mut self, quirks: &mut u64) -> bool { false }
|
|
||||||
}
|
|
||||||
|
|
||||||
// impl<T: Handler> Unpin for T {}
|
|
|
@ -7,7 +7,7 @@ use libloading;
|
||||||
use super::ffi::*;
|
use super::ffi::*;
|
||||||
|
|
||||||
pub struct LibretroApi {
|
pub struct LibretroApi {
|
||||||
lib: libloading::Library, // for tying our lifetime to its own
|
_lib: libloading::Library, // for tying our lifetime to its own
|
||||||
pub core_api: CoreAPI,
|
pub core_api: CoreAPI,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ impl LibretroApi {
|
||||||
retro_get_memory_data: *lib.get(b"retro_get_memory_data")?,
|
retro_get_memory_data: *lib.get(b"retro_get_memory_data")?,
|
||||||
retro_get_memory_size: *lib.get(b"retro_get_memory_size")?,
|
retro_get_memory_size: *lib.get(b"retro_get_memory_size")?,
|
||||||
};
|
};
|
||||||
Ok(LibretroApi { lib, core_api })
|
Ok(LibretroApi { _lib: lib, core_api })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// set_environment() must be called before init().
|
/// set_environment() must be called before init().
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod convert;
|
pub mod constants;
|
||||||
#[allow(non_camel_case_types, non_upper_case_globals, non_snake_case, dead_code)]
|
#[allow(non_camel_case_types, non_upper_case_globals, non_snake_case, dead_code)]
|
||||||
pub mod ffi;
|
pub mod ffi;
|
||||||
pub mod loading;
|
pub mod loading;
|
||||||
|
|
|
@ -4,20 +4,67 @@ use core::slice::from_raw_parts;
|
||||||
|
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::os::raw::{c_uint, c_char};
|
use std::os::raw::{c_uint, c_char};
|
||||||
use std::ops::DerefMut;
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::Path;
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use libretro_sys::{Message, PixelFormat, SystemAvInfo, GameGeometry};
|
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
use super::convert::*;
|
use super::constants::*;
|
||||||
|
use super::ffi::*;
|
||||||
use super::loading::*;
|
use super::loading::*;
|
||||||
|
|
||||||
static mut CB_SINGLETON: StaticCallbacks = StaticCallbacks {
|
static mut CB_SINGLETON: StaticCallbacks = StaticCallbacks {
|
||||||
handler: None,
|
handler: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub trait Handler: Unpin + 'static {
|
||||||
|
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint) {}
|
||||||
|
fn audio_sample(&mut self, left: i16, right: i16) {}
|
||||||
|
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { stereo_pcm.len() }
|
||||||
|
fn input_poll(&mut self) {}
|
||||||
|
fn input_state(&mut self, port: u32, device: Input, index: DeviceIndex) -> i16 { 0 }
|
||||||
|
|
||||||
|
// -- environment callbacks --
|
||||||
|
fn set_rotation(&mut self, rotation: EnvRotation) -> bool { false }
|
||||||
|
fn get_overscan(&mut self) -> Option<bool> { None }
|
||||||
|
fn get_can_dupe(&mut self) -> Option<bool> { None }
|
||||||
|
fn set_message(&mut self, message: Message) -> bool { false }
|
||||||
|
fn shutdown(&mut self) -> bool { false }
|
||||||
|
fn set_performance_level(&mut self, level: c_uint) -> bool { false }
|
||||||
|
fn get_system_directory(&mut self) -> Option<PathBuf> { None }
|
||||||
|
fn set_pixel_format(&mut self, format: PixelFormat) -> bool { false }
|
||||||
|
fn set_input_descriptors(&mut self, input_descriptors: &[InputDescriptor]) -> bool { false }
|
||||||
|
fn set_keyboard_callback(&mut self, cb: KeyboardCallback) -> bool { false }
|
||||||
|
fn set_disk_control_interface(&mut self, cb: DiskControlCallback) -> bool { false }
|
||||||
|
fn set_hw_render(&mut self, hw_render_callback: HwRenderCallback) -> bool { false }
|
||||||
|
fn get_variable(&mut self) -> Option<EnvVariable> { None }
|
||||||
|
fn set_variables(&mut self, variables: &[EnvVariable]) -> bool { false }
|
||||||
|
fn get_variable_update(&mut self) -> Option<bool> { None }
|
||||||
|
fn set_support_no_game(&mut self, supports_no_game: bool) -> bool { false }
|
||||||
|
fn get_libretro_path(&mut self) -> Option<PathBuf> { None }
|
||||||
|
fn set_frame_time_callback(&mut self, cb: FrameTimeCallback) -> bool { false }
|
||||||
|
fn set_audio_callback(&mut self, audio_callback: AudioCallback) -> bool { false }
|
||||||
|
fn get_rumble_interface(&mut self) -> Option<RumbleInterface> { None }
|
||||||
|
fn get_input_device_capabilities(&mut self) -> Option<u64> { None }
|
||||||
|
fn get_sensor_interface(&mut self) -> Option<SensorInterface> { None }
|
||||||
|
fn get_camera_interface(&mut self) -> Option<CameraCallback> { None }
|
||||||
|
fn get_log_interface(&mut self) -> Option<LogCallback> { None }
|
||||||
|
fn get_perf_interface(&mut self) -> Option<PerfCallback> { None }
|
||||||
|
fn get_location_interface(&mut self) -> Option<LocationCallback> { None }
|
||||||
|
fn get_core_assets_directory(&mut self) -> Option<PathBuf> { None }
|
||||||
|
fn get_save_directory(&mut self) -> Option<PathBuf> { None }
|
||||||
|
fn set_system_av_info(&mut self, system_av_info: SystemAvInfo) -> bool { false }
|
||||||
|
fn set_proc_address_callback(&mut self, cb: GetProcAddressInterface) -> bool { false }
|
||||||
|
fn set_subsystem_info(&mut self, subsystem_info: SubsystemInfo) -> bool { false }
|
||||||
|
fn set_controller_info(&mut self, controller_info: ControllerInfo) -> bool { false }
|
||||||
|
fn set_memory_maps(&mut self, memory_map: MemoryMap) -> bool { false }
|
||||||
|
fn set_geometry(&mut self, game_geometry: GameGeometry) -> bool { false }
|
||||||
|
fn get_username(&mut self) -> Option<String> { None }
|
||||||
|
fn get_language(&mut self) -> Option<Language> { None }
|
||||||
|
// fn set_serialization_quirks(&mut self, quirks: &mut u64) -> bool { false }
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct StaticCallbacks {
|
struct StaticCallbacks {
|
||||||
handler: Option<Pin<&'static mut dyn Handler>>,
|
handler: Option<Pin<&'static mut dyn Handler>>,
|
||||||
|
@ -48,7 +95,7 @@ impl StaticCallbacks {
|
||||||
T::try_from_primitive(number).ok()
|
T::try_from_primitive(number).ok()
|
||||||
}
|
}
|
||||||
fn environment_cb_inner(cmd: u32, data: *mut c_void) -> Option<bool> {
|
fn environment_cb_inner(cmd: u32, data: *mut c_void) -> Option<bool> {
|
||||||
let mut handler = unsafe { CB_SINGLETON.handler.as_mut() }?;
|
let handler = unsafe { CB_SINGLETON.handler.as_mut() }?;
|
||||||
match cmd.try_into().ok()? {
|
match cmd.try_into().ok()? {
|
||||||
EnvCmd::SetRotation => handler.set_rotation(Self::enum_from_void(data)?),
|
EnvCmd::SetRotation => handler.set_rotation(Self::enum_from_void(data)?),
|
||||||
EnvCmd::GetOverscan => Self::clone_into_void(data, &handler.get_overscan()?)?,
|
EnvCmd::GetOverscan => Self::clone_into_void(data, &handler.get_overscan()?)?,
|
||||||
|
@ -119,7 +166,11 @@ impl StaticCallbacks {
|
||||||
match CB_SINGLETON.handler.as_mut() {
|
match CB_SINGLETON.handler.as_mut() {
|
||||||
Some(cb) => match data.is_null() {
|
Some(cb) => match data.is_null() {
|
||||||
true => 0,
|
true => 0,
|
||||||
false => cb.audio_sample_batch(from_raw_parts(data, frames)),
|
false => {
|
||||||
|
let len = frames * 2; // stereo
|
||||||
|
let result = cb.audio_sample_batch(from_raw_parts(data, len));
|
||||||
|
result / 2
|
||||||
|
},
|
||||||
},
|
},
|
||||||
None => 0,
|
None => 0,
|
||||||
}
|
}
|
||||||
|
@ -176,7 +227,7 @@ impl Drop for LibretroWrapper {
|
||||||
// the library wrapper itself we're good (we wipe our 'static references on drop() too)
|
// the library wrapper itself we're good (we wipe our 'static references on drop() too)
|
||||||
pub fn register_handler(handler: Pin<&'_ mut (dyn Handler + '_)>) {
|
pub fn register_handler(handler: Pin<&'_ mut (dyn Handler + '_)>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut ptr = handler.get_unchecked_mut() as *mut dyn Handler;
|
let ptr = handler.get_unchecked_mut() as *mut dyn Handler;
|
||||||
CB_SINGLETON.handler.replace(Pin::new_unchecked(ptr.as_mut().unwrap()));
|
CB_SINGLETON.handler.replace(Pin::new_unchecked(ptr.as_mut().unwrap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue