From 742780ddc32b8a94637db20c2e6335629d79952e Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Mar 2022 12:41:54 +0100 Subject: [PATCH] kms: Create socket variants for different gpus To make it possible for clients to choose a gpu, we create special sockets per gpu, that advertise their gpu to clients. --- src/backend/kms/mod.rs | 66 +++++++++++++-- src/backend/kms/socket.rs | 173 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 7 deletions(-) create mode 100644 src/backend/kms/socket.rs diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 83831bb6..1641af89 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -47,7 +47,9 @@ use std::{ mod drm_helpers; mod session_fd; +mod socket; use session_fd::*; +use socket::*; pub struct KmsState { devices: HashMap, @@ -66,6 +68,7 @@ pub struct Device { formats: HashSet, supports_atomic: bool, event_token: Option, + socket: Option, } pub struct Surface { @@ -246,7 +249,7 @@ impl State { .spaces .active_space_mut(&surface.output) .send_frames( - state.common.start_time.elapsed().as_millis() as u32, + state.common.start_time.elapsed().as_millis() as u32 ); } Err(err) => slog_scope::warn!("Failed to submit frame: {}", err), @@ -264,6 +267,18 @@ impl State { .register_dispatcher(dispatcher.clone()) .with_context(|| format!("Failed to add drm device to event loop: {}", dev))?; + let socket = match self.create_socket(render_node, formats.clone().into_iter()) { + Ok(socket) => Some(socket), + Err(err) => { + slog_scope::warn!( + "Failed to initialize hardware-acceleration for clients on {}: {}", + render_node, + err + ); + None + } + }; + let mut device = Device { render_node, surfaces: HashMap::new(), @@ -272,6 +287,7 @@ impl State { formats, supports_atomic, event_token: Some(token), + socket, }; let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices @@ -335,6 +351,9 @@ impl State { 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); + } } Ok(()) } @@ -442,7 +461,11 @@ impl Device { let backend = state.backend.kms(); if let Some(device) = backend.devices.get_mut(&dev_id) { if let Some(surface) = device.surfaces.get_mut(&crtc) { - if let Err(err) = surface.render_output(&mut backend.api, &backend.primary, &device.render_node, &mut state.common) { + if let Err(err) = surface.render_output( + &mut backend.api, + &device.render_node, + &mut state.common, + ) { slog_scope::error!("Error rendering: {}", err); // TODO re-schedule? } @@ -471,18 +494,47 @@ impl Device { } } +const MAX_CPU_COPIES: usize = 3; + impl Surface { pub fn render_output( &mut self, api: &mut GpuManager, - render_node: &DrmNode, target_node: &DrmNode, state: &mut Common, ) -> Result<()> { - let mut renderer = api - .renderer(render_node, target_node) - .unwrap(); - + let nodes = state + .spaces + .active_space(&self.output) + .windows() + .flat_map(|w| { + w.toplevel() + .get_surface()? + .as_ref() + .client()? + .data_map() + .get::() + .cloned() + }) + .collect::>(); + let render_node = if nodes.contains(&target_node) || nodes.len() < MAX_CPU_COPIES { + &target_node + } else { + nodes + .iter() + .fold(HashMap::new(), |mut count_map, node| { + let count = count_map.entry(node).or_insert(0); + *count += 1; + count_map + }) + .into_iter() + .reduce(|a, b| if a.1 > b.1 { a } else { b }) + .map(|(node, _)| node) + .unwrap_or(&target_node) + }; + + let mut renderer = api.renderer(render_node, &target_node).unwrap(); + let (buffer, age) = self .surface .next_buffer() diff --git a/src/backend/kms/socket.rs b/src/backend/kms/socket.rs new file mode 100644 index 00000000..8d4a8379 --- /dev/null +++ b/src/backend/kms/socket.rs @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use anyhow::{Context, Result}; +use smithay::{ + backend::{ + allocator::Format, + drm::DrmNode, + renderer::{gles2::Gles2Renderbuffer, ImportDma}, + }, + reexports::{ + calloop::{generic::Generic, Interest, Mode, PostAction, RegistrationToken}, + wayland_protocols::unstable::linux_dmabuf, + wayland_server::Client, + }, + wayland::dmabuf::init_dmabuf_global_with_filter, +}; + +use std::{ + env, + os::unix::{ + io::{AsRawFd, IntoRawFd, RawFd}, + net::UnixListener, + }, + path::PathBuf, +}; + +use crate::{state::State, utils::GlobalDrop, wayland::init_wl_drm_global}; + +pub struct Socket { + pub token: RegistrationToken, + pub drm_global: GlobalDrop, + pub dmabuf_global: GlobalDrop, +} + +impl State { + pub(super) fn create_socket( + &mut self, + render_node: DrmNode, + formats: impl Iterator, + ) -> Result { + let formats = formats.collect::>(); + let is_primary = self.backend.kms().primary == render_node; + let socket_path = PathBuf::from(env::var("XDG_RUNTIME_DIR").unwrap()).join(format!( + "{}-{}", + &self.common.socket.to_string_lossy(), + render_node + .dev_path() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy() + )); + // HACK! + let _ = std::fs::remove_file(&socket_path); + + let listener = UnixListener::bind(socket_path.clone()) + .with_context(|| format!("Failed to bind socket to {}", socket_path.display()))?; + listener.set_nonblocking(true)?; + let listener = WaylandListener(listener); + let token = self + .common + .event_loop_handle + .insert_source( + Generic::new(listener, Interest::READ, Mode::Edge), + move |_, listener, state: &mut State| { + loop { + match listener.0.accept() { + Ok((stream, _)) => { + let display = state.common.display.clone(); + let client = unsafe { + display + .borrow_mut() + .create_client(stream.into_raw_fd(), state) + }; + client + .data_map() + .insert_if_missing_threadsafe(|| render_node); + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // we have exhausted all the pending connections + break; + } + Err(e) => { + // this is a legitimate error + if let Ok(addr) = listener.0.local_addr() { + if let Some(path) = addr.as_pathname() { + slog_scope::error!( + "Error accepting connection on listening socket {} : {}", + path.display(), + e + ); + return Err(e); + } + } + slog_scope::error!( + "Error accepting connection on listening socket : {}", + e + ); + return Err(e); + } + } + } + + Ok(PostAction::Continue) + }, + ) + .context("Failed to add gpu-wayland socket to the event loop")?; + + // initialize globals + let filter = move |client: Client| { + let dev_id = client.data_map().get::(); + if dev_id.is_none() && is_primary { + client + .data_map() + .insert_if_missing_threadsafe(|| render_node); + } + dev_id.map(|x| *x == render_node).unwrap_or(is_primary) + }; + + let drm_global = init_wl_drm_global( + &mut *self.common.display.borrow_mut(), + render_node.dev_path().unwrap(), + formats.clone(), + filter.clone(), + ); + let dmabuf_global = init_dmabuf_global_with_filter( + &mut *self.common.display.borrow_mut(), + formats, + move |buf, mut ddata| { + let state = ddata.get::().unwrap(); + state + .backend + .kms() + .api + .renderer::(&render_node, &render_node) + .map(|mut renderer| renderer.import_dmabuf(buf, None).is_ok()) + .unwrap_or(false) + }, + filter, + None, + ); + + slog_scope::info!( + "Adding socket at {} for gpu {}", + socket_path.display(), + render_node + ); + + Ok(Socket { + token, + drm_global: GlobalDrop::from(drm_global), + dmabuf_global: GlobalDrop::from(dmabuf_global), + }) + } +} + +struct WaylandListener(UnixListener); + +impl AsRawFd for WaylandListener { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl Drop for WaylandListener { + fn drop(&mut self) { + if let Ok(socketaddr) = self.0.local_addr() { + if let Some(path) = socketaddr.as_pathname() { + let _ = ::std::fs::remove_file(path); + } + } + } +}