1use super::drm_bindings::{DrmConnector, DrmModeInfo, DrmResources};
20use super::drm::DrmModeConnection;
21use crate::DrmOutputOptions;
22use ferroui_base::platform::SurfaceOrientation;
23use ferroui_base::PixelSize;
24use std::io;
25
26#[cfg(target_os = "linux")]
27pub use imp::DrmOutput;
28
29pub(crate) fn select_connector<'a>(
33 connectors: &'a [DrmConnector],
34 options: Option<&DrmOutputOptions>,
35) -> Option<&'a DrmConnector> {
36 connectors
37 .iter()
38 .filter(|c| options.and_then(|options| options.connector_type).is_none_or(|t| c.connector_type() == t))
39 .filter(|c| options.and_then(|options| options.connector_type_id).is_none_or(|id| c.connector_type_id() == id))
40 .find(|x| x.connection() == DrmModeConnection::DRM_MODE_CONNECTED)
41}
42
43pub(crate) fn select_mode<'a>(connector: &'a DrmConnector, options: Option<&DrmOutputOptions>) -> Option<&'a DrmModeInfo> {
48 let mut mode = None;
49
50 if let Some(video_mode) = options.and_then(|options| options.video_mode) {
51 mode = connector
52 .modes()
53 .iter()
54 .find(|x| x.resolution().width == video_mode.width && x.resolution().height == video_mode.height);
55 }
56
57 mode.or_else(|| {
58 let mut modes: Vec<&DrmModeInfo> = connector.modes().iter().collect();
59 modes.sort_by(|a, b| {
60 let area = |x: &DrmModeInfo| x.resolution().width as i64 * x.resolution().height as i64;
61 b.is_preferred().cmp(&a.is_preferred()).then_with(|| area(b).cmp(&area(a)))
62 });
63 modes.first().copied()
65 })
66}
67
68pub(crate) fn get_crtc(resources: &DrmResources, connector: &DrmConnector) -> io::Result<u32> {
72 if let Some(encoder) = resources.encoders().get(&connector.encoder_id()) {
73 return Ok(encoder.encoder().crtc_id);
75 }
76 for enc_id in connector.encoder_ids() {
77 if let Some(encoder) = resources.encoders().get(enc_id) {
78 if let Some(crtc) = encoder.possible_crtcs().first() {
79 return Ok(crtc.crtc_id);
80 }
81 }
82 }
83
84 Err(io::Error::other("Unable to find CRTC matching the desired mode"))
85}
86
87pub(crate) fn rotated_size(orientation: SurfaceOrientation, resolution: PixelSize) -> PixelSize {
90 if orientation == SurfaceOrientation::Rotation90 || orientation == SurfaceOrientation::Rotation270 {
91 PixelSize::new(resolution.height, resolution.width)
92 } else {
93 resolution
94 }
95}
96
97pub(crate) fn rotation_vertices(orientation: SurfaceOrientation) -> [f32; 16] {
100 #[rustfmt::skip]
101 let vertices = match orientation {
102 SurfaceOrientation::Rotation90 => [
103 -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, ],
109 SurfaceOrientation::Rotation180 => [
110 -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, ],
116 SurfaceOrientation::Rotation270 => [
117 -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, ],
123 SurfaceOrientation::Rotation0 => [
124 -1.0, -1.0, 0.0, 0.0,
126 1.0, -1.0, 1.0, 0.0,
127 1.0, 1.0, 1.0, 1.0,
128 -1.0, 1.0, 0.0, 1.0,
129 ],
130 };
131 vertices
132}
133
134#[cfg(target_os = "linux")]
135mod imp {
136 use super::super::drm::lib_drm::{
137 drmModeModeInfo, gbm_color_formats, DrmEventContext, DrmModePageFlip, GbmBoFlags, LibDrm,
138 };
139 use super::super::drm_bindings::{DrmCard, DrmConnector, DrmModeInfo, DrmResources};
140 use super::super::{IGlOutputBackend, IOutputBackend};
141 use super::{get_crtc, rotated_size, rotation_vertices, select_connector, select_mode};
142 use crate::DrmOutputOptions;
143 use ferroui_base::platform::surfaces::{IPlatformRenderSurface, IPlatformRenderSurfaceRenderTarget};
144 use ferroui_base::platform::{
145 IPlatformGraphics, IPlatformGraphicsContext, ISurfaceOrientation, PlatformRenderTargetState,
146 RenderTargetSceneInfo, SurfaceOrientation,
147 };
148 use ferroui_base::reactive::IDisposable;
149 use ferroui_base::rendering::composition::CompositionTransparencyLevel;
150 use ferroui_base::utilities::ThreadBound;
151 use ferroui_base::PixelSize;
152 use ferroui_opengl::egl::egl_consts::EGL_NONE;
153 use ferroui_opengl::egl::{EglContext, EglDisplay, EglDisplayCreationOptions, EglDisplayOptions, EglInterface, EglSurface};
154 use ferroui_opengl::gl_consts::*;
155 use ferroui_opengl::surfaces::{IGlPlatformSurface, IGlPlatformSurfaceRenderTarget, IGlPlatformSurfaceRenderingSession};
156 use ferroui_opengl::{GetProcAddress, IGlContext, OpenGlException};
157 use std::any::{Any, TypeId};
158 use std::cell::Cell;
159 use std::ffi::{c_char, c_int, c_uint, c_void, CString};
160 use std::io;
161 use std::rc::Rc;
162 use std::sync::{Arc, Mutex, OnceLock, PoisonError, Weak};
163
164 const EGL_PLATFORM_GBM_KHR: i32 = 0x31D7;
166
167 #[derive(Clone, Copy, Debug)]
170 struct GbmHandle(*mut c_void);
171
172 unsafe impl Send for GbmHandle {}
175 unsafe impl Sync for GbmHandle {}
177
178 type EglGetProcAddress = unsafe extern "C" fn(name: *const c_char) -> *const c_void;
179
180 #[derive(Clone, Copy)]
183 struct EglLibrary {
184 library: usize,
185 get_proc_address: EglGetProcAddress,
186 }
187
188 fn egl_library() -> io::Result<EglLibrary> {
189 static LIBRARY: OnceLock<Option<EglLibrary>> = OnceLock::new();
190 LIBRARY
191 .get_or_init(|| {
192 unsafe {
195 let library = libc::dlopen(c"libEGL.so.1".as_ptr(), libc::RTLD_NOW | libc::RTLD_LOCAL);
196 if library.is_null() {
197 return None;
198 }
199 let get_proc_address = libc::dlsym(library, c"eglGetProcAddress".as_ptr());
200 if get_proc_address.is_null() {
201 return None;
202 }
203 Some(EglLibrary {
204 library: library as usize,
205 get_proc_address: std::mem::transmute::<*mut c_void, EglGetProcAddress>(get_proc_address),
207 })
208 }
209 })
210 .ok_or_else(|| {
211 io::Error::new(io::ErrorKind::NotFound, "Unable to load shared library 'libEGL.so.1' or one of its dependencies.")
212 })
213 }
214
215 fn egl_get_proc_address() -> io::Result<GetProcAddress> {
220 let library = egl_library()?;
221 Ok(Rc::new(move |name: &str| -> *const c_void {
222 let Ok(name) = CString::new(name) else {
223 return std::ptr::null();
224 };
225 unsafe {
228 let address = libc::dlsym(library.library as *mut c_void, name.as_ptr());
229 if !address.is_null() {
230 return address.cast_const();
231 }
232 (library.get_proc_address)(name.as_ptr())
233 }
234 }))
235 }
236
237 struct FbUserData {
241 output: Weak<Inner>,
242 fb_id: u32,
243 }
244
245 unsafe extern "C" fn fb_destroy_callback(_bo: *mut c_void, user_data: *mut c_void) {
248 let data = unsafe { Box::from_raw(user_data.cast::<FbUserData>()) };
251 if let Some(output) = data.output.upgrade() {
252 unsafe {
254 output.lib.drm_mode_rm_fb(output.card.fd(), data.fb_id);
255 }
256 }
257 }
258
259 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) {
261 unsafe { (*user_data.cast::<Cell<bool>>()).set(false) };
264 }
265
266 struct Inner {
268 this: Weak<Inner>,
269 output_options: Mutex<DrmOutputOptions>,
270 card: DrmCard,
271 lib: &'static LibDrm,
272 mode: drmModeModeInfo,
273 connector_id: u32,
274 crtc_id: u32,
275 gbm_device: GbmHandle,
276 gbm_target_surface: GbmHandle,
277 egl: OnceLock<ThreadBound<Rc<EglState>>>,
279 }
280
281 #[allow(dead_code)]
285 struct EglState {
286 egl_display: Rc<EglDisplay>,
287 egl_surface: Rc<EglSurface>,
288 deferred_context: Rc<EglContext>,
289 current_bo: Cell<*mut c_void>,
290 rotation_fbo: i32,
291 rotation_texture: i32,
292 rotated_size: PixelSize,
293 rotation_program: i32,
294 rotation_vbo: i32,
295 rotation_vao: i32,
296 }
297
298 impl Inner {
299 fn options(&self) -> std::sync::MutexGuard<'_, DrmOutputOptions> {
300 self.output_options.lock().unwrap_or_else(PoisonError::into_inner)
301 }
302
303 fn orientation(&self) -> SurfaceOrientation {
304 self.options().orientation
305 }
306
307 fn scaling(&self) -> f64 {
308 self.options().scaling
309 }
310
311 fn resolution(&self) -> PixelSize {
312 self.mode.resolution()
313 }
314
315 fn get_fb_id_for_bo(&self, bo: *mut c_void) -> io::Result<u32> {
318 if bo.is_null() {
319 return Err(io::Error::new(io::ErrorKind::InvalidInput, "bo is 0"));
320 }
321 let gbm = self.lib;
322 let drm = self.lib;
323 unsafe {
326 let data = gbm.gbm_bo_get_user_data(bo);
327 if !data.is_null() {
328 return Ok((*data.cast::<FbUserData>()).fb_id);
329 }
330
331 let w = gbm.gbm_bo_get_width(bo);
332 let h = gbm.gbm_bo_get_height(bo);
333 let stride = gbm.gbm_bo_get_stride(bo);
334 let handle = gbm.gbm_bo_get_handle(bo).u32;
335 let format = gbm.gbm_bo_get_format(bo);
336
337 let handles = [handle, 0, 0, 0];
339 let pitches = [stride, 0, 0, 0];
340 let offsets = [0u32; 4];
341
342 let mut fb_handle = 0u32;
343 let mut ret = drm.drm_mode_add_fb2(
344 self.card.fd(),
345 w,
346 h,
347 format,
348 &handles,
349 &pitches,
350 &offsets,
351 &mut fb_handle,
352 0,
353 );
354
355 if ret != 0 {
356 ret = drm.drm_mode_add_fb(self.card.fd(), w, h, 24, 32, stride, handle, &mut fb_handle);
358
359 if ret != 0 {
360 return Err(io::Error::other(format!("drmModeAddFb failed {ret}")));
361 }
362 }
363
364 let data = Box::into_raw(Box::new(FbUserData { output: self.this.clone(), fb_id: fb_handle }));
365 gbm.gbm_bo_set_user_data(bo, data.cast(), Some(fb_destroy_callback));
366
367 Ok(fb_handle)
368 }
369 }
370
371 fn egl_state(&self) -> Rc<EglState> {
379 if let Some(state) = self.egl.get() {
380 return state.get().clone();
381 }
382 let state = match self.init_egl() {
383 Ok(state) => state,
384 Err(error) => panic!("{error}"),
385 };
386 if self.egl.set(ThreadBound::new(state.clone())).is_err() {
387 panic!("The EGL objects of the DRM output were created by two threads");
388 }
389 state
390 }
391
392 fn init_egl(&self) -> Result<Rc<EglState>, OpenGlException> {
396 let get_proc_address = egl_get_proc_address().map_err(|error| OpenGlException::new(error.to_string()))?;
397 let egl = Rc::new(unsafe { EglInterface::new(&get_proc_address) });
400 let egl_display = EglDisplay::new_with_creation_options(EglDisplayCreationOptions {
401 base: EglDisplayOptions {
402 egl: Some(egl),
403 supports_multiple_contexts: true,
404 supports_context_sharing: true,
405 ..Default::default()
406 },
407 platform_type: Some(EGL_PLATFORM_GBM_KHR),
408 platform_display: self.gbm_device.0 as isize,
409 ..Default::default()
410 })?;
411
412 let surface = egl_display.egl_interface().create_window_surface(
413 egl_display.handle(),
414 egl_display.config(),
415 self.gbm_target_surface.0 as isize,
416 Some(&[EGL_NONE, EGL_NONE]),
417 );
418 if surface == 0 {
419 return Err(OpenGlException::get_formatted_exception_for_egl(
420 "eglCreateWindowSurface",
421 egl_display.egl_interface(),
422 ));
423 }
424
425 let egl_surface = EglSurface::new(&egl_display, surface);
426
427 let deferred_context = egl_display.create_context(None)?;
428
429 let options = self.options().clone();
430 let color = options.initial_buffer_swapping_color;
431 let initial_buffer_swapping_color_r = color.r as f32 / 255.0;
432 let initial_buffer_swapping_color_g = color.g as f32 / 255.0;
433 let initial_buffer_swapping_color_b = color.b as f32 / 255.0;
434 let initial_buffer_swapping_color_a = color.a as f32 / 255.0;
435 let make_current = || {
436 deferred_context
437 .make_current_with_surface(Some(&egl_surface))
438 .map_err(|error| OpenGlException::new(error.to_string()))
439 };
440 {
441 let current = make_current()?;
442 let gl = deferred_context.gl_interface();
443 gl.clear_color(
444 initial_buffer_swapping_color_r,
445 initial_buffer_swapping_color_g,
446 initial_buffer_swapping_color_b,
447 initial_buffer_swapping_color_a,
448 );
449 gl.clear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
450 egl_surface.swap_buffers();
451 current.dispose();
452 }
453
454 let bo = unsafe { self.lib.gbm_surface_lock_front_buffer(self.gbm_target_surface.0) };
456 let fb_id = self.get_fb_id_for_bo(bo).map_err(|error| OpenGlException::new(error.to_string()))?;
457 let mut connector_id = self.connector_id;
458 let mut mode = self.mode;
459
460 let res =
462 unsafe { self.lib.drm_mode_set_crtc(self.card.fd(), self.crtc_id, fb_id, 0, 0, &mut connector_id, 1, &mut mode) };
463 if res != 0 {
464 return Err(OpenGlException::new(format!(
465 "drmModeSetCrtc failed: {} ({res})",
466 io::Error::last_os_error()
467 )));
468 }
469
470 let current_bo = Cell::new(bo);
471
472 let needs_rotation = options.orientation != SurfaceOrientation::Rotation0;
474 let (mut rotation_fbo, mut rotation_texture, mut rotation_program, mut rotation_vbo, mut rotation_vao) =
475 (0, 0, 0, 0, 0);
476 let rotated_size = if needs_rotation {
477 let rotated = rotated_size(options.orientation, self.resolution());
479
480 let current = make_current()?;
481 let gl = deferred_context.gl_interface();
482 rotation_fbo = gl.gen_framebuffer();
483 rotation_texture = gl.gen_texture();
484
485 gl.bind_texture(GL_TEXTURE_2D, rotation_texture);
486 unsafe {
488 gl.tex_image_2d(
489 GL_TEXTURE_2D,
490 0,
491 GL_RGBA,
492 rotated.width,
493 rotated.height,
494 0,
495 GL_RGBA,
496 GL_UNSIGNED_BYTE,
497 std::ptr::null(),
498 );
499 }
500 gl.tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
501 gl.tex_parameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
502
503 gl.bind_framebuffer(GL_FRAMEBUFFER, rotation_fbo);
504 gl.framebuffer_texture_2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rotation_texture, 0);
505 gl.bind_framebuffer(GL_FRAMEBUFFER, 0);
506
507 const VERTEX_SHADER: &str = "
509 attribute vec2 aPos;
510 attribute vec2 aTexCoord;
511 varying vec2 vTexCoord;
512 void main() {
513 gl_Position = vec4(aPos, 0.0, 1.0);
514 vTexCoord = aTexCoord;
515 }";
516
517 const FRAGMENT_SHADER: &str = "
518 precision mediump float;
519 varying vec2 vTexCoord;
520 uniform sampler2D uTexture;
521 void main() {
522 gl_FragColor = texture2D(uTexture, vTexCoord);
523 }";
524
525 let vs = gl.create_shader(GL_VERTEX_SHADER);
526 gl.shader_source_string(vs, VERTEX_SHADER);
527 gl.compile_shader(vs);
528
529 let fs = gl.create_shader(GL_FRAGMENT_SHADER);
530 gl.shader_source_string(fs, FRAGMENT_SHADER);
531 gl.compile_shader(fs);
532
533 rotation_program = gl.create_program();
534 gl.attach_shader(rotation_program, vs);
535 gl.attach_shader(rotation_program, fs);
536 gl.link_program(rotation_program);
537 gl.delete_shader(vs);
538 gl.delete_shader(fs);
539
540 let vertices = rotation_vertices(options.orientation);
543
544 rotation_vbo = gl.gen_buffer();
545 rotation_vao = gl.gen_vertex_array();
546
547 gl.bind_vertex_array(rotation_vao);
548 gl.bind_buffer(GL_ARRAY_BUFFER, rotation_vbo);
549
550 let float = std::mem::size_of::<f32>() as i32;
551 unsafe {
554 gl.buffer_data(
555 GL_ARRAY_BUFFER,
556 std::mem::size_of_val(&vertices) as isize,
557 vertices.as_ptr().cast(),
558 GL_STATIC_DRAW,
559 );
560
561 let pos_attrib = gl.get_attrib_location_string(rotation_program, "aPos");
562 gl.enable_vertex_attrib_array(pos_attrib);
563 gl.vertex_attrib_pointer(pos_attrib, 2, GL_FLOAT, 0, 4 * float, std::ptr::null());
564
565 let tex_attrib = gl.get_attrib_location_string(rotation_program, "aTexCoord");
566 gl.enable_vertex_attrib_array(tex_attrib);
567 gl.vertex_attrib_pointer(tex_attrib, 2, GL_FLOAT, 0, 4 * float, (2 * float) as usize as *const c_void);
568 }
569
570 gl.bind_vertex_array(0);
571 current.dispose();
572 rotated
573 } else {
574 self.resolution()
576 };
577
578 let state = Rc::new(EglState {
579 egl_display,
580 egl_surface,
581 deferred_context,
582 current_bo,
583 rotation_fbo,
584 rotation_texture,
585 rotated_size,
586 rotation_program,
587 rotation_vbo,
588 rotation_vao,
589 });
590
591 if options.enable_initial_buffer_swapping {
592 let scene_info =
594 RenderTargetSceneInfo::new(self.pixel_size(), 1.0, CompositionTransparencyLevel::None);
595 for _ in 0..2 {
596 let target = RenderTarget { parent: self.this(), egl: state.clone() };
597 let session = target.begin_draw(&scene_info);
598 let gl = state.deferred_context.gl_interface();
599 gl.clear_color(
600 initial_buffer_swapping_color_r,
601 initial_buffer_swapping_color_g,
602 initial_buffer_swapping_color_b,
603 initial_buffer_swapping_color_a,
604 );
605 gl.clear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
606 session.dispose();
607 }
608 }
609
610 Ok(state)
611 }
612
613 fn this(&self) -> Arc<Inner> {
614 self.this.upgrade().expect("an output that is called is alive")
615 }
616
617 fn pixel_size(&self) -> PixelSize {
618 let resolution = self.resolution();
619 let orientation = self.orientation();
620 if orientation == SurfaceOrientation::Rotation0 || orientation == SurfaceOrientation::Rotation180 {
621 PixelSize::new(resolution.width, resolution.height)
622 } else {
623 PixelSize::new(resolution.height, resolution.width)
624 }
625 }
626 }
627
628 struct SharedContextGraphics {
635 inner: Arc<Inner>,
636 }
637
638 impl IPlatformGraphics for SharedContextGraphics {
639 fn uses_shared_context(&self) -> bool {
640 true
641 }
642
643 fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
646 panic!("Specified method is not supported.")
647 }
648
649 fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
653 self.inner.egl_state().deferred_context.clone()
654 }
655 }
656
657 pub struct DrmOutput {
659 inner: Arc<Inner>,
660 platform_graphics: Arc<SharedContextGraphics>,
661 }
662
663 impl DrmOutput {
664 pub fn new_with_mode_and_options(
667 card: DrmCard,
668 resources: &DrmResources,
669 connector: &DrmConnector,
670 mode_info: &DrmModeInfo,
671 options: Option<DrmOutputOptions>,
672 ) -> io::Result<Arc<Self>> {
673 Self::init(card, resources, connector, mode_info, options.unwrap_or_default())
674 }
675
676 pub fn new(path: Option<&str>, connectors_force_probe: bool, options: Option<DrmOutputOptions>) -> io::Result<Arc<Self>> {
681 Self::from_card(DrmCard::new(path)?, connectors_force_probe, options)
682 }
683
684 pub fn from_card(card: DrmCard, connectors_force_probe: bool, options: Option<DrmOutputOptions>) -> io::Result<Arc<Self>> {
686 let resources = card.get_resources(connectors_force_probe)?;
687
688 let Some(connector) = select_connector(resources.connectors(), options.as_ref()) else {
689 return Err(io::Error::new(io::ErrorKind::NotFound, "Unable to find connected DRM connector"));
690 };
691
692 let Some(mode) = select_mode(connector, options.as_ref()) else {
693 return Err(io::Error::new(io::ErrorKind::NotFound, "Unable to find a usable DRM mode"));
694 };
695 Self::init(card, &resources, connector, mode, options.unwrap_or_default())
696 }
697
698 pub fn new_with_mode(
701 card: DrmCard,
702 resources: &DrmResources,
703 connector: &DrmConnector,
704 mode_info: &DrmModeInfo,
705 ) -> io::Result<Arc<Self>> {
706 Self::init(card, resources, connector, mode_info, DrmOutputOptions::default())
707 }
708
709 fn init(
713 card: DrmCard,
714 resources: &DrmResources,
715 connector: &DrmConnector,
716 mode_info: &DrmModeInfo,
717 options: DrmOutputOptions,
718 ) -> io::Result<Arc<Self>> {
719 let lib = LibDrm::get()?;
720 egl_library()?;
723
724 let crtc_id = get_crtc(resources, connector)?;
725 let device = unsafe { lib.gbm_create_device(card.fd()) };
727 if device.is_null() {
728 return Err(io::Error::other("Unable to create GBM device"));
729 }
730 let resolution = mode_info.resolution();
731 let gbm_target_surface = unsafe {
733 lib.gbm_surface_create(
734 device,
735 resolution.width as u32,
736 resolution.height as u32,
737 gbm_color_formats::GBM_FORMAT_XRGB8888,
738 GbmBoFlags::GBM_BO_USE_SCANOUT | GbmBoFlags::GBM_BO_USE_RENDERING,
739 )
740 };
741 if gbm_target_surface.is_null() {
742 return Err(io::Error::other("Unable to create GBM surface"));
743 }
744
745 let inner = Arc::new_cyclic(|this| Inner {
746 this: this.clone(),
747 output_options: Mutex::new(options),
748 card,
749 lib,
750 mode: mode_info.mode,
751 connector_id: connector.id(),
752 crtc_id,
753 gbm_device: GbmHandle(device),
754 gbm_target_surface: GbmHandle(gbm_target_surface),
755 egl: OnceLock::new(),
756 });
757 let platform_graphics = Arc::new(SharedContextGraphics { inner: inner.clone() });
758 Ok(Arc::new(Self { inner, platform_graphics }))
759 }
760
761 pub fn set_orientation(&self, value: SurfaceOrientation) {
762 self.inner.options().orientation = value;
763 }
764
765 pub fn card(&self) -> &DrmCard {
768 &self.inner.card
769 }
770
771 pub fn crtc_id(&self) -> u32 {
774 self.inner.crtc_id
775 }
776
777 pub fn create_gl_render_target_of_primary_context(&self) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
785 Rc::new(RenderTarget { parent: self.inner.clone(), egl: self.inner.egl_state() })
786 }
787
788 pub fn create_context(&self) -> Rc<dyn IGlContext> {
791 panic!("The method or operation is not implemented.")
792 }
793 }
794
795 impl IPlatformRenderSurface for DrmOutput {
796 fn try_get_surface_kind(&self, kind: TypeId) -> Option<Rc<dyn Any>> {
797 if kind == TypeId::of::<dyn IGlPlatformSurface>() {
798 let this: Rc<dyn IGlPlatformSurface> =
800 Rc::new(Self { inner: self.inner.clone(), platform_graphics: self.platform_graphics.clone() });
801 return Some(Rc::new(this));
802 }
803 None
804 }
805
806 fn as_any(&self) -> &dyn Any {
807 self
808 }
809 }
810
811 impl IGlPlatformSurface for DrmOutput {
812 fn create_gl_render_target(&self, context: &Rc<dyn IGlContext>) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
816 let egl = self.inner.egl_state();
817 let is_primary = context
818 .as_any()
819 .downcast_ref::<EglContext>()
820 .is_some_and(|context| std::ptr::eq(context, Rc::as_ptr(&egl.deferred_context)));
821 if !is_primary {
822 panic!("This platform backend can only create render targets for its primary context");
823 }
824 self.create_gl_render_target_of_primary_context()
825 }
826 }
827
828 impl IOutputBackend for DrmOutput {
829 fn pixel_size(&self) -> PixelSize {
830 self.inner.pixel_size()
831 }
832
833 fn scaling(&self) -> f64 {
834 self.inner.scaling()
835 }
836
837 fn set_scaling(&self, value: f64) {
838 self.inner.options().scaling = value;
839 }
840
841 fn as_render_surface(self: Arc<Self>) -> Arc<dyn IPlatformRenderSurface> {
842 self
843 }
844
845 fn as_gl_output_backend(&self) -> Option<&dyn IGlOutputBackend> {
846 Some(self)
847 }
848
849 fn as_surface_orientation(&self) -> Option<&dyn ISurfaceOrientation> {
850 Some(self)
851 }
852 }
853
854 impl IGlOutputBackend for DrmOutput {
855 fn platform_graphics(&self) -> Arc<dyn IPlatformGraphics> {
856 self.platform_graphics.clone()
857 }
858 }
859
860 impl ISurfaceOrientation for DrmOutput {
861 fn orientation(&self) -> SurfaceOrientation {
862 self.inner.orientation()
863 }
864 }
865
866 struct RenderTarget {
867 parent: Arc<Inner>,
868 egl: Rc<EglState>,
869 }
870
871 impl IPlatformRenderSurfaceRenderTarget for RenderTarget {
872 fn state(&self) -> PlatformRenderTargetState {
873 PlatformRenderTargetState::READY
874 }
875 }
876
877 impl IGlPlatformSurfaceRenderTarget for RenderTarget {
878 fn begin_draw(&self, _scene_info: &RenderTargetSceneInfo) -> Rc<dyn IGlPlatformSurfaceRenderingSession> {
882 let clear_context = match self.egl.deferred_context.make_current_with_surface(Some(&self.egl.egl_surface)) {
883 Ok(clear_context) => clear_context,
884 Err(error) => panic!("{error}"),
885 };
886 let gl = self.egl.deferred_context.gl_interface();
887
888 if self.parent.orientation() != SurfaceOrientation::Rotation0 {
889 gl.bind_framebuffer(GL_FRAMEBUFFER, self.egl.rotation_fbo);
891 gl.viewport(0, 0, self.egl.rotated_size.width, self.egl.rotated_size.height);
892 } else {
893 gl.bind_framebuffer(GL_FRAMEBUFFER, 0);
895 let resolution = self.parent.resolution();
896 gl.viewport(0, 0, resolution.width, resolution.height);
897 }
898
899 Rc::new(RenderSession {
900 parent: self.parent.clone(),
901 egl: self.egl.clone(),
902 clear_context,
903 disposed: Cell::new(false),
904 })
905 }
906
907 fn dispose(&self) {
908 }
910 }
911
912 struct RenderSession {
913 parent: Arc<Inner>,
914 egl: Rc<EglState>,
915 clear_context: Rc<dyn IDisposable>,
916 disposed: Cell<bool>,
917 }
918
919 impl IGlPlatformSurfaceRenderingSession for RenderSession {
920 fn context(&self) -> Rc<dyn IGlContext> {
921 self.egl.deferred_context.clone()
922 }
923
924 fn size(&self) -> PixelSize {
925 self.egl.rotated_size
926 }
927
928 fn scaling(&self) -> f64 {
929 self.parent.scaling()
930 }
931
932 fn is_y_flipped(&self) -> bool {
933 false
934 }
935
936 fn dispose(&self) {
940 if self.disposed.replace(true) {
942 return;
943 }
944 let parent = &self.parent;
945 let egl = &self.egl;
946 let gl = egl.deferred_context.gl_interface();
947
948 if parent.orientation() != SurfaceOrientation::Rotation0 {
949 gl.bind_framebuffer(GL_FRAMEBUFFER, 0);
952 let resolution = parent.resolution();
953 gl.viewport(0, 0, resolution.width, resolution.height);
954
955 gl.clear_color(0.0, 0.0, 0.0, 1.0);
957 gl.clear(GL_COLOR_BUFFER_BIT);
958
959 gl.use_program(egl.rotation_program);
961
962 gl.active_texture(GL_TEXTURE0);
964 gl.bind_texture(GL_TEXTURE_2D, egl.rotation_texture);
965
966 let tex_loc = gl.get_uniform_location_string(egl.rotation_program, "uTexture");
968 gl.uniform1i(tex_loc, 0);
969
970 gl.bind_vertex_array(egl.rotation_vao);
972 unsafe { gl.draw_arrays(GL_TRIANGLE_FAN, 0, 4) };
974 gl.bind_vertex_array(0);
975
976 gl.use_program(0);
977 }
978
979 gl.flush();
980 egl.egl_surface.swap_buffers();
981
982 let lib = parent.lib;
983 let surface = parent.gbm_target_surface.0;
984 let next_bo = unsafe { lib.gbm_surface_lock_front_buffer(surface) };
986 if next_bo.is_null() {
987 println!("gbm_surface_lock_front_buffer failed");
989 } else {
990 let fb = match parent.get_fb_id_for_bo(next_bo) {
991 Ok(fb) => fb,
992 Err(error) => panic!("{error}"),
993 };
994 let waiting_for_flip = Cell::new(true);
995
996 let fd = parent.card.fd();
997 let ret = unsafe {
1000 lib.drm_mode_page_flip(
1001 fd,
1002 parent.crtc_id,
1003 fb,
1004 DrmModePageFlip::EVENT,
1005 (&waiting_for_flip as *const Cell<bool>).cast_mut().cast(),
1006 )
1007 };
1008
1009 if ret != 0 {
1010 println!("drmModePageFlip failed {ret}");
1013 unsafe { lib.gbm_surface_release_buffer(surface, next_bo) };
1015 } else {
1016 let mut ctx = DrmEventContext {
1017 version: 4,
1018 page_flip_handler: Some(flip_cb),
1019 ..Default::default()
1020 };
1021 while waiting_for_flip.get() {
1022 let mut pfd = libc::pollfd { events: libc::POLLIN, fd, revents: 0 };
1023 unsafe {
1026 libc::poll(&mut pfd, 1, -1);
1027 lib.drm_handle_event(fd, &mut ctx);
1028 }
1029 }
1030
1031 unsafe { lib.gbm_surface_release_buffer(surface, egl.current_bo.get()) };
1034 egl.current_bo.set(next_bo);
1035 }
1036 }
1037 self.clear_context.dispose();
1038 }
1039 }
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044 use super::super::drm::lib_drm::{drmModeCrtc, drmModeEncoder};
1047 use super::super::drm_bindings::tests::{connector, mode};
1048 use super::super::drm_bindings::DrmEncoder;
1049 use super::*;
1050 use crate::DrmConnectorType;
1051 use std::collections::HashMap;
1052
1053 const CONNECTED: DrmModeConnection = DrmModeConnection::DRM_MODE_CONNECTED;
1054 const DISCONNECTED: DrmModeConnection = DrmModeConnection::DRM_MODE_DISCONNECTED;
1055
1056 fn connectors() -> Vec<DrmConnector> {
1057 vec![
1058 connector(30, 11, 1, DISCONNECTED, 0, &[50], &[mode(1920, 1080, true)]),
1059 connector(31, 11, 2, CONNECTED, 0, &[50], &[mode(1280, 720, true), mode(1920, 1080, false)]),
1060 connector(32, 10, 1, CONNECTED, 51, &[51], &[mode(800, 600, false), mode(1024, 768, false), mode(640, 480, false)]),
1061 ]
1062 }
1063
1064 #[test]
1065 fn the_first_connected_connector_is_chosen_unless_the_options_name_a_type() {
1066 let connectors = connectors();
1067 assert_eq!(select_connector(&connectors, None).map(DrmConnector::id), Some(31));
1068
1069 let display_port = DrmOutputOptions { connector_type: Some(DrmConnectorType::DISPLAY_PORT), ..Default::default() };
1070 assert_eq!(select_connector(&connectors, Some(&display_port)).map(DrmConnector::id), Some(32));
1071
1072 let second_hdmi = DrmOutputOptions {
1073 connector_type: Some(DrmConnectorType::HDMI_A),
1074 connector_type_id: Some(2),
1075 ..Default::default()
1076 };
1077 assert_eq!(select_connector(&connectors, Some(&second_hdmi)).map(DrmConnector::id), Some(31));
1078
1079 let first_hdmi = DrmOutputOptions { connector_type_id: Some(1), connector_type: Some(DrmConnectorType::HDMI_A), ..Default::default() };
1081 assert!(select_connector(&connectors, Some(&first_hdmi)).is_none());
1082 let vga = DrmOutputOptions { connector_type: Some(DrmConnectorType::VGA), ..Default::default() };
1083 assert!(select_connector(&connectors, Some(&vga)).is_none());
1084 }
1085
1086 #[test]
1087 fn the_preferred_mode_wins_then_the_largest_then_the_first() {
1088 let connectors = connectors();
1089 assert_eq!(select_mode(&connectors[1], None).map(DrmModeInfo::resolution), Some(PixelSize::new(1280, 720)));
1091 assert_eq!(select_mode(&connectors[2], None).map(DrmModeInfo::resolution), Some(PixelSize::new(1024, 768)));
1093
1094 let mut first = mode(1024, 768, false);
1096 first.vrefresh = 60;
1097 let mut second = mode(1024, 768, false);
1098 second.vrefresh = 75;
1099 let same = connector(40, 15, 1, CONNECTED, 0, &[], &[mode(800, 600, false), first, second]);
1100 assert_eq!(select_mode(&same, None).map(|m| m.mode.vrefresh), Some(60));
1101
1102 let empty = connector(41, 15, 1, CONNECTED, 0, &[], &[]);
1103 assert!(select_mode(&empty, None).is_none());
1104 }
1105
1106 #[test]
1107 fn a_video_mode_of_the_options_is_used_when_the_connector_has_it() {
1108 let connectors = connectors();
1109 let full_hd = DrmOutputOptions { video_mode: Some(PixelSize::new(1920, 1080)), ..Default::default() };
1110 assert_eq!(select_mode(&connectors[1], Some(&full_hd)).map(DrmModeInfo::resolution), Some(PixelSize::new(1920, 1080)));
1111 let four_k = DrmOutputOptions { video_mode: Some(PixelSize::new(3840, 2160)), ..Default::default() };
1113 assert_eq!(select_mode(&connectors[1], Some(&four_k)).map(DrmModeInfo::resolution), Some(PixelSize::new(1280, 720)));
1114 }
1115
1116 #[test]
1117 fn the_crtc_is_the_current_encoders_or_the_first_possible_one() {
1118 let crtcs: Vec<drmModeCrtc> = (0..2).map(|c| drmModeCrtc { crtc_id: 60 + c, ..Default::default() }).collect();
1119 let mut encoders = HashMap::new();
1120 encoders.insert(
1121 50,
1122 DrmEncoder::new(drmModeEncoder { encoder_id: 50, crtc_id: 0, possible_crtcs: 0b10, ..Default::default() }, &crtcs),
1123 );
1124 encoders.insert(
1125 51,
1126 DrmEncoder::new(drmModeEncoder { encoder_id: 51, crtc_id: 60, possible_crtcs: 0b01, ..Default::default() }, &crtcs),
1127 );
1128 let resources = DrmResources::from_parts(connectors(), encoders);
1129
1130 assert_eq!(get_crtc(&resources, &resources.connectors()[2]).unwrap(), 60);
1132 assert_eq!(get_crtc(&resources, &resources.connectors()[1]).unwrap(), 61);
1134
1135 let lonely = connector(42, 15, 1, CONNECTED, 0, &[99], &[]);
1136 assert_eq!(
1137 get_crtc(&resources, &lonely).unwrap_err().to_string(),
1138 "Unable to find CRTC matching the desired mode"
1139 );
1140 }
1141
1142 #[test]
1143 fn a_quarter_turn_exchanges_the_sides_and_the_quad_maps_the_corners() {
1144 let size = PixelSize::new(1024, 768);
1145 assert_eq!(rotated_size(SurfaceOrientation::Rotation0, size), size);
1146 assert_eq!(rotated_size(SurfaceOrientation::Rotation180, size), size);
1147 assert_eq!(rotated_size(SurfaceOrientation::Rotation90, size), PixelSize::new(768, 1024));
1148 assert_eq!(rotated_size(SurfaceOrientation::Rotation270, size), PixelSize::new(768, 1024));
1149
1150 let corner = |o| {
1152 let v = rotation_vertices(o);
1153 (v[2], v[3])
1154 };
1155 assert_eq!(corner(SurfaceOrientation::Rotation0), (0.0, 0.0));
1156 assert_eq!(corner(SurfaceOrientation::Rotation90), (1.0, 0.0));
1157 assert_eq!(corner(SurfaceOrientation::Rotation180), (1.0, 1.0));
1158 assert_eq!(corner(SurfaceOrientation::Rotation270), (0.0, 1.0));
1159 }
1160
1161 #[test]
1162 fn the_top_left_corner_of_the_scene_is_turned_clockwise() {
1163 let screen_corner = |o| {
1168 let v = rotation_vertices(o);
1169 let i = (0..4).find(|i| v[i * 4 + 2] == 0.0 && v[i * 4 + 3] == 1.0).unwrap();
1170 (v[i * 4], v[i * 4 + 1])
1171 };
1172 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)); }
1177}