cosmic-comp/src/backend/kms/mod.rs

678 lines
25 KiB
Rust
Raw Normal View History

2022-01-20 19:51:46 +01:00
// SPDX-License-Identifier: GPL-3.0-only
2024-06-10 20:41:29 +02:00
use crate::{config::OutputState, shell::Shell, state::BackendData, utils::prelude::*};
2022-02-04 21:23:27 +01:00
2022-01-25 15:45:15 +01:00
use anyhow::{Context, Result};
use calloop::LoopSignal;
use render::gles::GbmGlowBackend;
2022-01-25 15:45:15 +01:00
use smithay::{
backend::{
allocator::{
2024-02-07 12:33:32 +01:00
dmabuf::Dmabuf,
gbm::{GbmAllocator, GbmBufferFlags},
2022-12-27 18:27:29 +01:00
},
drm::{DrmDeviceFd, DrmNode, NodeType},
egl::{context::ContextPriority, EGLContext},
2022-04-25 23:00:30 +02:00
input::InputEvent,
2022-01-25 15:45:15 +01:00
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{glow::GlowRenderer, multigpu::GpuManager},
2023-01-03 19:17:51 +01:00
session::{libseat::LibSeatSession, Event as SessionEvent, Session},
2022-03-16 20:06:31 +01:00
udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent},
2022-01-25 15:45:15 +01:00
},
output::Output,
2022-01-25 15:45:15 +01:00
reexports::{
calloop::{Dispatcher, EventLoop, LoopHandle},
drm::control::{crtc, Device as _},
input::{self, Libinput},
wayland_server::{Client, DisplayHandle},
},
utils::{DevPath, Size},
wayland::{dmabuf::DmabufGlobal, relative_pointer::RelativePointerManagerState},
2022-01-25 15:45:15 +01:00
};
use tracing::{error, info, trace, warn};
2022-02-04 21:23:27 +01:00
2022-01-25 15:45:15 +01:00
use std::{
borrow::BorrowMut,
2022-01-25 15:45:15 +01:00
collections::{HashMap, HashSet},
path::Path,
sync::{atomic::AtomicBool, Arc, RwLock},
2022-01-25 15:45:15 +01:00
};
2022-01-20 19:51:46 +01:00
mod device;
2022-02-01 13:59:39 +01:00
mod drm_helpers;
pub mod render;
mod socket;
mod surface;
2022-01-25 15:45:15 +01:00
use device::*;
pub use surface::Timings;
2024-03-12 19:42:48 +01:00
use super::render::init_shaders;
2023-07-12 18:54:53 +02:00
#[derive(Debug)]
2022-01-25 15:45:15 +01:00
pub struct KmsState {
pub drm_devices: HashMap<DrmNode, Device>,
pub input_devices: HashMap<String, input::Device>,
pub primary_node: Option<DrmNode>,
pub api: GpuManager<GbmGlowBackend<DrmDeviceFd>>,
2023-01-03 19:17:51 +01:00
session: LibSeatSession,
libinput: Libinput,
2022-01-25 15:45:15 +01:00
}
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")?;
2022-01-25 15:45:15 +01:00
// setup input
let libinput_context = init_libinput(dh, &session, &event_loop.handle())
.context("Failed to initialize libinput backend")?;
2023-07-12 18:54:53 +02:00
// 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);
}
2022-01-25 15:45:15 +01:00
// watch for gpu events
let udev_dispatcher = init_udev(session.seat(), &event_loop.handle())
.context("Failed to initialize udev connection")?;
2024-04-24 17:25:33 +02:00
// handle session events
let loop_signal = event_loop.get_signal();
let dispatcher = udev_dispatcher.clone();
2024-06-10 20:41:29 +02:00
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();
}
2024-04-24 17:25:33 +02:00
})
.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);
}
2024-04-24 17:25:33 +02:00
}
Ok(())
}
2023-03-07 16:37:11 +01:00
fn init_libinput(
2022-07-04 16:00:29 +02:00
dh: &DisplayHandle,
session: &LibSeatSession,
evlh: &LoopHandle<'static, State>,
) -> Result<Libinput> {
2022-02-04 21:23:27 +01:00
let mut libinput_context =
2023-01-03 19:17:51 +01:00
Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
2022-02-04 21:23:27 +01:00
libinput_context
.udev_assign_seat(&session.seat())
.map_err(|_| anyhow::anyhow!("Failed to assign seat to libinput"))?;
let libinput_backend = LibinputInputBackend::new(libinput_context.clone());
2022-01-25 15:45:15 +01:00
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());
}
state.process_input_event(event, true);
2022-03-16 20:06:31 +01:00
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::<State>(&dh);
Ok(libinput_context)
}
fn determine_primary_gpu(seat: String) -> Option<DrmNode> {
if let Some(node) = std::env::var("COSMIC_RENDER_DEVICE")
2022-03-16 20:06:31 +01:00
.ok()
.and_then(|x| DrmNode::from_path(x).ok())
{
Some(node)
2022-03-16 20:06:31 +01:00
} else {
let primary_node = primary_gpu(&seat)
2022-03-16 20:06:31 +01:00
.ok()
.flatten()
.and_then(|x| DrmNode::from_path(x).ok());
primary_node
2022-03-16 20:06:31 +01:00
.and_then(|x| x.node_with_type(NodeType::Render).and_then(Result::ok))
.or_else(|| {
for dev in all_gpus(&seat).expect("Failed to query gpus") {
2022-03-16 20:06:31 +01:00
if let Some(node) = DrmNode::from_path(dev)
.ok()
.and_then(|x| x.node_with_type(NodeType::Render).and_then(Result::ok))
{
return Some(node);
2022-03-16 20:06:31 +01:00
}
}
None
2022-03-16 20:06:31 +01:00
})
}
}
fn init_udev(
seat: String,
evlh: &LoopHandle<'static, State>,
) -> Result<Dispatcher<'static, UdevBackend, State>> {
let udev_backend = UdevBackend::new(&seat)?;
2022-03-16 20:06:31 +01:00
let dispatcher = Dispatcher::new(udev_backend, move |event, _, state: &mut State| {
2023-09-29 21:33:16 +02:00
let dh = state.common.display_handle.clone();
match match event {
2023-09-29 21:33:16 +02:00
UdevEvent::Added { device_id, path } => state
2024-01-23 15:24:52 +00:00
.device_added(device_id, path, &dh)
.with_context(|| format!("Failed to add drm device: {}", device_id)),
2023-09-29 21:33:16 +02:00
UdevEvent::Changed { device_id } => state
.device_changed(device_id)
.with_context(|| format!("Failed to update drm device: {}", device_id)),
2023-09-29 21:33:16 +02:00
UdevEvent::Removed { device_id } => state
.device_removed(device_id, &dh)
.with_context(|| format!("Failed to remove drm device: {}", device_id)),
} {
Ok(()) => {
trace!("Successfully handled udev event.")
}
Err(err) => {
error!(?err, "Error while handling udev event.")
}
}
});
2024-06-10 20:41:29 +02:00
evlh.register_dispatcher(dispatcher.clone())
.context("Failed to register udev event source")?;
2023-01-18 20:23:41 +01:00
Ok(dispatcher)
2022-01-25 15:45:15 +01:00
}
impl State {
fn resume_session(
&mut self,
dispatcher: Dispatcher<'static, UdevBackend, Self>,
loop_handle: LoopHandle<'static, State>,
loop_signal: LoopSignal,
) {
let backend = self.backend.kms();
// 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");
}
if let Some(lease_state) = device.leasing_global.as_mut() {
lease_state.resume::<State>();
}
2022-01-25 15:45:15 +01:00
}
2022-05-03 13:37:51 +02:00
// 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) => {
error!(?err, "Failed to read drm device {}.", path.display(),);
continue;
}
};
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(),);
}
}
2022-01-25 15:45:15 +01:00
}
// 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(),
2024-04-10 15:49:08 +02:00
);
state.common.refresh();
});
loop_signal.wakeup();
2022-01-25 15:45:15 +01:00
}
fn pause_session(&mut self) {
2023-02-14 23:15:58 +01:00
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();
}
2022-01-25 15:45:15 +01:00
for surface in device.surfaces.values_mut() {
surface.suspend();
2022-08-11 17:13:56 +02:00
}
}
2022-01-25 15:45:15 +01:00
}
}
impl KmsState {
pub fn switch_vt(&mut self, num: i32) -> Result<(), anyhow::Error> {
self.session.change_vt(num).map_err(Into::into)
2022-01-25 15:45:15 +01:00
}
pub fn dmabuf_imported(
2022-01-25 15:45:15 +01:00
&mut self,
_client: Option<Client>,
global: &DmabufGlobal,
dmabuf: Dmabuf,
) -> Result<DrmNode> {
let (expected_node, other_nodes) =
self.drm_devices
.values_mut()
.partition::<Vec<_>, _>(|device| {
device
.socket
.as_ref()
.map(|s| &s.dmabuf_global == global)
.unwrap_or(false)
});
2022-01-25 15:45:15 +01:00
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 {
_egl = Some(init_egl(&device.gbm).context("Failed to initialize egl context")?);
&_egl.as_ref().unwrap().display
};
2022-01-25 15:45:15 +01:00
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);
}
Err(err) => {
trace!(?err, "Failed to import dmabuf on {:?}", device.render_node);
last_err = err;
}
}
}
2023-03-31 13:57:37 +02:00
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();
}
2022-07-04 16:00:29 +02:00
}
pub fn target_node_for_output(&self, output: &Output) -> Option<DrmNode> {
self.drm_devices
.values()
.find(|dev| dev.surfaces.values().any(|s| s.output == *output))
.map(|dev| &dev.render_node)
.copied()
2023-03-31 13:57:37 +02:00
}
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")?;
let mut renderer = 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")?
};
init_shaders(renderer.borrow_mut()).context("Failed to compile shaders")?;
self.api.as_mut().add_node(
device.render_node,
GbmAllocator::new(
device.gbm.clone(),
// SCANOUT because stride bugs
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
),
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);
}
}
2023-03-07 22:20:44 +01:00
}
// I hate this. I want partial borrows of hashmap values
let all_devices = self.drm_devices.keys().copied().collect::<Vec<_>>();
for node in all_devices {
let (mut device, mut others) = self
.drm_devices
.iter_mut()
.partition::<Vec<_>, _>(|(n, _)| **n == node);
let device = &mut device[0].1;
2024-03-12 19:42:48 +01:00
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);
2024-04-24 17:25:33 +02:00
}
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(),
)
2024-04-24 17:25:33 +02:00
};
surface.add_node(
render_node,
GbmAllocator::new(gbm, GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT),
EGLContext::new_shared_with_priority(
&egl.display,
&egl.context,
ContextPriority::High,
2024-04-24 17:25:33 +02:00
)
.context("Failed to create shared EGL context")?,
);
2024-04-24 17:25:33 +02:00
}
2022-01-25 15:45:15 +01:00
}
2023-03-07 16:37:11 +01:00
}
2022-01-25 15:45:15 +01:00
Ok(())
}
pub fn apply_config_for_outputs(
&mut self,
test_only: bool,
loop_handle: &LoopHandle<'static, State>,
shell: Arc<RwLock<Shell>>,
startup_done: Arc<AtomicBool>,
) -> Result<Vec<Output>, anyhow::Error> {
if !self.session.is_active() {
return Ok(Vec::new());
}
2024-04-24 17:25:33 +02:00
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
2024-04-24 17:25:33 +02:00
.iter()
.any(|(leased_conn, _)| *conn == leased_conn)
})
.map(|(_, output)| output.clone())
.collect::<Vec<_>>();
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::<HashSet<crtc::Handle>>();
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();
2023-02-27 23:53:27 +01:00
let drm = &mut device.drm;
let conn = surface.connector;
2022-08-11 17:13:56 +02:00
let conn_info = drm.get_connector(conn, false)?;
let mode = conn_info
.modes()
.iter()
2022-07-19 14:41:04 +02:00
// match the size
.filter(|mode| {
let (x, y) = mode.size();
Size::from((x as i32, y as i32)) == output_config.mode_size()
})
// and then select the closest refresh rate (e.g. to match 59.98 as 60)
.min_by_key(|mode| {
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!("Unable to find matching mode"))?;
if !test_only {
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);
2023-04-18 19:14:31 +02:00
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: {}",
surface.output.name()
2023-04-18 19:14:31 +02:00
);
}
}
2024-04-24 17:25:33 +02:00
std::mem::drop(output_config);
surface
.resume(drm_surface, gbm, cursor_size, vrr)
.context("Failed to create surface")?;
} else {
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);
2024-06-10 20:41:29 +02:00
surface
.set_mode(*mode)
.context("Failed to apply new mode")?;
2024-04-24 17:25:33 +02:00
}
}
}
// 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.push(output);
}
}
2022-12-03 00:06:20 +01:00
all_outputs.extend(outputs);
2022-01-25 15:45:15 +01:00
}
// we need to handle mirroring, after all outputs have been enabled
for device in self.drm_devices.values_mut() {
for surface in device.surfaces.values_mut() {
let mirrored_output =
if let OutputState::Mirroring(conn) = &surface.output.config().enabled {
Some(
all_outputs
.iter()
.find(|output| &output.name() == conn)
.cloned()
.ok_or(anyhow::anyhow!("Unable to find mirroring output"))?,
)
} else {
None
};
if !test_only {
if mirrored_output != surface.output.mirroring() {
surface.set_mirroring(mirrored_output.clone());
}
}
}
}
Ok(all_outputs)
}
2022-02-04 21:23:27 +01:00
}