use crate::prelude::*; use std::os::raw::c_uint; use std::path::Path; use sdl2::Sdl; use sdl2::rect::Rect; use sdl2::render::WindowCanvas; use sdl2::video::FullscreenType; use super::canvas::paint_frame_on_canvas; /// Create a root window in SDL2 with an OpenGL context and attaches libretro's /// `hw_get_proc_address` calls to that of the [sdl2::VideoSubsystem]. /// /// If the core provides 2D framebuffer data to the component, it will simply display it with a /// (GL-accelerated) [sdl2::render::Texture], just as /// [SimpleSdl2CanvasComponent](super::canvas::SimpleSdl2CanvasComponent) does. /// /// This component has no public interface and manages the SDL2 window on its own. #[allow(non_snake_case)] pub struct SimpleSdl2OpenglComponent { canvas: WindowCanvas, win_size: (u32, u32), window_fbo: c_uint, hw_context_reset_fn: Option, hw_context_destroy_fn: Option, glGetIntegerv: unsafe extern "C" fn(c_uint, *mut c_uint), glClear: unsafe extern "C" fn(c_uint), } impl SimpleSdl2OpenglComponent { pub fn new(sdl_context: &mut Sdl, ft: FullscreenType) -> Result> { let video = sdl_context.video()?; // default to old libsnes 256x224 window size until load_game (prereq of get_system_av_info) let mut window = video .window("ferretro SDL GL", 256, 224) .opengl() .resizable() .hidden() .build()?; window.set_fullscreen(ft)?; #[allow(non_snake_case)] let glGetIntegerv: unsafe extern "C" fn(c_uint, *mut c_uint) = unsafe { std::mem::transmute(video.gl_get_proc_address("glGetIntegerv")) }; #[allow(non_snake_case)] let glClear: unsafe extern "C" fn(c_uint) = unsafe { 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. // 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 win_size = canvas.output_size().unwrap(); Ok(SimpleSdl2OpenglComponent { canvas, win_size, window_fbo, hw_context_reset_fn: None, hw_context_destroy_fn: None, glGetIntegerv, glClear, }) } fn current_framebuffer_binding(&self) -> c_uint { let mut fbo: c_uint = 0; unsafe { #[allow(non_snake_case)] (self.glGetIntegerv)(gl::FRAMEBUFFER_BINDING, &mut fbo); } fbo } fn call_context_reset(&self) -> Option<()> { self.hw_context_reset_fn.map(|f| unsafe { f(); }) } fn call_context_destroy(&self) -> Option<()> { self.hw_context_destroy_fn.map(|f| unsafe { f(); }) } } impl RetroComponent for SimpleSdl2OpenglComponent { fn post_load_game(&mut self, retro: &mut LibretroWrapper, rom: &Path) -> crate::base::Result<()> { let sys_info = retro.get_system_info(); let title = format!( "{} - {} - ferretro SDL GL", sys_info.library_name, rom.file_stem().unwrap_or_default().to_string_lossy(), ); self.canvas.window_mut().set_title(&title)?; self.set_geometry(&retro.get_system_av_info().geometry); Ok(()) } } impl RetroCallbacks for SimpleSdl2OpenglComponent { fn video_refresh(&mut self, frame: &VideoFrame) { self.canvas.window_mut().show(); let output_size = self.canvas.output_size().unwrap(); match frame { VideoFrame::HardwareRender { .. } => { if output_size != self.win_size { let (w, h) = output_size; // FIXME: this isn't enough - need core to render to texture that we just scale self.canvas.set_viewport(Rect::new(0, 0, w, h)); self.win_size = output_size; } self.canvas.present(); unsafe { (self.glClear)(gl::COLOR_BUFFER_BIT); } } VideoFrame::Duplicate { .. } => {} _ => { if output_size != self.win_size { self.canvas.clear(); self.win_size = output_size; } paint_frame_on_canvas(frame, &mut self.canvas); }, } } fn set_pixel_format(&mut self, _pix_fmt: PixelFormat) -> Option { Some(true) } // TODO: depth, stencil, cache_context, bottom_left_origin? fn set_hw_render(&mut self, hw_render_callback: &HwRenderCallback) -> Option { self.canvas.window().subsystem().gl_attr().set_context_version( hw_render_callback.version_major as u8, hw_render_callback.version_minor as u8, ); 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) } fn get_variable(&mut self, key: &str) -> Option { match key { "parallel-n64-gfxplugin" => Some("glide64".to_string()), _ => None, } } 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).ok()?; let _ = self.canvas.set_logical_size(geom.base_width, geom.base_height).ok()?; self.window_fbo = self.current_framebuffer_binding(); 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.canvas.window().subsystem().gl_get_proc_address(sym)) } } impl Drop for SimpleSdl2OpenglComponent { fn drop(&mut self) { self.call_context_destroy(); } }