feat: open primary drm rendering device
This commit is contained in:
1 parent
c50dd98685
commit
c101b2cf8b
3 files changed
+179
-13
No files matched your search
@@ -1,20 +1,54 @@
|
|||||||
use std::{error::Error, io};
|
use std::{collections::HashMap, error::Error, io, path::Path, time::Duration};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
|
allocator::gbm::GbmDevice,
|
||||||
|
drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime, DrmNode},
|
||||||
|
egl::{EGLContext, EGLDisplay},
|
||||||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||||
|
renderer::gles::GlesRenderer,
|
||||||
session::{Event as SessionEvent, Session, libseat::LibSeatSession},
|
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;
|
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>> {
|
pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<(), Box<dyn Error>> {
|
||||||
let (session, notifier) = LibSeatSession::new()?;
|
let (session, notifier) = LibSeatSession::new()?;
|
||||||
let seat_name = session.seat();
|
let seat_name = session.seat();
|
||||||
let udev_backend = UdevBackend::new(&seat_name)?;
|
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 =
|
let mut libinput_context =
|
||||||
Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
|
Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
|
||||||
libinput_context
|
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}")))?;
|
.map_err(|()| io::Error::other(format!("failed to assign libinput seat {seat_name}")))?;
|
||||||
let libinput_backend = LibinputInputBackend::new(libinput_context.clone());
|
let libinput_backend = LibinputInputBackend::new(libinput_context.clone());
|
||||||
|
|
||||||
state.direct_session = Some(session);
|
state.direct_backend = Some(DirectBackendState {
|
||||||
state.session_active = true;
|
session,
|
||||||
|
active: true,
|
||||||
|
devices: HashMap::new(),
|
||||||
|
presentation: PresentationClock::default(),
|
||||||
|
});
|
||||||
|
open_primary_device(event_loop, state, &primary_path)?;
|
||||||
|
|
||||||
event_loop
|
event_loop
|
||||||
.handle()
|
.handle()
|
||||||
@@ -34,7 +73,13 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
|
|||||||
.handle()
|
.handle()
|
||||||
.insert_source(notifier, move |event, _, state| match event {
|
.insert_source(notifier, move |event, _, state| match event {
|
||||||
SessionEvent::PauseSession => {
|
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();
|
libinput_context.suspend();
|
||||||
tracing::info!("direct session paused");
|
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() {
|
if libinput_context.resume().is_err() {
|
||||||
tracing::error!("failed to resume libinput");
|
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");
|
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");
|
tracing::info!(seat = %seat_name, "initialized direct session input and device discovery");
|
||||||
Ok(())
|
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,4 +1,4 @@
|
|||||||
mod direct;
|
pub(crate) mod direct;
|
||||||
|
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
|||||||
+17
-5
@@ -11,7 +11,7 @@ use ferese_core::{WindowPlacement, WorkspaceSet};
|
|||||||
use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId};
|
use ferese_layout::{Axis, Direction, GapConfig, LayoutResult, Rect, SizeConstraints, WindowId};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::session::libseat::LibSeatSession,
|
backend::drm::DrmEventTime,
|
||||||
desktop::{PopupManager, Space, Window, WindowSurfaceType},
|
desktop::{PopupManager, Space, Window, WindowSurfaceType},
|
||||||
input::{Seat, SeatState},
|
input::{Seat, SeatState},
|
||||||
reexports::{
|
reexports::{
|
||||||
@@ -52,8 +52,7 @@ pub struct Ferese {
|
|||||||
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
pub window_geometry: HashMap<WindowId, WindowGeometry>,
|
||||||
pub focused_window: Option<WindowId>,
|
pub focused_window: Option<WindowId>,
|
||||||
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
|
pub intercepted_keys: HashSet<smithay::input::keyboard::Keycode>,
|
||||||
pub direct_session: Option<LibSeatSession>,
|
pub direct_backend: Option<crate::backends::direct::DirectBackendState>,
|
||||||
pub session_active: bool,
|
|
||||||
next_window_id: u64,
|
next_window_id: u64,
|
||||||
last_animation_tick: Instant,
|
last_animation_tick: Instant,
|
||||||
pub popups: PopupManager,
|
pub popups: PopupManager,
|
||||||
@@ -113,8 +112,7 @@ impl Ferese {
|
|||||||
window_geometry: HashMap::new(),
|
window_geometry: HashMap::new(),
|
||||||
focused_window: None,
|
focused_window: None,
|
||||||
intercepted_keys: HashSet::new(),
|
intercepted_keys: HashSet::new(),
|
||||||
direct_session: None,
|
direct_backend: None,
|
||||||
session_active: true,
|
|
||||||
next_window_id: 1,
|
next_window_id: 1,
|
||||||
last_animation_tick: start_time,
|
last_animation_tick: start_time,
|
||||||
popups: PopupManager::default(),
|
popups: PopupManager::default(),
|
||||||
@@ -382,6 +380,20 @@ impl Ferese {
|
|||||||
pub fn advance_animations(&mut self, now: Instant) -> bool {
|
pub fn advance_animations(&mut self, now: Instant) -> bool {
|
||||||
let delta = now.saturating_duration_since(self.last_animation_tick);
|
let delta = now.saturating_duration_since(self.last_animation_tick);
|
||||||
self.last_animation_tick = now;
|
self.last_animation_tick = now;
|
||||||
|
self.advance_animations_by(delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn record_drm_presentation(&mut self, time: DrmEventTime, sequence: u32) {
|
||||||
|
let delta = self
|
||||||
|
.direct_backend
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|backend| backend.record_presentation(time, sequence));
|
||||||
|
if let Some(delta) = delta {
|
||||||
|
self.advance_animations_by(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_animations_by(&mut self, delta: std::time::Duration) -> bool {
|
||||||
let windows = self
|
let windows = self
|
||||||
.space
|
.space
|
||||||
.elements()
|
.elements()
|
||||||
|
|||||||
Reference in new issue
Block a user