From 71240de38c38c9c53003a1f559b8b5703ce85f8f Mon Sep 17 00:00:00 2001 From: lifning <> Date: Sat, 21 Aug 2021 20:31:26 -0700 Subject: [PATCH] Support 2D video_refresh in the SDL+GL component ...and alter the behavior of ThreadSleep component a bit. --- .../examples/multifunction_emulator.rs | 5 +- .../src/provided/sdl2/canvas.rs | 17 ++--- .../src/provided/sdl2/opengl.rs | 72 ++++++++++++++----- ferretro_components/src/provided/stdlib.rs | 50 ++++++++----- 4 files changed, 94 insertions(+), 50 deletions(-) diff --git a/ferretro_components/examples/multifunction_emulator.rs b/ferretro_components/examples/multifunction_emulator.rs index 46bdd2e..0114afd 100644 --- a/ferretro_components/examples/multifunction_emulator.rs +++ b/ferretro_components/examples/multifunction_emulator.rs @@ -43,9 +43,8 @@ pub fn main() { emu.register_component(StderrLogComponent { prefix: "{log} ".to_string() }); - //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_ogl = SimpleSdl2OpenglComponent::new(&mut sdl_context, emu.libretro_core()).unwrap(); + emu.register_component(sdl2_ogl); let sdl2_audio = SimpleSdl2AudioComponent::new(&mut sdl_context, emu.libretro_core()); emu.register_component(sdl2_audio); diff --git a/ferretro_components/src/provided/sdl2/canvas.rs b/ferretro_components/src/provided/sdl2/canvas.rs index f69e9d4..18ab497 100644 --- a/ferretro_components/src/provided/sdl2/canvas.rs +++ b/ferretro_components/src/provided/sdl2/canvas.rs @@ -1,4 +1,3 @@ -use crate::base::ControlFlow; use crate::prelude::*; use std::ffi::CStr; @@ -40,20 +39,21 @@ impl SimpleSdl2CanvasComponent { } } +impl RetroComponent for SimpleSdl2CanvasComponent {} impl RetroCallbacks for SimpleSdl2CanvasComponent { 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 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 set_pixel_format(&mut self, pix_fmt: PixelFormat) -> Option { @@ -83,10 +83,3 @@ impl RetroCallbacks for SimpleSdl2CanvasComponent { Some(true) } } - -impl RetroComponent for SimpleSdl2CanvasComponent { - fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { - self.canvas.present(); - ControlFlow::Continue - } -} diff --git a/ferretro_components/src/provided/sdl2/opengl.rs b/ferretro_components/src/provided/sdl2/opengl.rs index e288d7c..3790344 100644 --- a/ferretro_components/src/provided/sdl2/opengl.rs +++ b/ferretro_components/src/provided/sdl2/opengl.rs @@ -1,11 +1,11 @@ -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}; +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 const GL_FRAMEBUFFER_BINDING: c_uint = 0x8CA6; @@ -13,9 +13,8 @@ const GL_COLOR_BUFFER_BIT: c_uint = 0x00004000; #[allow(non_snake_case)] pub struct SimpleSdl2OpenglComponent { - window: Window, + canvas: WindowCanvas, window_fbo: c_uint, - gl_context: GLContext, pixel_format: sdl2::pixels::PixelFormatEnum, hw_context_reset_fn: Option, hw_context_destroy_fn: Option, @@ -49,15 +48,18 @@ impl SimpleSdl2OpenglComponent { 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 - // likely to remain `0` on any platform that isn't iOS, but we'll do it anyhow - let gl_context = window.gl_create_context()?; + // likely to remain `0` on any platform that isn't iOS, but we'll do it anyhow. + // 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 }; Ok(SimpleSdl2OpenglComponent { - window, + canvas, window_fbo, - gl_context, pixel_format, hw_context_reset_fn: None, hw_context_destroy_fn: None, @@ -72,7 +74,6 @@ impl SimpleSdl2OpenglComponent { #[allow(non_snake_case)] (self.glGetIntegerv)(GL_FRAMEBUFFER_BINDING, &mut fbo); } - eprintln!("get_framebuffer_binding: {}", fbo); fbo } @@ -85,8 +86,25 @@ impl SimpleSdl2OpenglComponent { } } +impl RetroComponent 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) { + self.canvas.present(); unsafe { (self.glClear)(GL_COLOR_BUFFER_BIT); } } @@ -99,12 +117,11 @@ impl RetroCallbacks for SimpleSdl2OpenglComponent { Some(true) } + // TODO: depth, stencil, cache_context, bottom_left_origin? 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.call_context_reset(); - Some(true) } @@ -121,11 +138,29 @@ impl RetroCallbacks for SimpleSdl2OpenglComponent { } fn set_geometry(&mut self, geom: &GameGeometry) -> Option { - self.window.set_size(geom.base_width, geom.base_height).ok()?; - self.call_context_destroy()?; - self.gl_context = self.window.gl_create_context().ok()?; + 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.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()?; + */ Some(true) } @@ -134,13 +169,12 @@ impl RetroCallbacks for SimpleSdl2OpenglComponent { } 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 { - fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { - self.window.gl_swap_window(); - ControlFlow::Continue +impl Drop for SimpleSdl2OpenglComponent { + fn drop(&mut self) { + self.call_context_destroy(); } } diff --git a/ferretro_components/src/provided/stdlib.rs b/ferretro_components/src/provided/stdlib.rs index 6d204b4..80920ac 100644 --- a/ferretro_components/src/provided/stdlib.rs +++ b/ferretro_components/src/provided/stdlib.rs @@ -1,3 +1,4 @@ +use std::os::raw::c_uint; use std::path::PathBuf; use std::time::{Duration, Instant}; @@ -5,8 +6,6 @@ use crate::base::ControlFlow; use crate::prelude::*; use ferretro_base::retro::ffi::{Message, Language}; -use std::os::raw::c_uint; - #[derive(Default)] pub struct PathBufComponent { pub sys_path: Option, @@ -210,23 +209,49 @@ impl RetroCallbacks for StderrCallTraceComponent { } pub struct SleepFramerateLimitComponent { + did_sleep: bool, fps: f64, 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 { + 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 { self.fps = system_av_info.timing.fps; Some(true) } } -impl RetroComponent for SleepFramerateLimitComponent { - fn pre_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { - self.frame_begin = Instant::now(); - ControlFlow::Continue +impl SleepFramerateLimitComponent { + pub fn new(retro: &mut LibretroWrapper) -> Self { + SleepFramerateLimitComponent { + 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. let mut spf = 1.0 / self.fps; if spf.is_nan() || spf.is_infinite() { @@ -235,15 +260,8 @@ impl RetroComponent for SleepFramerateLimitComponent { Duration::from_secs_f64(spf) .checked_sub(self.frame_begin.elapsed()) .map(std::thread::sleep); - ControlFlow::Continue - } -} -impl SleepFramerateLimitComponent { - pub fn new(retro: &mut LibretroWrapper) -> Self { - SleepFramerateLimitComponent { - fps: retro.get_system_av_info().timing.fps, - frame_begin: Instant::now(), - } + self.did_sleep = true; + self.frame_begin = Instant::now(); } }