From 4e238d8848cb2702a47bc8efc898d2b83c0d6951 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 25 Jan 2022 15:45:15 +0100 Subject: [PATCH] kms: working state --- src/backend/kms/crtc_mapping.rs | 329 +++++++++++++++++++++++++ src/backend/kms/mod.rs | 423 +++++++++++++++++++++++++++++++- src/backend/kms/session_fd.rs | 37 +++ 3 files changed, 783 insertions(+), 6 deletions(-) create mode 100644 src/backend/kms/crtc_mapping.rs create mode 100644 src/backend/kms/session_fd.rs diff --git a/src/backend/kms/crtc_mapping.rs b/src/backend/kms/crtc_mapping.rs new file mode 100644 index 00000000..f23d312d --- /dev/null +++ b/src/backend/kms/crtc_mapping.rs @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use anyhow::Result; +use smithay::{ + reexports::drm::control::{ + AtomicCommitFlags, + Device as ControlDevice, + ResourceHandle, + atomic::AtomicModeReq, + crtc, + connector::{ + self, + State as ConnectorState, + }, + dumbbuffer::DumbBuffer, + property, + Mode, + ModeFlags, + }, +}; +use std::collections::HashMap; + +pub fn display_configuration(device: &mut impl ControlDevice, supports_atomic: bool) -> 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. + for conn in connectors + .iter() + .flat_map(|conn| device.get_connector(*conn).ok()) + { + if let Some(enc) = device.get_connector(conn.handle())?.current_encoder() { + 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); + // If not, the user just unplugged something, + // or the drm master did not cleanup? + // Well, I guess we cleanup after them. + } else { + cleanup.push(crtc); + } + } + } + } + // But just in case we try to match all remaining connectors. + for conn in connectors + .iter() + .flat_map(|conn| device.get_connector(*conn).ok()) + .filter(|conn| conn.state() == ConnectorState::Connected) + .filter(|conn| !map.contains_key(&conn.handle())) + .collect::>().iter() + { + 'outer: for encoder_info in conn + .encoders() + .iter() + .filter_map(|e| *e) + .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); + break 'outer; + } + } + } + } + + // And then cleanup + if supports_atomic { + let mut req = AtomicModeReq::new(); + let plane_handles = device.plane_handles()?; + + for conn in connectors + .iter() + .flat_map(|conn| device.get_connector(*conn).ok()) + .flat_map(|conn| conn.current_encoder()) + .flat_map(|enc| device.get_encoder(enc).ok()) + .flat_map(|enc| enc.crtc()) + .filter(|c| cleanup.contains(&c)) + { + let crtc_id = get_prop(device, conn, "CRTC_ID")?; + req.add_property(conn, crtc_id, property::Value::CRTC(None)); + } + + // We cannot just shortcut and use the legacy api for all cleanups because of this. + // (Technically a device does not need to be atomic for planes to be used, but nobody does this otherwise.) + for plane in plane_handles.planes() { + let info = device.get_plane(*plane)?; + if let Some(crtc) = info.crtc() { + if cleanup.contains(&crtc) { + let crtc_id = get_prop(device, *plane, "CRTC_ID")?; + let fb_id = get_prop(device, *plane, "FB_ID")?; + req.add_property(*plane, crtc_id, property::Value::CRTC(None)); + req.add_property(*plane, fb_id, property::Value::Framebuffer(None)); + } + } + } + + for crtc in cleanup { + let mode_id = get_prop(device, crtc, "MODE_ID")?; + let active = get_prop(device, crtc, "ACTIVE")?; + req.add_property(crtc, active, property::Value::Boolean(false)); + req.add_property(crtc, mode_id, property::Value::Unknown(0)); + } + + device.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)?; + } else { + for crtc in cleanup { + #[allow(deprecated)] + let _ = device.set_cursor(crtc, Option::<&DumbBuffer>::None); + // null commit (necessary to trigger removal on the kernel side with the legacy api.) + let _ = device.set_crtc(crtc, None, (0, 0), &[], None); + } + } + + Ok(map) +} + +pub fn interface_name(device: &impl ControlDevice, connector: connector::Handle) -> Result { + let conn_info = device.get_connector(connector)?; + + let other_short_name; + let interface_short_name = match conn_info.interface() { + connector::Interface::DVII => "DVI-I", + connector::Interface::DVID => "DVI-D", + connector::Interface::DVIA => "DVI-A", + connector::Interface::SVideo => "S-VIDEO", + connector::Interface::DisplayPort => "DP", + connector::Interface::HDMIA => "HDMI-A", + connector::Interface::HDMIB => "HDMI-B", + connector::Interface::EmbeddedDisplayPort => "eDP", + other => { + other_short_name = format!("{:?}", other); + &other_short_name + } + }; + + Ok(format!("{}-{}", interface_short_name, conn_info.interface_id())) +} + +pub struct EdidInfo { + pub model: String, + pub manufacturer: String, +} + +pub fn edid_info(device: &impl ControlDevice, connector: connector::Handle) -> Result { + use edid_rs::{parse as edid_parse, MonitorDescriptor}; + + let edid_prop = get_prop(device, connector, "EDID")?; + let edid_info = device.get_property(edid_prop)?; + let mut manufacturer = "Unknown".into(); + let mut model = "Unknown".into(); + let props = device.get_properties(connector)?; + let (ids, vals) = props.as_props_and_values(); + for (&id, &val) in ids.iter().zip(vals.iter()) { + if id == edid_prop { + if let property::Value::Blob(edid_blob) = + edid_info.value_type().convert_value(val) + { + let blob = device.get_property_blob(edid_blob)?; + let mut reader = std::io::Cursor::new(blob); + if let Some(edid) = edid_parse(&mut reader).ok() { + manufacturer = { + let id = edid.product.manufacturer_id; + let code = [id.0, id.1, id.2]; + get_manufacturer(&code).into() + }; + model = if let Some(MonitorDescriptor::MonitorName(name)) = edid.descriptors.0 + .iter() + .find(|x| matches!(x, MonitorDescriptor::MonitorName(_))) + { + name.clone() + } else { + format!("{}", edid.product.product_code) + }; + } + } + break; + } + } + + Ok(EdidInfo { + model, + manufacturer, + }) +} + +pub fn get_prop(device: &impl ControlDevice, handle: impl ResourceHandle, name: &str) -> Result { + let props = device.get_properties(handle)?; + let (prop_handles, _) = props.as_props_and_values(); + for prop in prop_handles { + let info = device.get_property(*prop)?; + if Some(name) == info.name().to_str().ok() { + return Ok(*prop); + } + } + anyhow::bail!("No prop found") +} + +pub fn get_property_val(device: &impl ControlDevice, handle: impl ResourceHandle, name: &str) -> Result<(property::ValueType, property::RawValue)> { + let props = device.get_properties(handle)?; + let (prop_handles, values) = props.as_props_and_values(); + for (&prop, &val) in prop_handles.iter().zip(values.iter()) { + let info = device.get_property(prop)?; + if Some(name) == info.name().to_str().ok() { + let val_type = info.value_type(); + return Ok((val_type, val)); + } + } + anyhow::bail!("No prop found") +} + +fn get_manufacturer(vendor: &[char; 3]) -> &'static str { + match vendor { + ['A', 'A', 'A'] => "Avolites Ltd", + ['A', 'C', 'I'] => "Ancor Communications Inc", + ['A', 'C', 'R'] => "Acer Technologies", + ['A', 'D', 'A'] => "Addi-Data GmbH", + ['A', 'P', 'P'] => "Apple Computer Inc", + ['A', 'S', 'K'] => "Ask A/S", + ['A', 'V', 'T'] => "Avtek (Electronics) Pty Ltd", + ['B', 'N', 'O'] => "Bang & Olufsen", + ['B', 'N', 'Q'] => "BenQ Corporation", + ['C', 'M', 'N'] => "Chimei Innolux Corporation", + ['C', 'M', 'O'] => "Chi Mei Optoelectronics corp.", + ['C', 'R', 'O'] => "Extraordinary Technologies PTY Limited", + ['D', 'E', 'L'] => "Dell Inc.", + ['D', 'G', 'C'] => "Data General Corporation", + ['D', 'O', 'N'] => "DENON, Ltd.", + ['E', 'N', 'C'] => "Eizo Nanao Corporation", + ['E', 'P', 'H'] => "Epiphan Systems Inc.", + ['E', 'X', 'P'] => "Data Export Corporation", + ['F', 'N', 'I'] => "Funai Electric Co., Ltd.", + ['F', 'U', 'S'] => "Fujitsu Siemens Computers GmbH", + ['G', 'S', 'M'] => "Goldstar Company Ltd", + ['H', 'I', 'Q'] => "Kaohsiung Opto Electronics Americas, Inc.", + ['H', 'S', 'D'] => "HannStar Display Corp", + ['H', 'T', 'C'] => "Hitachi Ltd", + ['H', 'W', 'P'] => "Hewlett Packard", + ['I', 'N', 'T'] => "Interphase Corporation", + ['I', 'N', 'X'] => "Communications Supply Corporation (A division of WESCO)", + ['I', 'T', 'E'] => "Integrated Tech Express Inc", + ['I', 'V', 'M'] => "Iiyama North America", + ['L', 'E', 'N'] => "Lenovo Group Limited", + ['M', 'A', 'X'] => "Rogen Tech Distribution Inc", + ['M', 'E', 'G'] => "Abeam Tech Ltd", + ['M', 'E', 'I'] => "Panasonic Industry Company", + ['M', 'T', 'C'] => "Mars-Tech Corporation", + ['M', 'T', 'X'] => "Matrox", + ['N', 'E', 'C'] => "NEC Corporation", + ['N', 'E', 'X'] => "Nexgen Mediatech Inc.", + ['O', 'N', 'K'] => "ONKYO Corporation", + ['O', 'R', 'N'] => "ORION ELECTRIC CO., LTD.", + ['O', 'T', 'M'] => "Optoma Corporation", + ['O', 'V', 'R'] => "Oculus VR, Inc.", + ['P', 'H', 'L'] => "Philips Consumer Electronics Company", + ['P', 'I', 'O'] => "Pioneer Electronic Corporation", + ['P', 'N', 'R'] => "Planar Systems, Inc.", + ['Q', 'D', 'S'] => "Quanta Display Inc.", + ['R', 'A', 'T'] => "Rent-A-Tech", + ['R', 'E', 'N'] => "Renesas Technology Corp.", + ['S', 'A', 'M'] => "Samsung Electric Company", + ['S', 'A', 'N'] => "Sanyo Electric Co., Ltd.", + ['S', 'E', 'C'] => "Seiko Epson Corporation", + ['S', 'H', 'P'] => "Sharp Corporation", + ['S', 'I', 'I'] => "Silicon Image, Inc.", + ['S', 'N', 'Y'] => "Sony", + ['S', 'T', 'D'] => "STD Computer Inc", + ['S', 'V', 'S'] => "SVSI", + ['S', 'Y', 'N'] => "Synaptics Inc", + ['T', 'C', 'L'] => "Technical Concepts Ltd", + ['T', 'O', 'P'] => "Orion Communications Co., Ltd.", + ['T', 'S', 'B'] => "Toshiba America Info Systems Inc", + ['T', 'S', 'T'] => "Transtream Inc", + ['U', 'N', 'K'] => "Unknown", + ['V', 'E', 'S'] => "Vestel Elektronik Sanayi ve Ticaret A. S.", + ['V', 'I', 'T'] => "Visitech AS", + ['V', 'I', 'Z'] => "VIZIO, Inc", + ['V', 'S', 'C'] => "ViewSonic Corporation", + ['Y', 'M', 'H'] => "Yamaha Corporation", + _ => "Unknown", + } +} + +pub fn calculate_refresh_rate(mode: Mode) -> u32 { + let htotal = mode.hsync().2 as u32; + let vtotal = mode.vsync().2 as u32; + let mut refresh = (mode.clock() as u64 * 1000000_u64 / htotal as u64 + + vtotal as u64 / 2) / vtotal as u64; + + if mode.flags().contains(ModeFlags::INTERLACE) { + refresh *= 2; + } + if mode.flags().contains(ModeFlags::DBLSCAN) { + refresh /= 2; + } + if mode.vscan() > 1 { + refresh /= mode.vscan() as u64; + } + + refresh as u32 +} + +pub fn supports_vrr(dev: &impl ControlDevice, conn: connector::Handle) -> Result { + get_property_val(dev, conn, "vrr_capable") + .map(|(val_type, val)| match val_type.convert_value(val) { + property::Value::UnsignedRange(res) => res == 1, + _ => false, + }) +} + +pub fn set_vrr(dev: &impl ControlDevice, crtc: crtc::Handle, conn: connector::Handle, vrr: bool) -> Result { + if supports_vrr(dev, conn)? { + dev.set_property(conn, get_prop(dev, crtc, "VRR_ENABLED")?, property::Value::UnsignedRange(if vrr { 1 } else { 0 }).into()) + .map_err(Into::::into) + .and_then(|_| get_property_val(dev, crtc, "VRR_ENABLED")) + .map(|(val_type, val)| match val_type.convert_value(val) { + property::Value::UnsignedRange(vrr) => vrr == 1, + _ => false, + }) + } else { + Ok(false) + } +} \ No newline at end of file diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 7dd57ac4..a8199de7 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,17 +1,428 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::state::State; -use anyhow::Result; -use smithay::reexports::calloop::EventLoop; +use crate::{ + state::{BackendData, Common, State}, + utils::GlobalDrop, +}; +use anyhow::{Context, Result}; +use smithay::{ + backend::{ + allocator::{gbm::GbmDevice, Format}, + drm::{DrmDevice, DrmEvent, GbmBufferedSurface, DrmEventTime}, + egl::{EGLDevice, EGLDisplay, EGLContext}, + libinput::{LibinputInputBackend, LibinputSessionInterface}, + session::{Session, Signal, auto::AutoSession, AsErrno}, + udev::{UdevBackend, UdevEvent, primary_gpu}, + renderer::{Bind, gles2::Gles2Renderer}, + }, + reexports::{ + drm::{ + control::{ + Device as ControlDevice, + connector, + crtc, + }, + }, + calloop::{EventLoop, Dispatcher, RegistrationToken, LoopHandle, timer::{Timer, TimerHandle}}, + input::Libinput, + nix::{ + fcntl::OFlag, + sys::stat::dev_t, + }, + wayland_server::{ + Display, + protocol::wl_output, + } + }, + wayland::output::{Output, Mode as OutputMode, PhysicalProperties}, + utils::signaling::{Signaler, Linkable}, +}; +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, + path::PathBuf, + rc::Rc, + time::{Duration, Instant, SystemTime}, +}; -pub struct KmsState {} +mod crtc_mapping; +mod session_fd; +use session_fd::*; + +pub struct KmsState { + devices: HashMap, + session: AutoSession, + signaler: Signaler, + tokens: Vec, +} + +pub struct Device { + renderer: Gles2Renderer, + surfaces: HashMap, + allocator: Rc>>, + drm: Dispatcher<'static, DrmDevice, State>, + formats: HashSet, + supports_atomic: bool, + event_token: Option, +} + +pub struct Surface { + surface: GbmBufferedSurface>>, SessionFd>, + output: Output, + _global: GlobalDrop, + last_submit: Option, + refresh_rate: u32, + vrr: bool, + pending: bool, + render_timer: TimerHandle<(dev_t, crtc::Handle)>, + render_timer_token: Option, +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct DevId(pub dev_t); pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Result<()> { - unimplemented!() + let (session, notifier) = AutoSession::new(None).context("Failed to acquire session")?; + let signaler = notifier.signaler(); + + let udev_backend = UdevBackend::new(session.seat(), None)?; + let mut libinput_context = Libinput::new_with_udev::>(session.clone().into()); + libinput_context.udev_assign_seat(&session.seat()).map_err(|_| anyhow::anyhow!("Failed to assign seat to libinput"))?; + let mut libinput_backend = LibinputInputBackend::new(libinput_context, None); + libinput_backend.link(signaler.clone()); + + let libinput_event_source = event_loop + .handle() + .insert_source(libinput_backend, move |event, _, state| { + state.common.process_input_event(event); + for output in state.common.spaces.outputs() { + state.backend.kms().schedule_render(output); + } + }) + .map_err(|err| err.error) + .context("Failed to initialize libinput event source")?; + let session_event_source = event_loop + .handle() + .insert_source(notifier, |(), &mut (), _state| {}) + .map_err(|err| err.error) + .context("Failed to initialize session event source")?; + + state.backend = BackendData::Kms(KmsState { + tokens: vec![ + libinput_event_source, + session_event_source, + ], + session, + signaler, + devices: HashMap::new(), + }); + + for (dev, path) in udev_backend.device_list() { + state.device_added(dev, path.into()) + .with_context(|| format!("Failed to add drm device: {}", path.display()))?; + } + + let udev_event_source = event_loop + .handle() + .insert_source(udev_backend, move |event, _, state| match match event { + UdevEvent::Added { device_id, path } => state.device_added(device_id, path) + .with_context(|| format!("Failed to add drm device: {}", device_id)), + UdevEvent::Changed { device_id } => state.device_changed(device_id) + .with_context(|| format!("Failed to update drm device: {}", device_id)), + UdevEvent::Removed { device_id } => state.device_removed(device_id) + .with_context(|| format!("Failed to remove drm device: {}", device_id)), + } { + Ok(()) => { slog_scope::debug!("Successfully handled udev event") }, + Err(err) => { slog_scope::error!("Error while handling udev event: {}", err) }, + }).unwrap(); + state.backend.kms().tokens.push(udev_event_source); + + Ok(()) +} + +impl State { + fn device_added(&mut self, dev: dev_t, path: PathBuf) -> Result<()> { + let fd = SessionFd::new(self.backend.kms().session.open(&path, OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK) + .with_context(|| format!("Failed to optain file descriptor for drm device: {}", path.display()))?); + let mut drm = DrmDevice::new(fd.clone(), false, None) + .with_context(|| format!("Failed to initialize drm device for: {}", path.display()))?; + let supports_atomic = drm.is_atomic(); + + let egl_device = EGLDevice::enumerate().context("Failed to enumerate EGLDevices")? + // TODO: this check compares the primary node path. + // On split display controller setups however this *may* return the render node + // *or* if `EGL_EXT_device_drm_render_node` is supported nothing and we need to + // query `EGL_DRM_RENDER_NODE_FILE_EXT` instead for comparision. + .find(|dev| dev.drm_device_path().map(|p| p == path).unwrap_or(false)) + .with_context(|| format!("Unable to find matching egl device for {}", path.display()))?; + let egl_display = EGLDisplay::new(&egl_device, None) + .with_context(|| format!("Failed to open EGLDisplay for device {:?}:{}", egl_device, path.display()))?; + let egl_context = EGLContext::new(&egl_display, None) + .with_context(|| format!("Failed to create EGLContext for device {:?}:{}", egl_device, path.display()))?; + let formats = egl_context.dmabuf_render_formats().clone(); + let renderer = unsafe { Gles2Renderer::new(egl_context, None) } + .with_context(|| format!("Failed to create OpenGLES renderer for device {:?}:{}", egl_device, path.display()))?; + + let gbm = GbmDevice::new(fd).with_context(|| format!("Failed to initialize GBM device for {}", path.display()))?; + drm.link(self.backend.kms().signaler.clone()); + let dispatcher = Dispatcher::new(drm, move |event, metadata, state: &mut State| { + match event { + DrmEvent::VBlank(crtc) => { + if let Some(device) = state.backend.kms().devices.get_mut(&dev) { + if let Some(surface) = device.surfaces.get_mut(&crtc) { + match surface.surface.frame_submitted() { + Ok(_) => { + surface.last_submit = metadata.take().map(|data| data.time); + surface.pending = false; + state.common.spaces.active_space_mut(&surface.output) + .send_frames(true, state.common.start_time.elapsed().as_millis() as u32); + }, + Err(err) => slog_scope::warn!("Failed to submit frame: {}", err), + }; + } + } + }, + DrmEvent::Error(err) => { + slog_scope::warn!("Failed to read events of device {:?}: {}", dev, err); + } + } + }); + let token = self.common.event_loop_handle.register_dispatcher(dispatcher.clone()) + .with_context(|| format!("Failed to add drm device to event loop: {}", dev))?; + + let mut device = Device { + renderer, + surfaces: HashMap::new(), + allocator: Rc::new(RefCell::new(gbm)), + drm: dispatcher, + formats, + supports_atomic, + event_token: Some(token), + }; + + let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices + for (crtc, conn) in outputs { + match device.setup_surface(crtc, conn, self.backend.kms().signaler.clone(), &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle) { + Ok(output) => self.common.spaces.map_output(&output), + Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), + }; + } + + self.backend.kms().devices.insert(dev, device); + Ok(()) + } + + fn device_changed(&mut self, dev: dev_t) -> Result<()> { + let signaler = self.backend.kms().signaler.clone(); + if let Some(device) = self.backend.kms().devices.get_mut(&dev) { + let changes = device.enumerate_surfaces()?; + for (crtc, _) in changes.removed { + if let Some(surface) = device.surfaces.get_mut(&crtc) { + if let Some(token) = surface.render_timer_token.take() { + self.common.event_loop_handle.remove(token); + } + self.common.spaces.unmap_output(&surface.output); + } + } + for (crtc, conn) in changes.added { + match device.setup_surface(crtc, conn, signaler.clone(), &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle) { + Ok(output) => self.common.spaces.map_output(&output), + Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), + }; + } + } + Ok(()) + } + + fn device_removed(&mut self, dev: dev_t) -> Result<()> { + if let Some(device) = self.backend.kms().devices.get_mut(&dev) { + for surface in device.surfaces.values_mut() { + if let Some(token) = surface.render_timer_token.take() { + self.common.event_loop_handle.remove(token); + } + self.common.spaces.unmap_output(&surface.output); + } + if let Some(token) = device.event_token.take() { + self.common.event_loop_handle.remove(token); + } + } + Ok(()) + } +} + +pub struct OutputChanges { + pub added: Vec<(crtc::Handle, connector::Handle)>, + pub removed: Vec<(crtc::Handle, connector::Handle)>, +} + +impl Device { + pub fn enumerate_surfaces(&mut self) -> Result { + let drm = &mut *self.drm.as_source_mut(); + + // enumerate our outputs + let config = crtc_mapping::display_configuration(drm, self.supports_atomic)?; + + let surfaces = self.surfaces.iter() + .map(|(c, s)| (*c, s.surface.current_connectors().into_iter().next().unwrap())) + .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, conn)| (*crtc, *conn)) + .collect::>(); + + Ok(OutputChanges { + added, + removed, + }) + } + + fn setup_surface( + &mut self, + crtc: crtc::Handle, + conn: connector::Handle, + signaler: Signaler, + display: &mut Display, + loop_handle: &mut LoopHandle<'static, State>, + ) -> Result { + let drm = &mut *self.drm.as_source_mut(); + let crtc_info = drm.get_crtc(crtc)?; + let conn_info = drm.get_connector(conn)?; + let vrr = crtc_mapping::set_vrr(drm, crtc, conn, true)?; + let interface = crtc_mapping::interface_name(drm, conn)?; + let edid_info = crtc_mapping::edid_info(drm, conn)?; + let mode = crtc_info.mode().unwrap_or(conn_info.modes()[0]); + let mut surface = drm.create_surface(crtc, mode, &[conn])?; + surface.link(signaler); + + let target = GbmBufferedSurface::new(surface, self.allocator.clone(), self.formats.clone(), None) + .with_context(|| format!("Failed to initialize Gbm surface for {}", interface))?; + + let output_mode = OutputMode { + size: (mode.size().0 as i32, mode.size().1 as i32).into(), + refresh: (mode.vrefresh() * 1000) as i32, + }; + let (phys_w, phys_h) = conn_info.size().unwrap_or((0, 0)); + let (output, output_global) = Output::new( + display, + interface, + PhysicalProperties { + size: (phys_w as i32, phys_h as i32).into(), + // TODO: We need to read that from the connector properties + subpixel: wl_output::Subpixel::Unknown, + make: edid_info.manufacturer, + model: edid_info.model, + }, + None + ); + output.set_preferred(output_mode.clone()); + output.change_current_state( + Some(output_mode), + None, // TODO: Readout property for monitor rotation + None, + None, + ); + + let timer = Timer::new()?; + let timer_handle = timer.handle(); + // render timer + let timer_token = loop_handle + .insert_source(timer, |(dev_id, crtc), _, state| { + if let Some(device) = state.backend.kms().devices.get_mut(&dev_id) { + let renderer = &mut device.renderer; + if let Some(surface) = device.surfaces.get_mut(&crtc) { + if let Err(err) = surface.render_output(renderer, &mut state.common) { + slog_scope::error!("Error rendering: {}", err); + // TODO re-schedule? + } + } + } + }) + .unwrap(); + timer_handle.add_timeout(Duration::ZERO, (drm.device_id(), crtc)); + + let data = Surface { + output: output.clone(), + _global: output_global.into(), + surface: target, + vrr, + refresh_rate: crtc_mapping::calculate_refresh_rate(mode), + last_submit: None, + pending: true, + render_timer: timer_handle, + render_timer_token: Some(timer_token), + }; + self.surfaces.insert(crtc, data); + + Ok(output) + } +} + +impl Surface { + pub fn render_output( + &mut self, + renderer: &mut Gles2Renderer, + state: &mut Common, + ) -> Result<()> { + let custom_elements = Vec::new(); + + let space = state.spaces.active_space_mut(&self.output); + let (buffer, age) = self + .surface + .next_buffer() + .with_context(|| "Failed to allocate buffer")?; + renderer + .bind(buffer) + .with_context(|| "Failed to bind buffer")?; + match space.render_output( + renderer, + &self.output, + age as usize, + [0.153, 0.161, 0.165, 1.0], + &*custom_elements, + ) { + Ok(_) => { + self.surface + .queue_buffer() + .with_context(|| "Failed to submit buffer for display")?; + } + Err(err) => { + self.surface.reset_buffers(); + anyhow::bail!("Rendering failed: {}", err); + } + }; + Ok(()) + } } impl KmsState { pub fn schedule_render(&mut self, output: &Output) { - unimplemented!(); + if let Some((device, surface)) = self.devices.values_mut() + .flat_map(|d| { + let dev_id = d.drm.as_source_ref().device_id(); + d.surfaces.values_mut().map(move |s| (dev_id, s)) + }) + .find(|(_, s)| s.output == *output) + { + if !surface.pending { + surface.pending = true; + let duration = match surface.last_submit { + Some(DrmEventTime::Monotonic(instant)) => Instant::now().duration_since(instant), + Some(DrmEventTime::Realtime(time)) => SystemTime::now().duration_since(time).unwrap_or(Duration::new(100, 0)), + None => Duration::new(100, 0), // Just do an insane amount + }; + let data = (device, surface.surface.crtc()); + if surface.vrr || 1.0 / (surface.refresh_rate as f64) < duration.as_secs_f64() / 1000.0 { + surface.render_timer.add_timeout(Duration::ZERO, data); + } else { + surface.render_timer.add_timeout(duration.saturating_sub(Duration::from_millis(5)), data); + } + } + } } } \ No newline at end of file diff --git a/src/backend/kms/session_fd.rs b/src/backend/kms/session_fd.rs new file mode 100644 index 00000000..3ec91857 --- /dev/null +++ b/src/backend/kms/session_fd.rs @@ -0,0 +1,37 @@ +use std::{ + fmt, + rc::Rc, + os::unix::io::{RawFd, AsRawFd}, +}; +use smithay::reexports::nix::unistd::close; + +#[derive(Clone)] +pub struct SessionFd(Rc); + +struct DropFd(RawFd); + +impl SessionFd { + pub fn new(fd: RawFd) -> SessionFd { + SessionFd(Rc::new(DropFd(fd))) + } +} + +impl fmt::Debug for SessionFd { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Session-provided File Descriptor [{}]", self.0.0) + } +} + +impl AsRawFd for SessionFd { + fn as_raw_fd(&self) -> RawFd { + self.0.0 + } +} + +impl Drop for DropFd { + fn drop(&mut self) { + if let Err(err) = close(self.0) { + slog_scope::warn!("Failed to close file descriptor {}", self.0); + } + } +} \ No newline at end of file