diff --git a/Cargo.lock b/Cargo.lock index 17d62cdd..9439764e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -887,6 +887,7 @@ dependencies = [ "sendfd", "serde", "serde_json", + "smallvec", "smithay", "smithay-egui", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 9896e6bf..464ad42b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ xkbcommon = "0.7" zbus = "3.15.0" profiling = { version = "1.0" } rustix = { version = "0.38.32", features = ["process"] } +smallvec = "1.13.2" [dependencies.id_tree] branch = "feature/copy_clone" diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs new file mode 100644 index 00000000..42b8fb78 --- /dev/null +++ b/src/backend/kms/device.rs @@ -0,0 +1,657 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + config::{OutputConfig, OutputState}, + shell::Shell, + utils::prelude::*, +}; + +use anyhow::{Context, Result}; +use libc::dev_t; +use smithay::{ + backend::{ + allocator::gbm::GbmDevice, + drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmNode}, + egl::{context::ContextPriority, EGLContext, EGLDevice, EGLDisplay}, + session::Session, + }, + output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel}, + reexports::{ + calloop::{LoopHandle, RegistrationToken}, + drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags}, + rustix::fs::OFlags, + wayland_server::{protocol::wl_buffer::WlBuffer, DisplayHandle, Weak}, + }, + utils::{DevPath, DeviceFd, Transform}, + wayland::drm_lease::{DrmLease, DrmLeaseState}, +}; +use tracing::{error, info, warn}; + +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, + fmt, + path::{Path, PathBuf}, + sync::{Arc, Condvar, Mutex, RwLock}, +}; + +use super::{drm_helpers, socket::Socket, surface::Surface}; + +#[derive(Debug)] +pub struct EGLInternals { + pub display: EGLDisplay, + pub device: EGLDevice, + pub context: EGLContext, +} + +pub struct Device { + pub dev_node: DrmNode, + pub render_node: DrmNode, + pub egl: Option, + + pub outputs: HashMap, + pub surfaces: HashMap, + pub gbm: GbmDevice, + pub drm: DrmDevice, + + supports_atomic: bool, + + pub leased_connectors: Vec<(connector::Handle, crtc::Handle)>, + pub leasing_global: Option, + pub active_leases: Vec, + pub active_buffers: HashSet>, + + event_token: Option, + pub socket: Option, +} + +impl fmt::Debug for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Device") + .field("dev_node", &self.render_node) + .field("render_node", &self.render_node) + .field("outputs", &self.outputs) + .field("surfaces", &self.surfaces) + .field("drm", &self.drm) + .field("gbm", &self.gbm) + .field("egl", &self.egl) + .field("supports_atomic", &self.supports_atomic) + .field("leased_connectors", &self.leased_connectors) + .field("leasing_global", &self.leasing_global) + .field("active_leases", &self.active_leases) + .field("event_token", &self.event_token) + .field("socket", &self.socket) + .finish() + } +} + +pub fn init_egl(gbm: &GbmDevice) -> Result { + let path = gbm.dev_path(); + + let display = unsafe { EGLDisplay::new(gbm.clone()) }.with_context(|| { + format!( + "Failed to create EGLDisplay for device: {:?}", + path.as_deref().map(Path::display) + ) + })?; + let device = EGLDevice::device_for_display(&display).with_context(|| { + format!( + "Unable to find matching egl device for {:?}", + path.as_deref().map(Path::display) + ) + })?; + + let context = + EGLContext::new_with_priority(&display, ContextPriority::High).with_context(|| { + format!( + "Failed to create EGLContext for device {:?}:{:?}", + device, + path.as_deref().map(Path::display), + ) + })?; + + Ok(EGLInternals { + display, + device, + context, + }) +} + +impl State { + pub fn device_added(&mut self, dev: dev_t, path: PathBuf, dh: &DisplayHandle) -> Result<()> { + if !self.backend.kms().session.is_active() { + return Ok(()); + } + + let fd = DrmDeviceFd::new(DeviceFd::from( + self.backend + .kms() + .session + .open( + &path, + OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK, + ) + .with_context(|| { + format!( + "Failed to optain file descriptor for drm device: {}", + path.display() + ) + })?, + )); + let (drm, notifier) = DrmDevice::new(fd.clone(), false) + .with_context(|| format!("Failed to initialize drm device for: {}", path.display()))?; + let drm_node = DrmNode::from_dev_id(dev)?; + let supports_atomic = drm.is_atomic(); + + let gbm = GbmDevice::new(fd) + .with_context(|| format!("Failed to initialize GBM device for {}", path.display()))?; + let (render_node, render_formats) = { + let egl = init_egl(&gbm)?; + + let render_node = egl + .device + .try_get_render_node() + .ok() + .and_then(std::convert::identity) + .unwrap_or(drm_node); + let render_formats = egl.context.dmabuf_texture_formats().clone(); + + (render_node, render_formats) + }; + + let token = self + .common + .event_loop_handle + .insert_source( + notifier, + move |event, metadata, state: &mut State| match event { + DrmEvent::VBlank(crtc) => { + if let Some(device) = state.backend.kms().drm_devices.get_mut(&drm_node) { + if let Some(surface) = device.surfaces.get_mut(&crtc) { + surface.on_vblank(metadata.take()); + } + } + } + DrmEvent::Error(err) => { + warn!(?err, "Failed to read events of device {:?}.", dev); + } + }, + ) + .with_context(|| format!("Failed to add drm device to event loop: {}", dev))?; + + let socket = match self.create_socket(dh, render_node, render_formats.clone().into_iter()) { + Ok(socket) => Some(socket), + Err(err) => { + warn!( + ?err, + "Failed to initialize hardware-acceleration for clients on {}.", render_node, + ); + None + } + }; + + let mut device = Device { + dev_node: drm_node, + render_node, + egl: None, + + outputs: HashMap::new(), + surfaces: HashMap::new(), + gbm: gbm.clone(), + drm, + + supports_atomic, + + leased_connectors: Vec::new(), + leasing_global: DrmLeaseState::new::(dh, &drm_node) + .map_err(|err| { + // TODO: replace with inspect_err, once stable + warn!( + ?err, + "Failed to initialize drm lease global for: {}", drm_node + ); + err + }) + .ok(), + active_leases: Vec::new(), + active_buffers: HashSet::new(), + + event_token: Some(token), + socket, + }; + + let connectors = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices + let mut wl_outputs = Vec::new(); + let mut w = self.common.shell.read().unwrap().global_space().size.w; + + { + for (conn, maybe_crtc) in connectors { + match device.connector_added( + self.backend.kms().primary_node.as_ref(), + conn, + maybe_crtc, + (0, w), + &self.common.event_loop_handle, + self.common.shell.clone(), + self.common.startup_done.clone(), + ) { + Ok((output, should_expose)) => { + if should_expose { + w += output.config().mode_size().w; + wl_outputs.push(output.clone()); + } + device.outputs.insert(conn, output); + } + Err(err) => { + warn!(?err, "Failed to initialize output, skipping"); + } + } + } + + // TODO atomic commit all surfaces together and drop surfaces, if it fails due to bandwidth + + self.backend.kms().drm_devices.insert(drm_node, device); + } + + self.backend.kms().refresh_used_devices(); + + self.common + .output_configuration_state + .add_heads(wl_outputs.iter()); + self.common.config.read_outputs( + &mut self.common.output_configuration_state, + &mut self.backend, + &self.common.shell, + &self.common.event_loop_handle, + &mut self.common.workspace_state.update(), + &self.common.xdg_activation_state, + self.common.startup_done.clone(), + ); + self.common.refresh(); + + Ok(()) + } + + pub fn device_changed(&mut self, dev: dev_t) -> Result<()> { + if !self.backend.kms().session.is_active() { + return Ok(()); + } + + let drm_node = DrmNode::from_dev_id(dev)?; + let mut outputs_removed = Vec::new(); + let mut outputs_added = Vec::new(); + + { + let backend = self.backend.kms(); + if let Some(device) = backend.drm_devices.get_mut(&drm_node) { + let changes = device.enumerate_surfaces()?; + + let mut w = self.common.shell.read().unwrap().global_space().size.w; + for conn in changes.removed { + // contains conns with updated crtcs, just drop the surface and re-create + if let Some(pos) = device + .leased_connectors + .iter() + .position(|(handle, _)| *handle == conn) + { + let _ = device.leased_connectors.remove(pos); + if let Some(leasing_state) = device.leasing_global.as_mut() { + leasing_state.withdraw_connector(conn); + } + } else if let Some(crtc) = device + .surfaces + .iter() + .find_map(|(crtc, surface)| (surface.connector == conn).then_some(crtc)) + .cloned() + { + let surface = device.surfaces.remove(&crtc).unwrap(); + // TODO: move up later outputs? + w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0); + } + + if !changes.added.iter().any(|(c, _)| c == &conn) { + outputs_removed.push( + device + .outputs + .remove(&conn) + .expect("Connector without output?"), + ); + } + } + + for (conn, maybe_crtc) in changes.added { + match device.connector_added( + backend.primary_node.as_ref(), + conn, + maybe_crtc, + (0, w), + &self.common.event_loop_handle, + self.common.shell.clone(), + self.common.startup_done.clone(), + ) { + Ok((output, should_expose)) => { + if should_expose { + w += output.config().mode_size().w; + outputs_added.push(output.clone()); + } + + device.outputs.insert(conn, output); + } + Err(err) => { + warn!(?err, "Failed to initialize output, skipping"); + } + } + } + } + } + + self.backend.kms().refresh_used_devices(); + + self.common + .output_configuration_state + .remove_heads(outputs_removed.iter()); + self.common + .output_configuration_state + .add_heads(outputs_added.iter()); + { + self.common.config.read_outputs( + &mut self.common.output_configuration_state, + &mut self.backend, + &self.common.shell, + &self.common.event_loop_handle, + &mut self.common.workspace_state.update(), + &self.common.xdg_activation_state, + self.common.startup_done.clone(), + ); + // Don't remove the outputs, before potentially new ones have been initialized. + // reading a new config means outputs might become enabled, that were previously disabled. + // This gives the shell more information on how to move outputs. + for output in outputs_removed { + self.common.remove_output(&output); + } + self.common.refresh(); + } + + Ok(()) + } + + pub fn device_removed(&mut self, dev: dev_t, dh: &DisplayHandle) -> Result<()> { + let drm_node = DrmNode::from_dev_id(dev)?; + let mut outputs_removed = Vec::new(); + let backend = self.backend.kms(); + if let Some(mut device) = backend.drm_devices.remove(&drm_node) { + if let Some(mut leasing_global) = device.leasing_global.take() { + leasing_global.disable_global::(); + } + for surface in device.surfaces.values_mut() { + outputs_removed.push(surface.output.clone()); + } + if let Some(token) = device.event_token.take() { + self.common.event_loop_handle.remove(token); + } + if let Some(socket) = device.socket.take() { + self.common.event_loop_handle.remove(socket.token); + self.common + .dmabuf_state + .destroy_global::(dh, socket.dmabuf_global); + dh.remove_global::(socket.drm_global); + } + } + self.common + .output_configuration_state + .remove_heads(outputs_removed.iter()); + + if self.backend.kms().session.is_active() { + for output in outputs_removed { + self.common.remove_output(&output); + } + self.common.config.read_outputs( + &mut self.common.output_configuration_state, + &mut self.backend, + &self.common.shell, + &self.common.event_loop_handle, + &mut self.common.workspace_state.update(), + &self.common.xdg_activation_state, + self.common.startup_done.clone(), + ); + self.common.refresh(); + } else { + self.common.output_configuration_state.update(); + } + + Ok(()) + } +} + +pub struct OutputChanges { + pub added: Vec<(connector::Handle, Option)>, + pub removed: Vec, +} + +impl Device { + pub fn enumerate_surfaces(&mut self) -> Result { + // enumerate our outputs + let config = drm_helpers::display_configuration(&mut self.drm, self.supports_atomic)?; + + let surfaces = self + .surfaces + .iter() + .map(|(c, s)| (s.connector, *c)) + .chain( + self.leased_connectors + .iter() + .map(|(conn, crtc)| (*conn, *crtc)), + ) + .collect::>(); + + let added = config + .iter() + .filter(|(conn, maybe)| match (surfaces.get(&conn), maybe) { + (Some(current_crtc), Some(new_crtc)) => current_crtc != new_crtc, + (None, _) => true, + _ => false, + }) + .map(|(conn, crtc)| (*conn, *crtc)) + .collect::>(); + + let removed = self + .outputs + .iter() + .filter(|(conn, _)| match config.get(conn) { + Some(Some(c)) => surfaces.get(&conn).is_some_and(|crtc| c != crtc), + _ => true, + }) + .map(|(conn, _)| *conn) + .collect::>(); + + Ok(OutputChanges { added, removed }) + } + + pub fn connector_added( + &mut self, + primary_node: Option<&DrmNode>, + conn: connector::Handle, + maybe_crtc: Option, + position: (i32, i32), + evlh: &LoopHandle<'static, State>, + shell: Arc>, + startup_done: Arc<(Mutex, Condvar)>, + ) -> Result<(Output, bool)> { + let output = self + .outputs + .get(&conn) + .cloned() + .map(|output| Ok(output)) + .unwrap_or_else(|| create_output_for_conn(&mut self.drm, conn)) + .context("Failed to create `Output`")?; + + let non_desktop = match drm_helpers::get_property_val(&self.drm, conn, "non-desktop") { + Ok((val_type, value)) => val_type.convert_value(value).as_boolean().unwrap(), + Err(err) => { + warn!( + ?err, + "Failed to determine if connector is meant desktop usage, assuming so." + ); + false + } + }; + + if non_desktop { + if let Some(crtc) = maybe_crtc { + self.leased_connectors.push((conn, crtc)); + info!( + "Connector {} is non-desktop, setting up for leasing", + output.name() + ); + if let Some(lease_state) = self.leasing_global.as_mut() { + let physical = output.physical_properties(); + lease_state.add_connector::( + conn, + output.name(), + format!("{} {}", physical.make, physical.model), + ); + } + } else { + warn!( + "Connector {} is non-desktop, but we don't have a free crtc: not leasing", + output.name() + ); + } + + Ok((output, false)) + } else { + output + .user_data() + .insert_if_missing(|| RefCell::new(OutputConfig::default())); + + populate_modes(&mut self.drm, &output, conn, position) + .with_context(|| "Failed to enumerate connector modes")?; + + let has_surface = if let Some(crtc) = maybe_crtc { + match Surface::new( + &output, + crtc, + conn, + primary_node.copied().unwrap_or(self.render_node), + self.dev_node, + self.render_node, + evlh, + shell, + startup_done, + ) { + Ok(data) => { + self.surfaces.insert(crtc, data); + true + } + Err(err) => { + error!(?crtc, "Failed to initialize surface: {}", err); + false + } + } + } else { + false + }; + + if !has_surface { + output + .user_data() + .get::>() + .unwrap() + .borrow_mut() + .enabled = OutputState::Disabled; + } + + Ok((output, true)) + } + } + + pub fn in_use(&self, primary: Option<&DrmNode>) -> bool { + Some(&self.render_node) == primary + || !self.surfaces.is_empty() + || !self.active_buffers.is_empty() + } +} + +fn create_output_for_conn(drm: &mut DrmDevice, conn: connector::Handle) -> Result { + let conn_info = drm + .get_connector(conn, false) + .with_context(|| "Failed to query connector info")?; + let interface = drm_helpers::interface_name(drm, conn)?; + let edid_info = drm_helpers::edid_info(drm, conn); + let (phys_w, phys_h) = conn_info.size().unwrap_or((0, 0)); + + Ok(Output::new( + interface, + PhysicalProperties { + size: (phys_w as i32, phys_h as i32).into(), + subpixel: match conn_info.subpixel() { + connector::SubPixel::HorizontalRgb => Subpixel::HorizontalRgb, + connector::SubPixel::HorizontalBgr => Subpixel::HorizontalBgr, + connector::SubPixel::VerticalRgb => Subpixel::VerticalRgb, + connector::SubPixel::VerticalBgr => Subpixel::VerticalBgr, + connector::SubPixel::None => Subpixel::None, + _ => Subpixel::Unknown, + }, + make: edid_info + .as_ref() + .map(|info| info.manufacturer.clone()) + .unwrap_or_else(|_| String::from("Unknown")), + model: edid_info + .as_ref() + .map(|info| info.model.clone()) + .unwrap_or_else(|_| String::from("Unknown")), + }, + )) +} + +fn populate_modes( + drm: &mut DrmDevice, + output: &Output, + conn: connector::Handle, + position: (i32, i32), +) -> Result<()> { + let conn_info = drm.get_connector(conn, false)?; + let max_bpc = drm_helpers::get_max_bpc(drm, conn)?.map(|(_val, range)| range.end.min(16)); + let mode = conn_info + .modes() + .iter() + .find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED)) + .copied() + .unwrap_or(conn_info.modes()[0]); + let refresh_rate = drm_helpers::calculate_refresh_rate(mode); + let output_mode = OutputMode { + size: (mode.size().0 as i32, mode.size().1 as i32).into(), + refresh: refresh_rate as i32, + }; + + for mode in conn_info.modes() { + let refresh_rate = drm_helpers::calculate_refresh_rate(*mode); + let mode = OutputMode { + size: (mode.size().0 as i32, mode.size().1 as i32).into(), + refresh: refresh_rate as i32, + }; + output.add_mode(mode); + } + output.set_preferred(output_mode); + output.change_current_state( + Some(output_mode), + // TODO: Readout property for monitor rotation + Some(Transform::Normal), + None, + Some(position.into()), + ); + + let mut output_config = output + .user_data() + .get::>() + .unwrap() + .borrow_mut(); + *output_config = OutputConfig { + mode: ((output_mode.size.w, output_mode.size.h), Some(refresh_rate)), + position, + max_bpc, + ..std::mem::take(&mut *output_config) + }; + + Ok(()) +} diff --git a/src/backend/kms/drm_helpers.rs b/src/backend/kms/drm_helpers.rs index d5115a2d..b908e428 100644 --- a/src/backend/kms/drm_helpers.rs +++ b/src/backend/kms/drm_helpers.rs @@ -14,12 +14,13 @@ use std::{collections::HashMap, ops::Range}; pub fn display_configuration( device: &mut impl ControlDevice, supports_atomic: bool, -) -> Result> { +) -> Result>> { let res_handles = device.resource_handles()?; let connectors = res_handles.connectors(); let mut map = HashMap::new(); let mut cleanup = Vec::new(); + // We expect the previous running drm master (likely the login mananger) // to leave the drm device in a sensible state. // That means, to reduce flickering, we try to keep an established mapping. @@ -31,7 +32,7 @@ pub fn display_configuration( if let Some(crtc) = device.get_encoder(enc)?.crtc() { // If is is connected we found a mapping if conn.state() == ConnectorState::Connected { - map.insert(conn.handle(), crtc); + map.insert(conn.handle(), Some(crtc)); // If not, the user just unplugged something, // or the drm master did not cleanup? // Well, I guess we cleanup after them. @@ -41,6 +42,7 @@ pub fn display_configuration( } } } + // But just in case we try to match all remaining connectors. for conn in connectors .iter() @@ -56,12 +58,16 @@ pub fn display_configuration( .flat_map(|encoder_handle| device.get_encoder(*encoder_handle)) { for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { - if !map.values().any(|v| *v == crtc) { - map.insert(conn.handle(), crtc); + if !map.values().any(|v| *v == Some(crtc)) { + map.insert(conn.handle(), Some(crtc)); break 'outer; } } } + + if !map.contains_key(&conn.handle()) { + map.insert(conn.handle(), None); + } } // And then cleanup @@ -119,9 +125,11 @@ pub fn display_configuration( device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?; } else { - for crtc in cleanup { + for crtc in res_handles.crtcs() { #[allow(deprecated)] - let _ = device.set_cursor(crtc, Option::<&DumbBuffer>::None); + let _ = device.set_cursor(*crtc, Option::<&DumbBuffer>::None); + } + for crtc in cleanup { // null commit (necessary to trigger removal on the kernel side with the legacy api.) let _ = device.set_crtc(crtc, None, (0, 0), &[], None); } diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 6d684dce..ee58a845 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,235 +1,138 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - backend::render::{ - element::{CosmicElement, DamageElement}, - workspace_elements, CLEAR_COLOR, - }, - config::{OutputConfig, OutputState}, - shell::Shell, - state::{BackendData, Common, Fps, SurfaceDmabufFeedback}, - utils::prelude::*, - wayland::{ - handlers::screencopy::{submit_buffer, FrameHolder, SessionData}, - protocols::{ - screencopy::{FailureReason, Frame as ScreencopyFrame, Session as ScreencopySession}, - workspace::WorkspaceUpdateGuard, - }, - }, + config::OutputState, shell::Shell, state::BackendData, utils::prelude::*, + wayland::handlers::output, }; use anyhow::{Context, Result}; -use libc::dev_t; +use calloop::LoopSignal; +use render::gles::GbmGlowBackend; +use smallvec::SmallVec; use smithay::{ backend::{ allocator::{ dmabuf::Dmabuf, - gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}, - Format, Fourcc, + gbm::{GbmAllocator, GbmBufferFlags}, }, - drm::{ - compositor::{BlitFrameResultError, DrmCompositor, FrameError, PrimaryPlaneElement}, - DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime, DrmNode, NodeType, - }, - egl::{EGLContext, EGLDevice, EGLDisplay}, + drm::{DrmDeviceFd, DrmNode, NodeType}, + egl::{context::ContextPriority, EGLContext}, input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, - renderer::{ - buffer_dimensions, - damage::{Error as RenderError, OutputDamageTracker}, - element::{ - texture::{TextureRenderBuffer, TextureRenderElement}, - utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior}, - Element, Kind, RenderElementStates, - }, - gles::{GlesRenderbuffer, GlesTexture}, - glow::GlowRenderer, - multigpu::{gbm::GbmGlesBackend, Error as MultiError, GpuManager}, - sync::SyncPoint, - utils::with_renderer_surface_state, - Bind, ImportDma, Offscreen, Renderer, Texture, - }, + renderer::{glow::GlowRenderer, multigpu::GpuManager}, session::{libseat::LibSeatSession, Event as SessionEvent, Session}, udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent}, }, - desktop::utils::OutputPresentationFeedback, - output::{Mode as OutputMode, Output, OutputNoMode, PhysicalProperties, Subpixel}, + output::Output, reexports::{ - calloop::{ - timer::{TimeoutAction, Timer}, - Dispatcher, EventLoop, InsertError, LoopHandle, RegistrationToken, - }, - drm::{ - control::{connector, crtc, Device as ControlDevice, ModeTypeFlags}, - Device as _, - }, + calloop::{Dispatcher, EventLoop, LoopHandle}, + drm::control::{crtc, Device as _}, input::{self, Libinput}, - rustix::fs::OFlags, - wayland_protocols::wp::{ - linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1, - presentation_time::server::wp_presentation_feedback, - }, - wayland_server::{ - protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface}, - Client, DisplayHandle, Weak, - }, - }, - utils::{Buffer as BufferCoords, DeviceFd, Physical, Rectangle, Size, Transform}, - wayland::{ - dmabuf::{get_dmabuf, DmabufFeedbackBuilder, DmabufGlobal}, - drm_lease::{DrmLease, DrmLeaseState}, - relative_pointer::RelativePointerManagerState, - seat::WaylandFocus, - shm::{shm_format_to_fourcc, with_buffer_contents}, - xdg_activation::XdgActivationState, + wayland_server::{Client, DisplayHandle}, }, + utils::{DevPath, Size}, + wayland::{dmabuf::DmabufGlobal, relative_pointer::RelativePointerManagerState}, }; use tracing::{error, info, trace, warn}; use std::{ - cell::RefCell, collections::{HashMap, HashSet}, - fmt, - path::PathBuf, - sync::mpsc::Receiver, - time::Duration, + path::Path, + sync::{Arc, Condvar, Mutex, RwLock}, }; +mod device; mod drm_helpers; +pub mod render; mod socket; -use socket::*; +mod surface; -use super::render::{element::AsGlowRenderer, init_shaders, CursorMode, GlMultiRenderer}; -// for now we assume we need at least 3ms -const MIN_DISPLAY_TIME: Duration = Duration::from_millis(3); - -/* -TODO Screencopy: -- Fixup blit, include additional damage -- Send accurate presentation time -- Handle early exit no damage case -- input... -*/ +use device::*; +pub use surface::Timings; #[derive(Debug)] pub struct KmsState { - pub devices: HashMap, + pub drm_devices: HashMap, pub input_devices: HashMap, - pub api: GpuManager>, - pub primary_node: DrmNode, + pub primary_node: Option, + pub api: GpuManager>, + session: LibSeatSession, - pub auto_assign: bool, - _tokens: Vec, + libinput: Libinput, } -pub struct Device { - pub render_node: DrmNode, - surfaces: HashMap, - pub drm: DrmDevice, - gbm: GbmDevice, - formats: HashSet, - supports_atomic: bool, - pub non_desktop_connectors: Vec<(connector::Handle, crtc::Handle)>, - pub leasing_global: Option, - pub active_leases: Vec, - pub active_buffers: HashSet>, - event_token: Option, - socket: Option, -} - -impl fmt::Debug for Device { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Device") - .field("render_node", &self.render_node) - .field("surfaces", &self.surfaces) - .field("drm", &self.drm) - .field("gbm", &self.gbm) - .field("formats", &self.formats) - .field("supports_atomic", &self.supports_atomic) - .field("non_desktop_connectors", &self.non_desktop_connectors) - .field("leasing_global", &self.leasing_global) - .field("active_leases", &self.active_leases) - .field("event_token", &self.event_token) - .field("socket", &self.socket) - .finish() - } -} - -#[derive(Debug)] -pub struct Surface { - surface: Option, - connector: connector::Handle, - - output: Output, - mirroring: Option, - mirroring_textures: HashMap, - - refresh_rate: u32, - vrr: bool, - scheduled: bool, - pending: bool, - render_timer_token: Option, - fps: Fps, - feedback: HashMap, -} - -#[derive(Debug)] -struct MirroringState { - texture: TextureRenderBuffer, - damage_tracker: OutputDamageTracker, -} - -impl MirroringState { - fn new_with_renderer( - renderer: &mut GlMultiRenderer, - format: Fourcc, - output: &Output, - ) -> Result { - let size = output - .current_mode() - .map(|mode| mode.size) - .unwrap_or_default() - .to_logical(1) - .to_buffer(1, Transform::Normal); - let opaque_regions = vec![Rectangle::from_loc_and_size((0, 0), size)]; - - let texture = Offscreen::::create_buffer(renderer, format, size)?; - let transform = output.current_transform(); - let texture_buffer = TextureRenderBuffer::from_texture( - renderer, - texture, - 1, - transform, - Some(opaque_regions), - ); - - let damage_tracker = OutputDamageTracker::from_output(output); - - Ok(MirroringState { - texture: texture_buffer, - damage_tracker, - }) - } -} - -pub type GbmDrmCompositor = DrmCompositor< - GbmAllocator, - GbmDevice, - Option<( - OutputPresentationFeedback, - Receiver<(ScreencopyFrame, Vec>)>, - )>, - DrmDeviceFd, ->; - pub fn init_backend( dh: &DisplayHandle, event_loop: &mut EventLoop<'static, State>, state: &mut State, ) -> Result<()> { + // establish session let (session, notifier) = LibSeatSession::new().context("Failed to acquire session")?; - let udev_backend = UdevBackend::new(session.seat())?; + // setup input + let libinput_context = init_libinput(dh, &session, &event_loop.handle()) + .context("Failed to initialize libinput backend")?; + + // get our primary gpu + let primary = determine_primary_gpu(session.seat()); + if let Some(primary) = primary.as_ref() { + info!("Using {} as primary gpu for rendering.", primary); + } + + // watch for gpu events + let udev_dispatcher = init_udev(session.seat(), &event_loop.handle()) + .context("Failed to initialize udev connection")?; + + // handle session events + let handle = event_loop.handle(); + let loop_signal = event_loop.get_signal(); + let dispatcher = udev_dispatcher.clone(); + let session_event_source = event_loop + .handle() + .insert_source(notifier, move |event, &mut (), state| match event { + SessionEvent::ActivateSession => { + state.resume_session( + dispatcher.clone(), + state.common.event_loop_handle.clone(), + loop_signal.clone(), + ); + } + SessionEvent::PauseSession => { + state.pause_session(); + } + }) + .map_err(|err| err.error) + .context("Failed to initialize session event source")?; + + // finish backend initialization + state.backend = BackendData::Kms(KmsState { + drm_devices: HashMap::new(), + input_devices: HashMap::new(), + primary_node: primary, + api: GpuManager::new(GbmGlowBackend::new()).context("Failed to initialize gpu backend")?, + + session, + libinput: libinput_context, + }); + + // start x11 + state.launch_xwayland(primary); + + // manually add already present gpus + for (dev, path) in udev_dispatcher.as_source_ref().device_list() { + if let Err(err) = state.device_added(dev, path.into(), dh) { + warn!("Failed to add device {}: {:?}", path.display(), err); + } + } + + Ok(()) +} + +fn init_libinput( + dh: &DisplayHandle, + session: &LibSeatSession, + evlh: &LoopHandle<'static, State>, +) -> Result { let mut libinput_context = Libinput::new_with_udev::>(session.clone().into()); libinput_context @@ -237,75 +140,68 @@ pub fn init_backend( .map_err(|_| anyhow::anyhow!("Failed to assign seat to libinput"))?; let libinput_backend = LibinputInputBackend::new(libinput_context.clone()); - let libinput_event_source = event_loop - .handle() - .insert_source(libinput_backend, move |mut event, _, state| { - if let InputEvent::DeviceAdded { ref mut device } = &mut event { - state.common.config.read_device(device); - state - .backend - .kms() - .input_devices - .insert(device.name().into(), device.clone()); - } else if let InputEvent::DeviceRemoved { device } = &event { - state.backend.kms().input_devices.remove(device.name()); - } - state.process_input_event(event, true); - for output in state.common.shell.read().unwrap().outputs() { - if let Err(err) = state.backend.kms().schedule_render( - &state.common.event_loop_handle, - output, - None, - ) { - error!( - ?err, - "Error scheduling event loop for output {}.", - output.name(), - ); - } - } - }) - .map_err(|err| err.error) - .context("Failed to initialize libinput event source")?; + evlh.insert_source(libinput_backend, move |mut event, _, state| { + if let InputEvent::DeviceAdded { ref mut device } = &mut event { + state.common.config.read_device(device); + state + .backend + .kms() + .input_devices + .insert(device.name().into(), device.clone()); + } else if let InputEvent::DeviceRemoved { device } = &event { + state.backend.kms().input_devices.remove(device.name()); + } - let mut api = GpuManager::new(GbmGlesBackend::::default()) - .context("Failed to initialize renderers")?; - api.as_mut() - .set_allocator_flags(GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT); + state.process_input_event(event, true); - // TODO get this info from system76-power, if available and setup a watcher - let primary = if let Some(path) = std::env::var("COSMIC_RENDER_DEVICE") + for output in state.common.shell.read().unwrap().outputs() { + state.backend.kms().schedule_render(output); + } + }) + .map_err(|err| err.error) + .context("Failed to initialize libinput event source")?; + + // Create relative pointer global + RelativePointerManagerState::new::(&dh); + + Ok(libinput_context) +} + +fn determine_primary_gpu(seat: String) -> Option { + if let Some(node) = std::env::var("COSMIC_RENDER_DEVICE") .ok() .and_then(|x| DrmNode::from_path(x).ok()) { - path + Some(node) } else { - let primary_node = primary_gpu(session.seat()) + let primary_node = primary_gpu(&seat) .ok() .flatten() .and_then(|x| DrmNode::from_path(x).ok()); primary_node .and_then(|x| x.node_with_type(NodeType::Render).and_then(Result::ok)) - .unwrap_or_else(|| { - for dev in all_gpus(session.seat()).expect("No GPU found") { + .or_else(|| { + for dev in all_gpus(&seat).expect("Failed to query gpus") { if let Some(node) = DrmNode::from_path(dev) .ok() .and_then(|x| x.node_with_type(NodeType::Render).and_then(Result::ok)) { - return node; + return Some(node); } } - // If we find no render nodes, use primary node - if let Some(primary_node) = primary_node { - return primary_node; - } else { - panic!("Failed to initialize any GPU"); - } - }) - }; - info!("Using {} as primary gpu for rendering.", primary); - let udev_dispatcher = Dispatcher::new(udev_backend, move |event, _, state: &mut State| { + None + }) + } +} + +fn init_udev( + seat: String, + evlh: &LoopHandle<'static, State>, +) -> Result> { + let udev_backend = UdevBackend::new(&seat)?; + + let dispatcher = Dispatcher::new(udev_backend, move |event, _, state: &mut State| { let dh = state.common.display_handle.clone(); match match event { UdevEvent::Added { device_id, path } => state @@ -326,1321 +222,87 @@ pub fn init_backend( } } }); - let udev_event_source = event_loop - .handle() - .register_dispatcher(udev_dispatcher.clone()) - .unwrap(); - let handle = event_loop.handle(); - let loop_signal = state.common.event_loop_signal.clone(); - let dispatcher = udev_dispatcher.clone(); - let session_event_source = event_loop - .handle() - .insert_source(notifier, move |event, &mut (), state| match event { - SessionEvent::ActivateSession => { - if let Err(err) = libinput_context.resume() { - error!(?err, "Failed to resume libinput context."); - } - for device in state.backend.kms().devices.values_mut() { - if let Err(err) = device.drm.activate(true) { - error!(?err, "Failed to resume drm device"); - } - // TODO save state, do the disable part manually, etc - if let Some(lease_state) = device.leasing_global.as_mut() { - lease_state.resume::(); - } - } - let dispatcher = dispatcher.clone(); - handle.insert_idle(move |state| { - for (dev, path) in dispatcher.as_source_ref().device_list() { - let drm_node = match DrmNode::from_dev_id(dev) { - Ok(node) => node, - Err(err) => { - error!(?err, "Failed to read drm device {}.", path.display(),); - continue; - } - }; - if state.backend.kms().devices.contains_key(&drm_node) { - if let Err(err) = state.device_changed(dev) { - error!(?err, "Failed to update drm device {}.", path.display(),); - } - } else { - let dh = state.common.display_handle.clone(); - if let Err(err) = state.device_added(dev, path.into(), &dh) { - error!(?err, "Failed to add drm device {}.", path.display(),); - } - } - } + let udev_event_source = evlh.register_dispatcher(dispatcher.clone()).unwrap(); - state.common.config.read_outputs( - &mut state.common.output_configuration_state, - &mut state.backend, - &mut state.common.shell.write().unwrap(), - &state.common.event_loop_handle, - &mut state.common.workspace_state.update(), - &state.common.xdg_activation_state, - ); - state.common.refresh(); - - for surface in state - .backend - .kms() - .devices - .values_mut() - .flat_map(|d| d.surfaces.values_mut()) - { - surface.scheduled = false; - surface.pending = false; - } - for output in state.common.shell.read().unwrap().outputs() { - if let Err(err) = state.backend.kms().schedule_render( - &state.common.event_loop_handle, - output, - None, - ) { - error!( - ?err, - "Error scheduling event loop for output {}.", - output.name(), - ); - } - } - }); - loop_signal.wakeup(); - } - SessionEvent::PauseSession => { - libinput_context.suspend(); - for device in state.backend.kms().devices.values_mut() { - device.drm.pause(); - if let Some(lease_state) = device.leasing_global.as_mut() { - lease_state.suspend(); - } - for surface in device.surfaces.values_mut() { - surface.surface = None; - if let Some(token) = surface.render_timer_token.take() { - state.common.event_loop_handle.remove(token); - } - surface.scheduled = false; - } - } - } - }) - .map_err(|err| err.error) - .context("Failed to initialize session event source")?; - - let auto_assign = matches!( - std::env::var("COSMIC_RENDER_AUTO_ASSIGN").map(|val| val.to_lowercase()), - Ok(val) if val == "y" || val == "yes" || val == "true" - ); - - state.backend = BackendData::Kms(KmsState { - api, - _tokens: vec![ - libinput_event_source, - session_event_source, - udev_event_source, - ], - primary_node: primary, - session, - auto_assign, - devices: HashMap::new(), - input_devices: HashMap::new(), - }); - - // Create relative pointer global - RelativePointerManagerState::new::(&dh); - - state.launch_xwayland(Some(primary)); - - for (dev, path) in udev_dispatcher.as_source_ref().device_list() { - if let Err(err) = state.device_added(dev, path.into(), dh) { - warn!("Failed to add device {}: {:?}", path.display(), err); - } - } - - Ok(()) + Ok(dispatcher) } impl State { - fn device_added(&mut self, dev: dev_t, path: PathBuf, dh: &DisplayHandle) -> Result<()> { - if !self.backend.kms().session.is_active() { - return Ok(()); - } - - let fd = DrmDeviceFd::new(DeviceFd::from( - self.backend - .kms() - .session - .open( - &path, - OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK, - ) - .with_context(|| { - format!( - "Failed to optain file descriptor for drm device: {}", - path.display() - ) - })?, - )); - let (drm, notifier) = DrmDevice::new(fd.clone(), false) - .with_context(|| format!("Failed to initialize drm device for: {}", path.display()))?; - let drm_node = DrmNode::from_dev_id(dev)?; - let supports_atomic = drm.is_atomic(); - - let gbm = GbmDevice::new(fd) - .with_context(|| format!("Failed to initialize GBM device for {}", path.display()))?; - let (render_node, formats) = { - let egl_display = unsafe { EGLDisplay::new(gbm.clone()) }.with_context(|| { - format!("Failed to create EGLDisplay for device: {}", path.display()) - })?; - let egl_device = EGLDevice::device_for_display(&egl_display).with_context(|| { - format!("Unable to find matching egl device for {}", path.display()) - })?; - let render_node = egl_device - .try_get_render_node() - .ok() - .and_then(std::convert::identity) - .unwrap_or(drm_node); - let egl_context = EGLContext::new(&egl_display).with_context(|| { - format!( - "Failed to create EGLContext for device {:?}:{}", - egl_device, - path.display() - ) - })?; - let formats = egl_context.dmabuf_texture_formats().clone(); - (render_node, formats) - // NOTE: We need the to drop the EGL types here again, - // otherwise the EGLDisplay created below might share the same GBM context - }; - - let token = self - .common - .event_loop_handle - .insert_source( - notifier, - move |event, metadata, state: &mut State| match event { - DrmEvent::VBlank(crtc) => { - let rescheduled = if let Some(device) = - state.backend.kms().devices.get_mut(&drm_node) - { - if let Some(surface) = device.surfaces.get_mut(&crtc) { - trace!(?crtc, "VBlank"); - #[cfg(feature = "debug")] - surface.fps.displayed(); - - match surface.surface.as_mut().map(|x| x.frame_submitted()) { - Some(Ok(feedback)) => { - if let Some((mut feedback, frames)) = feedback.flatten() { - let submit_time = - match metadata.take().map(|data| data.time) { - Some(DrmEventTime::Monotonic(tp)) => Some(tp), - _ => None, - }; - let seq = metadata - .as_ref() - .map(|metadata| metadata.sequence) - .unwrap_or(0); - - let (clock, flags) = if let Some(tp) = submit_time { - ( - tp.into(), - wp_presentation_feedback::Kind::Vsync - | wp_presentation_feedback::Kind::HwClock - | wp_presentation_feedback::Kind::HwCompletion, - ) - } else { - ( - state.common.clock.now(), - wp_presentation_feedback::Kind::Vsync, - ) - }; - - feedback.presented( - clock, - surface - .output - .current_mode() - .map(|mode| { - Duration::from_secs_f64( - 1_000.0 / mode.refresh as f64, - ) - }) - .unwrap_or_default(), - seq as u64, - flags, - ); - - while let Ok((frame, damage)) = frames.recv() { - frame.success( - surface.output.current_transform(), - damage, - clock, - ); - } - } - - surface.pending = false; - Some(( - surface.output.clone(), - surface.fps.avg_time_to_display(5), - )) - } - Some(Err(err)) => { - warn!(?err, "Failed to submit frame."); - None - } - _ => None, // got disabled - } - } else { - None - } - } else { - None - }; - - if let Some((output, avg_time)) = rescheduled { - let _estimated_rendertime = std::cmp::max(avg_time, MIN_DISPLAY_TIME); - if let Err(err) = state.backend.kms().schedule_render( - &state.common.event_loop_handle, - &output, - None, //Some(estimated_rendertime), - ) { - warn!(?err, "Failed to schedule render."); - } - } - } - DrmEvent::Error(err) => { - warn!(?err, "Failed to read events of device {:?}.", dev); - } - }, - ) - .with_context(|| format!("Failed to add drm device to event loop: {}", dev))?; - - let socket = match self.create_socket(dh, render_node, formats.clone().into_iter()) { - Ok(socket) => Some(socket), - Err(err) => { - warn!( - ?err, - "Failed to initialize hardware-acceleration for clients on {}.", render_node, - ); - None - } - }; - - let mut device = Device { - render_node, - surfaces: HashMap::new(), - gbm: gbm.clone(), - drm, - formats, - supports_atomic, - non_desktop_connectors: Vec::new(), - leasing_global: DrmLeaseState::new::(dh, &drm_node) - .map_err(|err| { - // TODO: replace with inspect_err, once stable - warn!( - ?err, - "Failed to initialize drm lease global for: {}", drm_node - ); - err - }) - .ok(), - active_leases: Vec::new(), - active_buffers: HashSet::new(), - event_token: Some(token), - socket, - }; - - let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices - let mut wl_outputs = Vec::new(); - let mut w = self.common.shell.read().unwrap().global_space().size.w; - { - let backend = self.backend.kms(); - backend - .api - .as_mut() - .add_node(render_node, gbm) - .with_context(|| { - format!( - "Failed to initialize renderer for device: {}, skipping", - render_node - ) - })?; - - let mut renderer = match backend.api.single_renderer(&render_node) { - Ok(renderer) => renderer, - Err(err) => { - backend.api.as_mut().remove_node(&render_node); - return Err(err).with_context(|| { - format!( - "Failed to initialize renderer for device: {}, skipping", - render_node - ) - }); - } - }; - init_shaders(&mut renderer).expect("Failed to initialize renderer"); - - for (crtc, conn) in outputs { - let non_desktop = - match drm_helpers::get_property_val(&device.drm, conn, "non-desktop") { - Ok((val_type, value)) => { - val_type.convert_value(value).as_boolean().unwrap() - } - Err(err) => { - warn!( - ?err, - "Failed to determine if connector is meant desktop usage, assuming so." - ); - false - } - }; - - if non_desktop { - let Ok(output_name) = drm_helpers::interface_name(&device.drm, conn) else { - continue; - }; - let drm_helpers::EdidInfo { - model, - manufacturer, - } = match drm_helpers::edid_info(&device.drm, conn) { - Ok(info) => info, - Err(_) => drm_helpers::EdidInfo { - model: "Unknown".into(), - manufacturer: "Unknown".into(), - }, - }; - - device.non_desktop_connectors.push((conn, crtc)); - info!( - "Connector {} is non-desktop, setting up for leasing", - output_name - ); - if let Some(lease_state) = device.leasing_global.as_mut() { - lease_state.add_connector::( - conn, - output_name, - format!("{} {}", manufacturer, model), - ); - } - } else { - match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { - Ok(output) => { - w += output - .user_data() - .get::>() - .unwrap() - .borrow() - .mode_size() - .w; - wl_outputs.push(output); - } - Err(err) => warn!(?err, "Failed to initialize output."), - }; - } - } - - if !device.in_use(&backend.primary_node) { - backend.api.as_mut().remove_node(&render_node); - } - - backend.devices.insert(drm_node, device); - } - - self.common - .output_configuration_state - .add_heads(wl_outputs.iter()); - self.common.config.read_outputs( - &mut self.common.output_configuration_state, - &mut self.backend, - &mut *self.common.shell.write().unwrap(), - &self.common.event_loop_handle, - &mut self.common.workspace_state.update(), - &self.common.xdg_activation_state, - ); - self.common.refresh(); - - Ok(()) - } - - pub(crate) fn device_changed(&mut self, dev: dev_t) -> Result<()> { - if !self.backend.kms().session.is_active() { - return Ok(()); - } - - let drm_node = DrmNode::from_dev_id(dev)?; - let mut outputs_removed = Vec::new(); - let mut outputs_added = Vec::new(); - { - let backend = self.backend.kms(); - if let Some(device) = backend.devices.get_mut(&drm_node) { - let changes = device.enumerate_surfaces()?; - let mut w = self.common.shell.read().unwrap().global_space().size.w; - for crtc in changes.removed { - if let Some(pos) = device - .non_desktop_connectors - .iter() - .position(|(_, handle)| *handle == crtc) - { - let (conn, _) = device.non_desktop_connectors.remove(pos); - if let Some(leasing_state) = device.leasing_global.as_mut() { - leasing_state.withdraw_connector(conn); - } - } else if let Some(surface) = device.surfaces.remove(&crtc) { - if let Some(token) = surface.render_timer_token { - self.common.event_loop_handle.remove(token); - } - w -= surface.output.current_mode().map(|m| m.size.w).unwrap_or(0); - outputs_removed.push(surface.output.clone()); - } - } - backend - .api - .as_mut() - .add_node(device.render_node, device.gbm.clone()) - .with_context(|| { - format!( - "Failed to initialize renderer for device: {}, skipping", - device.render_node - ) - })?; - - let mut renderer = match backend.api.single_renderer(&device.render_node) { - Ok(renderer) => renderer, - Err(err) => { - backend.api.as_mut().remove_node(&device.render_node); - return Err(err).with_context(|| { - format!( - "Failed to initialize renderer for device: {}, skipping", - device.render_node - ) - }); - } - }; - init_shaders(&mut renderer).expect("Failed to initialize renderer"); - - for (crtc, conn) in changes.added { - let non_desktop = - match drm_helpers::get_property_val(&device.drm, conn, "non-desktop") { - Ok((val_type, value)) => { - val_type.convert_value(value).as_boolean().unwrap() - } - Err(err) => { - warn!( - ?err, - "Failed to determine if connector is meant desktop usage, assuming so." - ); - false - } - }; - - if non_desktop { - let Ok(output_name) = drm_helpers::interface_name(&device.drm, conn) else { - continue; - }; - let drm_helpers::EdidInfo { - model, - manufacturer, - } = match drm_helpers::edid_info(&device.drm, conn) { - Ok(info) => info, - Err(_) => drm_helpers::EdidInfo { - model: "Unknown".into(), - manufacturer: "Unknown".into(), - }, - }; - - device.non_desktop_connectors.push((conn, crtc)); - info!( - "Connector {} is non-desktop, setting up for leasing", - output_name - ); - if let Some(lease_state) = device.leasing_global.as_mut() { - lease_state.add_connector::( - conn, - output_name, - format!("{} {}", manufacturer, model), - ); - } - } else { - match device.setup_surface(crtc, conn, (w, 0), &mut renderer) { - Ok(output) => { - w += output - .user_data() - .get::>() - .unwrap() - .borrow() - .mode_size() - .w; - outputs_added.push(output); - } - Err(err) => warn!(?err, "Failed to initialize output."), - }; - } - } - } - } - - self.common - .output_configuration_state - .remove_heads(outputs_removed.iter()); - self.common - .output_configuration_state - .add_heads(outputs_added.iter()); - { - self.common.config.read_outputs( - &mut self.common.output_configuration_state, - &mut self.backend, - &mut *self.common.shell.write().unwrap(), - &self.common.event_loop_handle, - &mut self.common.workspace_state.update(), - &self.common.xdg_activation_state, - ); - self.common.refresh(); - // Don't remove the outputs, before potentially new ones have been initialized. - // reading a new config means outputs might become enabled, that were previously disabled. - // If we have 0 outputs at some point, we won't quit, but shell doesn't know where to move - // windows and workspaces to. - for output in outputs_removed { - self.common.remove_output(&output); - } - } - - { - let backend = self.backend.kms(); - if let Some(device) = backend.devices.get_mut(&drm_node) { - if !device.in_use(&backend.primary_node) { - backend.api.as_mut().remove_node(&device.render_node); - } - } - } - - Ok(()) - } - - fn device_removed(&mut self, dev: dev_t, dh: &DisplayHandle) -> Result<()> { - let drm_node = DrmNode::from_dev_id(dev)?; - let mut outputs_removed = Vec::new(); + fn resume_session( + &mut self, + dispatcher: Dispatcher<'static, UdevBackend, Self>, + loop_handle: LoopHandle<'static, State>, + loop_signal: LoopSignal, + ) { let backend = self.backend.kms(); - if let Some(mut device) = backend.devices.remove(&drm_node) { - if let Some(mut leasing_global) = device.leasing_global.take() { - leasing_global.disable_global::(); + + // resume input + if let Err(err) = backend.libinput.resume() { + error!(?err, "Failed to resume libinput context."); + } + // active drm, resume leases + for device in backend.drm_devices.values_mut() { + if let Err(err) = device.drm.activate(true) { + error!(?err, "Failed to resume drm device"); } - backend.api.as_mut().remove_node(&device.render_node); - for surface in device.surfaces.values_mut() { - if let Some(token) = surface.render_timer_token.take() { - self.common.event_loop_handle.remove(token); - } - outputs_removed.push(surface.output.clone()); - } - if let Some(token) = device.event_token.take() { - self.common.event_loop_handle.remove(token); - } - if let Some(socket) = device.socket.take() { - self.common.event_loop_handle.remove(socket.token); - self.common - .dmabuf_state - .destroy_global::(dh, socket.dmabuf_global); - dh.remove_global::(socket.drm_global); + if let Some(lease_state) = device.leasing_global.as_mut() { + lease_state.resume::(); } } - self.common - .output_configuration_state - .remove_heads(outputs_removed.iter()); - if self.backend.kms().session.is_active() { - for output in outputs_removed { - self.common.remove_output(&output); - } - self.common.config.read_outputs( - &mut self.common.output_configuration_state, - &mut self.backend, - &mut *self.common.shell.write().unwrap(), - &self.common.event_loop_handle, - &mut self.common.workspace_state.update(), - &self.common.xdg_activation_state, - ); - self.common.refresh(); - } else { - self.common.output_configuration_state.update(); - } - - Ok(()) - } -} - -pub struct OutputChanges { - pub added: Vec<(crtc::Handle, connector::Handle)>, - pub removed: Vec, -} - -impl Device { - pub fn enumerate_surfaces(&mut self) -> Result { - // enumerate our outputs - let config = drm_helpers::display_configuration(&mut self.drm, self.supports_atomic)?; - - let surfaces = self - .surfaces - .iter() - .map(|(c, s)| (*c, s.connector)) - .chain( - self.non_desktop_connectors - .iter() - .map(|(conn, crtc)| (*crtc, *conn)), - ) - .collect::>(); - - let added = config - .iter() - .filter(|(conn, crtc)| surfaces.get(&crtc).map(|c| c != *conn).unwrap_or(true)) - .map(|(conn, crtc)| (crtc, conn)) - .map(|(crtc, conn)| (*crtc, *conn)) - .collect::>(); - let removed = surfaces - .iter() - .filter(|(crtc, conn)| config.get(conn).map(|c| c != *crtc).unwrap_or(true)) - .map(|(crtc, _)| *crtc) - .collect::>(); - - Ok(OutputChanges { added, removed }) - } - - fn setup_surface( - &mut self, - crtc: crtc::Handle, - conn: connector::Handle, - position: (i32, i32), - renderer: &mut GlMultiRenderer<'_>, - ) -> Result { - let drm = &mut self.drm; - let crtc_info = drm.get_crtc(crtc)?; - let conn_info = drm.get_connector(conn, false)?; - let vrr = drm_helpers::set_vrr(drm, crtc, conn, false).unwrap_or(false); - let max_bpc = drm_helpers::get_max_bpc(drm, conn)?.map(|(_val, range)| range.end.min(16)); - let interface = drm_helpers::interface_name(drm, conn)?; - let edid_info = drm_helpers::edid_info(drm, conn); - let mode = crtc_info.mode().unwrap_or_else(|| { - conn_info - .modes() - .iter() - .find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED)) - .copied() - .unwrap_or(conn_info.modes()[0]) - }); - let refresh_rate = drm_helpers::calculate_refresh_rate(mode); - let output_mode = OutputMode { - size: (mode.size().0 as i32, mode.size().1 as i32).into(), - refresh: refresh_rate as i32, - }; - let (phys_w, phys_h) = conn_info.size().unwrap_or((0, 0)); - let output = Output::new( - interface, - PhysicalProperties { - size: (phys_w as i32, phys_h as i32).into(), - subpixel: match conn_info.subpixel() { - connector::SubPixel::HorizontalRgb => Subpixel::HorizontalRgb, - connector::SubPixel::HorizontalBgr => Subpixel::HorizontalBgr, - connector::SubPixel::VerticalRgb => Subpixel::VerticalRgb, - connector::SubPixel::VerticalBgr => Subpixel::VerticalBgr, - connector::SubPixel::None => Subpixel::None, - _ => Subpixel::Unknown, - }, - make: edid_info - .as_ref() - .map(|info| info.manufacturer.clone()) - .unwrap_or_else(|_| String::from("Unknown")), - model: edid_info - .as_ref() - .map(|info| info.model.clone()) - .unwrap_or_else(|_| String::from("Unknown")), - }, - ); - for mode in conn_info.modes() { - let refresh_rate = drm_helpers::calculate_refresh_rate(*mode); - let mode = OutputMode { - size: (mode.size().0 as i32, mode.size().1 as i32).into(), - refresh: refresh_rate as i32, - }; - output.add_mode(mode); - } - output.set_preferred(output_mode); - output.change_current_state( - Some(output_mode), - // TODO: Readout property for monitor rotation - Some(Transform::Normal), - None, - Some(position.into()), - ); - output.user_data().insert_if_missing(|| { - RefCell::new(OutputConfig { - mode: ((output_mode.size.w, output_mode.size.h), Some(refresh_rate)), - vrr, - position, - max_bpc, - ..Default::default() - }) - }); - - let data = Surface { - output: output.clone(), - mirroring: None, - mirroring_textures: HashMap::new(), - surface: None, - connector: conn, - vrr, - refresh_rate, - scheduled: false, - pending: false, - render_timer_token: None, - fps: Fps::new(renderer.as_mut()), - feedback: HashMap::new(), - }; - self.surfaces.insert(crtc, data); - - Ok(output) - } - - pub fn in_use(&self, primary: &DrmNode) -> bool { - &self.render_node == primary || !self.surfaces.is_empty() || !self.active_buffers.is_empty() - } -} - -fn source_node_for_surface<'a>(w: &WlSurface) -> Option { - with_renderer_surface_state(w, |state| { - state - .buffer() - .and_then(|buffer| get_dmabuf(buffer).ok().and_then(|dmabuf| dmabuf.node())) - }) - .flatten() -} - -fn render_node_for_output( - output: &Output, - primary_node: DrmNode, - target_node: DrmNode, - shell: &Shell, -) -> DrmNode { - if target_node == primary_node { - return target_node; - } - - let workspace = shell.active_space(output); - let nodes = workspace - .get_fullscreen() - .map(|w| vec![w.clone()]) - .unwrap_or_else(|| { - workspace - .mapped() - .map(|mapped| mapped.active_window()) - .collect::>() - }) - .into_iter() - .flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s))) - .collect::>(); - - if nodes.contains(&target_node) || nodes.is_empty() { - target_node - } else { - primary_node - } -} - -fn get_surface_dmabuf_feedback( - render_node: DrmNode, - render_formats: HashSet, - target_formats: HashSet, - compositor: &GbmDrmCompositor, -) -> SurfaceDmabufFeedback { - let combined_formats = render_formats - .intersection(&target_formats) - .copied() - .collect::>(); - - let surface = compositor.surface(); - let planes = surface.planes(); - // We limit the scan-out trache 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 = planes - .primary - .formats - .iter() - .cloned() - .chain( - planes - .overlay - .iter() - .flat_map(|p| p.formats.iter().cloned()), - ) - .collect::>() - .intersection(&combined_formats) - .copied() - .collect::>(); - - let target_node = surface.device_fd().dev_id().unwrap(); - let builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats); - /* - // iris doesn't handle nvidia buffers very well (it hangs). - // so only do this in the future with v6 and clients telling us the gpu - if target_node != render_node.dev_id() && !combined_formats.is_empty() { - builder = builder.add_preference_tranche( - target_node, - Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout), - combined_formats, - ); - }; - */ - - let render_feedback = builder.clone().build().unwrap(); - // we would want to do this in other cases as well, but same thing as above applies - let scanout_feedback = if target_node == render_node.dev_id() { - builder - .add_preference_tranche( - target_node, - Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout), - planes_formats, - ) - .build() - .unwrap() - } else { - builder.build().unwrap() - }; - - SurfaceDmabufFeedback { - render_feedback, - scanout_feedback, - } -} - -impl Surface { - #[profiling::function] - pub fn render_output( - &mut self, - api: &mut GpuManager>, - render_node: Option<&DrmNode>, - target_node: &DrmNode, - state: &mut Common, - ) -> Result<()> { - if self.surface.is_none() { - return Ok(()); - } - - let compositor = self.surface.as_mut().unwrap(); - let (render_node, mut renderer) = match render_node { - Some(render_node) => ( - render_node, - api.renderer(&render_node, &target_node, compositor.format()) - .unwrap(), - ), - _ => (target_node, api.single_renderer(&target_node).unwrap()), - }; - - self.fps.start(); - #[cfg(feature = "debug")] - if let Some(rd) = self.fps.rd.as_mut() { - rd.start_frame_capture( - renderer.glow_renderer().egl_context().get_context_handle(), - std::ptr::null(), - ); - } - - let mut elements = { - let shell = state.shell.read().unwrap(); - let output = self.mirroring.as_ref().unwrap_or(&self.output); - - let (previous_workspace, workspace) = shell.workspaces.active(output); - let (previous_idx, idx) = shell.workspaces.active_num(output); - let previous_workspace = previous_workspace - .zip(previous_idx) - .map(|((w, start), idx)| (w.handle, idx, start)); - let workspace = (workspace.handle, idx); - - workspace_elements( - Some(&render_node), - &mut renderer, - &*shell, - &state.config, - &state.theme, - state.clock.now(), - output, - previous_workspace, - workspace, - CursorMode::All, - &mut Some(&mut self.fps), - false, - ) - .map_err(|err| { - anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) - })? - }; - self.fps.elements(); - - let frames: Vec<( - ScreencopySession, - ScreencopyFrame, - Result<(Option>>, RenderElementStates), OutputNoMode>, - )> = self - .mirroring - .is_none() - .then(|| { - self.output - .take_pending_frames() - .into_iter() - .map(|(session, frame)| { - let additional_damage = frame.damage(); - let session_data = session.user_data().get::().unwrap(); - let mut damage_tracking = session_data.borrow_mut(); - - let old_len = if !additional_damage.is_empty() { - let area = self - .output - .current_mode() - .unwrap() - /* TODO: Mode is Buffer..., why is this Physical in the first place */ - .size - .to_logical(1) - .to_buffer(1, Transform::Normal) - .to_f64(); - - let old_len = elements.len(); - elements.extend( - additional_damage - .into_iter() - .map(|rect| { - rect.to_f64() - .to_logical( - self.output.current_scale().fractional_scale(), - self.output.current_transform(), - &area, - ) - .to_i32_round() - }) - .map(DamageElement::new) - .map(Into::into), - ); - - Some(old_len) - } else { - None - }; - - let buffer = frame.buffer(); - let age = damage_tracking.age_for_buffer(&buffer); - let res = damage_tracking.dt.damage_output(age, &elements); - - if let Some(old_len) = old_len { - elements.truncate(old_len); - } - - let res = res.map(|(a, b)| (a.cloned(), b)); - std::mem::drop(damage_tracking); - (session, frame, res) - }) - .collect() - }) - .unwrap_or_default(); - - let res = if let Some(mirrored_output) = self.mirroring.as_ref().filter(|mirrored_output| { - mirrored_output.current_mode().is_some_and(|mirror_mode| { - self.output - .current_mode() - .is_some_and(|mode| mode != mirror_mode) - }) - }) { - let mirroring_state = { - let entry = self.mirroring_textures.entry(*target_node); - let mut new_state = None; - if matches!(entry, std::collections::hash_map::Entry::Vacant(_)) { - new_state = Some(MirroringState::new_with_renderer( - &mut renderer, - compositor.format(), - mirrored_output, - )?); - } - // I really want a failable initializer... - entry.or_insert_with(|| new_state.unwrap()) - }; - - mirroring_state - .texture - .render() - .draw::<_, ::Error>(|tex| { - let res = match mirroring_state.damage_tracker.render_output_with( - &mut renderer, - tex.clone(), - 1, - &elements, - CLEAR_COLOR, - ) { - Ok(res) => res, - Err(RenderError::Rendering(err)) => return Err(err), - Err(RenderError::OutputNoMode(_)) => unreachable!(), - }; - - renderer.wait(&res.sync)?; - - let transform = mirrored_output.current_transform(); - let area = tex.size().to_logical(1, transform); - - Ok(res - .damage - .cloned() - .map(|v| { - v.into_iter() - .map(|r| r.to_logical(1).to_buffer(1, transform, &area)) - .collect::>() - }) - .unwrap_or_default()) - }) - .context("Failed to draw to offscreen render target")?; - - let texture_elem = TextureRenderElement::from_texture_render_buffer( - (0., 0.), - &mirroring_state.texture, - Some(1.0), - None, - None, - Kind::Unspecified, - ); - let texture_geometry = texture_elem.geometry(1.0.into()); - elements = constrain_render_elements( - std::iter::once(texture_elem), - (0, 0), - Rectangle::from_loc_and_size( - (0, 0), - self.output - .geometry() - .size - .as_logical() - .to_f64() - .to_physical(self.output.current_scale().fractional_scale()) - .to_i32_round(), - ), - texture_geometry, - ConstrainScaleBehavior::Fit, - ConstrainAlign::CENTER, - 1.0, - ) - .map(CosmicElement::Mirror) - .collect::>(); - - renderer = api.single_renderer(&target_node).unwrap(); - compositor.render_frame(&mut renderer, &elements, [0.0, 0.0, 0.0, 1.0]) - } else { - compositor.render_frame( - &mut renderer, - &elements, - CLEAR_COLOR, // TODO use a theme neutral color - ) - }; - self.fps.render(); - - match res { - Ok(frame_result) => { - let (tx, rx) = std::sync::mpsc::channel(); - - let feedback = if !frame_result.is_empty && self.mirroring.is_none() { - Some(( - state.take_presentation_feedback(&self.output, &frame_result.states), - rx, - )) - } else { - None - }; - - if frame_result.needs_sync() { - if let PrimaryPlaneElement::Swapchain(elem) = &frame_result.primary_element { - elem.sync.wait()?; - } - } - - match compositor.queue_frame(feedback) { - x @ Ok(()) | x @ Err(FrameError::EmptyFrame) => { - for (session, frame, res) in frames { - let damage = match res { - Ok((damage, _)) => damage, - Err(err) => { - tracing::warn!(?err, "Failed to screencopy"); - session - .user_data() - .get::() - .unwrap() - .borrow_mut() - .reset(); - frame.fail(FailureReason::Unknown); - continue; - } - }; - - let mut sync = SyncPoint::default(); - - if let Some(ref damage) = damage { - let buffer = frame.buffer(); - if let Ok(dmabuf) = get_dmabuf(&buffer) { - renderer - .bind(dmabuf.clone()) - .map_err(RenderError::::Rendering)?; - } else { - let size = buffer_dimensions(&buffer).ok_or(RenderError::< - GlMultiRenderer, - >::Rendering( - MultiError::ImportFailed, - ))?; - let format = - with_buffer_contents(&buffer, |_, _, data| shm_format_to_fourcc(data.format)) - .map_err(|_| OutputNoMode)? // eh, we have to do some error - .expect("We should be able to convert all hardcoded shm screencopy formats"); - let render_buffer = - Offscreen::::create_buffer( - &mut renderer, - format, - size, - ) - .map_err(RenderError::::Rendering)?; - renderer - .bind(render_buffer) - .map_err(RenderError::::Rendering)?; - } - - let (output_size, output_scale, output_transform) = ( - self.output.current_mode().ok_or(OutputNoMode)?.size, - self.output.current_scale().fractional_scale(), - self.output.current_transform(), - ); - - let filter = (!session.draw_cursor()) - .then(|| { - elements.iter().filter_map(|elem| { - if let CosmicElement::Cursor(_) = elem { - Some(elem.id().clone()) - } else { - None - } - }) - }) - .into_iter() - .flatten(); - - match frame_result - .blit_frame_result( - output_size, - output_transform, - output_scale, - &mut renderer, - damage.iter().copied(), - filter, - ) - .map_err(|err| match err { - BlitFrameResultError::Rendering(err) => { - RenderError::::Rendering(err) - } - BlitFrameResultError::Export(_) => { - RenderError::::Rendering( - MultiError::DeviceMissing, - ) - } - }) { - Ok(new_sync) => { - sync = new_sync; - } - Err(err) => { - tracing::warn!(?err, "Failed to screencopy"); - session - .user_data() - .get::() - .unwrap() - .borrow_mut() - .reset(); - frame.fail(FailureReason::Unknown); - continue; - } - }; - } - - let transform = self.output.current_transform(); - - match submit_buffer( - frame, - &mut renderer, - transform, - damage.as_deref(), - sync, - ) { - Ok(Some((frame, damage))) => { - if frame_result.is_empty { - frame.success(transform, damage, state.clock.now()); - } else { - let _ = tx.send((frame, damage)); - } - } - Ok(None) => {} - Err(err) => { - session - .user_data() - .get::() - .unwrap() - .borrow_mut() - .reset(); - tracing::warn!(?err, "Failed to screencopy"); - } - } - } - - if x.is_ok() { - self.pending = true; - } else { - tracing::debug!("Stopped rendering"); - } - } + // update state and schedule new render, + // after processing the rest of the pending event loop events + let dispatcher = dispatcher.clone(); + loop_handle.insert_idle(move |state| { + // add new devices, update devices now + for (dev, path) in dispatcher.as_source_ref().device_list() { + let drm_node = match DrmNode::from_dev_id(dev) { + Ok(node) => node, Err(err) => { - for (session, frame, _) in frames { - session - .user_data() - .get::() - .unwrap() - .borrow_mut() - .reset(); - frame.fail(FailureReason::Unknown); - } - return Err(err).with_context(|| "Failed to submit result for display"); + error!(?err, "Failed to read drm device {}.", path.display(),); + continue; } }; - - if self.mirroring.is_none() { - state.send_frames(&self.output, &frame_result.states, |source_node| { - Some( - self.feedback - .entry(source_node) - .or_insert_with(|| { - let render_formats = api - .single_renderer(&source_node) - .unwrap() - .dmabuf_formats() - .collect::>(); - let target_formats = api - .single_renderer(target_node) - .unwrap() - .dmabuf_formats() - .collect::>(); - get_surface_dmabuf_feedback( - source_node, - render_formats, - target_formats, - compositor, - ) - }) - .clone(), - ) - }); + if state.backend.kms().drm_devices.contains_key(&drm_node) { + if let Err(err) = state.device_changed(dev) { + error!(?err, "Failed to update drm device {}.", path.display(),); + } + } else { + let dh = state.common.display_handle.clone(); + if let Err(err) = state.device_added(dev, path.into(), &dh) { + error!(?err, "Failed to add drm device {}.", path.display(),); + } } } - Err(err) => { - compositor.reset_buffers(); - anyhow::bail!("Rendering failed: {}", err); + + // update outputs + state.common.config.read_outputs( + &mut state.common.output_configuration_state, + &mut state.backend, + &state.common.shell, + &state.common.event_loop_handle, + &mut state.common.workspace_state.update(), + &state.common.xdg_activation_state, + state.common.startup_done.clone(), + ); + state.common.refresh(); + }); + loop_signal.wakeup(); + } + + fn pause_session(&mut self) { + let backend = self.backend.kms(); + backend.libinput.suspend(); + for device in backend.drm_devices.values_mut() { + device.drm.pause(); + if let Some(lease_state) = device.leasing_global.as_mut() { + lease_state.suspend(); + } + for surface in device.surfaces.values_mut() { + surface.suspend(); } } - - Ok(()) } } @@ -1649,70 +311,268 @@ impl KmsState { self.session.change_vt(num).map_err(Into::into) } - #[profiling::function] - pub fn apply_config_for_output( + pub fn dmabuf_imported( &mut self, - output: &Output, - shell: &mut Shell, - test_only: bool, - loop_handle: &LoopHandle<'_, State>, - workspace_state: &mut WorkspaceUpdateGuard<'_, State>, - xdg_activation_state: &XdgActivationState, - ) -> Result<(), anyhow::Error> { - let outputs = self - .devices - .values() - .flat_map(|device| { - device - .surfaces - .values() - .map(|surface| surface.output.clone()) - }) - .collect::>(); + _client: Option, + global: &DmabufGlobal, + dmabuf: Dmabuf, + ) -> Result { + let (expected_node, other_nodes) = + self.drm_devices + .values_mut() + .partition::, _>(|device| { + device + .socket + .as_ref() + .map(|s| &s.dmabuf_global == global) + .unwrap_or(false) + }); - let recreated = if let Some(device) = self - .devices - .values_mut() - .find(|dev| dev.surfaces.values().any(|s| s.output == *output)) - { - let (crtc, surface) = device - .surfaces - .iter_mut() - .find(|(_, s)| s.output == *output) - .unwrap(); - let output_config = output - .user_data() - .get::>() - .unwrap() - .borrow(); - let mirrored_output = if let OutputState::Mirroring(conn) = &output_config.enabled { - Some( - outputs - .iter() - .find(|output| &output.name() == conn) - .cloned() - .ok_or(anyhow::anyhow!("Unable to find mirroring output"))?, - ) + let mut last_err = anyhow::anyhow!("Dmabuf cannot be imported on any gpu"); + for device in expected_node.into_iter().chain(other_nodes.into_iter()) { + let mut _egl = None; + let egl_display = if let Some(egl_display) = + device.egl.as_ref().map(|internals| &internals.display) + { + egl_display } else { - None + _egl = Some(init_egl(&device.gbm).context("Failed to initialize egl context")?); + &_egl.as_ref().unwrap().display }; - if output_config.enabled == OutputState::Disabled { - if !test_only { - shell.workspaces.remove_output( - output, - shell.seats.iter(), - workspace_state, - xdg_activation_state, - ); - if surface.surface.take().is_some() { - // just drop it - surface.pending = false; - } - surface.mirroring_textures.clear(); + let result = egl_display + .create_image_from_dmabuf(&dmabuf) + .map(|_| device.render_node) + .map_err(Into::into); + + match result { + Ok(node) => { + dmabuf.set_node(node); // so the MultiRenderer knows what node to use + return Ok(node); } - false + Err(err) => { + trace!(?err, "Failed to import dmabuf on {:?}", device.render_node); + last_err = err; + } + } + } + + Err(last_err) + } + + pub fn schedule_render(&mut self, output: &Output) { + for surface in self + .drm_devices + .values() + .flat_map(|d| d.surfaces.values()) + .filter(|s| s.output == *output || s.output.mirroring().is_some_and(|o| &o == output)) + { + surface.schedule_render(); + } + } + + pub fn target_node_for_output(&self, output: &Output) -> Option { + self.drm_devices + .values() + .find(|dev| dev.surfaces.values().any(|s| s.output == *output)) + .map(|dev| &dev.render_node) + .copied() + } + + pub fn refresh_used_devices(&mut self) -> Result<()> { + let mut used_devices = HashSet::new(); + + for (node, device) in self.drm_devices.iter_mut() { + if device.in_use(self.primary_node.as_ref()) { + if device.egl.is_none() { + let egl = init_egl(&device.gbm).context("Failed to create EGL context")?; + self.api.as_mut().add_node( + device.render_node, + GbmAllocator::new( + device.gbm.clone(), + // SCANOUT because stride bugs + GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, + ), + unsafe { + GlowRenderer::new( + EGLContext::new_shared_with_priority( + &egl.display, + &egl.context, + ContextPriority::High, + ) + .context("Failed to create shared EGL context")?, + ) + .context("Failed to create GL renderer")? + }, + ); + device.egl = Some(egl); + } + used_devices.insert(*node); } else { + if device.egl.is_none() { + let _ = device.egl.take(); + self.api.as_mut().remove_node(&device.render_node); + } + } + } + + // I hate this. I want partial borrows of hashmap values + let all_devices = self.drm_devices.keys().copied().collect::>(); + for node in all_devices { + let (mut device, mut others) = self + .drm_devices + .iter_mut() + .partition::, _>(|(n, _)| **n == node); + let device = &mut device[0].1; + + for surface in device.surfaces.values_mut() { + let known_nodes = surface.known_nodes().clone(); + for gone_device in known_nodes.difference(&used_devices) { + surface.remove_node(*gone_device); + } + for new_device in used_devices.difference(&known_nodes) { + let (render_node, egl, gbm) = if node == *new_device { + // we need to make sure to do partial borrows here, as device.surfaces is borrowed mutable + ( + device.render_node, + device.egl.as_ref().unwrap(), + device.gbm.clone(), + ) + } else { + let device = others + .iter_mut() + .find(|(n, _)| *n == new_device) + .map(|(_, d)| d) + .unwrap(); + ( + device.render_node, + device.egl.as_ref().unwrap(), + device.gbm.clone(), + ) + }; + + surface.add_node( + render_node, + GbmAllocator::new(gbm, GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT), + EGLContext::new_shared_with_priority( + &egl.display, + &egl.context, + ContextPriority::High, + ) + .context("Failed to create shared EGL context")?, + ); + } + } + } + + Ok(()) + } + + pub fn apply_config_for_outputs( + &mut self, + test_only: bool, + loop_handle: &LoopHandle<'static, State>, + shell: Arc>, + startup_done: Arc<(Mutex, Condvar)>, + ) -> Result, anyhow::Error> { + if !self.session.is_active() { + return Ok(Vec::new()); + } + + let mut all_outputs = Vec::new(); + for device in self.drm_devices.values_mut() { + // we only want outputs exposed to wayland - not leased ones + // but that is also not all surface, because that doesn't contain all detected, but unmapped outputs + let outputs = device + .outputs + .iter() + .filter(|(conn, _)| { + !device + .leased_connectors + .iter() + .any(|(leased_conn, _)| *conn == leased_conn) + }) + .map(|(_, output)| output.clone()) + .collect::>(); + + let mut new_pairings = HashMap::new(); + + // figure out potential new crtcs + // TODO: Right now we always keep crtcs of already enabled outputs, + // even if another configuration could potentially enable more outputs + + let res_handles = device.drm.resource_handles()?; + let free_crtcs = res_handles + .crtcs() + .iter() + .filter(|crtc| { + !device + .surfaces + .get(crtc) + .is_some_and(|surface| surface.output.is_enabled()) + }) + .copied() + .collect::>(); + let open_conns = outputs + .iter() + .filter(|output| { + output.is_enabled() && !device.surfaces.values().any(|s| &s.output == *output) + }) + .flat_map(|output| { + device + .outputs + .iter() + .find_map(|(conn, o)| (output == o).then_some(*conn)) + }); + + for conn in open_conns { + let conn_info = device.drm.get_connector(conn, false).with_context(|| { + format!( + "Failed to query drm device: {:?}", + device.drm.dev_path().as_deref().map(Path::display), + ) + })?; + 'outer: for encoder_info in conn_info + .encoders() + .iter() + .flat_map(|encoder_handle| device.drm.get_encoder(*encoder_handle)) + { + for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { + if !free_crtcs.contains(&crtc) { + new_pairings.insert(conn, crtc); + break 'outer; + } + } + + // test failed, we don't have a crtc for conn + anyhow::bail!("Missing crtc for {conn:?}, gpu doesn't have enough resources."); + } + } + + // first drop old surfaces + if !test_only { + for output in outputs.iter().filter(|o| !o.is_enabled()) { + device + .surfaces + .retain(|_, surface| surface.output != *output); + } + } + + // reconfigure existing + for (crtc, surface) in device.surfaces.iter_mut() { + let output_config = surface.output.config(); + let mirrored_output = if let OutputState::Mirroring(conn) = &output_config.enabled { + Some( + outputs + .iter() + .find(|output| &output.name() == conn) + .cloned() + .ok_or(anyhow::anyhow!("Unable to find mirroring output"))?, + ) + } else { + None + }; + let drm = &mut device.drm; let conn = surface.connector; let conn_info = drm.get_connector(conn, false)?; @@ -1729,284 +589,77 @@ impl KmsState { let refresh_rate = drm_helpers::calculate_refresh_rate(**mode); (output_config.mode.1.unwrap() as i32 - refresh_rate as i32).abs() }) - .ok_or(anyhow::anyhow!("Unknown mode"))?; + .ok_or(anyhow::anyhow!("Unable to find matching mode"))?; if !test_only { - let res = if let Some(compositor) = surface.surface.as_mut() { - if output_config.vrr != surface.vrr { - surface.vrr = drm_helpers::set_vrr( - drm, - *crtc, - conn_info.handle(), - output_config.vrr, - )?; - } - compositor - .use_mode(*mode) - .context("Failed to apply new mode")?; - false - } else { - surface.vrr = drm_helpers::set_vrr(drm, *crtc, conn, output_config.vrr) + if !surface.is_active() { + let drm_surface = drm + .create_surface(*crtc, *mode, &[conn]) + .with_context(|| "Failed to create drm surface")?; + let gbm = device.gbm.clone(); + let cursor_size = drm.cursor_size(); + + let vrr = drm_helpers::set_vrr(drm, *crtc, conn, output_config.vrr) .unwrap_or(false); + surface.output.set_adaptive_sync(vrr); + if let Some(bpc) = output_config.max_bpc { if let Err(err) = drm_helpers::set_max_bpc(drm, conn, bpc) { warn!( ?bpc, ?err, "Failed to set max_bpc on connector: {}", - output.name() + surface.output.name() ); } } - surface.refresh_rate = drm_helpers::calculate_refresh_rate(*mode); - let drm_surface = drm.create_surface(*crtc, *mode, &[conn])?; - let driver = drm - .get_driver() - .with_context(|| "Failed to query drm driver")?; - let mut planes = drm_surface.planes().clone(); - // QUIRK: Using an overlay plane on a nvidia card breaks the display controller (wtf...) - if driver - .name() - .to_string_lossy() - .to_lowercase() - .contains("nvidia") - { - planes.overlay = vec![]; - } - - let target = DrmCompositor::new( - &surface.output, - drm_surface, - Some(planes), - GbmAllocator::new( - device.gbm.clone(), - GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, - ), - device.gbm.clone(), - &[ - Fourcc::Abgr2101010, - Fourcc::Argb2101010, - Fourcc::Abgr8888, - Fourcc::Argb8888, - ], - device.formats.clone(), - drm.cursor_size(), - Some(device.gbm.clone()), - ) - .with_context(|| { - format!( - "Failed to initialize drm surface for {}", - drm_helpers::interface_name(drm, conn) - .unwrap_or_else(|_| String::from("Unknown")) - ) - })?; - surface.surface = Some(target); - true - }; - - if mirrored_output != surface.mirroring { - shell.workspaces.remove_output( - output, - shell.seats.iter(), - workspace_state, - xdg_activation_state, - ); - surface.mirroring = mirrored_output.clone(); - surface.mirroring_textures.clear(); - } - if mirrored_output.is_none() { - shell - .workspaces - .add_output(output, workspace_state, xdg_activation_state); - } - res - } else { - false - } - } - } else { - false - }; - - if recreated { - if let Err(err) = self.schedule_render(loop_handle, output, None) { - error!( - ?err, - "Error scheduling event loop for output {}.", - output.name(), - ); - } - } - Ok(()) - } - - pub fn target_node_for_output(&self, output: &Output) -> Option { - self.devices - .values() - .find(|dev| dev.surfaces.values().any(|s| s.output == *output)) - .map(|dev| &dev.render_node) - .copied() - } - - pub fn dmabuf_imported( - &mut self, - _client: Option, - global: &DmabufGlobal, - dmabuf: Dmabuf, - ) -> Result { - let (expected_node, other_nodes) = - self.devices.values_mut().partition::, _>(|device| { - device - .socket - .as_ref() - .map(|s| &s.dmabuf_global == global) - .unwrap_or(false) - }); - - let mut last_err = anyhow::anyhow!("Dmabuf cannot be imported on any gpu"); - for device in expected_node.into_iter().chain(other_nodes.into_iter()) { - if device.render_node != self.primary_node { - if !device.in_use(&self.primary_node) { - self.api - .as_mut() - .add_node(device.render_node, device.gbm.clone()) - .context("Failed to initialize device")?; - - let mut renderer = match self.api.single_renderer(&device.render_node) { - Ok(renderer) => renderer, - Err(err) => { - self.api.as_mut().remove_node(&device.render_node); - return Err(err).context("Failed to initialize renderer"); - } - }; - init_shaders(&mut renderer).context("Failed to initialize shaders")?; - } - } - - let result = self - .api - .single_renderer(&device.render_node)? - // using the MultiRenderer here would actually try multiple devices - .glow_renderer_mut() - .import_dmabuf(&dmabuf, None) - .map(|_| device.render_node) - .map_err(Into::into); - - match result { - Ok(node) => { - dmabuf.set_node(node); // so the MultiRenderer knows what node to use - return Ok(node); - } - Err(err) => { - trace!(?err, "Failed to import dmabuf on {:?}", device.render_node); - last_err = err; - - if !device.in_use(&self.primary_node) { - self.api.as_mut().remove_node(&device.render_node); - } - } - }; - } - - Err(last_err) - } - - #[profiling::function] - pub fn schedule_render( - &mut self, - loop_handle: &LoopHandle<'_, State>, - output: &Output, - estimated_rendertime: Option, - ) -> Result<(), InsertError> { - for (device, crtc, surface) in self - .devices - .iter_mut() - .flat_map(|(node, d)| d.surfaces.iter_mut().map(move |(c, s)| (node, c, s))) - .filter(|(_, _, s)| { - s.output == *output || s.mirroring.as_ref().is_some_and(|o| o == output) - }) - { - if surface.surface.is_none() { - return Ok(()); - } - if !surface.scheduled && !surface.pending { - let device = *device; - let crtc = *crtc; - if let Some(token) = surface.render_timer_token.take() { - loop_handle.remove(token); - } - surface.render_timer_token = Some(loop_handle.insert_source( - if surface.vrr || estimated_rendertime.is_none() { - Timer::immediate() + std::mem::drop(output_config); + surface + .resume(drm_surface, gbm, cursor_size, vrr) + .context("Failed to create surface")?; } else { - Timer::from_duration( - Duration::from_secs_f64(1000.0 / surface.refresh_rate as f64) - .saturating_sub(estimated_rendertime.unwrap()), - ) - }, - move |_time, _, state| { - profiling::scope!("render_timer"); - let backend = state.backend.kms(); - let (mut device, mut other) = backend - .devices - .iter_mut() - .partition::, _>(|(key, _val)| *key == &device); - let target_device = &mut device[0].1; - - if let Some(surface) = target_device.surfaces.get_mut(&crtc) { - let common = &mut state.common; - let target_node = target_device.render_node; - let render_node = render_node_for_output( - surface.mirroring.as_ref().unwrap_or(&surface.output), - backend.primary_node, - target_node, - &*common.shell.read().unwrap(), - ); - - let result = if render_node != target_node { - let render_device = &mut other - .iter_mut() - .find(|(_, val)| val.render_node == render_node) - .unwrap() - .1; - surface.render_output( - &mut backend.api, - Some(&render_device.render_node), - &target_node, - common, - ) - } else { - surface.render_output(&mut backend.api, None, &target_node, common) - }; - - profiling::finish_frame!(); - - match result { - Ok(_) => { - trace!(?crtc, "Frame pending"); - surface.scheduled = false; - surface.render_timer_token = None; - return TimeoutAction::Drop; - } - Err(err) => { - if backend.session.is_active() { - error!(?err, "Error rendering."); - return TimeoutAction::ToDuration(Duration::from_secs_f64( - (1000.0 / surface.refresh_rate as f64) - 0.003, - )); - } - } - }; + if output_config.vrr != surface.output.adaptive_sync() { + surface.output.set_adaptive_sync(drm_helpers::set_vrr( + drm, + surface.crtc, + surface.connector, + output_config.vrr, + )?); } + std::mem::drop(output_config); + surface.set_mode(*mode); + // TODO: .context("Failed to apply new mode")?; + } - TimeoutAction::Drop - }, - )?); - trace!(?surface.render_timer_token, ?crtc, "Frame scheduled"); - surface.scheduled = true; + if mirrored_output != surface.output.mirroring() { + surface.set_mirroring(mirrored_output.clone()); + } + } } + + // add new ones + let mut w = shell.read().unwrap().global_space().size.w; + if !test_only { + for (conn, crtc) in new_pairings { + let (output, _) = device.connector_added( + self.primary_node.as_ref(), + conn, + Some(crtc), + (0, w), + loop_handle, + shell.clone(), + startup_done.clone(), + )?; + if output.mirroring().is_none() { + w += output.config().mode_size().w; + } + } + } + + all_outputs.extend(outputs); } - Ok(()) + + Ok(all_outputs) } } diff --git a/src/backend/kms/render/gles.rs b/src/backend/kms/render/gles.rs new file mode 100644 index 00000000..c4629137 --- /dev/null +++ b/src/backend/kms/render/gles.rs @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::backend::{ + allocator::{ + dmabuf::{AnyError, Dmabuf, DmabufAllocator}, + gbm::GbmAllocator, + Allocator, + }, + drm::{CreateDrmNodeError, DrmNode}, + renderer::{ + gles::GlesError, + glow::GlowRenderer, + multigpu::{ApiDevice, Error as MultiError, GraphicsApi}, + Renderer, + }, + SwapBuffersError, +}; +use std::cell::Cell; +use std::{ + collections::HashMap, + fmt, + os::unix::prelude::AsFd, + sync::atomic::{AtomicBool, Ordering}, +}; + +use crate::backend::render::element::FromGlesError; + +/// Errors raised by the [`GbmGlesBackend`] +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// OpenGL error + #[error(transparent)] + Gl(#[from] GlesError), + /// Error creating a drm node + #[error(transparent)] + DrmNode(#[from] CreateDrmNodeError), +} + +impl From for SwapBuffersError { + fn from(err: Error) -> SwapBuffersError { + match err { + x @ Error::DrmNode(_) => SwapBuffersError::ContextLost(Box::new(x)), + Error::Gl(x) => x.into(), + } + } +} + +/// A [`GraphicsApi`] utilizing user-provided GBM Devices and OpenGL ES for rendering. +pub struct GbmGlowBackend { + devices: HashMap, Cell>)>, + needs_enumeration: AtomicBool, +} + +impl fmt::Debug for GbmGlowBackend { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("GbmGlesBackend") + .field("devices", &self.devices.keys()) + .field("needs_enumeration", &self.needs_enumeration) + .finish() + } +} + +impl Default for GbmGlowBackend { + fn default() -> Self { + GbmGlowBackend { + devices: HashMap::new(), + needs_enumeration: AtomicBool::new(true), + } + } +} + +impl GbmGlowBackend { + pub fn new() -> Self { + GbmGlowBackend { + devices: HashMap::new(), + needs_enumeration: AtomicBool::new(false), + } + } + + pub fn current_devices(&self) -> impl Iterator { + self.devices.keys() + } + + pub fn add_node(&mut self, node: DrmNode, gbm: GbmAllocator, renderer: GlowRenderer) { + if self.devices.contains_key(&node) { + return; + } + + self.devices.insert(node, (gbm, Cell::new(Some(renderer)))); + self.needs_enumeration.store(true, Ordering::SeqCst); + } + + /// Remove a given node from the api + pub fn remove_node(&mut self, node: &DrmNode) { + if self.devices.remove(node).is_some() { + self.needs_enumeration.store(true, Ordering::SeqCst); + } + } +} + +impl GraphicsApi for GbmGlowBackend { + type Device = GbmGlowDevice; + type Error = Error; + + fn enumerate(&self, list: &mut Vec) -> Result<(), Self::Error> { + self.needs_enumeration.store(false, Ordering::SeqCst); + + // remove old stuff + list.retain(|renderer| { + self.devices + .keys() + .any(|node| renderer.node.dev_id() == node.dev_id()) + }); + + // add new stuff + let new_renderers = self + .devices + .iter() + // but don't replace already initialized renderers + .filter(|(node, _)| { + !list + .iter() + .any(|renderer| renderer.node.dev_id() == node.dev_id()) + }) + .flat_map(|(node, (allocator, renderer))| { + let renderer = renderer.replace(None)?; + + Some(GbmGlowDevice { + node: *node, + renderer, + allocator: Box::new(DmabufAllocator(allocator.clone())), + }) + }) + .collect::>(); + list.extend(new_renderers); + + Ok(()) + } + + fn needs_enumeration(&self) -> bool { + self.needs_enumeration.load(Ordering::Acquire) + } + + fn identifier() -> &'static str { + "gbm_glow" + } +} + +/// [`ApiDevice`] of the [`GbmGlesBackend`] +pub struct GbmGlowDevice { + node: DrmNode, + renderer: GlowRenderer, + allocator: Box>, +} + +impl fmt::Debug for GbmGlowDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("GbmGlesDevice") + .field("node", &self.node) + .field("renderer", &self.renderer) + .finish_non_exhaustive() + } +} + +impl ApiDevice for GbmGlowDevice { + type Renderer = GlowRenderer; + + fn renderer(&self) -> &Self::Renderer { + &self.renderer + } + fn renderer_mut(&mut self) -> &mut Self::Renderer { + &mut self.renderer + } + fn allocator(&mut self) -> &mut dyn Allocator { + self.allocator.as_mut() + } + fn node(&self) -> &DrmNode { + &self.node + } +} + +impl FromGlesError for MultiError, T> +where + T::Error: 'static, + <::Renderer as Renderer>::Error: 'static, +{ + #[inline] + fn from_gles_error(err: GlesError) -> MultiError, T> { + MultiError::Render(err) + } +} diff --git a/src/backend/kms/render/mod.rs b/src/backend/kms/render/mod.rs new file mode 100644 index 00000000..210a8332 --- /dev/null +++ b/src/backend/kms/render/mod.rs @@ -0,0 +1,2 @@ +pub mod gles; +pub mod pixman; diff --git a/src/backend/kms/render/pixman.rs b/src/backend/kms/render/pixman.rs new file mode 100644 index 00000000..8e627289 --- /dev/null +++ b/src/backend/kms/render/pixman.rs @@ -0,0 +1,155 @@ +use std::{ + collections::HashMap, + fmt, + os::fd::AsFd, + sync::atomic::{AtomicBool, Ordering}, +}; + +use smithay::backend::{ + allocator::{ + dmabuf::{AnyError, Dmabuf, DmabufAllocator}, + gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}, + Allocator, + }, + drm::DrmNode, + renderer::{ + multigpu::{ApiDevice, GraphicsApi}, + pixman::{PixmanError, PixmanRenderer}, + }, +}; +use tracing::warn; + +#[derive(Debug)] +pub struct GbmPixmanBackend { + devices: HashMap>, + needs_enumeration: AtomicBool, + allocator_flags: GbmBufferFlags, +} + +pub struct GbmPixmanDevice { + node: DrmNode, + allocator: Box>, + renderer: PixmanRenderer, +} + +impl fmt::Debug for GbmPixmanDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("GbmPixmanDevice") + .field("node", &self.node) + .field("allocator", &"...") + .field("renderer", &self.renderer) + .finish() + } +} + +impl GbmPixmanBackend { + pub fn new() -> Self { + GbmPixmanBackend { + devices: HashMap::new(), + needs_enumeration: AtomicBool::new(false), + allocator_flags: GbmBufferFlags::RENDERING, + } + } + + pub fn with_allocator_flags(allocator_flags: GbmBufferFlags) -> Self { + GbmPixmanBackend { + devices: HashMap::new(), + needs_enumeration: AtomicBool::new(false), + allocator_flags, + } + } + + pub fn set_allocator_flags(&mut self, flags: GbmBufferFlags) { + self.allocator_flags = flags; + } + + pub fn add_node(&mut self, node: DrmNode, gbm: GbmDevice) { + if self.devices.contains_key(&node) { + return; + } + + let allocator = GbmAllocator::new(gbm, self.allocator_flags); + self.devices.insert(node, allocator); + self.needs_enumeration.store(true, Ordering::SeqCst); + } + + /// Remove a given node from the api + pub fn remove_node(&mut self, node: &DrmNode) { + if self.devices.remove(node).is_some() { + self.needs_enumeration.store(true, Ordering::SeqCst); + } + } +} + +impl GraphicsApi for GbmPixmanBackend { + type Device = GbmPixmanDevice; + + type Error = PixmanError; + + fn enumerate(&self, list: &mut Vec) -> Result<(), Self::Error> { + self.needs_enumeration.store(false, Ordering::SeqCst); + + list.retain(|renderer| { + self.devices + .keys() + .any(|node| renderer.node.dev_id() == node.dev_id()) + }); + + // add new stuff + let new_renderers = self + .devices + .iter() + .filter(|(node, _)| { + !list + .iter() + .any(|renderer| renderer.node.dev_id() == node.dev_id()) + }) + .map(|(node, gbm)| { + Ok(GbmPixmanDevice { + node: *node, + allocator: Box::new(DmabufAllocator(gbm.clone())) + as Box>, + renderer: PixmanRenderer::new()?, + }) + }) + .flat_map(|x: Result| match x { + Ok(x) => Some(x), + Err(x) => { + warn!("Skipping pixman device: {}", x); + None + } + }) + .collect::>(); + list.extend(new_renderers); + + Ok(()) + } + + fn identifier() -> &'static str { + "gbm_pixman" + } + + fn needs_enumeration(&self) -> bool { + self.needs_enumeration.load(Ordering::SeqCst) + } +} + +impl ApiDevice for GbmPixmanDevice { + type Renderer = PixmanRenderer; + + fn renderer(&self) -> &Self::Renderer { + &self.renderer + } + + fn renderer_mut(&mut self) -> &mut Self::Renderer { + &mut self.renderer + } + + fn allocator(&mut self) -> &mut dyn Allocator { + &mut self.allocator + } + + fn node(&self) -> &DrmNode { + &self.node + } +} diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs new file mode 100644 index 00000000..08d06e5c --- /dev/null +++ b/src/backend/kms/surface/mod.rs @@ -0,0 +1,1358 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + backend::render::{ + element::{CosmicElement, DamageElement}, + init_shaders, workspace_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR, + }, + shell::Shell, + state::SurfaceDmabufFeedback, + utils::prelude::*, + wayland::{ + handlers::screencopy::{submit_buffer, FrameHolder, SessionData}, + protocols::screencopy::{ + FailureReason, Frame as ScreencopyFrame, Session as ScreencopySession, + }, + }, +}; + +use anyhow::{Context, Result}; +use smithay::{ + backend::{ + allocator::{ + gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}, + Format, Fourcc, + }, + drm::{ + compositor::{BlitFrameResultError, DrmCompositor, FrameError, PrimaryPlaneElement}, + DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, DrmSurface, + }, + egl::EGLContext, + renderer::{ + buffer_dimensions, + damage::{Error as RenderError, OutputDamageTracker}, + element::{ + texture::{TextureRenderBuffer, TextureRenderElement}, + utils::{constrain_render_elements, ConstrainAlign, ConstrainScaleBehavior}, + Element, Kind, RenderElementStates, + }, + gles::{GlesRenderbuffer, GlesTexture}, + glow::GlowRenderer, + multigpu::{Error as MultiError, GpuManager}, + sync::SyncPoint, + utils::with_renderer_surface_state, + Bind, ImportDma, Offscreen, Renderer, Texture, + }, + }, + desktop::utils::OutputPresentationFeedback, + output::{Output, OutputNoMode}, + reexports::{ + calloop::{ + channel::{channel, Event, Sender}, + timer::{TimeoutAction, Timer}, + EventLoop, LoopHandle, RegistrationToken, + }, + drm::{ + control::{connector, crtc, Mode}, + Device as _, + }, + wayland_protocols::wp::{ + linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1, + presentation_time::server::wp_presentation_feedback, + }, + wayland_server::protocol::wl_surface::WlSurface, + }, + utils::{Buffer as BufferCoords, Clock, Monotonic, Physical, Rectangle, Size, Transform}, + wayland::{ + dmabuf::{get_dmabuf, DmabufFeedbackBuilder}, + seat::WaylandFocus, + shm::{shm_format_to_fourcc, with_buffer_contents}, + }, +}; +use tracing::{error, trace, warn}; + +use std::{ + borrow::BorrowMut, + collections::{HashMap, HashSet}, + mem, + sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + mpsc::{Receiver, SyncSender}, + Arc, Condvar, Mutex, RwLock, + }, + thread::JoinHandle, + time::Duration, +}; + +mod timings; +pub use self::timings::Timings; + +use super::{drm_helpers, render::gles::GbmGlowBackend}; + +#[cfg(feature = "debug")] +static INTEL_LOGO: &'static [u8] = include_bytes!("../resources/icons/intel.svg"); +#[cfg(feature = "debug")] +static AMD_LOGO: &'static [u8] = include_bytes!("../resources/icons/amd.svg"); +#[cfg(feature = "debug")] +static NVIDIA_LOGO: &'static [u8] = include_bytes!("../resources/icons/nvidia.svg"); + +#[derive(Debug)] +pub struct Surface { + pub(super) connector: connector::Handle, + pub(super) crtc: crtc::Handle, + pub(super) output: Output, + known_nodes: HashSet, + + active: Arc, + frame_callback_seq: Arc, + feedback: HashMap, + plane_formats: HashSet, + + loop_handle: LoopHandle<'static, State>, + thread_command: Sender, + thread_handle: JoinHandle<()>, + thread_token: RegistrationToken, +} + +pub struct SurfaceThreadState { + // rendering + api: GpuManager>, + primary_node: DrmNode, + target_node: DrmNode, + active: Arc, + compositor: Option, + + state: QueueState, + timings: Timings, + frame_callback_seq: Arc, + thread_sender: Sender, + + output: Output, + mirroring: Option, + mirroring_textures: HashMap, + + shell: Arc>, + + loop_handle: LoopHandle<'static, Self>, + clock: Clock, + + #[cfg(feature = "debug")] + egui: EguiState, +} + +#[derive(Debug)] +struct MirroringState { + texture: TextureRenderBuffer, + damage_tracker: OutputDamageTracker, +} + +impl MirroringState { + fn new_with_renderer( + renderer: &mut GlMultiRenderer, + format: Fourcc, + output: &Output, + ) -> Result { + let size = output + .current_mode() + .map(|mode| mode.size) + .unwrap_or_default() + .to_logical(1) + .to_buffer(1, Transform::Normal); + let opaque_regions = vec![Rectangle::from_loc_and_size((0, 0), size)]; + + let texture = Offscreen::::create_buffer(renderer, format, size)?; + let transform = output.current_transform(); + let texture_buffer = TextureRenderBuffer::from_texture( + renderer, + texture, + 1, + transform, + Some(opaque_regions), + ); + + let damage_tracker = OutputDamageTracker::from_output(output); + + Ok(MirroringState { + texture: texture_buffer, + damage_tracker, + }) + } +} + +pub type GbmDrmCompositor = DrmCompositor< + GbmAllocator, + GbmDevice, + Option<( + OutputPresentationFeedback, + Receiver<(ScreencopyFrame, Vec>)>, + )>, + DrmDeviceFd, +>; + +#[derive(Debug)] +pub enum QueueState { + Idle, + /// A redraw is queued. + Queued(RegistrationToken), + /// We submitted a frame to the KMS and waiting for it to be presented. + WaitingForVBlank { + redraw_needed: bool, + }, + /// We did not submit anything to KMS and made a timer to fire at the estimated VBlank. + WaitingForEstimatedVBlank(RegistrationToken), + /// A redraw is queued on top of the above. + WaitingForEstimatedVBlankAndQueued { + estimated_vblank: RegistrationToken, + queued_render: RegistrationToken, + }, +} + +impl Default for QueueState { + fn default() -> Self { + QueueState::Idle + } +} + +#[derive(Debug)] +pub enum ThreadCommand { + Suspend, + Resume { + surface: DrmSurface, + gbm: GbmDevice, + cursor_size: Size, + vrr: bool, + result: SyncSender>, + }, + NodeAdded { + node: DrmNode, + gbm: GbmAllocator, + egl: EGLContext, // TODO: Option for software rendering + }, + NodeRemoved { + node: DrmNode, + }, + UpdateMirroring(Option), + VBlank(Option), + ScheduleRender, + // TODO: Error handling + SetMode(Mode), + End, +} + +#[derive(Debug)] +pub enum SurfaceCommand { + SendFrames, + RenderStates(RenderElementStates), +} + +impl Surface { + pub fn new( + output: &Output, + crtc: crtc::Handle, + connector: connector::Handle, + primary_node: DrmNode, + dev_node: DrmNode, + target_node: DrmNode, + evlh: &LoopHandle<'static, State>, + shell: Arc>, + startup_done: Arc<(Mutex, Condvar)>, + ) -> Result { + let frame_callback_seq = Arc::new(AtomicUsize::new(0)); + + let (tx, rx) = channel::(); + let (tx2, rx2) = channel::(); + let active = Arc::new(AtomicBool::new(false)); + let active_clone = active.clone(); + + let frame_callback_seq_clone = frame_callback_seq.clone(); + let output_clone = output.clone(); + let thread_handle = std::thread::spawn(move || { + profiling::register_thread!(format!("Surface Thread {}", output.name())); + + let mut event_loop = EventLoop::try_new().unwrap(); + + let api = GpuManager::new(GbmGlowBackend::::default()) + .expect("Failed to initialize rendering"); + /* + let software_api = GpuManager::new(GbmPixmanBackend::::with_allocator_flags( + gbm_flags, + )) + .context("Failed to initialize software rendering"); + */ + + #[cfg(feature = "debug")] + let egui = { + let state = smithay_egui::EguiState::new( + smithay::utils::Rectangle::from_loc_and_size((0, 0), (400, 800)), + ); + let mut visuals: egui::style::Visuals = Default::default(); + visuals.window_shadow.extrusion = 0.0; + state.context().set_visuals(visuals); + state + }; + + let mut state = SurfaceThreadState { + api, + primary_node, + target_node, + active: active_clone, + compositor: None, + + state: QueueState::Idle, + timings: Timings::new(None, false), + frame_callback_seq: frame_callback_seq_clone, + thread_sender: tx2, + + output: output_clone, + mirroring: None, + mirroring_textures: HashMap::new(), + + shell, + loop_handle: event_loop.handle(), + clock: Clock::new(), + #[cfg(feature = "debug")] + egui, + }; + + let signal = event_loop.get_signal(); + event_loop + .handle() + .insert_source(rx, move |command, _, state| match command { + Event::Msg(ThreadCommand::Suspend) => { + state.active.store(false, Ordering::SeqCst); + let _ = state.compositor.take(); + + match std::mem::replace(&mut state.state, QueueState::Idle) { + QueueState::Idle => {} + QueueState::Queued(token) + | QueueState::WaitingForEstimatedVBlank(token) => { + state.loop_handle.remove(token); + } + QueueState::WaitingForVBlank { .. } => { + state.timings.discard_current_frame() + } + QueueState::WaitingForEstimatedVBlankAndQueued { + estimated_vblank, + queued_render, + } => { + state.loop_handle.remove(estimated_vblank); + state.loop_handle.remove(queued_render); + } + }; + } + Event::Msg(ThreadCommand::Resume { + surface, + gbm, + cursor_size, + vrr, + result, + }) => { + let driver = surface.get_driver().ok(); + let mut planes = surface.planes().clone(); + + // QUIRK: Using an overlay plane on a nvidia card breaks the display controller (wtf...) + if driver.is_some_and(|driver| { + driver + .name() + .to_string_lossy() + .to_lowercase() + .contains("nvidia") + }) { + planes.overlay = vec![]; + } + + let render_formats = state + .api + .single_renderer(&state.target_node) + .unwrap() + .dmabuf_formats() + .collect(); + + state + .timings + .set_refresh_interval(Some(Duration::from_nanos( + drm_helpers::calculate_refresh_rate(surface.pending_mode()) as u64, + ))); + state.timings.set_vrr(vrr); + + match DrmCompositor::new( + &state.output, + surface, + Some(planes), + GbmAllocator::new( + gbm.clone(), + GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, + ), + gbm.clone(), + &[ + Fourcc::Abgr2101010, + Fourcc::Argb2101010, + Fourcc::Abgr8888, + Fourcc::Argb8888, + ], + render_formats, + cursor_size, + Some(gbm), + ) { + Ok(compositor) => { + state.active.store(true, Ordering::SeqCst); + state.compositor = Some(compositor); + let _ = result.send(Ok(())); + } + Err(err) => { + state.active.store(false, Ordering::SeqCst); + let _ = result.send(Err(err.into())); + } + } + } + Event::Msg(ThreadCommand::NodeAdded { node, gbm, egl }) => { + //if let Some(egl) = egl { + let mut renderer = + unsafe { GlowRenderer::new(egl) }.expect("Failed to create renderer"); + init_shaders(renderer.borrow_mut()); + + state.api.as_mut().add_node(node, gbm, renderer); + /* + } else { + state.software_api.as_mut().add_node(node, gbm); + } + */ + + #[cfg(feature = "debug")] + { + let renderer = state.api.single_renderer(node); + state + .egui + .load_svg(renderer, String::from("intel"), INTEL_LOGO) + .unwrap(); + state + .egui + .load_svg(renderer, String::from("amd"), AMD_LOGO) + .unwrap(); + state + .egui + .load_svg(renderer, String::from("nvidia"), NVIDIA_LOGO) + .unwrap(); + } + } + Event::Msg(ThreadCommand::NodeRemoved { node }) => { + state.api.as_mut().remove_node(&node); + //state.software_api.as_mut().remove_node(node); + } + Event::Msg(ThreadCommand::VBlank(metadata)) => { + state.on_vblank(metadata); + } + Event::Msg(ThreadCommand::ScheduleRender) => { + { + // Wait for start up. + let (lock, cvar) = &*startup_done; + // As long as the value inside the `Mutex` is `false`, we wait. + let _guard = cvar + .wait_while(lock.lock().unwrap(), |startup_done| !*startup_done) + .unwrap(); + } + + state.queue_redraw(false); + } + Event::Msg(ThreadCommand::UpdateMirroring(mirroring)) => { + state.mirroring = mirroring; + state.mirroring_textures.clear(); + } + Event::Msg(ThreadCommand::SetMode(mode)) => { + if let Some(compositor) = state.compositor.as_mut() { + compositor.use_mode(mode); + } + } + Event::Closed | Event::Msg(ThreadCommand::End) => { + signal.stop(); + signal.wakeup(); + } + }) + .unwrap(); + + if let Err(err) = event_loop.run(None, &mut state, |_| { + // TODO: if queued, but token invalid, schedule redraw timer again + }) { + error!("Surface thread crashed: {}", err); + } + }); + + let output_clone = output.clone(); + let thread_token = evlh + .insert_source(rx2, move |command, _, state| match command { + Event::Msg(SurfaceCommand::SendFrames) => { + state.common.send_frames(&output_clone); + } + Event::Msg(SurfaceCommand::RenderStates(states)) => { + state.common.update_primary_output(&output_clone, &states); + let kms = state.backend.kms(); + let surface = &mut kms + .drm_devices + .get_mut(&dev_node) + .unwrap() + .surfaces + .get_mut(&crtc) + .unwrap(); + state + .common + .send_dmabuf_feedback(&output_clone, &states, |source_node| { + Some( + surface + .feedback + .entry(source_node) + .or_insert_with(|| { + let render_formats = kms + .api + .single_renderer(&source_node) + .unwrap() + .dmabuf_formats() + .collect::>(); + let target_formats = kms + .api + .single_renderer(&target_node) + .unwrap() + .dmabuf_formats() + .collect::>(); + get_surface_dmabuf_feedback( + source_node, + target_node, + render_formats, + target_formats, + &surface.plane_formats, + ) + }) + .clone(), + ) + }); + } + Event::Closed => {} + }) + .map_err(|_| anyhow::anyhow!("Failed to establish channel to surface thread"))?; + + Ok(Surface { + connector, + crtc, + output: output.clone(), + frame_callback_seq, + known_nodes: HashSet::new(), + active, + feedback: HashMap::new(), + plane_formats: HashSet::new(), + loop_handle: evlh.clone(), + thread_command: tx, + thread_handle, + thread_token, + }) + } + + pub fn add_node(&mut self, node: DrmNode, gbm: GbmAllocator, egl: EGLContext) { + self.known_nodes.insert(node); + self.thread_command + .send(ThreadCommand::NodeAdded { node, gbm, egl }); + } + + pub fn remove_node(&mut self, node: DrmNode) { + self.known_nodes.remove(&node); + self.thread_command + .send(ThreadCommand::NodeRemoved { node }); + } + + pub fn known_nodes(&self) -> &HashSet { + &self.known_nodes + } + + pub fn on_vblank(&self, metadata: Option) { + self.thread_command.send(ThreadCommand::VBlank(metadata)); + } + + pub fn schedule_render(&self) { + self.thread_command.send(ThreadCommand::ScheduleRender); + } + + pub fn set_mirroring(&mut self, output: Option) { + self.thread_command + .send(ThreadCommand::UpdateMirroring(output)); + } + + pub fn set_mode(&mut self, mode: Mode) { + self.thread_command.send(ThreadCommand::SetMode(mode)); + } + + pub fn suspend(&mut self) { + self.thread_command.send(ThreadCommand::Suspend); + } + + pub fn resume( + &mut self, + surface: DrmSurface, + gbm: GbmDevice, + cursor_size: Size, + vrr: bool, + ) -> Result<()> { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + self.plane_formats = surface + .planes() + .primary + .formats + .iter() + .cloned() + .chain( + surface + .planes() + .overlay + .iter() + .flat_map(|p| p.formats.iter().cloned()), + ) + .collect::>(); + + let _ = self.thread_command.send(ThreadCommand::Resume { + surface, + gbm, + cursor_size, + vrr, + result: tx, + }); + + rx.recv().context("Surface thread died")? + } + + pub fn is_active(&self) -> bool { + self.active.load(Ordering::SeqCst) + } +} + +impl Drop for Surface { + fn drop(&mut self) { + let _ = self.thread_command.send(ThreadCommand::End); + self.loop_handle.remove(self.thread_token); + } +} + +impl SurfaceThreadState { + fn on_vblank(&mut self, metadata: Option) { + let Some(compositor) = self.compositor.as_mut() else { + return; + }; + if matches!(self.state, QueueState::Idle) { + // can happen right after resume + return; + } + + let now = self.clock.now(); + let presentation_time = match metadata.as_ref().map(|data| &data.time) { + Some(DrmEventTime::Monotonic(tp)) => Some(tp.clone()), + _ => None, + }; + let sequence = metadata.as_ref().map(|data| data.sequence).unwrap_or(0); + + // mark last frame completed + if let Ok(Some(Some((mut feedback, frames)))) = compositor.frame_submitted() { + if self.mirroring.is_none() { + let (clock, flags) = if let Some(tp) = presentation_time { + ( + tp.into(), + wp_presentation_feedback::Kind::Vsync + | wp_presentation_feedback::Kind::HwClock + | wp_presentation_feedback::Kind::HwCompletion, + ) + } else { + ( + now, + wp_presentation_feedback::Kind::Vsync + | wp_presentation_feedback::Kind::HwCompletion, + ) + }; + + feedback.presented( + clock, + self.output + .current_mode() + .map(|mode| Duration::from_secs_f64(1_000.0 / mode.refresh as f64)) + .unwrap_or_default(), + sequence as u64, + flags, + ); + + self.timings.presented(clock); + + while let Ok((frame, damage)) = frames.recv() { + frame.success(self.output.current_transform(), damage, clock); + } + } + } + + let redraw_needed = match mem::replace(&mut self.state, QueueState::Idle) { + QueueState::Idle => unreachable!(), + QueueState::Queued(_) => unreachable!(), + QueueState::WaitingForVBlank { redraw_needed } => redraw_needed, + QueueState::WaitingForEstimatedVBlank(_) => unreachable!(), + QueueState::WaitingForEstimatedVBlankAndQueued { .. } => unreachable!(), + }; + + if redraw_needed || self.shell.read().unwrap().animations_going() { + self.queue_redraw(false); + } else { + self.send_frame_callbacks(); + } + } + + fn on_estimated_vblank(&mut self) { + match mem::replace(&mut self.state, QueueState::Idle) { + QueueState::Idle => unreachable!(), + QueueState::Queued(_) => unreachable!(), + QueueState::WaitingForVBlank { .. } => unreachable!(), + QueueState::WaitingForEstimatedVBlank(_) => (), + // The timer fired just in front of a redraw. + QueueState::WaitingForEstimatedVBlankAndQueued { queued_render, .. } => { + self.state = QueueState::Queued(queued_render); + return; + } + } + + self.frame_callback_seq.fetch_add(1, Ordering::SeqCst); + + if self.shell.read().unwrap().animations_going() { + self.queue_redraw(false); + } else { + self.send_frame_callbacks(); + } + } + + fn queue_redraw(&mut self, force: bool) { + let Some(_compositor) = self.compositor.as_mut() else { + return; + }; + + if !force { + match &self.state { + QueueState::Idle | QueueState::WaitingForEstimatedVBlank(_) => {} + + // A redraw is already queued. + QueueState::Queued(_) | QueueState::WaitingForEstimatedVBlankAndQueued { .. } => { + return; + } + + // We're waiting for VBlank, request a redraw afterwards. + QueueState::WaitingForVBlank { .. } => { + self.state = QueueState::WaitingForVBlank { + redraw_needed: true, + }; + return; + } + }; + } + + let estimated_presentation = self.timings.next_presentation_time(&self.clock); + let render_start = self.timings.next_render_time(&self.clock); + + let timer = if render_start.is_zero() { + trace!("Running late for frame."); + // TODO triple buffering + Timer::immediate() + } else { + Timer::from_duration(render_start) + }; + + let token = self + .loop_handle + .insert_source(timer, move |_time, _, state| { + if let Err(err) = state.redraw(estimated_presentation) { + let name = state.output.name(); + warn!(?name, "Failed to submit rendering: {:?}", err); + state.queue_redraw(true); + } + return TimeoutAction::Drop; + }) + .expect("Failed to schedule render"); + + match &self.state { + QueueState::Idle => { + self.state = QueueState::Queued(token); + } + QueueState::WaitingForEstimatedVBlank(estimated_vblank) => { + self.state = QueueState::WaitingForEstimatedVBlankAndQueued { + estimated_vblank: estimated_vblank.clone(), + queued_render: token, + }; + } + _ => unreachable!(), + } + } + + fn redraw(&mut self, estimated_presentation: Duration) -> Result<()> { + let Some(compositor) = self.compositor.as_mut() else { + return Ok(()); + }; + + let render_node = render_node_for_output( + self.mirroring.as_ref().unwrap_or(&self.output), + &self.primary_node, + &self.target_node, + &*self.shell.read().unwrap(), + ); + + let mut renderer = if render_node != self.target_node { + self.api + .renderer(&render_node, &self.target_node, compositor.format()) + .unwrap() + } else { + self.api.single_renderer(&self.target_node).unwrap() + }; + + self.timings.start_render(&self.clock); + #[cfg(feature = "debug")] + if let Some(rd) = self.timings.rd.as_mut() { + rd.start_frame_capture( + renderer.glow_renderer().egl_context().get_context_handle(), + std::ptr::null(), + ); + } + + let mut elements = { + let shell = self.shell.read().unwrap(); + let output = self.mirroring.as_ref().unwrap_or(&self.output); + + let (previous_workspace, workspace) = shell.workspaces.active(output); + let (previous_idx, idx) = shell.workspaces.active_num(&output); + let previous_workspace = previous_workspace + .zip(previous_idx) + .map(|((w, start), idx)| (w.handle, idx, start)); + let workspace = (workspace.handle, idx); + + std::mem::drop(shell); + + workspace_elements( + Some(&render_node), + &mut renderer, + &self.shell, + self.clock.now(), + output, + previous_workspace, + workspace, + CursorMode::All, + false, + #[cfg(not(feature = "debug"))] + None, + #[cfg(feature = "debug")] + Some((&self.egui, &self.timings)), + ) + .map_err(|err| { + anyhow::format_err!("Failed to accumulate elements for rendering: {:?}", err) + })? + }; + self.timings.elements_done(&self.clock); + + // we can't use the elements after `compositor.render_frame`, + // so let's collect everything we need for screencopy now + let frames: Vec<( + ScreencopySession, + ScreencopyFrame, + Result<(Option>>, RenderElementStates), OutputNoMode>, + )> = self + .mirroring + .is_none() + .then(|| { + self.output + .take_pending_frames() + .into_iter() + .map(|(session, frame)| { + let additional_damage = frame.damage(); + let session_data = session.user_data().get::().unwrap(); + let mut damage_tracking = session_data.lock().unwrap(); + + let old_len = if !additional_damage.is_empty() { + let area = self + .output + .current_mode() + .unwrap() + /* TODO: Mode is Buffer..., why is this Physical in the first place */ + .size + .to_logical(1) + .to_buffer(1, Transform::Normal) + .to_f64(); + + let old_len = elements.len(); + elements.extend( + additional_damage + .into_iter() + .map(|rect| { + rect.to_f64() + .to_logical( + self.output.current_scale().fractional_scale(), + self.output.current_transform(), + &area, + ) + .to_i32_round() + }) + .map(DamageElement::new) + .map(Into::into), + ); + + Some(old_len) + } else { + None + }; + + let buffer = frame.buffer(); + let age = damage_tracking.age_for_buffer(&buffer); + let res = damage_tracking.dt.damage_output(age, &elements); + + if let Some(old_len) = old_len { + elements.truncate(old_len); + } + + let res = res.map(|(a, b)| (a.cloned(), b)); + std::mem::drop(damage_tracking); + (session, frame, res) + }) + .collect() + }).unwrap_or_default(); + + // actual rendering + let res = if let Some(mirrored_output) = self.mirroring.as_ref().filter(|mirrored_output| { + mirrored_output.current_mode().is_some_and(|mirror_mode| { + self.output + .current_mode() + .is_some_and(|mode| mode != mirror_mode) + }) + }) { + let mirroring_state = { + let entry = self.mirroring_textures.entry(self.target_node); + let mut new_state = None; + if matches!(entry, std::collections::hash_map::Entry::Vacant(_)) { + new_state = Some(MirroringState::new_with_renderer( + &mut renderer, + compositor.format(), + mirrored_output, + )?); + } + // I really want a failable initializer... + entry.or_insert_with(|| new_state.unwrap()) + }; + + mirroring_state + .texture + .render() + .draw::<_, ::Error>(|tex| { + let res = match mirroring_state.damage_tracker.render_output_with( + &mut renderer, + tex.clone(), + 1, + &elements, + CLEAR_COLOR, + ) { + Ok(res) => res, + Err(RenderError::Rendering(err)) => return Err(err), + Err(RenderError::OutputNoMode(_)) => unreachable!(), + }; + + renderer.wait(&res.sync)?; + + let transform = mirrored_output.current_transform(); + let area = tex.size().to_logical(1, transform); + + Ok(res + .damage + .cloned() + .map(|v| { + v.into_iter() + .map(|r| r.to_logical(1).to_buffer(1, transform, &area)) + .collect::>() + }) + .unwrap_or_default()) + }) + .context("Failed to draw to offscreen render target")?; + + let texture_elem = TextureRenderElement::from_texture_render_buffer( + (0., 0.), + &mirroring_state.texture, + Some(1.0), + None, + None, + Kind::Unspecified, + ); + let texture_geometry = texture_elem.geometry(1.0.into()); + elements = constrain_render_elements( + std::iter::once(texture_elem), + (0, 0), + Rectangle::from_loc_and_size( + (0, 0), + self.output + .geometry() + .size + .as_logical() + .to_f64() + .to_physical(self.output.current_scale().fractional_scale()) + .to_i32_round(), + ), + texture_geometry, + ConstrainScaleBehavior::Fit, + ConstrainAlign::CENTER, + 1.0, + ) + .map(CosmicElement::Mirror) + .collect::>(); + + renderer = self.api.single_renderer(&self.target_node).unwrap(); + compositor.render_frame(&mut renderer, &elements, [0.0, 0.0, 0.0, 1.0]) + } else { + compositor.render_frame( + &mut renderer, + &elements, + CLEAR_COLOR, // TODO use a theme neutral color + ) + }; + self.timings.draw_done(&self.clock); + + match res { + Ok(frame_result) => { + let (tx, rx) = std::sync::mpsc::channel(); + + let feedback = if !frame_result.is_empty && self.mirroring.is_none() { + Some(( + self.shell + .read() + .unwrap() + .take_presentation_feedback(&self.output, &frame_result.states), + rx, + )) + } else { + None + }; + + if frame_result.needs_sync() { + if let PrimaryPlaneElement::Swapchain(elem) = &frame_result.primary_element { + elem.sync.wait()?; + } + } + + match compositor.queue_frame(feedback) { + x @ Ok(()) | x @ Err(FrameError::EmptyFrame) => { + self.timings.submitted_for_presentation(&self.clock); + + for (session, frame, res) in frames { + let damage = match res { + Ok((damage, _)) => damage, + Err(err) => { + tracing::warn!(?err, "Failed to screencopy"); + session + .user_data() + .get::() + .unwrap() + .lock() + .unwrap() + .reset(); + frame.fail(FailureReason::Unknown); + continue; + } + }; + + let mut sync = SyncPoint::default(); + + if let Some(ref damage) = damage { + let buffer = frame.buffer(); + if let Ok(dmabuf) = get_dmabuf(&buffer) { + renderer + .bind(dmabuf.clone()) + .map_err(RenderError::::Rendering)?; + } else { + let size = buffer_dimensions(&buffer).ok_or(RenderError::< + GlMultiRenderer, + >::Rendering( + MultiError::ImportFailed, + ))?; + let format = + with_buffer_contents(&buffer, |_, _, data| shm_format_to_fourcc(data.format)) + .map_err(|_| OutputNoMode)? // eh, we have to do some error + .expect("We should be able to convert all hardcoded shm screencopy formats"); + let render_buffer = + Offscreen::::create_buffer( + &mut renderer, + format, + size, + ) + .map_err(RenderError::::Rendering)?; + renderer + .bind(render_buffer) + .map_err(RenderError::::Rendering)?; + } + + let (output_size, output_scale, output_transform) = ( + self.output.current_mode().ok_or(OutputNoMode)?.size, + self.output.current_scale().fractional_scale(), + self.output.current_transform(), + ); + + let filter = (!session.draw_cursor()) + .then(|| { + elements.iter().filter_map(|elem| { + if let CosmicElement::Cursor(_) = elem { + Some(elem.id().clone()) + } else { + None + } + }) + }) + .into_iter() + .flatten(); + + match frame_result + .blit_frame_result( + output_size, + output_transform, + output_scale, + &mut renderer, + damage.iter().copied(), + filter, + ) + .map_err(|err| match err { + BlitFrameResultError::Rendering(err) => { + RenderError::::Rendering(err) + } + BlitFrameResultError::Export(_) => { + RenderError::::Rendering( + MultiError::DeviceMissing, + ) + } + }) { + Ok(new_sync) => { + sync = new_sync; + } + Err(err) => { + tracing::warn!(?err, "Failed to screencopy"); + session + .user_data() + .get::() + .unwrap() + .lock() + .unwrap() + .reset(); + frame.fail(FailureReason::Unknown); + continue; + } + }; + } + + let transform = self.output.current_transform(); + + match submit_buffer( + frame, + &mut renderer, + transform, + damage.as_deref(), + sync, + ) { + Ok(Some((frame, damage))) => { + if frame_result.is_empty { + frame.success(transform, damage, self.clock.now()); + } else { + let _ = tx.send((frame, damage)); + } + } + Ok(None) => {} + Err(err) => { + session + .user_data() + .get::() + .unwrap() + .lock() + .unwrap() + .reset(); + tracing::warn!(?err, "Failed to screencopy"); + } + } + } + + if x.is_ok() { + let new_state = QueueState::WaitingForVBlank { + redraw_needed: false, + }; + match mem::replace(&mut self.state, new_state) { + QueueState::Idle => unreachable!(), + QueueState::Queued(_) => (), + QueueState::WaitingForVBlank { .. } => unreachable!(), + QueueState::WaitingForEstimatedVBlank(estimated_vblank) + | QueueState::WaitingForEstimatedVBlankAndQueued { + estimated_vblank, + .. + } => { + self.loop_handle.remove(estimated_vblank); + } + }; + + self.frame_callback_seq.fetch_add(1, Ordering::SeqCst); + + let states = frame_result.states; + self.send_frame_callbacks(); + self.send_dmabuf_feedback(states); + } else { + tracing::debug!("Stopped rendering"); + self.queue_estimated_vblank(estimated_presentation); + } + } + Err(err) => { + for (session, frame, _) in frames { + session + .user_data() + .get::() + .unwrap() + .lock() + .unwrap() + .reset(); + frame.fail(FailureReason::Unknown); + } + return Err(err).with_context(|| "Failed to submit result for display"); + } + }; + } + Err(err) => { + compositor.reset_buffers(); + anyhow::bail!("Rendering failed: {}", err); + } + } + + Ok(()) + } + + fn queue_estimated_vblank(&mut self, target_presentation_time: Duration) { + match mem::take(&mut self.state) { + QueueState::Idle => unreachable!(), + QueueState::Queued(_) => (), + QueueState::WaitingForVBlank { .. } => unreachable!(), + QueueState::WaitingForEstimatedVBlank(token) + | QueueState::WaitingForEstimatedVBlankAndQueued { + estimated_vblank: token, + .. + } => { + self.state = QueueState::WaitingForEstimatedVBlank(token); + return; + } + } + + let now = self.clock.now(); + let mut duration = target_presentation_time.saturating_sub(now.into()); + + // No use setting a zero timer, since we'll send frame callbacks anyway right after the call to + // render(). This can happen for example with unknown presentation time from DRM. + if duration.is_zero() { + duration += self.timings.refresh_interval(); + } + + trace!("queueing estimated vblank timer to fire in {duration:?}"); + + let timer = Timer::from_duration(duration); + let token = self + .loop_handle + .insert_source(timer, move |_, _, data| { + data.on_estimated_vblank(); + TimeoutAction::Drop + }) + .unwrap(); + self.state = QueueState::WaitingForEstimatedVBlank(token); + } + + fn send_frame_callbacks(&mut self) { + if self.mirroring.is_none() { + self.thread_sender.send(SurfaceCommand::SendFrames); + } + } + + fn send_dmabuf_feedback(&mut self, states: RenderElementStates) { + self.thread_sender + .send(SurfaceCommand::RenderStates(states)); + } +} + +fn source_node_for_surface<'a>(w: &WlSurface) -> Option { + with_renderer_surface_state(w, |state| { + state + .buffer() + .and_then(|buffer| get_dmabuf(buffer).ok().and_then(|dmabuf| dmabuf.node())) + }) + .flatten() +} + +fn render_node_for_output( + output: &Output, + primary_node: &DrmNode, + target_node: &DrmNode, + shell: &Shell, +) -> DrmNode { + if target_node == primary_node { + return *target_node; + } + + let workspace = shell.active_space(output); + let nodes = workspace + .get_fullscreen() + .map(|w| vec![w.clone()]) + .unwrap_or_else(|| { + workspace + .mapped() + .map(|mapped| mapped.active_window()) + .collect::>() + }) + .into_iter() + .flat_map(|w| w.wl_surface().and_then(|s| source_node_for_surface(&s))) + .collect::>(); + + if nodes.contains(&target_node) || nodes.is_empty() { + *target_node + } else { + *primary_node + } +} + +fn get_surface_dmabuf_feedback( + render_node: DrmNode, + target_node: DrmNode, + render_formats: HashSet, + target_formats: HashSet, + plane_formats: &HashSet, +) -> SurfaceDmabufFeedback { + let combined_formats = render_formats + .intersection(&target_formats) + .copied() + .collect::>(); + + // We limit the scan-out trache 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 = plane_formats + .intersection(&combined_formats) + .copied() + .collect::>(); + + let builder = DmabufFeedbackBuilder::new(render_node.dev_id(), render_formats); + /* + // iris doesn't handle nvidia buffers very well (it hangs). + // so only do this in the future with v6 and clients telling us the gpu + if target_node != render_node.dev_id() && !combined_formats.is_empty() { + builder = builder.add_preference_tranche( + target_node, + Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout), + combined_formats, + ); + }; + */ + + let render_feedback = builder.clone().build().unwrap(); + // we would want to do this in other cases as well, but same thing as above applies + let scanout_feedback = if target_node == render_node { + builder + .add_preference_tranche( + target_node.dev_id(), + Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout), + planes_formats, + ) + .build() + .unwrap() + } else { + builder.build().unwrap() + }; + + SurfaceDmabufFeedback { + render_feedback, + scanout_feedback, + } +} diff --git a/src/backend/kms/surface/timings.rs b/src/backend/kms/surface/timings.rs new file mode 100644 index 00000000..a934e455 --- /dev/null +++ b/src/backend/kms/surface/timings.rs @@ -0,0 +1,262 @@ +use std::{collections::VecDeque, num::NonZeroU64, time::Duration}; + +use smithay::utils::{Clock, Monotonic, Time}; +use tracing::error; + +const MIN_RENDER_TIME: Duration = Duration::from_millis(5); +const FRAME_TIME_WINDOW: usize = 3; + +pub struct Timings { + refresh_interval_ns: Option, + vrr: bool, + + pub pending_frame: Option, + pub previous_frames: VecDeque, +} + +#[derive(Debug)] +pub struct PendingFrame { + render_start: Time, + render_duration_elements: Option, + render_duration_draw: Option, + presentation_submitted: Option>, +} + +#[derive(Debug)] +pub struct Frame { + pub render_start: Time, + pub render_duration_elements: Duration, + pub render_duration_draw: Duration, + pub presentation_submitted: Time, + pub presentation_presented: Time, +} + +impl Frame { + fn render_time(&self) -> Duration { + self.render_duration_elements + self.render_duration_draw + } + + fn frame_time(&self) -> Duration { + Time::elapsed(&self.render_start, self.presentation_submitted) + } +} + +impl Timings { + const WINDOW_SIZE: usize = 360; + + pub fn new(refresh_interval: Option, vrr: bool) -> Self { + let refresh_interval_ns = if let Some(interval) = &refresh_interval { + assert_eq!(interval.as_secs(), 0); + Some(NonZeroU64::new(interval.subsec_nanos().into()).unwrap()) + } else { + None + }; + + Self { + refresh_interval_ns, + vrr, + + pending_frame: None, + previous_frames: VecDeque::new(), + } + } + + pub fn refresh_interval(&self) -> Duration { + match self.refresh_interval_ns { + Some(ns) => Duration::from_nanos(ns.get()), + None => Duration::ZERO, + } + } + + pub fn set_refresh_interval(&mut self, interval: Option) { + self.refresh_interval_ns = interval + .map(|duration| duration.subsec_nanos() as u64) + .and_then(NonZeroU64::new); + + self.previous_frames.clear(); + } + + pub fn set_vrr(&mut self, vrr: bool) { + self.vrr = vrr; + } + + pub fn start_render(&mut self, clock: &Clock) { + self.pending_frame = Some(PendingFrame { + render_start: clock.now(), + render_duration_elements: None, + render_duration_draw: None, + presentation_submitted: None, + }); + } + + pub fn elements_done(&mut self, clock: &Clock) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.render_duration_elements = Some(Time::elapsed(&frame.render_start, clock.now())); + } + } + + pub fn draw_done(&mut self, clock: &Clock) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.render_duration_draw = Some( + Time::elapsed(&frame.render_start, clock.now()) + - frame + .render_duration_elements + .clone() + .unwrap_or(Duration::ZERO), + ); + } + } + + pub fn submitted_for_presentation(&mut self, clock: &Clock) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.presentation_submitted = Some(clock.now()); + } + } + + pub fn presented(&mut self, value: Time) { + if let Some(frame) = self.pending_frame.take() { + self.previous_frames.push_back(Frame { + render_start: frame.render_start, + render_duration_elements: frame.render_duration_elements.unwrap_or_default(), + render_duration_draw: frame.render_duration_draw.unwrap_or_default(), + presentation_submitted: frame.presentation_submitted.unwrap(), + presentation_presented: value, + }); + while self.previous_frames.len() > Self::WINDOW_SIZE { + self.previous_frames.pop_front(); + } + } + } + + pub fn discard_current_frame(&mut self) { + let _ = self.pending_frame.take(); + } + + pub fn max_rendertime(&self) -> Duration { + self.previous_frames + .iter() + .map(|f| f.render_time()) + .max() + .unwrap_or(Duration::ZERO) + } + + pub fn min_rendertime(&self) -> Duration { + self.previous_frames + .iter() + .map(|f| f.render_time()) + .min() + .unwrap_or(Duration::ZERO) + } + + /* + pub fn max_time_to_display(&self) -> Duration { + self.previous_frames + .iter() + .map(|f| f.time_to_display()) + .max() + .unwrap_or(Duration::ZERO) + } + + pub fn min_time_to_display(&self) -> Duration { + self.previous_frames + .iter() + .map(|f| f.time_to_display()) + .min() + .unwrap_or(Duration::ZERO) + } + */ + + pub fn avg_rendertime(&self) -> Duration { + if self.previous_frames.is_empty() { + return Duration::ZERO; + } + self.previous_frames + .iter() + .map(|f| f.render_time()) + .sum::() + / (self.previous_frames.len() as u32) + } + + pub fn avg_frametime(&self, window: usize) -> Duration { + if self.previous_frames.is_empty() { + return MIN_RENDER_TIME; + } + + self.previous_frames + .iter() + .rev() + .take(window) + .map(|f| f.frame_time()) + .sum::() + / (window.min(self.previous_frames.len()) as u32) + } + + pub fn avg_fps(&self) -> f64 { + if self.previous_frames.is_empty() { + return 0.0; + } + let secs = match (self.previous_frames.front(), self.previous_frames.back()) { + (Some(Frame { render_start, .. }), Some(end_frame)) => { + Time::elapsed(render_start, end_frame.render_start.clone()) + end_frame.frame_time() + } + _ => Duration::ZERO, + } + .as_secs_f64(); + 1.0 / (secs / self.previous_frames.len() as f64) + } + + pub fn next_presentation_time(&self, clock: &Clock) -> Duration { + let mut now = clock.now().into(); + + let Some(refresh_interval_ns) = self.refresh_interval_ns else { + return Duration::ZERO; + }; + let Some(last_presentation_time): Option = self + .previous_frames + .back() + .map(|frame| frame.presentation_presented.into()) + else { + return Duration::ZERO; + }; + let refresh_interval_ns = refresh_interval_ns.get(); + + if now <= last_presentation_time { + // Got an early VBlank. + let orig_now = now; + now += Duration::from_nanos(refresh_interval_ns); + + if now < last_presentation_time { + // Not sure when this can happen. + error!( + now = ?orig_now, + ?last_presentation_time, + "got a 2+ early VBlank, {:?} until presentation", + last_presentation_time - now, + ); + now = last_presentation_time + Duration::from_nanos(refresh_interval_ns); + } + } + + let since_last = now - last_presentation_time; + let since_last_ns = + since_last.as_secs() * 1_000_000_000 + u64::from(since_last.subsec_nanos()); + let to_next_ns = (since_last_ns / refresh_interval_ns + 1) * refresh_interval_ns; + + // If VRR is enabled and more than one frame passed since last presentation, assume that we + // can present immediately. + if self.vrr && to_next_ns > refresh_interval_ns { + Duration::ZERO + } else { + last_presentation_time + Duration::from_nanos(to_next_ns) - now + } + } + + pub fn next_render_time(&self, clock: &Clock) -> Duration { + let estimated_presentation_time = self.next_presentation_time(clock); + if estimated_presentation_time.is_zero() { + return Duration::ZERO; + } + + estimated_presentation_time.saturating_sub(self.avg_frametime(FRAME_TIME_WINDOW)) + } +} diff --git a/src/backend/render/element.rs b/src/backend/render/element.rs index dcff9585..82dd5c45 100644 --- a/src/backend/render/element.rs +++ b/src/backend/render/element.rs @@ -8,7 +8,7 @@ use smithay::{ utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, Element, Id, Kind, RenderElement, UnderlyingStorage, }, - gles::GlesTexture, + gles::{GlesError, GlesTexture}, glow::{GlowFrame, GlowRenderer}, utils::{CommitCounter, DamageSet, OpaqueRegions}, Frame, ImportAll, ImportMem, Renderer, @@ -465,3 +465,13 @@ impl RenderElement for DamageElement { Ok(()) } } + +pub trait FromGlesError { + fn from_gles_error(err: GlesError) -> Self; +} + +impl FromGlesError for GlesError { + fn from_gles_error(err: GlesError) -> Self { + err + } +} diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 3ff3f0b8..4ac42ff2 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -11,17 +11,15 @@ use std::{ #[cfg(feature = "debug")] use crate::debug::fps_ui; use crate::{ - backend::render::element::DamageElement, - config::Config, + backend::{kms::render::gles::GbmGlowBackend, render::element::DamageElement}, shell::{ element::CosmicMappedKey, focus::target::WindowGroup, grabs::{SeatMenuGrabState, SeatMoveGrabState}, layout::tiling::ANIMATION_DURATION, - CosmicMapped, CosmicMappedRenderElement, OverviewMode, SeatExt, SessionLock, Trigger, - WorkspaceDelta, WorkspaceRenderElement, + CosmicMappedRenderElement, OverviewMode, SeatExt, SessionLock, Trigger, WorkspaceDelta, + WorkspaceRenderElement, }, - state::Fps, utils::prelude::*, wayland::{ handlers::{ @@ -34,6 +32,7 @@ use crate::{ use cosmic::Theme; use cosmic_comp_config::workspace::WorkspaceLayout; +use element::FromGlesError; use keyframe::{ease, functions::EaseInOutCubic}; use smithay::{ backend::{ @@ -51,8 +50,7 @@ use smithay::{ element::PixelShaderElement, GlesError, GlesPixelProgram, GlesRenderer, Uniform, UniformName, UniformType, }, - glow::GlowRenderer, - multigpu::{gbm::GbmGlesBackend, Error as MultiError, MultiFrame, MultiRenderer}, + multigpu::{Error as MultiError, MultiFrame, MultiRenderer}, sync::SyncPoint, Bind, Blit, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, TextureFilter, }, @@ -74,23 +72,11 @@ pub mod cursor; pub mod element; use self::element::{AsGlowRenderer, CosmicElement}; -pub type GlMultiRenderer<'a> = MultiRenderer< - 'a, - 'a, - GbmGlesBackend, - GbmGlesBackend, ->; -pub type GlMultiFrame<'a, 'frame> = MultiFrame< - 'a, - 'a, - 'frame, - GbmGlesBackend, - GbmGlesBackend, ->; -pub type GlMultiError = MultiError< - GbmGlesBackend, - GbmGlesBackend, ->; +pub type GlMultiRenderer<'a> = + MultiRenderer<'a, 'a, GbmGlowBackend, GbmGlowBackend>; +pub type GlMultiFrame<'a, 'frame> = + MultiFrame<'a, 'a, 'frame, GbmGlowBackend, GbmGlowBackend>; +pub type GlMultiError = MultiError, GbmGlowBackend>; pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; pub static OUTLINE_SHADER: &str = include_str!("./shaders/rounded_outline.frag"); @@ -341,12 +327,9 @@ impl BackdropShader { } } -pub fn init_shaders(renderer: &mut R) -> Result<(), GlesError> { - let glow_renderer = renderer.glow_renderer_mut(); - let gles_renderer: &mut GlesRenderer = glow_renderer.borrow_mut(); - +pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { { - let egl_context = gles_renderer.egl_context(); + let egl_context = renderer.egl_context(); if egl_context.user_data().get::().is_some() && egl_context.user_data().get::().is_some() { @@ -354,7 +337,7 @@ pub fn init_shaders(renderer: &mut R) -> Result<(), GlesError } } - let outline_shader = gles_renderer.compile_custom_pixel_shader( + let outline_shader = renderer.compile_custom_pixel_shader( OUTLINE_SHADER, &[ UniformName::new("color", UniformType::_3f), @@ -362,7 +345,7 @@ pub fn init_shaders(renderer: &mut R) -> Result<(), GlesError UniformName::new("radius", UniformType::_1f), ], )?; - let rectangle_shader = gles_renderer.compile_custom_pixel_shader( + let rectangle_shader = renderer.compile_custom_pixel_shader( RECTANGLE_SHADER, &[ UniformName::new("color", UniformType::_3f), @@ -370,7 +353,7 @@ pub fn init_shaders(renderer: &mut R) -> Result<(), GlesError ], )?; - let egl_context = gles_renderer.egl_context(); + let egl_context = renderer.egl_context(); egl_context .user_data() .insert_if_missing(|| IndicatorShader(outline_shader)); @@ -852,7 +835,7 @@ pub fn split_layer_elements( where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Clone + 'static, - ::Error: From, + ::Error: FromGlesError, CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { @@ -919,7 +902,7 @@ pub fn background_layer_elements( where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Clone + 'static, - ::Error: From, + ::Error: FromGlesError, CosmicMappedRenderElement: RenderElement, WorkspaceRenderElement: RenderElement, { diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 8f92eaf1..3201e297 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -139,9 +139,9 @@ pub fn init_backend( event_loop: &mut EventLoop, state: &mut State, ) -> Result<()> { - let (mut backend, mut input) = + let (mut backend, mut input): (WinitGraphicsBackend, _) = winit::init().map_err(|e| anyhow!("Failed to initilize winit backend: {e:?}"))?; - init_shaders(backend.renderer()).context("Failed to initialize renderer")?; + init_shaders(backend.renderer().borrow_mut()).context("Failed to initialize renderer")?; init_egl_client_side(dh, state, &mut backend)?; diff --git a/src/backend/x11.rs b/src/backend/x11.rs index dd74a96a..a3e61565 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -342,7 +342,7 @@ pub fn init_backend( let mut renderer = unsafe { GlowRenderer::new(context) }.with_context(|| "Failed to initialize renderer")?; - init_shaders(&mut renderer).context("Failed to initialize renderer")?; + init_shaders(renderer.borrow_mut()).context("Failed to initialize renderer")?; init_egl_client_side(dh, state, drm_node, &mut renderer)?; state.backend = BackendData::X11(X11State { diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs index 93d41ecc..cea8c261 100644 --- a/src/dbus/mod.rs +++ b/src/dbus/mod.rs @@ -16,7 +16,7 @@ pub fn init(evlh: &LoopHandle<'static, State>) -> Result> calloop::channel::Event::Msg(_) => { let nodes = match &mut state.backend { BackendData::Kms(kms) => { - kms.devices.keys().cloned().collect::>() + kms.drm_devices.keys().cloned().collect::>() } _ => Vec::new(), }; diff --git a/src/main.rs b/src/main.rs index afa97eba..b11a9d85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,11 +13,7 @@ use state::State; use std::{env, ffi::OsString, os::unix::process::CommandExt, process, sync::Arc}; use tracing::{error, info, warn}; -use crate::{ - shell::SeatExt, - state::{BackendData, ClientState}, - wayland::handlers::compositor::client_compositor_state, -}; +use crate::wayland::handlers::compositor::client_compositor_state; pub mod backend; pub mod config; @@ -91,6 +87,10 @@ fn main() -> Result<()> { logger::init_logger()?; info!("Cosmic starting up!"); + #[cfg(feature = "profile-with-tracy")] + profiling::tracy_client::Client::start(); + profiling::register_thread!("Main Thread"); + utils::rlimit::increase_nofile_limit(); // init event loop @@ -111,11 +111,6 @@ fn main() -> Result<()> { warn!(?err, "Failed to watch theme"); } - #[cfg(feature = "profile-with-tracy")] - profiling::tracy_client::Client::start(); - - profiling::register_thread!("Main Thread"); - // run the event loop event_loop.run(None, &mut state, |state| { // shall we shut down? @@ -210,29 +205,12 @@ fn init_wayland_display( event_loop .handle() .insert_source(source, |client_stream, _, state| { - let node = match &state.backend { - BackendData::Kms(kms_state) if kms_state.auto_assign => kms_state - .target_node_for_output( - &state - .common - .shell - .read() - .unwrap() - .seats - .last_active() - .active_output(), - ), - _ => None, - }; - let client_state = state.new_client_state(); - if let Err(err) = state.common.display_handle.insert_client( - client_stream, - Arc::new(ClientState { - advertised_drm_node: node.or(client_state.advertised_drm_node), - ..client_state - }), - ) { + if let Err(err) = state + .common + .display_handle + .insert_client(client_stream, Arc::new(client_state)) + { warn!(?err, "Error adding wayland client") }; }) diff --git a/src/state.rs b/src/state.rs index 480b882e..b5fa273f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -342,11 +342,7 @@ impl BackendData { // Winit has a very strict render-loop and skipping frames breaks atleast the wayland winit-backend. // Swapping with damage (which should be empty on these frames) is likely good enough anyway. BackendData::X11(ref mut state) => state.schedule_render(output), - BackendData::Kms(ref mut state) => { - if let Err(err) = state.schedule_render(loop_handle, output, None) { - error!(?err, "Failed to schedule event, are we shutting down?"); - } - } + BackendData::Kms(ref mut state) => state.schedule_render(output), _ => unreachable!("No backend was initialized"), } } @@ -554,7 +550,7 @@ impl State { compositor_client_state: CompositorClientState::default(), workspace_client_state: WorkspaceClientState::default(), advertised_drm_node: match &self.backend { - BackendData::Kms(kms_state) => Some(kms_state.primary_node), + BackendData::Kms(kms_state) => kms_state.primary_node, _ => None, }, privileged: !enable_wayland_security(), diff --git a/src/utils/screenshot.rs b/src/utils/screenshot.rs index 7c34f6ab..222f7da7 100644 --- a/src/utils/screenshot.rs +++ b/src/utils/screenshot.rs @@ -99,7 +99,7 @@ pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) { let res = match &mut state.backend { BackendData::Kms(kms) => { let node = advertised_node_for_surface(&wl_surface, &state.common.display_handle) - .unwrap_or(kms.primary_node); + .unwrap_or(kms.primary_node.expect("No Software Rendering")); kms.api .single_renderer(&node) .with_context(|| "Failed to get renderer for screenshot") diff --git a/src/wayland/handlers/buffer.rs b/src/wayland/handlers/buffer.rs index a3d0604c..fd7d6255 100644 --- a/src/wayland/handlers/buffer.rs +++ b/src/wayland/handlers/buffer.rs @@ -9,12 +9,12 @@ use smithay::{ impl BufferHandler for State { fn buffer_destroyed(&mut self, buffer: &WlBuffer) { if let BackendData::Kms(kms_state) = &mut self.backend { - for device in kms_state.devices.values_mut() { + for device in kms_state.drm_devices.values_mut() { if device.active_buffers.remove(&buffer.downgrade()) { - if !device.in_use(&kms_state.primary_node) { - kms_state.api.as_mut().remove_node(&device.render_node); + if !device.in_use(kms_state.primary_node.as_ref()) { + kms_state.refresh_used_devices(); + break; } - break; } } } diff --git a/src/wayland/handlers/dmabuf.rs b/src/wayland/handlers/dmabuf.rs index 5be49b9b..b823dedd 100644 --- a/src/wayland/handlers/dmabuf.rs +++ b/src/wayland/handlers/dmabuf.rs @@ -35,12 +35,13 @@ impl DmabufHandler for State { if let BackendData::Kms(kms_state) = &mut self.backend { if let Some(device) = kms_state - .devices + .drm_devices .values_mut() .find(|dev| dev.render_node == node) { device.active_buffers.insert(buffer.downgrade()); } + kms_state.refresh_used_devices(); } } Ok(None) => { diff --git a/src/wayland/handlers/drm.rs b/src/wayland/handlers/drm.rs index adb5ce6c..bb810d8a 100644 --- a/src/wayland/handlers/drm.rs +++ b/src/wayland/handlers/drm.rs @@ -26,12 +26,14 @@ impl DrmHandler> for State { // kms backend if let BackendData::Kms(kms_state) = &mut self.backend { if let Some(device) = kms_state - .devices + .drm_devices .values_mut() .find(|device| device.render_node == node) { device.active_buffers.insert(buffer.downgrade()); } + + kms_state.refresh_used_devices(); } } } diff --git a/src/wayland/handlers/drm_lease.rs b/src/wayland/handlers/drm_lease.rs index 35c0499b..026b6db3 100644 --- a/src/wayland/handlers/drm_lease.rs +++ b/src/wayland/handlers/drm_lease.rs @@ -13,7 +13,7 @@ impl DrmLeaseHandler for State { fn drm_lease_state(&mut self, node: DrmNode) -> &mut DrmLeaseState { self.backend .kms() - .devices + .drm_devices .get_mut(&node) .unwrap() .leasing_global @@ -29,14 +29,14 @@ impl DrmLeaseHandler for State { let backend = self .backend .kms() - .devices + .drm_devices .get_mut(&node) .ok_or(LeaseRejected::default())?; let mut builder = DrmLeaseBuilder::new(&backend.drm); for conn in request.connectors { if let Some((_, crtc)) = backend - .non_desktop_connectors + .leased_connectors .iter() .find(|(handle, _)| *handle == conn) { @@ -63,14 +63,14 @@ impl DrmLeaseHandler for State { } fn new_active_lease(&mut self, node: DrmNode, lease: DrmLease) { - if let Some(backend) = self.backend.kms().devices.get_mut(&node) { + if let Some(backend) = self.backend.kms().drm_devices.get_mut(&node) { backend.active_leases.push(lease); } // else the backend is gone, drop the lease } fn lease_destroyed(&mut self, node: DrmNode, lease: u32) { - if let Some(backend) = self.backend.kms().devices.get_mut(&node) { + if let Some(backend) = self.backend.kms().drm_devices.get_mut(&node) { backend.active_leases.retain(|l| l.id() != lease); } } diff --git a/src/wayland/handlers/screencopy/mod.rs b/src/wayland/handlers/screencopy/mod.rs index a8db5870..5d70fbce 100644 --- a/src/wayland/handlers/screencopy/mod.rs +++ b/src/wayland/handlers/screencopy/mod.rs @@ -359,7 +359,7 @@ fn constraints_for_output(output: &Output, backend: &mut BackendData) -> Option< BackendData::Kms(ref mut kms) => { let node = kms .target_node_for_output(&output) - .unwrap_or(kms.primary_node); + .unwrap_or(kms.primary_node.expect("No Software Rendering")); _kms_renderer = Some(kms.api.single_renderer(&node).unwrap()); _kms_renderer.as_mut().unwrap().as_mut() } @@ -388,7 +388,7 @@ fn constraints_for_toplevel( }) .flatten(); - let node = dma_node.unwrap_or(kms.primary_node); + let node = dma_node.unwrap_or(kms.primary_node.expect("No Software Rendering")); _kms_renderer = Some(kms.api.single_renderer(&node).unwrap()); _kms_renderer.as_mut().unwrap().as_mut() } diff --git a/src/wayland/handlers/screencopy/render.rs b/src/wayland/handlers/screencopy/render.rs index fc16e79a..b8f0c875 100644 --- a/src/wayland/handlers/screencopy/render.rs +++ b/src/wayland/handlers/screencopy/render.rs @@ -9,7 +9,7 @@ use smithay::{ utils::{Relocate, RelocateRenderElement}, AsRenderElements, RenderElement, }, - gles::{GlesError, GlesRenderbuffer}, + gles::GlesRenderbuffer, sync::SyncPoint, utils::with_renderer_surface_state, Bind, Blit, BufferType, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, @@ -35,7 +35,7 @@ use tracing::warn; use crate::{ backend::render::{ cursor, - element::{AsGlowRenderer, CosmicElement, DamageElement}, + element::{AsGlowRenderer, CosmicElement, DamageElement, FromGlesError}, render_workspace, CursorMode, CLEAR_COLOR, }, shell::{CosmicMappedRenderElement, CosmicSurface, WorkspaceRenderElement}, @@ -325,7 +325,7 @@ pub fn render_workspace_to_buffer( BackendData::Kms(kms) => { let render_node = kms .target_node_for_output(&output) - .unwrap_or(kms.primary_node); + .unwrap_or(kms.primary_node.expect("No Software Rendering")); let target_node = get_dmabuf(&buffer) .ok() .and_then(|dma| dma.node()) @@ -609,7 +609,7 @@ pub fn render_window_to_buffer( }) .flatten() }) - .unwrap_or(kms.primary_node); + .unwrap_or(kms.primary_node.expect("No Software Rendering")); let mut multirenderer = match kms.api.single_renderer(&node) { Ok(renderer) => renderer, @@ -800,7 +800,10 @@ pub fn render_cursor_to_buffer( let common = &mut state.common; let result = match &mut state.backend { BackendData::Kms(kms) => { - let mut multirenderer = match kms.api.single_renderer(&kms.primary_node) { + let mut multirenderer = match kms + .api + .single_renderer(&kms.primary_node.expect("No Software Rendering")) + { Ok(renderer) => renderer, Err(err) => { warn!(?err, "Couldn't use node for screencopy");