Initial OpenGL core support
mmmmmm, this really is NOT as clean as I want it to be, but here it is!
This commit is contained in:
parent
93525cb291
commit
b972478ee9
8 changed files with 457 additions and 31 deletions
34
Cargo.lock
generated
34
Cargo.lock
generated
|
@ -191,6 +191,26 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gl"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a94edab108827d67608095e269cf862e60d920f144a5026d3dbcfd8b877fb404"
|
||||||
|
dependencies = [
|
||||||
|
"gl_generator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gl_generator"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
|
||||||
|
dependencies = [
|
||||||
|
"khronos_api",
|
||||||
|
"log",
|
||||||
|
"xml-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -237,6 +257,12 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "khronos_api"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -433,6 +459,8 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cc",
|
"cc",
|
||||||
"clap",
|
"clap",
|
||||||
|
"gl",
|
||||||
|
"gl_generator",
|
||||||
"libvnc-sys",
|
"libvnc-sys",
|
||||||
"retro_frontend",
|
"retro_frontend",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -785,3 +813,9 @@ checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml-rs"
|
||||||
|
version = "0.8.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193"
|
||||||
|
|
|
@ -21,11 +21,22 @@ use tracing::{error, info};
|
||||||
/// Only one instance of Frontend can be active in an application.
|
/// Only one instance of Frontend can be active in an application.
|
||||||
pub(crate) static mut FRONTEND: *mut Frontend = std::ptr::null_mut();
|
pub(crate) static mut FRONTEND: *mut Frontend = std::ptr::null_mut();
|
||||||
|
|
||||||
|
/// A "Generalized" type for OpenGL's "getProcAddress" idiom
|
||||||
|
pub type GlGetProcAddress = *mut unsafe extern "C" fn(name: *const i8) -> unsafe extern "C" fn();
|
||||||
|
|
||||||
|
/// Initalization data for HW OpenGL cores.
|
||||||
|
pub struct HwGlInitData {
|
||||||
|
pub get_proc_address: GlGetProcAddress,
|
||||||
|
}
|
||||||
|
|
||||||
/// Interface for the frontend to call to user code.
|
/// Interface for the frontend to call to user code.
|
||||||
pub trait FrontendInterface {
|
pub trait FrontendInterface {
|
||||||
/// Called when video is updated.
|
/// Called when video is updated.
|
||||||
fn video_update(&mut self, slice: &[u32], pitch: u32);
|
fn video_update(&mut self, slice: &[u32], pitch: u32);
|
||||||
|
|
||||||
|
/// Called when video is updated and the core is using HW OpenGL rendering.
|
||||||
|
fn video_update_gl(&mut self);
|
||||||
|
|
||||||
/// Called when resize occurs.
|
/// Called when resize occurs.
|
||||||
fn video_resize(&mut self, width: u32, height: u32);
|
fn video_resize(&mut self, width: u32, height: u32);
|
||||||
|
|
||||||
|
@ -36,10 +47,8 @@ pub trait FrontendInterface {
|
||||||
/// Called to poll input
|
/// Called to poll input
|
||||||
fn input_poll(&mut self);
|
fn input_poll(&mut self);
|
||||||
|
|
||||||
// TODO(lily): Provide APIs for gathering a GL context/fbo.
|
/// Initalize hardware accelerated rendering using OpenGL.
|
||||||
// This should be abstracted so if a frontend written on top of retro_frontend
|
fn hw_gl_init(&mut self) -> HwGlInitData;
|
||||||
// wants to give a FBO from a GLX/WGL/windowed whatever context, it can,
|
|
||||||
// but a headless frontend can itself provide a headless EGL context just the same.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Per-core settings
|
/// Per-core settings
|
||||||
|
@ -73,6 +82,9 @@ pub struct Frontend {
|
||||||
pub(crate) fb_height: u32,
|
pub(crate) fb_height: u32,
|
||||||
pub(crate) fb_pitch: u32,
|
pub(crate) fb_pitch: u32,
|
||||||
|
|
||||||
|
// HW OpenGL FBO id.
|
||||||
|
pub(crate) gl_fbo_id: u32,
|
||||||
|
|
||||||
/// The "system" directory. Used for BIOS roms.
|
/// The "system" directory. Used for BIOS roms.
|
||||||
pub(crate) system_directory: CString,
|
pub(crate) system_directory: CString,
|
||||||
|
|
||||||
|
@ -109,6 +121,7 @@ impl Frontend {
|
||||||
fb_width: 0,
|
fb_width: 0,
|
||||||
fb_height: 0,
|
fb_height: 0,
|
||||||
fb_pitch: 0,
|
fb_pitch: 0,
|
||||||
|
gl_fbo_id: 0,
|
||||||
|
|
||||||
// TODO: We should let callers set these, probably.
|
// TODO: We should let callers set these, probably.
|
||||||
// For now, this is probably fine.
|
// For now, this is probably fine.
|
||||||
|
@ -431,6 +444,10 @@ impl Frontend {
|
||||||
(self.fb_width, self.fb_height)
|
(self.fb_width, self.fb_height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_gl_fbo(&mut self, id: u32) {
|
||||||
|
self.gl_fbo_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
let core_api = self.core_api.as_ref().unwrap();
|
let core_api = self.core_api.as_ref().unwrap();
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@ use std::ffi;
|
||||||
|
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
|
/// This function is used with HW OpenGL cores to transfer the current FBO's ID.
|
||||||
|
unsafe extern "C" fn hw_gl_get_framebuffer() -> usize {
|
||||||
|
(*FRONTEND).gl_fbo_id as usize
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) unsafe extern "C" fn environment_callback(
|
pub(crate) unsafe extern "C" fn environment_callback(
|
||||||
environment_command: u32,
|
environment_command: u32,
|
||||||
data: *mut ffi::c_void,
|
data: *mut ffi::c_void,
|
||||||
|
@ -99,6 +104,33 @@ pub(crate) unsafe extern "C" fn environment_callback(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ENVIRONMENT_SET_HW_RENDER => {
|
||||||
|
let hw_render = (data as *mut HwRenderCallback).as_mut().unwrap();
|
||||||
|
|
||||||
|
let hw_render_context_type =
|
||||||
|
HwContextType::from_uint(hw_render.context_type).expect("FUCK");
|
||||||
|
|
||||||
|
if hw_render_context_type != HwContextType::OpenGL && hw_render_context_type != HwContextType::OpenGLCore {
|
||||||
|
error!(
|
||||||
|
"Core is trying to request an context type we don't support ({:?})",
|
||||||
|
hw_render_context_type
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Core requesting context type {:?}", hw_render_context_type);
|
||||||
|
|
||||||
|
let init_data = (*(*FRONTEND).interface).hw_gl_init();
|
||||||
|
|
||||||
|
hw_render.get_current_framebuffer = hw_gl_get_framebuffer;
|
||||||
|
hw_render.get_proc_address = std::mem::transmute(init_data.get_proc_address);
|
||||||
|
|
||||||
|
// reset context
|
||||||
|
(hw_render.context_reset)();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ENVIRONMENT_GET_VARIABLE => {
|
ENVIRONMENT_GET_VARIABLE => {
|
||||||
// Make sure the core actually is giving us a pointer to a *Variable
|
// Make sure the core actually is giving us a pointer to a *Variable
|
||||||
// so we can fill it in.
|
// so we can fill it in.
|
||||||
|
@ -179,9 +211,19 @@ pub(crate) unsafe extern "C" fn video_refresh_callback(
|
||||||
|
|
||||||
//info!("Video refresh called, {width}, {height}, {pitch}");
|
//info!("Video refresh called, {width}, {height}, {pitch}");
|
||||||
|
|
||||||
|
if (*FRONTEND).fb_width != width || (*FRONTEND).fb_height != height {
|
||||||
|
(*(*FRONTEND).interface).video_resize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
// bleh
|
// bleh
|
||||||
(*FRONTEND).fb_width = width;
|
(*FRONTEND).fb_width = width;
|
||||||
(*FRONTEND).fb_height = height;
|
(*FRONTEND).fb_height = height;
|
||||||
|
|
||||||
|
if pixels == (-1i64 as *const ffi::c_void) {
|
||||||
|
(*(*FRONTEND).interface).video_update_gl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
(*FRONTEND).fb_pitch =
|
(*FRONTEND).fb_pitch =
|
||||||
pitch as u32 / util::bytes_per_pixel_from_libretro((*FRONTEND).pixel_format);
|
pitch as u32 / util::bytes_per_pixel_from_libretro((*FRONTEND).pixel_format);
|
||||||
|
|
||||||
|
@ -200,9 +242,6 @@ pub(crate) unsafe extern "C" fn video_refresh_callback(
|
||||||
(*FRONTEND)
|
(*FRONTEND)
|
||||||
.converted_pixel_buffer
|
.converted_pixel_buffer
|
||||||
.resize((pitch * height as usize) as usize, 0);
|
.resize((pitch * height as usize) as usize, 0);
|
||||||
|
|
||||||
// some cores are stupid
|
|
||||||
(*(*FRONTEND).interface).video_resize(width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this convert from weird pitches to native resolution where possible.
|
// TODO: Make this convert from weird pitches to native resolution where possible.
|
||||||
|
@ -217,7 +256,8 @@ pub(crate) unsafe extern "C" fn video_refresh_callback(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(*(*FRONTEND).interface).video_update(&(*FRONTEND).converted_pixel_buffer[..], pitch as u32);
|
(*(*FRONTEND).interface)
|
||||||
|
.video_update(&(*FRONTEND).converted_pixel_buffer[..], pitch as u32);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let pixel_data_slice = std::slice::from_raw_parts(
|
let pixel_data_slice = std::slice::from_raw_parts(
|
||||||
|
|
|
@ -7,6 +7,7 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
clap = { version = "4.5.6", features = ["cargo"] }
|
clap = { version = "4.5.6", features = ["cargo"] }
|
||||||
|
gl = "0.14.0"
|
||||||
libvnc-sys = "0.1.4"
|
libvnc-sys = "0.1.4"
|
||||||
retro_frontend = { path = "../retro_frontend" }
|
retro_frontend = { path = "../retro_frontend" }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
|
@ -14,3 +15,4 @@ tracing-subscriber = "0.3.18"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cc = "1.0.99"
|
cc = "1.0.99"
|
||||||
|
gl_generator = "0.14.0"
|
||||||
|
|
|
@ -1,8 +1,33 @@
|
||||||
use cc;
|
use cc;
|
||||||
|
|
||||||
fn main() {
|
use gl_generator::{Api, Fallbacks, Profile, Registry, StaticGenerator};
|
||||||
let mut build = cc::Build::new();
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// EGL
|
||||||
|
|
||||||
|
let dest = env::var("OUT_DIR").unwrap();
|
||||||
|
let mut file = File::create(&Path::new(&dest).join("egl_bindings.rs")).unwrap();
|
||||||
|
|
||||||
|
Registry::new(Api::Egl, (1, 5), Profile::Core, Fallbacks::All, [
|
||||||
|
"EGL_EXT_platform_base",
|
||||||
|
"EGL_EXT_device_base",
|
||||||
|
|
||||||
|
// This allows getting OpenGL APIs via eglGetProcAddress()
|
||||||
|
"EGL_KHR_get_all_proc_addresses",
|
||||||
|
"EGL_KHR_client_get_all_proc_addresses",
|
||||||
|
|
||||||
|
"EGL_EXT_platform_device"
|
||||||
|
|
||||||
|
])
|
||||||
|
.write_bindings(StaticGenerator, &mut file)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// C++
|
||||||
|
let mut build = cc::Build::new();
|
||||||
build
|
build
|
||||||
.emit_rerun_if_env_changed(true)
|
.emit_rerun_if_env_changed(true)
|
||||||
.cpp(true)
|
.cpp(true)
|
||||||
|
|
34
crates/retrovnc/src/egl.rs
Normal file
34
crates/retrovnc/src/egl.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
mod egl_impl {
|
||||||
|
pub type khronos_utime_nanoseconds_t = khronos_uint64_t;
|
||||||
|
pub type khronos_uint64_t = u64;
|
||||||
|
pub type khronos_ssize_t = std::ffi::c_long;
|
||||||
|
pub type EGLint = i32;
|
||||||
|
pub type EGLNativeDisplayType = *const std::ffi::c_void;
|
||||||
|
pub type EGLNativePixmapType = *const std::ffi::c_void;
|
||||||
|
pub type EGLNativeWindowType = *const std::ffi::c_void;
|
||||||
|
|
||||||
|
pub type NativeDisplayType = EGLNativeDisplayType;
|
||||||
|
pub type NativePixmapType = EGLNativePixmapType;
|
||||||
|
pub type NativeWindowType = EGLNativeWindowType;
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs"));
|
||||||
|
|
||||||
|
pub type GetPlatformDisplayExt = unsafe extern "C" fn(
|
||||||
|
platform: types::EGLenum,
|
||||||
|
native_display: *const std::ffi::c_void,
|
||||||
|
attrib_list: *const types::EGLint,
|
||||||
|
) -> types::EGLDisplay;
|
||||||
|
|
||||||
|
pub type QueryDevicesExt = unsafe extern "C" fn(
|
||||||
|
max_devices: self::types::EGLint,
|
||||||
|
devices: *mut self::types::EGLDeviceEXT,
|
||||||
|
devices: *mut EGLint,
|
||||||
|
) -> types::EGLBoolean;
|
||||||
|
|
||||||
|
// link EGL as a library dependency
|
||||||
|
#[link(name = "EGL")]
|
||||||
|
extern "C" {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use egl_impl::*;
|
|
@ -1,9 +1,9 @@
|
||||||
use std::{net::Ipv4Addr, path::Path};
|
use std::{net::Ipv4Addr, path::Path, ptr::addr_of_mut};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use retro_frontend::{
|
use retro_frontend::{
|
||||||
frontend::{Frontend, FrontendInterface},
|
frontend::{Frontend, FrontendInterface, HwGlInitData},
|
||||||
input_devices::{InputDevice, RetroPad},
|
input_devices::{InputDevice, RetroPad},
|
||||||
};
|
};
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
@ -11,6 +11,32 @@ use tracing_subscriber::FmtSubscriber;
|
||||||
|
|
||||||
use clap::{arg, command, value_parser};
|
use clap::{arg, command, value_parser};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod egl;
|
||||||
mod rfb;
|
mod rfb;
|
||||||
use rfb::*;
|
use rfb::*;
|
||||||
|
|
||||||
|
@ -18,6 +44,15 @@ struct App {
|
||||||
frontend: Option<Box<Frontend>>,
|
frontend: Option<Box<Frontend>>,
|
||||||
rfb_server: Box<RfbServer>,
|
rfb_server: Box<RfbServer>,
|
||||||
pad: RetroPad,
|
pad: RetroPad,
|
||||||
|
|
||||||
|
hw_render: bool,
|
||||||
|
|
||||||
|
// opengl state shits
|
||||||
|
texture_id: gl::types::GLuint,
|
||||||
|
renderbuffer_id: gl::types::GLuint,
|
||||||
|
fbo_id: gl::types::GLuint,
|
||||||
|
|
||||||
|
readback_buffer: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
@ -26,6 +61,11 @@ impl App {
|
||||||
frontend: None,
|
frontend: None,
|
||||||
rfb_server: RfbServer::new(rfb_config)?,
|
rfb_server: RfbServer::new(rfb_config)?,
|
||||||
pad: RetroPad::new(),
|
pad: RetroPad::new(),
|
||||||
|
hw_render: false,
|
||||||
|
texture_id: 0,
|
||||||
|
renderbuffer_id: 0,
|
||||||
|
fbo_id: 0,
|
||||||
|
readback_buffer: Vec::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Very very nasty, but honestly it works.
|
// Very very nasty, but honestly it works.
|
||||||
|
@ -80,6 +120,81 @@ impl App {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hw_gl_resize(&mut self, width: u32, height: u32) {
|
||||||
|
// TODO: if requested, add depth renderbuffer.
|
||||||
|
unsafe {
|
||||||
|
if self.fbo_id == 0 {
|
||||||
|
gl::GenFramebuffers(1, std::ptr::addr_of_mut!(self.fbo_id));
|
||||||
|
gl::BindFramebuffer(gl::FRAMEBUFFER, self.fbo_id);
|
||||||
|
} else {
|
||||||
|
gl::DeleteFramebuffers(1, std::ptr::addr_of_mut!(self.fbo_id));
|
||||||
|
|
||||||
|
gl::GenFramebuffers(1, std::ptr::addr_of_mut!(self.fbo_id));
|
||||||
|
gl::BindFramebuffer(gl::FRAMEBUFFER, self.fbo_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.texture_id == 0 {
|
||||||
|
gl::GenTextures(1, std::ptr::addr_of_mut!(self.texture_id));
|
||||||
|
gl::BindTexture(gl::TEXTURE_2D, self.texture_id);
|
||||||
|
} else {
|
||||||
|
gl::DeleteTextures(1, std::ptr::addr_of_mut!(self.texture_id));
|
||||||
|
gl::GenTextures(1, std::ptr::addr_of_mut!(self.texture_id));
|
||||||
|
gl::BindTexture(gl::TEXTURE_2D, self.texture_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl::TexImage2D(
|
||||||
|
gl::TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
gl::RGBA8 as i32,
|
||||||
|
width as i32,
|
||||||
|
height as i32,
|
||||||
|
0,
|
||||||
|
gl::RGBA,
|
||||||
|
gl::UNSIGNED_BYTE,
|
||||||
|
std::ptr::null(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.renderbuffer_id != 0 {
|
||||||
|
gl::GenRenderbuffers(1, std::ptr::addr_of_mut!(self.renderbuffer_id));
|
||||||
|
gl::BindRenderbuffer(gl::RENDERBUFFER, self.renderbuffer_id);
|
||||||
|
gl::RenderbufferStorage(
|
||||||
|
gl::RENDERBUFFER,
|
||||||
|
gl::DEPTH_COMPONENT,
|
||||||
|
width as i32,
|
||||||
|
height as i32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl::FramebufferTexture2D(
|
||||||
|
gl::FRAMEBUFFER,
|
||||||
|
gl::COLOR_ATTACHMENT0,
|
||||||
|
gl::TEXTURE_2D,
|
||||||
|
self.texture_id,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl::BindTexture(gl::TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
gl::FramebufferRenderbuffer(
|
||||||
|
gl::FRAMEBUFFER,
|
||||||
|
gl::DEPTH_ATTACHMENT,
|
||||||
|
gl::RENDERBUFFER,
|
||||||
|
self.renderbuffer_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
gl::BindRenderbuffer(gl::RENDERBUFFER, 0);
|
||||||
|
|
||||||
|
gl::Viewport(0, 0, width as i32, height as i32);
|
||||||
|
|
||||||
|
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
||||||
|
|
||||||
|
let id = self.fbo_id;
|
||||||
|
self.get_frontend().set_gl_fbo(id);
|
||||||
|
|
||||||
|
self.readback_buffer.resize((width * height) as usize, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
fn main_loop(&mut self) -> ! {
|
fn main_loop(&mut self) -> ! {
|
||||||
let frontend = self.get_frontend();
|
let frontend = self.get_frontend();
|
||||||
|
@ -101,13 +216,41 @@ impl FrontendInterface for App {
|
||||||
//let height = height * 2;
|
//let height = height * 2;
|
||||||
|
|
||||||
tracing::info!("Resized to {width}x{height}");
|
tracing::info!("Resized to {width}x{height}");
|
||||||
|
|
||||||
|
// Resize OpenGL
|
||||||
|
if self.hw_render {
|
||||||
|
self.hw_gl_resize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
self.rfb_server.resize(width as u16, height as u16);
|
self.rfb_server.resize(width as u16, height as u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn video_update(&mut self, slice: &[u32], pitch: u32) {
|
fn video_update(&mut self, slice: &[u32], pitch: u32) {
|
||||||
//let framebuffer_size = self.get_frontend().get_size();
|
//let framebuffer_size = self.get_frontend().get_size();
|
||||||
self.rfb_server
|
self.rfb_server.update_buffer(&slice, pitch, false);
|
||||||
.update_buffer(&slice, pitch);
|
}
|
||||||
|
|
||||||
|
fn video_update_gl(&mut self) {
|
||||||
|
let dimensions = self.get_frontend().get_size();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl::BindFramebuffer(gl::FRAMEBUFFER, self.fbo_id);
|
||||||
|
|
||||||
|
// I know this sucks but hey
|
||||||
|
gl::ReadPixels(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
dimensions.0 as i32,
|
||||||
|
dimensions.1 as i32,
|
||||||
|
gl::RGBA,
|
||||||
|
gl::UNSIGNED_BYTE,
|
||||||
|
self.readback_buffer.as_mut_ptr() as *mut std::ffi::c_void,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.rfb_server.update_buffer(&self.readback_buffer[..], dimensions.0, true);
|
||||||
|
|
||||||
|
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn audio_sample(&mut self, _slice: &[i16], _size: usize) {}
|
fn audio_sample(&mut self, _slice: &[i16], _size: usize) {}
|
||||||
|
@ -123,6 +266,112 @@ impl FrontendInterface for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hw_gl_init(&mut self) -> HwGlInitData {
|
||||||
|
if self.hw_render {
|
||||||
|
panic!("Nope!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let display = unsafe {
|
||||||
|
const NR_DEVICES_MAX: usize = 16;
|
||||||
|
let mut devices: [egl::types::EGLDeviceEXT; NR_DEVICES_MAX] =
|
||||||
|
[std::ptr::null(); NR_DEVICES_MAX];
|
||||||
|
|
||||||
|
let mut nr_devices_real: egl::EGLint = 0;
|
||||||
|
|
||||||
|
let query_devices_ext: egl::QueryDevicesExt = std::mem::transmute(egl::GetProcAddress(
|
||||||
|
b"eglQueryDevicesEXT\0".as_ptr() as *const i8,
|
||||||
|
));
|
||||||
|
let get_platform_display_ext: egl::GetPlatformDisplayExt = std::mem::transmute(
|
||||||
|
egl::GetProcAddress(b"eglGetPlatformDisplayEXT\0".as_ptr() as *const i8),
|
||||||
|
);
|
||||||
|
|
||||||
|
// load OpenGL functions (using EGL loader. We should probably check the one extension exists)
|
||||||
|
gl::load_with(|s| {
|
||||||
|
let str = std::ffi::CString::new(s).expect("you MOTHERFUCKER");
|
||||||
|
std::mem::transmute(egl::GetProcAddress(str.as_ptr()))
|
||||||
|
});
|
||||||
|
|
||||||
|
tracing::info!("before query devices");
|
||||||
|
|
||||||
|
(query_devices_ext)(
|
||||||
|
NR_DEVICES_MAX as i32,
|
||||||
|
devices.as_mut_ptr(),
|
||||||
|
std::ptr::addr_of_mut!(nr_devices_real),
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing::info!("after query devices");
|
||||||
|
(get_platform_display_ext)(egl::PLATFORM_DEVICE_EXT, devices[0], std::ptr::null())
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!("made EGL display");
|
||||||
|
|
||||||
|
let context = unsafe {
|
||||||
|
const EGL_CONFIG_ATTRIBUTES: [egl::types::EGLenum; 13] = [
|
||||||
|
egl::SURFACE_TYPE,
|
||||||
|
egl::PBUFFER_BIT,
|
||||||
|
egl::BLUE_SIZE,
|
||||||
|
8,
|
||||||
|
egl::RED_SIZE,
|
||||||
|
8,
|
||||||
|
egl::BLUE_SIZE,
|
||||||
|
8,
|
||||||
|
egl::DEPTH_SIZE,
|
||||||
|
8,
|
||||||
|
egl::RENDERABLE_TYPE,
|
||||||
|
egl::OPENGL_BIT,
|
||||||
|
egl::NONE,
|
||||||
|
];
|
||||||
|
let mut egl_major: egl::EGLint = 0;
|
||||||
|
let mut egl_minor: egl::EGLint = 0;
|
||||||
|
|
||||||
|
let mut egl_config_count: egl::EGLint = 0;
|
||||||
|
|
||||||
|
let mut config: egl::types::EGLConfig = std::ptr::null();
|
||||||
|
|
||||||
|
egl::Initialize(
|
||||||
|
display,
|
||||||
|
std::ptr::addr_of_mut!(egl_major),
|
||||||
|
std::ptr::addr_of_mut!(egl_minor),
|
||||||
|
);
|
||||||
|
|
||||||
|
egl::ChooseConfig(
|
||||||
|
display,
|
||||||
|
EGL_CONFIG_ATTRIBUTES.as_ptr() as *const egl::EGLint,
|
||||||
|
std::ptr::addr_of_mut!(config),
|
||||||
|
1,
|
||||||
|
std::ptr::addr_of_mut!(egl_config_count),
|
||||||
|
);
|
||||||
|
|
||||||
|
egl::BindAPI(egl::OPENGL_API);
|
||||||
|
|
||||||
|
egl::CreateContext(display, config, egl::NO_CONTEXT, std::ptr::null())
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
egl::MakeCurrent(display, egl::NO_SURFACE, egl::NO_SURFACE, context);
|
||||||
|
|
||||||
|
gl::Enable(gl::DEBUG_OUTPUT);
|
||||||
|
gl::DebugMessageCallback(Some(opengl_message_callback), std::ptr::null());
|
||||||
|
|
||||||
|
let dimensions = self.get_frontend().get_size();
|
||||||
|
|
||||||
|
self.hw_gl_resize(dimensions.0, dimensions.1);
|
||||||
|
|
||||||
|
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.hw_render = true;
|
||||||
|
|
||||||
|
return unsafe {
|
||||||
|
HwGlInitData {
|
||||||
|
get_proc_address: std::mem::transmute::<
|
||||||
|
_,
|
||||||
|
retro_frontend::frontend::GlGetProcAddress,
|
||||||
|
>(egl::GetProcAddress as usize),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
@ -170,7 +419,6 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
app.load_core(core_path)?;
|
app.load_core(core_path)?;
|
||||||
|
|
||||||
|
|
||||||
if let Some(rom_path) = matches.get_one::<String>("rom") {
|
if let Some(rom_path) = matches.get_one::<String>("rom") {
|
||||||
app.load_game(rom_path)?
|
app.load_game(rom_path)?
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub struct RfbServer {
|
||||||
buttons: [bool; 32],
|
buttons: [bool; 32],
|
||||||
|
|
||||||
width: u16,
|
width: u16,
|
||||||
height: u16
|
height: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RfbServer {
|
impl RfbServer {
|
||||||
|
@ -57,7 +57,7 @@ impl RfbServer {
|
||||||
framebuffer: Vec::new(),
|
framebuffer: Vec::new(),
|
||||||
buttons: [false; 32],
|
buttons: [false; 32],
|
||||||
width: config.width,
|
width: config.width,
|
||||||
height: config.height
|
height: config.height,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Disable the normal libvnc cursor (it's annoying)
|
// Disable the normal libvnc cursor (it's annoying)
|
||||||
|
@ -99,8 +99,7 @@ impl RfbServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_buffer(&mut self, slice: &[u32], pitch: u32) {
|
pub fn update_buffer(&mut self, slice: &[u32], pitch: u32, flipped: bool) {
|
||||||
|
|
||||||
//self.framebuffer.copy_from_slice(&slice);
|
//self.framebuffer.copy_from_slice(&slice);
|
||||||
|
|
||||||
let has_disconnected_pitch = pitch != self.width as u32;
|
let has_disconnected_pitch = pitch != self.width as u32;
|
||||||
|
@ -110,20 +109,47 @@ impl RfbServer {
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// copy a line at a time (test)
|
// copy a line at a time (test)
|
||||||
for y in 0..self.height {
|
|
||||||
let src_line_off = (y as u32 * pitch) as usize;
|
|
||||||
let mut dest_line_off = src_line_off;
|
|
||||||
|
|
||||||
// copy only
|
if flipped {
|
||||||
if has_disconnected_pitch {
|
let mut scanlines: Vec<&[u32]> = Vec::with_capacity(self.height as usize);
|
||||||
dest_line_off = (y * self.width) as usize;
|
|
||||||
|
for y in (0..self.height).rev() {
|
||||||
|
let src_line_off = (y as u32 * pitch) as usize;
|
||||||
|
let src_slice = &slice[src_line_off..src_line_off + self.width as usize];
|
||||||
|
scanlines.push(src_slice);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create slices repressenting each part
|
for y in (0..self.height) {
|
||||||
let src_slice = &slice[src_line_off..src_line_off + self.width as usize];
|
let src_line_off = (y as u32 * pitch) as usize;
|
||||||
let dest_slice = &mut self.framebuffer[dest_line_off..dest_line_off + self.width as usize];
|
let mut dest_line_off = src_line_off;
|
||||||
|
|
||||||
dest_slice.copy_from_slice(src_slice);
|
// copy only
|
||||||
|
if has_disconnected_pitch {
|
||||||
|
dest_line_off = (y * self.width) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dest_slice =
|
||||||
|
&mut self.framebuffer[dest_line_off..dest_line_off + self.width as usize];
|
||||||
|
|
||||||
|
dest_slice.copy_from_slice(scanlines[y as usize]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for y in 0..self.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 * self.width) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create slices repressenting each part
|
||||||
|
let src_slice = &slice[src_line_off..src_line_off + self.width as usize];
|
||||||
|
let dest_slice =
|
||||||
|
&mut self.framebuffer[dest_line_off..dest_line_off + self.width as usize];
|
||||||
|
|
||||||
|
dest_slice.copy_from_slice(src_slice);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
Loading…
Reference in a new issue