Support 2D video_refresh in the SDL+GL component

...and alter the behavior of ThreadSleep component a bit.
This commit is contained in:
lifning 2021-08-21 20:31:26 -07:00
parent 0c5d0b5338
commit 71240de38c
4 changed files with 94 additions and 50 deletions

View File

@ -43,9 +43,8 @@ pub fn main() {
emu.register_component(StderrLogComponent { prefix: "{log} ".to_string() }); emu.register_component(StderrLogComponent { prefix: "{log} ".to_string() });
//let sdl2_canvas = SimpleSdl2CanvasComponent::new(&mut sdl_context, emu.libretro_core()); let sdl2_ogl = SimpleSdl2OpenglComponent::new(&mut sdl_context, emu.libretro_core()).unwrap();
let sdl2_canvas = SimpleSdl2OpenglComponent::new(&mut sdl_context, emu.libretro_core()).unwrap(); emu.register_component(sdl2_ogl);
emu.register_component(sdl2_canvas);
let sdl2_audio = SimpleSdl2AudioComponent::new(&mut sdl_context, emu.libretro_core()); let sdl2_audio = SimpleSdl2AudioComponent::new(&mut sdl_context, emu.libretro_core());
emu.register_component(sdl2_audio); emu.register_component(sdl2_audio);

View File

@ -1,4 +1,3 @@
use crate::base::ControlFlow;
use crate::prelude::*; use crate::prelude::*;
use std::ffi::CStr; use std::ffi::CStr;
@ -40,20 +39,21 @@ impl SimpleSdl2CanvasComponent {
} }
} }
impl RetroComponent for SimpleSdl2CanvasComponent {}
impl RetroCallbacks for SimpleSdl2CanvasComponent { impl RetroCallbacks for SimpleSdl2CanvasComponent {
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 Ok(mut tex) = if let Ok(mut tex) = self.canvas
self.canvas .texture_creator()
.texture_creator() .create_texture_static(self.pixel_format, width, height)
.create_texture_static(self.pixel_format, width, height)
{ {
if tex.update(rect, data, pitch as usize).is_ok() { if tex.update(rect, data, pitch as usize).is_ok() {
self.canvas.clear(); self.canvas.clear();
self.canvas.copy(&tex, None, None).unwrap(); self.canvas.copy(&tex, None, None).unwrap();
} }
} }
self.canvas.present();
} }
fn set_pixel_format(&mut self, pix_fmt: PixelFormat) -> Option<bool> { fn set_pixel_format(&mut self, pix_fmt: PixelFormat) -> Option<bool> {
@ -83,10 +83,3 @@ impl RetroCallbacks for SimpleSdl2CanvasComponent {
Some(true) Some(true)
} }
} }
impl RetroComponent for SimpleSdl2CanvasComponent {
fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
self.canvas.present();
ControlFlow::Continue
}
}

View File

