🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668
use std::{
collections::hash_map::HashMap,
io,
ops::Not,
path::Path,
sync::{atomic::Ordering, Mutex, Once},
time::{Duration, Instant},
};
use crate::{
drawing::*,
render::*,
shell::WindowElement,
state::{take_presentation_feedback, update_primary_scanout_output, AnvilState, Backend},
};
use crate::{
shell::WindowRenderElement,
state::{DndIcon, SurfaceDmabufFeedback},
};
#[cfg(feature = "renderer_sync")]
use smithay::backend::drm::compositor::PrimaryPlaneElement;
#[cfg(feature = "egl")]
use smithay::backend::renderer::ImportEgl;
#[cfg(feature = "debug")]
use smithay::backend::renderer::{multigpu::MultiTexture, ImportMem};
use smithay::{
backend::{
allocator::{
dmabuf::Dmabuf,
format::FormatSet,
gbm::{GbmAllocator, GbmBufferFlags, GbmDevice},
Fourcc, Modifier,
},
drm::{
compositor::{DrmCompositor, FrameFlags},
exporter::gbm::GbmFramebufferExporter,
output::{DrmOutput, DrmOutputManager, DrmOutputRenderElements},
CreateDrmNodeError, DrmAccessError, DrmDevice, DrmDeviceFd, DrmError, DrmEvent, DrmEventMetadata,
DrmEventTime, DrmNode, DrmSurface, GbmBufferedSurface, NodeType,
},
egl::{self, context::ContextPriority, EGLDevice, EGLDisplay},
input::InputEvent,
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{
damage::Error as OutputDamageTrackerError,
element::{memory::MemoryRenderBuffer, AsRenderElements, RenderElementStates},
gles::GlesRenderer,
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer},
DebugFlags, ImportDma, ImportMemWl,
},
session::{
libseat::{self, LibSeatSession},
Event as SessionEvent, Session,
},
udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent},
SwapBuffersError,
},
delegate_dmabuf, delegate_drm_lease,
desktop::{
space::{Space, SurfaceTree},
utils::OutputPresentationFeedback,
},
input::{
keyboard::LedState,
pointer::{CursorImageAttributes, CursorImageStatus},
},
output::{Mode as WlMode, Output, PhysicalProperties},
reexports::{
calloop::{
timer::{TimeoutAction, Timer},
EventLoop, RegistrationToken,
},
drm::{
control::{connector, crtc, Device, ModeTypeFlags},
Device as _,
},
input::{DeviceCapability, Libinput},
rustix::fs::OFlags,
wayland_protocols::wp::{
linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1,
presentation_time::server::wp_presentation_feedback,
},
wayland_server::{backend::GlobalId, protocol::wl_surface, Display, DisplayHandle},
},
utils::{DeviceFd, IsAlive, Logical, Monotonic, Point, Scale, Time, Transform},
wayland::{
compositor,
dmabuf::{DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier},
drm_lease::{
DrmLease, DrmLeaseBuilder, DrmLeaseHandler, DrmLeaseRequest, DrmLeaseState, LeaseRejected,
},
drm_syncobj::{supports_syncobj_eventfd, DrmSyncobjHandler, DrmSyncobjState},
presentation::Refresh,
},
};
use smithay_drm_extras::{
display_info,
drm_scanner::{DrmScanEvent, DrmScanner},
};
use tracing::{debug, error, info, trace, warn};
// we cannot simply pick the first supported format of the intersection of *all* formats, because:
// - we do not want something like Abgr4444, which looses color information, if something better is available
// - some formats might perform terribly
// - we might need some work-arounds, if one supports modifiers, but the other does not
//
// So lets just pick `ARGB2101010` (10-bit) or `ARGB8888` (8-bit) for now, they are widely supported.
const SUPPORTED_FORMATS: &[Fourcc] = &[
Fourcc::Abgr2101010,
Fourcc::Argb2101010,
Fourcc::Abgr8888,
Fourcc::Argb8888,
];
const SUPPORTED_FORMATS_8BIT_ONLY: &[Fourcc] = &[Fourcc::Abgr8888, Fourcc::Argb8888];
type UdevRenderer<'a> = MultiRenderer<
'a,
'a,
GbmGlesBackend<GlesRenderer, DrmDeviceFd>,
GbmGlesBackend<GlesRenderer, DrmDeviceFd>,
>;
#[derive(Debug, PartialEq)]
struct UdevOutputId {
device_id: DrmNode,
crtc: crtc::Handle,
}
pub struct UdevData {
pub session: LibSeatSession,
dh: DisplayHandle,
dmabuf_state: Option<(DmabufState, DmabufGlobal)>,
syncobj_state: Option<DrmSyncobjState>,
primary_gpu: DrmNode,
gpus: GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>,
backends: HashMap<DrmNode, BackendData>,
pointer_images: Vec<(xcursor::parser::Image, MemoryRenderBuffer)>,
pointer_element: PointerElement,
#[cfg(feature = "debug")]
fps_texture: Option<MultiTexture>,
pointer_image: crate::cursor::Cursor,
debug_flags: DebugFlags,
keyboards: Vec<smithay::reexports::input::Device>,
}
impl UdevData {
pub fn set_debug_flags(&mut self, flags: DebugFlags) {
if self.debug_flags != flags {
self.debug_flags = flags;
for (_, backend) in self.backends.iter_mut() {
for (_, surface) in backend.surfaces.iter_mut() {
surface.drm_output.set_debug_flags(flags);
}
}
}
}
pub fn debug_flags(&self) -> DebugFlags {
self.debug_flags
}
}
impl DmabufHandler for AnvilState<UdevData> {
fn dmabuf_state(&mut self) -> &mut DmabufState {
&mut self.backend_data.dmabuf_state.as_mut().unwrap().0
}
fn dmabuf_imported(&mut self, _global: &DmabufGlobal, dmabuf: Dmabuf, notifier: ImportNotifier) {
if self
.backend_data
.gpus
.single_renderer(&self.backend_data.primary_gpu)
.and_then(|mut renderer| renderer.import_dmabuf(&dmabuf, None))
.is_ok()
{
dmabuf.set_node(self.backend_data.primary_gpu);
let _ = notifier.successful::<AnvilState<UdevData>>();
} else {
notifier.failed();
}
}
}
delegate_dmabuf!(AnvilState<UdevData>);
impl Backend for UdevData {
const HAS_RELATIVE_MOTION: bool = true;
const HAS_GESTURES: bool = true;
fn seat_name(&self) -> String {
self.session.seat()
}
fn reset_buffers(&mut self, output: &Output) {
if let Some(id) = output.user_data().get::<UdevOutputId>() {
if let Some(gpu) = self.backends.get_mut(&id.device_id) {
if let Some(surface) = gpu.surfaces.get_mut(&id.crtc) {
surface.drm_output.reset_buffers();
}
}
}
}
fn early_import(&mut self, surface: &wl_surface::WlSurface) {
if let Err(err) = self.gpus.early_import(self.primary_gpu, surface) {
warn!("Early buffer import failed: {}", err);
}
}
fn update_led_state(&mut self, led_state: LedState) {
for keyboard in self.keyboards.iter_mut() {
keyboard.led_update(led_state.into());
}
}
}
pub fn run_udev() {
let mut event_loop = EventLoop::try_new().unwrap();
let display = Display::new().unwrap();
let mut display_handle = display.handle();
/*
* Initialize session
*/
let (session, notifier) = match LibSeatSession::new() {
Ok(ret) => ret,
Err(err) => {
error!("Could not initialize a session: {}", err);
return;
}
};
/*
* Initialize the compositor
*/
let primary_gpu = if let Ok(var) = std::env::var("ANVIL_DRM_DEVICE") {
DrmNode::from_path(var).expect("Invalid drm device path")
} else {
primary_gpu(session.seat())
.unwrap()
.and_then(|x| DrmNode::from_path(x).ok()?.node_with_type(NodeType::Render)?.ok())
.unwrap_or_else(|| {
all_gpus(session.seat())
.unwrap()
.into_iter()
.find_map(|x| DrmNode::from_path(x).ok())
.expect("No GPU!")
})
};
info!("Using {} as primary gpu.", primary_gpu);
let gpus = GpuManager::new(GbmGlesBackend::with_context_priority(ContextPriority::High)).unwrap();
let data = UdevData {
dh: display_handle.clone(),
dmabuf_state: None,
syncobj_state: None,
session,
primary_gpu,
gpus,
backends: HashMap::new(),
pointer_image: crate::cursor::Cursor::load(),
pointer_images: Vec::new(),
pointer_element: PointerElement::default(),
#[cfg(feature = "debug")]
fps_texture: None,
debug_flags: DebugFlags::empty(),
keyboards: Vec::new(),
};
let mut state = AnvilState::init(display, event_loop.handle(), data, true);
/*
* Initialize the udev backend
*/
let udev_backend = match UdevBackend::new(&state.seat_name) {
Ok(ret) => ret,
Err(err) => {
error!(error = ?err, "Failed to initialize udev backend");
return;
}
};
/*
* Initialize libinput backend
*/
let mut libinput_context = Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(
state.backend_data.session.clone().into(),
);
libinput_context.udev_assign_seat(&state.seat_name).unwrap();
let libinput_backend = LibinputInputBackend::new(libinput_context.clone());
/*
* Bind all our objects that get driven by the event loop
*/
event_loop
.handle()
.insert_source(libinput_backend, move |mut event, _, data| {
let dh = data.backend_data.dh.clone();
if let InputEvent::DeviceAdded { device } = &mut event {
if device.has_capability(DeviceCapability::Keyboard) {
if let Some(led_state) = data.seat.get_keyboard().map(|keyboard| keyboard.led_state()) {
device.led_update(led_state.into());
}
data.backend_data.keyboards.push(device.clone());
}
} else if let InputEvent::DeviceRemoved { ref device } = event {
if device.has_capability(DeviceCapability::Keyboard) {
data.backend_data.keyboards.retain(|item| item != device);
}
}
data.process_input_event(&dh, event)
})
.unwrap();
event_loop
.handle()
.insert_source(notifier, move |event, &mut (), data| match event {
SessionEvent::PauseSession => {
libinput_context.suspend();
info!("pausing session");
for backend in data.backend_data.backends.values_mut() {
backend.drm_output_manager.pause();
backend.active_leases.clear();
if let Some(lease_global) = backend.leasing_global.as_mut() {
lease_global.suspend();
}
}
}
SessionEvent::ActivateSession => {
info!("resuming session");
if let Err(err) = libinput_context.resume() {
error!("Failed to resume libinput context: {:?}", err);
}
for (node, backend) in data
.backend_data
.backends
.iter_mut()
.map(|(handle, backend)| (*handle, backend))
{
// if we do not care about flicking (caused by modesetting) we could just
// pass true for disable connectors here. this would make sure our drm
// device is in a known state (all connectors and planes disabled).
// but for demonstration we choose a more optimistic path by leaving the
// state as is and assume it will just work. If this assumption fails
// we will try to reset the state when trying to queue a frame.
backend
.drm_output_manager
.activate(false)
.expect("failed to activate drm backend");
if let Some(lease_global) = backend.leasing_global.as_mut() {
lease_global.resume::<AnvilState<UdevData>>();
}
data.handle
.insert_idle(move |data| data.render(node, None, data.clock.now()));
}
}
})
.unwrap();
// We try to initialize the primary node before others to make sure
// any display only node can fall back to the primary node for rendering
let primary_node = primary_gpu
.node_with_type(NodeType::Primary)
.and_then(|node| node.ok());
let primary_device = udev_backend.device_list().find(|(device_id, _)| {
primary_node
.map(|primary_node| *device_id == primary_node.dev_id())
.unwrap_or(false)
|| *device_id == primary_gpu.dev_id()
});
if let Some((device_id, path)) = primary_device {
let node = DrmNode::from_dev_id(device_id).expect("failed to get primary node");
state
.device_added(node, path)
.expect("failed to initialize primary node");
}
let primary_device_id = primary_device.map(|(device_id, _)| device_id);
for (device_id, path) in udev_backend.device_list() {
if Some(device_id) == primary_device_id {
continue;
}
if let Err(err) = DrmNode::from_dev_id(device_id)
.map_err(DeviceAddError::DrmNode)
.and_then(|node| state.device_added(node, path))
{
error!("Skipping device {device_id}: {err}");
}
}
state.shm_state.update_formats(
state
.backend_data
.gpus
.single_renderer(&primary_gpu)
.unwrap()
.shm_formats(),
);
#[cfg_attr(not(feature = "egl"), allow(unused_mut))]
let mut renderer = state.backend_data.gpus.single_renderer(&primary_gpu).unwrap();
#[cfg(feature = "debug")]
{
#[allow(deprecated)]
let fps_image =
image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png)
.decode()
.unwrap();
let fps_texture = renderer
.import_memory(
&fps_image.to_rgba8(),
Fourcc::Abgr8888,
(fps_image.width() as i32, fps_image.height() as i32).into(),
false,
)
.expect("Unable to upload FPS texture");
for backend in state.backend_data.backends.values_mut() {
for surface in backend.surfaces.values_mut() {
surface.fps_element = Some(FpsElement::new(fps_texture.clone()));
}
}
state.backend_data.fps_texture = Some(fps_texture);
}
#[cfg(feature = "egl")]
{
info!(?primary_gpu, "Trying to initialize EGL Hardware Acceleration",);
match renderer.bind_wl_display(&display_handle) {
Ok(_) => info!("EGL hardware-acceleration enabled"),
Err(err) => info!(?err, "Failed to initialize EGL hardware-acceleration"),
}
}
// init dmabuf support with format list from our primary gpu
let dmabuf_formats = renderer.dmabuf_formats();
let default_feedback = DmabufFeedbackBuilder::new(primary_gpu.dev_id(), dmabuf_formats)
.build()
.unwrap();
let mut dmabuf_state = DmabufState::new();
let global = dmabuf_state
.create_global_with_default_feedback::<AnvilState<UdevData>>(&display_handle, &default_feedback);
state.backend_data.dmabuf_state = Some((dmabuf_state, global));
let gpus = &mut state.backend_data.gpus;
state
.backend_data
.backends
.iter_mut()
.for_each(|(node, backend_data)| {
// Update the per drm surface dmabuf feedback
backend_data.surfaces.values_mut().for_each(|surface_data| {
surface_data.dmabuf_feedback = surface_data.dmabuf_feedback.take().or_else(|| {
surface_data.drm_output.with_compositor(|compositor| {
get_surface_dmabuf_feedback(
primary_gpu,
surface_data.render_node,
*node,
gpus,
compositor.surface(),
)
})
});
});
});
// Expose syncobj protocol if supported by primary GPU
if let Some(primary_node) = state
.backend_data
.primary_gpu
.node_with_type(NodeType::Primary)
.and_then(|x| x.ok())
{
if let Some(backend) = state.backend_data.backends.get(&primary_node) {
let import_device = backend.drm_output_manager.device().device_fd().clone();
if supports_syncobj_eventfd(&import_device) {
let syncobj_state =
DrmSyncobjState::new::<AnvilState<UdevData>>(&display_handle, import_device);
state.backend_data.syncobj_state = Some(syncobj_state);
}
}
}
event_loop
.handle()
.insert_source(udev_backend, move |event, _, data| match event {
UdevEvent::Added { device_id, path } => {
if let Err(err) = DrmNode::from_dev_id(device_id)
.map_err(DeviceAddError::DrmNode)
.and_then(|node| data.device_added(node, &path))
{
error!("Skipping device {device_id}: {err}");
}
}
UdevEvent::Changed { device_id } => {
if let Ok(node) = DrmNode::from_dev_id(device_id) {
data.device_changed(node)
}
}
UdevEvent::Removed { device_id } => {
if let Ok(node) = DrmNode::from_dev_id(device_id) {
data.device_removed(node)
}
}
})
.unwrap();
/*
* Start XWayland if supported
*/
#[cfg(feature = "xwayland")]
state.start_xwayland();
/*
* And run our loop
*/
while state.running.load(Ordering::SeqCst) {
let result = event_loop.dispatch(Some(Duration::from_millis(16)), &mut state);
if result.is_err() {
state.running.store(false, Ordering::SeqCst);
} else {
state.space.refresh();
state.popups.cleanup();
display_handle.flush_clients().unwrap();
}
}
}
impl DrmLeaseHandler for AnvilState<UdevData> {
fn drm_lease_state(&mut self, node: DrmNode) -> &mut DrmLeaseState {
self.backend_data
.backends
.get_mut(&node)
.unwrap()
.leasing_global
.as_mut()
.unwrap()
}
fn lease_request(
&mut self,
node: DrmNode,
request: DrmLeaseRequest,
) -> Result<DrmLeaseBuilder, LeaseRejected> {
let backend = self
.backend_data
.backends
.get(&node)
.ok_or(LeaseRejected::default())?;
let drm_device = backend.drm_output_manager.device();
let mut builder = DrmLeaseBuilder::new(drm_device);
for conn in request.connectors {
if let Some((_, crtc)) = backend
.non_desktop_connectors
.iter()
.find(|(handle, _)| *handle == conn)
{
builder.add_connector(conn);
builder.add_crtc(*crtc);
let planes = drm_device.planes(crtc).map_err(LeaseRejected::with_cause)?;
let (primary_plane, primary_plane_claim) = planes
.primary
.iter()
.find_map(|plane| {
drm_device
.claim_plane(plane.handle, *crtc)
.map(|claim| (plane, claim))
})
.ok_or_else(LeaseRejected::default)?;
builder.add_plane(primary_plane.handle, primary_plane_claim);
if let Some((cursor, claim)) = planes.cursor.iter().find_map(|plane| {
drm_device
.claim_plane(plane.handle, *crtc)
.map(|claim| (plane, claim))
}) {
builder.add_plane(cursor.handle, claim);
}
} else {
tracing::warn!(?conn, "Lease requested for desktop connector, denying request");
return Err(LeaseRejected::default());
}
}
Ok(builder)
}
fn new_active_lease(&mut self, node: DrmNode, lease: DrmLease) {
let backend = self.backend_data.backends.get_mut(&node).unwrap();
backend.active_leases.push(lease);
}
fn lease_destroyed(&mut self, node: DrmNode, lease: u32) {
let backend = self.backend_data.backends.get_mut(&node).unwrap();
backend.active_leases.retain(|l| l.id() != lease);
}
}
delegate_drm_lease!(AnvilState<UdevData>);
impl DrmSyncobjHandler for AnvilState<UdevData> {
fn drm_syncobj_state(&mut self) -> Option<&mut DrmSyncobjState> {
self.backend_data.syncobj_state.as_mut()
}
}
smithay::delegate_drm_syncobj!(AnvilState<UdevData>);
pub type RenderSurface = GbmBufferedSurface<GbmAllocator<DrmDeviceFd>, Option<OutputPresentationFeedback>>;
pub type GbmDrmCompositor = DrmCompositor<
GbmAllocator<DrmDeviceFd>,
GbmDevice<DrmDeviceFd>,
Option<OutputPresentationFeedback>,
DrmDeviceFd,
>;
struct SurfaceData {
dh: DisplayHandle,
device_id: DrmNode,
render_node: Option<DrmNode>,
global: Option<GlobalId>,
drm_output: DrmOutput<
GbmAllocator<DrmDeviceFd>,
GbmFramebufferExporter<DrmDeviceFd>,
Option<OutputPresentationFeedback>,
DrmDeviceFd,
>,
disable_direct_scanout: bool,
#[cfg(feature = "debug")]
fps: fps_ticker::Fps,
#[cfg(feature = "debug")]
fps_element: Option<FpsElement<MultiTexture>>,
dmabuf_feedback: Option<SurfaceDmabufFeedback>,
last_presentation_time: Option<Time<Monotonic>>,
vblank_throttle_timer: Option<RegistrationToken>,
}
impl Drop for SurfaceData {
fn drop(&mut self) {
if let Some(global) = self.global.take() {
self.dh.remove_global::<AnvilState<UdevData>>(global);
}
}
}
struct BackendData {
surfaces: HashMap<crtc::Handle, SurfaceData>,
non_desktop_connectors: Vec<(connector::Handle, crtc::Handle)>,
leasing_global: Option<DrmLeaseState>,
active_leases: Vec<DrmLease>,
drm_output_manager: DrmOutputManager<
GbmAllocator<DrmDeviceFd>,
GbmFramebufferExporter<DrmDeviceFd>,
Option<OutputPresentationFeedback>,
DrmDeviceFd,
>,
drm_scanner: DrmScanner,
render_node: Option<DrmNode>,
registration_token: RegistrationToken,
}
#[derive(Debug, thiserror::Error)]
enum DeviceAddError {
#[error("Failed to open device using libseat: {0}")]
DeviceOpen(libseat::Error),
#[error("Failed to initialize drm device: {0}")]
DrmDevice(DrmError),
#[error("Failed to initialize gbm device: {0}")]
GbmDevice(std::io::Error),
#[error("Failed to access drm node: {0}")]
DrmNode(CreateDrmNodeError),
#[error("Failed to add device to GpuManager: {0}")]
AddNode(egl::Error),
#[error("The device has no render node")]
NoRenderNode,
#[error("Primary GPU is missing")]
PrimaryGpuMissing,
}
fn get_surface_dmabuf_feedback(
primary_gpu: DrmNode,
render_node: Option<DrmNode>,
scanout_node: DrmNode,
gpus: &mut GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>,
surface: &DrmSurface,
) -> Option<SurfaceDmabufFeedback> {
let primary_formats = gpus.single_renderer(&primary_gpu).ok()?.dmabuf_formats();
let render_formats = if let Some(render_node) = render_node {
gpus.single_renderer(&render_node).ok()?.dmabuf_formats()
} else {
FormatSet::default()
};
let all_render_formats = primary_formats
.iter()
.chain(render_formats.iter())
.copied()
.collect::<FormatSet>();
let planes = surface.planes().clone();
// We limit the scan-out tranche to formats we can also render from
// so that there is always a fallback render path available in case
// the supplied buffer can not be scanned out directly
let planes_formats = surface
.plane_info()
.formats
.iter()
.copied()
.chain(planes.overlay.into_iter().flat_map(|p| p.formats))
.collect::<FormatSet>()
.intersection(&all_render_formats)
.copied()
.collect::<FormatSet>();
let builder = DmabufFeedbackBuilder::new(primary_gpu.dev_id(), primary_formats);
let render_feedback = if let Some(render_node) = render_node {
builder
.clone()
.add_preference_tranche(render_node.dev_id(), None, render_formats.clone())
.build()
.unwrap()
} else {
builder.clone().build().unwrap()
};
let scanout_feedback = builder
.add_preference_tranche(
surface.device_fd().dev_id().unwrap(),
Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout),
planes_formats,
)
.add_preference_tranche(scanout_node.dev_id(), None, render_formats)
.build()
.unwrap();
Some(SurfaceDmabufFeedback {
render_feedback,
scanout_feedback,
})
}
impl AnvilState<UdevData> {
fn device_added(&mut self, node: DrmNode, path: &Path) -> Result<(), DeviceAddError> {
// Try to open the device
let fd = self
.backend_data
.session
.open(
path,
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
)
.map_err(DeviceAddError::DeviceOpen)?;
let fd = DrmDeviceFd::new(DeviceFd::from(fd));
let (drm, notifier) = DrmDevice::new(fd.clone(), true).map_err(DeviceAddError::DrmDevice)?;
let gbm = GbmDevice::new(fd).map_err(DeviceAddError::GbmDevice)?;
let registration_token = self
.handle
.insert_source(
notifier,
move |event, metadata, data: &mut AnvilState<_>| match event {
DrmEvent::VBlank(crtc) => {
profiling::scope!("vblank", &format!("{crtc:?}"));
data.frame_finish(node, crtc, metadata);
}
DrmEvent::Error(error) => {
error!("{:?}", error);
}
},
)
.unwrap();
let mut try_initialize_gpu = || {
let display = unsafe { EGLDisplay::new(gbm.clone()).map_err(DeviceAddError::AddNode)? };
let egl_device = EGLDevice::device_for_display(&display).map_err(DeviceAddError::AddNode)?;
if egl_device.is_software() {
return Err(DeviceAddError::NoRenderNode);
}
let render_node = egl_device.try_get_render_node().ok().flatten().unwrap_or(node);
self.backend_data
.gpus
.as_mut()
.add_node(render_node, gbm.clone())
.map_err(DeviceAddError::AddNode)?;
std::result::Result::<DrmNode, DeviceAddError>::Ok(render_node)
};
let render_node = try_initialize_gpu()
.inspect_err(|err| {
warn!(?err, "failed to initialize gpu");
})
.ok();
let allocator = render_node
.is_some()
.then(|| GbmAllocator::new(gbm.clone(), GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT))
.or_else(|| {
self.backend_data
.backends
.get(&self.backend_data.primary_gpu)
.or_else(|| {
self.backend_data
.backends
.values()
.find(|backend| backend.render_node == Some(self.backend_data.primary_gpu))
})
.map(|backend| backend.drm_output_manager.allocator().clone())
})
.ok_or(DeviceAddError::PrimaryGpuMissing)?;
let framebuffer_exporter = GbmFramebufferExporter::new(gbm.clone(), render_node);
let color_formats = if std::env::var("ANVIL_DISABLE_10BIT").is_ok() {
SUPPORTED_FORMATS_8BIT_ONLY
} else {
SUPPORTED_FORMATS
};
let mut renderer = self
.backend_data
.gpus
.single_renderer(&render_node.unwrap_or(self.backend_data.primary_gpu))
.unwrap();
let render_formats = renderer
.as_mut()
.egl_context()
.dmabuf_render_formats()
.iter()
.filter(|format| render_node.is_some() || format.modifier == Modifier::Linear)
.copied()
.collect::<FormatSet>();
let drm_output_manager = DrmOutputManager::new(
drm,
allocator,
framebuffer_exporter,
Some(gbm),
color_formats.iter().copied(),
render_formats,
);
self.backend_data.backends.insert(
node,
BackendData {
registration_token,
drm_output_manager,
drm_scanner: DrmScanner::new(),
non_desktop_connectors: Vec::new(),
render_node,
surfaces: HashMap::new(),
leasing_global: DrmLeaseState::new::<AnvilState<UdevData>>(&self.display_handle, &node)
.inspect_err(|err| {
warn!(?err, "Failed to initialize drm lease global for: {}", node);
})
.ok(),
active_leases: Vec::new(),
},
);
self.device_changed(node);
Ok(())
}
fn connector_connected(&mut self, node: DrmNode, connector: connector::Info, crtc: crtc::Handle) {
let device = if let Some(device) = self.backend_data.backends.get_mut(&node) {
device
} else {
return;
};
let render_node = device.render_node.unwrap_or(self.backend_data.primary_gpu);
let mut renderer = self.backend_data.gpus.single_renderer(&render_node).unwrap();
let output_name = format!("{}-{}", connector.interface().as_str(), connector.interface_id());
info!(?crtc, "Trying to setup connector {}", output_name,);
let drm_device = device.drm_output_manager.device();
let non_desktop = drm_device
.get_properties(connector.handle())
.ok()
.and_then(|props| {
let (info, value) = props
.into_iter()
.filter_map(|(handle, value)| {
let info = drm_device.get_property(handle).ok()?;
Some((info, value))
})
.find(|(info, _)| info.name().to_str() == Ok("non-desktop"))?;
info.value_type().convert_value(value).as_boolean()
})
.unwrap_or(false);
let display_info = display_info::for_connector(drm_device, connector.handle());
let make = display_info
.as_ref()
.and_then(|info| info.make())
.unwrap_or_else(|| "Unknown".into());
let model = display_info
.as_ref()
.and_then(|info| info.model())
.unwrap_or_else(|| "Unknown".into());
if non_desktop {
info!("Connector {} is non-desktop, setting up for leasing", output_name);
device.non_desktop_connectors.push((connector.handle(), crtc));
if let Some(lease_state) = device.leasing_global.as_mut() {
lease_state.add_connector::<AnvilState<UdevData>>(
connector.handle(),
output_name,
format!("{} {}", make, model),
);
}
} else {
let mode_id = connector
.modes()
.iter()
.position(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
.unwrap_or(0);
let drm_mode = connector.modes()[mode_id];
let wl_mode = WlMode::from(drm_mode);
let (phys_w, phys_h) = connector.size().unwrap_or((0, 0));
let output = Output::new(
output_name,
PhysicalProperties {
size: (phys_w as i32, phys_h as i32).into(),
subpixel: connector.subpixel().into(),
make,
model,
},
);
let global = output.create_global::<AnvilState<UdevData>>(&self.display_handle);
let x = self
.space
.outputs()
.fold(0, |acc, o| acc + self.space.output_geometry(o).unwrap().size.w);
let position = (x, 0).into();
output.set_preferred(wl_mode);
output.change_current_state(Some(wl_mode), None, None, Some(position));
self.space.map_output(&output, position);
output.user_data().insert_if_missing(|| UdevOutputId {
crtc,
device_id: node,
});
#[cfg(feature = "debug")]
let fps_element = self.backend_data.fps_texture.clone().map(FpsElement::new);
let driver = match drm_device.get_driver() {
Ok(driver) => driver,
Err(err) => {
warn!("Failed to query drm driver: {}", err);
return;
}
};
let mut planes = match drm_device.planes(&crtc) {
Ok(planes) => planes,
Err(err) => {
warn!("Failed to query crtc planes: {}", err);
return;
}
};
// Using an overlay plane on a nvidia card breaks
if driver.name().to_string_lossy().to_lowercase().contains("nvidia")
|| driver
.description()
.to_string_lossy()
.to_lowercase()
.contains("nvidia")
{
planes.overlay = vec![];
}
let drm_output = match device
.drm_output_manager
.initialize_output::<_, OutputRenderElements<UdevRenderer<'_>, WindowRenderElement<UdevRenderer<'_>>>>(
crtc,
drm_mode,
&[connector.handle()],
&output,
Some(planes),
&mut renderer,
&DrmOutputRenderElements::default(),
) {
Ok(drm_output) => drm_output,
Err(err) => {
warn!("Failed to initialize drm output: {}", err);
return;
}
};
let disable_direct_scanout = std::env::var("ANVIL_DISABLE_DIRECT_SCANOUT").is_ok();
let dmabuf_feedback = drm_output.with_compositor(|compositor| {
compositor.set_debug_flags(self.backend_data.debug_flags);
get_surface_dmabuf_feedback(
self.backend_data.primary_gpu,
device.render_node,
node,
&mut self.backend_data.gpus,
compositor.surface(),
)
});
let surface = SurfaceData {
dh: self.display_handle.clone(),
device_id: node,
render_node: device.render_node,
global: Some(global),
drm_output,
disable_direct_scanout,
#[cfg(feature = "debug")]
fps: fps_ticker::Fps::default(),
#[cfg(feature = "debug")]
fps_element,
dmabuf_feedback,
last_presentation_time: None,
vblank_throttle_timer: None,
};
device.surfaces.insert(crtc, surface);
// kick-off rendering
self.handle.insert_idle(move |state| {
state.render_surface(node, crtc, state.clock.now());
});
}
}
fn connector_disconnected(&mut self, node: DrmNode, connector: connector::Info, crtc: crtc::Handle) {
let device = if let Some(device) = self.backend_data.backends.get_mut(&node) {
device
} else {
return;
};
if let Some(pos) = device
.non_desktop_connectors
.iter()
.position(|(handle, _)| *handle == connector.handle())
{
let _ = device.non_desktop_connectors.remove(pos);
if let Some(leasing_state) = device.leasing_global.as_mut() {
leasing_state.withdraw_connector(connector.handle());
}
} else {
device.surfaces.remove(&crtc);
let output = self
.space
.outputs()
.find(|o| {
o.user_data()
.get::<UdevOutputId>()
.map(|id| id.device_id == node && id.crtc == crtc)
.unwrap_or(false)
})
.cloned();
if let Some(output) = output {
self.space.unmap_output(&output);
}
}
let render_node = device.render_node.unwrap_or(self.backend_data.primary_gpu);
let mut renderer = self.backend_data.gpus.single_renderer(&render_node).unwrap();
let _ = device.drm_output_manager.try_to_restore_modifiers::<_, OutputRenderElements<
UdevRenderer<'_>,
WindowRenderElement<UdevRenderer<'_>>,
>>(
&mut renderer,
// FIXME: For a flicker free operation we should return the actual elements for this output..
// Instead we just use black to "simulate" a modeset :)
&DrmOutputRenderElements::default(),
);
}
fn device_changed(&mut self, node: DrmNode) {
let device = if let Some(device) = self.backend_data.backends.get_mut(&node) {
device
} else {
return;
};
let scan_result = match device
.drm_scanner
.scan_connectors(device.drm_output_manager.device())
{
Ok(scan_result) => scan_result,
Err(err) => {
tracing::warn!(?err, "Failed to scan connectors");
return;
}
};
for event in scan_result {
match event {
DrmScanEvent::Connected {
connector,
crtc: Some(crtc),
} => {
self.connector_connected(node, connector, crtc);
}
DrmScanEvent::Disconnected {
connector,
crtc: Some(crtc),
} => {
self.connector_disconnected(node, connector, crtc);
}
_ => {}
}
}
// fixup window coordinates
crate::shell::fixup_positions(&mut self.space, self.pointer.current_location());
}
fn device_removed(&mut self, node: DrmNode) {
let device = if let Some(device) = self.backend_data.backends.get_mut(&node) {
device
} else {
return;
};
let crtcs: Vec<_> = device
.drm_scanner
.crtcs()
.map(|(info, crtc)| (info.clone(), crtc))
.collect();
for (connector, crtc) in crtcs {
self.connector_disconnected(node, connector, crtc);
}
debug!("Surfaces dropped");
// drop the backends on this side
if let Some(mut backend_data) = self.backend_data.backends.remove(&node) {
if let Some(mut leasing_global) = backend_data.leasing_global.take() {
leasing_global.disable_global::<AnvilState<UdevData>>();
}
if let Some(render_node) = backend_data.render_node {
self.backend_data.gpus.as_mut().remove_node(&render_node);
}
self.handle.remove(backend_data.registration_token);
debug!("Dropping device");
}
crate::shell::fixup_positions(&mut self.space, self.pointer.current_location());
}
fn frame_finish(&mut self, dev_id: DrmNode, crtc: crtc::Handle, metadata: &mut Option<DrmEventMetadata>) {
profiling::scope!("frame_finish", &format!("{crtc:?}"));
let device_backend = match self.backend_data.backends.get_mut(&dev_id) {
Some(backend) => backend,
None => {
error!("Trying to finish frame on non-existent backend {}", dev_id);
return;
}
};
let surface = match device_backend.surfaces.get_mut(&crtc) {
Some(surface) => surface,
None => {
error!("Trying to finish frame on non-existent crtc {:?}", crtc);
return;
}
};
if let Some(timer_token) = surface.vblank_throttle_timer.take() {
self.handle.remove(timer_token);
}
let output = if let Some(output) = self.space.outputs().find(|o| {
o.user_data().get::<UdevOutputId>()
== Some(&UdevOutputId {
device_id: surface.device_id,
crtc,
})
}) {
output.clone()
} else {
// somehow we got called with an invalid output
return;
};
let Some(frame_duration) = output
.current_mode()
.map(|mode| Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
else {
return;
};
let tp = metadata.as_ref().and_then(|metadata| match metadata.time {
smithay::backend::drm::DrmEventTime::Monotonic(tp) => tp.is_zero().not().then_some(tp),
smithay::backend::drm::DrmEventTime::Realtime(_) => None,
});
let seq = metadata.as_ref().map(|metadata| metadata.sequence).unwrap_or(0);
let (clock, flags) = if let Some(tp) = tp {
(
tp.into(),
wp_presentation_feedback::Kind::Vsync
| wp_presentation_feedback::Kind::HwClock
| wp_presentation_feedback::Kind::HwCompletion,
)
} else {
(self.clock.now(), wp_presentation_feedback::Kind::Vsync)
};
let vblank_remaining_time = surface.last_presentation_time.map(|last_presentation_time| {
frame_duration.saturating_sub(Time::elapsed(&last_presentation_time, clock))
});
if let Some(vblank_remaining_time) = vblank_remaining_time {
if vblank_remaining_time > frame_duration / 2 {
static WARN_ONCE: Once = Once::new();
WARN_ONCE.call_once(|| {
warn!("display running faster than expected, throttling vblanks and disabling HwClock")
});
let throttled_time = tp
.map(|tp| tp.saturating_add(vblank_remaining_time))
.unwrap_or(Duration::ZERO);
let throttled_metadata = DrmEventMetadata {
sequence: seq,
time: DrmEventTime::Monotonic(throttled_time),
};
let timer_token = self
.handle
.insert_source(Timer::from_duration(vblank_remaining_time), move |_, _, data| {
data.frame_finish(dev_id, crtc, &mut Some(throttled_metadata));
TimeoutAction::Drop
})
.expect("failed to register vblank throttle timer");
surface.vblank_throttle_timer = Some(timer_token);
return;
}
}
surface.last_presentation_time = Some(clock);
let submit_result = surface
.drm_output
.frame_submitted()
.map_err(Into::<SwapBuffersError>::into);
let schedule_render = match submit_result {
Ok(user_data) => {
if let Some(mut feedback) = user_data.flatten() {
feedback.presented(clock, Refresh::fixed(frame_duration), seq as u64, flags);
}
true
}
Err(err) => {
warn!("Error during rendering: {:?}", err);
match err {
SwapBuffersError::AlreadySwapped => true,
// If the device has been deactivated do not reschedule, this will be done
// by session resume
SwapBuffersError::TemporaryFailure(err)
if matches!(err.downcast_ref::<DrmError>(), Some(&DrmError::DeviceInactive)) =>
{
false
}
SwapBuffersError::TemporaryFailure(err) => matches!(
err.downcast_ref::<DrmError>(),
Some(DrmError::Access(DrmAccessError {
source,
..
})) if source.kind() == io::ErrorKind::PermissionDenied
),
SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err),
}
}
};
if schedule_render {
let next_frame_target = clock + frame_duration;
// What are we trying to solve by introducing a delay here:
//
// Basically it is all about latency of client provided buffers.
// A client driven by frame callbacks will wait for a frame callback
// to repaint and submit a new buffer. As we send frame callbacks
// as part of the repaint in the compositor the latency would always
// be approx. 2 frames. By introducing a delay before we repaint in
// the compositor we can reduce the latency to approx. 1 frame + the
// remaining duration from the repaint to the next VBlank.
//
// With the delay it is also possible to further reduce latency if
// the client is driven by presentation feedback. As the presentation
// feedback is directly sent after a VBlank the client can submit a
// new buffer during the repaint delay that can hit the very next
// VBlank, thus reducing the potential latency to below one frame.
//
// Choosing a good delay is a topic on its own so we just implement
// a simple strategy here. We just split the duration between two
// VBlanks into two steps, one for the client repaint and one for the
// compositor repaint. Theoretically the repaint in the compositor should
// be faster so we give the client a bit more time to repaint. On a typical
// modern system the repaint in the compositor should not take more than 2ms
// so this should be safe for refresh rates up to at least 120 Hz. For 120 Hz
// this results in approx. 3.33ms time for repainting in the compositor.
// A too big delay could result in missing the next VBlank in the compositor.
//
// A more complete solution could work on a sliding window analyzing past repaints
// and do some prediction for the next repaint.
let repaint_delay = Duration::from_secs_f64(frame_duration.as_secs_f64() * 0.6f64);
let timer = if surface
.render_node
.map(|render_node| render_node != self.backend_data.primary_gpu)
.unwrap_or(true)
{
// However, if we need to do a copy, that might not be enough.
// (And without actual comparision to previous frames we cannot really know.)
// So lets ignore that in those cases to avoid thrashing performance.
trace!("scheduling repaint timer immediately on {:?}", crtc);
Timer::immediate()
} else {
trace!(
"scheduling repaint timer with delay {:?} on {:?}",
repaint_delay,
crtc
);
Timer::from_duration(repaint_delay)
};
self.handle
.insert_source(timer, move |_, _, data| {
data.render(dev_id, Some(crtc), next_frame_target);
TimeoutAction::Drop
})
.expect("failed to schedule frame timer");
}
}
// If crtc is `Some()`, render it, else render all crtcs
fn render(&mut self, node: DrmNode, crtc: Option<crtc::Handle>, frame_target: Time<Monotonic>) {
let device_backend = match self.backend_data.backends.get_mut(&node) {
Some(backend) => backend,
None => {
error!("Trying to render on non-existent backend {}", node);
return;
}
};
if let Some(crtc) = crtc {
self.render_surface(node, crtc, frame_target);
} else {
let crtcs: Vec<_> = device_backend.surfaces.keys().copied().collect();
for crtc in crtcs {
self.render_surface(node, crtc, frame_target);
}
};
}
fn render_surface(&mut self, node: DrmNode, crtc: crtc::Handle, frame_target: Time<Monotonic>) {
profiling::scope!("render_surface", &format!("{crtc:?}"));
let output = if let Some(output) = self.space.outputs().find(|o| {
o.user_data().get::<UdevOutputId>()
== Some(&UdevOutputId {
device_id: node,
crtc,
})
}) {
output.clone()
} else {
// somehow we got called with an invalid output
return;
};
self.pre_repaint(&output, frame_target);
let device = if let Some(device) = self.backend_data.backends.get_mut(&node) {
device
} else {
return;
};
let surface = if let Some(surface) = device.surfaces.get_mut(&crtc) {
surface
} else {
return;
};
let start = Instant::now();
// TODO get scale from the rendersurface when supporting HiDPI
let frame = self
.backend_data
.pointer_image
.get_image(1 /*scale*/, self.clock.now().into());
let primary_gpu = self.backend_data.primary_gpu;
let render_node = surface.render_node.unwrap_or(primary_gpu);
let mut renderer = if primary_gpu == render_node {
self.backend_data.gpus.single_renderer(&render_node)
} else {
let format = surface.drm_output.format();
self.backend_data
.gpus
.renderer(&primary_gpu, &render_node, format)
}
.unwrap();
let pointer_images = &mut self.backend_data.pointer_images;
let pointer_image = pointer_images
.iter()
.find_map(|(image, texture)| {
if image == &frame {
Some(texture.clone())
} else {
None
}
})
.unwrap_or_else(|| {
let buffer = MemoryRenderBuffer::from_slice(
&frame.pixels_rgba,
Fourcc::Argb8888,
(frame.width as i32, frame.height as i32),
1,
Transform::Normal,
None,
);
pointer_images.push((frame, buffer.clone()));
buffer
});
let result = render_surface(
surface,
&mut renderer,
&self.space,
&output,
self.pointer.current_location(),
&pointer_image,
&mut self.backend_data.pointer_element,
&self.dnd_icon,
&mut self.cursor_status,
self.show_window_preview,
);
let reschedule = match result {
Ok((has_rendered, states)) => {
let dmabuf_feedback = surface.dmabuf_feedback.clone();
self.post_repaint(&output, frame_target, dmabuf_feedback, &states);
!has_rendered
}
Err(err) => {
warn!("Error during rendering: {:#?}", err);
match err {
SwapBuffersError::AlreadySwapped => false,
SwapBuffersError::TemporaryFailure(err) => match err.downcast_ref::<DrmError>() {
Some(DrmError::DeviceInactive) => true,
Some(DrmError::Access(DrmAccessError { source, .. })) => {
source.kind() == io::ErrorKind::PermissionDenied
}
_ => false,
},
SwapBuffersError::ContextLost(err) => match err.downcast_ref::<DrmError>() {
Some(DrmError::TestFailed(_)) => {
// reset the complete state, disabling all connectors and planes in case we hit a test failed
// most likely we hit this after a tty switch when a foreign master changed CRTC <-> connector bindings
// and we run in a mismatch
device
.drm_output_manager
.device_mut()
.reset_state()
.expect("failed to reset drm device");
true
}
_ => panic!("Rendering loop lost: {}", err),
},
}
}
};
if reschedule {
let output_refresh = match output.current_mode() {
Some(mode) => mode.refresh,
None => return,
};
// If reschedule is true we either hit a temporary failure or more likely rendering
// did not cause any damage on the output. In this case we just re-schedule a repaint
// after approx. one frame to re-test for damage.
let next_frame_target = frame_target + Duration::from_millis(1_000_000 / output_refresh as u64);
let reschedule_timeout =
Duration::from(next_frame_target).saturating_sub(self.clock.now().into());
trace!(
"reschedule repaint timer with delay {:?} on {:?}",
reschedule_timeout,
crtc,
);
let timer = Timer::from_duration(reschedule_timeout);
self.handle
.insert_source(timer, move |_, _, data| {
data.render(node, Some(crtc), next_frame_target);
TimeoutAction::Drop
})
.expect("failed to schedule frame timer");
} else {
let elapsed = start.elapsed();
tracing::trace!(?elapsed, "rendered surface");
}
profiling::finish_frame!();
}
}
#[allow(clippy::too_many_arguments)]
#[profiling::function]
fn render_surface<'a>(
surface: &'a mut SurfaceData,
renderer: &mut UdevRenderer<'a>,
space: &Space<WindowElement>,
output: &Output,
pointer_location: Point<f64, Logical>,
pointer_image: &MemoryRenderBuffer,
pointer_element: &mut PointerElement,
dnd_icon: &Option<DndIcon>,
cursor_status: &mut CursorImageStatus,
show_window_preview: bool,
) -> Result<(bool, RenderElementStates), SwapBuffersError> {
let output_geometry = space.output_geometry(output).unwrap();
let scale = Scale::from(output.current_scale().fractional_scale());
let mut custom_elements: Vec<CustomRenderElements<_>> = Vec::new();
if output_geometry.to_f64().contains(pointer_location) {
let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = cursor_status {
compositor::with_states(surface, |states| {
states
.data_map
.get::<Mutex<CursorImageAttributes>>()
.unwrap()
.lock()
.unwrap()
.hotspot
})
} else {
(0, 0).into()
};
let cursor_pos = pointer_location - output_geometry.loc.to_f64();
// set cursor
pointer_element.set_buffer(pointer_image.clone());
// draw the cursor as relevant
{
// reset the cursor if the surface is no longer alive
let mut reset = false;
if let CursorImageStatus::Surface(ref surface) = *cursor_status {
reset = !surface.alive();
}
if reset {
*cursor_status = CursorImageStatus::default_named();
}
pointer_element.set_status(cursor_status.clone());
}
custom_elements.extend(
pointer_element.render_elements(
renderer,
(cursor_pos - cursor_hotspot.to_f64())
.to_physical(scale)
.to_i32_round(),
scale,
1.0,
),
);
// draw the dnd icon if applicable
{
if let Some(icon) = dnd_icon.as_ref() {
let dnd_icon_pos = (cursor_pos + icon.offset.to_f64())
.to_physical(scale)
.to_i32_round();
if icon.surface.alive() {
custom_elements.extend(AsRenderElements::<UdevRenderer<'a>>::render_elements(
&SurfaceTree::from_surface(&icon.surface),
renderer,
dnd_icon_pos,
scale,
1.0,
));
}
}
}
}
#[cfg(feature = "debug")]
if let Some(element) = surface.fps_element.as_mut() {
element.update_fps(surface.fps.avg().round() as u32);
surface.fps.tick();
custom_elements.push(CustomRenderElements::Fps(element.clone()));
}
let (elements, clear_color) =
output_elements(output, space, custom_elements, renderer, show_window_preview);
let frame_mode = if surface.disable_direct_scanout {
FrameFlags::empty()
} else {
FrameFlags::DEFAULT
};
let (rendered, states) = surface
.drm_output
.render_frame(renderer, &elements, clear_color, frame_mode)
.map(|render_frame_result| {
#[cfg(feature = "renderer_sync")]
if let PrimaryPlaneElement::Swapchain(element) = render_frame_result.primary_element {
element.sync.wait();
}
(!render_frame_result.is_empty, render_frame_result.states)
})
.map_err(|err| match err {
smithay::backend::drm::compositor::RenderFrameError::PrepareFrame(err) => {
SwapBuffersError::from(err)
}
smithay::backend::drm::compositor::RenderFrameError::RenderFrame(
OutputDamageTrackerError::Rendering(err),
) => SwapBuffersError::from(err),
_ => unreachable!(),
})?;
update_primary_scanout_output(space, output, dnd_icon, cursor_status, &states);
if rendered {
let output_presentation_feedback = take_presentation_feedback(output, space, &states);
surface
.drm_output
.queue_frame(Some(output_presentation_feedback))
.map_err(Into::<SwapBuffersError>::into)?;
}
Ok((rendered, states))
}