This commit is contained in:
Lily Tsuru 2024-10-10 18:47:25 -04:00
parent 9b696d3f8b
commit 1c86b877ca
4 changed files with 735 additions and 18 deletions

99
server/Cargo.lock generated
View file

@ -714,6 +714,16 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.16.0" version = "1.16.0"
@ -739,6 +749,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.3" version = "0.12.3"
@ -1026,6 +1042,15 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -1109,6 +1134,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.8.0" version = "1.8.0"
@ -1257,6 +1292,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@ -1322,6 +1383,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@ -1344,12 +1411,16 @@ dependencies = [
"ffmpeg-next", "ffmpeg-next",
"futures", "futures",
"futures-util", "futures-util",
"gl",
"letsplay_gpu", "letsplay_gpu",
"rand", "rand",
"retro_frontend", "retro_frontend",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"tracing",
"tracing-subscriber",
"xkeysym",
] ]
[[package]] [[package]]
@ -1358,6 +1429,28 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"
@ -1440,6 +1533,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "xkeysym"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
[[package]] [[package]]
name = "xml-rs" name = "xml-rs"
version = "0.8.22" version = "0.8.22"

View file

@ -7,8 +7,10 @@ edition = "2021"
anyhow = "1.0.86" anyhow = "1.0.86"
# Libretro Sex
letsplay_gpu.path = "/home/lily/source/lets-play/crates/letsplay_gpu" letsplay_gpu.path = "/home/lily/source/lets-play/crates/letsplay_gpu"
retro_frontend.path = "/home/lily/source/lets-play/crates/retro_frontend" retro_frontend.path = "/home/lily/source/lets-play/crates/retro_frontend"
gl = "0.14.0"
# async # async
tokio = { version = "1.39.3", features = ["full"] } tokio = { version = "1.39.3", features = ["full"] }
@ -26,6 +28,9 @@ rand = "0.8.5"
serde = "1.0.209" serde = "1.0.209"
serde_json = "1.0.128" serde_json = "1.0.128"
cudarc = { version = "0.12.1", features = [ "cuda-11050" ] } cudarc = { version = "0.12.1", features = [ "cuda-11050" ] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
xkeysym = "0.2.1"
[patch.crates-io] [patch.crates-io]

View file

@ -1,21 +1,25 @@
mod retro_thread;
mod surface; mod surface;
mod types; mod types;
mod video; mod video;
use retro_thread::{spawn_retro_thread, App, RetroEvent};
use video::ffmpeg; use video::ffmpeg;
use video::h264_encoder::H264Encoder; use video::h264_encoder::H264Encoder;
use std::{ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
thread,
time::Duration, time::Duration,
}; };
use rand::distributions::DistString; use rand::distributions::DistString;
use std::net::SocketAddr;
use tokio::sync::{ use tokio::sync::{
broadcast, broadcast,
mpsc::{self, error::TryRecvError}, mpsc::{self, error::TryRecvError},
Mutex as TokioMutex, Mutex as TokioMutex,
}; };
use std::net::SocketAddr;
use axum::{ use axum::{
extract::{ extract::{
@ -32,18 +36,18 @@ use futures::{sink::SinkExt, stream::StreamExt};
struct AppState { struct AppState {
encoder_tx: Arc<TokioMutex<mpsc::Sender<()>>>, encoder_tx: Arc<TokioMutex<mpsc::Sender<()>>>,
inputs: Arc<TokioMutex<Vec<u32>>>,
websocket_broadcast_tx: broadcast::Sender<ws::Message>, websocket_broadcast_tx: broadcast::Sender<ws::Message>,
websocket_count: TokioMutex<usize>, websocket_count: TokioMutex<usize>,
} }
impl AppState { impl AppState {
fn new( fn new(encoder_tx: mpsc::Sender<()>) -> Self {
encoder_tx: mpsc::Sender<()>,
) -> Self {
let (chat_tx, _chat_rx) = broadcast::channel(10); let (chat_tx, _chat_rx) = broadcast::channel(10);
Self { Self {
encoder_tx: Arc::new(TokioMutex::new(encoder_tx)), encoder_tx: Arc::new(TokioMutex::new(encoder_tx)),
inputs: Arc::new(TokioMutex::new(Vec::new())),
websocket_broadcast_tx: chat_tx, websocket_broadcast_tx: chat_tx,
websocket_count: TokioMutex::const_new(0usize), websocket_count: TokioMutex::const_new(0usize),
} }
@ -74,7 +78,7 @@ impl EncoderState {
// FIXME: use create_frame() on H264Encoder // FIXME: use create_frame() on H264Encoder
self.frame = Some(ffmpeg::frame::Video::new( self.frame = Some(ffmpeg::frame::Video::new(
ffmpeg::format::Pixel::BGRA, ffmpeg::format::Pixel::RGBA,
size.width, size.width,
size.height, size.height,
)); ));
@ -109,7 +113,9 @@ impl EncoderState {
} }
encoder.send_frame(&*frame); encoder.send_frame(&*frame);
encoder.receive_packet(&mut self.packet).expect("Failed to recieve packet"); encoder
.receive_packet(&mut self.packet)
.expect("Failed to recieve packet");
unsafe { unsafe {
if !self.packet.is_empty() { if !self.packet.is_empty() {
@ -123,6 +129,13 @@ impl EncoderState {
#[tokio::main(flavor = "multi_thread", worker_threads = 8)] #[tokio::main(flavor = "multi_thread", worker_threads = 8)]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
// Setup a tracing subscriber
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_max_level(tracing::Level::INFO)
.finish();
tracing::subscriber::set_global_default(subscriber).unwrap();
let surface = Arc::new(Mutex::new(surface::Surface::new())); let surface = Arc::new(Mutex::new(surface::Surface::new()));
// H.264 encoder related // H.264 encoder related
@ -131,6 +144,126 @@ async fn main() -> anyhow::Result<()> {
let state = Arc::new(AppState::new(encoder_tx)); let state = Arc::new(AppState::new(encoder_tx));
let (mut event_rx, event_in_tx) = spawn_retro_thread(surface.clone());
let state_clone = state.clone();
let encoder_state_clone = encoder_state.clone();
let vnc_recv_handle = tokio::spawn(async move {
let surface_clone = surface.clone();
// first frame is always a key frame
let mut pts = 0u64;
let mut force_keyframe = true;
let mut frame_update = false;
// start the thread now that we're alive
let _ = event_in_tx.send(retro_thread::RetroInEvent::Start).await;
loop {
match encoder_rx.try_recv() {
Ok(()) => {
// force keyframe
force_keyframe = true;
frame_update = true;
}
Err(TryRecvError::Disconnected) => break,
Err(TryRecvError::Empty) => {}
}
match event_rx.try_recv() {
Ok(msg) => match msg {
RetroEvent::Frame => {
{
let mut state_locked = encoder_state_clone.lock().await;
let mut_frame = state_locked.frame();
let width = mut_frame.width();
let height = mut_frame.height();
let mut surf = surface_clone.lock().expect(
"locking the VNC surface to paint it to the ffmpeg frame failed",
);
let surf_buf = surf.get_buffer();
let buf_ptr =
unsafe { (*(*mut_frame.as_mut_ptr()).buf[0]).data as *mut u32 };
for y in 0..height {
let line_stride = (y * width) as usize;
// Make a slice for the line
// SAFETY: The allocation is guaranteed to be large enough
// for this to work from y = 0..height
let dest_line_slice = unsafe {
let dest_line_ptr = buf_ptr.add(line_stride);
std::slice::from_raw_parts_mut(dest_line_ptr, width as usize)
};
dest_line_slice.copy_from_slice(
&surf_buf[line_stride..line_stride + width as usize],
);
}
}
frame_update = true;
}
RetroEvent::Resize { size } => {
{
let mut state_locked = encoder_state_clone.lock().await;
state_locked.init(size).expect("fuck you");
// reset our internal state
pts = 0;
force_keyframe = true;
frame_update = false;
}
}
RetroEvent::WantInputs { tx } => {
let inputs = state_clone.inputs.lock().await;
//tracing::info!("giving inputs {:?}", inputs);
tx.send(inputs.clone()).expect("FUCK");
}
},
Err(TryRecvError::Disconnected) => break,
Err(TryRecvError::Empty) => {}
}
// send frame if we should.
if frame_update {
let mut state_locked = encoder_state_clone.lock().await;
match state_locked.send_frame(pts, force_keyframe) {
Some(mut packet) => {
let vec = {
let data = packet.data_mut().expect("packet is empty somehow");
data.to_vec()
};
let _ = state_clone
.websocket_broadcast_tx
.send(ws::Message::Binary(vec));
pts += 1;
if force_keyframe {
force_keyframe = false;
}
}
None => {}
}
frame_update = false;
}
tokio::time::sleep(Duration::from_millis(1)).await;
}
});
// Axum websocket server // Axum websocket server
let app: Router<()> = Router::new() let app: Router<()> = Router::new()
@ -260,6 +393,21 @@ async fn handle_socket(socket: WebSocket, who: SocketAddr, state: Arc<AppState>)
let keysym = json["keysym"].as_u64().unwrap() as u32; let keysym = json["keysym"].as_u64().unwrap() as u32;
let pressed = json["pressed"].as_u64().unwrap() == 1; let pressed = json["pressed"].as_u64().unwrap() == 1;
// FIXME: This would be MUCH better off being a set, so we don't
// hack-code set semantics here. Oh well.
{
let mut lock = recv_clone.inputs.lock().await;
if pressed {
if let None = lock.iter().position(|e| *e == keysym) {
lock.push(keysym);
}
} else {
if let Some(at) = lock.iter().position(|e| *e == keysym) {
lock.remove(at);
}
}
}
/*let _ = recv_clone /*let _ = recv_clone
.engine_tx .engine_tx
.send(vnc_engine::VncMessageInput::KeyEvent { .send(vnc_engine::VncMessageInput::KeyEvent {

465
server/src/retro_thread.rs Normal file
View file

@ -0,0 +1,465 @@
use std::{
path::Path,
sync::{Arc, Mutex},
time::Duration,
};
use tokio::sync::{mpsc, oneshot};
use anyhow::Result;
use retro_frontend::{
frontend::{Frontend, FrontendInterface, HwGlInitData},
input_devices::{InputDevice, RetroPad},
libretro_sys_new,
};
use gpu::egl_helpers::DeviceContext;
use letsplay_gpu as gpu;
use crate::{
surface::Surface,
types::{Rect, Size},
};
/// Called by OpenGL. We use this to dump errors.
extern "system" fn opengl_message_callback(
source: gl::types::GLenum,
_type: gl::types::GLenum,
id: gl::types::GLuint,
_severity: gl::types::GLenum,
_length: gl::types::GLsizei,
message: *const gl::types::GLchar,
_user: *mut std::ffi::c_void,
) {
unsafe {
let message = std::ffi::CStr::from_ptr(message);
if _type == gl::DEBUG_TYPE_ERROR {
tracing::error!(
"OpenGL error: {:?} (res {:08x}, id = {:08x}, source = {:08x})",
message,
_type,
id,
source
);
}
}
}
pub struct App {
frontend: Option<Box<Frontend>>,
pad: RetroPad,
// EGL state
egl_context: Option<DeviceContext>,
framebuffer: Arc<Mutex<Surface>>,
// OpenGL object IDs
gl_framebuffer: gpu::GlFramebuffer,
/// Cached readback buffer.
readback_buffer: Surface,
event_tx: mpsc::Sender<RetroEvent>,
}
impl App {
pub fn new(framebuffer: Arc<Mutex<Surface>>, event_tx: mpsc::Sender<RetroEvent>) -> Box<Self> {
let mut boxed = Box::new(Self {
frontend: None,
pad: RetroPad::new(),
egl_context: None,
framebuffer,
gl_framebuffer: gpu::GlFramebuffer::new(),
readback_buffer: Surface::new(),
event_tx,
});
// SAFETY: The only way to touch the pointer involves the frontend library calling retro_run,
// and the core calling one of the given callbacks. Therefore this is gnarly, but "fine",
// since once the main loop ends, there won't be an opporturnity for said callbacks to be called again.
//
// I'm still not really sure how to tell the borrow checker that this is alright,
// short of Box::leak() (which I don't want to do, since ideally I'd like actual cleanup to occur).
let obj = &mut *boxed as *mut dyn FrontendInterface;
boxed.frontend = Some(Frontend::new(obj));
boxed
}
fn get_frontend(&mut self) -> &mut Frontend {
self.frontend.as_mut().unwrap()
}
/// Inserts our RetroPad and initalizes the display.
pub fn init(&mut self) {
// SAFETY: This too won't ever be Use-After-Free'd because the only chance to
// goes away on drop as well. That's a bit flaky reasoning wise, but is true.
//
// In all honesty, I'm not sure this even needs to be a *mut so I could see if
// making it a immutable reference works.
let pad = &mut self.pad as *mut dyn InputDevice;
self.get_frontend().plug_input_device(0, pad);
self.init_display();
}
fn init_display(&mut self) {
let av_info = self.get_frontend().get_av_info().expect("No AV info");
//self.window.resize(
// av_info.geometry.base_width as u16,
// av_info.geometry.base_height as u16,
//);
}
pub fn load_core<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
// Unload an existing core.
if self.get_frontend().core_loaded() {
let _ = self.get_frontend().unload_core();
}
self.get_frontend().load_core(path)?;
Ok(())
}
pub fn load_game<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
self.get_frontend().load_game(path)?;
Ok(())
}
/// Initalizes the headless EGL context used for OpenGL rendering.
fn hw_gl_egl_init(&mut self) {
self.egl_context = Some(DeviceContext::new(0));
}
/// Destroys OpenGL resources and the EGL context.
fn hw_gl_destroy(&mut self) {
if self.egl_context.is_some() {
self.gl_framebuffer.destroy();
self.egl_context.take().unwrap().destroy()
}
}
pub fn tick(&mut self) {
let av_info = self.get_frontend().get_av_info().expect("???");
let step_ms = (1.0 / av_info.timing.fps) * 1000.;
let step_duration = Duration::from_millis(step_ms as u64);
self.get_frontend().run_frame();
let _ = self.event_tx.blocking_send(RetroEvent::Frame);
std::thread::sleep(step_duration);
}
/// Bleh, I don't like this is an associated fn, but whatever
fn update_impl(framebuffer: Arc<Mutex<Surface>>, slice: &[u32], pitch: u32, from_opengl: bool) {
let mut framebuffer_locked = framebuffer.lock().expect("could not lock framebuffer");
let size = framebuffer_locked.size.clone();
let has_disconnected_pitch = pitch != size.width as u32;
// If this frame came from OpenGL we need to flip the image around
// so it is right side up (from our perspective).
if from_opengl {
let mut scanlines: Vec<&[u32]> = Vec::with_capacity(size.height as usize);
// Push scanline slices in reverse order (which will actually flip them to the right orientation)
for y in (0..size.height).rev() {
let src_line_off = (y as u32 * pitch) as usize;
let src_slice = &slice[src_line_off..src_line_off + size.width as usize];
scanlines.push(src_slice);
}
// Draw them
for y in 0..size.height {
let src_line_off = (y as u32 * pitch) as usize;
let mut dest_line_off = src_line_off;
// copy only
if has_disconnected_pitch {
dest_line_off = (y * size.width.min(pitch)) as usize;
}
let dest_slice = &mut framebuffer_locked.get_buffer()
[dest_line_off..dest_line_off + size.width as usize];
dest_slice.copy_from_slice(scanlines[y as usize]);
// swap the scanline pixels to BGRA order to make minifb happy
// not the fastest code but this should do for an example
//for pix in dest_slice {
// let a = (*pix & 0xff000000) >> 24;
// let b = (*pix & 0x00ff0000) >> 16;
// let g = (*pix & 0x0000ff00) >> 8;
// let r = *pix & 0x000000ff;
// *pix = a << 24 | r << 16 | g << 8 | b;
//}
}
} else {
for y in 0..size.height {
let src_line_off = (y as u32 * pitch) as usize;
let mut dest_line_off = src_line_off;
// copy only
if has_disconnected_pitch {
dest_line_off = (y * size.width) as usize;
}
// Create slices repressenting each part
let src_slice = &slice[src_line_off..src_line_off + size.width as usize];
let dest_slice = &mut framebuffer_locked.get_buffer()
[dest_line_off..dest_line_off + size.width as usize];
dest_slice.copy_from_slice(src_slice);
}
}
}
}
impl FrontendInterface for App {
fn video_resize(&mut self, width: u32, height: u32) {
tracing::info!("Resized to {width}x{height}");
if self.egl_context.is_some() {
self.gl_framebuffer.resize(width, height);
let raw = self.gl_framebuffer.as_raw();
// Notify the frontend layer about the new FBO ID
self.get_frontend().set_gl_fbo(raw);
// Resize the readback buffer
self.readback_buffer.resize(Size { width, height });
}
self.framebuffer
.lock()
.expect("its over?")
.resize(Size { width, height });
let _ = self.event_tx.blocking_send(RetroEvent::Resize {
size: Size { width, height },
});
}
fn video_update(&mut self, slice: &[u32], pitch: u32) {
Self::update_impl(self.framebuffer.clone(), slice, pitch, false);
}
fn video_update_gl(&mut self) {
let dimensions = self.get_frontend().get_size();
// Read back the framebuffer
let slice = {
//self.gl_framebuffer.read_pixels(
// &mut self.readback_buffer.get_buffer()[..],
// dimensions.0,
// dimensions.1,
//);
unsafe {
let scope = self.gl_framebuffer.bind();
gl::ReadPixels(
0,
0,
dimensions.0 as i32,
dimensions.1 as i32,
gl::BGRA,
gl::UNSIGNED_BYTE,
(&mut self.readback_buffer.get_buffer()).as_mut_ptr() as *mut std::ffi::c_void,
);
}
self.readback_buffer.get_buffer()
};
Self::update_impl(self.framebuffer.clone(), slice, dimensions.0, true);
}
fn audio_sample(&mut self, _slice: &[i16], _size: usize) {}
fn input_poll(&mut self) {
self.pad.reset();
let (tx, rx) = oneshot::channel();
let _ = self.event_tx.blocking_send(RetroEvent::WantInputs { tx });
let inputs = rx.blocking_recv().expect("what the FUCK are you doing");
for key in &inputs {
use xkeysym::key as Key;
match *key {
Key::backslash => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_SELECT, None);
}
Key::Return => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_START, None);
}
Key::Up => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_UP, None);
}
Key::Down => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_DOWN, None);
}
Key::Left => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_LEFT, None);
}
Key::Right => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_RIGHT, None);
}
Key::s => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_B, None);
}
Key::a => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_A, None);
}
Key::q => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_X, None);
}
Key::w => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_Y, None);
}
Key::Control_L => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_L, None);
}
Key::Shift_L => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_L2, None);
}
Key::Alt_L => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_R, None);
}
Key::z => {
self.pad
.press_button(libretro_sys_new::DEVICE_ID_JOYPAD_R2, None);
}
_ => {}
}
}
}
fn hw_gl_init(&mut self) -> Option<HwGlInitData> {
// Only create a new EGL/OpenGL context if we have to.
if self.egl_context.is_none() {
// Initalize EGL
self.hw_gl_egl_init();
let context = self.egl_context.as_ref().unwrap();
let extensions = gpu::egl_helpers::get_extensions(context.get_display());
tracing::debug!("Supported EGL extensions: {:?}", extensions);
// Check for EGL_KHR_get_all_proc_addresses, so we can use eglGetProcAddress() to load OpenGL functions
if !extensions.contains(&"EGL_KHR_get_all_proc_addresses".into()) {
tracing::error!("Your graphics driver doesn't support the EGL_KHR_get_all_proc_addresses extension.");
tracing::error!("Retrodemo currently needs this to load OpenGL functions. HW rendering will be disabled.");
return None;
}
unsafe {
// Load OpenGL functions using the EGL loader.
gl::load_with(|s| {
let str = std::ffi::CString::new(s).expect("gl::load_with fail");
std::mem::transmute(gpu::egl::GetProcAddress(str.as_ptr()))
});
// set OpenGL debug message callback
gl::Enable(gl::DEBUG_OUTPUT);
gl::DebugMessageCallback(Some(opengl_message_callback), std::ptr::null());
}
}
// Create the initial FBO for the core to render to
let dimensions = self.get_frontend().get_size();
self.gl_framebuffer.resize(dimensions.0, dimensions.1);
return Some(HwGlInitData {
get_proc_address: gpu::egl::GetProcAddress as *mut std::ffi::c_void,
});
}
}
impl Drop for App {
fn drop(&mut self) {
// Terminate EGL and GL resources if need be
self.hw_gl_destroy();
}
}
pub enum RetroEvent {
Frame,
Resize { size: Size },
WantInputs { tx: oneshot::Sender<Vec<u32>> },
}
pub enum RetroInEvent {
Start,
}
fn retro_thread_main(
surface: Arc<Mutex<Surface>>,
event_tx: mpsc::Sender<RetroEvent>,
mut event_rx: mpsc::Receiver<RetroInEvent>,
) {
let mut app = App::new(surface, event_tx);
app.load_core("cores/swanstation_libretro.so")
.expect("failed to load core");
app.load_game("roms/merged/nmv1/us/nmv1_us.cue") //merged/nmv1/us/nmv1_us.cue
.expect("failed to load game");
// sync
loop {
match event_rx.blocking_recv() {
None => return (),
Some(msg) => match msg {
RetroInEvent::Start => break,
},
}
}
app.init();
loop {
app.tick();
}
}
pub fn spawn_retro_thread(
surface: Arc<Mutex<Surface>>,
) -> (mpsc::Receiver<RetroEvent>, mpsc::Sender<RetroInEvent>) {
let (event_tx, event_rx) = mpsc::channel(8);
let (event_in_tx, event_in_rx) = mpsc::channel(8);
let fb_clone = surface.clone();
std::thread::spawn(move || {
retro_thread_main(fb_clone, event_tx, event_in_rx);
});
(event_rx, event_in_tx)
}