@ -1,11 +1,11 @@
use crate::base::ControlFlow;
use crate::prelude::*; use crate::prelude::*;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::c_uint; use std::os::raw::c_uint;
use sdl2::Sdl; use sdl2::Sdl;
use sdl2::video::{GLContext, Window}; use sdl2::rect::Rect;
use sdl2::render::WindowCanvas;
// TODO: get these from somewhere better without pulling in too much of a mess for the sdl2 feature // TODO: get these from somewhere better without pulling in too much of a mess for the sdl2 feature
const GL_FRAMEBUFFER_BINDING: c_uint = 0x8CA6; const GL_FRAMEBUFFER_BINDING: c_uint = 0x8CA6;
@ -13,9 +13,8 @@ const GL_COLOR_BUFFER_BIT: c_uint = 0x00004000;
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub struct SimpleSdl2OpenglComponent { pub struct SimpleSdl2OpenglComponent {
window: Window, canvas: WindowCanvas,
window_fbo: c_uint, window_fbo: c_uint,
gl_context: GLContext,
pixel_format: sdl2::pixels::PixelFormatEnum, pixel_format: sdl2::pixels::PixelFormatEnum,
hw_context_reset_fn: Option<HwContextResetFn>, hw_context_reset_fn: Option<HwContextResetFn>,
hw_context_destroy_fn: Option<HwContextResetFn>, hw_context_destroy_fn: Option<HwContextResetFn>,
@ -49,15 +48,18 @@ impl SimpleSdl2OpenglComponent {
std::mem::transmute(video.gl_get_proc_address("glClear")) std::mem::transmute(video.gl_get_proc_address("glClear"))
}; };
let canvas = window.into_canvas()
.accelerated()
.target_texture()
.build()?;
// http://forums.libsdl.org/viewtopic.php?p=43353 // 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 // likely to remain `0` on any platform that isn't iOS, but we'll do it anyhow.
let gl_context = window.gl_create_context()?; // SDL_CreateRenderer, called by CanvasBuilder::build, creates a new GL Context.
let window_fbo = unsafe { let mut fbo = 0; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mut fbo); fbo }; let window_fbo = unsafe { let mut fbo = 0; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mut fbo); fbo };
Ok(SimpleSdl2OpenglComponent { Ok(SimpleSdl2OpenglComponent {
window, canvas,
window_fbo, window_fbo,
gl_context,
pixel_format, pixel_format,
hw_context_reset_fn: None, hw_context_reset_fn: None,
hw_context_destroy_fn: None, hw_context_destroy_fn: None,
@ -72,7 +74,6 @@ impl SimpleSdl2OpenglComponent {
#[allow(non_snake_case)] #[allow(non_snake_case)]
(self.glGetIntegerv)(GL_FRAMEBUFFER_BINDING, &mut fbo); (self.glGetIntegerv)(GL_FRAMEBUFFER_BINDING, &mut fbo);
} }
eprintln!("get_framebuffer_binding: {}", fbo);
fbo fbo
} }
@ -85,8 +86,25 @@ impl SimpleSdl2OpenglComponent {
} }
} }
impl RetroComponent for SimpleSdl2OpenglComponent {}
impl RetroCallbacks for SimpleSdl2OpenglComponent { impl RetroCallbacks for SimpleSdl2OpenglComponent {
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();
}
}
self.canvas.present();
}
fn video_refresh_hw(&mut self, _width: c_uint, _height: c_uint) { fn video_refresh_hw(&mut self, _width: c_uint, _height: c_uint) {
self.canvas.present();
unsafe { (self.glClear)(GL_COLOR_BUFFER_BIT); } unsafe { (self.glClear)(GL_COLOR_BUFFER_BIT); }
} }
@ -99,12 +117,11 @@ impl RetroCallbacks for SimpleSdl2OpenglComponent {
Some(true) Some(true)
} }
// TODO: depth, stencil, cache_context, bottom_left_origin?
fn set_hw_render(&mut self, hw_render_callback: &HwRenderCallback) -> Option<bool> { 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_reset_fn.replace(hw_render_callback.context_reset);
self.hw_context_destroy_fn.replace(hw_render_callback.context_destroy); self.hw_context_destroy_fn.replace(hw_render_callback.context_destroy);
self.call_context_reset(); self.call_context_reset();
Some(true) Some(true)
} }
@ -121,11 +138,29 @@ impl RetroCallbacks for SimpleSdl2OpenglComponent {
} }
fn set_geometry(&mut self, geom: &GameGeometry) -> Option<bool> { fn set_geometry(&mut self, geom: &GameGeometry) -> Option<bool> {
self.window.set_size(geom.base_width, geom.base_height).ok()?; let _ = self.canvas.window_mut().set_size(geom.base_width, geom.base_height);
self.call_context_destroy()?; let _ = self.canvas.set_logical_size(geom.base_width, geom.base_height);
self.gl_context = self.window.gl_create_context().ok()?;
self.window_fbo = self.current_framebuffer_binding(); self.window_fbo = self.current_framebuffer_binding();
/*
let mut old_canvas = unsafe { MaybeUninit::uninit().assume_init() };
std::mem::swap(&mut old_canvas, &mut self.canvas);
// no funny ? operators here because we've got uninitialized memory in self.canvas!
let mut window = old_canvas.into_window();
window.set_size(geom.base_width, geom.base_height).unwrap();
self.call_context_destroy().unwrap();
let mut new_canvas = window.into_canvas()
.accelerated()
.target_texture()
.build()
.unwrap();
self.window_fbo = self.current_framebuffer_binding();
std::mem::swap(&mut self.canvas, &mut new_canvas);
std::mem::forget(new_canvas);
self.call_context_reset()?; self.call_context_reset()?;
*/
Some(true) Some(true)
} }
@ -134,13 +169,12 @@ impl RetroCallbacks for SimpleSdl2OpenglComponent {
} }
fn hw_get_proc_address(&mut self, sym: &str) -> Option<*const ()> { fn hw_get_proc_address(&mut self, sym: &str) -> Option<*const ()> {
Some(self.window.subsystem().gl_get_proc_address(sym)) Some(self.canvas.window().subsystem().gl_get_proc_address(sym))
} }
} }
impl RetroComponent for SimpleSdl2OpenglComponent { impl Drop for SimpleSdl2OpenglComponent {
fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { fn drop(&mut self) {
self.window.gl_swap_window(); self.call_context_destroy();
ControlFlow::Continue
} }
} }

