use std::{collections::HashMap, hash::Hash};
use drm::control::{self, atomic::AtomicModeReq, connector, crtc, plane, property};
use thiserror::Error;
use crate::gles_context::{GlesContext, GlesContextError};
pub(crate) struct MonitorResourceAllocation {
pub crtc_info: crtc::Info,
pub primary_plane: plane::Handle,
pub cursor_plane: Option<plane::Handle>,
}
pub struct Monitor<T> {
connector_id: connector::Handle,
current_crtc: crtc::Info,
default_mode: control::Mode,
requested_mode: Option<control::Mode>,
current_mode: Option<control::Mode>,
primary_plane_id: plane::Handle,
cursor_plane_id: Option<plane::Handle>,
gles_context: GlesContext,
can_render: bool,
was_drawn: bool,
previous_bo: Option<gbm::BufferObject<()>>,
previous_fence_fd: Option<i32>,
previous_sync: Option<*mut std::ffi::c_void>,
connector_properties: HashMap<String, property::Info>,
crtc_properties: HashMap<String, property::Info>,
plane_properties: HashMap<String, property::Info>,
first_frame: bool,
user_context: T,
}
impl<T> PartialEq for Monitor<T> {
fn eq(&self, other: &Self) -> bool {
other.connector_id == self.connector_id
}
}
impl<T> Eq for Monitor<T> {}
impl<T> Hash for Monitor<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.connector_id.hash(state);
}
}
#[derive(Debug, Error)]
pub enum MonitorSetupError {
#[error("IO Error: {0}")]
IOError(#[from] std::io::Error),
#[error("monitor is not connected")]
NotConnected,
#[error("monitor doesn't have any crtcs available")]
NoCRTCFound,
#[error("monitor doesn't have any display modes")]
NoModesFound,
#[error("no primary plane found for this monitor")]
NoPrimaryPlaneFound,
#[error("failed to create OpenGL ES context: {0}")]
GlesContextError(#[from] GlesContextError),
#[error("DRM error: {0}")]
DrmError(String),
}
impl<T> Monitor<T> {
pub(crate) fn setup<F>(
card: &impl control::Device,
gbm_device: &gbm::Device<std::fs::File>,
connector_id: connector::Handle,
allocation: MonitorResourceAllocation,
context_constructor: F,
) -> Result<Self, MonitorSetupError>
where
F: FnOnce(&crate::gl::Gles2, usize, usize) -> T,
{
let connector = card.get_connector(connector_id, true)?;
let MonitorResourceAllocation {
crtc_info,
primary_plane,
cursor_plane,
} = allocation;
let default_mode = connector
.modes()
.first()
.cloned()
.ok_or(MonitorSetupError::NoModesFound)?;
let gles_context = GlesContext::new(gbm_device, &default_mode)?;
let user_context = context_constructor(
gles_context.gl(),
default_mode.size().0 as _,
default_mode.size().1 as _,
);
let connector_properties = card
.get_properties(connector_id)?
.as_hashmap(card)
.map_err(|e| {
MonitorSetupError::DrmError(format!("Failed to get connector properties: {}", e))
})?;
let crtc_properties = card
.get_properties(crtc_info.handle())?
.as_hashmap(card)
.map_err(|e| {
MonitorSetupError::DrmError(format!("Failed to get CRTC properties: {}", e))
})?;
let plane_properties = card
.get_properties(primary_plane)?
.as_hashmap(card)
.map_err(|e| {
MonitorSetupError::DrmError(format!("Failed to get plane properties: {}", e))
})?;
Ok(Monitor {
connector_id,
current_crtc: crtc_info,
default_mode,
requested_mode: None, current_mode: None, primary_plane_id: primary_plane,
cursor_plane_id: cursor_plane,
gles_context,
can_render: true, was_drawn: false,
previous_bo: None,
previous_fence_fd: None,
previous_sync: None,
connector_properties,
crtc_properties,
plane_properties,
first_frame: true,
user_context,
})
}
pub fn context(&self) -> &T {
&self.user_context
}
pub fn context_mut(&mut self) -> &mut T {
&mut self.user_context
}
pub fn can_render(&self) -> bool {
self.can_render
}
pub(crate) fn was_drawn(&self) -> bool {
self.was_drawn
}
pub(crate) fn set_can_render(&mut self, value: bool) {
self.can_render = value;
}
pub(crate) fn reset_drawn_flag(&mut self) {
self.was_drawn = false;
}
pub fn make_current(&mut self) -> Result<(), GlesContextError> {
self.gles_context.make_current()?;
self.was_drawn = true;
Ok(())
}
pub(crate) fn swap_buffers(
&mut self,
card: &impl control::Device,
atomic_req: &mut AtomicModeReq,
) -> Result<(), MonitorSetupError> {
dbg!(self.connector_id);
self.gles_context.make_current()?;
let bo = self
.gles_context
.swap_buffers()
.map_err(|e| MonitorSetupError::DrmError(format!("Failed to swap buffers: {}", e)))?;
if let Some(old_fence_fd) = self.previous_fence_fd.take() {
unsafe {
libc::close(old_fence_fd);
}
}
if let Some(old_sync) = self.previous_sync.take() {
self.destroy_egl_sync(old_sync);
}
let (fence_fd, sync) = self
.create_egl_fence()
.map_err(|e| MonitorSetupError::DrmError(format!("Failed to create fence: {}", e)))?;
let fb = card.add_framebuffer(&bo, 24, 32).map_err(|e| {
MonitorSetupError::DrmError(format!("Failed to add framebuffer: {}", e))
})?;
let target_mode = self.requested_mode.as_ref().unwrap_or(&self.default_mode);
let needs_mode_set = self.needs_mode_set();
atomic_req.add_property(
self.connector_id,
self.connector_properties["CRTC_ID"].handle(),
property::Value::CRTC(Some(self.current_crtc.handle())),
);
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["CRTC_ID"].handle(),
property::Value::CRTC(Some(self.current_crtc.handle())),
);
let (width, height) = target_mode.size();
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["SRC_X"].handle(),
property::Value::UnsignedRange(0),
);
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["SRC_Y"].handle(),
property::Value::UnsignedRange(0),
);
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["SRC_W"].handle(),
property::Value::UnsignedRange((width as u64) << 16),
);
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["SRC_H"].handle(),
property::Value::UnsignedRange((height as u64) << 16),
);
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["CRTC_X"].handle(),
property::Value::SignedRange(0),
);
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["CRTC_Y"].handle(),
property::Value::SignedRange(0),
);
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["CRTC_W"].handle(),
property::Value::UnsignedRange(width as u64),
);
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["CRTC_H"].handle(),
property::Value::UnsignedRange(height as u64),
); if needs_mode_set {
let mode_blob = card.create_property_blob(target_mode).map_err(|e| {
MonitorSetupError::DrmError(format!("Failed to create mode blob: {}", e))
})?;
atomic_req.add_property(
self.current_crtc.handle(),
self.crtc_properties["MODE_ID"].handle(),
mode_blob,
);
atomic_req.add_property(
self.current_crtc.handle(),
self.crtc_properties["ACTIVE"].handle(),
property::Value::Boolean(true),
);
}
atomic_req.add_property(
self.primary_plane_id,
self.plane_properties["FB_ID"].handle(),
property::Value::Framebuffer(Some(fb)),
);
if let Some(fence_prop) = self.crtc_properties.get("IN_FENCE_FD") {
atomic_req.add_property(
self.current_crtc.handle(),
fence_prop.handle(),
property::Value::SignedRange(fence_fd as i64),
);
} else if let Some(fence_prop) = self.plane_properties.get("IN_FENCE_FD") {
atomic_req.add_property(
self.primary_plane_id,
fence_prop.handle(),
property::Value::SignedRange(fence_fd as i64),
);
}
self.previous_bo = Some(bo);
self.previous_fence_fd = Some(fence_fd);
self.previous_sync = Some(sync);
self.first_frame = false;
self.can_render = false;
if needs_mode_set {
self.mark_mode_set();
}
Ok(())
}
fn create_egl_fence(&self) -> Result<(i32, *mut std::ffi::c_void), String> {
unsafe {
let egl = self.gles_context.display().egl();
const EGL_SYNC_FENCE_KHR: u32 = 0x3144;
let sync = egl.CreateSyncKHR(
egl.GetCurrentDisplay(),
EGL_SYNC_FENCE_KHR,
std::ptr::null(),
);
if sync.is_null() {
return Err("Failed to create EGL sync".to_string());
}
let fence_fd = egl.DupNativeFenceFDANDROID(egl.GetCurrentDisplay(), sync);
if fence_fd < 0 {
return Err(format!("Failed to duplicate fence FD: fence_fd={fence_fd}"));
}
Ok((fence_fd, sync as *mut std::ffi::c_void))
}
}
fn destroy_egl_sync(&self, sync: *mut std::ffi::c_void) {
unsafe {
let egl = self.gles_context.display().egl();
let egl_display = egl.GetCurrentDisplay();
egl.DestroySyncKHR(egl_display, sync);
}
}
pub(crate) fn needs_mode_set(&self) -> bool {
self.requested_mode != self.current_mode
}
pub(crate) fn mark_mode_set(&mut self) {
self.current_mode = self.requested_mode;
}
#[allow(dead_code)]
pub(crate) fn clear_mode_state(&mut self) {
self.current_mode = None;
self.first_frame = true; }
pub fn gl(&self) -> &crate::gl::Gles2 {
self.gles_context.gl()
}
pub fn get_proc_address(&self, symbol: &str) -> *const std::ffi::c_void {
self.gles_context.get_proc_address(symbol)
}
pub fn connector_id(&self) -> connector::Handle {
self.connector_id
}
pub fn crtc(&self) -> &crtc::Info {
&self.current_crtc
}
pub fn default_mode(&self) -> &control::Mode {
&self.default_mode
}
pub fn current_mode(&self) -> Option<&control::Mode> {
self.current_mode.as_ref()
}
pub fn requested_mode(&self) -> Option<&control::Mode> {
self.requested_mode.as_ref()
}
pub fn set_mode(&mut self, mode: Option<control::Mode>) {
self.requested_mode = mode;
}
pub fn active_mode(&self) -> &control::Mode {
self.requested_mode.as_ref().unwrap_or(&self.default_mode)
}
pub fn primary_plane(&self) -> plane::Handle {
self.primary_plane_id
}
pub fn cursor_plane(&self) -> Option<plane::Handle> {
self.cursor_plane_id
}
pub fn size(&self) -> (u16, u16) {
self.active_mode().size()
}
}
impl<T> Drop for Monitor<T> {
fn drop(&mut self) {
if let Some(fence_fd) = self.previous_fence_fd.take() {
unsafe {
libc::close(fence_fd);
}
}
if let Some(sync) = self.previous_sync.take() {
self.destroy_egl_sync(sync);
}
}
}