feat: open primary drm rendering device

This commit is contained in:
Samuel Onoja committed 2026-09-24 16:20:27 +01:00
1 parent c50dd98685
commit c101b2cf8b
3 files changed
+179 -13

No files matched your search

+161 -7
View File
@@ -1,20 +1,54 @@
use std::{error::Error, io};
use std::{collections::HashMap, error::Error, io, path::Path, time::Duration};
use smithay::{
backend::{
allocator::gbm::GbmDevice,
drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime, DrmNode},
egl::{EGLContext, EGLDisplay},
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::gles::GlesRenderer,
session::{Event as SessionEvent, Session, libseat::LibSeatSession},
udev::{UdevBackend, UdevEvent},
udev::{UdevBackend, UdevEvent, primary_gpu},
},
reexports::{calloop::EventLoop, input::Libinput},
reexports::{calloop::EventLoop, input::Libinput, rustix::fs::OFlags},
utils::DeviceFd,
};
use crate::Ferese;
pub struct DirectBackendState {
pub session: LibSeatSession,
pub active: bool,
devices: HashMap<DrmNode, DirectDevice>,
presentation: PresentationClock,
}
#[derive(Default)]
struct PresentationClock {
last_presentation: Option<Duration>,
presented_frames: u64,
}
struct DirectDevice {
drm: DrmDevice,
#[allow(dead_code)]
gbm: GbmDevice<DrmDeviceFd>,
#[allow(dead_code)]
renderer: GlesRenderer,
}
pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<(), Box<dyn Error>> {
let (session, notifier) = LibSeatSession::new()?;
let seat_name = session.seat();
let udev_backend = UdevBackend::new(&seat_name)?;
let primary_path = primary_gpu(&seat_name)?
.or_else(|| {
udev_backend
.device_list()
.next()
.map(|(_, path)| path.to_owned())
})
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no DRM device found"))?;
let mut libinput_context =
Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
libinput_context
@@ -22,8 +56,13 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
.map_err(|()| io::Error::other(format!("failed to assign libinput seat {seat_name}")))?;
let libinput_backend = LibinputInputBackend::new(libinput_context.clone());
state.direct_session = Some(session);
state.session_active = true;
state.direct_backend = Some(DirectBackendState {
session,
active: true,
devices: HashMap::new(),
presentation: PresentationClock::default(),
});
open_primary_device(event_loop, state, &primary_path)?;
event_loop
.handle()
@@ -34,7 +73,13 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
.handle()
.insert_source(notifier, move |event, _, state| match event {
SessionEvent::PauseSession => {
state.session_active = false;
if let Some(backend) = state.direct_backend.as_mut() {
backend.active = false;
backend
.devices
.values_mut()
.for_each(|device| device.drm.pause());
}
libinput_context.suspend();
tracing::info!("direct session paused");
}
@@ -42,7 +87,14 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
if libinput_context.resume().is_err() {
tracing::error!("failed to resume libinput");
}
state.session_active = true;
if let Some(backend) = state.direct_backend.as_mut() {
backend.active = true;
for device in backend.devices.values_mut() {
if let Err(error) = device.drm.activate(false) {
tracing::error!(%error, "failed to reactivate DRM device");
}
}
}
tracing::info!("direct session activated");
}
})?;
@@ -63,3 +115,105 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
tracing::info!(seat = %seat_name, "initialized direct session input and device discovery");
Ok(())
}
fn open_primary_device(
event_loop: &mut EventLoop<Ferese>,
state: &mut Ferese,
path: &Path,
) -> Result<(), Box<dyn Error>> {
let backend = state
.direct_backend
.as_mut()
.expect("direct backend state is initialized before the DRM device");
let node = DrmNode::from_path(path)?;
let fd = backend.session.open(
path,
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
)?;
let fd = DrmDeviceFd::new(DeviceFd::from(fd));
let (drm, notifier) = DrmDevice::new(fd.clone(), true)?;
let gbm = GbmDevice::new(fd)?;
// SAFETY: GBM owns a valid DRM descriptor for the lifetime of the EGL display.
let egl_display = unsafe { EGLDisplay::new(gbm.clone())? };
let egl_context = EGLContext::new(&egl_display)?;
// SAFETY: the new context is not current on another thread and remains renderer-owned.
let renderer = unsafe { GlesRenderer::new(egl_context)? };
event_loop
.handle()
.insert_source(notifier, move |event, metadata, state| match event {
DrmEvent::VBlank(crtc) => {
if let Some(metadata) = metadata {
state.record_drm_presentation(metadata.time, metadata.sequence);
tracing::trace!(?node, ?crtc, sequence = metadata.sequence, "page flip");
}
}
DrmEvent::Error(error) => {
tracing::error!(?node, %error, "DRM event error");
}
})?;
backend
.devices
.insert(node, DirectDevice { drm, gbm, renderer });
tracing::info!(?node, ?path, "initialized primary DRM/GBM device");
Ok(())
}
impl DirectBackendState {
pub fn record_presentation(&mut self, time: DrmEventTime, sequence: u32) -> Option<Duration> {
self.presentation.record(time, sequence)
}
}
impl PresentationClock {
fn record(&mut self, time: DrmEventTime, sequence: u32) -> Option<Duration> {
let DrmEventTime::Monotonic(time) = time else {
tracing::warn!(
sequence,
"DRM driver reported a realtime page-flip timestamp"
);
return None;
};
let delta = self
.last_presentation
.map(|previous| time.saturating_sub(previous));
self.last_presentation = Some(time);
self.presented_frames = self.presented_frames.saturating_add(1);
delta
}
}
#[cfg(test)]
mod tests {
use std::time::SystemTime;
use super::*;
#[test]
fn presentation_clock_uses_measured_monotonic_deltas() {
let mut clock = PresentationClock::default();
assert_eq!(
clock.record(DrmEventTime::Monotonic(Duration::from_millis(100)), 1),
None
);
assert_eq!(
clock.record(DrmEventTime::Monotonic(Duration::from_millis(116)), 2),
Some(Duration::from_millis(16))
);
assert_eq!(clock.presented_frames, 2);
}
#[test]
fn presentation_clock_rejects_realtime_timestamps() {
let mut clock = PresentationClock::default();
assert_eq!(
clock.record(DrmEventTime::Realtime(SystemTime::now()), 1),
None
);
assert_eq!(clock.presented_frames, 0);
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
mod direct;
pub(crate) mod direct;
use std::ffi::OsString;