use std::collections::{HashMap, HashSet};
use std::future::pending;
use std::ops::Deref;
use std::os::fd::{AsFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, Mutex};
use drm::Device;
use drm::control::atomic::AtomicModeReq;
use drm::control::{
AtomicCommitFlags, Device as ControlDevice, Event, PlaneType, connector, crtc, plane,
};
use gbm::Device as GbmDevice;
use libc::{F_GETFL, F_SETFL, O_NONBLOCK, fcntl};
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
use thiserror::Error;
use tokio::io::Interest;
use tokio::io::unix::AsyncFd;
use crate::card::Card;
pub use crate::gles_context::{GlesContext, GlesContextError};
use crate::monitor::{MonitorResourceAllocation, MonitorSetupError};
mod card;
mod gles_context;
mod hotplug;
mod monitor;
pub use monitor::Monitor;
#[allow(clippy::all, warnings)]
pub mod gl {
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
pub struct MonitorContextCreationRequest<'a> {
pub gl: &'a crate::gl::Gles2,
pub width: usize,
pub height: usize,
pub get_proc_address: &'a dyn Fn(&str) -> *const std::ffi::c_void,
}
#[derive(Debug, Error)]
pub enum EasyDRMError {
#[error("IO Error: {0}")]
IOError(#[from] std::io::Error),
#[error("Monitor setup error: {0}")]
MonitorSetup(#[from] MonitorSetupError),
}
pub struct SwapResult {
pub committed_connectors: Vec<connector::Handle>,
pub render_fence: i32,
}
pub struct EasyDRM<T> {
card: Card,
gbm_device: GbmDevice<std::fs::File>,
shared_gles_context: Arc<Mutex<GlesContext>>,
monitors: HashMap<connector::Handle, Monitor<T>>,
refresh_rate_groups: HashMap<u32, Vec<connector::Handle>>, fastest_group_refresh: Option<u32>,
fastest_group_pending: HashSet<connector::Handle>,
should_update_flag: bool,
context_constructor: Box<dyn for<'a> Fn(&MonitorContextCreationRequest<'a>) -> T + 'static>,
uevent_socket: Option<hotplug::UEventSocket>,
last_fence_fd: Option<OwnedFd>
}
const EGL_SYNC_FENCE_KHR: u32 = 0x3144;
impl<T> EasyDRM<T> {
pub fn init<F>(context_constructor: F) -> Result<Self, EasyDRMError>
where
F: for<'a> Fn(&MonitorContextCreationRequest<'a>) -> T + 'static,
{
let card = Card::open_default_card();
card.set_client_capability(drm::ClientCapability::UniversalPlanes, true)
.expect("Unable to request UniversalPlanes capability");
card.set_client_capability(drm::ClientCapability::Atomic, true)
.expect("Unable to request Atomic capability");
let fd = card.as_raw_fd();
let gbm_device = unsafe {
use std::os::unix::io::FromRawFd;
GbmDevice::new(std::fs::File::from_raw_fd(libc::dup(fd)))
.expect("Failed to create GBM device")
};
let shared_gles_context = Arc::new(Mutex::new(
GlesContext::new(&gbm_device).map_err(MonitorSetupError::GlesContextError)?,
));
let mut easydrm = EasyDRM {
card,
gbm_device,
shared_gles_context,
monitors: HashMap::new(),
refresh_rate_groups: HashMap::new(),
fastest_group_refresh: None,
fastest_group_pending: HashSet::new(),
should_update_flag: false,
context_constructor: Box::new(context_constructor),
uevent_socket: hotplug::UEventSocket::open().ok(),
last_fence_fd: None
};
if easydrm.uevent_socket.is_none() {
eprintln!(
"[WARNING] Failed to open uevent socket, monitors plugged in afterwards won't be able to be detected"
);
}
easydrm.discover_monitors()?;
Ok(easydrm)
}
fn discover_monitors(&mut self) -> Result<(), EasyDRMError> {
let res = self.card.resource_handles()?;
let connector_handles: Vec<connector::Handle> = res.connectors().to_vec();
let (mut used_crtcs, mut used_primary_planes, mut used_cursor_planes) =
self.current_resource_usage();
for connector_id in connector_handles {
if self.monitors.contains_key(&connector_id) {
continue;
}
let allocation = match self.allocate_monitor_resources(
connector_id,
&used_crtcs,
&used_primary_planes,
&used_cursor_planes,
) {
Ok(allocation) => allocation,
Err(e) => {
eprintln!(
"Warning: Failed to allocate resources for monitor {:?}: {}",
connector_id, e
);
continue;
}
};
match Monitor::setup(
&self.card,
&self.gbm_device,
Arc::clone(&self.shared_gles_context),
connector_id,
allocation,
|request| (self.context_constructor)(request),
) {
Ok(monitor) => {
used_crtcs.insert(monitor.crtc().handle());
used_primary_planes.insert(monitor.primary_plane());
if let Some(cursor) = monitor.cursor_plane() {
used_cursor_planes.insert(cursor);
}
self.monitors.insert(connector_id, monitor);
}
Err(e) => {
eprintln!("Warning: Failed to setup monitor {:?}: {}", connector_id, e);
}
}
}
self.update_refresh_rate_groups();
Ok(())
}
fn update_refresh_rate_groups(&mut self) {
self.refresh_rate_groups.clear();
for (&connector_id, monitor) in self.monitors.iter() {
let refresh_rate = monitor.active_mode().vrefresh();
self.refresh_rate_groups
.entry(refresh_rate)
.or_default()
.push(connector_id);
}
self.fastest_group_refresh = self.refresh_rate_groups.keys().max().copied();
self.reset_fastest_group_pending();
}
fn reset_fastest_group_pending(&mut self) {
self.fastest_group_pending.clear();
if let Some(refresh) = self.fastest_group_refresh {
if let Some(connectors) = self.refresh_rate_groups.get(&refresh) {
self.fastest_group_pending
.extend(connectors.iter().copied());
}
}
self.should_update_flag =
self.fastest_group_pending.is_empty() && self.fastest_group_refresh.is_some();
}
fn mark_fast_group_commit(&mut self, connector_id: connector::Handle) {
if self.fastest_group_pending.remove(&connector_id) && self.fastest_group_pending.is_empty()
{
self.should_update_flag = true;
}
}
fn handle_hotplug(&mut self) -> Result<(), EasyDRMError> {
let res = self.card.resource_handles()?;
let drm_connected = res
.connectors()
.into_iter()
.copied()
.collect::<HashSet<_>>();
let current_monitors: HashSet<connector::Handle> = self.monitors.keys().copied().collect();
let disconnected: Vec<connector::Handle> = current_monitors
.difference(&drm_connected)
.copied()
.collect();
let newly_connected: Vec<connector::Handle> = drm_connected
.difference(¤t_monitors)
.copied()
.collect();
let needs_update = !disconnected.is_empty() || !newly_connected.is_empty();
for connector_id in disconnected {
self.monitors.retain(|&c, _| c != connector_id);
}
let (mut used_crtcs, mut used_primary_planes, mut used_cursor_planes) =
self.current_resource_usage();
for connector_id in newly_connected {
let allocation = match self.allocate_monitor_resources(
connector_id,
&used_crtcs,
&used_primary_planes,
&used_cursor_planes,
) {
Ok(allocation) => allocation,
Err(e) => {
eprintln!(
"Warning: Failed to allocate resources for monitor {:?}: {}",
connector_id, e
);
continue;
}
};
match Monitor::setup(
&self.card,
&self.gbm_device,
Arc::clone(&self.shared_gles_context),
connector_id,
allocation,
|request| (self.context_constructor)(request),
) {
Ok(monitor) => {
used_crtcs.insert(monitor.crtc().handle());
used_primary_planes.insert(monitor.primary_plane());
if let Some(cursor) = monitor.cursor_plane() {
used_cursor_planes.insert(cursor);
}
self.monitors.insert(connector_id, monitor);
}
Err(e) => {
eprintln!("Warning: Failed to setup monitor {:?}: {}", connector_id, e);
}
}
}
if needs_update {
self.update_refresh_rate_groups();
}
Ok(())
}
fn current_resource_usage(
&self,
) -> (
HashSet<crtc::Handle>,
HashSet<plane::Handle>,
HashSet<plane::Handle>,
) {
let mut used_crtcs = HashSet::new();
let mut used_primary_planes = HashSet::new();
let mut used_cursor_planes = HashSet::new();
for monitor in self.monitors.values() {
used_crtcs.insert(monitor.crtc().handle());
used_primary_planes.insert(monitor.primary_plane());
if let Some(cursor) = monitor.cursor_plane() {
used_cursor_planes.insert(cursor);
}
}
(used_crtcs, used_primary_planes, used_cursor_planes)
}
fn allocate_monitor_resources(
&self,
connector_id: connector::Handle,
used_crtcs: &HashSet<crtc::Handle>,
used_primary_planes: &HashSet<plane::Handle>,
used_cursor_planes: &HashSet<plane::Handle>,
) -> Result<MonitorResourceAllocation, MonitorSetupError> {
let connector = self.card.get_connector(connector_id, true)?;
if connector.state() != connector::State::Connected {
return Err(MonitorSetupError::NotConnected);
}
let res = self.card.resource_handles()?;
let crtc_candidates = self.crtc_candidates_for_connector(&connector, &res, used_crtcs)?;
let planes = self.card.plane_handles()?;
let plane_handles: Vec<plane::Handle> = planes.iter().copied().collect();
let mut crtc_info = None;
for handle in crtc_candidates {
if let Ok(info) = self.card.get_crtc(handle) {
crtc_info = Some(info);
break;
}
}
let crtc_info = crtc_info.ok_or(MonitorSetupError::NoCRTCFound)?;
let primary_plane = self
.find_plane_for_crtc(
&plane_handles,
&res,
crtc_info.handle(),
PlaneType::Primary,
used_primary_planes,
)?
.ok_or(MonitorSetupError::NoPrimaryPlaneFound)?;
let cursor_plane = self.find_plane_for_crtc(
&plane_handles,
&res,
crtc_info.handle(),
PlaneType::Cursor,
used_cursor_planes,
)?;
Ok(MonitorResourceAllocation {
crtc_info,
primary_plane,
cursor_plane,
})
}
fn crtc_candidates_for_connector(
&self,
connector: &connector::Info,
res: &drm::control::ResourceHandles,
used_crtcs: &HashSet<crtc::Handle>,
) -> Result<Vec<crtc::Handle>, MonitorSetupError> {
let mut seen = HashSet::new();
let mut candidates = Vec::new();
for encoder_handle in connector.encoders() {
let Ok(encoder) = self.card.get_encoder(*encoder_handle) else {
continue;
};
for crtc_handle in res.filter_crtcs(encoder.possible_crtcs()) {
if used_crtcs.contains(&crtc_handle) {
continue;
}
if seen.insert(crtc_handle) {
candidates.push(crtc_handle);
}
}
}
Ok(candidates)
}
fn find_plane_for_crtc(
&self,
plane_handles: &[plane::Handle],
res: &drm::control::ResourceHandles,
crtc_handle: crtc::Handle,
plane_type: PlaneType,
used_planes: &HashSet<plane::Handle>,
) -> Result<Option<plane::Handle>, MonitorSetupError> {
for plane_handle in plane_handles {
if used_planes.contains(plane_handle) {
continue;
}
let Ok(plane_info) = self.card.get_plane(*plane_handle) else {
continue;
};
let compatible_crtcs = res.filter_crtcs(plane_info.possible_crtcs());
if !compatible_crtcs.contains(&crtc_handle) {
continue;
}
if self.plane_is_type(*plane_handle, plane_type)? {
return Ok(Some(*plane_handle));
}
}
Ok(None)
}
fn plane_is_type(
&self,
plane_handle: plane::Handle,
plane_type: PlaneType,
) -> Result<bool, MonitorSetupError> {
let properties = self.card.get_properties(plane_handle)?;
for (&id, &value) in properties.iter() {
let Ok(info) = self.card.get_property(id) else {
continue;
};
if info
.name()
.to_str()
.map(|name| name == "type")
.unwrap_or(false)
{
return Ok(value == (plane_type as u32).into());
}
}
Ok(false)
}
pub fn poll_events(&mut self) -> Result<(), EasyDRMError> {
self.poll_events_ex([])
}
pub fn poll_events_ex(
&mut self,
extra_fds: impl IntoIterator<Item = RawFd>,
) -> Result<(), EasyDRMError> {
let drm_fd = self.card.as_fd();
let uevents_socket = self.uevent_socket.as_ref();
let mut fds = vec![PollFd::new(drm_fd, PollFlags::POLLIN)];
fds.extend(
extra_fds
.into_iter()
.map(|f| PollFd::new(unsafe { BorrowedFd::borrow_raw(f) }, PollFlags::POLLIN)),
);
if let Some(uevents_socket) = uevents_socket {
fds.push(PollFd::new(uevents_socket.fd.as_fd(), PollFlags::POLLIN));
}
poll(&mut fds, PollTimeout::NONE).ok();
let drm_ready = fds[0]
.revents()
.unwrap_or(PollFlags::empty())
.contains(PollFlags::POLLIN);
if let Some(uevents_socket) = uevents_socket {
let hotplug_ready = fds
.iter()
.find(|p| p.as_fd().as_raw_fd() == uevents_socket.fd.as_raw_fd())
.and_then(|p| p.revents())
.unwrap_or(PollFlags::empty())
.contains(PollFlags::POLLIN);
if hotplug_ready && uevents_socket.drain_hotplug_events().unwrap_or(false) {
println!("[INFO] Hotplug detected, refreshing monitors.");
self.handle_hotplug()?;
}
}
if drm_ready {
Self::handle_drm_events(&mut self.card, &mut self.monitors)?;
}
Ok(())
}
pub async fn poll_events_async(&mut self) -> Result<(), EasyDRMError> {
let drm_sync_fd = self.card.as_raw_fd();
let drm_async_fd = AsyncFd::with_interest(drm_sync_fd, Interest::READABLE)?;
let uevents_raw_fd = self.uevent_socket.as_ref().map(|s| s.fd.as_raw_fd());
unsafe {
fcntl(
drm_sync_fd,
F_SETFL,
fcntl(drm_sync_fd, F_GETFL) | O_NONBLOCK,
);
if let Some(fd) = uevents_raw_fd {
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
}
}
let uevents_async_fd = self
.uevent_socket
.as_ref()
.map(|s| AsyncFd::with_interest(s.fd.as_raw_fd(), Interest::READABLE))
.transpose()?;
tokio::select! {
guard = drm_async_fd.readable() => {
let mut guard = guard?;
if let Ok(r) = guard.try_io(|_fd| {
Self::handle_drm_events(&mut self.card, &mut self.monitors)
}) { r? }
}
_ = async {
if let Some(ref uevents_async_fd) = uevents_async_fd {
uevents_async_fd.readable().await
} else {
pending().await
}
} => {
if let Some(ue) = self.uevent_socket.as_ref()
&& ue.drain_hotplug_events().unwrap_or(false) {
self.handle_hotplug()?;
}
}
}
Ok(())
}
pub fn monitors(&self) -> impl Iterator<Item = &Monitor<T>> {
self.monitors.values()
}
pub fn gl(&self) -> Result<crate::gl::Gles2, GlesContextError> {
let ctx = self
.shared_gles_context
.lock()
.map_err(|_| GlesContextError::MakeCurrentFailed)?;
Ok(ctx.gl().clone())
}
pub fn make_current(&self) -> Result<(), GlesContextError> {
let ctx = self
.shared_gles_context
.lock()
.map_err(|_| GlesContextError::MakeCurrentFailed)?;
ctx.make_current_surfaceless()
}
pub fn get_proc_address(&self, symbol: &str) -> *const std::ffi::c_void {
self.shared_gles_context
.lock()
.map(|ctx| ctx.get_proc_address(symbol))
.unwrap_or(std::ptr::null())
}
pub fn egl_context(&self) -> Arc<Mutex<GlesContext>> {
Arc::clone(&self.shared_gles_context)
}
pub fn monitors_mut(&mut self) -> impl Iterator<Item = &mut Monitor<T>> {
self.monitors.values_mut()
}
pub fn get_monitor_mut(&mut self, connector_id: connector::Handle) -> Option<&mut Monitor<T>> {
self.monitors.get_mut(&connector_id)
}
pub fn get_monitor(&self, connector_id: connector::Handle) -> Option<&Monitor<T>> {
self.monitors.get(&connector_id)
}
fn create_egl_fence_fd(ctx: &GlesContext) -> Result<OwnedFd, String> {
ctx.make_current_surfaceless()
.map_err(|e| format!("failed to make current context for fence creation: {e}"))?;
let egl = ctx.display().egl();
let (fence_fd, sync) = unsafe {
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 {
egl.DestroySyncKHR(egl.GetCurrentDisplay(), sync);
return Err(format!("Failed to duplicate fence FD: fence_fd={fence_fd}"));
}
(fence_fd, sync as *mut std::ffi::c_void)
};
if !sync.is_null() {
unsafe{egl.DestroySyncKHR(egl.GetCurrentDisplay(), sync);}
}
if fence_fd < 0 {
return Err(format!("invalid fence fd: {fence_fd}"));
}
Ok(unsafe { OwnedFd::from_raw_fd(fence_fd) })
}
pub fn swap_buffers_with_result(&mut self) -> Result<SwapResult, EasyDRMError> {
let mut atomic_req = AtomicModeReq::new();
let mut flags = AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK;
let committed_connectors = self
.monitors
.iter()
.filter_map(|(&connector_id, monitor)| {
if monitor.can_render() && monitor.was_drawn() {
Some(connector_id)
} else {
None
}
})
.collect::<Vec<_>>();
if committed_connectors.is_empty() {
return Ok(SwapResult {
committed_connectors,
render_fence: -1,
});
}
self.last_fence_fd = Some({
let ctx = self
.shared_gles_context
.lock()
.map_err(|_| MonitorSetupError::DrmError("Shared GLES context poisoned".into()))?;
Self::create_egl_fence_fd(ctx.deref()).map_err(MonitorSetupError::DrmError)?
});
let fence_fd = self.last_fence_fd.as_ref().map(|f| f.as_raw_fd()).unwrap_or(-1);
for connector_id in committed_connectors.iter().copied() {
let Some(monitor) = self.monitors.get_mut(&connector_id) else {
continue;
};
if monitor.needs_mode_set() {
flags |= AtomicCommitFlags::ALLOW_MODESET;
}
monitor.populate_commit(&self.card, &mut atomic_req, self.last_fence_fd.as_ref().map(|f| f.as_raw_fd()))?;
monitor.reset_drawn_flag();
}
if !committed_connectors.is_empty() {
self
.card
.atomic_commit(flags, atomic_req)
.map_err(|e| MonitorSetupError::DrmError(format!("Failed to commit: {}", e)))?
}
for connector_id in committed_connectors.iter().copied() {
self.mark_fast_group_commit(connector_id);
}
Ok(SwapResult {
committed_connectors,
render_fence: fence_fd,
})
}
pub fn swap_buffers(&mut self) -> Result<(), EasyDRMError> {
let _ = self.swap_buffers_with_result()?;
Ok(())
}
pub fn monitor_count(&self) -> usize {
self.monitors.len()
}
pub fn has_monitors(&self) -> bool {
!self.monitors.is_empty()
}
pub fn any_can_render(&self) -> bool {
self.monitors.values().any(|m| m.can_render())
}
pub fn refresh_rate_groups(&self) -> &HashMap<u32, Vec<connector::Handle>> {
&self.refresh_rate_groups
}
pub fn should_update(&mut self) -> bool {
if self.should_update_flag {
self.should_update_flag = false;
self.reset_fastest_group_pending();
true
} else {
false
}
}
fn handle_drm_events(
card: &mut Card,
monitors: &mut HashMap<connector::Handle, Monitor<T>>,
) -> std::io::Result<()> {
for event in card.receive_events()? {
match event {
Event::PageFlip(page_flip_event) => {
let crtc_handle = page_flip_event.crtc;
for monitor in monitors.values_mut() {
if monitor.crtc().handle() == crtc_handle {
monitor.set_can_render(true);
}
}
}
_ => {}
}
}
Ok(())
}
}
impl EasyDRM<()> {
pub fn init_empty() -> Result<Self, EasyDRMError> {
Self::init(|_| ())
}
}