multibg-wayland/src/wayland.rs
2025-04-23 15:21:51 +02:00

1191 lines
40 KiB
Rust

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<Self>,
_surface: &WlSurface,
_new_factor: i32,
) {
}
fn frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &WlSurface,
_time: u32,
) {
}
fn transform_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &WlSurface,
_new_transform: wl_output::Transform,
) {
}
fn surface_enter(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &WlSurface,
_output: &wl_output::WlOutput,
) {
}
fn surface_leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_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<Self>,
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<Self>,
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<Self>,
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<Self>,
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<Self>,
_layer: &LayerSurface
) {
}
fn configure(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
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<Self>,
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::<Vec<_>>();
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<Self>,
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<Self>,
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::<Vec<_>>().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::<Vec<_>>().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<WpViewporter, ()> for State {
fn event(
_state: &mut Self,
_proxy: &WpViewporter,
_event: <WpViewporter as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
unreachable!("wp_viewporter has no events");
}
}
impl Dispatch<WpViewport, ()> for State {
fn event(
_state: &mut Self,
_proxy: &WpViewport,
_event: <WpViewport as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
unreachable!("wp_viewport has no events");
}
}
impl Dispatch<WlBuffer, ()> for State {
fn event(
state: &mut Self,
proxy: &WlBuffer,
_event: <WlBuffer as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
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<WorkspaceBackground>,
pub current_wallpaper: Option<Rc<RefCell<Wallpaper>>>,
pub queued_wallpaper: Option<Weak<RefCell<Wallpaper>>>,
pub transform: Transform,
pub viewport: Option<WpViewport>,
pub dmabuf_feedback: Option<ZwpLinuxDmabufFeedbackV1>,
}
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::<Vec<_>>().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<RefCell<Wallpaper>>,
}
pub struct Wallpaper {
pub wl_buffer: Option<WlBuffer>,
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<ZwpLinuxBufferParamsV1> },
}
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<String> {
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<Rc<RefCell<Wallpaper>>> {
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<Rc<RefCell<Wallpaper>>> {
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<State>,
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<State>,
bg_layer_index: usize,
mut gpu_uploader: Option<GpuUploader>,
) {
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::<Vec<_>>().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<State>,
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::<Vec<_>>().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<State>,
gpu_wallpaper: GpuWallpaper,
width: i32,
height: i32,
canon_path: PathBuf,
canon_modified: u128,
) -> Rc<RefCell<Wallpaper>> {
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,
}))
}