use std::{ cell::RefCell, os::fd::AsFd, path::PathBuf, rc::{Rc, Weak}, }; use anyhow::{bail, Context}; use log::{debug, error, warn}; use rustix::fs::{Dev, major, minor}; use smithay_client_toolkit::{ delegate_compositor, delegate_dmabuf, delegate_layer, delegate_output, delegate_registry, delegate_shm, compositor::{CompositorHandler, Region}, dmabuf::{DmabufFeedback, DmabufHandler, DmabufState}, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, shell::{ WaylandSurface, wlr_layer::{ Anchor, KeyboardInteractivity, Layer, LayerShellHandler, LayerSurface, LayerSurfaceConfigure, }, }, shm::{ Shm, ShmHandler, raw::RawPool, }, }; use smithay_client_toolkit::reexports::client::{ Connection, Dispatch, Proxy, QueueHandle, protocol::{ wl_buffer::WlBuffer, wl_output::{self, Transform, WlOutput}, wl_shm, wl_surface::WlSurface, }, }; use smithay_client_toolkit::reexports::protocols::wp::{ linux_dmabuf::zv1::client::{ zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, zwp_linux_buffer_params_v1::{self, ZwpLinuxBufferParamsV1}, }, viewporter::client::{ wp_viewport::WpViewport, wp_viewporter::WpViewporter } }; use crate::{ State, gpu::{ DRM_FORMAT_XRGB8888, fmt_modifier, GpuMemory, GpuUploader, GpuWallpaper, }, image::{load_wallpaper, output_wallpaper_files, WallpaperFile}, }; impl CompositorHandler for State { fn scale_factor_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &WlSurface, _new_factor: i32, ) { } fn frame( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &WlSurface, _time: u32, ) { } fn transform_changed( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &WlSurface, _new_transform: wl_output::Transform, ) { } fn surface_enter( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &WlSurface, _output: &wl_output::WlOutput, ) { } fn surface_leave( &mut self, _conn: &Connection, _qh: &QueueHandle, _surface: &WlSurface, _output: &wl_output::WlOutput, ) { } } impl DmabufHandler for State { fn dmabuf_state(&mut self) -> &mut DmabufState { &mut self.dmabuf_state } fn dmabuf_feedback( &mut self, _conn: &Connection, qh: &QueueHandle, proxy: &ZwpLinuxDmabufFeedbackV1, feedback: DmabufFeedback, ) { let Some(bg_layer_pos) = self.background_layers.iter() .position(|bg_layer| bg_layer.dmabuf_feedback.as_ref() == Some(proxy) ) else { error!("Received unexpected Linux DMA-BUF feedback"); return }; if let Err(e) = handle_dmabuf_feedback( self, qh, feedback, bg_layer_pos ) { error!("Failed to proceed with DMA-BUF feedback, \ falling back to shm: {e:#}"); fallback_shm_load_wallpapers(self, qh, bg_layer_pos); } } fn created( &mut self, _conn: &Connection, _qh: &QueueHandle, params: &ZwpLinuxBufferParamsV1, buffer: WlBuffer, ) { for bg_layer in self.background_layers.iter_mut() { for workspace_bg in bg_layer.workspace_backgrounds.iter_mut() { let wallpaper = &workspace_bg.wallpaper; let mut wallpaper_borrow = wallpaper.borrow_mut(); if wallpaper_borrow.memory.dmabuf_params_destroy_eq(params) { wallpaper_borrow.wl_buffer = Some(buffer); debug!("Created Linux DMA-BUF buffer for wallpaper \ file {:?}", wallpaper_borrow.canon_path); drop(wallpaper_borrow); if let Some(queued_weak) = &bg_layer.queued_wallpaper { if let Some(queued) = queued_weak.upgrade() { if Rc::ptr_eq(&queued, wallpaper) { let name = workspace_bg.workspace_name.clone(); bg_layer.draw_workspace_bg(&name); } } } return } } } error!("Received unexpected created Linux DMA-BUF buffer"); } fn failed( &mut self, _conn: &Connection, qh: &QueueHandle, params: &ZwpLinuxBufferParamsV1, ) { error!("Failed to create a Linux DMA-BUF buffer"); let mut failed_bg_layer_indecies = Vec::new(); for (i, bg_layer) in self.background_layers.iter_mut().enumerate() { for workspace_bg in bg_layer.workspace_backgrounds.iter_mut() { let mut wallpaper = workspace_bg.wallpaper.borrow_mut(); if wallpaper.memory.dmabuf_params_destroy_eq(params) { error!("Falling back to shm and reloading wallpapers \ for output {}", bg_layer.output_name); failed_bg_layer_indecies.push(i); break } } } for index in failed_bg_layer_indecies { fallback_shm_load_wallpapers(self, qh, index); } } fn released( &mut self, _conn: &Connection, _qh: &QueueHandle, buffer: &WlBuffer ) { for bg in self.background_layers.iter_mut() .flat_map(|bg_layer| &mut bg_layer.workspace_backgrounds) { let mut wallpaper = bg.wallpaper.borrow_mut(); if wallpaper.wl_buffer.as_ref() == Some(buffer) { if let Some(new_count) = wallpaper.active_count.checked_sub(1) { debug!("Compositor released the DMA-BUF wl_buffer of {:?}", wallpaper.canon_path); wallpaper.active_count = new_count; } else { error!("Unexpected release event for the DMA-BUF \ wl_buffer of {:?}", wallpaper.canon_path); } return } } warn!("Release event for already destroyed DMA-BUF wl_buffer"); } } impl LayerShellHandler for State { fn closed( &mut self, _conn: &Connection, _qh: &QueueHandle, _layer: &LayerSurface ) { } fn configure( &mut self, _conn: &Connection, _qh: &QueueHandle, layer: &LayerSurface, configure: LayerSurfaceConfigure, _serial: u32, ) { // The new layer is ready: request all the visible workspace from sway, // it will get picked up by the main event loop and be drawn from there let bg_layer = self.background_layers.iter_mut() .find(|bg_layer| &bg_layer.layer == layer).unwrap(); if !bg_layer.configured { bg_layer.configured = true; self.compositor_connection_task .request_visible_workspace(&bg_layer.output_name); debug!("Configured layer on output: {}, new surface size {}x{}", bg_layer.output_name, configure.new_size.0, configure.new_size.1); } else { debug!("Ignoring configure for already configured layer \ on output: {}, new surface size {}x{}", bg_layer.output_name, configure.new_size.0, configure.new_size.1); } } } impl OutputHandler for State { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, qh: &QueueHandle, output: WlOutput, ) { let Some(info) = self.output_state.info(&output) else { error!("New output has no output info, skipping"); return }; let Some(output_name) = info.name else { error!("New output has no name, skipping"); return }; let Some((width, height)) = info.modes.iter() .find(|mode| mode.current) .map(|mode| mode.dimensions) else { error!("New output {} has no current mode set, skipping", output_name); return }; if !width.is_positive() || !height.is_positive() { error!("New output {} has non-positive resolution: {} x {}, \ skipping", output_name, width, height); return } let (width, height) = { match info.transform { Transform::Normal | Transform::_180 | Transform::Flipped | Transform::Flipped180 => (width, height), Transform::_90 | Transform::_270 | Transform::Flipped90 | Transform::Flipped270 => (height, width), _ => { warn!("New output {} has unsupported transform", output_name); (width, height) } } }; let integer_scale_factor = info.scale_factor; let Some((logical_width, logical_height)) = info.logical_size else { error!("New output {} has no logical_size, skipping", output_name); return }; if !logical_width.is_positive() || !logical_height.is_positive() { error!("New output {} has non-positive logical size: {} x {}, \ skipping", output_name, logical_width, logical_height); return } debug!("New output, name: {}, resolution: {}x{}, integer scale \ factor: {}, logical size: {}x{}, transform: {:?}", output_name, width, height, integer_scale_factor, logical_width, logical_height, info.transform); let layer = self.layer_shell.create_layer_surface( qh, self.compositor_state.create_surface(qh), Layer::Background, layer_surface_name(&output_name), Some(&output) ); layer.set_anchor( Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT ); layer.set_exclusive_zone(-1); // Don't let the status bar push it around layer.set_keyboard_interactivity(KeyboardInteractivity::None); let surface = layer.wl_surface(); // Disable receiving pointer, touch, and tablet events // by setting an empty input region. // This prevents disappearing or hidden cursor when a normal window // closes below the pointer leaving it above our surface match Region::new(&self.compositor_state) { Ok(region) => surface.set_input_region(Some(region.wl_region())), Err(error) => error!( "Failed to create empty input region, on new output {}: {}", output_name, error ) }; let mut viewport = None; if width == logical_width || height == logical_height { debug!("Output {} needs no scaling", output_name); } else if width == logical_width * integer_scale_factor && height == logical_height * integer_scale_factor { debug!("Output {} needs integer scaling", output_name); surface.set_buffer_scale(integer_scale_factor); } else { debug!("Output {} needs fractional scaling", output_name); let new_viewport = self.viewporter.get_viewport(surface, qh, ()); new_viewport.set_destination(logical_width, logical_height); viewport = Some(new_viewport); } layer.commit(); let mut dmabuf_feedback = None; let mut gpu_uploader = None; if let Some(gpu) = self.gpu.as_mut() { if self.dmabuf_state.version().unwrap() >= 4 { match self.dmabuf_state.get_surface_feedback(surface, qh) { Ok(feedback) => { debug!("Requesting Linux DMA-BUF surface feedback \ for output {}", output_name); dmabuf_feedback = Some(feedback); }, Err(e) => { error!("Failed to request Linux DMA-BUF surface \ feedback for the surface on output {}: {}", output_name, e); }, } } else { let drm_format_modifiers = self.dmabuf_state.modifiers().iter() .filter(|dmabuf_format| dmabuf_format.format == DRM_FORMAT_XRGB8888 ) .map(|dmabuf_format| dmabuf_format.modifier) .collect::>(); match gpu.uploader( None, width as u32, height as u32, drm_format_modifiers, ) { Ok(uploader) => gpu_uploader = Some(uploader), Err(e) => error!("Failed to obtain GPU uploader: {e:#}"), }; } } let is_dmabuf_feedback = dmabuf_feedback.is_some(); let bg_layer_index = self.background_layers.len(); self.background_layers.push(BackgroundLayer { output_name, width, height, layer, configured: false, workspace_backgrounds: Vec::new(), current_wallpaper: None, queued_wallpaper: None, transform: info.transform, viewport, dmabuf_feedback, }); if !is_dmabuf_feedback { load_wallpapers(self, qh, bg_layer_index, gpu_uploader); } } fn update_output( &mut self, _conn: &Connection, qh: &QueueHandle, output: WlOutput, ) { let Some(info) = self.output_state.info(&output) else { error!("Updated output has no output info, skipping"); return }; let Some(output_name) = info.name else { error!("Updated output has no name, skipping"); return }; let Some((width, height)) = info.modes.iter() .find(|mode| mode.current) .map(|mode| mode.dimensions) else { error!("Updated output {} has no current mode set, skipping", output_name); return }; if !width.is_positive() || !height.is_positive() { error!("Updated output {} has non-positive resolution: {} x {}, \ skipping", output_name, width, height); return } let (width, height) = { match info.transform { Transform::Normal | Transform::_180 | Transform::Flipped | Transform::Flipped180 => (width, height), Transform::_90 | Transform::_270 | Transform::Flipped90 | Transform::Flipped270 => (height, width), _ => { warn!("Updated output {} has unsupported transform", output_name); (width, height) } } }; let integer_scale_factor = info.scale_factor; let Some((logical_width, logical_height)) = info.logical_size else { error!("Updated output {} has no logical_size, skipping", output_name); return }; if !logical_width.is_positive() || !logical_height.is_positive() { error!("Updated output {} has non-positive logical size: {} x {}, \ skipping", output_name, logical_width, logical_height); return } debug!("Updated output, name: {}, resolution: {}x{}, integer scale \ factor: {}, logical size: {}x{}, transform: {:?}", output_name, width, height, integer_scale_factor, logical_width, logical_height, info.transform); let Some(bg_layer) = self.background_layers.iter_mut() .find(|bg_layers| bg_layers.output_name == output_name) else { error!("Updated output {} has no background layer, skipping", output_name); return }; if bg_layer.width != width || bg_layer.height != height { warn!("Handling of output mode or transform changes are not yet \ implemented. Restart multibg-sway or expect broken wallpapers \ or low quality due to scaling"); } let surface = bg_layer.layer.wl_surface(); if width == logical_width || height == logical_height { debug!("Output {} needs no scaling", output_name); surface.set_buffer_scale(1); if let Some(old_viewport) = bg_layer.viewport.take() { old_viewport.destroy(); }; } else if width == logical_width * integer_scale_factor && height == logical_height * integer_scale_factor { debug!("Output {} needs integer scaling", output_name); surface.set_buffer_scale(integer_scale_factor); if let Some(old_viewport) = bg_layer.viewport.take() { old_viewport.destroy(); }; } else { debug!("Output {} needs fractional scaling", output_name); surface.set_buffer_scale(1); bg_layer.viewport .get_or_insert_with(|| self.viewporter.get_viewport(surface, qh, ()) ) .set_destination(logical_width, logical_height); } surface.commit(); } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, output: WlOutput, ) { let Some(info) = self.output_state.info(&output) else { error!("Destroyed output has no output info, skipping"); return }; let Some(output_name) = info.name else { error!("Destroyed output has no name, skipping"); return }; debug!("Output destroyed: {}", output_name); if let Some(bg_layer_index) = self.background_layers.iter() .position(|bg_layers| bg_layers.output_name == output_name) { let removed_bg_layer = self.background_layers .swap_remove(bg_layer_index); // Workspaces on the destroyed output may have been moved anywhere // so reset the wallpaper on all the visible workspaces self.compositor_connection_task.request_visible_workspaces(); debug!( "Dropping {} wallpapers on destroyed output for workspaces: {}", removed_bg_layer.workspace_backgrounds.len(), removed_bg_layer.workspace_backgrounds.iter() .map(|workspace_bg| workspace_bg.workspace_name.as_str()) .collect::>().join(", ") ); drop(removed_bg_layer); } else { error!( "Ignoring destroyed output with unknown name '{}', \ known outputs were: {}", output_name, self.background_layers.iter() .map(|bg_layer| bg_layer.output_name.as_str()) .collect::>().join(", ") ); } print_memory_stats(&self.background_layers); } } impl ProvidesRegistryState for State { registry_handlers![OutputState]; fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } } impl ShmHandler for State { fn shm_state(&mut self) -> &mut Shm { &mut self.shm } } delegate_compositor!(State); delegate_dmabuf!(State); delegate_layer!(State); delegate_output!(State); delegate_registry!(State); delegate_shm!(State); impl Dispatch for State { fn event( _state: &mut Self, _proxy: &WpViewporter, _event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { unreachable!("wp_viewporter has no events"); } } impl Dispatch for State { fn event( _state: &mut Self, _proxy: &WpViewport, _event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { unreachable!("wp_viewport has no events"); } } impl Dispatch for State { fn event( state: &mut Self, proxy: &WlBuffer, _event: ::Event, _data: &(), _conn: &Connection, _qhandle: &QueueHandle, ) { for bg in state.background_layers.iter_mut() .flat_map(|bg_layer| &mut bg_layer.workspace_backgrounds) { let mut wallpaper = bg.wallpaper.borrow_mut(); if wallpaper.wl_buffer.as_ref() == Some(proxy) { if let Some(new_count) = wallpaper.active_count.checked_sub(1) { debug!("Compositor released the wl_shm wl_buffer of {:?}", wallpaper.canon_path); wallpaper.active_count = new_count; } else { error!("Unexpected release event for the wl_shm \ wl_buffer of {:?}", wallpaper.canon_path); } return } } warn!("Release event for already destroyed wl_shm wl_buffer"); } } pub struct BackgroundLayer { pub output_name: String, pub width: i32, pub height: i32, pub layer: LayerSurface, pub configured: bool, pub workspace_backgrounds: Vec, pub current_wallpaper: Option>>, pub queued_wallpaper: Option>>, pub transform: Transform, pub viewport: Option, pub dmabuf_feedback: Option, } impl BackgroundLayer { pub fn draw_workspace_bg(&mut self, workspace_name: &str) { if !self.configured { error!("Cannot draw wallpaper image on the not yet configured \ layer for output: {}", self.output_name); return } let Some(workspace_bg) = self.workspace_backgrounds.iter() .find(|workspace_bg| workspace_bg.workspace_name == workspace_name) .or_else(|| self.workspace_backgrounds.iter() .find(|workspace_bg| workspace_bg.workspace_name == "_default") ) else { error!( "There is no wallpaper image on output {} for workspace {}, \ only for: {}", self.output_name, workspace_name, self.workspace_backgrounds.iter() .map(|workspace_bg| workspace_bg.workspace_name.as_str()) .collect::>().join(", ") ); return }; let wallpaper = &workspace_bg.wallpaper; if let Some(current) = &self.current_wallpaper { if Rc::ptr_eq(current, wallpaper) { debug!("Skipping draw on output {} for workspace {} \ because its wallpaper is already set", self.output_name, workspace_name); return } } let mut wallpaper_borrow = wallpaper.borrow_mut(); let Some(wl_buffer) = wallpaper_borrow.wl_buffer.as_ref() else { debug!("Wallpaper for output {} workspace {} is not ready yet", self.output_name, workspace_name); self.queued_wallpaper = Some(Rc::downgrade(wallpaper)); return }; // Attach and commit to new workspace background self.layer.attach(Some(wl_buffer), 0, 0); wallpaper_borrow.active_count += 1; // Damage the entire surface self.layer.wl_surface().damage_buffer(0, 0, self.width, self.height); self.layer.commit(); self.current_wallpaper = Some(Rc::clone(wallpaper)); self.queued_wallpaper = None; debug!("Setting wallpaper on output {} for workspace: {}", self.output_name, workspace_name); } } pub struct WorkspaceBackground { pub workspace_name: String, pub wallpaper: Rc>, } pub struct Wallpaper { pub wl_buffer: Option, pub active_count: usize, pub memory: Memory, pub canon_path: PathBuf, pub canon_modified: u128, } impl Drop for Wallpaper { fn drop(&mut self) { if let Some(wl_buffer) = &self.wl_buffer { if self.active_count != 0 { warn!("Destroying a {} times active wl_buffer of \ wallpaper {:?}", self.active_count, self.canon_path); } wl_buffer.destroy(); } } } pub enum Memory { WlShm { pool: RawPool }, Dmabuf { gpu_memory: GpuMemory, params: Option }, } impl Memory { fn gpu_uploader_eq(&self, gpu_uploader: Option<&GpuUploader>) -> bool { if let Some(gpu_uploader) = gpu_uploader { match self { Memory::WlShm { .. } => false, Memory::Dmabuf { gpu_memory, .. } => { gpu_memory.gpu_uploader_eq(gpu_uploader) }, } } else { match self { Memory::WlShm { .. } => true, Memory::Dmabuf { .. } => false, } } } fn dmabuf_params_destroy_eq( &mut self, other_params: &ZwpLinuxBufferParamsV1, ) -> bool { if let Memory::Dmabuf { params: params_option, .. } = self { if let Some(params) = params_option { if params == other_params { params.destroy(); *params_option = None; return true } } } false } } fn layer_surface_name(output_name: &str) -> Option { Some([env!("CARGO_PKG_NAME"), "_wallpaper_", output_name].concat()) } fn find_equal_wallpaper( background_layers: &[BackgroundLayer], width: i32, height: i32, transform: Transform, wallpaper_file: &WallpaperFile, gpu_uploader: Option<&GpuUploader>, ) -> Option>> { for bg_layer in background_layers { if bg_layer.width == width && bg_layer.height == height && bg_layer.transform == transform { for bg in &bg_layer.workspace_backgrounds { let wallpaper = bg.wallpaper.borrow(); if wallpaper.canon_modified == wallpaper_file.canon_modified && wallpaper.canon_path == wallpaper_file.canon_path && wallpaper.memory.gpu_uploader_eq(gpu_uploader) { debug!("Reusing the wallpaper of output {} workspace {}", bg_layer.output_name, bg.workspace_name); return Some(Rc::clone(&bg.wallpaper)); } } } } None } fn find_equal_output_wallpaper( workspace_backgrounds: &[WorkspaceBackground], wallpaper_file: &WallpaperFile, gpu_uploader: Option<&GpuUploader>, ) -> Option>> { for bg in workspace_backgrounds { let wallpaper = bg.wallpaper.borrow(); if wallpaper.canon_modified == wallpaper_file.canon_modified && wallpaper.canon_path == wallpaper_file.canon_path && wallpaper.memory.gpu_uploader_eq(gpu_uploader) { debug!("Reusing the wallpaper of workspace {}", bg.workspace_name); return Some(Rc::clone(&bg.wallpaper)); } } None } fn print_memory_stats(background_layers: &[BackgroundLayer]) { if log::log_enabled!(log::Level::Debug) { let mut wl_shm_count = 0.0f32; let mut wl_shm_size = 0.0f32; let mut dmabuf_count = 0.0f32; let mut dmabuf_size = 0.0f32; for bg_layer in background_layers { for bg in &bg_layer.workspace_backgrounds { let factor = 1.0 / Rc::strong_count(&bg.wallpaper) as f32; match &bg.wallpaper.borrow().memory { Memory::WlShm { pool } => { wl_shm_count += factor; wl_shm_size += factor * pool.len() as f32; }, Memory::Dmabuf { gpu_memory, .. } => { dmabuf_count += factor; dmabuf_size += factor * gpu_memory.size() as f32; }, } } } let wl_shm_count = (wl_shm_count + 0.5) as usize; let wl_shm_size_kb = (wl_shm_size + 0.5) as usize / 1024; let dmabuf_count = (dmabuf_count + 0.5) as usize; let dmabuf_size_kb = (dmabuf_size + 0.5) as usize / 1024; debug!("Memory use: {wl_shm_size_kb} KiB from {wl_shm_count} wl_shm \ pools, {dmabuf_size_kb} KiB from {dmabuf_count} DMA-BUFs"); } } fn fallback_shm_load_wallpapers( state: &mut State, qh: &QueueHandle, bg_layer_index: usize, ) { let bg_layer = &mut state.background_layers[bg_layer_index]; bg_layer.dmabuf_feedback = None; bg_layer.workspace_backgrounds.clear(); load_wallpapers(state, qh, bg_layer_index, None); } fn load_wallpapers( state: &mut State, qh: &QueueHandle, bg_layer_index: usize, mut gpu_uploader: Option, ) { let bg_layer = &state.background_layers[bg_layer_index]; let output_name = bg_layer.output_name.as_str(); let width = bg_layer.width; let height = bg_layer.height; let transform = bg_layer.transform; let output_dir = state.wallpaper_dir.join(output_name); debug!("Looking for wallpapers for new output {} in {:?}", output_name, output_dir); let wallpaper_files = match output_wallpaper_files(&output_dir) { Ok(wallpaper_files) => wallpaper_files, Err(e) => { error!("Failed to get wallpapers for new output {output_name} \ form {output_dir:?}: {e:#}"); return } }; let shm_stride = match state.shm_format { wl_shm::Format::Xrgb8888 => width as usize * 4, wl_shm::Format::Bgr888 => { // Align buffer stride to both 4 and pixel format // block size. Not being aligned to 4 caused // https://github.com/gergo-salyi/multibg-sway/issues/6 (width as usize * 3).next_multiple_of(4) }, _ => unreachable!(), }; let shm_size = shm_stride * height as usize; let mut workspace_backgrounds = Vec::new(); let mut resizer = fast_image_resize::Resizer::new(); let mut reused_count = 0usize; let mut loaded_count = 0usize; let mut error_count = 0usize; for wallpaper_file in wallpaper_files { if log::log_enabled!(log::Level::Debug) { if wallpaper_file.path == wallpaper_file.canon_path { debug!("Wallpaper file {:?} for workspace {}", wallpaper_file.path, wallpaper_file.workspace); } else { debug!("Wallpaper file {:?} -> {:?} for workspace {}", wallpaper_file.path, wallpaper_file.canon_path, wallpaper_file.workspace); } } if let Some(wallpaper) = find_equal_output_wallpaper( &workspace_backgrounds, &wallpaper_file, gpu_uploader.as_ref(), ) { workspace_backgrounds.push(WorkspaceBackground { workspace_name: wallpaper_file.workspace, wallpaper, }); reused_count += 1; continue } if let Some(wallpaper) = find_equal_wallpaper( &state.background_layers, width, height, transform, &wallpaper_file, gpu_uploader.as_ref(), ) { workspace_backgrounds.push(WorkspaceBackground { workspace_name: wallpaper_file.workspace, wallpaper, }); reused_count += 1; continue } if let Some(uploader) = gpu_uploader.as_mut() { if let Err(e) = load_wallpaper( &wallpaper_file.path, uploader.staging_buffer(), width as u32, height as u32, width as usize * 4, wl_shm::Format::Xrgb8888, state.color_transform, &mut resizer, ) { error!("Failed to load wallpaper: {e:#}"); error_count += 1; continue } match uploader.upload() { Ok(gpu_wallpaper) => { let wallpaper = wallpaper_dmabuf( &state.dmabuf_state, qh, gpu_wallpaper, width, height, wallpaper_file.canon_path, wallpaper_file.canon_modified, ); workspace_backgrounds.push(WorkspaceBackground { workspace_name: wallpaper_file.workspace, wallpaper, }); loaded_count += 1; continue }, Err(e) => { error!("Failed to upload wallpaper to GPU: {e:#}"); gpu_uploader = None; // fall back to shm } } } let mut shm_pool = match RawPool::new(shm_size, &state.shm) { Ok(shm_pool) => shm_pool, Err(e) => { error!("Failed to create shm pool: {e}"); error_count += 1; continue } }; if let Err(e) = load_wallpaper( &wallpaper_file.path, shm_pool.mmap(), width as u32, height as u32, shm_stride, state.shm_format, state.color_transform, &mut resizer, ) { error!("Failed to load wallpaper: {e:#}"); error_count += 1; continue } let wl_buffer = shm_pool.create_buffer( 0, width, height, shm_stride.try_into().unwrap(), state.shm_format, (), qh, ); workspace_backgrounds.push(WorkspaceBackground { workspace_name: wallpaper_file.workspace, wallpaper: Rc::new(RefCell::new(Wallpaper { wl_buffer: Some(wl_buffer), active_count: 0, memory: Memory::WlShm { pool: shm_pool }, canon_path: wallpaper_file.canon_path, canon_modified: wallpaper_file.canon_modified, })), }); loaded_count += 1; } debug!("Wallpapers for new output: {} reused, {} loaded, {} errors", reused_count, loaded_count, error_count); debug!("Wallpapers are available for workspaces: {}", workspace_backgrounds.iter() .map(|bg| bg.workspace_name.as_str()) .collect::>().join(", ")); state.background_layers[bg_layer_index].workspace_backgrounds = workspace_backgrounds; print_memory_stats(&state.background_layers); } fn handle_dmabuf_feedback( state: &mut State, qh: &QueueHandle, feedback: DmabufFeedback, bg_layer_pos: usize, ) -> anyhow::Result<()> { let bg_layer = &mut state.background_layers[bg_layer_pos]; let main_dev = feedback.main_device(); let format_table = feedback.format_table(); let tranches = feedback.tranches(); debug!("Linux DMA-BUF feedback for output {}, main device {}:{}, \ {} format table entries, {} tranches", &bg_layer.output_name, major(main_dev), minor(main_dev), format_table.len(), tranches.len()); if tranches.is_empty() { bail!("Linux DMA-BUF feedback has 0 tranches"); } let mut selected = None; for (index, tranche) in tranches.iter().enumerate() { let target_dev = tranche.device; debug!("Tranche {index} target device {}:{}", major(target_dev), minor(target_dev)); if selected.is_none() && target_dev == main_dev { selected = Some((index, tranche.formats.as_slice())); } } let Some((index, formats)) = selected else { bail!("No tranche has the main device as target device"); }; debug!("Selected tranche {}, it has {} dmabuf formats", index, formats.len()); let mut drm_format_modifiers = Vec::new(); for index in formats { let Some(dmabuf_format) = format_table.get(*index as usize) else { error!("Format index {index} is out of bounds"); continue }; if dmabuf_format.format == DRM_FORMAT_XRGB8888 { drm_format_modifiers.push(dmabuf_format.modifier); } } if drm_format_modifiers.is_empty() { bail!("Selected tranche has no modifiers for DRM_FORMAT_XRGB8888"); } debug!("Modifiers for DRM_FORMAT_XRGB8888: {}", drm_format_modifiers.iter() .map(|&modifier| fmt_modifier(modifier)) .collect::>().join(", ")); let dmabuf_drm_dev = Some(main_dev as Dev); if !bg_layer.workspace_backgrounds.is_empty() && bg_layer.workspace_backgrounds.iter().all(|bg| { let memory = &bg.wallpaper.borrow().memory; if let Memory::Dmabuf { gpu_memory, .. } = memory { gpu_memory.dmabuf_feedback_eq( dmabuf_drm_dev, &drm_format_modifiers ) } else { false } }) { debug!("Ignoring DMA-BUF feedback with no changes"); return Ok(()) } let gpu_uploader = state.gpu.as_mut().unwrap().uploader( dmabuf_drm_dev, bg_layer.width as u32, bg_layer.height as u32, drm_format_modifiers ).context("Failed to create GPU uploader")?; if !bg_layer.workspace_backgrounds.is_empty() { debug!("DMA-BUF feedback changed, reloading wallpapers"); bg_layer.workspace_backgrounds.clear(); } load_wallpapers(state, qh, bg_layer_pos, Some(gpu_uploader)); Ok(()) } fn wallpaper_dmabuf( dmabuf_state: &DmabufState, qh: &QueueHandle, gpu_wallpaper: GpuWallpaper, width: i32, height: i32, canon_path: PathBuf, canon_modified: u128, ) -> Rc> { let GpuWallpaper { drm_format_modifier, memory_planes_len, memory_planes, gpu_memory, fd, } = gpu_wallpaper; let dmabuf_params = dmabuf_state.create_params(qh).unwrap(); #[allow(clippy::needless_range_loop)] for memory_plane_index in 0..memory_planes_len { dmabuf_params.add( fd.as_fd(), memory_plane_index as u32, memory_planes[memory_plane_index].offset as u32, memory_planes[memory_plane_index].stride as u32, drm_format_modifier, ); } let params = dmabuf_params.create( width, height, DRM_FORMAT_XRGB8888, zwp_linux_buffer_params_v1::Flags::empty(), ); Rc::new(RefCell::new(Wallpaper { wl_buffer: None, active_count: 0, memory: Memory::Dmabuf { gpu_memory, params: Some(params) }, canon_path, canon_modified, })) }