From 013cc247b9f8f68571a674c932028864b675183c Mon Sep 17 00:00:00 2001 From: lifning <> Date: Thu, 19 Aug 2021 02:42:21 -0700 Subject: [PATCH] wip messing around with SDL_GL as a possible hw render provider to later mix in with the existing 2D codepaths --- ferretro_base/src/lib.rs | 2 +- ferretro_base/src/retro/constants.rs | 2 + ferretro_base/src/retro/wrapper.rs | 77 ++++++++----- .../examples/multifunction_emulator.rs | 4 +- ferretro_components/src/base/mod.rs | 63 ++++++++--- ferretro_components/src/lib.rs | 2 +- ferretro_components/src/provided/sdl2.rs | 2 + .../src/provided/sdl2/opengl.rs | 106 ++++++++++++++++++ 8 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 ferretro_components/src/provided/sdl2/opengl.rs diff --git a/ferretro_base/src/lib.rs b/ferretro_base/src/lib.rs index a4c6d02..c280b88 100644 --- a/ferretro_base/src/lib.rs +++ b/ferretro_base/src/lib.rs @@ -6,5 +6,5 @@ pub mod prelude { pub use crate::retro::constants::*; pub use crate::retro::wrapped_types::*; pub use crate::retro::wrapper::{RetroCallbacks, LibretroWrapper, LibretroWrapperAccess}; - pub use crate::retro::ffi::{PixelFormat, GameGeometry, SystemAvInfo, SystemInfo}; + pub use crate::retro::ffi::{PixelFormat, GameGeometry, HwContextResetFn, HwRenderCallback, SystemAvInfo, SystemInfo}; } diff --git a/ferretro_base/src/retro/constants.rs b/ferretro_base/src/retro/constants.rs index faa5252..0b44af3 100644 --- a/ferretro_base/src/retro/constants.rs +++ b/ferretro_base/src/retro/constants.rs @@ -152,5 +152,7 @@ pub enum EnvCmd { SetGeometry = ENVIRONMENT_SET_GEOMETRY, GetUsername = ENVIRONMENT_GET_USERNAME, GetLanguage = ENVIRONMENT_GET_LANGUAGE, + GetCurrentSoftwareFramebuffer = ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER, + GetHwRenderInterface = ENVIRONMENT_GET_HW_RENDER_INTERFACE, // SetSerializationQuirks = ENVIRONMENT_SET_SERIALIZATION_QUIRKS, } diff --git a/ferretro_base/src/retro/wrapper.rs b/ferretro_base/src/retro/wrapper.rs index 46a9d19..7920677 100644 --- a/ferretro_base/src/retro/wrapper.rs +++ b/ferretro_base/src/retro/wrapper.rs @@ -45,8 +45,10 @@ pub trait RetroCallbacks: Unpin + 'static { /// Certain graphic APIs, such as OpenGL ES, do not like textures /// that are not packed in memory. fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint) {} - /// Called instead of video_refresh when the core reports a duplicate frame (NULL). + /// Called instead of video_refresh when a core reports a duplicate frame (NULL). fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint) {} + /// Called instead of video_refresh when a core uses hardware rendering (HW_FRAMEBUFFER_VALID). + fn video_refresh_hw(&mut self, width: c_uint, height: c_uint) {} /// Renders a single audio frame. Should only be used if implementation /// generates a single sample at a time. /// Format is signed 16-bit native endian. @@ -342,28 +344,33 @@ pub trait RetroCallbacks: Unpin + 'static { fn set_rumble_state(&mut self, port: c_uint, effect: RumbleEffect, strength: u16) -> bool { false } /// Returns current time in microseconds. /// Tries to use the most accurate timer available. - fn perf_get_time_usec_cb(&mut self) -> Time { 0 } + fn perf_get_time_usec(&mut self) -> Time { 0 } /// A simple counter. Usually nanoseconds, but can also be CPU cycles. /// Can be used directly if desired (when creating a more sophisticated /// performance counter system). - fn perf_get_counter_cb(&mut self) -> PerfTick { 0 } + fn perf_get_counter(&mut self) -> PerfTick { 0 } /// Returns a bit-mask of detected CPU features ([libretro_sys]::SIMD_*). - fn perf_get_cpu_features_cb(&mut self) -> u64 { 0 } + fn perf_get_cpu_features(&mut self) -> u64 { 0 } /// Asks frontend to log and/or display the state of performance counters. /// Performance counters can always be poked into manually as well. - fn perf_log_cb(&mut self) {} + fn perf_log(&mut self) {} /// Register a performance counter. /// ident field must be set with a discrete value and other values in /// retro_perf_counter must be 0. /// Registering can be called multiple times. To avoid calling to /// frontend redundantly, you can check registered field first. - fn perf_register_cb(&mut self, counter: &mut PerfCounter) {} + fn perf_register(&mut self, counter: &mut PerfCounter) {} /// Starts a registered counter. - fn perf_start_cb(&mut self, counter: &mut PerfCounter) {} + fn perf_start(&mut self, counter: &mut PerfCounter) {} /// Stops a registered counter. - fn perf_stop_cb(&mut self, counter: &mut PerfCounter) {} + fn perf_stop(&mut self, counter: &mut PerfCounter) {} fn set_sensor_state(&mut self, port: c_uint, action: SensorAction, rate: c_uint) -> bool { false } fn get_sensor_input(&mut self, port: c_uint, id: c_uint) -> f32 { 0.0 } + /// Gets current framebuffer which is to be rendered to. + /// Could change every frame potentially. + fn hw_get_current_framebuffer(&mut self) -> Option { None } + /// Get a symbol from HW context. + fn hw_get_proc_address(&mut self, sym: &str) -> Option<*const ()> { None } } pub trait LibretroWrapperAccess { @@ -486,7 +493,7 @@ impl StaticCallbacks { .replace(hwr.context_destroy); hwr.get_current_framebuffer = Self::hw_get_current_framebuffer_fn; hwr.get_proc_address = Self::hw_get_proc_address_fn; - false // TODO: finish + handler.set_hw_render(hwr).unwrap_or(false) } EnvCmd::GetVariable => { let mut var = Self::from_void::(data)?; @@ -602,6 +609,8 @@ impl StaticCallbacks { } EnvCmd::GetUsername => Self::string_into_void(data, handler.get_username()?)?, EnvCmd::GetLanguage => Self::clone_into_void(data, &handler.get_language()?)?, + // TODO EnvCmd::GetCurrentSoftwareFramebuffer => {} + // TODO EnvCmd::GetHwRenderInterface => {} // TODO (not in libretro-sys) EnvCmd::SetSerializationQuirks => handler.set_serialization_quirks(Self::from_void(data)?), x => { if cfg!(debug) { @@ -623,13 +632,16 @@ impl StaticCallbacks { pitch: usize, ) { if let Some(cb) = unsafe { CB_SINGLETON.handler.as_mut() } { - if data.is_null() { - cb.video_refresh_dupe(width, height, pitch as c_uint); - } else if data != HW_FRAME_BUFFER_VALID { - let data = data as *const u8; - let len = pitch * (height as usize); - let slice = unsafe { from_raw_parts(data, len) }; - cb.video_refresh(slice, width, height, pitch as c_uint); + const NULL: *const c_void = std::ptr::null(); + match data { + NULL => cb.video_refresh_dupe(width, height, pitch as c_uint), + HW_FRAME_BUFFER_VALID => cb.video_refresh_hw(width, height), + data => { + let data = data as *const u8; + let len = pitch * (height as usize); + let slice = unsafe { from_raw_parts(data, len) }; + cb.video_refresh(slice, width, height, pitch as c_uint); + } } } } @@ -681,27 +693,27 @@ impl StaticCallbacks { } extern "C" fn perf_get_time_usec_cb() -> Time { unsafe { - CB_SINGLETON.handler.as_mut().map(|cb| cb.perf_get_time_usec_cb()) + CB_SINGLETON.handler.as_mut().map(|cb| cb.perf_get_time_usec()) }.unwrap_or_default() } extern "C" fn perf_get_counter_cb() -> PerfTick { unsafe { - CB_SINGLETON.handler.as_mut().map(|cb| cb.perf_get_counter_cb()) + CB_SINGLETON.handler.as_mut().map(|cb| cb.perf_get_counter()) }.unwrap_or_default() } extern "C" fn perf_get_cpu_features_cb() -> u64 { unsafe { - CB_SINGLETON.handler.as_mut().map(|cb| cb.perf_get_cpu_features_cb()) + CB_SINGLETON.handler.as_mut().map(|cb| cb.perf_get_cpu_features()) }.unwrap_or_default() } extern "C" fn perf_log_cb() { - unsafe { CB_SINGLETON.handler.as_mut().map(|cb| cb.perf_log_cb()); } + unsafe { CB_SINGLETON.handler.as_mut().map(|cb| cb.perf_log()); } } extern "C" fn perf_register_cb(counter: *mut PerfCounter) { unsafe { match (CB_SINGLETON.handler.as_mut(), counter.as_mut()) { (Some(cb), Some(counter)) => { - cb.perf_register_cb(counter); + cb.perf_register(counter); } _ => {} } @@ -711,7 +723,7 @@ impl StaticCallbacks { unsafe { match (CB_SINGLETON.handler.as_mut(), counter.as_mut()) { (Some(cb), Some(counter)) => { - cb.perf_start_cb(counter); + cb.perf_start(counter); } _ => {} } @@ -721,7 +733,7 @@ impl StaticCallbacks { unsafe { match (CB_SINGLETON.handler.as_mut(), counter.as_mut()) { (Some(cb), Some(counter)) => { - cb.perf_stop_cb(counter); + cb.perf_stop(counter); } _ => {} } @@ -739,14 +751,21 @@ impl StaticCallbacks { }.unwrap_or_default() } - // TODO: trait methods, etc. - extern "C" fn hw_dummy_fn() {} - extern "C" fn hw_get_proc_address_fn(_sym: *const c_char) -> ProcAddressFn { - Self::hw_dummy_fn // FIXME: obvious hack + extern "C" fn hw_get_proc_address_fn(sym: *const c_char) -> ProcAddressFn { + unsafe { + std::mem::transmute( + CB_SINGLETON.handler.as_mut() + .and_then(|cb| cb.hw_get_proc_address(CStr::from_ptr(sym).to_str().unwrap())) + .unwrap_or(std::ptr::null()) + ) + } } - // note: libretro.h claims this is obsolete + // note: libretro.h claims this is obsolete, but (at least) paraLLEl-n64 uses it extern "C" fn hw_get_current_framebuffer_fn() -> usize { - 0 + unsafe { + CB_SINGLETON.handler.as_mut() + .and_then(|cb| cb.hw_get_current_framebuffer()) + }.unwrap_or_default() } } diff --git a/ferretro_components/examples/multifunction_emulator.rs b/ferretro_components/examples/multifunction_emulator.rs index 4e252e3..e14375b 100644 --- a/ferretro_components/examples/multifunction_emulator.rs +++ b/ferretro_components/examples/multifunction_emulator.rs @@ -41,7 +41,8 @@ pub fn main() { let mut sdl_context = sdl2::init().unwrap(); - let sdl2_canvas = SimpleSdl2CanvasComponent::new(&mut sdl_context, emu.libretro_core()); + //let sdl2_canvas = SimpleSdl2CanvasComponent::new(&mut sdl_context, emu.libretro_core()); + let sdl2_canvas = SimpleSdl2OpenglComponent::new(&mut sdl_context, emu.libretro_core()).unwrap(); emu.register_component(sdl2_canvas); let sdl2_audio = SimpleSdl2AudioComponent::new(&mut sdl_context, emu.libretro_core()); @@ -67,6 +68,7 @@ pub fn main() { emu.register_component(ffmpeg_comp); } + emu.init().unwrap(); emu.load_game(&opt.rom).unwrap(); if let Some(state) = opt.state { emu.unserialize_path(state).unwrap(); diff --git a/ferretro_components/src/base/mod.rs b/ferretro_components/src/base/mod.rs index c2357a2..0587f6f 100644 --- a/ferretro_components/src/base/mod.rs +++ b/ferretro_components/src/base/mod.rs @@ -36,10 +36,12 @@ pub type Result = std::result::Result>; #[rustfmt::skip] #[allow(unused_variables)] pub trait RetroComponent: RetroCallbacks { - fn pre_run(&mut self, retro: &mut LibretroWrapper) -> ControlFlow { ControlFlow::Continue } - fn post_run(&mut self, retro: &mut LibretroWrapper) -> ControlFlow { ControlFlow::Continue } + fn pre_init(&mut self, retro: &mut LibretroWrapper) -> Result<()> { Ok(()) } + fn post_init(&mut self, retro: &mut LibretroWrapper) -> Result<()> { Ok(()) } fn pre_load_game(&mut self, retro: &mut LibretroWrapper, rom: &Path) -> Result<()> { Ok(()) } fn post_load_game(&mut self, retro: &mut LibretroWrapper, rom: &Path) -> Result<()> { Ok(()) } + fn pre_run(&mut self, retro: &mut LibretroWrapper) -> ControlFlow { ControlFlow::Continue } + fn post_run(&mut self, retro: &mut LibretroWrapper) -> ControlFlow { ControlFlow::Continue } } impl RetroComponentBase { @@ -68,10 +70,23 @@ impl RetroComponentBase { let mut pin_emu = Box::pin(emu); ferretro_base::retro::wrapper::set_handler(pin_emu.as_mut()); - pin_emu.retro.init(); pin_emu } + pub fn init(&mut self) -> Result<()> { + for comp in &mut self.components { + comp.pre_init(&mut self.retro)?; + } + + self.retro.init(); + + for comp in &mut self.components { + comp.post_init(&mut self.retro)?; + } + + Ok(()) + } + pub fn register_component(&mut self, comp: T) -> Option<()> // TODO: Result where T: RetroComponent { @@ -434,45 +449,45 @@ impl RetroCallbacks for RetroComponentBase { .fold(false, |x, y| x || y) // not "any" because we don't short-circuit } - fn perf_get_time_usec_cb(&mut self) -> Time { + fn perf_get_time_usec(&mut self) -> Time { self.components.first_mut() - .map(|comp| comp.perf_get_time_usec_cb()) + .map(|comp| comp.perf_get_time_usec()) .unwrap_or_default() } - fn perf_get_counter_cb(&mut self) -> PerfTick { + fn perf_get_counter(&mut self) -> PerfTick { self.components.first_mut() - .map(|comp| comp.perf_get_counter_cb()) + .map(|comp| comp.perf_get_counter()) .unwrap_or_default() } - fn perf_get_cpu_features_cb(&mut self) -> u64 { + fn perf_get_cpu_features(&mut self) -> u64 { self.components.first_mut() - .map(|comp| comp.perf_get_cpu_features_cb()) + .map(|comp| comp.perf_get_cpu_features()) .unwrap_or_default() } - fn perf_log_cb(&mut self) { + fn perf_log(&mut self) { if let Some(comp) = self.components.first_mut() { - comp.perf_log_cb() + comp.perf_log() } } - fn perf_register_cb(&mut self, counter: &mut PerfCounter) { + fn perf_register(&mut self, counter: &mut PerfCounter) { if let Some(comp) = self.components.first_mut() { - comp.perf_register_cb(counter) + comp.perf_register(counter) } } - fn perf_start_cb(&mut self, counter: &mut PerfCounter) { + fn perf_start(&mut self, counter: &mut PerfCounter) { if let Some(comp) = self.components.first_mut() { - comp.perf_start_cb(counter) + comp.perf_start(counter) } } - fn perf_stop_cb(&mut self, counter: &mut PerfCounter) { + fn perf_stop(&mut self, counter: &mut PerfCounter) { if let Some(comp) = self.components.first_mut() { - comp.perf_stop_cb(counter) + comp.perf_stop(counter) } } @@ -489,6 +504,20 @@ impl RetroCallbacks for RetroComponentBase { .last() .unwrap_or_default() } + + fn hw_get_current_framebuffer(&mut self) -> Option { + self.components.iter_mut() + .map(|comp| comp.hw_get_current_framebuffer()) + .flatten() + .next() + } + + fn hw_get_proc_address(&mut self, sym: &str) -> Option<*const ()> { + self.components.iter_mut() + .map(|comp| comp.hw_get_proc_address(sym)) + .flatten() + .next() + } } impl Drop for RetroComponentBase { diff --git a/ferretro_components/src/lib.rs b/ferretro_components/src/lib.rs index 4a14ba6..29c76cd 100644 --- a/ferretro_components/src/lib.rs +++ b/ferretro_components/src/lib.rs @@ -6,5 +6,5 @@ pub mod prelude { pub use ferretro_base::retro::constants::*; pub use ferretro_base::retro::wrapped_types::*; pub use ferretro_base::retro::wrapper::{RetroCallbacks, LibretroWrapper, LibretroWrapperAccess}; - pub use ferretro_base::retro::ffi::{PixelFormat, GameGeometry, SystemAvInfo, SystemInfo}; + pub use ferretro_base::retro::ffi::{PixelFormat, GameGeometry, HwContextResetFn, HwRenderCallback, SystemAvInfo, SystemInfo}; } diff --git a/ferretro_components/src/provided/sdl2.rs b/ferretro_components/src/provided/sdl2.rs index 1fb8cf1..c5e1c04 100644 --- a/ferretro_components/src/provided/sdl2.rs +++ b/ferretro_components/src/provided/sdl2.rs @@ -1,7 +1,9 @@ mod canvas; mod audio; mod gamepad; +mod opengl; pub use canvas::SimpleSdl2CanvasComponent; +pub use opengl::SimpleSdl2OpenglComponent; pub use audio::SimpleSdl2AudioComponent; pub use gamepad::SimpleSdl2GamepadComponent; diff --git a/ferretro_components/src/provided/sdl2/opengl.rs b/ferretro_components/src/provided/sdl2/opengl.rs new file mode 100644 index 0000000..ab381dd --- /dev/null +++ b/ferretro_components/src/provided/sdl2/opengl.rs @@ -0,0 +1,106 @@ +use crate::base::ControlFlow; +use crate::prelude::*; + +use std::ffi::CStr; +use std::os::raw::c_uint; + +use sdl2::Sdl; +use sdl2::video::{GLContext, Window}; + +pub struct SimpleSdl2OpenglComponent { + window: Window, + window_fbo: c_uint, + gl_context: GLContext, + pixel_format: sdl2::pixels::PixelFormatEnum, + hw_context_reset_fn: Option, + hw_context_destroy_fn: Option, +} + +impl SimpleSdl2OpenglComponent { + pub fn new(sdl_context: &mut Sdl, retro: &LibretroWrapper) -> Result> { + let sys_info = retro.get_system_info(); + let title = format!( + "{} - ferretro", + unsafe { CStr::from_ptr(sys_info.library_name) }.to_string_lossy() + ); + + let geometry = retro.get_system_av_info().geometry; + let pixel_format = sdl2::pixels::PixelFormatEnum::ARGB1555; + + let video = sdl_context.video()?; + let window = video + .window(title.as_str(), geometry.base_width, geometry.base_height) + .opengl() + .build()?; + + // http://forums.libsdl.org/viewtopic.php?p=43353 + // likely to remain `0` on any platform that isn't iOS, but we'll do it anyhow + let gl_context = window.gl_create_context()?; + let mut window_fbo: c_uint = 0; + unsafe { + const GL_FRAMEBUFFER_BINDING: c_uint = 0x8CA6; + #[allow(non_snake_case)] + let glGetIntegerv: unsafe extern "C" fn(c_uint, *mut c_uint) = std::mem::transmute( + video.gl_get_proc_address("glGetIntegerv") + ); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mut window_fbo); + } + + Ok(SimpleSdl2OpenglComponent { + window, + window_fbo, + gl_context, + pixel_format, + hw_context_reset_fn: None, + hw_context_destroy_fn: None, + }) + } +} + +impl RetroCallbacks for SimpleSdl2OpenglComponent { + 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 set_hw_render(&mut self, hw_render_callback: &HwRenderCallback) -> Option { + self.hw_context_reset_fn.replace(hw_render_callback.context_reset); + self.hw_context_destroy_fn.replace(hw_render_callback.context_destroy); + + self.hw_context_reset_fn.map(|f| unsafe { f() }); + + Some(true) + } + + /* + fn set_system_av_info(&mut self, av_info: &SystemAvInfo) -> Option { + self.set_geometry(&av_info.geometry); + 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); + Some(true) + } + */ + + fn hw_get_current_framebuffer(&mut self) -> Option { + Some(self.window_fbo as usize) + } + + fn hw_get_proc_address(&mut self, sym: &str) -> Option<*const ()> { + Some(self.window.subsystem().gl_get_proc_address(sym)) + } +} + +impl RetroComponent for SimpleSdl2OpenglComponent { + fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { + self.window.gl_swap_window(); + ControlFlow::Continue + } +}