Merge pull request 'liffy/sdl-gl' (#16) from liffy/sdl-gl into matriarch
Reviewed-on: https://git.vvn.space/cinnabon/rustro/pulls/16
This commit is contained in:
commit
409bd03621
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ libloading = "0.5"
|
|||
num_enum = "0.4"
|
||||
ffmpeg-next = { version = "4.3.8", optional = true }
|
||||
sdl2 = { version = "0.32", optional = true }
|
||||
gl = { version = "0.14", optional = true }
|
||||
crossbeam-channel = { version = "0.4", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -21,4 +22,4 @@ structopt = "0.3"
|
|||
|
||||
[features]
|
||||
ffmpeg_comp = ["ffmpeg-next"]
|
||||
sdl2_comp = ["sdl2", "crossbeam-channel"]
|
||||
sdl2_comp = ["sdl2", "gl", "crossbeam-channel"]
|
||||
|
|
|
@ -41,8 +41,10 @@ pub fn main() {
|
|||
|
||||
let mut sdl_context = sdl2::init().unwrap();
|
||||
|
||||
let sdl2_canvas = SimpleSdl2CanvasComponent::new(&mut sdl_context, emu.libretro_core());
|
||||
emu.register_component(sdl2_canvas);
|
||||
emu.register_component(StderrLogComponent { prefix: "{log} ".to_string() });
|
||||
|
||||
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);
|
||||
|
@ -52,7 +54,6 @@ pub fn main() {
|
|||
let sleep_fps = SleepFramerateLimitComponent::new(emu.libretro_core());
|
||||
emu.register_component(sleep_fps);
|
||||
|
||||
emu.register_component(StderrLogComponent::default());
|
||||
emu.register_component(PathBufComponent {
|
||||
sys_path: opt.system.clone(),
|
||||
libretro_path: Some(opt.core.to_path_buf()),
|
||||
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -195,6 +210,12 @@ impl RetroCallbacks for RetroComponentBase {
|
|||
}
|
||||
}
|
||||
|
||||
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint) {
|
||||
for comp in &mut self.components {
|
||||
comp.video_refresh_hw(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
fn audio_sample(&mut self, left: i16, right: i16) {
|
||||
for comp in &mut self.components {
|
||||
comp.audio_sample(left, right);
|
||||
|
@ -434,45 +455,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 +510,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 {
|
||||
|
|
|
@ -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, MemoryMap};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::base::ControlFlow;
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
@ -13,10 +12,10 @@ pub struct SimpleSdl2CanvasComponent {
|
|||
}
|
||||
|
||||
impl SimpleSdl2CanvasComponent {
|
||||
pub fn new(sdl_context: &mut Sdl, retro: &LibretroWrapper) -> Self {
|
||||
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",
|
||||
"{} - ferretro SDL",
|
||||
unsafe { CStr::from_ptr(sys_info.library_name) }.to_string_lossy()
|
||||
);
|
||||
|
||||
|
@ -24,36 +23,34 @@ impl SimpleSdl2CanvasComponent {
|
|||
let pixel_format = sdl2::pixels::PixelFormatEnum::ARGB1555;
|
||||
|
||||
let window = sdl_context
|
||||
.video()
|
||||
.unwrap()
|
||||
.video()?
|
||||
.window(title.as_str(), geometry.base_width, geometry.base_height)
|
||||
.opengl()
|
||||
.build()
|
||||
.unwrap();
|
||||
.build()?;
|
||||
|
||||
let canvas = window.into_canvas().build().unwrap();
|
||||
let canvas = window.into_canvas().build()?;
|
||||
|
||||
SimpleSdl2CanvasComponent {
|
||||
Ok(SimpleSdl2CanvasComponent {
|
||||
canvas,
|
||||
pixel_format,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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<bool> {
|
||||
|
@ -83,10 +80,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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_uint;
|
||||
|
||||
use sdl2::Sdl;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::WindowCanvas;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub struct SimpleSdl2OpenglComponent {
|
||||
canvas: WindowCanvas,
|
||||
window_fbo: c_uint,
|
||||
pixel_format: sdl2::pixels::PixelFormatEnum,
|
||||
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, retro: &LibretroWrapper) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let sys_info = retro.get_system_info();
|
||||
let title = format!(
|
||||
"{} - ferretro SDL GL",
|
||||
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()?;
|
||||
|
||||
#[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 };
|
||||
|
||||
Ok(SimpleSdl2OpenglComponent {
|
||||
canvas,
|
||||
window_fbo,
|
||||
pixel_format,
|
||||
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 {}
|
||||
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); }
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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> {
|
||||
eprintln!("set_geometry({:?})", geom);
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
use std::os::raw::c_uint;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::base::ControlFlow;
|
||||
use crate::prelude::*;
|
||||
use ferretro_base::retro::ffi::{Message, Language};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PathBufComponent {
|
||||
|
@ -76,30 +78,180 @@ pub struct StderrCallTraceComponent {
|
|||
|
||||
impl RetroComponent for StderrCallTraceComponent {}
|
||||
impl RetroCallbacks for StderrCallTraceComponent {
|
||||
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint) {
|
||||
eprintln!("{}video_refresh([u8; {}], {}, {}, {})", self.prefix, data.len(), width, height, pitch);
|
||||
}
|
||||
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint) {
|
||||
eprintln!("{}video_refresh_dupe({}, {}, {})", self.prefix, width, height, pitch);
|
||||
}
|
||||
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint) {
|
||||
eprintln!("{}video_refresh_hw({}, {})", self.prefix, width, height);
|
||||
}
|
||||
fn audio_sample(&mut self, left: i16, right: i16) {
|
||||
eprintln!("{}audio_sample({}, {})", self.prefix, left, right);
|
||||
}
|
||||
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize {
|
||||
eprintln!("{}audio_sample_batch([i16; {}])", self.prefix, stereo_pcm.len());
|
||||
0
|
||||
}
|
||||
fn input_poll(&mut self) {
|
||||
eprintln!("{}input_poll()", self.prefix);
|
||||
}
|
||||
fn input_state(&mut self, port: u32, device: InputDeviceId, index: InputIndex) -> i16 {
|
||||
eprintln!("{}input_state({}, {:?}, {:?})", self.prefix, port, device, index);
|
||||
0
|
||||
}
|
||||
fn set_rotation(&mut self, rotation: EnvRotation) -> Option<bool> {
|
||||
eprintln!("{}set_rotation({:?})", self.prefix, rotation);
|
||||
None
|
||||
}
|
||||
fn get_overscan(&mut self) -> Option<bool> {
|
||||
eprintln!("{}get_overscan()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn set_message(&mut self, message: &Message) -> Option<bool> {
|
||||
eprintln!("{}set_message({:?})", self.prefix, message);
|
||||
None
|
||||
}
|
||||
fn shutdown(&mut self) -> Option<bool> {
|
||||
eprintln!("{}shutdown()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn set_performance_level(&mut self, level: c_uint) -> Option<bool> {
|
||||
eprintln!("{}set_performance_level({})", self.prefix, level);
|
||||
None
|
||||
}
|
||||
fn get_system_directory(&mut self) -> Option<PathBuf> {
|
||||
eprintln!("{}get_system_directory()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn set_pixel_format(&mut self, format: PixelFormat) -> Option<bool> {
|
||||
eprintln!("{}set_pixel_format({:?})", self.prefix, format);
|
||||
None
|
||||
}
|
||||
fn set_input_descriptors(&mut self, input_descriptors: &Vec<InputDescriptor2>) -> Option<bool> {
|
||||
eprintln!("{}set_input_descriptors(vec![InputDescriptor2; {}])", self.prefix, input_descriptors.len());
|
||||
None
|
||||
}
|
||||
fn set_hw_render(&mut self, hw_render_callback: &HwRenderCallback) -> Option<bool> {
|
||||
eprintln!("{}set_hw_render({:?})", self.prefix, hw_render_callback);
|
||||
None
|
||||
}
|
||||
fn get_variable(&mut self, key: &str) -> Option<String> {
|
||||
eprintln!("{}get_variable({:?})", self.prefix, key);
|
||||
None
|
||||
}
|
||||
fn set_variables(&mut self, variables: &Vec<Variable2>) -> Option<bool> {
|
||||
eprintln!("{}set_variables(vec![Variable2; {}])", self.prefix, variables.len());
|
||||
None
|
||||
}
|
||||
fn get_variable_update(&mut self) -> Option<bool> {
|
||||
eprintln!("{}get_variable_update()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn set_support_no_game(&mut self, supports_no_game: bool) -> Option<bool> {
|
||||
eprintln!("{}set_support_no_game({})", self.prefix, supports_no_game);
|
||||
None
|
||||
}
|
||||
fn get_libretro_path(&mut self) -> Option<PathBuf> {
|
||||
eprintln!("{}get_libretro_path()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn get_input_device_capabilities(&mut self) -> Option<u64> {
|
||||
eprintln!("{}get_input_device_capabilities()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn get_core_assets_directory(&mut self) -> Option<PathBuf> {
|
||||
eprintln!("{}get_core_assets_directory()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn get_save_directory(&mut self) -> Option<PathBuf> {
|
||||
eprintln!("{}get_save_directory()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn set_system_av_info(&mut self, system_av_info: &SystemAvInfo) -> Option<bool> {
|
||||
eprintln!("{}set_system_av_info({:?})", self.prefix, system_av_info);
|
||||
None
|
||||
}
|
||||
fn set_subsystem_info(&mut self, subsystem_info: &Vec<SubsystemInfo2>) -> Option<bool> {
|
||||
eprintln!("{}set_subsystem_info(vec![SubsystemInfo2; {}])", self.prefix, subsystem_info.len());
|
||||
None
|
||||
}
|
||||
fn set_controller_info(&mut self, controller_info: &Vec<ControllerDescription2>) -> Option<bool> {
|
||||
eprintln!("{}set_controller_info(vec![ControllerDescription2; {}])", self.prefix, controller_info.len());
|
||||
None
|
||||
}
|
||||
fn set_memory_maps(&mut self, memory_map: &MemoryMap) -> Option<bool> {
|
||||
eprintln!("{}set_memory_maps({:?})", self.prefix, memory_map);
|
||||
None
|
||||
}
|
||||
fn set_geometry(&mut self, game_geometry: &GameGeometry) -> Option<bool> {
|
||||
eprintln!("{}set_geometry({:?})", self.prefix, game_geometry);
|
||||
None
|
||||
}
|
||||
fn get_username(&mut self) -> Option<String> {
|
||||
eprintln!("{}get_username()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn get_language(&mut self) -> Option<Language> {
|
||||
eprintln!("{}get_language()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn hw_get_current_framebuffer(&mut self) -> Option<usize> {
|
||||
eprintln!("{}hw_get_current_framebuffer()", self.prefix);
|
||||
None
|
||||
}
|
||||
fn hw_get_proc_address(&mut self, sym: &str) -> Option<*const ()> {
|
||||
eprintln!("{}hw_get_proc_address({:?})", self.prefix, sym);
|
||||
None
|
||||
}
|
||||
// TODO: etc...
|
||||
}
|
||||
|
||||
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<bool> {
|
||||
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() {
|
||||
|
@ -108,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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue