diff --git a/Cargo.lock b/Cargo.lock index 7fcf9c27..31444696 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -1901,9 +1901,9 @@ dependencies = [ [[package]] name = "gbm" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c724107aa10444b1d2709aae4727c18a33c16b3e15ea8a46cc4ae226c084c88a" +checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" dependencies = [ "bitflags 2.6.0", "drm 0.14.0", @@ -1914,9 +1914,9 @@ dependencies = [ [[package]] name = "gbm-sys" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9cc2f64de9fa707b5c6b2d2f10d7a7e49e845018a9f5685891eb40d3bab2538" +checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" dependencies = [ "libc", ] @@ -2472,7 +2472,7 @@ dependencies = [ [[package]] name = "id_tree" version = "1.8.0" -source = "git+https://github.com/Drakulix/id-tree.git?branch=feature/copy_clone#632a57d6d49160e18d7300fa7edae52281ec5482" +source = "git+https://github.com/Drakulix/id-tree.git?branch=feature%2Fcopy_clone#632a57d6d49160e18d7300fa7edae52281ec5482" dependencies = [ "snowflake", ] @@ -3874,9 +3874,8 @@ dependencies = [ [[package]] name = "pixman" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a24da0bec14f4e43a495c1837a3c358b87532e7fe66bd75c348b89f0451b6" +version = "0.2.0" +source = "git+https://github.com/ids1024/pixman-rs?branch=send#096317d6ffa277a222c5deafe74fcf767cf55bf1" dependencies = [ "drm-fourcc", "paste", @@ -3887,8 +3886,7 @@ dependencies = [ [[package]] name = "pixman-sys" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0483e89e81d7915defe83c51f23f6800594d64f6f4a21253ce87fd8444ada" +source = "git+https://github.com/ids1024/pixman-rs?branch=send#096317d6ffa277a222c5deafe74fcf767cf55bf1" [[package]] name = "pkg-config" @@ -4638,7 +4636,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/smithay//smithay?rev=bc1d732#bc1d7320f95cdf17f9e7aa6867cccc5903548032" +source = "git+https://github.com/cmeissl/smithay?branch=feature%2Fdrm_auto_select#afa1117c3e7668a3894cb9539c6c7e03e5418266" dependencies = [ "appendlist", "ash", diff --git a/Cargo.toml b/Cargo.toml index 6b8e669d..d89577e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,4 +119,8 @@ inherits = "release" lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/smithay//smithay", rev = "bc1d732" } +#smithay = { git = "https://github.com/smithay//smithay", rev = "bc1d732" } +smithay = { git = "https://github.com/cmeissl/smithay", branch = "feature/drm_auto_select" } + +[patch.crates-io] +pixman = { git = "https://github.com/ids1024/pixman-rs", branch = "send" } \ No newline at end of file diff --git a/src/backend/kms/device.rs b/src/backend/kms/device.rs index be22b7f9..e9204b0d 100644 --- a/src/backend/kms/device.rs +++ b/src/backend/kms/device.rs @@ -1,28 +1,40 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ + backend::render::{output_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR}, config::{AdaptiveSync, OutputConfig, OutputState}, shell::Shell, utils::prelude::*, + wayland::protocols::screencopy::Frame as ScreencopyFrame, }; use anyhow::{Context, Result}; use libc::dev_t; use smithay::{ backend::{ - allocator::gbm::GbmDevice, - drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmNode}, + allocator::{ + gbm::{GbmAllocator, GbmDevice}, + Fourcc, + }, + drm::{ + compositor::FrameMode, output::DrmOutputManager, DrmDevice, DrmDeviceFd, DrmEvent, + DrmNode, + }, egl::{context::ContextPriority, EGLContext, EGLDevice, EGLDisplay}, session::Session, }, + desktop::utils::OutputPresentationFeedback, output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel}, reexports::{ calloop::{LoopHandle, RegistrationToken}, drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags}, + gbm::BufferObjectFlags as GbmBufferFlags, rustix::fs::OFlags, wayland_server::{protocol::wl_buffer::WlBuffer, DisplayHandle, Weak}, }, - utils::{DevPath, DeviceFd, Point, Transform}, + utils::{ + Buffer as BufferCoords, Clock, DevPath, DeviceFd, Monotonic, Point, Rectangle, Transform, + }, wayland::drm_lease::{DrmLease, DrmLeaseState}, }; use tracing::{error, info, warn}; @@ -32,7 +44,7 @@ use std::{ collections::{HashMap, HashSet}, fmt, path::{Path, PathBuf}, - sync::{atomic::AtomicBool, Arc, RwLock}, + sync::{atomic::AtomicBool, mpsc::Receiver, Arc, RwLock}, }; use super::{drm_helpers, socket::Socket, surface::Surface}; @@ -44,6 +56,16 @@ pub struct EGLInternals { pub context: EGLContext, } +pub type GbmDrmOutputManager = DrmOutputManager< + GbmAllocator, + GbmDevice, + Option<( + OutputPresentationFeedback, + Receiver<(ScreencopyFrame, Vec>)>, + )>, + DrmDeviceFd, +>; + pub struct Device { pub dev_node: DrmNode, pub render_node: DrmNode, @@ -51,8 +73,8 @@ pub struct Device { pub outputs: HashMap, pub surfaces: HashMap, + pub drm: GbmDrmOutputManager, pub gbm: GbmDevice, - pub drm: DrmDevice, supports_atomic: bool, @@ -72,8 +94,7 @@ impl fmt::Debug for Device { .field("render_node", &self.render_node) .field("outputs", &self.outputs) .field("surfaces", &self.surfaces) - .field("drm", &self.drm) - .field("gbm", &self.gbm) + .field("drm", &"..") .field("egl", &self.egl) .field("supports_atomic", &self.supports_atomic) .field("leased_connectors", &self.leased_connectors) @@ -190,6 +211,23 @@ impl State { } }; + let drm = GbmDrmOutputManager::new( + drm, + GbmAllocator::new( + gbm.clone(), + GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, + ), + gbm.clone(), + Some(gbm.clone()), + [ + Fourcc::Abgr2101010, + Fourcc::Argb2101010, + Fourcc::Abgr8888, + Fourcc::Argb8888, + ], + render_formats, + ); + let mut device = Device { dev_node: drm_node, render_node, @@ -197,8 +235,8 @@ impl State { outputs: HashMap::new(), surfaces: HashMap::new(), - gbm: gbm.clone(), drm, + gbm, supports_atomic, @@ -264,6 +302,7 @@ impl State { &mut self.common.workspace_state.update(), &self.common.xdg_activation_state, self.common.startup_done.clone(), + &self.common.clock, ); self.backend.kms().refresh_used_devices()?; @@ -364,6 +403,7 @@ impl State { &mut self.common.workspace_state.update(), &self.common.xdg_activation_state, self.common.startup_done.clone(), + &self.common.clock, ); // 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. @@ -417,6 +457,7 @@ impl State { &mut self.common.workspace_state.update(), &self.common.xdg_activation_state, self.common.startup_done.clone(), + &self.common.clock, ); self.common.refresh(); } else { @@ -435,7 +476,8 @@ pub struct OutputChanges { 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 config = + drm_helpers::display_configuration(self.drm.device_mut(), self.supports_atomic)?; let surfaces = self .surfaces @@ -486,19 +528,20 @@ impl Device { .get(&conn) .cloned() .map(|output| Ok(output)) - .unwrap_or_else(|| create_output_for_conn(&mut self.drm, conn)) + .unwrap_or_else(|| create_output_for_conn(self.drm.device_mut(), 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 - } - }; + let non_desktop = + match drm_helpers::get_property_val(self.drm.device_mut(), 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 { @@ -528,7 +571,7 @@ impl Device { .user_data() .insert_if_missing(|| RefCell::new(OutputConfig::default())); - populate_modes(&mut self.drm, &output, conn, position) + populate_modes(self.drm.device_mut(), &output, conn, position) .with_context(|| "Failed to enumerate connector modes")?; let has_surface = if let Some(crtc) = maybe_crtc { @@ -569,6 +612,59 @@ impl Device { } } + pub fn allow_direct_scanout( + &mut self, + flag: bool, + renderer: &mut GlMultiRenderer, + clock: &Clock, + shell: &Arc>, + ) -> Result<()> { + for surface in self.surfaces.values_mut() { + surface.allow_direct_scanout(flag); + } + + if !flag { + let now = clock.now(); + + let output_map = self + .surfaces + .iter() + .map(|(crtc, surface)| (*crtc, surface.output.clone())) + .collect::>(); + + self.drm.with_compositors::>(|map| { + for (crtc, compositor) in map.iter() { + let elements = match output_map.get(crtc) { + Some(output) => output_elements( + Some(&self.render_node), + renderer, + shell, + now, + &output, + CursorMode::All, + None, + ) + .with_context(|| "Failed to render outputs")?, + None => Vec::new(), + }; + + let mut compositor = compositor.lock().unwrap(); + compositor.render_frame( + renderer, + &elements, + CLEAR_COLOR, + FrameMode::COMPOSITE, + )?; + compositor.commit_frame()?; + } + + Ok(()) + })?; + } + + Ok(()) + } + pub fn in_use(&self, primary: Option<&DrmNode>) -> bool { Some(&self.render_node) == primary || !self.surfaces.is_empty() diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index b1e54710..6d9c83f4 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -16,7 +16,7 @@ use smithay::{ dmabuf::Dmabuf, gbm::{GbmAllocator, GbmBufferFlags}, }, - drm::{DrmDeviceFd, DrmNode, NodeType}, + drm::{output::DrmOutputRenderElements, DrmDeviceFd, DrmNode, NodeType}, egl::{context::ContextPriority, EGLContext, EGLDevice, EGLDisplay}, input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, @@ -27,13 +27,17 @@ use smithay::{ output::Output, reexports::{ calloop::{Dispatcher, EventLoop, LoopHandle}, - drm::control::{crtc, Device as _}, + drm::{ + control::{crtc, Device as _}, + Device as _, + }, input::{self, Libinput}, wayland_server::{Client, DisplayHandle}, }, - utils::{DevPath, Size}, + utils::{Clock, DevPath, Monotonic, Size}, wayland::{dmabuf::DmabufGlobal, relative_pointer::RelativePointerManagerState}, }; +use surface::GbmDrmOutput; use tracing::{error, info, trace, warn}; use std::{ @@ -53,7 +57,7 @@ pub(crate) use surface::Surface; use device::*; pub use surface::Timings; -use super::render::init_shaders; +use super::render::{init_shaders, output_elements, CursorMode, CLEAR_COLOR}; #[derive(Debug)] pub struct KmsState { @@ -321,6 +325,7 @@ impl State { &mut state.common.workspace_state.update(), &state.common.xdg_activation_state, state.common.startup_done.clone(), + &state.common.clock, ); state.common.refresh(); }); @@ -528,6 +533,7 @@ impl KmsState { loop_handle: &LoopHandle<'static, State>, shell: Arc>, startup_done: Arc, + clock: &Clock, ) -> Result, anyhow::Error> { if !self.session.is_active() { return Ok(Vec::new()); @@ -554,7 +560,7 @@ impl KmsState { // 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 res_handles = device.drm.device().resource_handles()?; let free_crtcs = res_handles .crtcs() .iter() @@ -579,16 +585,20 @@ impl KmsState { }); 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), - ) - })?; + let conn_info = device + .drm + .device() + .get_connector(conn, false) + .with_context(|| { + format!( + "Failed to query drm device: {:?}", + device.drm.device().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)) + .flat_map(|encoder_handle| device.drm.device().get_encoder(*encoder_handle)) { for crtc in res_handles.filter_crtcs(encoder_info.possible_crtcs()) { if free_crtcs.contains(&crtc) { @@ -638,13 +648,21 @@ impl KmsState { let mut all_outputs = Vec::new(); for device in self.drm_devices.values_mut() { - // reconfigure + // reconfigure existing + let now = clock.now(); + let output_map = device + .surfaces + .iter() + .map(|(crtc, surface)| (*crtc, surface.output.clone())) + .filter(|(_, output)| shell.read().unwrap().outputs().any(|o| o == output)) + .collect::>(); + for (crtc, surface) in device.surfaces.iter_mut() { let output_config = surface.output.config(); let drm = &mut device.drm; let conn = surface.connector; - let conn_info = drm.get_connector(conn, false)?; + let conn_info = drm.device().get_connector(conn, false)?; let mode = conn_info .modes() .iter() @@ -662,14 +680,64 @@ impl KmsState { 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 compositor: GbmDrmOutput = { + let mut planes = drm + .device() + .planes(crtc) + .with_context(|| "Failed to enumerate planes")?; + let driver = drm.device().get_driver().ok(); + + // 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 mut renderer = self + .api + .single_renderer(&device.render_node) + .with_context(|| "Failed to create renderer")?; + + let mut elements = DrmOutputRenderElements::default(); + for (crtc, output) in output_map.iter() { + let output_elements = output_elements( + Some(&device.render_node), + &mut renderer, + &shell, + now, + &output, + CursorMode::All, + None, + ) + .with_context(|| "Failed to render outputs")?; + + elements.add_output(crtc, CLEAR_COLOR, output_elements); + } + + let compositor = drm + .initialize_output( + *crtc, + *mode, + &[conn], + &surface.output, + Some(planes), + &mut renderer, + &elements, + ) + .with_context(|| "Failed to create drm surface")?; + + let _ = renderer; + + compositor + }; if let Some(bpc) = output_config.max_bpc { - if let Err(err) = drm_helpers::set_max_bpc(drm, conn, bpc) { + if let Err(err) = drm_helpers::set_max_bpc(drm.device(), conn, bpc) { warn!( ?bpc, ?err, @@ -682,7 +750,7 @@ impl KmsState { let vrr = output_config.vrr; std::mem::drop(output_config); - match surface.resume(drm_surface, gbm, cursor_size) { + match surface.resume(compositor) { Ok(_) => { surface.output.set_adaptive_sync_support( surface.adaptive_sync_support().ok(), @@ -711,8 +779,29 @@ impl KmsState { surface.output.set_adaptive_sync(AdaptiveSync::Disabled); } } - surface - .set_mode(*mode) + + let mut renderer = self + .api + .single_renderer(&device.render_node) + .with_context(|| "Failed to create renderer")?; + + let mut elements = DrmOutputRenderElements::default(); + for (crtc, output) in output_map.iter() { + let output_elements = output_elements( + Some(&device.render_node), + &mut renderer, + &shell, + now, + &output, + CursorMode::All, + None, + ) + .with_context(|| "Failed to render outputs")?; + + elements.add_output(crtc, CLEAR_COLOR, output_elements); + } + + drm.use_mode(&surface.crtc, *mode, &mut renderer, &elements) .context("Failed to apply new mode")?; } } diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index 2fe8ef05..af48ee37 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -3,12 +3,12 @@ use crate::{ backend::render::{ element::{CosmicElement, DamageElement}, - init_shaders, workspace_elements, CursorMode, ElementFilter, GlMultiRenderer, CLEAR_COLOR, + init_shaders, output_elements, CursorMode, GlMultiRenderer, CLEAR_COLOR, }, config::AdaptiveSync, shell::Shell, state::SurfaceDmabufFeedback, - utils::{prelude::*, quirks::workspace_overview_is_open}, + utils::prelude::*, wayland::{ handlers::screencopy::{submit_buffer, FrameHolder, SessionData}, protocols::screencopy::{ @@ -23,12 +23,13 @@ use smithay::{ backend::{ allocator::{ format::FormatSet, - gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}, + gbm::{GbmAllocator, GbmDevice}, Fourcc, }, drm::{ - compositor::{BlitFrameResultError, DrmCompositor, FrameError, PrimaryPlaneElement}, - DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, DrmSurface, VrrSupport, + compositor::{BlitFrameResultError, FrameError, FrameMode, PrimaryPlaneElement}, + output::DrmOutput, + DrmDeviceFd, DrmEventMetadata, DrmEventTime, DrmNode, VrrSupport, }, egl::EGLContext, renderer::{ @@ -55,17 +56,14 @@ use smithay::{ timer::{TimeoutAction, Timer}, EventLoop, LoopHandle, RegistrationToken, }, - drm::{ - control::{connector, crtc, Mode}, - Device as _, - }, + drm::control::{connector, crtc}, 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}, + utils::{Buffer as BufferCoords, Clock, Monotonic, Physical, Rectangle, Transform}, wayland::{ dmabuf::{get_dmabuf, DmabufFeedbackBuilder}, presentation::Refresh, @@ -105,7 +103,7 @@ static NVIDIA_LOGO: &'static [u8] = include_bytes!("../../../../resources/icons/ #[derive(Debug)] pub struct Surface { pub(crate) connector: connector::Handle, - pub(super) _crtc: crtc::Handle, + pub(super) crtc: crtc::Handle, pub(crate) output: Output, known_nodes: HashSet, @@ -127,7 +125,8 @@ pub struct SurfaceThreadState { target_node: DrmNode, active: Arc, vrr_mode: AdaptiveSync, - compositor: Option, + direct_scanout_allowed: bool, + compositor: Option, state: QueueState, timings: Timings, @@ -186,7 +185,7 @@ impl MirroringState { } } -pub type GbmDrmCompositor = DrmCompositor< +pub type GbmDrmOutput = DrmOutput< GbmAllocator, GbmDevice, Option<( @@ -224,9 +223,7 @@ impl Default for QueueState { pub enum ThreadCommand { Suspend(SyncSender<()>), Resume { - surface: DrmSurface, - gbm: GbmDevice, - cursor_size: Size, + compositor: GbmDrmOutput, result: SyncSender>, }, NodeAdded { @@ -240,9 +237,9 @@ pub enum ThreadCommand { UpdateMirroring(Option), VBlank(Option), ScheduleRender, - SetMode(Mode, SyncSender>), AdaptiveSyncAvailable(SyncSender>), UseAdaptiveSync(AdaptiveSync), + AllowDirectScanout(bool, SyncSender<()>), End, DpmsOff, } @@ -348,7 +345,7 @@ impl Surface { Ok(Surface { connector, - _crtc: crtc, + crtc, output: output.clone(), known_nodes: HashSet::new(), active, @@ -399,12 +396,6 @@ impl Surface { .send(ThreadCommand::UpdateMirroring(output)); } - pub fn set_mode(&mut self, mode: Mode) -> Result<()> { - let (tx, rx) = std::sync::mpsc::sync_channel(1); - let _ = self.thread_command.send(ThreadCommand::SetMode(mode, tx)); - rx.recv().context("Surface thread died")? - } - pub fn adaptive_sync_support(&self) -> Result { let (tx, rx) = std::sync::mpsc::sync_channel(1); let _ = self @@ -432,37 +423,40 @@ impl Surface { Ok(true) } + pub fn allow_direct_scanout(&mut self, flag: bool) { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + let _ = self + .thread_command + .send(ThreadCommand::AllowDirectScanout(flag, tx)); + let _ = rx.recv(); + } + pub fn suspend(&mut self) { let (tx, rx) = std::sync::mpsc::sync_channel(1); let _ = self.thread_command.send(ThreadCommand::Suspend(tx)); let _ = rx.recv(); } - pub fn resume( - &mut self, - surface: DrmSurface, - gbm: GbmDevice, - cursor_size: Size, - ) -> Result<()> { + pub fn resume(&mut self, compositor: GbmDrmOutput) -> Result<()> { let (tx, rx) = std::sync::mpsc::sync_channel(1); - self.plane_formats = surface - .plane_info() - .formats - .iter() - .copied() - .chain( - surface - .planes() - .overlay - .iter() - .flat_map(|p| p.formats.iter().cloned()), - ) - .collect::(); + self.plane_formats = compositor.with_compositor(|c| { + c.surface() + .plane_info() + .formats + .iter() + .copied() + .chain( + c.surface() + .planes() + .overlay + .iter() + .flat_map(|p| p.formats.iter().cloned()), + ) + .collect::() + }); let _ = self.thread_command.send(ThreadCommand::Resume { - surface, - gbm, - cursor_size, + compositor, result: tx, }); @@ -533,6 +527,8 @@ fn surface_thread( target_node, active, compositor: None, + direct_scanout_allowed: !crate::utils::env::bool_var("COSMIC_DISABLE_DIRECT_SCANOUT") + .unwrap_or(false), vrr_mode: AdaptiveSync::Disabled, state: QueueState::Idle, @@ -556,13 +552,8 @@ fn surface_thread( .handle() .insert_source(thread_receiver, move |command, _, state| match command { Event::Msg(ThreadCommand::Suspend(tx)) => state.suspend(tx), - Event::Msg(ThreadCommand::Resume { - surface, - gbm, - cursor_size, - result, - }) => { - let _ = result.send(state.resume(surface, gbm, cursor_size)); + Event::Msg(ThreadCommand::Resume { compositor, result }) => { + let _ = result.send(state.resume(compositor)); } Event::Msg(ThreadCommand::NodeAdded { node, gbm, egl }) => { if let Err(err) = state.node_added(node, gbm, egl) { @@ -585,20 +576,13 @@ fn surface_thread( Event::Msg(ThreadCommand::UpdateMirroring(mirroring_output)) => { state.update_mirroring(mirroring_output); } - Event::Msg(ThreadCommand::SetMode(mode, result)) => { - if let Some(compositor) = state.compositor.as_mut() { - let _ = result.send(compositor.use_mode(mode).map_err(Into::into)); - } else { - let _ = result.send(Err(anyhow::anyhow!("Set mode with inactive surface"))); - } - } Event::Msg(ThreadCommand::AdaptiveSyncAvailable(result)) => { if let Some(compositor) = state.compositor.as_mut() { let _ = result.send( compositor - .vrr_supported( - compositor.pending_connectors().into_iter().next().unwrap(), - ) + .with_compositor(|c| { + c.vrr_supported(c.pending_connectors().into_iter().next().unwrap()) + }) .map_err(Into::into), ); } else { @@ -610,7 +594,7 @@ fn surface_thread( } Event::Msg(ThreadCommand::DpmsOff) => { if let Some(compositor) = state.compositor.as_mut() { - if let Err(err) = compositor.clear() { + if let Err(err) = compositor.with_compositor(|c| c.clear()) { error!("Failed to set DPMS off: {:?}", err); } match std::mem::replace(&mut state.state, QueueState::Idle) { @@ -632,6 +616,12 @@ fn surface_thread( }; } } + Event::Msg(ThreadCommand::AllowDirectScanout(flag, tx)) => { + state.direct_scanout_allowed = flag + && !crate::utils::env::bool_var("COSMIC_DISABLE_DIRECT_SCANOUT") + .unwrap_or(false); + let _ = tx.send(()); + } Event::Closed | Event::Msg(ThreadCommand::End) => { signal.stop(); signal.wakeup(); @@ -666,69 +656,19 @@ impl SurfaceThreadState { let _ = tx.send(()); } - fn resume( - &mut self, - surface: DrmSurface, - gbm: GbmDevice, - cursor_size: Size, - ) -> 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 = self - .api - .single_renderer(&self.target_node) - .unwrap() - .dmabuf_formats(); - + fn resume(&mut self, compositor: GbmDrmOutput) -> Result<()> { + let mode = compositor.with_compositor(|c| c.surface().pending_mode()); self.timings .set_refresh_interval(Some(Duration::from_secs_f64( - 1_000.0 / drm_helpers::calculate_refresh_rate(surface.pending_mode()) as f64, + 1_000.0 / drm_helpers::calculate_refresh_rate(mode) as f64, ))); - match DrmCompositor::new( - &self.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(mut compositor) => { - if crate::utils::env::bool_var("COSMIC_DISABLE_DIRECT_SCANOUT").unwrap_or(false) { - compositor.use_direct_scanout(false); - } - self.active.store(true, Ordering::SeqCst); - self.compositor = Some(compositor); - Ok(()) - } - Err(err) => { - self.active.store(false, Ordering::SeqCst); - Err(err.into()) - } - } + /*if crate::utils::env::bool_var("COSMIC_DISABLE_DIRECT_SCANOUT").unwrap_or(false) { + compositor.use_direct_scanout(false); + }*/ + self.active.store(true, Ordering::SeqCst); + self.compositor = Some(compositor); + Ok(()) } fn node_added( @@ -813,7 +753,7 @@ impl SurfaceThreadState { if self .compositor .as_ref() - .is_some_and(|comp| comp.vrr_enabled()) => + .is_some_and(|comp| comp.with_compositor(|c| c.vrr_enabled())) => { Refresh::Variable(rate) } @@ -971,47 +911,27 @@ impl SurfaceThreadState { _ => false, }; - let mut elements = { + if self.vrr_mode == AdaptiveSync::Enabled { let shell = self.shell.read().unwrap(); let output = self.mirroring.as_ref().unwrap_or(&self.output); + vrr = shell.workspaces.active(output).1.get_fullscreen().is_some(); + } - 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)); - if self.vrr_mode == AdaptiveSync::Enabled { - vrr = workspace.get_fullscreen().is_some(); - } - let workspace = (workspace.handle, idx); - - std::mem::drop(shell); - - let element_filter = if workspace_overview_is_open(output) { - ElementFilter::LayerShellOnly - } else { - ElementFilter::All - }; - - workspace_elements( - Some(&render_node), - &mut renderer, - &self.shell, - self.clock.now(), - output, - previous_workspace, - workspace, - CursorMode::All, - element_filter, - #[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) - })? - }; + let mut elements = output_elements( + Some(&render_node), + &mut renderer, + &self.shell, + self.clock.now(), + self.mirroring.as_ref().unwrap_or(&self.output), + CursorMode::All, + #[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.set_vrr(vrr); self.timings.elements_done(&self.clock); @@ -1168,18 +1088,29 @@ impl SurfaceThreadState { .collect::>(); renderer = self.api.single_renderer(&self.target_node).unwrap(); - if let Err(err) = compositor.use_vrr(false) { - warn!("Unable to set adaptive VRR state: {}", err); - } - compositor.render_frame(&mut renderer, &elements, [0.0, 0.0, 0.0, 1.0]) + compositor.render_frame( + &mut renderer, + &elements, + [0.0, 0.0, 0.0, 1.0], + if self.direct_scanout_allowed { + FrameMode::ALL + } else { + FrameMode::COMPOSITE + }, + ) } else { - if let Err(err) = compositor.use_vrr(vrr) { + if let Err(err) = compositor.with_compositor(|c| c.use_vrr(vrr)) { warn!("Unable to set adaptive VRR state: {}", err); } compositor.render_frame( &mut renderer, &elements, CLEAR_COLOR, // TODO use a theme neutral color + if self.direct_scanout_allowed { + FrameMode::ALL + } else { + FrameMode::COMPOSITE + }, ) }; self.timings.draw_done(&self.clock); @@ -1234,10 +1165,10 @@ impl SurfaceThreadState { if let Ok(dmabuf) = get_dmabuf(&buffer) { renderer .bind(dmabuf.clone()) - .map_err(RenderError::::Rendering)?; + .map_err(RenderError::<::Error>::Rendering)?; } else { let size = buffer_dimensions(&buffer).ok_or(RenderError::< - GlMultiRenderer, + ::Error, >::Rendering( MultiError::ImportFailed, ))?; @@ -1251,10 +1182,10 @@ impl SurfaceThreadState { format, size, ) - .map_err(RenderError::::Rendering)?; + .map_err(RenderError::<::Error>::Rendering)?; renderer .bind(render_buffer) - .map_err(RenderError::::Rendering)?; + .map_err(RenderError::<::Error>::Rendering)?; } let (output_size, output_scale, output_transform) = ( @@ -1296,14 +1227,16 @@ impl SurfaceThreadState { filter, ) .map_err(|err| match err { - BlitFrameResultError::Rendering(err) => { - RenderError::::Rendering(err) - } - BlitFrameResultError::Export(_) => { - RenderError::::Rendering( - MultiError::DeviceMissing, - ) - } + BlitFrameResultError::Rendering(err) => RenderError::< + ::Error, + >::Rendering( + err + ), + BlitFrameResultError::Export(_) => RenderError::< + ::Error, + >::Rendering( + MultiError::DeviceMissing, + ), }) { Ok(new_sync) => { sync = new_sync; diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index aa545068..53fd49b8 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -499,6 +499,53 @@ pub enum ElementFilter { LayerShellOnly, } +pub fn output_elements( + _gpu: Option<&DrmNode>, + renderer: &mut R, + shell: &Arc>, + now: Time, + output: &Output, + cursor_mode: CursorMode, + _fps: Option<(&EguiState, &Timings)>, +) -> Result>, RenderError> +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: Send + Clone + 'static, + ::Error: FromGlesError, + CosmicMappedRenderElement: RenderElement, + WorkspaceRenderElement: RenderElement, +{ + let shell_guard = shell.read().unwrap(); + + let (previous_workspace, workspace) = shell_guard.workspaces.active(output); + let (previous_idx, idx) = shell_guard.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_guard); + + let element_filter = if workspace_overview_is_open(output) { + ElementFilter::LayerShellOnly + } else { + ElementFilter::All + }; + + workspace_elements( + _gpu, + renderer, + shell, + now, + output, + previous_workspace, + workspace, + cursor_mode, + element_filter, + _fps, + ) +} + #[profiling::function] pub fn workspace_elements( _gpu: Option<&DrmNode>, @@ -511,7 +558,7 @@ pub fn workspace_elements( cursor_mode: CursorMode, element_filter: ElementFilter, _fps: Option<(&EguiState, &Timings)>, -) -> Result>, RenderError> +) -> Result>, RenderError> where R: Renderer + ImportAll + ImportMem + AsGlowRenderer, ::TextureId: Send + Clone + 'static, @@ -867,7 +914,7 @@ pub fn render_output<'d, R, Target, OffTarget>( now: Time, output: &Output, cursor_mode: CursorMode, -) -> Result, RenderError> +) -> Result, RenderError> where R: Renderer + ImportAll @@ -1026,7 +1073,7 @@ pub fn render_workspace<'d, R, Target, OffTarget>( current: (WorkspaceHandle, usize), cursor_mode: CursorMode, element_filter: ElementFilter, -) -> Result<(RenderOutputResult<'d>, Vec>), RenderError> +) -> Result<(RenderOutputResult<'d>, Vec>), RenderError> where R: Renderer + ImportAll diff --git a/src/backend/winit.rs b/src/backend/winit.rs index a1a2bdce..f293341c 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -228,6 +228,7 @@ pub fn init_backend( &mut state.common.workspace_state.update(), &state.common.xdg_activation_state, state.common.startup_done.clone(), + &state.common.clock, ); state.common.refresh(); } diff --git a/src/backend/x11.rs b/src/backend/x11.rs index ba33c14b..a18bcaf5 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -363,6 +363,7 @@ pub fn init_backend( &mut state.common.workspace_state.update(), &state.common.xdg_activation_state, state.common.startup_done.clone(), + &state.common.clock, ); state.common.refresh(); } diff --git a/src/config/mod.rs b/src/config/mod.rs index f8bd9a2f..edf13d30 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -11,7 +11,6 @@ use cosmic_config::{ConfigGet, CosmicConfigEntry}; use cosmic_settings_config::window_rules::ApplicationException; use cosmic_settings_config::{shortcuts, window_rules, Shortcuts}; use serde::{Deserialize, Serialize}; -use smithay::wayland::xdg_activation::XdgActivationState; pub use smithay::{ backend::input::KeyState, input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState}, @@ -25,6 +24,10 @@ pub use smithay::{ }, utils::{Logical, Physical, Point, Size, Transform}, }; +use smithay::{ + utils::{Clock, Monotonic}, + wayland::xdg_activation::XdgActivationState, +}; use std::{ cell::RefCell, collections::{BTreeMap, HashMap}, @@ -381,6 +384,7 @@ impl Config { workspace_state: &mut WorkspaceUpdateGuard<'_, State>, xdg_activation_state: &XdgActivationState, startup_done: Arc, + clock: &Clock, ) { let outputs = output_state.outputs().collect::>(); let mut infos = outputs @@ -422,6 +426,7 @@ impl Config { workspace_state, xdg_activation_state, startup_done.clone(), + clock, ) { warn!(?err, "Failed to set new config."); found_outputs.clear(); @@ -446,6 +451,7 @@ impl Config { workspace_state, xdg_activation_state, startup_done, + clock, ) { error!(?err, "Failed to reset config."); } else { @@ -477,6 +483,7 @@ impl Config { workspace_state, xdg_activation_state, startup_done, + clock, ) { warn!(?err, "Failed to set new config.",); } else { diff --git a/src/state.rs b/src/state.rs index fc2ba06e..e7a09efa 100644 --- a/src/state.rs +++ b/src/state.rs @@ -297,11 +297,16 @@ impl BackendData { workspace_state: &mut WorkspaceUpdateGuard<'_, State>, xdg_activation_state: &XdgActivationState, startup_done: Arc, + clock: &Clock, ) -> Result<(), anyhow::Error> { let result = match self { - BackendData::Kms(ref mut state) => { - state.apply_config_for_outputs(test_only, loop_handle, shell.clone(), startup_done) - } + BackendData::Kms(ref mut state) => state.apply_config_for_outputs( + test_only, + loop_handle, + shell.clone(), + startup_done, + clock, + ), BackendData::Winit(ref mut state) => state.apply_config_for_outputs(test_only), BackendData::X11(ref mut state) => state.apply_config_for_outputs(test_only), _ => unreachable!("No backend set when applying output config"), diff --git a/src/wayland/handlers/drm_lease.rs b/src/wayland/handlers/drm_lease.rs index daf8794f..02bd1a14 100644 --- a/src/wayland/handlers/drm_lease.rs +++ b/src/wayland/handlers/drm_lease.rs @@ -26,14 +26,32 @@ impl DrmLeaseHandler for State { node: DrmNode, request: DrmLeaseRequest, ) -> Result { - let backend = self - .backend - .kms() + let kms = self.backend.kms(); + let backend = kms .drm_devices .get_mut(&node) .ok_or(LeaseRejected::default())?; + let mut renderer = match kms.api.single_renderer(&backend.render_node) { + Ok(renderer) => renderer, + Err(err) => { + tracing::warn!( + ?err, + "Failed to create renderer to disable direct scanout, denying lease" + ); + return Err(LeaseRejected::default()); + } + }; + if let Err(err) = backend.allow_direct_scanout( + false, + &mut renderer, + &self.common.clock, + &self.common.shell, + ) { + tracing::warn!(?err, "Failed to disable direct scanout"); + return Err(LeaseRejected::default()); + } - let mut builder = DrmLeaseBuilder::new(&backend.drm); + let mut builder = DrmLeaseBuilder::new(backend.drm.device()); for conn in request.connectors { if let Some((_, crtc)) = backend .leased_connectors @@ -44,6 +62,7 @@ impl DrmLeaseHandler for State { builder.add_crtc(*crtc); let planes = backend .drm + .device() .planes(crtc) .map_err(LeaseRejected::with_cause)?; let (primary_plane, primary_plane_claim) = planes @@ -52,6 +71,7 @@ impl DrmLeaseHandler for State { .find_map(|plane| { backend .drm + .device_mut() .claim_plane(plane.handle, *crtc) .map(|claim| (plane, claim)) }) @@ -60,6 +80,7 @@ impl DrmLeaseHandler for State { if let Some((cursor, claim)) = planes.cursor.into_iter().find_map(|plane| { backend .drm + .device_mut() .claim_plane(plane.handle, *crtc) .map(|claim| (plane, claim)) }) { @@ -85,8 +106,27 @@ impl DrmLeaseHandler for State { } fn lease_destroyed(&mut self, node: DrmNode, lease: u32) { - if let Some(backend) = self.backend.kms().drm_devices.get_mut(&node) { + let kms = self.backend.kms(); + if let Some(backend) = kms.drm_devices.get_mut(&node) { backend.active_leases.retain(|l| l.id() != lease); + + if backend.active_leases.is_empty() { + let mut renderer = match kms.api.single_renderer(&backend.render_node) { + Ok(renderer) => renderer, + Err(err) => { + tracing::warn!(?err, "Failed to create renderer to enable direct scanout."); + return; + } + }; + if let Err(err) = backend.allow_direct_scanout( + true, + &mut renderer, + &self.common.clock, + &self.common.shell, + ) { + tracing::warn!(?err, "Failed to enable direct scanout"); + } + } } } } diff --git a/src/wayland/handlers/output_configuration.rs b/src/wayland/handlers/output_configuration.rs index eb900ddc..72549a8b 100644 --- a/src/wayland/handlers/output_configuration.rs +++ b/src/wayland/handlers/output_configuration.rs @@ -140,6 +140,7 @@ impl State { &mut self.common.workspace_state.update(), &self.common.xdg_activation_state, self.common.startup_done.clone(), + &self.common.clock, ); if let Err(err) = res { warn!(?err, "Failed to apply config. Resetting"); @@ -161,6 +162,7 @@ impl State { &mut self.common.workspace_state.update(), &self.common.xdg_activation_state, self.common.startup_done.clone(), + &self.common.clock, ) { error!(?err, "Failed to reset output config."); } diff --git a/src/wayland/handlers/screencopy/render.rs b/src/wayland/handlers/screencopy/render.rs index eda0951b..236bb765 100644 --- a/src/wayland/handlers/screencopy/render.rs +++ b/src/wayland/handlers/screencopy/render.rs @@ -140,7 +140,7 @@ pub fn render_session( frame: Frame, transform: Transform, render_fn: F, -) -> Result>)>, DTError> +) -> Result>)>, DTError> where R: ExportMem, ::Error: FromGlesError, @@ -150,7 +150,7 @@ where &'d mut OutputDamageTracker, usize, Vec>, - ) -> Result, DTError>, + ) -> Result, DTError>, { let mut session_damage_tracking = session.lock().unwrap(); @@ -225,7 +225,7 @@ pub fn render_workspace_to_buffer( common: &mut Common, output: &Output, handle: (WorkspaceHandle, usize), - ) -> Result, DTError> + ) -> Result, DTError> where R: Renderer + ImportAll @@ -464,7 +464,7 @@ pub fn render_window_to_buffer( common: &mut Common, window: &CosmicSurface, geometry: Rectangle, - ) -> Result, DTError> + ) -> Result, DTError> where R: Renderer + ImportAll @@ -700,7 +700,7 @@ pub fn render_cursor_to_buffer( additional_damage: Vec>, common: &mut Common, seat: &Seat, - ) -> Result, DTError> + ) -> Result, DTError> where R: Renderer + ImportAll