use super::drm_bindings::{DrmConnector, DrmModeInfo, DrmResources};
use super::drm::DrmModeConnection;
use crate::DrmOutputOptions;
use ferroui_base::platform::SurfaceOrientation;
use ferroui_base::PixelSize;
use std::io;
#[cfg(target_os = "linux")]
pub use imp::DrmOutput;
pub(crate) fn select_connector<'a>(
connectors: &'a [DrmConnector],
options: Option<&DrmOutputOptions>,
) -> Option<&'a DrmConnector> {
connectors
.iter()
.filter(|c| options.and_then(|options| options.connector_type).is_none_or(|t| c.connector_type() == t))
.filter(|c| options.and_then(|options| options.connector_type_id).is_none_or(|id| c.connector_type_id() == id))
.find(|x| x.connection() == DrmModeConnection::DRM_MODE_CONNECTED)
}
pub(crate) fn select_mode<'a>(connector: &'a DrmConnector, options: Option<&DrmOutputOptions>) -> Option<&'a DrmModeInfo> {
let mut mode = None;
if let Some(video_mode) = options.and_then(|options| options.video_mode) {
mode = connector
.modes()
.iter()
.find(|x| x.resolution().width == video_mode.width && x.resolution().height == video_mode.height);
}
mode.or_else(|| {
let mut modes: Vec<&DrmModeInfo> = connector.modes().iter().collect();
modes.sort_by(|a, b| {
let area = |x: &DrmModeInfo| x.resolution().width as i64 * x.resolution().height as i64;
b.is_preferred().cmp(&a.is_preferred()).then_with(|| area(b).cmp(&area(a)))
});
modes.first().copied()
})
}
pub(crate) fn get_crtc(resources: &DrmResources, connector: &DrmConnector) -> io::Result<u32> {
if let Some(encoder) = resources.encoders().get(&connector.encoder_id()) {
return Ok(encoder.encoder().crtc_id);
}
for enc_id in connector.encoder_ids() {
if let Some(encoder) = resources.encoders().get(enc_id) {
if let Some(crtc) = encoder.possible_crtcs().first() {
return Ok(crtc.crtc_id);
}
}
}
Err(io::Error::other("Unable to find CRTC matching the desired mode"))
}
pub(crate) fn rotated_size(orientation: SurfaceOrientation, resolution: PixelSize) -> PixelSize {
if orientation == SurfaceOrientation::Rotation90 || orientation == SurfaceOrientation::Rotation270 {
PixelSize::new(resolution.height, resolution.width)
} else {
resolution
}
}
pub(crate) fn rotation_vertices(orientation: SurfaceOrientation) -> [f32; 16] {
#[rustfmt::skip]
let vertices = match orientation {
SurfaceOrientation::Rotation90 => [
-1.0, -1.0, 1.0, 0.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, -1.0, 1.0, 0.0, 0.0, ],
SurfaceOrientation::Rotation180 => [
-1.0, -1.0, 1.0, 1.0, 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, -1.0, 1.0, 1.0, 0.0, ],
SurfaceOrientation::Rotation270 => [
-1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, -1.0, 1.0, 1.0, 1.0, ],
SurfaceOrientation::Rotation0 => [
-1.0, -1.0, 0.0, 0.0,
1.0, -1.0, 1.0, 0.0,
1.0, 1.0, 1.0, 1.0,
-1.0, 1.0, 0.0, 1.0,
],
};
vertices
}
#[cfg(target_os = "linux")]
mod imp {
use super::super::drm::lib_drm::{
drmModeModeInfo, gbm_color_formats, DrmEventContext, DrmModePageFlip, GbmBoFlags, LibDrm,
};
use super::super::drm_bindings::{DrmCard, DrmConnector, DrmModeInfo, DrmResources};
use super::super::{IGlOutputBackend, IOutputBackend};
use super::{get_crtc, rotated_size, rotation_vertices, select_connector, select_mode};
use crate::DrmOutputOptions;
use ferroui_base::platform::surfaces::{IPlatformRenderSurface, IPlatformRenderSurfaceRenderTarget};
use ferroui_base::platform::{
IPlatformGraphics, IPlatformGraphicsContext, ISurfaceOrientation, PlatformRenderTargetState,
RenderTargetSceneInfo, SurfaceOrientation,
};
use ferroui_base::reactive::IDisposable;
use ferroui_base::rendering::composition::CompositionTransparencyLevel;
use ferroui_base::utilities::ThreadBound;
use ferroui_base::PixelSize;
use ferroui_opengl::egl::egl_consts::EGL_NONE;
use ferroui_opengl::egl::{EglContext, EglDisplay, EglDisplayCreationOptions, EglDisplayOptions, EglInterface, EglSurface};
use ferroui_opengl::gl_consts::*;
use ferroui_opengl::surfaces::{IGlPlatformSurface, IGlPlatformSurfaceRenderTarget, IGlPlatformSurfaceRenderingSession};
use ferroui_opengl::{GetProcAddress, IGlContext, OpenGlException};
use std::any::{Any, TypeId};
use std::cell::Cell;
use std::ffi::{c_char, c_int, c_uint, c_void, CString};
use std::io;
use std::rc::Rc;
use std::sync::{Arc, Mutex, OnceLock, PoisonError, Weak};
const EGL_PLATFORM_GBM_KHR: i32 = 0x31D7;
#[derive(Clone, Copy, Debug)]
struct GbmHandle(*mut c_void);
unsafe impl Send for GbmHandle {}
unsafe impl Sync for GbmHandle {}
type EglGetProcAddress = unsafe extern "C" fn(name: *const c_char) -> *const c_void;
#[derive(Clone, Copy)]
struct EglLibrary {
library: usize,
get_proc_address: EglGetProcAddress,
}
fn egl_library() -> io::Result<EglLibrary> {
static LIBRARY: OnceLock<Option<EglLibrary>> = OnceLock::new();
LIBRARY
.get_or_init(|| {
unsafe {
let library = libc::dlopen(c"libEGL.so.1".as_ptr(), libc::RTLD_NOW | libc::RTLD_LOCAL);
if library.is_null() {
return None;
}
let get_proc_address = libc::dlsym(library, c"eglGetProcAddress".as_ptr());
if get_proc_address.is_null() {
return None;
}
Some(EglLibrary {
library: library as usize,
get_proc_address: std::mem::transmute::<*mut c_void, EglGetProcAddress>(get_proc_address),
})
}
})
.ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, "Unable to load shared library 'libEGL.so.1' or one of its dependencies.")
})
}
fn egl_get_proc_address() -> io::Result<GetProcAddress> {
let library = egl_library()?;
Ok(Rc::new(move |name: &str| -> *const c_void {
let Ok(name) = CString::new(name) else {
return std::ptr::null();
};
unsafe {
let address = libc::dlsym(library.library as *mut c_void, name.as_ptr());
if !address.is_null() {
return address.cast_const();
}
(library.get_proc_address)(name.as_ptr())
}
}))
}
struct FbUserData {
output: Weak<Inner>,
fb_id: u32,
}
unsafe extern "C" fn fb_destroy_callback(_bo: *mut c_void, user_data: *mut c_void) {
let data = unsafe { Box::from_raw(user_data.cast::<FbUserData>()) };
if let Some(output) = data.output.upgrade() {
unsafe {
output.lib.drm_mode_rm_fb(output.card.fd(), data.fb_id);
}
}
}
unsafe extern "C" fn flip_cb(_fd: c_int, _sequence: c_uint, _tv_sec: c_uint, _tv_usec: c_uint, user_data: *mut c_void) {
unsafe { (*user_data.cast::<Cell<bool>>()).set(false) };
}
struct Inner {
this: Weak<Inner>,
output_options: Mutex<DrmOutputOptions>,
card: DrmCard,
lib: &'static LibDrm,
mode: drmModeModeInfo,
connector_id: u32,
crtc_id: u32,
gbm_device: GbmHandle,
gbm_target_surface: GbmHandle,
egl: OnceLock<ThreadBound<Rc<EglState>>>,
}
#[allow(dead_code)]
struct EglState {
egl_display: Rc<EglDisplay>,
egl_surface: Rc<EglSurface>,
deferred_context: Rc<EglContext>,
current_bo: Cell<*mut c_void>,
rotation_fbo: i32,
rotation_texture: i32,
rotated_size: PixelSize,
rotation_program: i32,
rotation_vbo: i32,
rotation_vao: i32,
}
impl Inner {
fn options(&self) -> std::sync::MutexGuard<'_, DrmOutputOptions> {
self.output_options.lock().unwrap_or_else(PoisonError::into_inner)
}
fn orientation(&self) -> SurfaceOrientation {
self.options().orientation
}
fn scaling(&self) -> f64 {
self.options().scaling
}
fn resolution(&self) -> PixelSize {
self.mode.resolution()
}
fn get_fb_id_for_bo(&self, bo: *mut c_void) -> io::Result<u32> {
if bo.is_null() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "bo is 0"));
}
let gbm = self.lib;
let drm = self.lib;
unsafe {
let data = gbm.gbm_bo_get_user_data(bo);
if !data.is_null() {
return Ok((*data.cast::<FbUserData>()).fb_id);
}
let w = gbm.gbm_bo_get_width(bo);
let h = gbm.gbm_bo_get_height(bo);
let stride = gbm.gbm_bo_get_stride(bo);
let handle = gbm.gbm_bo_get_handle(bo).u32;
let format = gbm.gbm_bo_get_format(bo);
let handles = [handle, 0, 0, 0];
let pitches = [stride, 0, 0, 0];
let offsets = [0u32; 4];
let mut fb_handle = 0u32;
let mut ret = drm.drm_mode_add_fb2(
self.card.fd(),
w,
h,
format,
&handles,
&pitches,
&offsets,
&mut fb_handle,
0,
);
if ret != 0 {
ret = drm.drm_mode_add_fb(self.card.fd(), w, h, 24, 32, stride, handle, &mut fb_handle);
if ret != 0 {
return Err(io::Error::other(format!("drmModeAddFb failed {ret}")));
}
}
let data = Box::into_raw(Box::new(FbUserData { output: self.this.clone(), fb_id: fb_handle }));
gbm.gbm_bo_set_user_data(bo, data.cast(), Some(fb_destroy_callback));
Ok(fb_handle)
}
}
fn egl_state(&self) -> Rc<EglState> {
if let Some(state) = self.egl.get() {
return state.get().clone();
}
let state = match self.init_egl() {
Ok(state) => state,
Err(error) => panic!("{error}"),
};
if self.egl.set(ThreadBound::new(state.clone())).is_err() {
panic!("The EGL objects of the DRM output were created by two threads");
}
state
}
fn init_egl(&self) -> Result<Rc<EglState>, OpenGlException> {
let get_proc_address = egl_get_proc_address().map_err(|error| OpenGlException::new(error.to_string()))?;
let egl = Rc::new(unsafe { EglInterface::new(&get_proc_address) });
let egl_display = EglDisplay::new_with_creation_options(EglDisplayCreationOptions {
base: EglDisplayOptions {
egl: Some(egl),
supports_multiple_contexts: true,
supports_context_sharing: true,
..Default::default()
},
platform_type: Some(EGL_PLATFORM_GBM_KHR),
platform_display: self.gbm_device.0 as isize,
..Default::default()
})?;
let surface = egl_display.egl_interface().create_window_surface(
egl_display.handle(),
egl_display.config(),
self.gbm_target_surface.0 as isize,
Some(&[EGL_NONE, EGL_NONE]),
);
if surface == 0 {
return Err(OpenGlException::get_formatted_exception_for_egl(
"eglCreateWindowSurface",
egl_display.egl_interface(),
));
}
let egl_surface = EglSurface::new(&egl_display, surface);
let deferred_context = egl_display.create_context(None)?;
let options = self.options().clone();
let color = options.initial_buffer_swapping_color;
let initial_buffer_swapping_color_r = color.r as f32 / 255.0;
let initial_buffer_swapping_color_g = color.g as f32 / 255.0;
let initial_buffer_swapping_color_b = color.b as f32 / 255.0;
let initial_buffer_swapping_color_a = color.a as f32 / 255.0;
let make_current = || {
deferred_context
.make_current_with_surface(Some(&egl_surface))
.map_err(|error| OpenGlException::new(error.to_string()))
};
{
let current = make_current()?;
let gl = deferred_context.gl_interface();
gl.clear_color(
initial_buffer_swapping_color_r,
initial_buffer_swapping_color_g,
initial_buffer_swapping_color_b,
initial_buffer_swapping_color_a,
);
gl.clear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
egl_surface.swap_buffers();
current.dispose();
}
let bo = unsafe { self.lib.gbm_surface_lock_front_buffer(self.gbm_target_surface.0) };
let fb_id = self.get_fb_id_for_bo(bo).map_err(|error| OpenGlException::new(error.to_string()))?;
let mut connector_id = self.connector_id;
let mut mode = self.mode;
let res =
unsafe { self.lib.drm_mode_set_crtc(self.card.fd(), self.crtc_id, fb_id, 0, 0, &mut connector_id, 1, &mut mode) };
if res != 0 {
return Err(OpenGlException::new(format!(
"drmModeSetCrtc failed: {} ({res})",
io::Error::last_os_error()
)));
}
let current_bo = Cell::new(bo);
let needs_rotation = options.orientation != SurfaceOrientation::Rotation0;
let (mut rotation_fbo, mut rotation_texture, mut rotation_program, mut rotation_vbo, mut rotation_vao) =
(0, 0, 0, 0, 0);
let rotated_size = if needs_rotation {
let rotated = rotated_size(options.orientation, self.resolution());
let current = make_current()?;
let gl = deferred_context.gl_interface();
rotation_fbo = gl.gen_framebuffer();
rotation_texture = gl.gen_texture();
gl.bind_texture(GL_TEXTURE_2D, rotation_texture);
unsafe {
gl.tex_image_2d(
GL_TEXTURE_2D,
0,
GL_RGBA,
rotated.width,
rotated.height,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
std::ptr::null(),
);
}
gl.tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl.tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl.bind_framebuffer(GL_FRAMEBUFFER, rotation_fbo);
gl.framebuffer_texture_2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rotation_texture, 0);
gl.bind_framebuffer(GL_FRAMEBUFFER, 0);
const VERTEX_SHADER: &str = "
attribute vec2 aPos;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
vTexCoord = aTexCoord;
}";
const FRAGMENT_SHADER: &str = "
precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D uTexture;
void main() {
gl_FragColor = texture2D(uTexture, vTexCoord);
}";
let vs = gl.create_shader(GL_VERTEX_SHADER);
gl.shader_source_string(vs, VERTEX_SHADER);
gl.compile_shader(vs);
let fs = gl.create_shader(GL_FRAGMENT_SHADER);
gl.shader_source_string(fs, FRAGMENT_SHADER);
gl.compile_shader(fs);
rotation_program = gl.create_program();
gl.attach_shader(rotation_program, vs);
gl.attach_shader(rotation_program, fs);
gl.link_program(rotation_program);
gl.delete_shader(vs);
gl.delete_shader(fs);
let vertices = rotation_vertices(options.orientation);
rotation_vbo = gl.gen_buffer();
rotation_vao = gl.gen_vertex_array();
gl.bind_vertex_array(rotation_vao);
gl.bind_buffer(GL_ARRAY_BUFFER, rotation_vbo);
let float = std::mem::size_of::<f32>() as i32;
unsafe {
gl.buffer_data(
GL_ARRAY_BUFFER,
std::mem::size_of_val(&vertices) as isize,
vertices.as_ptr().cast(),
GL_STATIC_DRAW,
);
let pos_attrib = gl.get_attrib_location_string(rotation_program, "aPos");
gl.enable_vertex_attrib_array(pos_attrib);
gl.vertex_attrib_pointer(pos_attrib, 2, GL_FLOAT, 0, 4 * float, std::ptr::null());
let tex_attrib = gl.get_attrib_location_string(rotation_program, "aTexCoord");
gl.enable_vertex_attrib_array(tex_attrib);
gl.vertex_attrib_pointer(tex_attrib, 2, GL_FLOAT, 0, 4 * float, (2 * float) as usize as *const c_void);
}
gl.bind_vertex_array(0);
current.dispose();
rotated
} else {
self.resolution()
};
let state = Rc::new(EglState {
egl_display,
egl_surface,
deferred_context,
current_bo,
rotation_fbo,
rotation_texture,
rotated_size,
rotation_program,
rotation_vbo,
rotation_vao,
});
if options.enable_initial_buffer_swapping {
let scene_info =
RenderTargetSceneInfo::new(self.pixel_size(), 1.0, CompositionTransparencyLevel::None);
for _ in 0..2 {
let target = RenderTarget { parent: self.this(), egl: state.clone() };
let session = target.begin_draw(&scene_info);
let gl = state.deferred_context.gl_interface();
gl.clear_color(
initial_buffer_swapping_color_r,
initial_buffer_swapping_color_g,
initial_buffer_swapping_color_b,
initial_buffer_swapping_color_a,
);
gl.clear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
session.dispose();
}
}
Ok(state)
}
fn this(&self) -> Arc<Inner> {
self.this.upgrade().expect("an output that is called is alive")
}
fn pixel_size(&self) -> PixelSize {
let resolution = self.resolution();
let orientation = self.orientation();
if orientation == SurfaceOrientation::Rotation0 || orientation == SurfaceOrientation::Rotation180 {
PixelSize::new(resolution.width, resolution.height)
} else {
PixelSize::new(resolution.height, resolution.width)
}
}
}
struct SharedContextGraphics {
inner: Arc<Inner>,
}
impl IPlatformGraphics for SharedContextGraphics {
fn uses_shared_context(&self) -> bool {
true
}
fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
panic!("Specified method is not supported.")
}
fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
self.inner.egl_state().deferred_context.clone()
}
}
pub struct DrmOutput {
inner: Arc<Inner>,
platform_graphics: Arc<SharedContextGraphics>,
}
impl DrmOutput {
pub fn new_with_mode_and_options(
card: DrmCard,
resources: &DrmResources,
connector: &DrmConnector,
mode_info: &DrmModeInfo,
options: Option<DrmOutputOptions>,
) -> io::Result<Arc<Self>> {
Self::init(card, resources, connector, mode_info, options.unwrap_or_default())
}
pub fn new(path: Option<&str>, connectors_force_probe: bool, options: Option<DrmOutputOptions>) -> io::Result<Arc<Self>> {
Self::from_card(DrmCard::new(path)?, connectors_force_probe, options)
}
pub fn from_card(card: DrmCard, connectors_force_probe: bool, options: Option<DrmOutputOptions>) -> io::Result<Arc<Self>> {
let resources = card.get_resources(connectors_force_probe)?;
let Some(connector) = select_connector(resources.connectors(), options.as_ref()) else {
return Err(io::Error::new(io::ErrorKind::NotFound, "Unable to find connected DRM connector"));
};
let Some(mode) = select_mode(connector, options.as_ref()) else {
return Err(io::Error::new(io::ErrorKind::NotFound, "Unable to find a usable DRM mode"));
};
Self::init(card, &resources, connector, mode, options.unwrap_or_default())
}
pub fn new_with_mode(
card: DrmCard,
resources: &DrmResources,
connector: &DrmConnector,
mode_info: &DrmModeInfo,
) -> io::Result<Arc<Self>> {
Self::init(card, resources, connector, mode_info, DrmOutputOptions::default())
}
fn init(
card: DrmCard,
resources: &DrmResources,
connector: &DrmConnector,
mode_info: &DrmModeInfo,
options: DrmOutputOptions,
) -> io::Result<Arc<Self>> {
let lib = LibDrm::get()?;
egl_library()?;
let crtc_id = get_crtc(resources, connector)?;
let device = unsafe { lib.gbm_create_device(card.fd()) };
if device.is_null() {
return Err(io::Error::other("Unable to create GBM device"));
}
let resolution = mode_info.resolution();
let gbm_target_surface = unsafe {
lib.gbm_surface_create(
device,
resolution.width as u32,
resolution.height as u32,
gbm_color_formats::GBM_FORMAT_XRGB8888,
GbmBoFlags::GBM_BO_USE_SCANOUT | GbmBoFlags::GBM_BO_USE_RENDERING,
)
};
if gbm_target_surface.is_null() {
return Err(io::Error::other("Unable to create GBM surface"));
}
let inner = Arc::new_cyclic(|this| Inner {
this: this.clone(),
output_options: Mutex::new(options),
card,
lib,
mode: mode_info.mode,
connector_id: connector.id(),
crtc_id,
gbm_device: GbmHandle(device),
gbm_target_surface: GbmHandle(gbm_target_surface),
egl: OnceLock::new(),
});
let platform_graphics = Arc::new(SharedContextGraphics { inner: inner.clone() });
Ok(Arc::new(Self { inner, platform_graphics }))
}
pub fn set_orientation(&self, value: SurfaceOrientation) {
self.inner.options().orientation = value;
}
pub fn card(&self) -> &DrmCard {
&self.inner.card
}
pub fn crtc_id(&self) -> u32 {
self.inner.crtc_id
}
pub fn create_gl_render_target_of_primary_context(&self) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
Rc::new(RenderTarget { parent: self.inner.clone(), egl: self.inner.egl_state() })
}
pub fn create_context(&self) -> Rc<dyn IGlContext> {
panic!("The method or operation is not implemented.")
}
}
impl IPlatformRenderSurface for DrmOutput {
fn try_get_surface_kind(&self, kind: TypeId) -> Option<Rc<dyn Any>> {
if kind == TypeId::of::<dyn IGlPlatformSurface>() {
let this: Rc<dyn IGlPlatformSurface> =
Rc::new(Self { inner: self.inner.clone(), platform_graphics: self.platform_graphics.clone() });
return Some(Rc::new(this));
}
None
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl IGlPlatformSurface for DrmOutput {
fn create_gl_render_target(&self, context: &Rc<dyn IGlContext>) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
let egl = self.inner.egl_state();
let is_primary = context
.as_any()
.downcast_ref::<EglContext>()
.is_some_and(|context| std::ptr::eq(context, Rc::as_ptr(&egl.deferred_context)));
if !is_primary {
panic!("This platform backend can only create render targets for its primary context");
}
self.create_gl_render_target_of_primary_context()
}
}
impl IOutputBackend for DrmOutput {
fn pixel_size(&self) -> PixelSize {
self.inner.pixel_size()
}
fn scaling(&self) -> f64 {
self.inner.scaling()
}
fn set_scaling(&self, value: f64) {
self.inner.options().scaling = value;
}
fn as_render_surface(self: Arc<Self>) -> Arc<dyn IPlatformRenderSurface> {
self
}
fn as_gl_output_backend(&self) -> Option<&dyn IGlOutputBackend> {
Some(self)
}
fn as_surface_orientation(&self) -> Option<&dyn ISurfaceOrientation> {
Some(self)
}
}
impl IGlOutputBackend for DrmOutput {
fn platform_graphics(&self) -> Arc<dyn IPlatformGraphics> {
self.platform_graphics.clone()
}
}
impl ISurfaceOrientation for DrmOutput {
fn orientation(&self) -> SurfaceOrientation {
self.inner.orientation()
}
}
struct RenderTarget {
parent: Arc<Inner>,
egl: Rc<EglState>,
}
impl IPlatformRenderSurfaceRenderTarget for RenderTarget {
fn state(&self) -> PlatformRenderTargetState {
PlatformRenderTargetState::READY
}
}
impl IGlPlatformSurfaceRenderTarget for RenderTarget {
fn begin_draw(&self, _scene_info: &RenderTargetSceneInfo) -> Rc<dyn IGlPlatformSurfaceRenderingSession> {
let clear_context = match self.egl.deferred_context.make_current_with_surface(Some(&self.egl.egl_surface)) {
Ok(clear_context) => clear_context,
Err(error) => panic!("{error}"),
};
let gl = self.egl.deferred_context.gl_interface();
if self.parent.orientation() != SurfaceOrientation::Rotation0 {
gl.bind_framebuffer(GL_FRAMEBUFFER, self.egl.rotation_fbo);
gl.viewport(0, 0, self.egl.rotated_size.width, self.egl.rotated_size.height);
} else {
gl.bind_framebuffer(GL_FRAMEBUFFER, 0);
let resolution = self.parent.resolution();
gl.viewport(0, 0, resolution.width, resolution.height);
}
Rc::new(RenderSession {
parent: self.parent.clone(),
egl: self.egl.clone(),
clear_context,
disposed: Cell::new(false),
})
}
fn dispose(&self) {
}
}
struct RenderSession {
parent: Arc<Inner>,
egl: Rc<EglState>,
clear_context: Rc<dyn IDisposable>,
disposed: Cell<bool>,
}
impl IGlPlatformSurfaceRenderingSession for RenderSession {
fn context(&self) -> Rc<dyn IGlContext> {
self.egl.deferred_context.clone()
}
fn size(&self) -> PixelSize {
self.egl.rotated_size
}
fn scaling(&self) -> f64 {
self.parent.scaling()
}
fn is_y_flipped(&self) -> bool {
false
}
fn dispose(&self) {
if self.disposed.replace(true) {
return;
}
let parent = &self.parent;
let egl = &self.egl;
let gl = egl.deferred_context.gl_interface();
if parent.orientation() != SurfaceOrientation::Rotation0 {
gl.bind_framebuffer(GL_FRAMEBUFFER, 0);
let resolution = parent.resolution();
gl.viewport(0, 0, resolution.width, resolution.height);
gl.clear_color(0.0, 0.0, 0.0, 1.0);
gl.clear(GL_COLOR_BUFFER_BIT);
gl.use_program(egl.rotation_program);
gl.active_texture(GL_TEXTURE0);
gl.bind_texture(GL_TEXTURE_2D, egl.rotation_texture);
let tex_loc = gl.get_uniform_location_string(egl.rotation_program, "uTexture");
gl.uniform1i(tex_loc, 0);
gl.bind_vertex_array(egl.rotation_vao);
unsafe { gl.draw_arrays(GL_TRIANGLE_FAN, 0, 4) };
gl.bind_vertex_array(0);
gl.use_program(0);
}
gl.flush();
egl.egl_surface.swap_buffers();
let lib = parent.lib;
let surface = parent.gbm_target_surface.0;
let next_bo = unsafe { lib.gbm_surface_lock_front_buffer(surface) };
if next_bo.is_null() {
println!("gbm_surface_lock_front_buffer failed");
} else {
let fb = match parent.get_fb_id_for_bo(next_bo) {
Ok(fb) => fb,
Err(error) => panic!("{error}"),
};
let waiting_for_flip = Cell::new(true);
let fd = parent.card.fd();
let ret = unsafe {
lib.drm_mode_page_flip(
fd,
parent.crtc_id,
fb,
DrmModePageFlip::EVENT,
(&waiting_for_flip as *const Cell<bool>).cast_mut().cast(),
)
};
if ret != 0 {
println!("drmModePageFlip failed {ret}");
unsafe { lib.gbm_surface_release_buffer(surface, next_bo) };
} else {
let mut ctx = DrmEventContext {
version: 4,
page_flip_handler: Some(flip_cb),
..Default::default()
};
while waiting_for_flip.get() {
let mut pfd = libc::pollfd { events: libc::POLLIN, fd, revents: 0 };
unsafe {
libc::poll(&mut pfd, 1, -1);
lib.drm_handle_event(fd, &mut ctx);
}
}
unsafe { lib.gbm_surface_release_buffer(surface, egl.current_bo.get()) };
egl.current_bo.set(next_bo);
}
}
self.clear_context.dispose();
}
}
}
#[cfg(test)]
mod tests {
use super::super::drm::lib_drm::{drmModeCrtc, drmModeEncoder};
use super::super::drm_bindings::tests::{connector, mode};
use super::super::drm_bindings::DrmEncoder;
use super::*;
use crate::DrmConnectorType;
use std::collections::HashMap;
const CONNECTED: DrmModeConnection = DrmModeConnection::DRM_MODE_CONNECTED;
const DISCONNECTED: DrmModeConnection = DrmModeConnection::DRM_MODE_DISCONNECTED;
fn connectors() -> Vec<DrmConnector> {
vec![
connector(30, 11, 1, DISCONNECTED, 0, &[50], &[mode(1920, 1080, true)]),
connector(31, 11, 2, CONNECTED, 0, &[50], &[mode(1280, 720, true), mode(1920, 1080, false)]),
connector(32, 10, 1, CONNECTED, 51, &[51], &[mode(800, 600, false), mode(1024, 768, false), mode(640, 480, false)]),
]
}
#[test]
fn the_first_connected_connector_is_chosen_unless_the_options_name_a_type() {
let connectors = connectors();
assert_eq!(select_connector(&connectors, None).map(DrmConnector::id), Some(31));
let display_port = DrmOutputOptions { connector_type: Some(DrmConnectorType::DISPLAY_PORT), ..Default::default() };
assert_eq!(select_connector(&connectors, Some(&display_port)).map(DrmConnector::id), Some(32));
let second_hdmi = DrmOutputOptions {
connector_type: Some(DrmConnectorType::HDMI_A),
connector_type_id: Some(2),
..Default::default()
};
assert_eq!(select_connector(&connectors, Some(&second_hdmi)).map(DrmConnector::id), Some(31));
let first_hdmi = DrmOutputOptions { connector_type_id: Some(1), connector_type: Some(DrmConnectorType::HDMI_A), ..Default::default() };
assert!(select_connector(&connectors, Some(&first_hdmi)).is_none());
let vga = DrmOutputOptions { connector_type: Some(DrmConnectorType::VGA), ..Default::default() };
assert!(select_connector(&connectors, Some(&vga)).is_none());
}
#[test]
fn the_preferred_mode_wins_then_the_largest_then_the_first() {
let connectors = connectors();
assert_eq!(select_mode(&connectors[1], None).map(DrmModeInfo::resolution), Some(PixelSize::new(1280, 720)));
assert_eq!(select_mode(&connectors[2], None).map(DrmModeInfo::resolution), Some(PixelSize::new(1024, 768)));
let mut first = mode(1024, 768, false);
first.vrefresh = 60;
let mut second = mode(1024, 768, false);
second.vrefresh = 75;
let same = connector(40, 15, 1, CONNECTED, 0, &[], &[mode(800, 600, false), first, second]);
assert_eq!(select_mode(&same, None).map(|m| m.mode.vrefresh), Some(60));
let empty = connector(41, 15, 1, CONNECTED, 0, &[], &[]);
assert!(select_mode(&empty, None).is_none());
}
#[test]
fn a_video_mode_of_the_options_is_used_when_the_connector_has_it() {
let connectors = connectors();
let full_hd = DrmOutputOptions { video_mode: Some(PixelSize::new(1920, 1080)), ..Default::default() };
assert_eq!(select_mode(&connectors[1], Some(&full_hd)).map(DrmModeInfo::resolution), Some(PixelSize::new(1920, 1080)));
let four_k = DrmOutputOptions { video_mode: Some(PixelSize::new(3840, 2160)), ..Default::default() };
assert_eq!(select_mode(&connectors[1], Some(&four_k)).map(DrmModeInfo::resolution), Some(PixelSize::new(1280, 720)));
}
#[test]
fn the_crtc_is_the_current_encoders_or_the_first_possible_one() {
let crtcs: Vec<drmModeCrtc> = (0..2).map(|c| drmModeCrtc { crtc_id: 60 + c, ..Default::default() }).collect();
let mut encoders = HashMap::new();
encoders.insert(
50,
DrmEncoder::new(drmModeEncoder { encoder_id: 50, crtc_id: 0, possible_crtcs: 0b10, ..Default::default() }, &crtcs),
);
encoders.insert(
51,
DrmEncoder::new(drmModeEncoder { encoder_id: 51, crtc_id: 60, possible_crtcs: 0b01, ..Default::default() }, &crtcs),
);
let resources = DrmResources::from_parts(connectors(), encoders);
assert_eq!(get_crtc(&resources, &resources.connectors()[2]).unwrap(), 60);
assert_eq!(get_crtc(&resources, &resources.connectors()[1]).unwrap(), 61);
let lonely = connector(42, 15, 1, CONNECTED, 0, &[99], &[]);
assert_eq!(
get_crtc(&resources, &lonely).unwrap_err().to_string(),
"Unable to find CRTC matching the desired mode"
);
}
#[test]
fn a_quarter_turn_exchanges_the_sides_and_the_quad_maps_the_corners() {
let size = PixelSize::new(1024, 768);
assert_eq!(rotated_size(SurfaceOrientation::Rotation0, size), size);
assert_eq!(rotated_size(SurfaceOrientation::Rotation180, size), size);
assert_eq!(rotated_size(SurfaceOrientation::Rotation90, size), PixelSize::new(768, 1024));
assert_eq!(rotated_size(SurfaceOrientation::Rotation270, size), PixelSize::new(768, 1024));
let corner = |o| {
let v = rotation_vertices(o);
(v[2], v[3])
};
assert_eq!(corner(SurfaceOrientation::Rotation0), (0.0, 0.0));
assert_eq!(corner(SurfaceOrientation::Rotation90), (1.0, 0.0));
assert_eq!(corner(SurfaceOrientation::Rotation180), (1.0, 1.0));
assert_eq!(corner(SurfaceOrientation::Rotation270), (0.0, 1.0));
}
#[test]
fn the_top_left_corner_of_the_scene_is_turned_clockwise() {
let screen_corner = |o| {
let v = rotation_vertices(o);
let i = (0..4).find(|i| v[i * 4 + 2] == 0.0 && v[i * 4 + 3] == 1.0).unwrap();
(v[i * 4], v[i * 4 + 1])
};
assert_eq!(screen_corner(SurfaceOrientation::Rotation0), (-1.0, 1.0)); assert_eq!(screen_corner(SurfaceOrientation::Rotation90), (1.0, 1.0)); assert_eq!(screen_corner(SurfaceOrientation::Rotation180), (1.0, -1.0)); assert_eq!(screen_corner(SurfaceOrientation::Rotation270), (-1.0, -1.0)); }
}