wip messing around with SDL_GL as a possible hw render provider to later mix in with the existing 2D codepaths

This commit is contained in:
lifning 2021-08-19 02:42:21 -07:00
parent e1c0670feb
commit 013cc247b9
8 changed files with 209 additions and 49 deletions

View File

@ -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};
}

View File

@ -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,
}

View File

@ -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<usize> { 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::<Variable>(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()
}
}

View File

@ -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();

View File

@ -36,10 +36,12 @@ pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[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<T>(&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<usize> {
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 {

View File

@ -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};
}

View File

@ -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;

View File

@ -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<HwContextResetFn>,
hw_context_destroy_fn: Option<HwContextResetFn>,
}
impl SimpleSdl2OpenglComponent {
pub fn new(sdl_context: &mut Sdl, retro: &LibretroWrapper) -> Result<Self, Box<dyn std::error::Error>> {
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<bool> {
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<bool> {
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<bool> {
self.set_geometry(&av_info.geometry);
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);
Some(true)
}
*/
fn hw_get_current_framebuffer(&mut self) -> Option<usize> {
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
}
}