ferretro/ferretro_components/src/provided/sdl2/opengl.rs

183 lines
6.3 KiB
Rust

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<HwContextResetFn>,
hw_context_destroy_fn: Option<HwContextResetFn>,
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<Self, Box<dyn std::error::Error + Send + Sync>> {
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<bool> {
Some(true)
}
// TODO: depth, stencil, cache_context, bottom_left_origin?
fn set_hw_render(&mut self, hw_render_callback: &HwRenderCallback) -> Option<bool> {
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<String> {
match key {
"parallel-n64-gfxplugin" => Some("glide64".to_string()),
_ => None,
}
}
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).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<usize> {
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();
}
}