Compare commits
2 commits
46520d22ae
...
91dee22da5
Author | SHA1 | Date | |
---|---|---|---|
91dee22da5 | |||
897f45e49f |
3 changed files with 133 additions and 99 deletions
|
@ -247,7 +247,6 @@ pub(crate) unsafe extern "C" fn video_refresh_callback(
|
||||||
.resize((pitch * height as usize) as usize, 0);
|
.resize((pitch * height as usize) as usize, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this convert from weird pitches to native resolution where possible.
|
|
||||||
for x in 0..pitch as usize {
|
for x in 0..pitch as usize {
|
||||||
for y in 0..height as usize {
|
for y in 0..height as usize {
|
||||||
let rgb = Rgb565::from_rgb565(pixel_data_slice[y * pitch as usize + x]);
|
let rgb = Rgb565::from_rgb565(pixel_data_slice[y * pitch as usize + x]);
|
||||||
|
|
|
@ -27,6 +27,29 @@ mod egl_impl {
|
||||||
devices: *mut EGLint,
|
devices: *mut EGLint,
|
||||||
) -> types::EGLBoolean;
|
) -> types::EGLBoolean;
|
||||||
|
|
||||||
|
/// A helper to get a display on the EGL "Device" platform, which allows headless rendering,
|
||||||
|
/// without any window system interface.
|
||||||
|
pub unsafe fn get_device_platform_display() -> types::EGLDisplay {
|
||||||
|
const NR_DEVICES_MAX: usize = 16;
|
||||||
|
let mut devices: [types::EGLDeviceEXT; NR_DEVICES_MAX] = [std::ptr::null(); NR_DEVICES_MAX];
|
||||||
|
|
||||||
|
let mut nr_devices_real: EGLint = 0;
|
||||||
|
|
||||||
|
let query_devices_ext: QueryDevicesExt =
|
||||||
|
std::mem::transmute(GetProcAddress(b"eglQueryDevicesEXT\0".as_ptr() as *const i8));
|
||||||
|
let get_platform_display_ext: GetPlatformDisplayExt = std::mem::transmute(GetProcAddress(
|
||||||
|
b"eglGetPlatformDisplayEXT\0".as_ptr() as *const i8,
|
||||||
|
));
|
||||||
|
|
||||||
|
(query_devices_ext)(
|
||||||
|
NR_DEVICES_MAX as i32,
|
||||||
|
devices.as_mut_ptr(),
|
||||||
|
std::ptr::addr_of_mut!(nr_devices_real),
|
||||||
|
);
|
||||||
|
|
||||||
|
(get_platform_display_ext)(PLATFORM_DEVICE_EXT, devices[0], std::ptr::null())
|
||||||
|
}
|
||||||
|
|
||||||
// link EGL as a library dependency
|
// link EGL as a library dependency
|
||||||
#[link(name = "EGL")]
|
#[link(name = "EGL")]
|
||||||
extern "C" {}
|
extern "C" {}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{net::Ipv4Addr, path::Path};
|
use std::{net::Ipv4Addr, path::Path, time::Duration};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
@ -46,6 +46,10 @@ struct App {
|
||||||
|
|
||||||
hw_render: bool,
|
hw_render: bool,
|
||||||
|
|
||||||
|
// EGL state
|
||||||
|
egl_display: egl::types::EGLDisplay,
|
||||||
|
egl_context: egl::types::EGLContext,
|
||||||
|
|
||||||
// OpenGL object IDs
|
// OpenGL object IDs
|
||||||
texture_id: gl::types::GLuint,
|
texture_id: gl::types::GLuint,
|
||||||
renderbuffer_id: gl::types::GLuint,
|
renderbuffer_id: gl::types::GLuint,
|
||||||
|
@ -62,6 +66,8 @@ impl App {
|
||||||
rfb_server: RfbServer::new(rfb_config)?,
|
rfb_server: RfbServer::new(rfb_config)?,
|
||||||
pad: RetroPad::new(),
|
pad: RetroPad::new(),
|
||||||
hw_render: false,
|
hw_render: false,
|
||||||
|
egl_display: std::ptr::null(),
|
||||||
|
egl_context: std::ptr::null(),
|
||||||
texture_id: 0,
|
texture_id: 0,
|
||||||
renderbuffer_id: 0,
|
renderbuffer_id: 0,
|
||||||
fbo_id: 0,
|
fbo_id: 0,
|
||||||
|
@ -120,26 +126,98 @@ impl App {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hw_gl_resize(&mut self, width: u32, height: u32) {
|
/// Initalizes a headless EGL context for OpenGL rendering.
|
||||||
// TODO: cleanup "delete" codepaths so we aren't duplicating as much.
|
unsafe fn hw_gl_egl_init(&mut self) {
|
||||||
|
self.egl_display = egl::get_device_platform_display();
|
||||||
|
|
||||||
|
self.egl_context = {
|
||||||
|
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(
|
||||||
|
self.egl_display,
|
||||||
|
std::ptr::addr_of_mut!(egl_major),
|
||||||
|
std::ptr::addr_of_mut!(egl_minor),
|
||||||
|
);
|
||||||
|
|
||||||
|
egl::ChooseConfig(
|
||||||
|
self.egl_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);
|
||||||
|
|
||||||
|
let context =
|
||||||
|
egl::CreateContext(self.egl_display, config, egl::NO_CONTEXT, std::ptr::null());
|
||||||
|
|
||||||
|
// Make the context current on the display so OpenGL routines "just work"
|
||||||
|
egl::MakeCurrent(self.egl_display, egl::NO_SURFACE, egl::NO_SURFACE, context);
|
||||||
|
|
||||||
|
context
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroys EGL resources.
|
||||||
|
unsafe fn hw_gl_egl_exit(&mut self) {
|
||||||
|
// Release the EGL context we created before destroying it
|
||||||
|
egl::MakeCurrent(
|
||||||
|
self.egl_display,
|
||||||
|
egl::NO_SURFACE,
|
||||||
|
egl::NO_SURFACE,
|
||||||
|
egl::NO_CONTEXT,
|
||||||
|
);
|
||||||
|
egl::DestroyContext(self.egl_display, self.egl_context);
|
||||||
|
egl::Terminate(self.egl_display);
|
||||||
|
self.egl_display = std::ptr::null();
|
||||||
|
self.egl_context = std::ptr::null();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hw_gl_delete_fbo(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
if self.fbo_id == 0 {
|
gl::DeleteFramebuffers(1, std::ptr::addr_of_mut!(self.fbo_id));
|
||||||
gl::GenFramebuffers(1, std::ptr::addr_of_mut!(self.fbo_id));
|
self.fbo_id = 0;
|
||||||
gl::BindFramebuffer(gl::FRAMEBUFFER, self.fbo_id);
|
|
||||||
} else {
|
gl::DeleteTextures(1, std::ptr::addr_of_mut!(self.texture_id));
|
||||||
gl::DeleteFramebuffers(1, std::ptr::addr_of_mut!(self.fbo_id));
|
self.texture_id = 0;
|
||||||
gl::GenFramebuffers(1, std::ptr::addr_of_mut!(self.fbo_id));
|
|
||||||
gl::BindFramebuffer(gl::FRAMEBUFFER, self.fbo_id);
|
gl::DeleteRenderbuffers(1, std::ptr::addr_of_mut!(self.renderbuffer_id));
|
||||||
|
self.renderbuffer_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hw_gl_create_fbo(&mut self, width: u32, height: u32) {
|
||||||
|
unsafe {
|
||||||
|
if self.fbo_id != 0 {
|
||||||
|
self.hw_gl_delete_fbo();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.texture_id == 0 {
|
gl::GenFramebuffers(1, std::ptr::addr_of_mut!(self.fbo_id));
|
||||||
gl::GenTextures(1, std::ptr::addr_of_mut!(self.texture_id));
|
gl::BindFramebuffer(gl::FRAMEBUFFER, self.fbo_id);
|
||||||
gl::BindTexture(gl::TEXTURE_2D, self.texture_id);
|
|
||||||
} else {
|
gl::GenTextures(1, std::ptr::addr_of_mut!(self.texture_id));
|
||||||
gl::DeleteTextures(1, std::ptr::addr_of_mut!(self.texture_id));
|
gl::BindTexture(gl::TEXTURE_2D, 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::TexImage2D(
|
||||||
gl::TEXTURE_2D,
|
gl::TEXTURE_2D,
|
||||||
|
@ -153,14 +231,8 @@ impl App {
|
||||||
std::ptr::null(),
|
std::ptr::null(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.renderbuffer_id == 0 {
|
gl::GenRenderbuffers(1, std::ptr::addr_of_mut!(self.renderbuffer_id));
|
||||||
gl::GenRenderbuffers(1, std::ptr::addr_of_mut!(self.renderbuffer_id));
|
gl::BindRenderbuffer(gl::RENDERBUFFER, self.renderbuffer_id);
|
||||||
gl::BindRenderbuffer(gl::RENDERBUFFER, self.renderbuffer_id);
|
|
||||||
} else {
|
|
||||||
gl::DeleteRenderbuffers(1, std::ptr::addr_of_mut!(self.renderbuffer_id));
|
|
||||||
gl::GenRenderbuffers(1, std::ptr::addr_of_mut!(self.renderbuffer_id));
|
|
||||||
gl::BindRenderbuffer(gl::RENDERBUFFER, self.renderbuffer_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
gl::RenderbufferStorage(
|
gl::RenderbufferStorage(
|
||||||
gl::RENDERBUFFER,
|
gl::RENDERBUFFER,
|
||||||
|
@ -192,9 +264,11 @@ impl App {
|
||||||
|
|
||||||
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
|
||||||
|
|
||||||
|
// Notify the frontend layer about the new FBO
|
||||||
let id = self.fbo_id;
|
let id = self.fbo_id;
|
||||||
self.get_frontend().set_gl_fbo(id);
|
self.get_frontend().set_gl_fbo(id);
|
||||||
|
|
||||||
|
// Resize the readback buffer
|
||||||
self.readback_buffer.resize((width * height) as usize, 0);
|
self.readback_buffer.resize((width * height) as usize, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,12 +278,13 @@ impl App {
|
||||||
let frontend = self.get_frontend();
|
let frontend = self.get_frontend();
|
||||||
|
|
||||||
let av_info = frontend.get_av_info().expect("???");
|
let av_info = frontend.get_av_info().expect("???");
|
||||||
let step_ms = ((1.0 / av_info.timing.fps) * 1000.) as u64;
|
let step_ms = (1.0 / av_info.timing.fps) * 1000.;
|
||||||
|
let step_duration = Duration::from_micros((step_ms * 1000.) as u64);
|
||||||
|
|
||||||
// Do the main loop
|
// Do the main loop
|
||||||
loop {
|
loop {
|
||||||
frontend.run_frame();
|
frontend.run_frame();
|
||||||
std::thread::sleep(std::time::Duration::from_millis(step_ms));
|
std::thread::sleep(step_duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +295,7 @@ impl FrontendInterface for App {
|
||||||
|
|
||||||
// Resize OpenGL resources if we need to.
|
// Resize OpenGL resources if we need to.
|
||||||
if self.hw_render {
|
if self.hw_render {
|
||||||
self.hw_gl_resize(width, height);
|
self.hw_gl_create_fbo(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rfb_server.resize(width as u16, height as u16);
|
self.rfb_server.resize(width as u16, height as u16);
|
||||||
|
@ -274,19 +349,9 @@ impl FrontendInterface for App {
|
||||||
panic!("Cannot initalize HW rendering more than once");
|
panic!("Cannot initalize HW rendering more than once");
|
||||||
}
|
}
|
||||||
|
|
||||||
let display = unsafe {
|
unsafe {
|
||||||
const NR_DEVICES_MAX: usize = 16;
|
// Initalize EGL
|
||||||
let mut devices: [egl::types::EGLDeviceEXT; NR_DEVICES_MAX] =
|
self.hw_gl_egl_init();
|
||||||
[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)
|
// load OpenGL functions (using EGL loader. We should probably check the one extension exists)
|
||||||
gl::load_with(|s| {
|
gl::load_with(|s| {
|
||||||
|
@ -294,66 +359,13 @@ impl FrontendInterface for App {
|
||||||
std::mem::transmute(egl::GetProcAddress(str.as_ptr()))
|
std::mem::transmute(egl::GetProcAddress(str.as_ptr()))
|
||||||
});
|
});
|
||||||
|
|
||||||
(query_devices_ext)(
|
// set OpenGL debug message callback
|
||||||
NR_DEVICES_MAX as i32,
|
|
||||||
devices.as_mut_ptr(),
|
|
||||||
std::ptr::addr_of_mut!(nr_devices_real),
|
|
||||||
);
|
|
||||||
|
|
||||||
(get_platform_display_ext)(egl::PLATFORM_DEVICE_EXT, devices[0], std::ptr::null())
|
|
||||||
};
|
|
||||||
|
|
||||||
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::Enable(gl::DEBUG_OUTPUT);
|
||||||
gl::DebugMessageCallback(Some(opengl_message_callback), std::ptr::null());
|
gl::DebugMessageCallback(Some(opengl_message_callback), std::ptr::null());
|
||||||
|
|
||||||
// Resize to initial dimensions (this will create an FBO as well)
|
// Create the initial FBO for the core to render to
|
||||||
let dimensions = self.get_frontend().get_size();
|
let dimensions = self.get_frontend().get_size();
|
||||||
self.hw_gl_resize(dimensions.0, dimensions.1);
|
self.hw_gl_create_fbo(dimensions.0, dimensions.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.hw_render = true;
|
self.hw_render = true;
|
||||||
|
|
Loading…
Reference in a new issue