View File

@ -1,3 +1,4 @@
use std::os::raw::c_uint;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -5,8 +6,6 @@ use crate::base::ControlFlow;
use crate::prelude::*; use crate::prelude::*;
use ferretro_base::retro::ffi::{Message, Language}; use ferretro_base::retro::ffi::{Message, Language};
use std::os::raw::c_uint;
#[derive(Default)] #[derive(Default)]
pub struct PathBufComponent { pub struct PathBufComponent {
pub sys_path: Option<PathBuf>, pub sys_path: Option<PathBuf>,
@ -210,23 +209,49 @@ impl RetroCallbacks for StderrCallTraceComponent {
} }
pub struct SleepFramerateLimitComponent { pub struct SleepFramerateLimitComponent {
did_sleep: bool,
fps: f64, fps: f64,
frame_begin: Instant, frame_begin: Instant,
} }
impl RetroComponent for SleepFramerateLimitComponent {
fn pre_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
self.did_sleep = false;
ControlFlow::Continue
}
fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
if !self.did_sleep {
self.do_sleep();
}
ControlFlow::Continue
}
}
impl RetroCallbacks for SleepFramerateLimitComponent { impl RetroCallbacks for SleepFramerateLimitComponent {
fn video_refresh(&mut self, _data: &[u8], _width: c_uint, _height: c_uint, _pitch: c_uint) {
self.do_sleep();
}
fn video_refresh_dupe(&mut self, _width: c_uint, _height: c_uint, _pitch: c_uint) {
self.do_sleep();
}
fn video_refresh_hw(&mut self, _width: c_uint, _height: c_uint) {
self.do_sleep();
}
fn set_system_av_info(&mut self, system_av_info: &SystemAvInfo) -> Option<bool> { fn set_system_av_info(&mut self, system_av_info: &SystemAvInfo) -> Option<bool> {
self.fps = system_av_info.timing.fps; self.fps = system_av_info.timing.fps;
Some(true) Some(true)
} }
} }
impl RetroComponent for SleepFramerateLimitComponent { impl SleepFramerateLimitComponent {
fn pre_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { pub fn new(retro: &mut LibretroWrapper) -> Self {
self.frame_begin = Instant::now(); SleepFramerateLimitComponent {
ControlFlow::Continue did_sleep: false,
fps: retro.get_system_av_info().timing.fps,
frame_begin: Instant::now(),
}
} }
fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow {
fn do_sleep(&mut self) {
// similar hack to the sample rate, make sure we don't divide by zero. // similar hack to the sample rate, make sure we don't divide by zero.
let mut spf = 1.0 / self.fps; let mut spf = 1.0 / self.fps;
if spf.is_nan() || spf.is_infinite() { if spf.is_nan() || spf.is_infinite() {
@ -235,15 +260,8 @@ impl RetroComponent for SleepFramerateLimitComponent {
Duration::from_secs_f64(spf) Duration::from_secs_f64(spf)
.checked_sub(self.frame_begin.elapsed()) .checked_sub(self.frame_begin.elapsed())
.map(std::thread::sleep); .map(std::thread::sleep);
ControlFlow::Continue
}
}
impl SleepFramerateLimitComponent { self.did_sleep = true;
pub fn new(retro: &mut LibretroWrapper) -> Self { self.frame_begin = Instant::now();
SleepFramerateLimitComponent {
fps: retro.get_system_av_info().timing.fps,
frame_begin: Instant::now(),
}
} }
} }