From 55ec6dc7cbae78a44fa8432a5f7658ae43275a0e Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sat, 27 Nov 2021 20:10:41 +0100 Subject: [PATCH 01/52] output: Add userdata to wayland output object Anvil's output map has a wrapper type `Output`, that largely stores information of the wayland `Output` type, that is just not accessible. Lets change that and make it possible to associate userdata with the output to remove the need to use another wrapper type. --- CHANGELOG.md | 1 + src/wayland/output/mod.rs | 82 ++++++++++++++++++++++++++++----------- src/wayland/output/xdg.rs | 2 +- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 682fe98..1b67e92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - Add support for the zxdg-foreign-v2 protocol. - Support for `xdg_wm_base` protocol version 3 - Added the option to initialize the dmabuf global with a client filter +- `wayland::output::Output` now has user data attached to it and more functions to query its properties #### Backends diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs index 1909ab7..236205a 100644 --- a/src/wayland/output/mod.rs +++ b/src/wayland/output/mod.rs @@ -61,7 +61,7 @@ use wayland_server::protocol::{ }; use wayland_server::{ protocol::wl_output::{Mode as WMode, WlOutput}, - Client, Display, Filter, Global, Main, + Client, Display, Filter, Global, Main, UserDataMap, }; use slog::{info, o, trace, warn}; @@ -115,6 +115,8 @@ struct Inner { xdg_output: Option, } +type InnerType = Arc<(Mutex, UserDataMap)>; + impl Inner { fn new_global(&mut self, output: WlOutput) { trace!(self.log, "New global instantiated."); @@ -166,9 +168,9 @@ impl Inner { /// /// This handle is stored in the event loop, and allows you to notify clients /// about any change in the properties of this output. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Output { - inner: Arc>, + inner: InnerType, } impl Output { @@ -190,19 +192,22 @@ impl Output { info!(log, "Creating new wl_output"; "name" => &name); - let inner = Arc::new(Mutex::new(Inner { - name, - log, - instances: Vec::new(), - physical, - location: (0, 0).into(), - transform: Transform::Normal, - scale: 1, - modes: Vec::new(), - current_mode: None, - preferred_mode: None, - xdg_output: None, - })); + let inner = Arc::new(( + Mutex::new(Inner { + name, + log, + instances: Vec::new(), + physical, + location: (0, 0).into(), + transform: Transform::Normal, + scale: 1, + modes: Vec::new(), + current_mode: None, + preferred_mode: None, + xdg_output: None, + }), + UserDataMap::default(), + )); let output = Output { inner: inner.clone() }; @@ -210,8 +215,9 @@ impl Output { 3, Filter::new(move |(output, _version): (Main, _), _, _| { output.assign_destructor(Filter::new(|output: WlOutput, _, _| { - let inner = output.as_ref().user_data().get::>>().unwrap(); + let inner = output.as_ref().user_data().get::().unwrap(); inner + .0 .lock() .unwrap() .instances @@ -221,7 +227,7 @@ impl Output { let inner = inner.clone(); move || inner }); - inner.lock().unwrap().new_global(output.deref().clone()); + inner.0.lock().unwrap().new_global(output.deref().clone()); }), ); @@ -233,7 +239,7 @@ impl Output { output .as_ref() .user_data() - .get::>>() + .get::() .cloned() .map(|inner| Output { inner }) } @@ -243,7 +249,7 @@ impl Output { /// If the provided mode was not previously known to this output, it is added to its /// internal list. pub fn set_preferred(&self, mode: Mode) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.0.lock().unwrap(); inner.preferred_mode = Some(mode); if inner.modes.iter().all(|&m| m != mode) { inner.modes.push(mode); @@ -252,18 +258,33 @@ impl Output { /// Adds a mode to the list of known modes to this output pub fn add_mode(&self, mode: Mode) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.0.lock().unwrap(); if inner.modes.iter().all(|&m| m != mode) { inner.modes.push(mode); } } + /// Returns the currently advertised mode of the output + pub fn current_mode(&self) -> Option { + self.inner.0.lock().unwrap().current_mode + } + + /// Returns the currently advertised transformation of the output + pub fn current_transform(&self) -> Transform { + self.inner.0.lock().unwrap().transform + } + + /// Returns the name of the output + pub fn name(&self) -> String { + self.inner.0.lock().unwrap().name.clone() + } + /// Removes a mode from the list of known modes /// /// It will not de-advertise it from existing clients (the protocol does not /// allow it), but it won't be advertised to now clients from now on. pub fn delete_mode(&self, mode: Mode) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.0.lock().unwrap(); inner.modes.retain(|&m| m != mode); if inner.current_mode == Some(mode) { inner.current_mode = None; @@ -289,7 +310,7 @@ impl Output { new_scale: Option, new_location: Option>, ) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.0.lock().unwrap(); if let Some(mode) = new_mode { if inner.modes.iter().all(|&m| m != mode) { inner.modes.push(mode); @@ -337,6 +358,7 @@ impl Output { /// Check is given [`wl_output`](WlOutput) instance is managed by this [`Output`]. pub fn owns(&self, output: &WlOutput) -> bool { self.inner + .0 .lock() .unwrap() .instances @@ -351,6 +373,7 @@ impl Output { F: FnMut(&WlOutput), { self.inner + .0 .lock() .unwrap() .instances @@ -377,4 +400,17 @@ impl Output { self.with_client_outputs(client, |output| surface.leave(output)) } } + + /// Returns the user data of this output + pub fn user_data(&self) -> &UserDataMap { + &self.inner.1 + } } + +impl PartialEq for Output { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.inner, &other.inner) + } +} + +impl Eq for Output {} diff --git a/src/wayland/output/xdg.rs b/src/wayland/output/xdg.rs index 6ed1cee..810ac91 100644 --- a/src/wayland/output/xdg.rs +++ b/src/wayland/output/xdg.rs @@ -159,7 +159,7 @@ where output: wl_output, } => { let output = Output::from_resource(&wl_output).unwrap(); - let mut inner = output.inner.lock().unwrap(); + let mut inner = output.inner.0.lock().unwrap(); if inner.xdg_output.is_none() { inner.xdg_output = Some(XdgOutput::new(&inner, log.clone())); From 57f45d99419358ee35734b31037de494a4c944cc Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sat, 27 Nov 2021 20:17:18 +0100 Subject: [PATCH 02/52] renderer: Allow clear calls on specific regions --- CHANGELOG.md | 1 + anvil/src/render.rs | 2 +- anvil/src/udev.rs | 2 +- src/backend/renderer/gles2/mod.rs | 9 ++++++++- src/backend/renderer/mod.rs | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b67e92..81eda76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - `X11Surface::buffer` now additionally returns the age of the buffer - `X11Surface` now has an explicit `submit` function - `X11Surface` is now multi-window capable. +- `Renderer::clear` now expects a second argument to optionally only clear parts of the buffer/surface ### Additions diff --git a/anvil/src/render.rs b/anvil/src/render.rs index 4848c45..0d26b30 100644 --- a/anvil/src/render.rs +++ b/anvil/src/render.rs @@ -24,7 +24,7 @@ pub fn render_layers_and_windows( output_scale: f32, logger: &Logger, ) -> Result<(), SwapBuffersError> { - frame.clear([0.8, 0.8, 0.9, 1.0])?; + frame.clear([0.8, 0.8, 0.9, 1.0], None)?; for layer in [Layer::Background, Layer::Bottom] { draw_layers( diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 957a419..f5a63bc 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -873,7 +873,7 @@ fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) -> renderer .render((1, 1).into(), Transform::Normal, |_, frame| { frame - .clear([0.8, 0.8, 0.9, 1.0]) + .clear([0.8, 0.8, 0.9, 1.0], None) .map_err(Into::::into) }) .map_err(Into::::into) diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 3a8c49b..5c15b3a 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -1121,10 +1121,17 @@ impl Frame for Gles2Frame { type Error = Gles2Error; type TextureId = Gles2Texture; - fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error> { + fn clear(&mut self, color: [f32; 4], at: Option>) -> Result<(), Self::Error> { unsafe { + if let Some(rect) = at { + self.gl.Enable(ffi::SCISSOR_TEST); + self.gl.Scissor(rect.loc.x, rect.loc.y, rect.size.w, rect.size.h); + } self.gl.ClearColor(color[0], color[1], color[2], color[3]); self.gl.Clear(ffi::COLOR_BUFFER_BIT); + if at.is_some() { + self.gl.Disable(ffi::SCISSOR_TEST); + } } Ok(()) diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index a975894..f2c8ee6 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -171,7 +171,7 @@ pub trait Frame { /// /// This operation is only valid in between a `begin` and `finish`-call. /// If called outside this operation may error-out, do nothing or modify future rendering results in any way. - fn clear(&mut self, color: [f32; 4]) -> Result<(), Self::Error>; + fn clear(&mut self, color: [f32; 4], at: Option>) -> Result<(), Self::Error>; /// Render a texture to the current target as a flat 2d-plane at a given /// position and applying the given transformation with the given alpha value. From eccdd5221cff08aca8628319232db8e786dea277 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sat, 27 Nov 2021 21:13:33 +0100 Subject: [PATCH 03/52] renderer: Make Transform::transform_size use a Size instead of u32 --- CHANGELOG.md | 1 + src/backend/renderer/mod.rs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81eda76..7feaa88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - `X11Surface` now has an explicit `submit` function - `X11Surface` is now multi-window capable. - `Renderer::clear` now expects a second argument to optionally only clear parts of the buffer/surface +- `Transform::transform_size` now takes a `Size` instead of two `u32` ### Additions diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index f2c8ee6..2711e22 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -10,7 +10,7 @@ use std::collections::HashSet; use std::error::Error; -use crate::utils::{Buffer, Physical, Point, Rectangle, Size}; +use crate::utils::{Buffer, Coordinate, Physical, Point, Rectangle, Size}; #[cfg(feature = "wayland_frontend")] use crate::wayland::compositor::SurfaceData; @@ -94,15 +94,15 @@ impl Transform { } /// Transformed size after applying this transformation. - pub fn transform_size(&self, width: u32, height: u32) -> (u32, u32) { + pub fn transform_size(&self, size: Size) -> Size { if *self == Transform::_90 || *self == Transform::_270 || *self == Transform::Flipped90 || *self == Transform::Flipped270 { - (height, width) + (size.h, size.w).into() } else { - (width, height) + size } } } From 76787fb7df1ae9e872cc3bf9a665278116fb9a94 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sat, 27 Nov 2021 21:48:01 +0100 Subject: [PATCH 04/52] renderer: Account for OpenGLs coordinate system in the Gles2Renderer --- CHANGELOG.md | 1 + anvil/src/udev.rs | 137 ++++++++++++++---------------- anvil/src/x11.rs | 109 ++++++++++++------------ src/backend/renderer/gles2/mod.rs | 5 +- src/backend/winit/mod.rs | 3 +- 5 files changed, 122 insertions(+), 133 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7feaa88..110b268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - `X11Surface` is now multi-window capable. - `Renderer::clear` now expects a second argument to optionally only clear parts of the buffer/surface - `Transform::transform_size` now takes a `Size` instead of two `u32` +- `Gles2Renderer` now automatically flips the `render` result to account for OpenGLs coordinate system ### Additions diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index f5a63bc..a41c478 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -743,53 +743,19 @@ fn render_surface( // and draw to our buffer match renderer - .render( - mode.size, - Transform::Flipped180, // Scanout is rotated - |renderer, frame| { - render_layers_and_windows( - renderer, - frame, - window_map, - output_geometry, - output_scale, - logger, - )?; + .render(mode.size, Transform::Normal, |renderer, frame| { + render_layers_and_windows(renderer, frame, window_map, output_geometry, output_scale, logger)?; - // set cursor - if output_geometry.to_f64().contains(pointer_location) { - let (ptr_x, ptr_y) = pointer_location.into(); - let relative_ptr_location = - Point::::from((ptr_x as i32, ptr_y as i32)) - output_geometry.loc; - // draw the dnd icon if applicable - { - if let Some(ref wl_surface) = dnd_icon.as_ref() { - if wl_surface.as_ref().is_alive() { - draw_dnd_icon( - renderer, - frame, - wl_surface, - relative_ptr_location, - output_scale, - logger, - )?; - } - } - } - - // draw the cursor as relevant - { - // reset the cursor if the surface is no longer alive - let mut reset = false; - if let CursorImageStatus::Image(ref surface) = *cursor_status { - reset = !surface.as_ref().is_alive(); - } - if reset { - *cursor_status = CursorImageStatus::Default; - } - - if let CursorImageStatus::Image(ref wl_surface) = *cursor_status { - draw_cursor( + // set cursor + if output_geometry.to_f64().contains(pointer_location) { + let (ptr_x, ptr_y) = pointer_location.into(); + let relative_ptr_location = + Point::::from((ptr_x as i32, ptr_y as i32)) - output_geometry.loc; + // draw the dnd icon if applicable + { + if let Some(ref wl_surface) = dnd_icon.as_ref() { + if wl_surface.as_ref().is_alive() { + draw_dnd_icon( renderer, frame, wl_surface, @@ -797,38 +763,61 @@ fn render_surface( output_scale, logger, )?; - } else { - frame.render_texture_at( - pointer_image, - relative_ptr_location - .to_f64() - .to_physical(output_scale as f64) - .to_i32_round(), - 1, - output_scale as f64, - Transform::Normal, - 1.0, - )?; } } - - #[cfg(feature = "debug")] - { - draw_fps( - renderer, - frame, - fps_texture, - output_scale as f64, - surface.fps.avg().round() as u32, - )?; - - surface.fps.tick(); - } } - Ok(()) - }, - ) + // draw the cursor as relevant + { + // reset the cursor if the surface is no longer alive + let mut reset = false; + if let CursorImageStatus::Image(ref surface) = *cursor_status { + reset = !surface.as_ref().is_alive(); + } + if reset { + *cursor_status = CursorImageStatus::Default; + } + + if let CursorImageStatus::Image(ref wl_surface) = *cursor_status { + draw_cursor( + renderer, + frame, + wl_surface, + relative_ptr_location, + output_scale, + logger, + )?; + } else { + frame.render_texture_at( + pointer_image, + relative_ptr_location + .to_f64() + .to_physical(output_scale as f64) + .to_i32_round(), + 1, + output_scale as f64, + Transform::Normal, + 1.0, + )?; + } + } + + #[cfg(feature = "debug")] + { + draw_fps( + renderer, + frame, + fps_texture, + output_scale as f64, + surface.fps.avg().round() as u32, + )?; + + surface.fps.tick(); + } + } + + Ok(()) + }) .map_err(Into::::into) .and_then(|x| x) .map_err(Into::::into) diff --git a/anvil/src/x11.rs b/anvil/src/x11.rs index 04ec7d0..69766c8 100644 --- a/anvil/src/x11.rs +++ b/anvil/src/x11.rs @@ -239,56 +239,23 @@ pub fn run_x11(log: Logger) { // drawing logic match renderer - // X11 scanout for a Dmabuf is upside down // TODO: Address this issue in renderer. - .render( - backend_data.mode.size, - Transform::Flipped180, - |renderer, frame| { - render_layers_and_windows( - renderer, - frame, - &*window_map, - output_geometry, - output_scale, - &log, - )?; + .render(backend_data.mode.size, Transform::Normal, |renderer, frame| { + render_layers_and_windows( + renderer, + frame, + &*window_map, + output_geometry, + output_scale, + &log, + )?; - // draw the dnd icon if any - { - let guard = dnd_icon.lock().unwrap(); - if let Some(ref surface) = *guard { - if surface.as_ref().is_alive() { - draw_dnd_icon( - renderer, - frame, - surface, - (x as i32, y as i32).into(), - output_scale, - &log, - )?; - } - } - } - - // draw the cursor as relevant - { - let mut guard = cursor_status.lock().unwrap(); - // reset the cursor if the surface is no longer alive - let mut reset = false; - - if let CursorImageStatus::Image(ref surface) = *guard { - reset = !surface.as_ref().is_alive(); - } - - if reset { - *guard = CursorImageStatus::Default; - } - - // draw as relevant - if let CursorImageStatus::Image(ref surface) = *guard { - cursor_visible = false; - draw_cursor( + // draw the dnd icon if any + { + let guard = dnd_icon.lock().unwrap(); + if let Some(ref surface) = *guard { + if surface.as_ref().is_alive() { + draw_dnd_icon( renderer, frame, surface, @@ -296,21 +263,49 @@ pub fn run_x11(log: Logger) { output_scale, &log, )?; - } else { - cursor_visible = true; } } + } - #[cfg(feature = "debug")] - { - use crate::drawing::draw_fps; + // draw the cursor as relevant + { + let mut guard = cursor_status.lock().unwrap(); + // reset the cursor if the surface is no longer alive + let mut reset = false; - draw_fps(renderer, frame, fps_texture, output_scale as f64, fps)?; + if let CursorImageStatus::Image(ref surface) = *guard { + reset = !surface.as_ref().is_alive(); } - Ok(()) - }, - ) + if reset { + *guard = CursorImageStatus::Default; + } + + // draw as relevant + if let CursorImageStatus::Image(ref surface) = *guard { + cursor_visible = false; + draw_cursor( + renderer, + frame, + surface, + (x as i32, y as i32).into(), + output_scale, + &log, + )?; + } else { + cursor_visible = true; + } + } + + #[cfg(feature = "debug")] + { + use crate::drawing::draw_fps; + + draw_fps(renderer, frame, fps_texture, output_scale as f64, fps)?; + } + + Ok(()) + }) .map_err(Into::::into) .and_then(|x| x) .map_err(Into::::into) diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 5c15b3a..762100b 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -1077,11 +1077,14 @@ impl Renderer for Gles2Renderer { renderer[2][0] = -(1.0f32.copysign(renderer[0][0] + renderer[1][0])); renderer[2][1] = -(1.0f32.copysign(renderer[0][1] + renderer[1][1])); + // We account for OpenGLs coordinate system here + let flip180 = Matrix3::new(1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0); + let mut frame = Gles2Frame { gl: self.gl.clone(), programs: self.programs.clone(), // output transformation passed in by the user - current_projection: transform.matrix() * renderer, + current_projection: flip180 * transform.matrix() * renderer, }; let result = rendering(self, &mut frame); diff --git a/src/backend/winit/mod.rs b/src/backend/winit/mod.rs index c534fed..63a7a3f 100644 --- a/src/backend/winit/mod.rs +++ b/src/backend/winit/mod.rs @@ -298,7 +298,8 @@ impl WinitGraphicsBackend { }; self.renderer.bind(self.egl.clone())?; - let result = self.renderer.render(size, Transform::Normal, rendering)?; + // Why is winit falling out of place with the coordinate system? + let result = self.renderer.render(size, Transform::Flipped180, rendering)?; self.egl.swap_buffers()?; self.renderer.unbind()?; Ok(result) From 98906555f54c3d1d93482143a9fc4814a0831fbe Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sun, 28 Nov 2021 01:22:00 +0100 Subject: [PATCH 05/52] desktop: Add new desktop abstractions module Adds a new `Window` and `Space` abstraction to make it easier to handle windows of various shells and their positions in a compositor space. --- Cargo.toml | 4 +- src/desktop/mod.rs | 7 + src/desktop/space.rs | 419 ++++++++++++++++++++++++++++++++++ src/desktop/window.rs | 510 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 5 files changed, 941 insertions(+), 1 deletion(-) create mode 100644 src/desktop/mod.rs create mode 100644 src/desktop/space.rs create mode 100644 src/desktop/window.rs diff --git a/Cargo.toml b/Cargo.toml index e959cd1..6bcd24b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ drm = { version = "0.5.0", optional = true } drm-ffi = { version = "0.2.0", optional = true } gbm = { version = "0.7.0", optional = true, default-features = false, features = ["drm-support"] } input = { version = "0.7", default-features = false, features=["libinput_1_14"], optional = true } +indexmap = { version = "1.7", optional = true } lazy_static = "1" libc = "0.2.103" libseat= { version = "0.1.1", optional = true } @@ -59,7 +60,7 @@ gl_generator = { version = "0.14", optional = true } pkg-config = { version = "0.3.17", optional = true } [features] -default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"] +default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "desktop", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog", "backend_x11"] backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl"] backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"] backend_drm = ["drm", "drm-ffi"] @@ -71,6 +72,7 @@ backend_udev = ["udev", "input/udev"] backend_session_logind = ["dbus", "backend_session", "pkg-config"] backend_session_elogind = ["backend_session_logind"] backend_session_libseat = ["backend_session", "libseat"] +desktop = ["indexmap", "wayland_frontend"] renderer_gl = ["gl_generator", "backend_egl"] use_system_lib = ["wayland_frontend", "wayland-sys", "wayland-server/use_system_lib"] wayland_frontend = ["wayland-server", "wayland-commons", "wayland-protocols", "tempfile"] diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs new file mode 100644 index 0000000..7c885a5 --- /dev/null +++ b/src/desktop/mod.rs @@ -0,0 +1,7 @@ +// TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings +#![allow(missing_docs, clippy::all)] +mod space; +mod window; + +pub use self::space::*; +pub use self::window::*; diff --git a/src/desktop/space.rs b/src/desktop/space.rs new file mode 100644 index 0000000..7365cdf --- /dev/null +++ b/src/desktop/space.rs @@ -0,0 +1,419 @@ +use super::{draw_window, Window}; +use crate::{ + backend::renderer::{Frame, ImportAll, Renderer, Transform}, + utils::{Logical, Point, Rectangle}, + wayland::output::Output, +}; +use indexmap::{IndexMap, IndexSet}; +use std::{ + cell::{RefCell, RefMut}, + collections::{HashMap, HashSet, VecDeque}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Mutex, + }, +}; +use wayland_server::protocol::wl_surface; + +static SPACE_ID: AtomicUsize = AtomicUsize::new(0); +lazy_static::lazy_static! { + static ref SPACE_IDS: Mutex> = Mutex::new(HashSet::new()); +} +fn next_space_id() -> usize { + let mut ids = SPACE_IDS.lock().unwrap(); + if ids.len() == usize::MAX { + // Theoretically the code below wraps around correctly, + // but that is hard to detect and might deadlock. + // Maybe make this a debug_assert instead? + panic!("Out of space ids"); + } + + let mut id = SPACE_ID.fetch_add(1, Ordering::SeqCst); + while ids.iter().any(|k| *k == id) { + id = SPACE_ID.fetch_add(1, Ordering::SeqCst); + } + + ids.insert(id); + id +} + +#[derive(Default)] +struct WindowState { + location: Point, + drawn: bool, +} + +type WindowUserdata = RefCell>; +fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> { + let userdata = w.user_data(); + userdata.insert_if_missing(WindowUserdata::default); + RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { + m.entry(space).or_default() + }) +} + +#[derive(Clone, Default)] +struct OutputState { + location: Point, + render_scale: f64, + // damage and last_state in space coordinate space + old_damage: VecDeque>>, + last_state: IndexMap>, +} + +type OutputUserdata = RefCell>; +fn output_state(space: usize, o: &Output) -> RefMut<'_, OutputState> { + let userdata = o.user_data(); + userdata.insert_if_missing(OutputUserdata::default); + RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { + m.entry(space).or_default() + }) +} + +// TODO: Maybe replace UnmanagedResource if nothing else comes up? +#[derive(Debug, thiserror::Error)] +pub enum SpaceError { + #[error("Window is not mapped to this space")] + UnknownWindow, +} + +#[derive(Debug)] +pub struct Space { + id: usize, + // in z-order, back to front + windows: IndexSet, + outputs: Vec, + // TODO: + //layers: Vec, + logger: ::slog::Logger, +} + +impl Drop for Space { + fn drop(&mut self) { + SPACE_IDS.lock().unwrap().remove(&self.id); + } +} + +impl Space { + pub fn new(log: L) -> Space + where + L: Into, + { + Space { + id: next_space_id(), + windows: IndexSet::new(), + outputs: Vec::new(), + logger: log.into(), + } + } + + /// Map window and moves it to top of the stack + /// + /// This can safely be called on an already mapped window + pub fn map_window(&mut self, window: &Window, location: Point) -> Result<(), SpaceError> { + window_state(self.id, window).location = location; + self.windows.shift_remove(window); + self.windows.insert(window.clone()); + Ok(()) + } + + pub fn raise_window(&mut self, window: &Window) { + let loc = window_state(self.id, window).location; + let _ = self.map_window(window, loc); + + // TODO: should this be handled by us? + window.set_activated(true); + for w in self.windows.iter() { + if w != window { + w.set_activated(false); + } + } + } + + /// Unmap a window from this space by its id + pub fn unmap_window(&mut self, window: &Window) { + if let Some(map) = window.user_data().get::() { + map.borrow_mut().remove(&self.id); + } + self.windows.shift_remove(window); + } + + /// Iterate window in z-order back to front + pub fn windows(&self) -> impl Iterator { + self.windows.iter() + } + + /// Get a reference to the window under a given point, if any + pub fn window_under(&self, point: Point) -> Option<&Window> { + self.windows.iter().find(|w| { + let loc = window_state(self.id, w).location; + let mut bbox = w.bbox(); + bbox.loc += loc; + bbox.to_f64().contains(point) + }) + } + + pub fn window_for_surface(&self, surface: &wl_surface::WlSurface) -> Option<&Window> { + if !surface.as_ref().is_alive() { + return None; + } + + self.windows + .iter() + .find(|w| w.toplevel().get_surface().map(|x| x == surface).unwrap_or(false)) + } + + pub fn window_geometry(&self, w: &Window) -> Option> { + if !self.windows.contains(w) { + return None; + } + + Some(window_rect(w, &self.id)) + } + + pub fn map_output(&mut self, output: &Output, scale: f64, location: Point) { + let mut state = output_state(self.id, output); + *state = OutputState { + location, + render_scale: scale, + ..Default::default() + }; + if !self.outputs.contains(output) { + self.outputs.push(output.clone()); + } + } + + pub fn outputs(&self) -> impl Iterator { + self.outputs.iter() + } + + pub fn unmap_output(&mut self, output: &Output) { + if let Some(map) = output.user_data().get::() { + map.borrow_mut().remove(&self.id); + } + self.outputs.retain(|o| o != output); + } + + pub fn output_geometry(&self, o: &Output) -> Option> { + if !self.outputs.contains(o) { + return None; + } + + let state = output_state(self.id, o); + o.current_mode().map(|mode| { + Rectangle::from_loc_and_size( + state.location, + mode.size.to_f64().to_logical(state.render_scale).to_i32_round(), + ) + }) + } + + pub fn output_scale(&self, o: &Output) -> Option { + if !self.outputs.contains(o) { + return None; + } + + let state = output_state(self.id, o); + Some(state.render_scale) + } + + pub fn output_for_window(&self, w: &Window) -> Option { + if !self.windows.contains(w) { + return None; + } + + let w_geo = self.window_geometry(w).unwrap(); + for o in &self.outputs { + let o_geo = self.output_geometry(o).unwrap(); + if w_geo.overlaps(o_geo) { + return Some(o.clone()); + } + } + + // TODO primary output + self.outputs.get(0).cloned() + } + + pub fn cleanup(&mut self) { + self.windows.retain(|w| w.toplevel().alive()); + } + + pub fn render_output( + &mut self, + renderer: &mut R, + output: &Output, + age: usize, + clear_color: [f32; 4], + ) -> Result> + where + R: Renderer + ImportAll, + R::TextureId: 'static, + { + let mut state = output_state(self.id, output); + let output_size = output + .current_mode() + .ok_or(RenderError::OutputNoMode)? + .size + .to_f64() + .to_logical(state.render_scale) + .to_i32_round(); + let output_geo = Rectangle::from_loc_and_size(state.location, output_size); + + // This will hold all the damage we need for this rendering step + let mut damage = Vec::>::new(); + // First add damage for windows gone + for old_window in state + .last_state + .iter() + .filter_map(|(id, w)| { + if !self.windows.iter().any(|w| w.0.id == *id) { + Some(*w) + } else { + None + } + }) + .collect::>>() + { + slog::debug!(self.logger, "Removing window at: {:?}", old_window); + damage.push(old_window); + } + + // lets iterate front to back and figure out, what new windows or unmoved windows we have + for window in self.windows.iter().rev() { + let geo = window_rect(window, &self.id); + let old_geo = state.last_state.get(&window.0.id).cloned(); + + // window was moved or resized + if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { + // Add damage for the old position of the window + damage.push(old_geo.unwrap()); + } /* else { + // window stayed at its place + // TODO: Only push surface damage + // But this would need to take subsurfaces into account and accumulate damage at least. + // Even better would be if damage would be ignored that is hidden by subsurfaces... + }*/ + // Add damage for the new position (see TODO above for a better approach) + damage.push(geo); + } + + // That is all completely new damage, which we need to store for subsequent renders + let new_damage = damage.clone(); + // We now add old damage states, if we have an age value + if age > 0 && state.old_damage.len() >= age { + // We do not need older states anymore + state.old_damage.truncate(age); + damage.extend(state.old_damage.iter().flatten().copied()); + } else { + // just damage everything, if we have no damage + damage = vec![output_geo]; + } + + // Optimize the damage for rendering + damage.retain(|rect| rect.overlaps(output_geo)); + damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0); + for rect in damage.clone().iter() { + // if this rect was already removed, because it was smaller as another one, + // there is no reason to evaluate this. + if damage.contains(rect) { + // remove every rectangle that is contained in this rectangle + damage.retain(|other| !rect.contains_rect(*other)); + } + } + + let output_transform: Transform = output.current_transform().into(); + if let Err(err) = renderer.render( + output_transform + .transform_size(output_size) + .to_f64() + .to_physical(state.render_scale) + .to_i32_round(), + output_transform, + |renderer, frame| { + // First clear all damaged regions + for geo in &damage { + slog::debug!(self.logger, "Clearing at {:?}", geo); + frame.clear( + clear_color, + Some(geo.to_f64().to_physical(state.render_scale).to_i32_ceil()), + )?; + } + + // Then re-draw all window overlapping with a damage rect. + for window in self.windows.iter() { + let wgeo = window_rect(window, &self.id); + let mut loc = window_loc(window, &self.id); + if damage.iter().any(|geo| wgeo.overlaps(*geo)) { + loc -= output_geo.loc; + slog::debug!(self.logger, "Rendering window at {:?}", wgeo); + draw_window(renderer, frame, window, state.render_scale, loc, &self.logger)?; + window_state(self.id, window).drawn = true; + } + } + + Result::<(), R::Error>::Ok(()) + }, + ) { + // if the rendering errors on us, we need to be prepared, that this whole buffer was partially updated and thus now unusable. + // thus clean our old states before returning + state.old_damage = VecDeque::new(); + state.last_state = IndexMap::new(); + return Err(RenderError::Rendering(err)); + } + + // If rendering was successful capture the state and add the damage + state.last_state = self + .windows + .iter() + .map(|window| { + let wgeo = window_rect(window, &self.id); + (window.0.id, wgeo) + }) + .collect(); + state.old_damage.push_front(new_damage); + + // Return if we actually rendered something + Ok(!damage.is_empty()) + } + + pub fn send_frames(&self, all: bool, time: u32) { + for window in self.windows.iter().filter(|w| { + all || { + let mut state = window_state(self.id, w); + std::mem::replace(&mut state.drawn, false) + } + }) { + window.send_frame(time); + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RenderError { + #[error(transparent)] + Rendering(R::Error), + #[error("Output has no active mode")] + OutputNoMode, +} + +fn window_rect(window: &Window, space_id: &usize) -> Rectangle { + let loc = window_loc(window, space_id); + window_bbox_with_pos(window, loc) +} + +fn window_loc(window: &Window, space_id: &usize) -> Point { + window + .user_data() + .get::>>() + .unwrap() + .borrow() + .get(space_id) + .unwrap() + .location +} + +fn window_bbox_with_pos(window: &Window, pos: Point) -> Rectangle { + let mut wgeo = window.bbox(); + wgeo.loc += pos; + wgeo +} diff --git a/src/desktop/window.rs b/src/desktop/window.rs new file mode 100644 index 0000000..f3292d0 --- /dev/null +++ b/src/desktop/window.rs @@ -0,0 +1,510 @@ +use crate::{ + backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, + utils::{Logical, Physical, Point, Rectangle, Size}, + wayland::{ + compositor::{ + add_commit_hook, is_sync_subsurface, with_states, with_surface_tree_downward, + with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, SurfaceAttributes, + TraversalAction, + }, + shell::xdg::{SurfaceCachedState, ToplevelSurface}, + }, +}; +use std::{ + cell::RefCell, + collections::HashSet, + hash::{Hash, Hasher}, + rc::Rc, + sync::{ + atomic::{AtomicUsize, Ordering}, + Mutex, + }, +}; +use wayland_commons::user_data::UserDataMap; +use wayland_protocols::xdg_shell::server::xdg_toplevel; +use wayland_server::protocol::{wl_buffer, wl_surface}; + +static WINDOW_ID: AtomicUsize = AtomicUsize::new(0); +lazy_static::lazy_static! { + static ref WINDOW_IDS: Mutex> = Mutex::new(HashSet::new()); +} + +fn next_window_id() -> usize { + let mut ids = WINDOW_IDS.lock().unwrap(); + if ids.len() == usize::MAX { + // Theoretically the code below wraps around correctly, + // but that is hard to detect and might deadlock. + // Maybe make this a debug_assert instead? + panic!("Out of window ids"); + } + + let mut id = WINDOW_ID.fetch_add(1, Ordering::SeqCst); + while ids.iter().any(|k| *k == id) { + id = WINDOW_ID.fetch_add(1, Ordering::SeqCst); + } + + ids.insert(id); + id +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Kind { + Xdg(ToplevelSurface), + #[cfg(feature = "xwayland")] + X11(X11Surface), +} + +// Big TODO +#[derive(Debug, Clone)] +pub struct X11Surface { + surface: wl_surface::WlSurface, +} + +impl std::cmp::PartialEq for X11Surface { + fn eq(&self, other: &Self) -> bool { + self.alive() && other.alive() && self.surface == other.surface + } +} + +impl X11Surface { + pub fn alive(&self) -> bool { + self.surface.as_ref().is_alive() + } + + pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { + if self.alive() { + Some(&self.surface) + } else { + None + } + } +} + +impl Kind { + pub fn alive(&self) -> bool { + match *self { + Kind::Xdg(ref t) => t.alive(), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => t.alive(), + } + } + + pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { + match *self { + Kind::Xdg(ref t) => t.get_surface(), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => t.get_surface(), + } + } +} + +#[derive(Default)] +struct SurfaceState { + buffer_dimensions: Option>, + buffer_scale: i32, + buffer: Option, + texture: Option>, +} + +fn surface_commit(surface: &wl_surface::WlSurface) { + if !is_sync_subsurface(surface) { + with_surface_tree_upward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |_surf, states, _| { + states + .data_map + .insert_if_missing(|| RefCell::new(SurfaceState::default())); + let mut data = states + .data_map + .get::>() + .unwrap() + .borrow_mut(); + data.update_buffer(&mut *states.cached_state.current::()); + }, + |_, _, _| true, + ); + } +} + +impl SurfaceState { + pub fn update_buffer(&mut self, attrs: &mut SurfaceAttributes) { + match attrs.buffer.take() { + Some(BufferAssignment::NewBuffer { buffer, .. }) => { + // new contents + self.buffer_dimensions = buffer_dimensions(&buffer); + self.buffer_scale = attrs.buffer_scale; + if let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) { + if &old_buffer != self.buffer.as_ref().unwrap() { + old_buffer.release(); + } + } + self.texture = None; + } + Some(BufferAssignment::Removed) => { + // remove the contents + self.buffer_dimensions = None; + if let Some(buffer) = self.buffer.take() { + buffer.release(); + }; + self.texture = None; + } + None => {} + } + } + + /// Returns the size of the surface. + pub fn size(&self) -> Option> { + self.buffer_dimensions + .map(|dims| dims.to_logical(self.buffer_scale)) + } + + fn contains_point(&self, attrs: &SurfaceAttributes, point: Point) -> bool { + let size = match self.size() { + None => return false, // If the surface has no size, it can't have an input region. + Some(size) => size, + }; + + let rect = Rectangle { + loc: (0, 0).into(), + size, + } + .to_f64(); + + // The input region is always within the surface itself, so if the surface itself doesn't contain the + // point we can return false. + if !rect.contains(point) { + return false; + } + + // If there's no input region, we're done. + if attrs.input_region.is_none() { + return true; + } + + attrs + .input_region + .as_ref() + .unwrap() + .contains(point.to_i32_floor()) + } +} + +#[derive(Debug)] +pub(super) struct WindowInner { + pub(super) id: usize, + toplevel: Kind, + user_data: UserDataMap, +} + +impl Drop for WindowInner { + fn drop(&mut self) { + WINDOW_IDS.lock().unwrap().remove(&self.id); + } +} + +#[derive(Debug, Clone)] +pub struct Window(pub(super) Rc); + +impl PartialEq for Window { + fn eq(&self, other: &Self) -> bool { + self.0.id == other.0.id + } +} + +impl Eq for Window {} + +impl Hash for Window { + fn hash(&self, state: &mut H) { + self.0.id.hash(state); + } +} + +impl Window { + pub fn new(toplevel: Kind) -> Window { + let id = next_window_id(); + + // TODO: Do we want this? For new lets add Window::commit + //add_commit_hook(toplevel.get_surface().unwrap(), surface_commit); + + Window(Rc::new(WindowInner { + id, + toplevel, + user_data: UserDataMap::new(), + })) + } + + /// Returns the geometry of this window. + pub fn geometry(&self) -> Rectangle { + // It's the set geometry with the full bounding box as the fallback. + with_states(self.0.toplevel.get_surface().unwrap(), |states| { + states.cached_state.current::().geometry + }) + .unwrap() + .unwrap_or_else(|| self.bbox()) + } + + /// A bounding box over this window and its children. + // TODO: Cache and document when to trigger updates. If possible let space do it + pub fn bbox(&self) -> Rectangle { + let mut bounding_box = Rectangle::from_loc_and_size((0, 0), (0, 0)); + if let Some(wl_surface) = self.0.toplevel.get_surface() { + with_surface_tree_downward( + wl_surface, + (0, 0).into(), + |_, states, loc: &Point| { + let mut loc = *loc; + let data = states.data_map.get::>(); + + if let Some(size) = data.and_then(|d| d.borrow().size()) { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + loc += current.location; + } + + // Update the bounding box. + bounding_box = bounding_box.merge(Rectangle::from_loc_and_size(loc, size)); + + TraversalAction::DoChildren(loc) + } else { + // If the parent surface is unmapped, then the child surfaces are hidden as + // well, no need to consider them here. + TraversalAction::SkipChildren + } + }, + |_, _, _| {}, + |_, _, _| true, + ); + } + bounding_box + } + + /// Activate/Deactivate this window + // TODO: Add more helpers for Maximize? Minimize? Fullscreen? I dunno + pub fn set_activated(&self, active: bool) -> bool { + match self.0.toplevel { + Kind::Xdg(ref t) => t + .with_pending_state(|state| { + if active { + state.states.set(xdg_toplevel::State::Activated) + } else { + state.states.unset(xdg_toplevel::State::Activated) + } + }) + .unwrap_or(false), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => unimplemented!(), + } + } + + /// Commit any changes to this window + pub fn configure(&self) { + match self.0.toplevel { + Kind::Xdg(ref t) => t.send_configure(), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => unimplemented!(), + } + } + + /// Sends the frame callback to all the subsurfaces in this + /// window that requested it + pub fn send_frame(&self, time: u32) { + if let Some(wl_surface) = self.0.toplevel.get_surface() { + with_surface_tree_downward( + wl_surface, + (), + |_, _, &()| TraversalAction::DoChildren(()), + |_surf, states, &()| { + // the surface may not have any user_data if it is a subsurface and has not + // yet been commited + for callback in states + .cached_state + .current::() + .frame_callbacks + .drain(..) + { + callback.done(time); + } + }, + |_, _, &()| true, + ); + } + } + + /// Finds the topmost surface under this point if any and returns it together with the location of this + /// surface. + pub fn surface_under( + &self, + point: Point, + ) -> Option<(wl_surface::WlSurface, Point)> { + let found = RefCell::new(None); + if let Some(wl_surface) = self.0.toplevel.get_surface() { + with_surface_tree_downward( + wl_surface, + (0, 0).into(), + |wl_surface, states, location: &Point| { + let mut location = *location; + let data = states.data_map.get::>(); + + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + + let contains_the_point = data + .map(|data| { + data.borrow() + .contains_point(&*states.cached_state.current(), point - location.to_f64()) + }) + .unwrap_or(false); + if contains_the_point { + *found.borrow_mut() = Some((wl_surface.clone(), location)); + } + + TraversalAction::DoChildren(location) + }, + |_, _, _| {}, + |_, _, _| { + // only continue if the point is not found + found.borrow().is_none() + }, + ); + } + found.into_inner() + } + + pub fn toplevel(&self) -> &Kind { + &self.0.toplevel + } + + pub fn user_data(&self) -> &UserDataMap { + &self.0.user_data + } + + /// Has to be called on commit - Window handles the buffer for you + pub fn commit(surface: &wl_surface::WlSurface) { + surface_commit(surface) + } +} + +// TODO: This is basically `draw_surface_tree` from anvil. +// Can we move this somewhere, where it is also usable for other things then windows? +// Maybe add this as a helper function for surfaces to `backend::renderer`? +// How do we handle SurfaceState in that case? Like we need a closure to +// store and retrieve textures for arbitrary surface trees? Or leave this to the +// compositor, but that feels like a lot of unnecessary code dublication. + +// TODO: This does not handle ImportAll errors properly and uses only one texture slot. +// This means it is *not* compatible with MultiGPU setups at all. +// Current plan is to make sure it does not crash at least in that case and later add +// A `MultiGpuManager` that opens gpus automatically, creates renderers for them, +// implements `Renderer` and `ImportAll` itself and dispatches everything accordingly, +// even copying buffers if necessary. This abstraction will likely also handle dmabuf- +// protocol(s) (and maybe explicit sync?). Then this function will be fine and all the +// gore of handling multiple gpus will be hidden away for most if not all cases. + +// TODO: This function does not crop or scale windows to fit into a space. +// How do we want to handle this? Add an additional size property to a window? +// Let the user specify the max size and the method to handle it? + +pub fn draw_window( + renderer: &mut R, + frame: &mut F, + window: &Window, + scale: f64, + location: Point, + log: &slog::Logger, +) -> Result<(), R::Error> +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, +{ + let mut result = Ok(()); + if let Some(surface) = window.0.toplevel.get_surface() { + with_surface_tree_upward( + surface, + location, + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let mut data = data.borrow_mut(); + let attributes = states.cached_state.current::(); + // Import a new buffer if necessary + if data.texture.is_none() { + if let Some(buffer) = data.buffer.as_ref() { + let damage = attributes + .damage + .iter() + .map(|dmg| match dmg { + Damage::Buffer(rect) => *rect, + // TODO also apply transformations + Damage::Surface(rect) => rect.to_buffer(attributes.buffer_scale), + }) + .collect::>(); + + match renderer.import_buffer(buffer, Some(states), &damage) { + Some(Ok(m)) => { + data.texture = Some(Box::new(m)); + } + Some(Err(err)) => { + slog::warn!(log, "Error loading buffer: {}", err); + } + None => { + slog::error!(log, "Unknown buffer format for: {:?}", buffer); + } + } + } + } + // Now, should we be drawn ? + if data.texture.is_some() { + // if yes, also process the children + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + TraversalAction::DoChildren(location) + } else { + // we are not displayed, so our children are neither + TraversalAction::SkipChildren + } + } else { + // we are not displayed, so our children are neither + TraversalAction::SkipChildren + } + }, + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let mut data = data.borrow_mut(); + let buffer_scale = data.buffer_scale; + let attributes = states.cached_state.current::(); + if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::()) { + // we need to re-extract the subsurface offset, as the previous closure + // only passes it to our children + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + // TODO: Take wp_viewporter into account + if let Err(err) = frame.render_texture_at( + texture, + location.to_f64().to_physical(scale).to_i32_round(), + buffer_scale, + scale, + attributes.buffer_transform.into(), + 1.0, + ) { + result = Err(err); + } + } + } + }, + |_, _, _| true, + ); + } + + result +} diff --git a/src/lib.rs b/src/lib.rs index bee4d66..3472e88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,8 @@ pub extern crate nix; pub mod backend; +#[cfg(feature = "desktop")] +pub mod desktop; pub mod utils; #[cfg(feature = "wayland_frontend")] pub mod wayland; From 44829d8fba1d3a6f937f891dd467446e729d7e05 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Sun, 28 Nov 2021 22:30:17 +0100 Subject: [PATCH 06/52] desktop: Only render surface damage, if window was not moved --- src/desktop/space.rs | 17 ++++++++------- src/desktop/window.rs | 51 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 7365cdf..70dcfc1 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -287,14 +287,15 @@ impl Space { if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { // Add damage for the old position of the window damage.push(old_geo.unwrap()); - } /* else { - // window stayed at its place - // TODO: Only push surface damage - // But this would need to take subsurfaces into account and accumulate damage at least. - // Even better would be if damage would be ignored that is hidden by subsurfaces... - }*/ - // Add damage for the new position (see TODO above for a better approach) - damage.push(geo); + damage.push(geo); + } else { + // window stayed at its place + let loc = window_loc(window, &self.id); + damage.extend(window.accumulated_damage().into_iter().map(|mut rect| { + rect.loc += loc; + rect + })); + } } // That is all completely new damage, which we need to store for subsequent renders diff --git a/src/desktop/window.rs b/src/desktop/window.rs index f3292d0..9077983 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -374,6 +374,57 @@ impl Window { found.into_inner() } + /// Damage of all the surfaces of this window + pub(super) fn accumulated_damage(&self) -> Vec> { + let mut damage = Vec::new(); + let location = (0, 0).into(); + if let Some(surface) = self.0.toplevel.get_surface() { + with_surface_tree_upward( + surface, + location, + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let data = data.borrow(); + if data.texture.is_none() { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + return TraversalAction::DoChildren(location); + } + } + TraversalAction::SkipChildren + }, + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let data = data.borrow(); + let attributes = states.cached_state.current::(); + + if data.texture.is_none() { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + + damage.extend(attributes.damage.iter().map(|dmg| { + let mut rect = match dmg { + Damage::Buffer(rect) => rect.to_logical(attributes.buffer_scale), + Damage::Surface(rect) => *rect, + }; + rect.loc += location; + rect + })); + } + } + }, + |_, _, _| true, + ) + } + damage + } + pub fn toplevel(&self) -> &Kind { &self.0.toplevel } From 2c0ae025de166aa81661602efd3b96f7b91be1a8 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 30 Nov 2021 16:53:42 +0100 Subject: [PATCH 07/52] desktop: Track surface enter/leave for outputs --- src/desktop/space.rs | 135 ++++++++++++++++++++++++++++++++++++++++-- src/desktop/window.rs | 2 +- 2 files changed, 130 insertions(+), 7 deletions(-) diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 70dcfc1..93bedf0 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -1,8 +1,11 @@ -use super::{draw_window, Window}; +use super::{draw_window, SurfaceState, Window}; use crate::{ backend::renderer::{Frame, ImportAll, Renderer, Transform}, utils::{Logical, Point, Rectangle}, - wayland::output::Output, + wayland::{ + compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction}, + output::Output, + }, }; use indexmap::{IndexMap, IndexSet}; use std::{ @@ -13,7 +16,7 @@ use std::{ Mutex, }, }; -use wayland_server::protocol::wl_surface; +use wayland_server::protocol::wl_surface::WlSurface; static SPACE_ID: AtomicUsize = AtomicUsize::new(0); lazy_static::lazy_static! { @@ -56,9 +59,13 @@ fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> { struct OutputState { location: Point, render_scale: f64, - // damage and last_state in space coordinate space + + // damage and last_state are in space coordinate space old_damage: VecDeque>>, last_state: IndexMap>, + + // surfaces for tracking enter and leave events + surfaces: Vec, } type OutputUserdata = RefCell>; @@ -153,7 +160,7 @@ impl Space { }) } - pub fn window_for_surface(&self, surface: &wl_surface::WlSurface) -> Option<&Window> { + pub fn window_for_surface(&self, surface: &WlSurface) -> Option<&Window> { if !surface.as_ref().is_alive() { return None; } @@ -234,8 +241,124 @@ impl Space { self.outputs.get(0).cloned() } - pub fn cleanup(&mut self) { + pub fn refresh(&mut self) { self.windows.retain(|w| w.toplevel().alive()); + + for output in &mut self.outputs { + output_state(self.id, output) + .surfaces + .retain(|s| s.as_ref().is_alive()); + } + + for window in &self.windows { + let bbox = window_rect(window, &self.id); + let kind = window.toplevel(); + + for output in &self.outputs { + let output_geometry = self + .output_geometry(output) + .unwrap_or_else(|| Rectangle::from_loc_and_size((0, 0), (0, 0))); + let mut output_state = output_state(self.id, output); + + // Check if the bounding box of the toplevel intersects with + // the output, if not no surface in the tree can intersect with + // the output. + if !output_geometry.overlaps(bbox) { + if let Some(surface) = kind.get_surface() { + with_surface_tree_downward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |wl_surface, _, _| { + if output_state.surfaces.contains(wl_surface) { + slog::trace!( + self.logger, + "surface ({:?}) leaving output {:?}", + wl_surface, + output.name() + ); + output.leave(wl_surface); + output_state.surfaces.retain(|s| s != wl_surface); + } + }, + |_, _, _| true, + ) + } + continue; + } + + if let Some(surface) = kind.get_surface() { + with_surface_tree_downward( + surface, + window_loc(window, &self.id), + |_, states, location| { + let mut location = *location; + let data = states.data_map.get::>(); + + if data.is_some() { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + + TraversalAction::DoChildren(location) + } else { + // If the parent surface is unmapped, then the child surfaces are hidden as + // well, no need to consider them here. + TraversalAction::SkipChildren + } + }, + |wl_surface, states, &loc| { + let data = states.data_map.get::>(); + + if let Some(size) = data.and_then(|d| d.borrow().size()) { + let surface_rectangle = Rectangle { loc, size }; + + if output_geometry.overlaps(surface_rectangle) { + // We found a matching output, check if we already sent enter + if !output_state.surfaces.contains(wl_surface) { + slog::trace!( + self.logger, + "surface ({:?}) entering output {:?}", + wl_surface, + output.name() + ); + output.enter(wl_surface); + output_state.surfaces.push(wl_surface.clone()); + } + } else { + // Surface does not match output, if we sent enter earlier + // we should now send leave + if output_state.surfaces.contains(wl_surface) { + slog::trace!( + self.logger, + "surface ({:?}) leaving output {:?}", + wl_surface, + output.name() + ); + output.leave(wl_surface); + output_state.surfaces.retain(|s| s != wl_surface); + } + } + } else { + // Maybe the the surface got unmapped, send leave on output + if output_state.surfaces.contains(wl_surface) { + slog::trace!( + self.logger, + "surface ({:?}) leaving output {:?}", + wl_surface, + output.name() + ); + output.leave(wl_surface); + output_state.surfaces.retain(|s| s != wl_surface); + } + } + }, + |_, _, _| true, + ) + } + } + } } pub fn render_output( diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 9077983..99dde99 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -99,7 +99,7 @@ impl Kind { } #[derive(Default)] -struct SurfaceState { +pub(super) struct SurfaceState { buffer_dimensions: Option>, buffer_scale: i32, buffer: Option, From 7578fab9cfbca350eb320c6792c338fd089d964a Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 1 Dec 2021 18:46:23 +0100 Subject: [PATCH 08/52] desktop: map_window remove unnecessary Result and activate --- src/desktop/space.rs | 50 +++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 93bedf0..28a1280 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -117,16 +117,10 @@ impl Space { /// Map window and moves it to top of the stack /// /// This can safely be called on an already mapped window - pub fn map_window(&mut self, window: &Window, location: Point) -> Result<(), SpaceError> { - window_state(self.id, window).location = location; + pub fn map_window(&mut self, window: &Window, location: Point) { + window_state(self.id, window).location = location - window.geometry().loc; self.windows.shift_remove(window); self.windows.insert(window.clone()); - Ok(()) - } - - pub fn raise_window(&mut self, window: &Window) { - let loc = window_state(self.id, window).location; - let _ = self.map_window(window, loc); // TODO: should this be handled by us? window.set_activated(true); @@ -137,6 +131,11 @@ impl Space { } } + pub fn raise_window(&mut self, window: &Window) { + let loc = window_geo(window, &self.id).loc; + self.map_window(window, loc); + } + /// Unmap a window from this space by its id pub fn unmap_window(&mut self, window: &Window) { if let Some(map) = window.user_data().get::() { @@ -153,9 +152,7 @@ impl Space { /// Get a reference to the window under a given point, if any pub fn window_under(&self, point: Point) -> Option<&Window> { self.windows.iter().find(|w| { - let loc = window_state(self.id, w).location; - let mut bbox = w.bbox(); - bbox.loc += loc; + let bbox = window_rect(w, &self.id); bbox.to_f64().contains(point) }) } @@ -175,6 +172,14 @@ impl Space { return None; } + Some(window_geo(w, &self.id)) + } + + pub fn window_bbox(&self, w: &Window) -> Option> { + if !self.windows.contains(w) { + return None; + } + Some(window_rect(w, &self.id)) } @@ -397,7 +402,7 @@ impl Space { }) .collect::>>() { - slog::debug!(self.logger, "Removing window at: {:?}", old_window); + slog::trace!(self.logger, "Removing window at: {:?}", old_window); damage.push(old_window); } @@ -456,7 +461,7 @@ impl Space { |renderer, frame| { // First clear all damaged regions for geo in &damage { - slog::debug!(self.logger, "Clearing at {:?}", geo); + slog::trace!(self.logger, "Clearing at {:?}", geo); frame.clear( clear_color, Some(geo.to_f64().to_physical(state.render_scale).to_i32_ceil()), @@ -469,7 +474,7 @@ impl Space { let mut loc = window_loc(window, &self.id); if damage.iter().any(|geo| wgeo.overlaps(*geo)) { loc -= output_geo.loc; - slog::debug!(self.logger, "Rendering window at {:?}", wgeo); + slog::trace!(self.logger, "Rendering window at {:?}", wgeo); draw_window(renderer, frame, window, state.render_scale, loc, &self.logger)?; window_state(self.id, window).drawn = true; } @@ -520,9 +525,18 @@ pub enum RenderError { OutputNoMode, } +fn window_geo(window: &Window, space_id: &usize) -> Rectangle { + let loc = window_loc(window, space_id); + let mut wgeo = window.geometry(); + wgeo.loc += loc; + wgeo +} + fn window_rect(window: &Window, space_id: &usize) -> Rectangle { let loc = window_loc(window, space_id); - window_bbox_with_pos(window, loc) + let mut wgeo = window.bbox(); + wgeo.loc += loc; + wgeo } fn window_loc(window: &Window, space_id: &usize) -> Point { @@ -535,9 +549,3 @@ fn window_loc(window: &Window, space_id: &usize) -> Point { .unwrap() .location } - -fn window_bbox_with_pos(window: &Window, pos: Point) -> Rectangle { - let mut wgeo = window.bbox(); - wgeo.loc += pos; - wgeo -} From 25b74e2eaa83240f9fc5a6ef19b4b60f6c3b7598 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Thu, 2 Dec 2021 12:45:21 +0100 Subject: [PATCH 09/52] space: change output_for_window to return Vec --- src/desktop/space.rs | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 28a1280..4d60205 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -229,21 +229,33 @@ impl Space { Some(state.render_scale) } - pub fn output_for_window(&self, w: &Window) -> Option { + pub fn outputs_for_window(&self, w: &Window) -> Vec { if !self.windows.contains(w) { - return None; + return Vec::new(); } - let w_geo = self.window_geometry(w).unwrap(); - for o in &self.outputs { - let o_geo = self.output_geometry(o).unwrap(); - if w_geo.overlaps(o_geo) { - return Some(o.clone()); - } - } - - // TODO primary output - self.outputs.get(0).cloned() + let w_geo = window_rect(w, &self.id); + let mut outputs = self + .outputs + .iter() + .cloned() + .filter(|o| { + let o_geo = self.output_geometry(o).unwrap(); + w_geo.overlaps(o_geo) + }) + .collect::>(); + outputs.sort_by(|o1, o2| { + let overlap = |rect1: Rectangle, rect2: Rectangle| -> i32 { + // x overlap + std::cmp::max(0, std::cmp::min(rect1.loc.x + rect1.size.w, rect2.loc.x + rect2.size.w) - std::cmp::max(rect1.loc.x, rect2.loc.x)) + // y overlap + * std::cmp::max(0, std::cmp::min(rect1.loc.y + rect1.size.h, rect2.loc.y + rect2.size.h) - std::cmp::max(rect1.loc.y, rect2.loc.y)) + }; + let o1_area = overlap(self.output_geometry(o1).unwrap(), w_geo); + let o2_area = overlap(self.output_geometry(o2).unwrap(), w_geo); + o1_area.cmp(&o2_area) + }); + outputs } pub fn refresh(&mut self) { From 90f7d53a3a157f78e6694e9d65911e6c493990da Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Thu, 2 Dec 2021 12:45:58 +0100 Subject: [PATCH 10/52] space: fixup miscalculations --- src/desktop/space.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 4d60205..eba4c70 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -118,7 +118,11 @@ impl Space { /// /// This can safely be called on an already mapped window pub fn map_window(&mut self, window: &Window, location: Point) { - window_state(self.id, window).location = location - window.geometry().loc; + self.raise_window(window); + window_state(self.id, window).location = location; + } + + pub fn raise_window(&mut self, window: &Window) { self.windows.shift_remove(window); self.windows.insert(window.clone()); @@ -131,11 +135,6 @@ impl Space { } } - pub fn raise_window(&mut self, window: &Window) { - let loc = window_geo(window, &self.id).loc; - self.map_window(window, loc); - } - /// Unmap a window from this space by its id pub fn unmap_window(&mut self, window: &Window) { if let Some(map) = window.user_data().get::() { @@ -540,7 +539,7 @@ pub enum RenderError { fn window_geo(window: &Window, space_id: &usize) -> Rectangle { let loc = window_loc(window, space_id); let mut wgeo = window.geometry(); - wgeo.loc += loc; + wgeo.loc = loc; wgeo } From f55f1bbbe00ce693d244eb13c5e79800e358927f Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Mon, 6 Dec 2021 20:19:03 +0100 Subject: [PATCH 11/52] desktop: handle xdg-popups --- src/backend/renderer/mod.rs | 2 + src/backend/renderer/utils/mod.rs | 167 ++++++++++ src/desktop/mod.rs | 2 + src/desktop/popup.rs | 262 +++++++++++++++ src/desktop/space.rs | 19 +- src/desktop/window.rs | 508 +++++++++++++----------------- 6 files changed, 667 insertions(+), 293 deletions(-) create mode 100644 src/backend/renderer/utils/mod.rs create mode 100644 src/desktop/popup.rs diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index 2711e22..c71d770 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -32,6 +32,8 @@ use crate::backend::egl::{ Error as EglError, }; +pub mod utils; + #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] /// Possible transformations to two-dimensional planes pub enum Transform { diff --git a/src/backend/renderer/utils/mod.rs b/src/backend/renderer/utils/mod.rs new file mode 100644 index 0000000..e9d4723 --- /dev/null +++ b/src/backend/renderer/utils/mod.rs @@ -0,0 +1,167 @@ +use crate::{ + backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, + utils::{Logical, Physical, Point, Size}, + wayland::compositor::{ + is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, + SurfaceAttributes, TraversalAction, + }, +}; +use std::cell::RefCell; +use wayland_server::protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface}; + +#[derive(Default)] +pub(crate) struct SurfaceState { + pub(crate) buffer_dimensions: Option>, + pub(crate) buffer_scale: i32, + pub(crate) buffer: Option, + pub(crate) texture: Option>, +} + +impl SurfaceState { + pub fn update_buffer(&mut self, attrs: &mut SurfaceAttributes) { + match attrs.buffer.take() { + Some(BufferAssignment::NewBuffer { buffer, .. }) => { + // new contents + self.buffer_dimensions = buffer_dimensions(&buffer); + self.buffer_scale = attrs.buffer_scale; + if let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) { + if &old_buffer != self.buffer.as_ref().unwrap() { + old_buffer.release(); + } + } + self.texture = None; + } + Some(BufferAssignment::Removed) => { + // remove the contents + self.buffer_dimensions = None; + if let Some(buffer) = self.buffer.take() { + buffer.release(); + }; + self.texture = None; + } + None => {} + } + } +} + +pub fn on_commit_buffer_handler(surface: &WlSurface) { + if !is_sync_subsurface(surface) { + with_surface_tree_upward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |_surf, states, _| { + states + .data_map + .insert_if_missing(|| RefCell::new(SurfaceState::default())); + let mut data = states + .data_map + .get::>() + .unwrap() + .borrow_mut(); + data.update_buffer(&mut *states.cached_state.current::()); + }, + |_, _, _| true, + ); + } +} + +pub fn draw_surface_tree( + renderer: &mut R, + frame: &mut F, + surface: &WlSurface, + scale: f64, + location: Point, + log: &slog::Logger, +) -> Result<(), R::Error> +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, +{ + let mut result = Ok(()); + with_surface_tree_upward( + surface, + location, + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let mut data = data.borrow_mut(); + let attributes = states.cached_state.current::(); + // Import a new buffer if necessary + if data.texture.is_none() { + if let Some(buffer) = data.buffer.as_ref() { + let damage = attributes + .damage + .iter() + .map(|dmg| match dmg { + Damage::Buffer(rect) => *rect, + // TODO also apply transformations + Damage::Surface(rect) => rect.to_buffer(attributes.buffer_scale), + }) + .collect::>(); + + match renderer.import_buffer(buffer, Some(states), &damage) { + Some(Ok(m)) => { + data.texture = Some(Box::new(m)); + } + Some(Err(err)) => { + slog::warn!(log, "Error loading buffer: {}", err); + } + None => { + slog::error!(log, "Unknown buffer format for: {:?}", buffer); + } + } + } + } + // Now, should we be drawn ? + if data.texture.is_some() { + // if yes, also process the children + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + TraversalAction::DoChildren(location) + } else { + // we are not displayed, so our children are neither + TraversalAction::SkipChildren + } + } else { + // we are not displayed, so our children are neither + TraversalAction::SkipChildren + } + }, + |surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let mut data = data.borrow_mut(); + let buffer_scale = data.buffer_scale; + let attributes = states.cached_state.current::(); + if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::()) { + // we need to re-extract the subsurface offset, as the previous closure + // only passes it to our children + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + + // TODO: Take wp_viewporter into account + if let Err(err) = frame.render_texture_at( + texture, + location.to_f64().to_physical(scale).to_i32_round(), + buffer_scale, + scale, + attributes.buffer_transform.into(), + 1.0, + ) { + result = Err(err); + } + } + } + }, + |_, _, _| true, + ); + + result +} diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index 7c885a5..fb33afc 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -1,7 +1,9 @@ // TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings #![allow(missing_docs, clippy::all)] +mod popup; mod space; mod window; +pub use self::popup::*; pub use self::space::*; pub use self::window::*; diff --git a/src/desktop/popup.rs b/src/desktop/popup.rs new file mode 100644 index 0000000..a6e2de6 --- /dev/null +++ b/src/desktop/popup.rs @@ -0,0 +1,262 @@ +use crate::{ + utils::{DeadResource, Logical, Point}, + wayland::{ + compositor::{get_role, with_states}, + shell::xdg::{PopupSurface, XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE}, + }, +}; +use std::sync::{Arc, Mutex}; +use wayland_server::protocol::wl_surface::WlSurface; + +#[derive(Debug)] +pub struct PopupManager { + unmapped_popups: Vec, + popup_trees: Vec, + logger: ::slog::Logger, +} + +impl PopupManager { + pub fn new>>(logger: L) -> Self { + PopupManager { + unmapped_popups: Vec::new(), + popup_trees: Vec::new(), + logger: crate::slog_or_fallback(logger), + } + } + + pub fn track_popup(&mut self, kind: PopupKind) -> Result<(), DeadResource> { + if kind.parent().is_some() { + self.add_popup(kind) + } else { + slog::trace!(self.logger, "Adding unmapped popups: {:?}", kind); + self.unmapped_popups.push(kind); + Ok(()) + } + } + + pub fn commit(&mut self, surface: &WlSurface) { + if get_role(surface) == Some(XDG_POPUP_ROLE) { + if let Some(i) = self + .unmapped_popups + .iter() + .enumerate() + .find(|(_, p)| p.get_surface() == Some(surface)) + .map(|(i, _)| i) + { + slog::trace!(self.logger, "Popup got mapped"); + let popup = self.unmapped_popups.swap_remove(i); + // at this point the popup must have a parent, + // or it would have raised a protocol error + let _ = self.add_popup(popup); + } + } + } + + fn add_popup(&mut self, popup: PopupKind) -> Result<(), DeadResource> { + let mut parent = popup.parent().unwrap(); + while get_role(&parent) == Some(XDG_POPUP_ROLE) { + parent = with_states(&parent, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .parent + .as_ref() + .cloned() + .unwrap() + })?; + } + + with_states(&parent, |states| { + let tree = PopupTree::default(); + if states.data_map.insert_if_missing(|| tree.clone()) { + self.popup_trees.push(tree); + }; + let tree = states.data_map.get::().unwrap(); + if !tree.alive() { + // if it previously had no popups, we likely removed it from our list already + self.popup_trees.push(tree.clone()); + } + slog::trace!(self.logger, "Adding popup {:?} to parent {:?}", popup, parent); + tree.insert(popup); + }) + } + + pub fn find_popup(&self, surface: &WlSurface) -> Option { + self.unmapped_popups + .iter() + .find(|p| p.get_surface() == Some(surface)) + .cloned() + .or_else(|| { + self.popup_trees + .iter() + .map(|tree| tree.iter_popups()) + .flatten() + .find(|(p, _)| p.get_surface() == Some(surface)) + .map(|(p, _)| p) + }) + } + + pub fn popups_for_surface( + surface: &WlSurface, + ) -> Result)>, DeadResource> { + with_states(surface, |states| { + states + .data_map + .get::() + .map(|x| x.iter_popups()) + .into_iter() + .flatten() + }) + } + + pub fn cleanup(&mut self) { + // retain_mut is sadly still unstable + self.popup_trees.iter_mut().for_each(|tree| tree.cleanup()); + self.popup_trees.retain(|tree| tree.alive()); + self.unmapped_popups.retain(|surf| surf.alive()); + } +} + +#[derive(Debug, Default, Clone)] +struct PopupTree(Arc>>); + +#[derive(Debug, Clone)] +struct PopupNode { + surface: PopupKind, + children: Vec, +} + +impl PopupTree { + fn iter_popups(&self) -> impl Iterator)> { + self.0 + .lock() + .unwrap() + .iter() + .map(|n| n.iter_popups_relative_to((0, 0)).map(|(p, l)| (p.clone(), l))) + .flatten() + .collect::>() + .into_iter() + } + + fn insert(&self, popup: PopupKind) { + let children = &mut *self.0.lock().unwrap(); + for child in children.iter_mut() { + if child.insert(popup.clone()) { + return; + } + } + children.push(PopupNode::new(popup)); + } + + fn cleanup(&mut self) { + let mut children = self.0.lock().unwrap(); + for child in children.iter_mut() { + child.cleanup(); + } + children.retain(|n| n.surface.alive()); + } + + fn alive(&self) -> bool { + !self.0.lock().unwrap().is_empty() + } +} + +impl PopupNode { + fn new(surface: PopupKind) -> Self { + PopupNode { + surface, + children: Vec::new(), + } + } + + fn iter_popups_relative_to>>( + &self, + loc: P, + ) -> impl Iterator)> { + let relative_to = loc.into() + self.surface.location(); + std::iter::once((&self.surface, relative_to)).chain( + self.children + .iter() + .map(move |x| { + Box::new(x.iter_popups_relative_to(relative_to)) + as Box)>> + }) + .flatten(), + ) + } + + fn insert(&mut self, popup: PopupKind) -> bool { + let parent = popup.parent().unwrap(); + if self.surface.get_surface() == Some(&parent) { + self.children.push(PopupNode::new(popup)); + true + } else { + for child in &mut self.children { + if child.insert(popup.clone()) { + return true; + } + } + false + } + } + + fn cleanup(&mut self) { + for child in &mut self.children { + child.cleanup(); + } + self.children.retain(|n| n.surface.alive()); + } +} + +#[derive(Debug, Clone)] +pub enum PopupKind { + Xdg(PopupSurface), +} + +impl PopupKind { + fn alive(&self) -> bool { + match *self { + PopupKind::Xdg(ref t) => t.alive(), + } + } + + pub fn get_surface(&self) -> Option<&WlSurface> { + match *self { + PopupKind::Xdg(ref t) => t.get_surface(), + } + } + + fn parent(&self) -> Option { + match *self { + PopupKind::Xdg(ref t) => t.get_parent_surface(), + } + } + + fn location(&self) -> Point { + let wl_surface = match self.get_surface() { + Some(s) => s, + None => return (0, 0).into(), + }; + with_states(wl_surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .current + .geometry + }) + .unwrap_or_default() + .loc + } +} + +impl From for PopupKind { + fn from(p: PopupSurface) -> PopupKind { + PopupKind::Xdg(p) + } +} diff --git a/src/desktop/space.rs b/src/desktop/space.rs index eba4c70..82dfe7d 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -1,6 +1,6 @@ -use super::{draw_window, SurfaceState, Window}; +use super::{draw_window, Window}; use crate::{ - backend::renderer::{Frame, ImportAll, Renderer, Transform}, + backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform}, utils::{Logical, Point, Rectangle}, wayland::{ compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction}, @@ -150,7 +150,7 @@ impl Space { /// Get a reference to the window under a given point, if any pub fn window_under(&self, point: Point) -> Option<&Window> { - self.windows.iter().find(|w| { + self.windows.iter().rev().find(|w| { let bbox = window_rect(w, &self.id); bbox.to_f64().contains(point) }) @@ -419,7 +419,7 @@ impl Space { // lets iterate front to back and figure out, what new windows or unmoved windows we have for window in self.windows.iter().rev() { - let geo = window_rect(window, &self.id); + let geo = window_rect_with_popups(window, &self.id); let old_geo = state.last_state.get(&window.0.id).cloned(); // window was moved or resized @@ -481,7 +481,7 @@ impl Space { // Then re-draw all window overlapping with a damage rect. for window in self.windows.iter() { - let wgeo = window_rect(window, &self.id); + let wgeo = window_rect_with_popups(window, &self.id); let mut loc = window_loc(window, &self.id); if damage.iter().any(|geo| wgeo.overlaps(*geo)) { loc -= output_geo.loc; @@ -506,7 +506,7 @@ impl Space { .windows .iter() .map(|window| { - let wgeo = window_rect(window, &self.id); + let wgeo = window_rect_with_popups(window, &self.id); (window.0.id, wgeo) }) .collect(); @@ -550,6 +550,13 @@ fn window_rect(window: &Window, space_id: &usize) -> Rectangle { wgeo } +fn window_rect_with_popups(window: &Window, space_id: &usize) -> Rectangle { + let loc = window_loc(window, space_id); + let mut wgeo = window.bbox_with_popups(); + wgeo.loc += loc; + wgeo +} + fn window_loc(window: &Window, space_id: &usize) -> Point { window .user_data() diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 99dde99..3da2cb4 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -1,11 +1,14 @@ use crate::{ - backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, - utils::{Logical, Physical, Point, Rectangle, Size}, + backend::renderer::{ + utils::{draw_surface_tree, SurfaceState}, + Frame, ImportAll, Renderer, Texture, + }, + desktop::PopupManager, + utils::{Logical, Point, Rectangle, Size}, wayland::{ compositor::{ - add_commit_hook, is_sync_subsurface, with_states, with_surface_tree_downward, - with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, SurfaceAttributes, - TraversalAction, + with_states, with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState, + SurfaceAttributes, TraversalAction, }, shell::xdg::{SurfaceCachedState, ToplevelSurface}, }, @@ -98,62 +101,7 @@ impl Kind { } } -#[derive(Default)] -pub(super) struct SurfaceState { - buffer_dimensions: Option>, - buffer_scale: i32, - buffer: Option, - texture: Option>, -} - -fn surface_commit(surface: &wl_surface::WlSurface) { - if !is_sync_subsurface(surface) { - with_surface_tree_upward( - surface, - (), - |_, _, _| TraversalAction::DoChildren(()), - |_surf, states, _| { - states - .data_map - .insert_if_missing(|| RefCell::new(SurfaceState::default())); - let mut data = states - .data_map - .get::>() - .unwrap() - .borrow_mut(); - data.update_buffer(&mut *states.cached_state.current::()); - }, - |_, _, _| true, - ); - } -} - impl SurfaceState { - pub fn update_buffer(&mut self, attrs: &mut SurfaceAttributes) { - match attrs.buffer.take() { - Some(BufferAssignment::NewBuffer { buffer, .. }) => { - // new contents - self.buffer_dimensions = buffer_dimensions(&buffer); - self.buffer_scale = attrs.buffer_scale; - if let Some(old_buffer) = std::mem::replace(&mut self.buffer, Some(buffer)) { - if &old_buffer != self.buffer.as_ref().unwrap() { - old_buffer.release(); - } - } - self.texture = None; - } - Some(BufferAssignment::Removed) => { - // remove the contents - self.buffer_dimensions = None; - if let Some(buffer) = self.buffer.take() { - buffer.release(); - }; - self.texture = None; - } - None => {} - } - } - /// Returns the size of the surface. pub fn size(&self) -> Option> { self.buffer_dimensions @@ -248,34 +196,25 @@ impl Window { /// A bounding box over this window and its children. // TODO: Cache and document when to trigger updates. If possible let space do it pub fn bbox(&self) -> Rectangle { - let mut bounding_box = Rectangle::from_loc_and_size((0, 0), (0, 0)); - if let Some(wl_surface) = self.0.toplevel.get_surface() { - with_surface_tree_downward( - wl_surface, - (0, 0).into(), - |_, states, loc: &Point| { - let mut loc = *loc; - let data = states.data_map.get::>(); + if let Some(surface) = self.0.toplevel.get_surface() { + bbox_from_surface_tree(surface, (0, 0)) + } else { + Rectangle::from_loc_and_size((0, 0), (0, 0)) + } + } - if let Some(size) = data.and_then(|d| d.borrow().size()) { - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - loc += current.location; - } - - // Update the bounding box. - bounding_box = bounding_box.merge(Rectangle::from_loc_and_size(loc, size)); - - TraversalAction::DoChildren(loc) - } else { - // If the parent surface is unmapped, then the child surfaces are hidden as - // well, no need to consider them here. - TraversalAction::SkipChildren - } - }, - |_, _, _| {}, - |_, _, _| true, - ); + pub fn bbox_with_popups(&self) -> Rectangle { + let mut bounding_box = self.bbox(); + if let Some(surface) = self.0.toplevel.get_surface() { + for (popup, location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, location)); + } + } } bounding_box } @@ -310,25 +249,17 @@ impl Window { /// Sends the frame callback to all the subsurfaces in this /// window that requested it pub fn send_frame(&self, time: u32) { - if let Some(wl_surface) = self.0.toplevel.get_surface() { - with_surface_tree_downward( - wl_surface, - (), - |_, _, &()| TraversalAction::DoChildren(()), - |_surf, states, &()| { - // the surface may not have any user_data if it is a subsurface and has not - // yet been commited - for callback in states - .cached_state - .current::() - .frame_callbacks - .drain(..) - { - callback.done(time); - } - }, - |_, _, &()| true, - ); + if let Some(surface) = self.0.toplevel.get_surface() { + send_frames_surface_tree(surface, time); + for (popup, _) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + send_frames_surface_tree(surface, time); + } + } } } @@ -338,89 +269,41 @@ impl Window { &self, point: Point, ) -> Option<(wl_surface::WlSurface, Point)> { - let found = RefCell::new(None); - if let Some(wl_surface) = self.0.toplevel.get_surface() { - with_surface_tree_downward( - wl_surface, - (0, 0).into(), - |wl_surface, states, location: &Point| { - let mut location = *location; - let data = states.data_map.get::>(); + if let Some(surface) = self.0.toplevel.get_surface() { + for (popup, location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(result) = popup + .get_surface() + .and_then(|surface| under_from_surface_tree(surface, point, location)) + { + return Some(result); + } + } - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } - - let contains_the_point = data - .map(|data| { - data.borrow() - .contains_point(&*states.cached_state.current(), point - location.to_f64()) - }) - .unwrap_or(false); - if contains_the_point { - *found.borrow_mut() = Some((wl_surface.clone(), location)); - } - - TraversalAction::DoChildren(location) - }, - |_, _, _| {}, - |_, _, _| { - // only continue if the point is not found - found.borrow().is_none() - }, - ); + under_from_surface_tree(surface, point, (0, 0)) + } else { + None } - found.into_inner() } /// Damage of all the surfaces of this window pub(super) fn accumulated_damage(&self) -> Vec> { let mut damage = Vec::new(); - let location = (0, 0).into(); if let Some(surface) = self.0.toplevel.get_surface() { - with_surface_tree_upward( - surface, - location, - |_surface, states, location| { - let mut location = *location; - if let Some(data) = states.data_map.get::>() { - let data = data.borrow(); - if data.texture.is_none() { - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } - return TraversalAction::DoChildren(location); - } - } - TraversalAction::SkipChildren - }, - |_surface, states, location| { - let mut location = *location; - if let Some(data) = states.data_map.get::>() { - let data = data.borrow(); - let attributes = states.cached_state.current::(); - - if data.texture.is_none() { - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } - - damage.extend(attributes.damage.iter().map(|dmg| { - let mut rect = match dmg { - Damage::Buffer(rect) => rect.to_logical(attributes.buffer_scale), - Damage::Surface(rect) => *rect, - }; - rect.loc += location; - rect - })); - } - } - }, - |_, _, _| true, - ) + damage.extend(damage_from_surface_tree(surface, (0, 0))); + for (popup, location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + let popup_damage = damage_from_surface_tree(surface, location); + damage.extend(popup_damage); + } + } } damage } @@ -432,32 +315,155 @@ impl Window { pub fn user_data(&self) -> &UserDataMap { &self.0.user_data } - - /// Has to be called on commit - Window handles the buffer for you - pub fn commit(surface: &wl_surface::WlSurface) { - surface_commit(surface) - } } -// TODO: This is basically `draw_surface_tree` from anvil. -// Can we move this somewhere, where it is also usable for other things then windows? -// Maybe add this as a helper function for surfaces to `backend::renderer`? -// How do we handle SurfaceState in that case? Like we need a closure to -// store and retrieve textures for arbitrary surface trees? Or leave this to the -// compositor, but that feels like a lot of unnecessary code dublication. +fn damage_from_surface_tree

(surface: &wl_surface::WlSurface, location: P) -> Vec> +where + P: Into>, +{ + let mut damage = Vec::new(); + with_surface_tree_upward( + surface, + location.into(), + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let data = data.borrow(); + if data.texture.is_none() { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + return TraversalAction::DoChildren(location); + } + } + TraversalAction::SkipChildren + }, + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let data = data.borrow(); + let attributes = states.cached_state.current::(); -// TODO: This does not handle ImportAll errors properly and uses only one texture slot. -// This means it is *not* compatible with MultiGPU setups at all. -// Current plan is to make sure it does not crash at least in that case and later add -// A `MultiGpuManager` that opens gpus automatically, creates renderers for them, -// implements `Renderer` and `ImportAll` itself and dispatches everything accordingly, -// even copying buffers if necessary. This abstraction will likely also handle dmabuf- -// protocol(s) (and maybe explicit sync?). Then this function will be fine and all the -// gore of handling multiple gpus will be hidden away for most if not all cases. + if data.texture.is_none() { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } -// TODO: This function does not crop or scale windows to fit into a space. -// How do we want to handle this? Add an additional size property to a window? -// Let the user specify the max size and the method to handle it? + damage.extend(attributes.damage.iter().map(|dmg| { + let mut rect = match dmg { + Damage::Buffer(rect) => rect.to_logical(attributes.buffer_scale), + Damage::Surface(rect) => *rect, + }; + rect.loc += location; + rect + })); + } + } + }, + |_, _, _| true, + ); + damage +} + +fn bbox_from_surface_tree

(surface: &wl_surface::WlSurface, location: P) -> Rectangle +where + P: Into>, +{ + let location = location.into(); + let mut bounding_box = Rectangle::from_loc_and_size(location, (0, 0)); + with_surface_tree_downward( + surface, + location, + |_, states, loc: &Point| { + let mut loc = *loc; + let data = states.data_map.get::>(); + + if let Some(size) = data.and_then(|d| d.borrow().size()) { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + loc += current.location; + } + + // Update the bounding box. + bounding_box = bounding_box.merge(Rectangle::from_loc_and_size(loc, size)); + + TraversalAction::DoChildren(loc) + } else { + // If the parent surface is unmapped, then the child surfaces are hidden as + // well, no need to consider them here. + TraversalAction::SkipChildren + } + }, + |_, _, _| {}, + |_, _, _| true, + ); + bounding_box +} + +pub fn under_from_surface_tree

( + surface: &wl_surface::WlSurface, + point: Point, + location: P, +) -> Option<(wl_surface::WlSurface, Point)> +where + P: Into>, +{ + let found = RefCell::new(None); + with_surface_tree_downward( + surface, + location.into(), + |wl_surface, states, location: &Point| { + let mut location = *location; + let data = states.data_map.get::>(); + + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + + let contains_the_point = data + .map(|data| { + data.borrow() + .contains_point(&*states.cached_state.current(), point - location.to_f64()) + }) + .unwrap_or(false); + if contains_the_point { + *found.borrow_mut() = Some((wl_surface.clone(), location)); + } + + TraversalAction::DoChildren(location) + }, + |_, _, _| {}, + |_, _, _| { + // only continue if the point is not found + found.borrow().is_none() + }, + ); + found.into_inner() +} + +fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) { + with_surface_tree_downward( + surface, + (), + |_, _, &()| TraversalAction::DoChildren(()), + |_surf, states, &()| { + // the surface may not have any user_data if it is a subsurface and has not + // yet been commited + for callback in states + .cached_state + .current::() + .frame_callbacks + .drain(..) + { + callback.done(time); + } + }, + |_, _, &()| true, + ); +} pub fn draw_window( renderer: &mut R, @@ -473,89 +479,17 @@ where E: std::error::Error, T: Texture + 'static, { - let mut result = Ok(()); - if let Some(surface) = window.0.toplevel.get_surface() { - with_surface_tree_upward( - surface, - location, - |_surface, states, location| { - let mut location = *location; - if let Some(data) = states.data_map.get::>() { - let mut data = data.borrow_mut(); - let attributes = states.cached_state.current::(); - // Import a new buffer if necessary - if data.texture.is_none() { - if let Some(buffer) = data.buffer.as_ref() { - let damage = attributes - .damage - .iter() - .map(|dmg| match dmg { - Damage::Buffer(rect) => *rect, - // TODO also apply transformations - Damage::Surface(rect) => rect.to_buffer(attributes.buffer_scale), - }) - .collect::>(); - - match renderer.import_buffer(buffer, Some(states), &damage) { - Some(Ok(m)) => { - data.texture = Some(Box::new(m)); - } - Some(Err(err)) => { - slog::warn!(log, "Error loading buffer: {}", err); - } - None => { - slog::error!(log, "Unknown buffer format for: {:?}", buffer); - } - } - } - } - // Now, should we be drawn ? - if data.texture.is_some() { - // if yes, also process the children - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } - TraversalAction::DoChildren(location) - } else { - // we are not displayed, so our children are neither - TraversalAction::SkipChildren - } - } else { - // we are not displayed, so our children are neither - TraversalAction::SkipChildren - } - }, - |_surface, states, location| { - let mut location = *location; - if let Some(data) = states.data_map.get::>() { - let mut data = data.borrow_mut(); - let buffer_scale = data.buffer_scale; - let attributes = states.cached_state.current::(); - if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::()) { - // we need to re-extract the subsurface offset, as the previous closure - // only passes it to our children - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } - // TODO: Take wp_viewporter into account - if let Err(err) = frame.render_texture_at( - texture, - location.to_f64().to_physical(scale).to_i32_round(), - buffer_scale, - scale, - attributes.buffer_transform.into(), - 1.0, - ) { - result = Err(err); - } - } - } - }, - |_, _, _| true, - ); + if let Some(surface) = window.toplevel().get_surface() { + draw_surface_tree(renderer, frame, surface, scale, location, log)?; + for (popup, p_location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + draw_surface_tree(renderer, frame, surface, scale, location + p_location, log)?; + } + } } - - result + Ok(()) } From 31b308836fa7d1cd04d0f28757c63109daa71bd3 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 7 Dec 2021 18:34:10 +0100 Subject: [PATCH 12/52] renderer: Use fine grained damage for rendering Up until now we always redrew the whole windows, which worked fine - except for some transparency issues - but left performance on the table. So now we use instancing to render only the actually damaged regions inside the window, which also fixes our few rendering issues. We also use a shader now for clearing the screen to use instancing as well and lower the amount of draw calls at the begin of a frame for clearing parts of it. And while we are add it we remove the last rendering artifacts by optimizing the damage vector by deduplicating and merging rectangles. --- src/backend/renderer/gles2/mod.rs | 297 +++++++++++++++++++------- src/backend/renderer/gles2/shaders.rs | 61 +++++- src/backend/renderer/mod.rs | 5 +- src/backend/renderer/utils/mod.rs | 27 ++- src/desktop/space.rs | 54 ++++- src/desktop/window.rs | 21 +- 6 files changed, 374 insertions(+), 91 deletions(-) diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 762100b..00d2344 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -44,23 +44,27 @@ pub mod ffi { include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); } -// This static is used to assign every created Renderer a unique ID (until is overflows...). -// -// This id is used to differenciate between user_data of different renderers, because one -// cannot assume, that resources between two renderers are (and even can be) shared. -static RENDERER_COUNTER: AtomicUsize = AtomicUsize::new(0); - #[derive(Debug, Clone)] -struct Gles2Program { +struct Gles2TexProgram { program: ffi::types::GLuint, uniform_tex: ffi::types::GLint, uniform_matrix: ffi::types::GLint, uniform_invert_y: ffi::types::GLint, uniform_alpha: ffi::types::GLint, + attrib_vert: ffi::types::GLint, attrib_position: ffi::types::GLint, attrib_tex_coords: ffi::types::GLint, } +#[derive(Debug, Clone)] +struct Gles2SolidProgram { + program: ffi::types::GLuint, + uniform_matrix: ffi::types::GLint, + uniform_color: ffi::types::GLint, + attrib_vert: ffi::types::GLint, + attrib_position: ffi::types::GLint, +} + /// A handle to a GLES2 texture #[derive(Debug, Clone)] pub struct Gles2Texture(Rc); @@ -158,40 +162,20 @@ struct Gles2Buffer { _dmabuf: Dmabuf, } -#[cfg(feature = "wayland_frontend")] -struct BufferEntry { - id: u32, - buffer: wl_buffer::WlBuffer, -} - -#[cfg(feature = "wayland_frontend")] -impl std::hash::Hash for BufferEntry { - fn hash(&self, hasher: &mut H) { - self.id.hash(hasher); - } -} -#[cfg(feature = "wayland_frontend")] -impl PartialEq for BufferEntry { - fn eq(&self, other: &Self) -> bool { - self.buffer == other.buffer - } -} -#[cfg(feature = "wayland_frontend")] -impl Eq for BufferEntry {} - /// A renderer utilizing OpenGL ES 2 pub struct Gles2Renderer { - id: usize, buffers: Vec, target_buffer: Option, target_surface: Option>, extensions: Vec, - programs: [Gles2Program; shaders::FRAGMENT_COUNT], + tex_programs: [Gles2TexProgram; shaders::FRAGMENT_COUNT], + solid_program: Gles2SolidProgram, #[cfg(feature = "wayland_frontend")] dmabuf_cache: std::collections::HashMap, egl: EGLContext, #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] egl_reader: Option, + vbos: [ffi::types::GLuint; 2], gl: ffi::Gles2, destruction_callback: Receiver, // This field is only accessed if the image or wayland_frontend features are active @@ -206,14 +190,19 @@ pub struct Gles2Renderer { pub struct Gles2Frame { current_projection: Matrix3, gl: ffi::Gles2, - programs: [Gles2Program; shaders::FRAGMENT_COUNT], + tex_programs: [Gles2TexProgram; shaders::FRAGMENT_COUNT], + solid_program: Gles2SolidProgram, + vbos: [ffi::types::GLuint; 2], + size: Size, } impl fmt::Debug for Gles2Frame { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Gles2Frame") .field("current_projection", &self.current_projection) - .field("programs", &self.programs) + .field("tex_programs", &self.tex_programs) + .field("solid_program", &self.solid_program) + .field("size", &self.size) .finish_non_exhaustive() } } @@ -221,12 +210,12 @@ impl fmt::Debug for Gles2Frame { impl fmt::Debug for Gles2Renderer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Gles2Renderer") - .field("id", &self.id) .field("buffers", &self.buffers) .field("target_buffer", &self.target_buffer) .field("target_surface", &self.target_surface) .field("extensions", &self.extensions) - .field("programs", &self.programs) + .field("tex_programs", &self.tex_programs) + .field("solid_program", &self.solid_program) // ffi::Gles2 does not implement Debug .field("egl", &self.egl) .field("logger", &self.logger) @@ -382,9 +371,10 @@ unsafe fn link_program( Ok(program) } -unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result { +unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result { let program = link_program(gl, shaders::VERTEX_SHADER, frag)?; + let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated"); let position = CStr::from_bytes_with_nul(b"position\0").expect("NULL terminated"); let tex_coords = CStr::from_bytes_with_nul(b"tex_coords\0").expect("NULL terminated"); let tex = CStr::from_bytes_with_nul(b"tex\0").expect("NULL terminated"); @@ -392,17 +382,35 @@ unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result Result { + let program = link_program(gl, shaders::VERTEX_SHADER_SOLID, shaders::FRAGMENT_SHADER_SOLID)?; + + let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated"); + let color = CStr::from_bytes_with_nul(b"color\0").expect("NULL terminated"); + let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated"); + let position = CStr::from_bytes_with_nul(b"position\0").expect("NULL terminated"); + + Ok(Gles2SolidProgram { + program, + uniform_matrix: gl.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar), + uniform_color: gl.GetUniformLocation(program, color.as_ptr() as *const ffi::types::GLchar), + attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar), + attrib_position: gl.GetAttribLocation(program, position.as_ptr() as *const ffi::types::GLchar), + }) +} + impl Gles2Renderer { /// Creates a new OpenGL ES 2 renderer from a given [`EGLContext`](crate::backend::egl::EGLBuffer). /// @@ -473,6 +481,16 @@ impl Gles2Renderer { if gl_version < version::GLES_3_0 && !exts.iter().any(|ext| ext == "GL_EXT_unpack_subimage") { return Err(Gles2Error::GLExtensionNotSupported(&["GL_EXT_unpack_subimage"])); } + // required for instanced damage rendering + if gl_version < version::GLES_3_0 + && !(exts.iter().any(|ext| ext == "GL_EXT_instanced_arrays") + && exts.iter().any(|ext| ext == "GL_EXT_draw_instanced")) + { + return Err(Gles2Error::GLExtensionNotSupported(&[ + "GL_EXT_instanced_arrays", + "GL_EXT_draw_instanced", + ])); + } let logger = if exts.iter().any(|ext| ext == "GL_KHR_debug") { let logger = Box::into_raw(Box::new(log.clone())); @@ -487,21 +505,33 @@ impl Gles2Renderer { (gl, exts, logger) }; - let programs = [ + let tex_programs = [ texture_program(&gl, shaders::FRAGMENT_SHADER_ABGR)?, texture_program(&gl, shaders::FRAGMENT_SHADER_XBGR)?, texture_program(&gl, shaders::FRAGMENT_SHADER_EXTERNAL)?, ]; + let solid_program = solid_program(&gl)?; + + let mut vbos = [0; 2]; + gl.GenBuffers(2, vbos.as_mut_ptr()); + gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]); + gl.BufferData( + ffi::ARRAY_BUFFER, + (std::mem::size_of::() * VERTS.len()) as isize, + VERTS.as_ptr() as *const _, + ffi::STATIC_DRAW, + ); + gl.BindBuffer(ffi::ARRAY_BUFFER, 0); let (tx, rx) = channel(); let mut renderer = Gles2Renderer { - id: RENDERER_COUNTER.fetch_add(1, Ordering::SeqCst), gl, egl: context, #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] egl_reader: None, extensions: exts, - programs, + tex_programs, + solid_program, target_buffer: None, target_surface: None, buffers: Vec::new(), @@ -509,6 +539,7 @@ impl Gles2Renderer { dmabuf_cache: std::collections::HashMap::new(), destruction_callback: rx, destruction_callback_sender: tx, + vbos, logger_ptr, logger: log, _not_send: std::ptr::null_mut(), @@ -966,9 +997,11 @@ impl Drop for Gles2Renderer { unsafe { if self.egl.make_current().is_ok() { self.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0); - for program in &self.programs { + for program in &self.tex_programs { self.gl.DeleteProgram(program.program); } + self.gl.DeleteProgram(self.solid_program.program); + self.gl.DeleteBuffers(2, self.vbos.as_ptr()); if self.extensions.iter().any(|ext| ext == "GL_KHR_debug") { self.gl.Disable(ffi::DEBUG_OUTPUT); @@ -1082,9 +1115,12 @@ impl Renderer for Gles2Renderer { let mut frame = Gles2Frame { gl: self.gl.clone(), - programs: self.programs.clone(), + tex_programs: self.tex_programs.clone(), + solid_program: self.solid_program.clone(), // output transformation passed in by the user current_projection: flip180 * transform.matrix() * renderer, + vbos: self.vbos, + size, }; let result = rendering(self, &mut frame); @@ -1124,17 +1160,85 @@ impl Frame for Gles2Frame { type Error = Gles2Error; type TextureId = Gles2Texture; - fn clear(&mut self, color: [f32; 4], at: Option>) -> Result<(), Self::Error> { + fn clear(&mut self, color: [f32; 4], at: &[Rectangle]) -> Result<(), Self::Error> { + if at.is_empty() { + return Ok(()); + } + + let mut mat = Matrix3::::identity(); + mat = mat * Matrix3::from_translation(Vector2::new(0.0, 0.0)); + mat = mat * Matrix3::from_nonuniform_scale(self.size.w as f32, self.size.h as f32); + mat = self.current_projection * mat; + + let damage = at + .iter() + .map(|rect| { + [ + rect.loc.x as f32 / self.size.w as f32, + rect.loc.y as f32 / self.size.h as f32, + rect.size.w as f32 / self.size.w as f32, + rect.size.h as f32 / self.size.h as f32, + ] + }) + .flatten() + .collect::>(); + unsafe { - if let Some(rect) = at { - self.gl.Enable(ffi::SCISSOR_TEST); - self.gl.Scissor(rect.loc.x, rect.loc.y, rect.size.w, rect.size.h); - } - self.gl.ClearColor(color[0], color[1], color[2], color[3]); - self.gl.Clear(ffi::COLOR_BUFFER_BIT); - if at.is_some() { - self.gl.Disable(ffi::SCISSOR_TEST); - } + self.gl.UseProgram(self.solid_program.program); + self.gl.Uniform4f( + self.solid_program.uniform_color, + color[0], + color[1], + color[2], + color[3], + ); + self.gl + .UniformMatrix3fv(self.solid_program.uniform_matrix, 1, ffi::FALSE, mat.as_ptr()); + + self.gl + .EnableVertexAttribArray(self.solid_program.attrib_vert as u32); + self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[0]); + self.gl.VertexAttribPointer( + self.solid_program.attrib_vert as u32, + 2, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); + + self.gl + .EnableVertexAttribArray(self.solid_program.attrib_position as u32); + self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[1]); + self.gl.BufferData( + ffi::ARRAY_BUFFER, + (std::mem::size_of::() * damage.len()) as isize, + damage.as_ptr() as *const _, + ffi::STREAM_DRAW, + ); + + self.gl.VertexAttribPointer( + self.solid_program.attrib_position as u32, + 4, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); + self.gl + .VertexAttribDivisor(self.solid_program.attrib_vert as u32, 0); + + self.gl + .VertexAttribDivisor(self.solid_program.attrib_position as u32, 1); + + self.gl + .DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, at.len() as i32); + + self.gl.BindBuffer(ffi::ARRAY_BUFFER, 0); + self.gl + .DisableVertexAttribArray(self.solid_program.attrib_vert as u32); + self.gl + .DisableVertexAttribArray(self.solid_program.attrib_position as u32); } Ok(()) @@ -1145,6 +1249,7 @@ impl Frame for Gles2Frame { texture: &Self::TextureId, src: Rectangle, dest: Rectangle, + damage: &[Rectangle], transform: Transform, alpha: f32, ) -> Result<(), Self::Error> { @@ -1168,7 +1273,7 @@ impl Frame for Gles2Frame { let texture_mat = Matrix3::from_nonuniform_scale(tex_size.w as f32, tex_size.h as f32) .invert() .unwrap(); - let verts = [ + let tex_verts = [ (texture_mat * Vector3::new((src.loc.x + src.size.w) as f32, src.loc.y as f32, 0.0)).truncate(), // top-right (texture_mat * Vector3::new(src.loc.x as f32, src.loc.y as f32, 0.0)).truncate(), // top-left (texture_mat @@ -1180,7 +1285,21 @@ impl Frame for Gles2Frame { .truncate(), // bottom-right (texture_mat * Vector3::new(src.loc.x as f32, (src.loc.y + src.size.h) as f32, 0.0)).truncate(), // bottom-left ]; - self.render_texture(texture, mat, verts, alpha) + + let damage = damage + .iter() + .map(|rect| { + [ + rect.loc.x as f32 / dest.size.w as f32, + rect.loc.y as f32 / dest.size.h as f32, + rect.size.w as f32 / dest.size.w as f32, + rect.size.h as f32 / dest.size.h as f32, + ] + }) + .flatten() + .collect::>(); + + self.render_texture(texture, mat, Some(&damage), tex_verts, alpha) } } @@ -1191,6 +1310,7 @@ impl Gles2Frame { &mut self, tex: &Gles2Texture, mut matrix: Matrix3, + instances: Option<&[ffi::types::GLfloat]>, tex_coords: [Vector2; 4], alpha: f32, ) -> Result<(), Gles2Error> { @@ -1209,33 +1329,27 @@ impl Gles2Frame { self.gl.BindTexture(target, tex.0.texture); self.gl .TexParameteri(target, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32); - self.gl.UseProgram(self.programs[tex.0.texture_kind].program); + self.gl.UseProgram(self.tex_programs[tex.0.texture_kind].program); self.gl - .Uniform1i(self.programs[tex.0.texture_kind].uniform_tex, 0); + .Uniform1i(self.tex_programs[tex.0.texture_kind].uniform_tex, 0); self.gl.UniformMatrix3fv( - self.programs[tex.0.texture_kind].uniform_matrix, + self.tex_programs[tex.0.texture_kind].uniform_matrix, 1, ffi::FALSE, matrix.as_ptr(), ); self.gl.Uniform1i( - self.programs[tex.0.texture_kind].uniform_invert_y, + self.tex_programs[tex.0.texture_kind].uniform_invert_y, if tex.0.y_inverted { 1 } else { 0 }, ); self.gl - .Uniform1f(self.programs[tex.0.texture_kind].uniform_alpha, alpha); + .Uniform1f(self.tex_programs[tex.0.texture_kind].uniform_alpha, alpha); + self.gl + .EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32); self.gl.VertexAttribPointer( - self.programs[tex.0.texture_kind].attrib_position as u32, - 2, - ffi::FLOAT, - ffi::FALSE, - 0, - VERTS.as_ptr() as *const _, - ); - self.gl.VertexAttribPointer( - self.programs[tex.0.texture_kind].attrib_tex_coords as u32, + self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32, 2, ffi::FLOAT, ffi::FALSE, @@ -1244,18 +1358,55 @@ impl Gles2Frame { ); self.gl - .EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32); - self.gl - .EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32); + .EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_vert as u32); + self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[0]); + self.gl.VertexAttribPointer( + self.solid_program.attrib_vert as u32, + 2, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); - self.gl.DrawArrays(ffi::TRIANGLE_STRIP, 0, 4); + let damage = instances.unwrap_or(&[0.0, 0.0, 1.0, 1.0]); + self.gl + .EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_position as u32); + self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[1]); + self.gl.BufferData( + ffi::ARRAY_BUFFER, + (std::mem::size_of::() * damage.len()) as isize, + damage.as_ptr() as *const _, + ffi::STREAM_DRAW, + ); + + let count = (damage.len() / 4) as i32; + self.gl.VertexAttribPointer( + self.tex_programs[tex.0.texture_kind].attrib_position as u32, + 4, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); self.gl - .DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32); + .VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_vert as u32, 0); self.gl - .DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32); + .VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32, 0); + self.gl + .VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_position as u32, 1); + self.gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, count); + + self.gl.BindBuffer(ffi::ARRAY_BUFFER, 0); self.gl.BindTexture(target, 0); + self.gl + .DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32); + self.gl + .DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_vert as u32); + self.gl + .DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_position as u32); } Ok(()) diff --git a/src/backend/renderer/gles2/shaders.rs b/src/backend/renderer/gles2/shaders.rs index f17ef6c..229ec22 100644 --- a/src/backend/renderer/gles2/shaders.rs +++ b/src/backend/renderer/gles2/shaders.rs @@ -2,29 +2,48 @@ * OpenGL Shaders */ pub const VERTEX_SHADER: &str = r#" + #version 100 uniform mat3 matrix; uniform bool invert_y; -attribute vec2 position; + +attribute vec2 vert; attribute vec2 tex_coords; +attribute vec4 position; + varying vec2 v_tex_coords; + +mat2 scale(vec2 scale_vec){ + return mat2( + scale_vec.x, 0.0, + 0.0, scale_vec.y + ); +} + void main() { - gl_Position = vec4(matrix * vec3(position, 1.0), 1.0); if (invert_y) { v_tex_coords = vec2(tex_coords.x, 1.0 - tex_coords.y); } else { v_tex_coords = tex_coords; } -}"#; + + vec2 transform_translation = position.xy; + vec2 transform_scale = position.zw; + v_tex_coords = (vec3((tex_coords * scale(transform_scale)) + transform_translation, 1.0)).xy; + gl_Position = vec4(matrix * vec3((vert * scale(transform_scale)) + transform_translation, 1.0), 1.0); +} +"#; pub const FRAGMENT_COUNT: usize = 3; pub const FRAGMENT_SHADER_ABGR: &str = r#" #version 100 + precision mediump float; uniform sampler2D tex; uniform float alpha; varying vec2 v_tex_coords; + void main() { gl_FragColor = texture2D(tex, v_tex_coords) * alpha; } @@ -32,10 +51,12 @@ void main() { pub const FRAGMENT_SHADER_XBGR: &str = r#" #version 100 + precision mediump float; uniform sampler2D tex; uniform float alpha; varying vec2 v_tex_coords; + void main() { gl_FragColor = vec4(texture2D(tex, v_tex_coords).rgb, 1.0) * alpha; } @@ -44,11 +65,45 @@ void main() { pub const FRAGMENT_SHADER_EXTERNAL: &str = r#" #version 100 #extension GL_OES_EGL_image_external : require + precision mediump float; uniform samplerExternalOES tex; uniform float alpha; varying vec2 v_tex_coords; + void main() { gl_FragColor = texture2D(tex, v_tex_coords) * alpha; } "#; + +pub const VERTEX_SHADER_SOLID: &str = r#" +#version 100 + +uniform mat3 matrix; +attribute vec2 vert; +attribute vec4 position; + +mat2 scale(vec2 scale_vec){ + return mat2( + scale_vec.x, 0.0, + 0.0, scale_vec.y + ); +} + +void main() { + vec2 transform_translation = position.xy; + vec2 transform_scale = position.zw; + gl_Position = vec4(matrix * vec3((vert * scale(transform_scale)) + transform_translation, 1.0), 1.0); +} +"#; + +pub const FRAGMENT_SHADER_SOLID: &str = r#" +#version 100 + +precision mediump float; +uniform vec4 color; + +void main() { + gl_FragColor = color; +} +"#; diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index c71d770..e02de36 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -173,7 +173,7 @@ pub trait Frame { /// /// This operation is only valid in between a `begin` and `finish`-call. /// If called outside this operation may error-out, do nothing or modify future rendering results in any way. - fn clear(&mut self, color: [f32; 4], at: Option>) -> Result<(), Self::Error>; + fn clear(&mut self, color: [f32; 4], at: &[Rectangle]) -> Result<(), Self::Error>; /// Render a texture to the current target as a flat 2d-plane at a given /// position and applying the given transformation with the given alpha value. @@ -185,6 +185,7 @@ pub trait Frame { texture_scale: i32, output_scale: f64, src_transform: Transform, + damage: &[Rectangle], alpha: f32, ) -> Result<(), Self::Error> { self.render_texture_from_to( @@ -198,6 +199,7 @@ pub trait Frame { .to_f64() .to_physical(output_scale), ), + damage, src_transform, alpha, ) @@ -211,6 +213,7 @@ pub trait Frame { texture: &Self::TextureId, src: Rectangle, dst: Rectangle, + damage: &[Rectangle], src_transform: Transform, alpha: f32, ) -> Result<(), Self::Error>; diff --git a/src/backend/renderer/utils/mod.rs b/src/backend/renderer/utils/mod.rs index e9d4723..6949e88 100644 --- a/src/backend/renderer/utils/mod.rs +++ b/src/backend/renderer/utils/mod.rs @@ -1,6 +1,6 @@ use crate::{ backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, - utils::{Logical, Physical, Point, Size}, + utils::{Buffer, Logical, Physical, Point, Rectangle, Size}, wayland::compositor::{ is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, SurfaceAttributes, TraversalAction, @@ -66,12 +66,14 @@ pub fn on_commit_buffer_handler(surface: &WlSurface) { } } +/// TODO pub fn draw_surface_tree( renderer: &mut R, frame: &mut F, surface: &WlSurface, scale: f64, location: Point, + damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> where @@ -81,6 +83,10 @@ where T: Texture + 'static, { let mut result = Ok(()); + let damage = damage + .iter() + .map(|geo| geo.to_f64().to_physical(scale).to_i32_round()) + .collect::>(); with_surface_tree_upward( surface, location, @@ -137,15 +143,33 @@ where if let Some(data) = states.data_map.get::>() { let mut data = data.borrow_mut(); let buffer_scale = data.buffer_scale; + let buffer_dimensions = data.buffer_dimensions; let attributes = states.cached_state.current::(); if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::()) { // we need to re-extract the subsurface offset, as the previous closure // only passes it to our children + let mut surface_offset = (0, 0).into(); if states.role == Some("subsurface") { let current = states.cached_state.current::(); + surface_offset = current.location; location += current.location; } + let rect = Rectangle::::from_loc_and_size( + surface_offset.to_f64().to_physical(scale).to_i32_round(), + buffer_dimensions.unwrap(), + ); + let new_damage = damage + .iter() + .cloned() + .filter(|geo| geo.overlaps(rect)) + .map(|geo| geo.intersection(rect)) + .map(|mut geo| { + geo.loc -= rect.loc; + geo + }) + .collect::>(); + // TODO: Take wp_viewporter into account if let Err(err) = frame.render_texture_at( texture, @@ -153,6 +177,7 @@ where buffer_scale, scale, attributes.buffer_transform.into(), + &new_damage, 1.0, ) { result = Err(err); diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 82dfe7d..aa2253c 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -450,6 +450,7 @@ impl Space { } // Optimize the damage for rendering + damage.dedup(); damage.retain(|rect| rect.overlaps(output_geo)); damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0); for rect in damage.clone().iter() { @@ -460,6 +461,18 @@ impl Space { damage.retain(|other| !rect.contains_rect(*other)); } } + damage = damage.into_iter().fold(Vec::new(), |mut new_damage, rect| { + if let Some(existing) = new_damage.iter_mut().find(|other| rect.overlaps(**other)) { + *existing = existing.merge(rect); + } else { + new_damage.push(rect); + } + new_damage + }); + + if damage.is_empty() { + return Ok(false); + } let output_transform: Transform = output.current_transform().into(); if let Err(err) = renderer.render( @@ -471,13 +484,14 @@ impl Space { output_transform, |renderer, frame| { // First clear all damaged regions - for geo in &damage { - slog::trace!(self.logger, "Clearing at {:?}", geo); - frame.clear( - clear_color, - Some(geo.to_f64().to_physical(state.render_scale).to_i32_ceil()), - )?; - } + slog::trace!(self.logger, "Clearing at {:#?}", damage); + frame.clear( + clear_color, + &damage + .iter() + .map(|geo| geo.to_f64().to_physical(state.render_scale).to_i32_round()) + .collect::>(), + )?; // Then re-draw all window overlapping with a damage rect. for window in self.windows.iter() { @@ -485,8 +499,27 @@ impl Space { let mut loc = window_loc(window, &self.id); if damage.iter().any(|geo| wgeo.overlaps(*geo)) { loc -= output_geo.loc; - slog::trace!(self.logger, "Rendering window at {:?}", wgeo); - draw_window(renderer, frame, window, state.render_scale, loc, &self.logger)?; + let win_damage = damage + .iter() + .filter(|geo| geo.overlaps(wgeo)) + .map(|geo| geo.intersection(wgeo)) + .map(|geo| Rectangle::from_loc_and_size(geo.loc - wgeo.loc, geo.size)) + .collect::>(); + slog::trace!( + self.logger, + "Rendering window at {:?} with damage {:#?}", + wgeo, + damage + ); + draw_window( + renderer, + frame, + window, + state.render_scale, + loc, + &win_damage, + &self.logger, + )?; window_state(self.id, window).drawn = true; } } @@ -512,8 +545,7 @@ impl Space { .collect(); state.old_damage.push_front(new_damage); - // Return if we actually rendered something - Ok(!damage.is_empty()) + Ok(true) } pub fn send_frames(&self, all: bool, time: u32) { diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 3da2cb4..6e8a5bc 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -471,6 +471,7 @@ pub fn draw_window( window: &Window, scale: f64, location: Point, + damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> where @@ -480,14 +481,30 @@ where T: Texture + 'static, { if let Some(surface) = window.toplevel().get_surface() { - draw_surface_tree(renderer, frame, surface, scale, location, log)?; + draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?; for (popup, p_location) in PopupManager::popups_for_surface(surface) .ok() .into_iter() .flatten() { if let Some(surface) = popup.get_surface() { - draw_surface_tree(renderer, frame, surface, scale, location + p_location, log)?; + let damage = damage + .iter() + .cloned() + .map(|mut geo| { + geo.loc -= p_location; + geo + }) + .collect::>(); + draw_surface_tree( + renderer, + frame, + surface, + scale, + location + p_location, + &damage, + log, + )?; } } } From cea88fdde039856ebed7d5fac79595d4798f5502 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Mon, 13 Dec 2021 22:06:13 +0100 Subject: [PATCH 13/52] desktop: Various cleanups --- src/desktop/mod.rs | 2 + src/desktop/output.rs | 47 +++++++++ src/desktop/space.rs | 52 +++++----- src/desktop/utils.rs | 199 +++++++++++++++++++++++++++++++++++++ src/desktop/window.rs | 203 +------------------------------------- src/wayland/output/mod.rs | 9 +- 6 files changed, 282 insertions(+), 230 deletions(-) create mode 100644 src/desktop/output.rs create mode 100644 src/desktop/utils.rs diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index fb33afc..da44617 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -1,7 +1,9 @@ // TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings #![allow(missing_docs, clippy::all)] +mod output; mod popup; mod space; +pub mod utils; mod window; pub use self::popup::*; diff --git a/src/desktop/output.rs b/src/desktop/output.rs new file mode 100644 index 0000000..de69c2b --- /dev/null +++ b/src/desktop/output.rs @@ -0,0 +1,47 @@ +use crate::{ + utils::{Logical, Point, Rectangle}, + wayland::output::Output, +}; +use indexmap::IndexMap; +use wayland_server::protocol::wl_surface::WlSurface; + +use std::{ + cell::{RefCell, RefMut}, + collections::{HashMap, VecDeque}, +}; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub(super) enum ToplevelId { + Xdg(usize), +} + +impl ToplevelId { + pub fn is_xdg(&self) -> bool { + match self { + ToplevelId::Xdg(_) => true, + _ => false, + } + } +} + +#[derive(Clone, Default)] +pub(super) struct OutputState { + pub location: Point, + pub render_scale: f64, + + // damage and last_state are in space coordinate space + pub old_damage: VecDeque>>, + pub last_state: IndexMap>, + + // surfaces for tracking enter and leave events + pub surfaces: Vec, +} + +pub(super) type OutputUserdata = RefCell>; +pub(super) fn output_state(space: usize, o: &Output) -> RefMut<'_, OutputState> { + let userdata = o.user_data(); + userdata.insert_if_missing(OutputUserdata::default); + RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { + m.entry(space).or_default() + }) +} diff --git a/src/desktop/space.rs b/src/desktop/space.rs index aa2253c..39a9eeb 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -1,6 +1,7 @@ use super::{draw_window, Window}; use crate::{ backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform}, + desktop::output::*, utils::{Logical, Point, Rectangle}, wayland::{ compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction}, @@ -55,28 +56,6 @@ fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> { }) } -#[derive(Clone, Default)] -struct OutputState { - location: Point, - render_scale: f64, - - // damage and last_state are in space coordinate space - old_damage: VecDeque>>, - last_state: IndexMap>, - - // surfaces for tracking enter and leave events - surfaces: Vec, -} - -type OutputUserdata = RefCell>; -fn output_state(space: usize, o: &Output) -> RefMut<'_, OutputState> { - let userdata = o.user_data(); - userdata.insert_if_missing(OutputUserdata::default); - RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { - m.entry(space).or_default() - }) -} - // TODO: Maybe replace UnmanagedResource if nothing else comes up? #[derive(Debug, thiserror::Error)] pub enum SpaceError { @@ -118,12 +97,17 @@ impl Space { /// /// This can safely be called on an already mapped window pub fn map_window(&mut self, window: &Window, location: Point) { - self.raise_window(window); + self.insert_window(window); window_state(self.id, window).location = location; } pub fn raise_window(&mut self, window: &Window) { - self.windows.shift_remove(window); + if self.windows.shift_remove(window) { + self.insert_window(window); + } + } + + fn insert_window(&mut self, window: &Window) { self.windows.insert(window.clone()); // TODO: should this be handled by us? @@ -144,7 +128,7 @@ impl Space { } /// Iterate window in z-order back to front - pub fn windows(&self) -> impl Iterator { + pub fn windows(&self) -> impl DoubleEndedIterator { self.windows.iter() } @@ -156,6 +140,14 @@ impl Space { }) } + /// Get a reference to the output under a given point, if any + pub fn output_under(&self, point: Point) -> Option<&Output> { + self.outputs.iter().rev().find(|o| { + let bbox = self.output_geometry(o); + bbox.map(|bbox| bbox.to_f64().contains(point)).unwrap_or(false) + }) + } + pub fn window_for_surface(&self, surface: &WlSurface) -> Option<&Window> { if !surface.as_ref().is_alive() { return None; @@ -405,7 +397,7 @@ impl Space { .last_state .iter() .filter_map(|(id, w)| { - if !self.windows.iter().any(|w| w.0.id == *id) { + if !self.windows.iter().any(|w| ToplevelId::Xdg(w.0.id) == *id) { Some(*w) } else { None @@ -413,14 +405,14 @@ impl Space { }) .collect::>>() { - slog::trace!(self.logger, "Removing window at: {:?}", old_window); + slog::trace!(self.logger, "Removing toplevel at: {:?}", old_window); damage.push(old_window); } // lets iterate front to back and figure out, what new windows or unmoved windows we have - for window in self.windows.iter().rev() { + for window in self.windows.iter() { let geo = window_rect_with_popups(window, &self.id); - let old_geo = state.last_state.get(&window.0.id).cloned(); + let old_geo = state.last_state.get(&ToplevelId::Xdg(window.0.id)).cloned(); // window was moved or resized if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { @@ -540,7 +532,7 @@ impl Space { .iter() .map(|window| { let wgeo = window_rect_with_popups(window, &self.id); - (window.0.id, wgeo) + (ToplevelId::Xdg(window.0.id), wgeo) }) .collect(); state.old_damage.push_front(new_damage); diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs new file mode 100644 index 0000000..8a08fdc --- /dev/null +++ b/src/desktop/utils.rs @@ -0,0 +1,199 @@ +use crate::{ + backend::renderer::utils::SurfaceState, + utils::{Logical, Point, Rectangle, Size}, + wayland::compositor::{ + with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState, + SurfaceAttributes, TraversalAction, + }, +}; +use wayland_server::protocol::wl_surface; + +use std::cell::RefCell; + +impl SurfaceState { + /// Returns the size of the surface. + pub fn size(&self) -> Option> { + self.buffer_dimensions + .map(|dims| dims.to_logical(self.buffer_scale)) + } + + fn contains_point(&self, attrs: &SurfaceAttributes, point: Point) -> bool { + let size = match self.size() { + None => return false, // If the surface has no size, it can't have an input region. + Some(size) => size, + }; + + let rect = Rectangle { + loc: (0, 0).into(), + size, + } + .to_f64(); + + // The input region is always within the surface itself, so if the surface itself doesn't contain the + // point we can return false. + if !rect.contains(point) { + return false; + } + + // If there's no input region, we're done. + if attrs.input_region.is_none() { + return true; + } + + attrs + .input_region + .as_ref() + .unwrap() + .contains(point.to_i32_floor()) + } +} + +pub fn bbox_from_surface_tree

(surface: &wl_surface::WlSurface, location: P) -> Rectangle +where + P: Into>, +{ + let location = location.into(); + let mut bounding_box = Rectangle::from_loc_and_size(location, (0, 0)); + with_surface_tree_downward( + surface, + location, + |_, states, loc: &Point| { + let mut loc = *loc; + let data = states.data_map.get::>(); + + if let Some(size) = data.and_then(|d| d.borrow().size()) { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + loc += current.location; + } + + // Update the bounding box. + bounding_box = bounding_box.merge(Rectangle::from_loc_and_size(loc, size)); + + TraversalAction::DoChildren(loc) + } else { + // If the parent surface is unmapped, then the child surfaces are hidden as + // well, no need to consider them here. + TraversalAction::SkipChildren + } + }, + |_, _, _| {}, + |_, _, _| true, + ); + bounding_box +} + +pub fn damage_from_surface_tree

( + surface: &wl_surface::WlSurface, + location: P, +) -> Vec> +where + P: Into>, +{ + let mut damage = Vec::new(); + with_surface_tree_upward( + surface, + location.into(), + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let data = data.borrow(); + if data.texture.is_none() { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + } + } + TraversalAction::DoChildren(location) + }, + |_surface, states, location| { + let mut location = *location; + if let Some(data) = states.data_map.get::>() { + let data = data.borrow(); + let attributes = states.cached_state.current::(); + + if data.texture.is_none() { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + + damage.extend(attributes.damage.iter().map(|dmg| { + let mut rect = match dmg { + Damage::Buffer(rect) => rect.to_logical(attributes.buffer_scale), + Damage::Surface(rect) => *rect, + }; + rect.loc += location; + rect + })); + } + } + }, + |_, _, _| true, + ); + damage +} + +pub fn under_from_surface_tree

( + surface: &wl_surface::WlSurface, + point: Point, + location: P, +) -> Option<(wl_surface::WlSurface, Point)> +where + P: Into>, +{ + let found = RefCell::new(None); + with_surface_tree_downward( + surface, + location.into(), + |wl_surface, states, location: &Point| { + let mut location = *location; + let data = states.data_map.get::>(); + + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + location += current.location; + } + + let contains_the_point = data + .map(|data| { + data.borrow() + .contains_point(&*states.cached_state.current(), point - location.to_f64()) + }) + .unwrap_or(false); + if contains_the_point { + *found.borrow_mut() = Some((wl_surface.clone(), location)); + } + + TraversalAction::DoChildren(location) + }, + |_, _, _| {}, + |_, _, _| { + // only continue if the point is not found + found.borrow().is_none() + }, + ); + found.into_inner() +} + +pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) { + with_surface_tree_downward( + surface, + (), + |_, _, &()| TraversalAction::DoChildren(()), + |_surf, states, &()| { + // the surface may not have any user_data if it is a subsurface and has not + // yet been commited + for callback in states + .cached_state + .current::() + .frame_callbacks + .drain(..) + { + callback.done(time); + } + }, + |_, _, &()| true, + ); +} diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 6e8a5bc..61a8478 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -1,20 +1,13 @@ use crate::{ - backend::renderer::{ - utils::{draw_surface_tree, SurfaceState}, - Frame, ImportAll, Renderer, Texture, - }, - desktop::PopupManager, - utils::{Logical, Point, Rectangle, Size}, + backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture}, + desktop::{utils::*, PopupManager}, + utils::{Logical, Point, Rectangle}, wayland::{ - compositor::{ - with_states, with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState, - SurfaceAttributes, TraversalAction, - }, + compositor::with_states, shell::xdg::{SurfaceCachedState, ToplevelSurface}, }, }; use std::{ - cell::RefCell, collections::HashSet, hash::{Hash, Hasher}, rc::Rc, @@ -25,7 +18,7 @@ use std::{ }; use wayland_commons::user_data::UserDataMap; use wayland_protocols::xdg_shell::server::xdg_toplevel; -use wayland_server::protocol::{wl_buffer, wl_surface}; +use wayland_server::protocol::wl_surface; static WINDOW_ID: AtomicUsize = AtomicUsize::new(0); lazy_static::lazy_static! { @@ -101,44 +94,6 @@ impl Kind { } } -impl SurfaceState { - /// Returns the size of the surface. - pub fn size(&self) -> Option> { - self.buffer_dimensions - .map(|dims| dims.to_logical(self.buffer_scale)) - } - - fn contains_point(&self, attrs: &SurfaceAttributes, point: Point) -> bool { - let size = match self.size() { - None => return false, // If the surface has no size, it can't have an input region. - Some(size) => size, - }; - - let rect = Rectangle { - loc: (0, 0).into(), - size, - } - .to_f64(); - - // The input region is always within the surface itself, so if the surface itself doesn't contain the - // point we can return false. - if !rect.contains(point) { - return false; - } - - // If there's no input region, we're done. - if attrs.input_region.is_none() { - return true; - } - - attrs - .input_region - .as_ref() - .unwrap() - .contains(point.to_i32_floor()) - } -} - #[derive(Debug)] pub(super) struct WindowInner { pub(super) id: usize, @@ -317,154 +272,6 @@ impl Window { } } -fn damage_from_surface_tree

(surface: &wl_surface::WlSurface, location: P) -> Vec> -where - P: Into>, -{ - let mut damage = Vec::new(); - with_surface_tree_upward( - surface, - location.into(), - |_surface, states, location| { - let mut location = *location; - if let Some(data) = states.data_map.get::>() { - let data = data.borrow(); - if data.texture.is_none() { - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } - return TraversalAction::DoChildren(location); - } - } - TraversalAction::SkipChildren - }, - |_surface, states, location| { - let mut location = *location; - if let Some(data) = states.data_map.get::>() { - let data = data.borrow(); - let attributes = states.cached_state.current::(); - - if data.texture.is_none() { - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } - - damage.extend(attributes.damage.iter().map(|dmg| { - let mut rect = match dmg { - Damage::Buffer(rect) => rect.to_logical(attributes.buffer_scale), - Damage::Surface(rect) => *rect, - }; - rect.loc += location; - rect - })); - } - } - }, - |_, _, _| true, - ); - damage -} - -fn bbox_from_surface_tree

(surface: &wl_surface::WlSurface, location: P) -> Rectangle -where - P: Into>, -{ - let location = location.into(); - let mut bounding_box = Rectangle::from_loc_and_size(location, (0, 0)); - with_surface_tree_downward( - surface, - location, - |_, states, loc: &Point| { - let mut loc = *loc; - let data = states.data_map.get::>(); - - if let Some(size) = data.and_then(|d| d.borrow().size()) { - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - loc += current.location; - } - - // Update the bounding box. - bounding_box = bounding_box.merge(Rectangle::from_loc_and_size(loc, size)); - - TraversalAction::DoChildren(loc) - } else { - // If the parent surface is unmapped, then the child surfaces are hidden as - // well, no need to consider them here. - TraversalAction::SkipChildren - } - }, - |_, _, _| {}, - |_, _, _| true, - ); - bounding_box -} - -pub fn under_from_surface_tree

( - surface: &wl_surface::WlSurface, - point: Point, - location: P, -) -> Option<(wl_surface::WlSurface, Point)> -where - P: Into>, -{ - let found = RefCell::new(None); - with_surface_tree_downward( - surface, - location.into(), - |wl_surface, states, location: &Point| { - let mut location = *location; - let data = states.data_map.get::>(); - - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } - - let contains_the_point = data - .map(|data| { - data.borrow() - .contains_point(&*states.cached_state.current(), point - location.to_f64()) - }) - .unwrap_or(false); - if contains_the_point { - *found.borrow_mut() = Some((wl_surface.clone(), location)); - } - - TraversalAction::DoChildren(location) - }, - |_, _, _| {}, - |_, _, _| { - // only continue if the point is not found - found.borrow().is_none() - }, - ); - found.into_inner() -} - -fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) { - with_surface_tree_downward( - surface, - (), - |_, _, &()| TraversalAction::DoChildren(()), - |_surf, states, &()| { - // the surface may not have any user_data if it is a subsurface and has not - // yet been commited - for callback in states - .cached_state - .current::() - .frame_callbacks - .drain(..) - { - callback.done(time); - } - }, - |_, _, &()| true, - ); -} - pub fn draw_window( renderer: &mut R, frame: &mut F, diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs index 236205a..fdfba5e 100644 --- a/src/wayland/output/mod.rs +++ b/src/wayland/output/mod.rs @@ -100,7 +100,7 @@ pub struct PhysicalProperties { } #[derive(Debug)] -struct Inner { +pub(crate) struct Inner { name: String, log: ::slog::Logger, instances: Vec, @@ -170,7 +170,7 @@ impl Inner { /// about any change in the properties of this output. #[derive(Debug, Clone)] pub struct Output { - inner: InnerType, + pub(crate) inner: InnerType, } impl Output { @@ -274,6 +274,11 @@ impl Output { self.inner.0.lock().unwrap().transform } + /// Returns the currenly advertised scale of the output + pub fn current_scale(&self) -> i32 { + self.inner.0.lock().unwrap().scale + } + /// Returns the name of the output pub fn name(&self) -> String { self.inner.0.lock().unwrap().name.clone() From 3b39d780fe1bf8548cc3a41bd1ba798275bd9ac0 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Mon, 13 Dec 2021 22:06:39 +0100 Subject: [PATCH 14/52] desktop: layer-shell support --- src/desktop/layer.rs | 501 +++++++++++++++++++++++++++ src/desktop/mod.rs | 2 + src/desktop/output.rs | 8 + src/desktop/space.rs | 130 ++++++- src/wayland/shell/wlr_layer/types.rs | 2 +- 5 files changed, 639 insertions(+), 4 deletions(-) create mode 100644 src/desktop/layer.rs diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs new file mode 100644 index 0000000..7221631 --- /dev/null +++ b/src/desktop/layer.rs @@ -0,0 +1,501 @@ +use crate::{ + backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture}, + desktop::{utils::*, PopupManager}, + utils::{user_data::UserDataMap, Logical, Point, Rectangle}, + wayland::{ + compositor::with_states, + output::{Inner as OutputInner, Output}, + shell::wlr_layer::{ + Anchor, ExclusiveZone, KeyboardInteractivity, Layer as WlrLayer, LayerSurface as WlrLayerSurface, + LayerSurfaceCachedState, + }, + }, +}; +use indexmap::IndexSet; +use wayland_server::protocol::wl_surface::WlSurface; + +use std::{ + cell::{RefCell, RefMut}, + collections::HashSet, + hash::{Hash, Hasher}, + rc::Rc, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, Weak, + }, +}; + +// TODO: Should this be a macro? +static LAYER_ID: AtomicUsize = AtomicUsize::new(0); +lazy_static::lazy_static! { + static ref LAYER_IDS: Mutex> = Mutex::new(HashSet::new()); +} + +fn next_layer_id() -> usize { + let mut ids = LAYER_IDS.lock().unwrap(); + if ids.len() == usize::MAX { + // Theoretically the code below wraps around correctly, + // but that is hard to detect and might deadlock. + // Maybe make this a debug_assert instead? + panic!("Out of window ids"); + } + + let mut id = LAYER_ID.fetch_add(1, Ordering::SeqCst); + while ids.iter().any(|k| *k == id) { + id = LAYER_ID.fetch_add(1, Ordering::SeqCst); + } + + ids.insert(id); + id +} + +#[derive(Debug)] +pub struct LayerMap { + layers: IndexSet, + output: Weak<(Mutex, wayland_server::UserDataMap)>, + zone: Rectangle, +} + +pub fn layer_map_for_output(o: &Output) -> RefMut<'_, LayerMap> { + let userdata = o.user_data(); + let weak_output = Arc::downgrade(&o.inner); + userdata.insert_if_missing(|| { + RefCell::new(LayerMap { + layers: IndexSet::new(), + output: weak_output, + zone: Rectangle::from_loc_and_size( + (0, 0), + o.current_mode() + .map(|mode| mode.size.to_logical(o.current_scale())) + .unwrap_or((0, 0).into()), + ), + }) + }); + userdata.get::>().unwrap().borrow_mut() +} + +#[derive(Debug, thiserror::Error)] +pub enum LayerError { + #[error("Layer is already mapped to a different map")] + AlreadyMapped, +} + +impl LayerMap { + pub fn map_layer(&mut self, layer: &LayerSurface) -> Result<(), LayerError> { + if !self.layers.contains(layer) { + if layer + .0 + .userdata + .get::() + .map(|s| s.borrow().is_some()) + .unwrap_or(false) + { + return Err(LayerError::AlreadyMapped); + } + + self.layers.insert(layer.clone()); + self.arrange(); + } + Ok(()) + } + + pub fn unmap_layer(&mut self, layer: &LayerSurface) { + if self.layers.shift_remove(layer) { + let _ = layer.user_data().get::().take(); + self.arrange(); + } + } + + pub fn non_exclusive_zone(&self) -> Rectangle { + self.zone + } + + pub fn layer_geometry(&self, layer: &LayerSurface) -> Rectangle { + let mut bbox = layer.bbox_with_popups(); + let state = layer_state(layer); + bbox.loc += state.location; + bbox + } + + pub fn layer_under(&self, layer: WlrLayer, point: Point) -> Option<&LayerSurface> { + self.layers_on(layer).rev().find(|l| { + let bbox = self.layer_geometry(l); + bbox.to_f64().contains(point) + }) + } + + pub fn layers(&self) -> impl DoubleEndedIterator { + self.layers.iter() + } + + pub fn layers_on(&self, layer: WlrLayer) -> impl DoubleEndedIterator { + self.layers + .iter() + .filter(move |l| l.layer().map(|l| l == layer).unwrap_or(false)) + } + + pub fn layer_for_surface(&self, surface: &WlSurface) -> Option<&LayerSurface> { + if !surface.as_ref().is_alive() { + return None; + } + + self.layers + .iter() + .find(|w| w.get_surface().map(|x| x == surface).unwrap_or(false)) + } + + pub fn arrange(&mut self) { + if let Some(output) = self.output() { + let output_rect = Rectangle::from_loc_and_size( + (0, 0), + output + .current_mode() + .map(|mode| mode.size.to_logical(output.current_scale())) + .unwrap_or((0, 0).into()), + ); + let mut zone = output_rect.clone(); + slog::debug!( + crate::slog_or_fallback(None), + "Arranging layers into {:?}", + output_rect.size + ); + + for layer in self.layers.iter() { + let surface = if let Some(surface) = layer.get_surface() { + surface + } else { + continue; + }; + + let data = with_states(surface, |states| { + *states.cached_state.current::() + }) + .unwrap(); + + let source = match data.exclusive_zone { + ExclusiveZone::Neutral | ExclusiveZone::Exclusive(_) => &zone, + ExclusiveZone::DontCare => &output_rect, + }; + + let mut size = data.size; + if size.w == 0 { + size.w = source.size.w / 2; + } + if size.h == 0 { + size.h = source.size.h / 2; + } + if data.anchor.anchored_horizontally() { + size.w = source.size.w; + } + if data.anchor.anchored_vertically() { + size.h = source.size.h; + } + + let x = if data.anchor.contains(Anchor::LEFT) { + source.loc.x + data.margin.left + } else if data.anchor.contains(Anchor::RIGHT) { + source.loc.x + (source.size.w - size.w) - data.margin.right + } else { + source.loc.x + ((source.size.w / 2) - (size.w / 2)) + }; + + let y = if data.anchor.contains(Anchor::TOP) { + source.loc.y + data.margin.top + } else if data.anchor.contains(Anchor::BOTTOM) { + source.loc.y + (source.size.h - size.h) - data.margin.bottom + } else { + source.loc.y + ((source.size.h / 2) - (size.h / 2)) + }; + + let location: Point = (x, y).into(); + + if let ExclusiveZone::Exclusive(amount) = data.exclusive_zone { + match data.anchor { + x if x.contains(Anchor::LEFT) && !x.contains(Anchor::RIGHT) => { + zone.loc.x += amount as i32 + data.margin.left + data.margin.right + } + x if x.contains(Anchor::TOP) && !x.contains(Anchor::BOTTOM) => { + zone.loc.y += amount as i32 + data.margin.top + data.margin.bottom + } + x if x.contains(Anchor::RIGHT) && !x.contains(Anchor::LEFT) => { + zone.size.w -= amount as i32 + data.margin.left + data.margin.right + } + x if x.contains(Anchor::BOTTOM) && !x.contains(Anchor::TOP) => { + zone.size.h -= amount as i32 + data.margin.top + data.margin.top + } + _ => {} + } + } + + slog::debug!( + crate::slog_or_fallback(None), + "Setting layer to pos {:?} and size {:?}", + location, + size + ); + if layer + .0 + .surface + .with_pending_state(|state| { + state.size.replace(size).map(|old| old != size).unwrap_or(true) + }) + .unwrap() + { + layer.0.surface.send_configure(); + } + + layer_state(&layer).location = location; + } + + slog::debug!(crate::slog_or_fallback(None), "Remaining zone {:?}", zone); + self.zone = zone; + } + } + + fn output(&self) -> Option { + self.output.upgrade().map(|inner| Output { inner }) + } + + pub fn cleanup(&mut self) { + self.layers.retain(|layer| layer.alive()) + } +} + +#[derive(Debug, Default)] +pub(super) struct LayerState { + location: Point, +} + +type LayerUserdata = RefCell>; +fn layer_state(layer: &LayerSurface) -> RefMut<'_, LayerState> { + let userdata = layer.user_data(); + userdata.insert_if_missing(LayerUserdata::default); + RefMut::map(userdata.get::().unwrap().borrow_mut(), |opt| { + if opt.is_none() { + *opt = Some(LayerState::default()); + } + opt.as_mut().unwrap() + }) +} + +#[derive(Debug, Clone)] +pub struct LayerSurface(pub(crate) Rc); + +impl PartialEq for LayerSurface { + fn eq(&self, other: &Self) -> bool { + self.0.id == other.0.id + } +} + +impl Eq for LayerSurface {} + +impl Hash for LayerSurface { + fn hash(&self, state: &mut H) { + self.0.id.hash(state); + } +} + +#[derive(Debug)] +pub struct LayerSurfaceInner { + pub(crate) id: usize, + surface: WlrLayerSurface, + namespace: String, + userdata: UserDataMap, +} + +impl Drop for LayerSurfaceInner { + fn drop(&mut self) { + LAYER_IDS.lock().unwrap().remove(&self.id); + } +} + +impl LayerSurface { + pub fn new(surface: WlrLayerSurface, namespace: String) -> LayerSurface { + LayerSurface(Rc::new(LayerSurfaceInner { + id: next_layer_id(), + surface, + namespace, + userdata: UserDataMap::new(), + })) + } + + pub fn alive(&self) -> bool { + self.0.surface.alive() + } + + pub fn layer_surface(&self) -> &WlrLayerSurface { + &self.0.surface + } + + pub fn get_surface(&self) -> Option<&WlSurface> { + self.0.surface.get_surface() + } + + pub fn cached_state(&self) -> Option { + self.0.surface.get_surface().map(|surface| { + with_states(surface, |states| { + *states.cached_state.current::() + }) + .unwrap() + }) + } + + pub fn can_receive_keyboard_focus(&self) -> bool { + self.0 + .surface + .get_surface() + .map(|surface| { + with_states(surface, |states| { + match states + .cached_state + .current::() + .keyboard_interactivity + { + KeyboardInteractivity::Exclusive | KeyboardInteractivity::OnDemand => true, + KeyboardInteractivity::None => false, + } + }) + .unwrap() + }) + .unwrap_or(false) + } + + pub fn layer(&self) -> Option { + self.0.surface.get_surface().map(|surface| { + with_states(surface, |states| { + states.cached_state.current::().layer + }) + .unwrap() + }) + } + + pub fn namespace(&self) -> &str { + &self.0.namespace + } + + /// A bounding box over this window and its children. + // TODO: Cache and document when to trigger updates. If possible let space do it + pub fn bbox(&self) -> Rectangle { + if let Some(surface) = self.0.surface.get_surface() { + bbox_from_surface_tree(surface, (0, 0)) + } else { + Rectangle::from_loc_and_size((0, 0), (0, 0)) + } + } + + pub fn bbox_with_popups(&self) -> Rectangle { + let mut bounding_box = self.bbox(); + if let Some(surface) = self.0.surface.get_surface() { + for (popup, location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, location)); + } + } + } + bounding_box + } + + /// Finds the topmost surface under this point if any and returns it together with the location of this + /// surface. + pub fn surface_under(&self, point: Point) -> Option<(WlSurface, Point)> { + if let Some(surface) = self.get_surface() { + for (popup, location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(result) = popup + .get_surface() + .and_then(|surface| under_from_surface_tree(surface, point, location)) + { + return Some(result); + } + } + + under_from_surface_tree(surface, point, (0, 0)) + } else { + None + } + } + + /// Damage of all the surfaces of this layer + pub(super) fn accumulated_damage(&self) -> Vec> { + let mut damage = Vec::new(); + if let Some(surface) = self.get_surface() { + damage.extend(damage_from_surface_tree(surface, (0, 0))); + for (popup, location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + let popup_damage = damage_from_surface_tree(surface, location); + damage.extend(popup_damage); + } + } + } + damage + } + + /// Sends the frame callback to all the subsurfaces in this + /// window that requested it + pub fn send_frame(&self, time: u32) { + if let Some(wl_surface) = self.0.surface.get_surface() { + send_frames_surface_tree(wl_surface, time) + } + } + + pub fn user_data(&self) -> &UserDataMap { + &self.0.userdata + } +} + +pub fn draw_layer( + renderer: &mut R, + frame: &mut F, + layer: &LayerSurface, + scale: f64, + location: Point, + damage: &[Rectangle], + log: &slog::Logger, +) -> Result<(), R::Error> +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, +{ + if let Some(surface) = layer.get_surface() { + draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?; + for (popup, p_location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + let damage = damage + .iter() + .cloned() + .map(|mut geo| { + geo.loc -= p_location; + geo + }) + .collect::>(); + draw_surface_tree( + renderer, + frame, + surface, + scale, + location + p_location, + &damage, + log, + )?; + } + } + } + Ok(()) +} diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index da44617..e5bcbbb 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -1,11 +1,13 @@ // TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings #![allow(missing_docs, clippy::all)] +mod layer; mod output; mod popup; mod space; pub mod utils; mod window; +pub use self::layer::*; pub use self::popup::*; pub use self::space::*; pub use self::window::*; diff --git a/src/desktop/output.rs b/src/desktop/output.rs index de69c2b..88a1d43 100644 --- a/src/desktop/output.rs +++ b/src/desktop/output.rs @@ -13,6 +13,7 @@ use std::{ #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub(super) enum ToplevelId { Xdg(usize), + Layer(usize), } impl ToplevelId { @@ -22,6 +23,13 @@ impl ToplevelId { _ => false, } } + + pub fn is_layer(&self) -> bool { + match self { + ToplevelId::Layer(_) => true, + _ => false, + } + } } #[derive(Clone, Default)] diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 39a9eeb..e54ce91 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -1,11 +1,12 @@ use super::{draw_window, Window}; use crate::{ backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform}, - desktop::output::*, + desktop::{layer::*, output::*}, utils::{Logical, Point, Rectangle}, wayland::{ compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction}, output::Output, + shell::wlr_layer::Layer as WlrLayer, }, }; use indexmap::{IndexMap, IndexSet}; @@ -56,6 +57,20 @@ fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> { }) } +#[derive(Default)] +struct LayerState { + drawn: bool, +} + +type LayerUserdata = RefCell>; +fn layer_state(space: usize, l: &LayerSurface) -> RefMut<'_, LayerState> { + let userdata = l.user_data(); + userdata.insert_if_missing(LayerUserdata::default); + RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { + m.entry(space).or_default() + }) +} + // TODO: Maybe replace UnmanagedResource if nothing else comes up? #[derive(Debug, thiserror::Error)] pub enum SpaceError { @@ -158,6 +173,16 @@ impl Space { .find(|w| w.toplevel().get_surface().map(|x| x == surface).unwrap_or(false)) } + pub fn layer_for_surface(&self, surface: &WlSurface) -> Option { + if !surface.as_ref().is_alive() { + return None; + } + self.outputs.iter().find_map(|o| { + let map = layer_map_for_output(o); + map.layer_for_surface(surface).cloned() + }) + } + pub fn window_geometry(&self, w: &Window) -> Option> { if !self.windows.contains(w) { return None; @@ -389,6 +414,7 @@ impl Space { .to_logical(state.render_scale) .to_i32_round(); let output_geo = Rectangle::from_loc_and_size(state.location, output_size); + let layer_map = layer_map_for_output(output); // This will hold all the damage we need for this rendering step let mut damage = Vec::>::new(); @@ -397,7 +423,9 @@ impl Space { .last_state .iter() .filter_map(|(id, w)| { - if !self.windows.iter().any(|w| ToplevelId::Xdg(w.0.id) == *id) { + if !self.windows.iter().any(|w| ToplevelId::Xdg(w.0.id) == *id) + && !layer_map.layers().any(|l| ToplevelId::Layer(l.0.id) == *id) + { Some(*w) } else { None @@ -428,6 +456,23 @@ impl Space { })); } } + for layer in layer_map.layers() { + let geo = layer_map.layer_geometry(layer); + let old_geo = state.last_state.get(&ToplevelId::Layer(layer.0.id)).cloned(); + + // layer moved or resized + if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { + // Add damage for the old position of the layer + damage.push(old_geo.unwrap()); + damage.push(geo); + } else { + let location = geo.loc; + damage.extend(layer.accumulated_damage().into_iter().map(|mut rect| { + rect.loc += location; + rect + })); + } + } // That is all completely new damage, which we need to store for subsequent renders let new_damage = damage.clone(); @@ -485,7 +530,39 @@ impl Space { .collect::>(), )?; - // Then re-draw all window overlapping with a damage rect. + // Then re-draw all windows & layers overlapping with a damage rect. + + for layer in layer_map + .layers_on(WlrLayer::Background) + .chain(layer_map.layers_on(WlrLayer::Bottom)) + { + let lgeo = layer_map.layer_geometry(layer); + if damage.iter().any(|geo| lgeo.overlaps(*geo)) { + let layer_damage = damage + .iter() + .filter(|geo| geo.overlaps(lgeo)) + .map(|geo| geo.intersection(lgeo)) + .map(|geo| Rectangle::from_loc_and_size(geo.loc - lgeo.loc, geo.size)) + .collect::>(); + slog::trace!( + self.logger, + "Rendering layer at {:?} with damage {:#?}", + lgeo, + damage + ); + draw_layer( + renderer, + frame, + layer, + state.render_scale, + lgeo.loc, + &layer_damage, + &self.logger, + )?; + layer_state(self.id, layer).drawn = true; + } + } + for window in self.windows.iter() { let wgeo = window_rect_with_popups(window, &self.id); let mut loc = window_loc(window, &self.id); @@ -516,6 +593,37 @@ impl Space { } } + for layer in layer_map + .layers_on(WlrLayer::Top) + .chain(layer_map.layers_on(WlrLayer::Overlay)) + { + let lgeo = layer_map.layer_geometry(layer); + if damage.iter().any(|geo| lgeo.overlaps(*geo)) { + let layer_damage = damage + .iter() + .filter(|geo| geo.overlaps(lgeo)) + .map(|geo| geo.intersection(lgeo)) + .map(|geo| Rectangle::from_loc_and_size(geo.loc - lgeo.loc, geo.size)) + .collect::>(); + slog::trace!( + self.logger, + "Rendering layer at {:?} with damage {:#?}", + lgeo, + damage + ); + draw_layer( + renderer, + frame, + layer, + state.render_scale, + lgeo.loc, + &layer_damage, + &self.logger, + )?; + layer_state(self.id, layer).drawn = true; + } + } + Result::<(), R::Error>::Ok(()) }, ) { @@ -534,6 +642,10 @@ impl Space { let wgeo = window_rect_with_popups(window, &self.id); (ToplevelId::Xdg(window.0.id), wgeo) }) + .chain(layer_map.layers().map(|layer| { + let lgeo = layer_map.layer_geometry(layer); + (ToplevelId::Layer(layer.0.id), lgeo) + })) .collect(); state.old_damage.push_front(new_damage); @@ -549,6 +661,18 @@ impl Space { }) { window.send_frame(time); } + + for output in self.outputs.iter() { + let map = layer_map_for_output(output); + for layer in map.layers().filter(|l| { + all || { + let mut state = layer_state(self.id, l); + std::mem::replace(&mut state.drawn, false) + } + }) { + layer.send_frame(time); + } + } } } diff --git a/src/wayland/shell/wlr_layer/types.rs b/src/wayland/shell/wlr_layer/types.rs index 2fe383c..ba3282e 100644 --- a/src/wayland/shell/wlr_layer/types.rs +++ b/src/wayland/shell/wlr_layer/types.rs @@ -50,7 +50,7 @@ impl Default for Layer { /// - some applications are not interested in keyboard events /// and not allowing them to be focused can improve the desktop experience /// - some applications will want to take exclusive keyboard focus. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum KeyboardInteractivity { /// This value indicates that this surface is not interested in keyboard events /// and the compositor should never assign it the keyboard focus. From 94968e2d0f2d0e903b741518e81ac9b590a0e57f Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 14 Dec 2021 14:12:08 +0100 Subject: [PATCH 15/52] desktop: track damage per space/output --- src/backend/renderer/utils/mod.rs | 5 ++++- src/desktop/layer.rs | 11 +++++++---- src/desktop/space.rs | 28 ++++++++++++++++++--------- src/desktop/utils.rs | 32 ++++++++++++++++++++++++------- src/desktop/window.rs | 12 ++++++++---- 5 files changed, 63 insertions(+), 25 deletions(-) diff --git a/src/backend/renderer/utils/mod.rs b/src/backend/renderer/utils/mod.rs index 6949e88..ae61bfd 100644 --- a/src/backend/renderer/utils/mod.rs +++ b/src/backend/renderer/utils/mod.rs @@ -6,7 +6,7 @@ use crate::{ SurfaceAttributes, TraversalAction, }, }; -use std::cell::RefCell; +use std::{cell::RefCell, collections::HashSet}; use wayland_server::protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface}; #[derive(Default)] @@ -15,6 +15,7 @@ pub(crate) struct SurfaceState { pub(crate) buffer_scale: i32, pub(crate) buffer: Option, pub(crate) texture: Option>, + pub(crate) damage_seen: HashSet<(usize, *const ())>, } impl SurfaceState { @@ -30,6 +31,7 @@ impl SurfaceState { } } self.texture = None; + self.damage_seen.clear(); } Some(BufferAssignment::Removed) => { // remove the contents @@ -38,6 +40,7 @@ impl SurfaceState { buffer.release(); }; self.texture = None; + self.damage_seen.clear(); } None => {} } diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 7221631..c3176b4 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -1,6 +1,6 @@ use crate::{ backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture}, - desktop::{utils::*, PopupManager}, + desktop::{utils::*, PopupManager, Space}, utils::{user_data::UserDataMap, Logical, Point, Rectangle}, wayland::{ compositor::with_states, @@ -423,17 +423,20 @@ impl LayerSurface { } /// Damage of all the surfaces of this layer - pub(super) fn accumulated_damage(&self) -> Vec> { + pub(super) fn accumulated_damage( + &self, + for_values: Option<(&Space, &Output)>, + ) -> Vec> { let mut damage = Vec::new(); if let Some(surface) = self.get_surface() { - damage.extend(damage_from_surface_tree(surface, (0, 0))); + damage.extend(damage_from_surface_tree(surface, (0, 0), for_values)); for (popup, location) in PopupManager::popups_for_surface(surface) .ok() .into_iter() .flatten() { if let Some(surface) = popup.get_surface() { - let popup_damage = damage_from_surface_tree(surface, location); + let popup_damage = damage_from_surface_tree(surface, location, for_values); damage.extend(popup_damage); } } diff --git a/src/desktop/space.rs b/src/desktop/space.rs index e54ce91..f4af0e9 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -80,7 +80,7 @@ pub enum SpaceError { #[derive(Debug)] pub struct Space { - id: usize, + pub(super) id: usize, // in z-order, back to front windows: IndexSet, outputs: Vec, @@ -450,10 +450,15 @@ impl Space { } else { // window stayed at its place let loc = window_loc(window, &self.id); - damage.extend(window.accumulated_damage().into_iter().map(|mut rect| { - rect.loc += loc; - rect - })); + damage.extend( + window + .accumulated_damage(Some((self, output))) + .into_iter() + .map(|mut rect| { + rect.loc += loc; + rect + }), + ); } } for layer in layer_map.layers() { @@ -467,10 +472,15 @@ impl Space { damage.push(geo); } else { let location = geo.loc; - damage.extend(layer.accumulated_damage().into_iter().map(|mut rect| { - rect.loc += location; - rect - })); + damage.extend( + layer + .accumulated_damage(Some((self, output))) + .into_iter() + .map(|mut rect| { + rect.loc += location; + rect + }), + ); } } diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs index 8a08fdc..67c6433 100644 --- a/src/desktop/utils.rs +++ b/src/desktop/utils.rs @@ -1,14 +1,18 @@ use crate::{ backend::renderer::utils::SurfaceState, + desktop::Space, utils::{Logical, Point, Rectangle, Size}, - wayland::compositor::{ - with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState, - SurfaceAttributes, TraversalAction, + wayland::{ + compositor::{ + with_surface_tree_downward, with_surface_tree_upward, Damage, SubsurfaceCachedState, + SurfaceAttributes, TraversalAction, + }, + output::Output, }, }; use wayland_server::protocol::wl_surface; -use std::cell::RefCell; +use std::{cell::RefCell, sync::Arc}; impl SurfaceState { /// Returns the size of the surface. @@ -86,11 +90,13 @@ where pub fn damage_from_surface_tree

( surface: &wl_surface::WlSurface, location: P, + key: Option<(&Space, &Output)>, ) -> Vec> where P: Into>, { let mut damage = Vec::new(); + let key = key.map(|(space, output)| (space.id, Arc::as_ptr(&output.inner) as *const ())); with_surface_tree_upward( surface, location.into(), @@ -98,7 +104,11 @@ where let mut location = *location; if let Some(data) = states.data_map.get::>() { let data = data.borrow(); - if data.texture.is_none() { + if key + .as_ref() + .map(|key| !data.damage_seen.contains(key)) + .unwrap_or(true) + { if states.role == Some("subsurface") { let current = states.cached_state.current::(); location += current.location; @@ -110,10 +120,14 @@ where |_surface, states, location| { let mut location = *location; if let Some(data) = states.data_map.get::>() { - let data = data.borrow(); + let mut data = data.borrow_mut(); let attributes = states.cached_state.current::(); - if data.texture.is_none() { + if key + .as_ref() + .map(|key| !data.damage_seen.contains(key)) + .unwrap_or(true) + { if states.role == Some("subsurface") { let current = states.cached_state.current::(); location += current.location; @@ -127,6 +141,10 @@ where rect.loc += location; rect })); + + if let Some(key) = key { + data.damage_seen.insert(key); + } } } }, diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 61a8478..02be9f7 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -1,9 +1,10 @@ use crate::{ backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture}, - desktop::{utils::*, PopupManager}, + desktop::{utils::*, PopupManager, Space}, utils::{Logical, Point, Rectangle}, wayland::{ compositor::with_states, + output::Output, shell::xdg::{SurfaceCachedState, ToplevelSurface}, }, }; @@ -245,17 +246,20 @@ impl Window { } /// Damage of all the surfaces of this window - pub(super) fn accumulated_damage(&self) -> Vec> { + pub(super) fn accumulated_damage( + &self, + for_values: Option<(&Space, &Output)>, + ) -> Vec> { let mut damage = Vec::new(); if let Some(surface) = self.0.toplevel.get_surface() { - damage.extend(damage_from_surface_tree(surface, (0, 0))); + damage.extend(damage_from_surface_tree(surface, (0, 0), for_values)); for (popup, location) in PopupManager::popups_for_surface(surface) .ok() .into_iter() .flatten() { if let Some(surface) = popup.get_surface() { - let popup_damage = damage_from_surface_tree(surface, location); + let popup_damage = damage_from_surface_tree(surface, location, for_values); damage.extend(popup_damage); } } From d7350d18eebcda827bfdcb73fd595d63cf6ca516 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 14 Dec 2021 14:38:33 +0100 Subject: [PATCH 16/52] desktop: fix negative damage values --- src/desktop/space.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/desktop/space.rs b/src/desktop/space.rs index f4af0e9..e47ad96 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -582,7 +582,7 @@ impl Space { .iter() .filter(|geo| geo.overlaps(wgeo)) .map(|geo| geo.intersection(wgeo)) - .map(|geo| Rectangle::from_loc_and_size(geo.loc - wgeo.loc, geo.size)) + .map(|geo| Rectangle::from_loc_and_size(geo.loc - loc, geo.size)) .collect::>(); slog::trace!( self.logger, From d84a66e053e4838eb44e908049a9cbc576af4c7b Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Thu, 16 Dec 2021 13:04:00 +0100 Subject: [PATCH 17/52] desktop: api cleanups --- src/desktop/layer.rs | 19 +++++++++++++++---- src/desktop/space.rs | 18 ++++++++++-------- src/desktop/utils.rs | 3 ++- src/desktop/window.rs | 11 +++++++---- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index c3176b4..427f96d 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -117,7 +117,12 @@ impl LayerMap { bbox } - pub fn layer_under(&self, layer: WlrLayer, point: Point) -> Option<&LayerSurface> { + pub fn layer_under>>( + &self, + layer: WlrLayer, + point: P, + ) -> Option<&LayerSurface> { + let point = point.into(); self.layers_on(layer).rev().find(|l| { let bbox = self.layer_geometry(l); bbox.to_f64().contains(point) @@ -401,7 +406,11 @@ impl LayerSurface { /// Finds the topmost surface under this point if any and returns it together with the location of this /// surface. - pub fn surface_under(&self, point: Point) -> Option<(WlSurface, Point)> { + pub fn surface_under>>( + &self, + point: P, + ) -> Option<(WlSurface, Point)> { + let point = point.into(); if let Some(surface) = self.get_surface() { for (popup, location) in PopupManager::popups_for_surface(surface) .ok() @@ -457,12 +466,12 @@ impl LayerSurface { } } -pub fn draw_layer( +pub fn draw_layer( renderer: &mut R, frame: &mut F, layer: &LayerSurface, scale: f64, - location: Point, + location: P, damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> @@ -471,7 +480,9 @@ where F: Frame, E: std::error::Error, T: Texture + 'static, + P: Into>, { + let location = location.into(); if let Some(surface) = layer.get_surface() { draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?; for (popup, p_location) in PopupManager::popups_for_surface(surface) diff --git a/src/desktop/space.rs b/src/desktop/space.rs index e47ad96..d2fd8b2 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -98,22 +98,22 @@ impl Drop for Space { impl Space { pub fn new(log: L) -> Space where - L: Into, + L: Into>, { Space { id: next_space_id(), windows: IndexSet::new(), outputs: Vec::new(), - logger: log.into(), + logger: crate::slog_or_fallback(log), } } /// Map window and moves it to top of the stack /// /// This can safely be called on an already mapped window - pub fn map_window(&mut self, window: &Window, location: Point) { + pub fn map_window>>(&mut self, window: &Window, location: P) { self.insert_window(window); - window_state(self.id, window).location = location; + window_state(self.id, window).location = location.into(); } pub fn raise_window(&mut self, window: &Window) { @@ -148,7 +148,8 @@ impl Space { } /// Get a reference to the window under a given point, if any - pub fn window_under(&self, point: Point) -> Option<&Window> { + pub fn window_under>>(&self, point: P) -> Option<&Window> { + let point = point.into(); self.windows.iter().rev().find(|w| { let bbox = window_rect(w, &self.id); bbox.to_f64().contains(point) @@ -156,7 +157,8 @@ impl Space { } /// Get a reference to the output under a given point, if any - pub fn output_under(&self, point: Point) -> Option<&Output> { + pub fn output_under>>(&self, point: P) -> Option<&Output> { + let point = point.into(); self.outputs.iter().rev().find(|o| { let bbox = self.output_geometry(o); bbox.map(|bbox| bbox.to_f64().contains(point)).unwrap_or(false) @@ -199,10 +201,10 @@ impl Space { Some(window_rect(w, &self.id)) } - pub fn map_output(&mut self, output: &Output, scale: f64, location: Point) { + pub fn map_output>>(&mut self, output: &Output, scale: f64, location: P) { let mut state = output_state(self.id, output); *state = OutputState { - location, + location: location.into(), render_scale: scale, ..Default::default() }; diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs index 67c6433..eb2cbf7 100644 --- a/src/desktop/utils.rs +++ b/src/desktop/utils.rs @@ -21,7 +21,8 @@ impl SurfaceState { .map(|dims| dims.to_logical(self.buffer_scale)) } - fn contains_point(&self, attrs: &SurfaceAttributes, point: Point) -> bool { + fn contains_point>>(&self, attrs: &SurfaceAttributes, point: P) -> bool { + let point = point.into(); let size = match self.size() { None => return false, // If the surface has no size, it can't have an input region. Some(size) => size, diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 02be9f7..e8ced19 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -221,10 +221,11 @@ impl Window { /// Finds the topmost surface under this point if any and returns it together with the location of this /// surface. - pub fn surface_under( + pub fn surface_under>>( &self, - point: Point, + point: P, ) -> Option<(wl_surface::WlSurface, Point)> { + let point = point.into(); if let Some(surface) = self.0.toplevel.get_surface() { for (popup, location) in PopupManager::popups_for_surface(surface) .ok() @@ -276,12 +277,12 @@ impl Window { } } -pub fn draw_window( +pub fn draw_window( renderer: &mut R, frame: &mut F, window: &Window, scale: f64, - location: Point, + location: P, damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> @@ -290,7 +291,9 @@ where F: Frame, E: std::error::Error, T: Texture + 'static, + P: Into>, { + let location = location.into(); if let Some(surface) = window.toplevel().get_surface() { draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?; for (popup, p_location) in PopupManager::popups_for_surface(surface) From a20fd0c65de6b01908726405f5db96584ee05832 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Fri, 17 Dec 2021 12:59:11 +0100 Subject: [PATCH 18/52] window: cache bbox --- src/desktop/space.rs | 20 +++++++++++++++++++- src/desktop/window.rs | 15 ++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/desktop/space.rs b/src/desktop/space.rs index d2fd8b2..a0836b9 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -4,7 +4,10 @@ use crate::{ desktop::{layer::*, output::*}, utils::{Logical, Point, Rectangle}, wayland::{ - compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction}, + compositor::{ + get_parent, is_sync_subsurface, with_surface_tree_downward, SubsurfaceCachedState, + TraversalAction, + }, output::Output, shell::wlr_layer::Layer as WlrLayer, }, @@ -396,6 +399,21 @@ impl Space { } } + /// Automatically calls `Window::refresh` for the window that belongs to the given surface, + /// if managed by this space. + pub fn commit(&mut self, surface: &WlSurface) { + if is_sync_subsurface(surface) { + return; + } + let mut root = surface.clone(); + while let Some(parent) = get_parent(&root) { + root = parent; + } + if let Some(window) = self.windows().find(|w| w.toplevel().get_surface() == Some(&root)) { + window.refresh(); + } + } + pub fn render_output( &mut self, renderer: &mut R, diff --git a/src/desktop/window.rs b/src/desktop/window.rs index e8ced19..7b8b6a3 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -9,6 +9,7 @@ use crate::{ }, }; use std::{ + cell::Cell, collections::HashSet, hash::{Hash, Hasher}, rc::Rc, @@ -99,6 +100,7 @@ impl Kind { pub(super) struct WindowInner { pub(super) id: usize, toplevel: Kind, + bbox: Cell>, user_data: UserDataMap, } @@ -135,6 +137,7 @@ impl Window { Window(Rc::new(WindowInner { id, toplevel, + bbox: Cell::new(Rectangle::from_loc_and_size((0, 0), (0, 0))), user_data: UserDataMap::new(), })) } @@ -153,7 +156,7 @@ impl Window { // TODO: Cache and document when to trigger updates. If possible let space do it pub fn bbox(&self) -> Rectangle { if let Some(surface) = self.0.toplevel.get_surface() { - bbox_from_surface_tree(surface, (0, 0)) + self.0.bbox.get() } else { Rectangle::from_loc_and_size((0, 0), (0, 0)) } @@ -219,6 +222,16 @@ impl Window { } } + /// Updates internal values + /// + /// Needs to be called whenever the toplevel surface or any unsynchronized subsurfaces of this window are updated + /// to correctly update the bounding box of this window. + pub fn refresh(&self) { + if let Some(surface) = self.0.toplevel.get_surface() { + self.0.bbox.set(bbox_from_surface_tree(surface, (0, 0))); + } + } + /// Finds the topmost surface under this point if any and returns it together with the location of this /// surface. pub fn surface_under>>( From a948ff8829b2cacad39ef11460000b0328ba1e02 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Mon, 20 Dec 2021 13:40:08 +0100 Subject: [PATCH 19/52] space: commit does not need mutable self --- src/desktop/space.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/desktop/space.rs b/src/desktop/space.rs index a0836b9..1dd1828 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -401,7 +401,7 @@ impl Space { /// Automatically calls `Window::refresh` for the window that belongs to the given surface, /// if managed by this space. - pub fn commit(&mut self, surface: &WlSurface) { + pub fn commit(&self, surface: &WlSurface) { if is_sync_subsurface(surface) { return; } From 827a3c8c2a13107cd809a4e26b4d008041b2e2ea Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Mon, 20 Dec 2021 19:12:21 +0100 Subject: [PATCH 20/52] desktop: Fixup intersection calls --- src/backend/renderer/utils/mod.rs | 3 +-- src/desktop/space.rs | 9 +++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/backend/renderer/utils/mod.rs b/src/backend/renderer/utils/mod.rs index ae61bfd..bbf65a8 100644 --- a/src/backend/renderer/utils/mod.rs +++ b/src/backend/renderer/utils/mod.rs @@ -165,8 +165,7 @@ where let new_damage = damage .iter() .cloned() - .filter(|geo| geo.overlaps(rect)) - .map(|geo| geo.intersection(rect)) + .flat_map(|geo| geo.intersection(rect)) .map(|mut geo| { geo.loc -= rect.loc; geo diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 1dd1828..63e4bea 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -570,8 +570,7 @@ impl Space { if damage.iter().any(|geo| lgeo.overlaps(*geo)) { let layer_damage = damage .iter() - .filter(|geo| geo.overlaps(lgeo)) - .map(|geo| geo.intersection(lgeo)) + .flat_map(|geo| geo.intersection(lgeo)) .map(|geo| Rectangle::from_loc_and_size(geo.loc - lgeo.loc, geo.size)) .collect::>(); slog::trace!( @@ -600,8 +599,7 @@ impl Space { loc -= output_geo.loc; let win_damage = damage .iter() - .filter(|geo| geo.overlaps(wgeo)) - .map(|geo| geo.intersection(wgeo)) + .flat_map(|geo| geo.intersection(wgeo)) .map(|geo| Rectangle::from_loc_and_size(geo.loc - loc, geo.size)) .collect::>(); slog::trace!( @@ -631,8 +629,7 @@ impl Space { if damage.iter().any(|geo| lgeo.overlaps(*geo)) { let layer_damage = damage .iter() - .filter(|geo| geo.overlaps(lgeo)) - .map(|geo| geo.intersection(lgeo)) + .flat_map(|geo| geo.intersection(lgeo)) .map(|geo| Rectangle::from_loc_and_size(geo.loc - lgeo.loc, geo.size)) .collect::>(); slog::trace!( From 5b85333eafbd2d58ddb3f8394484d4dcfa2a97fc Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Mon, 20 Dec 2021 19:13:30 +0100 Subject: [PATCH 21/52] desktop: clamp damage to bbox --- src/desktop/layer.rs | 9 +++++++-- src/desktop/window.rs | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 427f96d..2d0f16f 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -438,15 +438,20 @@ impl LayerSurface { ) -> Vec> { let mut damage = Vec::new(); if let Some(surface) = self.get_surface() { - damage.extend(damage_from_surface_tree(surface, (0, 0), for_values)); + damage.extend( + damage_from_surface_tree(surface, (0, 0), for_values) + .into_iter() + .flat_map(|rect| rect.intersection(self.bbox())), + ); for (popup, location) in PopupManager::popups_for_surface(surface) .ok() .into_iter() .flatten() { if let Some(surface) = popup.get_surface() { + let bbox = bbox_from_surface_tree(surface, location); let popup_damage = damage_from_surface_tree(surface, location, for_values); - damage.extend(popup_damage); + damage.extend(popup_damage.into_iter().flat_map(|rect| rect.intersection(bbox))); } } } diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 7b8b6a3..762676d 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -266,15 +266,20 @@ impl Window { ) -> Vec> { let mut damage = Vec::new(); if let Some(surface) = self.0.toplevel.get_surface() { - damage.extend(damage_from_surface_tree(surface, (0, 0), for_values)); + damage.extend( + damage_from_surface_tree(surface, (0, 0), for_values) + .into_iter() + .flat_map(|rect| rect.intersection(self.bbox())), + ); for (popup, location) in PopupManager::popups_for_surface(surface) .ok() .into_iter() .flatten() { if let Some(surface) = popup.get_surface() { + let bbox = bbox_from_surface_tree(surface, location); let popup_damage = damage_from_surface_tree(surface, location, for_values); - damage.extend(popup_damage); + damage.extend(popup_damage.into_iter().flat_map(|rect| rect.intersection(bbox))); } } } From bb1e68c9167d1f198dbcaf23e9f38a8ce77fee83 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Mon, 20 Dec 2021 19:13:55 +0100 Subject: [PATCH 22/52] space: Add support for drawing custom elements --- src/desktop/output.rs | 9 +++ src/desktop/space.rs | 153 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 154 insertions(+), 8 deletions(-) diff --git a/src/desktop/output.rs b/src/desktop/output.rs index 88a1d43..445f46f 100644 --- a/src/desktop/output.rs +++ b/src/desktop/output.rs @@ -6,6 +6,7 @@ use indexmap::IndexMap; use wayland_server::protocol::wl_surface::WlSurface; use std::{ + any::TypeId, cell::{RefCell, RefMut}, collections::{HashMap, VecDeque}, }; @@ -14,6 +15,7 @@ use std::{ pub(super) enum ToplevelId { Xdg(usize), Layer(usize), + Custom(TypeId, usize), } impl ToplevelId { @@ -30,6 +32,13 @@ impl ToplevelId { _ => false, } } + + pub fn is_custom(&self) -> bool { + match self { + ToplevelId::Custom(_, _) => true, + _ => false, + } + } } #[derive(Clone, Default)] diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 63e4bea..3a2cfa4 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -1,6 +1,6 @@ use super::{draw_window, Window}; use crate::{ - backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform}, + backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Texture, Transform}, desktop::{layer::*, output::*}, utils::{Logical, Point, Rectangle}, wayland::{ @@ -14,6 +14,7 @@ use crate::{ }; use indexmap::{IndexMap, IndexSet}; use std::{ + any::{Any, TypeId}, cell::{RefCell, RefMut}, collections::{HashMap, HashSet, VecDeque}, sync::{ @@ -420,10 +421,18 @@ impl Space { output: &Output, age: usize, clear_color: [f32; 4], + custom_elements: &[&(dyn RenderElement< + R, + ::Frame, + ::Error, + ::TextureId, + >)], ) -> Result> where - R: Renderer + ImportAll, + R: Renderer + ImportAll + 'static, R::TextureId: 'static, + R::Error: 'static, + R::Frame: 'static, { let mut state = output_state(self.id, output); let output_size = output @@ -439,12 +448,15 @@ impl Space { // This will hold all the damage we need for this rendering step let mut damage = Vec::>::new(); // First add damage for windows gone - for old_window in state + for old_toplevel in state .last_state .iter() .filter_map(|(id, w)| { if !self.windows.iter().any(|w| ToplevelId::Xdg(w.0.id) == *id) && !layer_map.layers().any(|l| ToplevelId::Layer(l.0.id) == *id) + && !custom_elements + .iter() + .any(|c| ToplevelId::Custom(c.type_of(), c.id()) == *id) { Some(*w) } else { @@ -453,8 +465,8 @@ impl Space { }) .collect::>>() { - slog::trace!(self.logger, "Removing toplevel at: {:?}", old_window); - damage.push(old_window); + slog::trace!(self.logger, "Removing toplevel at: {:?}", old_toplevel); + damage.push(old_toplevel); } // lets iterate front to back and figure out, what new windows or unmoved windows we have @@ -503,6 +515,30 @@ impl Space { ); } } + for elem in custom_elements { + let geo = elem.geometry(); + let old_geo = state + .last_state + .get(&ToplevelId::Custom(elem.type_of(), elem.id())) + .cloned(); + + // moved of resized + if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { + // Add damage for the old position of the layer + damage.push(old_geo.unwrap()); + damage.push(geo); + } else { + let location = geo.loc; + damage.extend( + elem.accumulated_damage(Some((self, output))) + .into_iter() + .map(|mut rect| { + rect.loc += location; + rect + }), + ); + } + } // That is all completely new damage, which we need to store for subsequent renders let new_damage = damage.clone(); @@ -577,7 +613,7 @@ impl Space { self.logger, "Rendering layer at {:?} with damage {:#?}", lgeo, - damage + layer_damage ); draw_layer( renderer, @@ -606,7 +642,7 @@ impl Space { self.logger, "Rendering window at {:?} with damage {:#?}", wgeo, - damage + win_damage ); draw_window( renderer, @@ -636,7 +672,7 @@ impl Space { self.logger, "Rendering layer at {:?} with damage {:#?}", lgeo, - damage + layer_damage ); draw_layer( renderer, @@ -651,6 +687,31 @@ impl Space { } } + for elem in custom_elements { + let egeo = elem.geometry(); + if damage.iter().any(|geo| egeo.overlaps(*geo)) { + let elem_damage = damage + .iter() + .flat_map(|geo| geo.intersection(egeo)) + .map(|geo| Rectangle::from_loc_and_size(geo.loc - egeo.loc, geo.size)) + .collect::>(); + slog::trace!( + self.logger, + "Rendering custom element at {:?} with damage {:#?}", + egeo, + elem_damage + ); + elem.draw( + renderer, + frame, + state.render_scale, + egeo.loc, + &elem_damage, + &self.logger, + )?; + } + } + Result::<(), R::Error>::Ok(()) }, ) { @@ -673,6 +734,10 @@ impl Space { let lgeo = layer_map.layer_geometry(layer); (ToplevelId::Layer(layer.0.id), lgeo) })) + .chain(custom_elements.iter().map(|custom| { + let egeo = custom.geometry(); + (ToplevelId::Custom(custom.type_of(), custom.id()), egeo) + })) .collect(); state.old_damage.push_front(new_damage); @@ -703,6 +768,78 @@ impl Space { } } +pub trait RenderElement +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, + Self: Any + 'static, +{ + fn id(&self) -> usize; + fn type_of(&self) -> TypeId { + std::any::Any::type_id(self) + } + fn geometry(&self) -> Rectangle; + fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec>; + fn draw( + &self, + renderer: &mut R, + frame: &mut F, + scale: f64, + location: Point, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), R::Error>; +} + +#[derive(Debug)] +pub struct SurfaceTree { + pub surface: WlSurface, + pub position: Point, +} + +impl RenderElement for SurfaceTree +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, +{ + fn id(&self) -> usize { + self.surface.as_ref().id() as usize + } + fn geometry(&self) -> Rectangle { + let mut bbox = super::utils::bbox_from_surface_tree(&self.surface, (0, 0)); + bbox.loc += self.position; + bbox + } + + fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { + super::utils::damage_from_surface_tree(&self.surface, (0, 0), for_values) + } + + fn draw( + &self, + renderer: &mut R, + frame: &mut F, + scale: f64, + location: Point, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), R::Error> { + crate::backend::renderer::utils::draw_surface_tree( + renderer, + frame, + &self.surface, + scale, + location, + damage, + log, + ) + } +} + #[derive(Debug, thiserror::Error)] pub enum RenderError { #[error(transparent)] From 8536fa90a10445dbb293448aadeb659d6f600fce Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Mon, 27 Dec 2021 00:03:42 +0100 Subject: [PATCH 23/52] desktop: streamline custom elements logic --- src/desktop/layer.rs | 8 +- src/desktop/mod.rs | 9 +- src/desktop/space/element.rs | 141 +++++++++ src/desktop/space/layer.rs | 72 +++++ src/desktop/{space.rs => space/mod.rs} | 393 +++++-------------------- src/desktop/{ => space}/output.rs | 47 ++- src/desktop/space/window.rs | 106 +++++++ 7 files changed, 422 insertions(+), 354 deletions(-) create mode 100644 src/desktop/space/element.rs create mode 100644 src/desktop/space/layer.rs rename src/desktop/{space.rs => space/mod.rs} (63%) rename src/desktop/{ => space}/output.rs (53%) create mode 100644 src/desktop/space/window.rs diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 2d0f16f..4fa52aa 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -1,6 +1,6 @@ use crate::{ backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture}, - desktop::{utils::*, PopupManager, Space}, + desktop::{space::RenderElement, utils::*, PopupManager, Space}, utils::{user_data::UserDataMap, Logical, Point, Rectangle}, wayland::{ compositor::with_states, @@ -267,12 +267,12 @@ impl LayerMap { } #[derive(Debug, Default)] -pub(super) struct LayerState { - location: Point, +pub struct LayerState { + pub location: Point, } type LayerUserdata = RefCell>; -fn layer_state(layer: &LayerSurface) -> RefMut<'_, LayerState> { +pub fn layer_state(layer: &LayerSurface) -> RefMut<'_, LayerState> { let userdata = layer.user_data(); userdata.insert_if_missing(LayerUserdata::default); RefMut::map(userdata.get::().unwrap().borrow_mut(), |opt| { diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index e5bcbbb..aad577a 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -1,13 +1,12 @@ // TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings #![allow(missing_docs, clippy::all)] -mod layer; -mod output; +pub(crate) mod layer; mod popup; -mod space; +pub mod space; pub mod utils; mod window; -pub use self::layer::*; +pub use self::layer::{draw_layer, layer_map_for_output, LayerMap, LayerSurface}; pub use self::popup::*; -pub use self::space::*; +pub use self::space::Space; pub use self::window::*; diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs new file mode 100644 index 0000000..b62dc49 --- /dev/null +++ b/src/desktop/space/element.rs @@ -0,0 +1,141 @@ +use crate::{ + backend::renderer::{Frame, ImportAll, Renderer, Texture}, + desktop::{space::*, utils::*}, + utils::{Logical, Point, Rectangle}, + wayland::output::Output, +}; +use std::any::{Any, TypeId}; +use wayland_server::protocol::wl_surface::WlSurface; + +pub trait RenderElement +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, + Self: Any + 'static, +{ + fn id(&self) -> usize; + #[doc(hidden)] + fn type_of(&self) -> TypeId { + std::any::Any::type_id(self) + } + fn geometry(&self) -> Rectangle; + fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec>; + fn draw( + &self, + renderer: &mut R, + frame: &mut F, + scale: f64, + location: Point, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), R::Error>; +} + +pub(crate) trait SpaceElement +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture, +{ + fn id(&self) -> usize; + fn type_of(&self) -> TypeId; + fn location(&self, space_id: usize) -> Point { + self.geometry(space_id).loc + } + fn geometry(&self, space_id: usize) -> Rectangle; + fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec>; + fn draw( + &self, + space_id: usize, + renderer: &mut R, + frame: &mut F, + scale: f64, + location: Point, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), R::Error>; +} + +impl SpaceElement for Box> +where + R: Renderer + ImportAll + 'static, + F: Frame + 'static, + E: std::error::Error + 'static, + T: Texture + 'static, +{ + fn id(&self) -> usize { + (&**self as &dyn RenderElement).id() + } + fn type_of(&self) -> TypeId { + (&**self as &dyn RenderElement).type_of() + } + fn geometry(&self, _space_id: usize) -> Rectangle { + (&**self as &dyn RenderElement).geometry() + } + fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { + (&**self as &dyn RenderElement).accumulated_damage(for_values) + } + fn draw( + &self, + _space_id: usize, + renderer: &mut R, + frame: &mut F, + scale: f64, + location: Point, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), R::Error> { + (&**self as &dyn RenderElement).draw(renderer, frame, scale, location, damage, log) + } +} + +#[derive(Debug)] +pub struct SurfaceTree { + pub surface: WlSurface, + pub position: Point, +} + +impl RenderElement for SurfaceTree +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, +{ + fn id(&self) -> usize { + self.surface.as_ref().id() as usize + } + + fn geometry(&self) -> Rectangle { + let mut bbox = bbox_from_surface_tree(&self.surface, (0, 0)); + bbox.loc += self.position; + bbox + } + + fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { + damage_from_surface_tree(&self.surface, (0, 0), for_values) + } + + fn draw( + &self, + renderer: &mut R, + frame: &mut F, + scale: f64, + location: Point, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), R::Error> { + crate::backend::renderer::utils::draw_surface_tree( + renderer, + frame, + &self.surface, + scale, + location, + damage, + log, + ) + } +} diff --git a/src/desktop/space/layer.rs b/src/desktop/space/layer.rs new file mode 100644 index 0000000..d9157e8 --- /dev/null +++ b/src/desktop/space/layer.rs @@ -0,0 +1,72 @@ +use crate::{ + backend::renderer::{Frame, ImportAll, Renderer, Texture}, + desktop::{ + layer::{layer_state as output_layer_state, *}, + space::{Space, SpaceElement}, + }, + utils::{Logical, Point, Rectangle}, + wayland::output::Output, +}; +use std::{ + any::TypeId, + cell::{RefCell, RefMut}, + collections::HashMap, +}; + +#[derive(Default)] +pub struct LayerState { + pub drawn: bool, +} + +type LayerUserdata = RefCell>; +pub fn layer_state(space: usize, l: &LayerSurface) -> RefMut<'_, LayerState> { + let userdata = l.user_data(); + userdata.insert_if_missing(LayerUserdata::default); + RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { + m.entry(space).or_default() + }) +} + +impl SpaceElement for LayerSurface +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, +{ + fn id(&self) -> usize { + self.0.id + } + + fn type_of(&self) -> TypeId { + TypeId::of::() + } + + fn geometry(&self, space_id: usize) -> Rectangle { + let mut bbox = self.bbox_with_popups(); + let state = output_layer_state(self); + bbox.loc += state.location; + bbox + } + + fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { + self.accumulated_damage(for_values) + } + + fn draw( + &self, + space_id: usize, + renderer: &mut R, + frame: &mut F, + scale: f64, + location: Point, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), R::Error> { + let res = draw_layer(renderer, frame, self, scale, location, damage, log); + if res.is_ok() { + layer_state(space_id, self).drawn = true; + } + res + } +} diff --git a/src/desktop/space.rs b/src/desktop/space/mod.rs similarity index 63% rename from src/desktop/space.rs rename to src/desktop/space/mod.rs index 3a2cfa4..4734d14 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space/mod.rs @@ -1,7 +1,9 @@ -use super::{draw_window, Window}; use crate::{ - backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Texture, Transform}, - desktop::{layer::*, output::*}, + backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform}, + desktop::{ + layer::{layer_map_for_output, LayerSurface}, + window::Window, + }, utils::{Logical, Point, Rectangle}, wayland::{ compositor::{ @@ -14,9 +16,8 @@ use crate::{ }; use indexmap::{IndexMap, IndexSet}; use std::{ - any::{Any, TypeId}, - cell::{RefCell, RefMut}, - collections::{HashMap, HashSet, VecDeque}, + cell::RefCell, + collections::{HashSet, VecDeque}, sync::{ atomic::{AtomicUsize, Ordering}, Mutex, @@ -24,6 +25,16 @@ use std::{ }; use wayland_server::protocol::wl_surface::WlSurface; +mod element; +mod layer; +mod output; +mod window; + +pub use self::element::*; +use self::layer::*; +use self::output::*; +use self::window::*; + static SPACE_ID: AtomicUsize = AtomicUsize::new(0); lazy_static::lazy_static! { static ref SPACE_IDS: Mutex> = Mutex::new(HashSet::new()); @@ -46,35 +57,6 @@ fn next_space_id() -> usize { id } -#[derive(Default)] -struct WindowState { - location: Point, - drawn: bool, -} - -type WindowUserdata = RefCell>; -fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> { - let userdata = w.user_data(); - userdata.insert_if_missing(WindowUserdata::default); - RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { - m.entry(space).or_default() - }) -} - -#[derive(Default)] -struct LayerState { - drawn: bool, -} - -type LayerUserdata = RefCell>; -fn layer_state(space: usize, l: &LayerSurface) -> RefMut<'_, LayerState> { - let userdata = l.user_data(); - userdata.insert_if_missing(LayerUserdata::default); - RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { - m.entry(space).or_default() - }) -} - // TODO: Maybe replace UnmanagedResource if nothing else comes up? #[derive(Debug, thiserror::Error)] pub enum SpaceError { @@ -421,12 +403,9 @@ impl Space { output: &Output, age: usize, clear_color: [f32; 4], - custom_elements: &[&(dyn RenderElement< - R, - ::Frame, - ::Error, - ::TextureId, - >)], + custom_elements: &[Box< + dyn RenderElement::Frame, ::Error, ::TextureId>, + >], ) -> Result> where R: Renderer + ImportAll + 'static, @@ -434,6 +413,9 @@ impl Space { R::Error: 'static, R::Frame: 'static, { + type SpaceElem = + dyn SpaceElement::Frame, ::Error, ::TextureId>; + let mut state = output_state(self.id, output); let output_size = output .current_mode() @@ -451,14 +433,16 @@ impl Space { for old_toplevel in state .last_state .iter() - .filter_map(|(id, w)| { - if !self.windows.iter().any(|w| ToplevelId::Xdg(w.0.id) == *id) - && !layer_map.layers().any(|l| ToplevelId::Layer(l.0.id) == *id) - && !custom_elements - .iter() - .any(|c| ToplevelId::Custom(c.type_of(), c.id()) == *id) + .filter_map(|(id, geo)| { + if !self + .windows + .iter() + .map(|w| w as &SpaceElem) + .chain(layer_map.layers().map(|l| l as &SpaceElem)) + .chain(custom_elements.iter().map(|c| c as &SpaceElem)) + .any(|e| ToplevelId::from(e) == *id) { - Some(*w) + Some(*geo) } else { None } @@ -470,9 +454,15 @@ impl Space { } // lets iterate front to back and figure out, what new windows or unmoved windows we have - for window in self.windows.iter() { - let geo = window_rect_with_popups(window, &self.id); - let old_geo = state.last_state.get(&ToplevelId::Xdg(window.0.id)).cloned(); + for element in self + .windows + .iter() + .map(|w| w as &SpaceElem) + .chain(layer_map.layers().map(|l| l as &SpaceElem)) + .chain(custom_elements.iter().map(|c| c as &SpaceElem)) + { + let geo = element.geometry(self.id); + let old_geo = state.last_state.get(&ToplevelId::from(element)).cloned(); // window was moved or resized if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { @@ -481,62 +471,13 @@ impl Space { damage.push(geo); } else { // window stayed at its place - let loc = window_loc(window, &self.id); - damage.extend( - window - .accumulated_damage(Some((self, output))) - .into_iter() - .map(|mut rect| { - rect.loc += loc; - rect - }), - ); - } - } - for layer in layer_map.layers() { - let geo = layer_map.layer_geometry(layer); - let old_geo = state.last_state.get(&ToplevelId::Layer(layer.0.id)).cloned(); - - // layer moved or resized - if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { - // Add damage for the old position of the layer - damage.push(old_geo.unwrap()); - damage.push(geo); - } else { - let location = geo.loc; - damage.extend( - layer - .accumulated_damage(Some((self, output))) - .into_iter() - .map(|mut rect| { - rect.loc += location; - rect - }), - ); - } - } - for elem in custom_elements { - let geo = elem.geometry(); - let old_geo = state - .last_state - .get(&ToplevelId::Custom(elem.type_of(), elem.id())) - .cloned(); - - // moved of resized - if old_geo.map(|old_geo| old_geo != geo).unwrap_or(false) { - // Add damage for the old position of the layer - damage.push(old_geo.unwrap()); - damage.push(geo); - } else { - let location = geo.loc; - damage.extend( - elem.accumulated_damage(Some((self, output))) - .into_iter() - .map(|mut rect| { - rect.loc += location; - rect - }), - ); + let loc = element.location(self.id); + damage.extend(element.accumulated_damage(Some((self, output))).into_iter().map( + |mut rect| { + rect.loc += loc; + rect + }, + )); } } @@ -598,115 +539,40 @@ impl Space { // Then re-draw all windows & layers overlapping with a damage rect. - for layer in layer_map + for element in layer_map .layers_on(WlrLayer::Background) .chain(layer_map.layers_on(WlrLayer::Bottom)) + .map(|l| l as &SpaceElem) + .chain(self.windows.iter().map(|w| w as &SpaceElem)) + .chain( + layer_map + .layers_on(WlrLayer::Top) + .chain(layer_map.layers_on(WlrLayer::Overlay)) + .map(|l| l as &SpaceElem), + ) + .chain(custom_elements.iter().map(|c| c as &SpaceElem)) { - let lgeo = layer_map.layer_geometry(layer); - if damage.iter().any(|geo| lgeo.overlaps(*geo)) { - let layer_damage = damage + let geo = element.geometry(self.id); + if damage.iter().any(|d| d.overlaps(geo)) { + let loc = element.location(self.id) - output_geo.loc; + let damage = damage .iter() - .flat_map(|geo| geo.intersection(lgeo)) - .map(|geo| Rectangle::from_loc_and_size(geo.loc - lgeo.loc, geo.size)) - .collect::>(); - slog::trace!( - self.logger, - "Rendering layer at {:?} with damage {:#?}", - lgeo, - layer_damage - ); - draw_layer( - renderer, - frame, - layer, - state.render_scale, - lgeo.loc, - &layer_damage, - &self.logger, - )?; - layer_state(self.id, layer).drawn = true; - } - } - - for window in self.windows.iter() { - let wgeo = window_rect_with_popups(window, &self.id); - let mut loc = window_loc(window, &self.id); - if damage.iter().any(|geo| wgeo.overlaps(*geo)) { - loc -= output_geo.loc; - let win_damage = damage - .iter() - .flat_map(|geo| geo.intersection(wgeo)) + .flat_map(|d| d.intersection(geo)) .map(|geo| Rectangle::from_loc_and_size(geo.loc - loc, geo.size)) .collect::>(); slog::trace!( self.logger, - "Rendering window at {:?} with damage {:#?}", - wgeo, - win_damage + "Rendering toplevel at {:?} with damage {:#?}", + geo, + damage ); - draw_window( + element.draw( + self.id, renderer, frame, - window, state.render_scale, loc, - &win_damage, - &self.logger, - )?; - window_state(self.id, window).drawn = true; - } - } - - for layer in layer_map - .layers_on(WlrLayer::Top) - .chain(layer_map.layers_on(WlrLayer::Overlay)) - { - let lgeo = layer_map.layer_geometry(layer); - if damage.iter().any(|geo| lgeo.overlaps(*geo)) { - let layer_damage = damage - .iter() - .flat_map(|geo| geo.intersection(lgeo)) - .map(|geo| Rectangle::from_loc_and_size(geo.loc - lgeo.loc, geo.size)) - .collect::>(); - slog::trace!( - self.logger, - "Rendering layer at {:?} with damage {:#?}", - lgeo, - layer_damage - ); - draw_layer( - renderer, - frame, - layer, - state.render_scale, - lgeo.loc, - &layer_damage, - &self.logger, - )?; - layer_state(self.id, layer).drawn = true; - } - } - - for elem in custom_elements { - let egeo = elem.geometry(); - if damage.iter().any(|geo| egeo.overlaps(*geo)) { - let elem_damage = damage - .iter() - .flat_map(|geo| geo.intersection(egeo)) - .map(|geo| Rectangle::from_loc_and_size(geo.loc - egeo.loc, geo.size)) - .collect::>(); - slog::trace!( - self.logger, - "Rendering custom element at {:?} with damage {:#?}", - egeo, - elem_damage - ); - elem.draw( - renderer, - frame, - state.render_scale, - egeo.loc, - &elem_damage, + &damage, &self.logger, )?; } @@ -726,18 +592,13 @@ impl Space { state.last_state = self .windows .iter() - .map(|window| { - let wgeo = window_rect_with_popups(window, &self.id); - (ToplevelId::Xdg(window.0.id), wgeo) + .map(|w| w as &SpaceElem) + .chain(layer_map.layers().map(|l| l as &SpaceElem)) + .chain(custom_elements.iter().map(|c| c as &SpaceElem)) + .map(|elem| { + let geo = elem.geometry(self.id); + (ToplevelId::from(elem), geo) }) - .chain(layer_map.layers().map(|layer| { - let lgeo = layer_map.layer_geometry(layer); - (ToplevelId::Layer(layer.0.id), lgeo) - })) - .chain(custom_elements.iter().map(|custom| { - let egeo = custom.geometry(); - (ToplevelId::Custom(custom.type_of(), custom.id()), egeo) - })) .collect(); state.old_damage.push_front(new_damage); @@ -768,78 +629,6 @@ impl Space { } } -pub trait RenderElement -where - R: Renderer + ImportAll, - F: Frame, - E: std::error::Error, - T: Texture + 'static, - Self: Any + 'static, -{ - fn id(&self) -> usize; - fn type_of(&self) -> TypeId { - std::any::Any::type_id(self) - } - fn geometry(&self) -> Rectangle; - fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec>; - fn draw( - &self, - renderer: &mut R, - frame: &mut F, - scale: f64, - location: Point, - damage: &[Rectangle], - log: &slog::Logger, - ) -> Result<(), R::Error>; -} - -#[derive(Debug)] -pub struct SurfaceTree { - pub surface: WlSurface, - pub position: Point, -} - -impl RenderElement for SurfaceTree -where - R: Renderer + ImportAll, - F: Frame, - E: std::error::Error, - T: Texture + 'static, -{ - fn id(&self) -> usize { - self.surface.as_ref().id() as usize - } - fn geometry(&self) -> Rectangle { - let mut bbox = super::utils::bbox_from_surface_tree(&self.surface, (0, 0)); - bbox.loc += self.position; - bbox - } - - fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { - super::utils::damage_from_surface_tree(&self.surface, (0, 0), for_values) - } - - fn draw( - &self, - renderer: &mut R, - frame: &mut F, - scale: f64, - location: Point, - damage: &[Rectangle], - log: &slog::Logger, - ) -> Result<(), R::Error> { - crate::backend::renderer::utils::draw_surface_tree( - renderer, - frame, - &self.surface, - scale, - location, - damage, - log, - ) - } -} - #[derive(Debug, thiserror::Error)] pub enum RenderError { #[error(transparent)] @@ -847,35 +636,3 @@ pub enum RenderError { #[error("Output has no active mode")] OutputNoMode, } - -fn window_geo(window: &Window, space_id: &usize) -> Rectangle { - let loc = window_loc(window, space_id); - let mut wgeo = window.geometry(); - wgeo.loc = loc; - wgeo -} - -fn window_rect(window: &Window, space_id: &usize) -> Rectangle { - let loc = window_loc(window, space_id); - let mut wgeo = window.bbox(); - wgeo.loc += loc; - wgeo -} - -fn window_rect_with_popups(window: &Window, space_id: &usize) -> Rectangle { - let loc = window_loc(window, space_id); - let mut wgeo = window.bbox_with_popups(); - wgeo.loc += loc; - wgeo -} - -fn window_loc(window: &Window, space_id: &usize) -> Point { - window - .user_data() - .get::>>() - .unwrap() - .borrow() - .get(space_id) - .unwrap() - .location -} diff --git a/src/desktop/output.rs b/src/desktop/space/output.rs similarity index 53% rename from src/desktop/output.rs rename to src/desktop/space/output.rs index 445f46f..2b0b3fa 100644 --- a/src/desktop/output.rs +++ b/src/desktop/space/output.rs @@ -1,4 +1,6 @@ use crate::{ + backend::renderer::{Frame, ImportAll, Renderer, Texture}, + desktop::{space::SpaceElement, LayerSurface, Window}, utils::{Logical, Point, Rectangle}, wayland::output::Output, }; @@ -6,43 +8,34 @@ use indexmap::IndexMap; use wayland_server::protocol::wl_surface::WlSurface; use std::{ - any::TypeId, + any::{Any, TypeId}, cell::{RefCell, RefMut}, collections::{HashMap, VecDeque}, }; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub(super) enum ToplevelId { - Xdg(usize), - Layer(usize), - Custom(TypeId, usize), +pub struct ToplevelId { + t_id: TypeId, + id: usize, } -impl ToplevelId { - pub fn is_xdg(&self) -> bool { - match self { - ToplevelId::Xdg(_) => true, - _ => false, - } - } - - pub fn is_layer(&self) -> bool { - match self { - ToplevelId::Layer(_) => true, - _ => false, - } - } - - pub fn is_custom(&self) -> bool { - match self { - ToplevelId::Custom(_, _) => true, - _ => false, +impl From<&dyn SpaceElement> for ToplevelId +where + R: Renderer + ImportAll + 'static, + F: Frame + 'static, + E: std::error::Error + 'static, + T: Texture + 'static, +{ + fn from(elem: &dyn SpaceElement) -> ToplevelId { + ToplevelId { + t_id: elem.type_of(), + id: elem.id(), } } } #[derive(Clone, Default)] -pub(super) struct OutputState { +pub struct OutputState { pub location: Point, pub render_scale: f64, @@ -54,8 +47,8 @@ pub(super) struct OutputState { pub surfaces: Vec, } -pub(super) type OutputUserdata = RefCell>; -pub(super) fn output_state(space: usize, o: &Output) -> RefMut<'_, OutputState> { +pub type OutputUserdata = RefCell>; +pub fn output_state(space: usize, o: &Output) -> RefMut<'_, OutputState> { let userdata = o.user_data(); userdata.insert_if_missing(OutputUserdata::default); RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { diff --git a/src/desktop/space/window.rs b/src/desktop/space/window.rs new file mode 100644 index 0000000..31dc021 --- /dev/null +++ b/src/desktop/space/window.rs @@ -0,0 +1,106 @@ +use crate::{ + backend::renderer::{Frame, ImportAll, Renderer, Texture}, + desktop::{ + space::{Space, SpaceElement}, + window::{draw_window, Window}, + }, + utils::{Logical, Point, Rectangle}, + wayland::output::Output, +}; +use std::{ + any::TypeId, + cell::{RefCell, RefMut}, + collections::HashMap, +}; + +#[derive(Default)] +pub struct WindowState { + pub location: Point, + pub drawn: bool, +} + +pub type WindowUserdata = RefCell>; +pub fn window_state(space: usize, w: &Window) -> RefMut<'_, WindowState> { + let userdata = w.user_data(); + userdata.insert_if_missing(WindowUserdata::default); + RefMut::map(userdata.get::().unwrap().borrow_mut(), |m| { + m.entry(space).or_default() + }) +} + +pub fn window_geo(window: &Window, space_id: &usize) -> Rectangle { + let loc = window_loc(window, space_id); + let mut wgeo = window.geometry(); + wgeo.loc = loc; + wgeo +} + +pub fn window_rect(window: &Window, space_id: &usize) -> Rectangle { + let loc = window_loc(window, space_id); + let mut wgeo = window.bbox(); + wgeo.loc += loc; + wgeo +} + +pub fn window_rect_with_popups(window: &Window, space_id: &usize) -> Rectangle { + let loc = window_loc(window, space_id); + let mut wgeo = window.bbox_with_popups(); + wgeo.loc += loc; + wgeo +} + +pub fn window_loc(window: &Window, space_id: &usize) -> Point { + window + .user_data() + .get::>>() + .unwrap() + .borrow() + .get(space_id) + .unwrap() + .location +} + +impl SpaceElement for Window +where + R: Renderer + ImportAll, + F: Frame, + E: std::error::Error, + T: Texture + 'static, +{ + fn id(&self) -> usize { + self.0.id + } + + fn type_of(&self) -> TypeId { + TypeId::of::() + } + + fn location(&self, space_id: usize) -> Point { + window_loc(self, &space_id) + } + + fn geometry(&self, space_id: usize) -> Rectangle { + window_rect_with_popups(self, &space_id) + } + + fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { + self.accumulated_damage(for_values) + } + + fn draw( + &self, + space_id: usize, + renderer: &mut R, + frame: &mut F, + scale: f64, + location: Point, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), R::Error> { + let res = draw_window(renderer, frame, &self, scale, location, damage, log); + if res.is_ok() { + window_state(space_id, self).drawn = true; + } + res + } +} From 64c535464af39c654f496fd5731ae186cd990fdb Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 28 Dec 2021 13:06:11 +0100 Subject: [PATCH 24/52] swapchain: Keep buffers on reset and wipe metadata We make no guarantees about the buffer contents after a fresh allocation in smithay anyway, so to avoid expensive recreation of a bunch of resources, try to keep the buffers on reset and just wipe all its metadata (most importantly the age). --- src/backend/allocator/swapchain.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/backend/allocator/swapchain.rs b/src/backend/allocator/swapchain.rs index 0603cc9..60783bf 100644 --- a/src/backend/allocator/swapchain.rs +++ b/src/backend/allocator/swapchain.rs @@ -196,6 +196,15 @@ where /// Remove all internally cached buffers to e.g. reset age values pub fn reset_buffers(&mut self) { - self.slots = Default::default(); + for slot in &mut self.slots { + if let Some(internal_slot) = Arc::get_mut(slot) { + *internal_slot = InternalSlot { + buffer: internal_slot.buffer.take(), + ..Default::default() + }; + } else { + *slot = Default::default(); + } + } } } From 359c060e0f47055c342127de2bbfc40028a6af54 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 28 Dec 2021 13:08:18 +0100 Subject: [PATCH 25/52] space: apply transform for output_geometry --- src/desktop/space/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 4734d14..08b5b3e 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -215,11 +215,16 @@ impl Space { return None; } + let transform: Transform = o.current_transform().into(); let state = output_state(self.id, o); o.current_mode().map(|mode| { Rectangle::from_loc_and_size( state.location, - mode.size.to_f64().to_logical(state.render_scale).to_i32_round(), + transform + .transform_size(mode.size) + .to_f64() + .to_logical(state.render_scale) + .to_i32_round(), ) }) } From 3530ac7335553d68418bc1afdf582ee934087479 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 29 Dec 2021 15:52:18 +0100 Subject: [PATCH 26/52] egl: stop egl spamming nclassified messages --- src/backend/egl/ffi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/egl/ffi.rs b/src/backend/egl/ffi.rs index 6fc7c69..ed376c6 100644 --- a/src/backend/egl/ffi.rs +++ b/src/backend/egl/ffi.rs @@ -40,7 +40,7 @@ extern "system" fn egl_debug_log( } egl::DEBUG_MSG_WARN_KHR => slog::warn!(logger, "[EGL] {}: {}", command_utf8, message_utf8), egl::DEBUG_MSG_INFO_KHR => slog::info!(logger, "[EGL] {}: {}", command_utf8, message_utf8), - _ => slog::debug!(logger, "[EGL] {}: {}", command_utf8, message_utf8), + _ => {} }; }); } From f4232448642102e3ca641bbc7e23b38be708de21 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 29 Dec 2021 15:54:59 +0100 Subject: [PATCH 27/52] egl: Optionally support buffer age and damage --- build.rs | 2 ++ src/backend/egl/native.rs | 13 ++++++++++++- src/backend/egl/surface.rs | 23 +++++++++++++++++++++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/build.rs b/build.rs index 5ebcb19..a8585ac 100644 --- a/build.rs +++ b/build.rs @@ -35,6 +35,8 @@ fn gl_generate() { "EGL_KHR_image_base", "EGL_EXT_image_dma_buf_import", "EGL_EXT_image_dma_buf_import_modifiers", + "EGL_EXT_buffer_age", + "EGL_EXT_swap_buffers_with_damage", ], ) .write_bindings(gl_generator::GlobalGenerator, &mut file) diff --git a/src/backend/egl/native.rs b/src/backend/egl/native.rs index 097c206..1219c39 100644 --- a/src/backend/egl/native.rs +++ b/src/backend/egl/native.rs @@ -1,6 +1,7 @@ //! Type safe native types for safe context/surface creation use super::{display::EGLDisplayHandle, ffi, wrap_egl_call, EGLDevice, SwapBuffersError}; +use crate::utils::{Physical, Rectangle}; #[cfg(feature = "backend_winit")] use std::os::raw::c_int; use std::os::raw::c_void; @@ -246,9 +247,19 @@ pub unsafe trait EGLNativeSurface: Send + Sync { &self, display: &Arc, surface: ffi::egl::types::EGLSurface, + damage: Option<&mut [Rectangle]>, ) -> Result<(), SwapBuffersError> { wrap_egl_call(|| unsafe { - ffi::egl::SwapBuffers(***display, surface as *const _); + if let Some(damage) = damage { + ffi::egl::SwapBuffersWithDamageEXT( + ***display, + surface as *const _, + damage.as_mut_ptr() as *mut _, + damage.len() as i32, + ); + } else { + ffi::egl::SwapBuffers(***display, surface as *const _); + } }) .map_err(SwapBuffersError::EGLSwapBuffers) } diff --git a/src/backend/egl/surface.rs b/src/backend/egl/surface.rs index 0070ff4..20f5113 100644 --- a/src/backend/egl/surface.rs +++ b/src/backend/egl/surface.rs @@ -12,6 +12,7 @@ use crate::backend::egl::{ native::EGLNativeSurface, EGLError, SwapBuffersError, }; +use crate::utils::{Physical, Rectangle}; use slog::{debug, o}; @@ -79,12 +80,30 @@ impl EGLSurface { }) } + /// Returns the buffer age of the underlying back buffer + pub fn buffer_age(&self) -> i32 { + let surface = self.surface.load(Ordering::SeqCst); + let mut age = 0; + unsafe { + ffi::egl::QuerySurface( + **self.display, + surface as *const _, + ffi::egl::BUFFER_AGE_EXT as i32, + &mut age as *mut _, + ); + } + age + } + /// Swaps buffers at the end of a frame. - pub fn swap_buffers(&self) -> ::std::result::Result<(), SwapBuffersError> { + pub fn swap_buffers( + &self, + damage: Option<&mut [Rectangle]>, + ) -> ::std::result::Result<(), SwapBuffersError> { let surface = self.surface.load(Ordering::SeqCst); let result = if !surface.is_null() { - self.native.swap_buffers(&self.display, surface) + self.native.swap_buffers(&self.display, surface, damage) } else { Err(SwapBuffersError::EGLSwapBuffers(EGLError::BadSurface)) }; From 7ae79fcba5d918dc100e58754683f4433cf7a5b8 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 29 Dec 2021 15:55:44 +0100 Subject: [PATCH 28/52] winit: Support damage-tracking --- src/backend/winit/mod.rs | 72 +++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/src/backend/winit/mod.rs b/src/backend/winit/mod.rs index 63a7a3f..46f5f7b 100644 --- a/src/backend/winit/mod.rs +++ b/src/backend/winit/mod.rs @@ -27,11 +27,11 @@ use crate::{ }, input::InputEvent, renderer::{ - gles2::{Gles2Error, Gles2Frame, Gles2Renderer}, - Bind, Renderer, Transform, Unbind, + gles2::{Gles2Error, Gles2Renderer}, + Bind, }, }, - utils::{Logical, Physical, Size}, + utils::{Logical, Physical, Rectangle, Size}, }; use std::{cell::RefCell, rc::Rc, time::Instant}; use wayland_egl as wegl; @@ -90,6 +90,7 @@ pub struct WinitGraphicsBackend { egl: Rc, window: Rc, size: Rc>, + damage_tracking: bool, resize_notification: Rc>>>, } @@ -220,6 +221,10 @@ where let egl = Rc::new(surface); let renderer = unsafe { Gles2Renderer::new(context, log.clone())? }; let resize_notification = Rc::new(Cell::new(None)); + let damage_tracking = display.extensions.iter().any(|ext| ext == "EGL_EXT_buffer_age") + && display.extensions.iter().any(|ext| { + ext == "EGL_KHR_swap_buffers_with_damage" || ext == "EGL_EXT_swap_buffers_with_damage" + }); Ok(( WinitGraphicsBackend { @@ -227,6 +232,7 @@ where _display: display, egl, renderer, + damage_tracking, size: size.clone(), resize_notification: resize_notification.clone(), }, @@ -281,28 +287,56 @@ impl WinitGraphicsBackend { &mut self.renderer } - /// Shortcut to `Renderer::render` with the current window dimensions - /// and this window set as the rendering target. - pub fn render(&mut self, rendering: F) -> Result - where - F: FnOnce(&mut Gles2Renderer, &mut Gles2Frame) -> R, - { + /// Bind the underlying window to the underlying renderer + pub fn bind(&mut self) -> Result<(), crate::backend::SwapBuffersError> { // Were we told to resize? if let Some(size) = self.resize_notification.take() { self.egl.resize(size.w, size.h, 0, 0); } - let size = { - let size = self.size.borrow(); - size.physical_size - }; - self.renderer.bind(self.egl.clone())?; - // Why is winit falling out of place with the coordinate system? - let result = self.renderer.render(size, Transform::Flipped180, rendering)?; - self.egl.swap_buffers()?; - self.renderer.unbind()?; - Ok(result) + Ok(()) + } + + /// Retrieve the buffer age of the current backbuffer of the window + pub fn buffer_age(&self) -> usize { + if self.damage_tracking { + self.egl.buffer_age() as usize + } else { + 0 + } + } + + /// Submits the back buffer to the window by swapping, requires the window to be previously bound (see [`WinitGraphicsBackend::bind`]). + pub fn submit( + &mut self, + damage: Option<&[Rectangle]>, + scale: f64, + ) -> Result<(), crate::backend::SwapBuffersError> { + let mut damage = if self.damage_tracking && damage.is_some() && !damage.unwrap().is_empty() { + let size = self + .size + .borrow() + .physical_size + .to_f64() + .to_logical(scale) + .to_i32_round::(); + let damage = damage + .unwrap() + .iter() + .map(|rect| { + Rectangle::from_loc_and_size((rect.loc.x, size.h - rect.loc.y - rect.size.h), rect.size) + .to_f64() + .to_physical(scale) + .to_i32_round::() + }) + .collect::>(); + Some(damage) + } else { + None + }; + self.egl.swap_buffers(damage.as_mut().map(|x| &mut **x))?; + Ok(()) } } From ab21df1943c1e9355e973631730cd62539f2091d Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 29 Dec 2021 15:56:00 +0100 Subject: [PATCH 29/52] space: Return new damage on render --- src/desktop/space/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 08b5b3e..7065ec2 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -411,7 +411,7 @@ impl Space { custom_elements: &[Box< dyn RenderElement::Frame, ::Error, ::TextureId>, >], - ) -> Result> + ) -> Result>>, RenderError> where R: Renderer + ImportAll + 'static, R::TextureId: 'static, @@ -520,7 +520,7 @@ impl Space { }); if damage.is_empty() { - return Ok(false); + return Ok(None); } let output_transform: Transform = output.current_transform().into(); @@ -605,9 +605,9 @@ impl Space { (ToplevelId::from(elem), geo) }) .collect(); - state.old_damage.push_front(new_damage); + state.old_damage.push_front(new_damage.clone()); - Ok(true) + Ok(Some(new_damage)) } pub fn send_frames(&self, all: bool, time: u32) { From 08ac5ba6d1bd49d1e85b9e4fbce00ad6eb189129 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Thu, 30 Dec 2021 15:57:40 +0100 Subject: [PATCH 30/52] desktop: Make window damage public --- src/desktop/window.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 762676d..1ebd138 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -260,10 +260,7 @@ impl Window { } /// Damage of all the surfaces of this window - pub(super) fn accumulated_damage( - &self, - for_values: Option<(&Space, &Output)>, - ) -> Vec> { + pub fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { let mut damage = Vec::new(); if let Some(surface) = self.0.toplevel.get_surface() { damage.extend( From 171456c7ba18d8c0abbcceb5f819d348438aeff9 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Thu, 30 Dec 2021 20:08:47 +0100 Subject: [PATCH 31/52] gbm: Support resetting buffers --- src/backend/drm/surface/gbm.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend/drm/surface/gbm.rs b/src/backend/drm/surface/gbm.rs index c587dd2..d5d646e 100644 --- a/src/backend/drm/surface/gbm.rs +++ b/src/backend/drm/surface/gbm.rs @@ -238,6 +238,11 @@ where flip.map_err(Error::DrmError) } + /// Reset the underlying buffers + pub fn reset_buffers(&mut self) { + self.swapchain.reset_buffers() + } + /// Returns the underlying [`crtc`](drm::control::crtc) of this surface pub fn crtc(&self) -> crtc::Handle { self.drm.crtc() From 5b6700c151a73db9f3b835b6ebccedf3f989e4bc Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 4 Jan 2022 12:50:07 +0100 Subject: [PATCH 32/52] desktop: fix popup placement --- src/desktop/popup.rs | 20 ++++++++++++++++++-- src/desktop/utils.rs | 2 +- src/desktop/window.rs | 24 ++++++++++-------------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/desktop/popup.rs b/src/desktop/popup.rs index a6e2de6..58aa164 100644 --- a/src/desktop/popup.rs +++ b/src/desktop/popup.rs @@ -1,8 +1,8 @@ use crate::{ - utils::{DeadResource, Logical, Point}, + utils::{DeadResource, Logical, Point, Rectangle}, wayland::{ compositor::{get_role, with_states}, - shell::xdg::{PopupSurface, XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE}, + shell::xdg::{PopupSurface, SurfaceCachedState, XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE}, }, }; use std::sync::{Arc, Mutex}; @@ -235,6 +235,22 @@ impl PopupKind { } } + pub fn geometry(&self) -> Rectangle { + let wl_surface = match self.get_surface() { + Some(s) => s, + None => return Rectangle::from_loc_and_size((0, 0), (0, 0)), + }; + + with_states(wl_surface, |states| { + states + .cached_state + .current::() + .geometry + .unwrap_or_default() + }) + .unwrap() + } + fn location(&self) -> Point { let wl_surface = match self.get_surface() { Some(s) => s, diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs index eb2cbf7..b0d847e 100644 --- a/src/desktop/utils.rs +++ b/src/desktop/utils.rs @@ -49,7 +49,7 @@ impl SurfaceState { .input_region .as_ref() .unwrap() - .contains(point.to_i32_floor()) + .contains(point.to_i32_round()) } } diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 1ebd138..42c6954 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -171,7 +171,8 @@ impl Window { .flatten() { if let Some(surface) = popup.get_surface() { - bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, location)); + let offset = self.geometry().loc + location - popup.geometry().loc; + bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, offset)); } } } @@ -245,9 +246,10 @@ impl Window { .into_iter() .flatten() { + let offset = self.geometry().loc + location - popup.geometry().loc; if let Some(result) = popup .get_surface() - .and_then(|surface| under_from_surface_tree(surface, point, location)) + .and_then(|surface| under_from_surface_tree(surface, point, offset)) { return Some(result); } @@ -274,8 +276,9 @@ impl Window { .flatten() { if let Some(surface) = popup.get_surface() { - let bbox = bbox_from_surface_tree(surface, location); - let popup_damage = damage_from_surface_tree(surface, location, for_values); + let offset = self.geometry().loc + location - popup.geometry().loc; + let bbox = bbox_from_surface_tree(surface, offset); + let popup_damage = damage_from_surface_tree(surface, offset, for_values); damage.extend(popup_damage.into_iter().flat_map(|rect| rect.intersection(bbox))); } } @@ -317,23 +320,16 @@ where .flatten() { if let Some(surface) = popup.get_surface() { + let offset = window.geometry().loc + p_location - popup.geometry().loc; let damage = damage .iter() .cloned() .map(|mut geo| { - geo.loc -= p_location; + geo.loc -= offset; geo }) .collect::>(); - draw_surface_tree( - renderer, - frame, - surface, - scale, - location + p_location, - &damage, - log, - )?; + draw_surface_tree(renderer, frame, surface, scale, location + offset, &damage, log)?; } } } From 61b19e4198451b35eb121b09d4831c2d78723e6f Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 4 Jan 2022 18:05:43 +0100 Subject: [PATCH 33/52] renderer: fixup damage calculations for smaller buffer sizes --- src/backend/renderer/utils/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/backend/renderer/utils/mod.rs b/src/backend/renderer/utils/mod.rs index bbf65a8..08323a7 100644 --- a/src/backend/renderer/utils/mod.rs +++ b/src/backend/renderer/utils/mod.rs @@ -88,7 +88,7 @@ where let mut result = Ok(()); let damage = damage .iter() - .map(|geo| geo.to_f64().to_physical(scale).to_i32_round()) + .map(|geo| geo.to_f64().to_physical(scale).to_i32_up()) .collect::>(); with_surface_tree_upward( surface, @@ -160,7 +160,12 @@ where let rect = Rectangle::::from_loc_and_size( surface_offset.to_f64().to_physical(scale).to_i32_round(), - buffer_dimensions.unwrap(), + buffer_dimensions + .unwrap_or_default() + .to_logical(buffer_scale) + .to_f64() + .to_physical(scale) + .to_i32_round(), ); let new_damage = damage .iter() From 3674daf0830deb6be26d202ba8b08031ca72b9af Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 4 Jan 2022 18:28:39 +0100 Subject: [PATCH 34/52] utils: Add macro for global ids --- src/desktop/layer.rs | 30 ++---------------------------- src/desktop/space/mod.rs | 31 ++----------------------------- src/desktop/window.rs | 28 +--------------------------- src/utils/ids.rs | 37 +++++++++++++++++++++++++++++++++++++ src/utils/mod.rs | 1 + 5 files changed, 43 insertions(+), 84 deletions(-) create mode 100644 src/utils/ids.rs diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 4fa52aa..4313005 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -16,38 +16,12 @@ use wayland_server::protocol::wl_surface::WlSurface; use std::{ cell::{RefCell, RefMut}, - collections::HashSet, hash::{Hash, Hasher}, rc::Rc, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, Weak, - }, + sync::{Arc, Mutex, Weak}, }; -// TODO: Should this be a macro? -static LAYER_ID: AtomicUsize = AtomicUsize::new(0); -lazy_static::lazy_static! { - static ref LAYER_IDS: Mutex> = Mutex::new(HashSet::new()); -} - -fn next_layer_id() -> usize { - let mut ids = LAYER_IDS.lock().unwrap(); - if ids.len() == usize::MAX { - // Theoretically the code below wraps around correctly, - // but that is hard to detect and might deadlock. - // Maybe make this a debug_assert instead? - panic!("Out of window ids"); - } - - let mut id = LAYER_ID.fetch_add(1, Ordering::SeqCst); - while ids.iter().any(|k| *k == id) { - id = LAYER_ID.fetch_add(1, Ordering::SeqCst); - } - - ids.insert(id); - id -} +crate::utils::ids::id_gen!(next_layer_id, LAYER_ID, LAYER_IDS); #[derive(Debug)] pub struct LayerMap { diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 7065ec2..89191bc 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -15,14 +15,7 @@ use crate::{ }, }; use indexmap::{IndexMap, IndexSet}; -use std::{ - cell::RefCell, - collections::{HashSet, VecDeque}, - sync::{ - atomic::{AtomicUsize, Ordering}, - Mutex, - }, -}; +use std::{cell::RefCell, collections::VecDeque}; use wayland_server::protocol::wl_surface::WlSurface; mod element; @@ -35,27 +28,7 @@ use self::layer::*; use self::output::*; use self::window::*; -static SPACE_ID: AtomicUsize = AtomicUsize::new(0); -lazy_static::lazy_static! { - static ref SPACE_IDS: Mutex> = Mutex::new(HashSet::new()); -} -fn next_space_id() -> usize { - let mut ids = SPACE_IDS.lock().unwrap(); - if ids.len() == usize::MAX { - // Theoretically the code below wraps around correctly, - // but that is hard to detect and might deadlock. - // Maybe make this a debug_assert instead? - panic!("Out of space ids"); - } - - let mut id = SPACE_ID.fetch_add(1, Ordering::SeqCst); - while ids.iter().any(|k| *k == id) { - id = SPACE_ID.fetch_add(1, Ordering::SeqCst); - } - - ids.insert(id); - id -} +crate::utils::ids::id_gen!(next_space_id, SPACE_ID, SPACE_IDS); // TODO: Maybe replace UnmanagedResource if nothing else comes up? #[derive(Debug, thiserror::Error)] diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 42c6954..515e043 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -10,40 +10,14 @@ use crate::{ }; use std::{ cell::Cell, - collections::HashSet, hash::{Hash, Hasher}, rc::Rc, - sync::{ - atomic::{AtomicUsize, Ordering}, - Mutex, - }, }; use wayland_commons::user_data::UserDataMap; use wayland_protocols::xdg_shell::server::xdg_toplevel; use wayland_server::protocol::wl_surface; -static WINDOW_ID: AtomicUsize = AtomicUsize::new(0); -lazy_static::lazy_static! { - static ref WINDOW_IDS: Mutex> = Mutex::new(HashSet::new()); -} - -fn next_window_id() -> usize { - let mut ids = WINDOW_IDS.lock().unwrap(); - if ids.len() == usize::MAX { - // Theoretically the code below wraps around correctly, - // but that is hard to detect and might deadlock. - // Maybe make this a debug_assert instead? - panic!("Out of window ids"); - } - - let mut id = WINDOW_ID.fetch_add(1, Ordering::SeqCst); - while ids.iter().any(|k| *k == id) { - id = WINDOW_ID.fetch_add(1, Ordering::SeqCst); - } - - ids.insert(id); - id -} +crate::utils::ids::id_gen!(next_window_id, WINDOW_ID, WINDOW_IDS); #[derive(Debug, Clone, PartialEq)] pub enum Kind { diff --git a/src/utils/ids.rs b/src/utils/ids.rs new file mode 100644 index 0000000..ce4178d --- /dev/null +++ b/src/utils/ids.rs @@ -0,0 +1,37 @@ +macro_rules! id_gen { + ($func_name:ident, $id_name:ident, $ids_name:ident) => { + static $id_name: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + lazy_static::lazy_static! { + static ref $ids_name: std::sync::Mutex> = + std::sync::Mutex::new(std::collections::HashSet::new()); + } + + fn $func_name() -> usize { + let mut ids = $ids_name.lock().unwrap(); + if ids.len() == usize::MAX { + panic!("Out of ids"); + } + + let id = loop { + let new_id = $id_name.fetch_update( + std::sync::atomic::Ordering::SeqCst, + std::sync::atomic::Ordering::SeqCst, + |mut id| { + while ids.iter().any(|k| *k == id) { + id += 1; + } + Some(id) + }, + ); + if let Ok(id) = new_id { + break id; + } + }; + + ids.insert(id); + id + } + }; +} + +pub(crate) use id_gen; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9b8edd3..d4d5b38 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -6,6 +6,7 @@ pub mod signaling; #[cfg(feature = "x11rb_event_source")] pub mod x11rb; +pub(crate) mod ids; pub mod user_data; pub use self::geometry::{Buffer, Coordinate, Logical, Physical, Point, Raw, Rectangle, Size}; From 537b34fe0b924b8708cda1a6d15afeecdf19db6e Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 4 Jan 2022 18:33:04 +0100 Subject: [PATCH 35/52] cleanup: imports --- src/backend/renderer/utils/mod.rs | 2 +- src/desktop/layer.rs | 2 +- src/desktop/space/layer.rs | 2 +- src/desktop/space/output.rs | 4 ++-- src/desktop/window.rs | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/backend/renderer/utils/mod.rs b/src/backend/renderer/utils/mod.rs index 08323a7..28d90f7 100644 --- a/src/backend/renderer/utils/mod.rs +++ b/src/backend/renderer/utils/mod.rs @@ -1,6 +1,6 @@ use crate::{ backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, - utils::{Buffer, Logical, Physical, Point, Rectangle, Size}, + utils::{Logical, Physical, Point, Rectangle, Size}, wayland::compositor::{ is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, SurfaceAttributes, TraversalAction, diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 4313005..0e101d9 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -1,6 +1,6 @@ use crate::{ backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture}, - desktop::{space::RenderElement, utils::*, PopupManager, Space}, + desktop::{utils::*, PopupManager, Space}, utils::{user_data::UserDataMap, Logical, Point, Rectangle}, wayland::{ compositor::with_states, diff --git a/src/desktop/space/layer.rs b/src/desktop/space/layer.rs index d9157e8..53f186f 100644 --- a/src/desktop/space/layer.rs +++ b/src/desktop/space/layer.rs @@ -42,7 +42,7 @@ where TypeId::of::() } - fn geometry(&self, space_id: usize) -> Rectangle { + fn geometry(&self, _space_id: usize) -> Rectangle { let mut bbox = self.bbox_with_popups(); let state = output_layer_state(self); bbox.loc += state.location; diff --git a/src/desktop/space/output.rs b/src/desktop/space/output.rs index 2b0b3fa..cb4f825 100644 --- a/src/desktop/space/output.rs +++ b/src/desktop/space/output.rs @@ -1,6 +1,6 @@ use crate::{ backend::renderer::{Frame, ImportAll, Renderer, Texture}, - desktop::{space::SpaceElement, LayerSurface, Window}, + desktop::space::SpaceElement, utils::{Logical, Point, Rectangle}, wayland::output::Output, }; @@ -8,7 +8,7 @@ use indexmap::IndexMap; use wayland_server::protocol::wl_surface::WlSurface; use std::{ - any::{Any, TypeId}, + any::TypeId, cell::{RefCell, RefMut}, collections::{HashMap, VecDeque}, }; diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 515e043..e4e6aa1 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -129,7 +129,7 @@ impl Window { /// A bounding box over this window and its children. // TODO: Cache and document when to trigger updates. If possible let space do it pub fn bbox(&self) -> Rectangle { - if let Some(surface) = self.0.toplevel.get_surface() { + if self.0.toplevel.get_surface().is_some() { self.0.bbox.get() } else { Rectangle::from_loc_and_size((0, 0), (0, 0)) @@ -167,7 +167,7 @@ impl Window { }) .unwrap_or(false), #[cfg(feature = "xwayland")] - Kind::X11(ref t) => unimplemented!(), + Kind::X11(ref _t) => unimplemented!(), } } @@ -176,7 +176,7 @@ impl Window { match self.0.toplevel { Kind::Xdg(ref t) => t.send_configure(), #[cfg(feature = "xwayland")] - Kind::X11(ref t) => unimplemented!(), + Kind::X11(ref _t) => unimplemented!(), } } From 26e1576f87857ec892b0001389eddb9c1cf6838c Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 4 Jan 2022 18:42:52 +0100 Subject: [PATCH 36/52] space: make window activation optional --- src/desktop/space/mod.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 89191bc..bd38ee8 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -70,25 +70,26 @@ impl Space { /// Map window and moves it to top of the stack /// /// This can safely be called on an already mapped window - pub fn map_window>>(&mut self, window: &Window, location: P) { - self.insert_window(window); + pub fn map_window>>(&mut self, window: &Window, location: P, activate: bool) { + self.insert_window(window, activate); window_state(self.id, window).location = location.into(); } - pub fn raise_window(&mut self, window: &Window) { + pub fn raise_window(&mut self, window: &Window, activate: bool) { if self.windows.shift_remove(window) { - self.insert_window(window); + self.insert_window(window, activate); } } - fn insert_window(&mut self, window: &Window) { + fn insert_window(&mut self, window: &Window, activate: bool) { self.windows.insert(window.clone()); - // TODO: should this be handled by us? - window.set_activated(true); - for w in self.windows.iter() { - if w != window { - w.set_activated(false); + if activate { + window.set_activated(true); + for w in self.windows.iter() { + if w != window { + w.set_activated(false); + } } } } From d69d15630e33a9fbb41ed1c22526d96302bb1156 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 4 Jan 2022 19:06:18 +0100 Subject: [PATCH 37/52] space: make render error check more readable --- src/desktop/space/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index bd38ee8..a737c74 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -498,7 +498,7 @@ impl Space { } let output_transform: Transform = output.current_transform().into(); - if let Err(err) = renderer.render( + let res = renderer.render( output_transform .transform_size(output_size) .to_f64() @@ -559,7 +559,9 @@ impl Space { Result::<(), R::Error>::Ok(()) }, - ) { + ); + + if let Err(err) = res { // if the rendering errors on us, we need to be prepared, that this whole buffer was partially updated and thus now unusable. // thus clean our old states before returning state.old_damage = VecDeque::new(); From a5f3c5c5d2ff83800b8e6aac42aa01eee76c9eef Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 5 Jan 2022 13:03:41 +0100 Subject: [PATCH 38/52] clippy fixes --- src/backend/renderer/gles2/mod.rs | 5 +--- src/backend/renderer/mod.rs | 1 + src/backend/renderer/utils/mod.rs | 2 +- src/backend/winit/mod.rs | 41 +++++++++++++++++-------------- src/desktop/layer.rs | 14 +++++------ src/desktop/mod.rs | 2 +- src/desktop/space/element.rs | 1 + src/desktop/space/mod.rs | 10 +++----- src/desktop/space/window.rs | 2 +- src/desktop/utils.rs | 7 +++--- src/desktop/window.rs | 5 ---- src/wayland/compositor/mod.rs | 14 +++++------ 12 files changed, 49 insertions(+), 55 deletions(-) diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 00d2344..4faa4c3 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -5,10 +5,7 @@ use std::ffi::CStr; use std::fmt; use std::ptr; use std::rc::Rc; -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - mpsc::{channel, Receiver, Sender}, -}; +use std::sync::mpsc::{channel, Receiver, Sender}; use std::{collections::HashSet, os::raw::c_char}; use cgmath::{prelude::*, Matrix3, Vector2, Vector3}; diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index e02de36..1b70e85 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -178,6 +178,7 @@ pub trait Frame { /// Render a texture to the current target as a flat 2d-plane at a given /// position and applying the given transformation with the given alpha value. /// (Meaning `src_transform` should match the orientation of surface being rendered). + #[allow(clippy::too_many_arguments)] fn render_texture_at( &mut self, texture: &Self::TextureId, diff --git a/src/backend/renderer/utils/mod.rs b/src/backend/renderer/utils/mod.rs index 28d90f7..9ac79e3 100644 --- a/src/backend/renderer/utils/mod.rs +++ b/src/backend/renderer/utils/mod.rs @@ -141,7 +141,7 @@ where TraversalAction::SkipChildren } }, - |surface, states, location| { + |_surface, states, location| { let mut location = *location; if let Some(data) = states.data_map.get::>() { let mut data = data.borrow_mut(); diff --git a/src/backend/winit/mod.rs b/src/backend/winit/mod.rs index 46f5f7b..b6f2f58 100644 --- a/src/backend/winit/mod.rs +++ b/src/backend/winit/mod.rs @@ -313,29 +313,32 @@ impl WinitGraphicsBackend { damage: Option<&[Rectangle]>, scale: f64, ) -> Result<(), crate::backend::SwapBuffersError> { - let mut damage = if self.damage_tracking && damage.is_some() && !damage.unwrap().is_empty() { - let size = self - .size - .borrow() - .physical_size - .to_f64() - .to_logical(scale) - .to_i32_round::(); - let damage = damage - .unwrap() - .iter() - .map(|rect| { - Rectangle::from_loc_and_size((rect.loc.x, size.h - rect.loc.y - rect.size.h), rect.size) + let mut damage = match damage { + Some(damage) if self.damage_tracking && !damage.is_empty() => { + let size = self + .size + .borrow() + .physical_size + .to_f64() + .to_logical(scale) + .to_i32_round::(); + let damage = damage + .iter() + .map(|rect| { + Rectangle::from_loc_and_size( + (rect.loc.x, size.h - rect.loc.y - rect.size.h), + rect.size, + ) .to_f64() .to_physical(scale) .to_i32_round::() - }) - .collect::>(); - Some(damage) - } else { - None + }) + .collect::>(); + Some(damage) + } + _ => None, }; - self.egl.swap_buffers(damage.as_mut().map(|x| &mut **x))?; + self.egl.swap_buffers(damage.as_deref_mut())?; Ok(()) } } diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 0e101d9..8a99374 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -41,7 +41,7 @@ pub fn layer_map_for_output(o: &Output) -> RefMut<'_, LayerMap> { (0, 0), o.current_mode() .map(|mode| mode.size.to_logical(o.current_scale())) - .unwrap_or((0, 0).into()), + .unwrap_or_else(|| (0, 0).into()), ), }) }); @@ -130,9 +130,9 @@ impl LayerMap { output .current_mode() .map(|mode| mode.size.to_logical(output.current_scale())) - .unwrap_or((0, 0).into()), + .unwrap_or_else(|| (0, 0).into()), ); - let mut zone = output_rect.clone(); + let mut zone = output_rect; slog::debug!( crate::slog_or_fallback(None), "Arranging layers into {:?}", @@ -212,18 +212,18 @@ impl LayerMap { location, size ); - if layer + let size_changed = layer .0 .surface .with_pending_state(|state| { state.size.replace(size).map(|old| old != size).unwrap_or(true) }) - .unwrap() - { + .unwrap(); + if size_changed { layer.0.surface.send_configure(); } - layer_state(&layer).location = location; + layer_state(layer).location = location; } slog::debug!(crate::slog_or_fallback(None), "Remaining zone {:?}", zone); diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index aad577a..66e8d65 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -1,5 +1,5 @@ // TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings -#![allow(missing_docs, clippy::all)] +#![allow(missing_docs)] pub(crate) mod layer; mod popup; pub mod space; diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index b62dc49..a719310 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -47,6 +47,7 @@ where } fn geometry(&self, space_id: usize) -> Rectangle; fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec>; + #[allow(clippy::too_many_arguments)] fn draw( &self, space_id: usize, diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index a737c74..d92a6de 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -30,7 +30,6 @@ use self::window::*; crate::utils::ids::id_gen!(next_space_id, SPACE_ID, SPACE_IDS); -// TODO: Maybe replace UnmanagedResource if nothing else comes up? #[derive(Debug, thiserror::Error)] pub enum SpaceError { #[error("Window is not mapped to this space")] @@ -43,11 +42,12 @@ pub struct Space { // in z-order, back to front windows: IndexSet, outputs: Vec, - // TODO: - //layers: Vec, logger: ::slog::Logger, } +pub type DynamicRenderElements = + Box::Frame, ::Error, ::TextureId>>; + impl Drop for Space { fn drop(&mut self) { SPACE_IDS.lock().unwrap().remove(&self.id); @@ -382,9 +382,7 @@ impl Space { output: &Output, age: usize, clear_color: [f32; 4], - custom_elements: &[Box< - dyn RenderElement::Frame, ::Error, ::TextureId>, - >], + custom_elements: &[DynamicRenderElements], ) -> Result>>, RenderError> where R: Renderer + ImportAll + 'static, diff --git a/src/desktop/space/window.rs b/src/desktop/space/window.rs index 31dc021..f1ece6b 100644 --- a/src/desktop/space/window.rs +++ b/src/desktop/space/window.rs @@ -97,7 +97,7 @@ where damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> { - let res = draw_window(renderer, frame, &self, scale, location, damage, log); + let res = draw_window(renderer, frame, self, scale, location, damage, log); if res.is_ok() { window_state(space_id, self).drawn = true; } diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs index b0d847e..79b27f6 100644 --- a/src/desktop/utils.rs +++ b/src/desktop/utils.rs @@ -109,11 +109,10 @@ where .as_ref() .map(|key| !data.damage_seen.contains(key)) .unwrap_or(true) + && states.role == Some("subsurface") { - if states.role == Some("subsurface") { - let current = states.cached_state.current::(); - location += current.location; - } + let current = states.cached_state.current::(); + location += current.location; } } TraversalAction::DoChildren(location) diff --git a/src/desktop/window.rs b/src/desktop/window.rs index e4e6aa1..9f43e59 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -105,9 +105,6 @@ impl Window { pub fn new(toplevel: Kind) -> Window { let id = next_window_id(); - // TODO: Do we want this? For new lets add Window::commit - //add_commit_hook(toplevel.get_surface().unwrap(), surface_commit); - Window(Rc::new(WindowInner { id, toplevel, @@ -127,7 +124,6 @@ impl Window { } /// A bounding box over this window and its children. - // TODO: Cache and document when to trigger updates. If possible let space do it pub fn bbox(&self) -> Rectangle { if self.0.toplevel.get_surface().is_some() { self.0.bbox.get() @@ -154,7 +150,6 @@ impl Window { } /// Activate/Deactivate this window - // TODO: Add more helpers for Maximize? Minimize? Fullscreen? I dunno pub fn set_activated(&self, active: bool) -> bool { match self.0.toplevel { Kind::Xdg(ref t) => t diff --git a/src/wayland/compositor/mod.rs b/src/wayland/compositor/mod.rs index 9d35d7c..08f2d53 100644 --- a/src/wayland/compositor/mod.rs +++ b/src/wayland/compositor/mod.rs @@ -449,7 +449,7 @@ mod tests { #[test] fn region_attributes_empty() { let region = RegionAttributes { rects: vec![] }; - assert_eq!(region.contains((0, 0)), false); + assert!(!region.contains((0, 0))); } #[test] @@ -458,7 +458,7 @@ mod tests { rects: vec![(RectangleKind::Add, Rectangle::from_loc_and_size((0, 0), (10, 10)))], }; - assert_eq!(region.contains((0, 0)), true); + assert!(region.contains((0, 0))); } #[test] @@ -473,8 +473,8 @@ mod tests { ], }; - assert_eq!(region.contains((0, 0)), false); - assert_eq!(region.contains((5, 5)), true); + assert!(!region.contains((0, 0))); + assert!(region.contains((5, 5))); } #[test] @@ -490,8 +490,8 @@ mod tests { ], }; - assert_eq!(region.contains((0, 0)), false); - assert_eq!(region.contains((5, 5)), true); - assert_eq!(region.contains((2, 2)), true); + assert!(!region.contains((0, 0))); + assert!(region.contains((5, 5))); + assert!(region.contains((2, 2))); } } From 8e34865acc3f23c8654cdd157c6d44b6aced4f88 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 5 Jan 2022 20:40:52 +0100 Subject: [PATCH 39/52] space: Let downstream `RenderElements` hash `(&Space, &Output)` --- src/backend/renderer/mod.rs | 1 + .../renderer/{utils/mod.rs => utils.rs} | 3 +- src/desktop/layer.rs | 9 ++- src/desktop/space/element.rs | 58 +++++++++++++++---- src/desktop/space/mod.rs | 28 ++++----- src/desktop/utils.rs | 6 +- 6 files changed, 74 insertions(+), 31 deletions(-) rename src/backend/renderer/{utils/mod.rs => utils.rs} (99%) diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index 1b70e85..95cdb2d 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -32,6 +32,7 @@ use crate::backend::egl::{ Error as EglError, }; +#[cfg(feature = "wayland_frontend")] pub mod utils; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] diff --git a/src/backend/renderer/utils/mod.rs b/src/backend/renderer/utils.rs similarity index 99% rename from src/backend/renderer/utils/mod.rs rename to src/backend/renderer/utils.rs index 9ac79e3..31eb4bb 100644 --- a/src/backend/renderer/utils/mod.rs +++ b/src/backend/renderer/utils.rs @@ -15,7 +15,7 @@ pub(crate) struct SurfaceState { pub(crate) buffer_scale: i32, pub(crate) buffer: Option, pub(crate) texture: Option>, - pub(crate) damage_seen: HashSet<(usize, *const ())>, + pub(crate) damage_seen: HashSet, } impl SurfaceState { @@ -69,7 +69,6 @@ pub fn on_commit_buffer_handler(surface: &WlSurface) { } } -/// TODO pub fn draw_surface_tree( renderer: &mut R, frame: &mut F, diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 8a99374..4f72e0d 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -84,11 +84,14 @@ impl LayerMap { self.zone } - pub fn layer_geometry(&self, layer: &LayerSurface) -> Rectangle { + pub fn layer_geometry(&self, layer: &LayerSurface) -> Option> { + if !self.layers.contains(layer) { + return None; + } let mut bbox = layer.bbox_with_popups(); let state = layer_state(layer); bbox.loc += state.location; - bbox + Some(bbox) } pub fn layer_under>>( @@ -98,7 +101,7 @@ impl LayerMap { ) -> Option<&LayerSurface> { let point = point.into(); self.layers_on(layer).rev().find(|l| { - let bbox = self.layer_geometry(l); + let bbox = self.layer_geometry(l).unwrap(); bbox.to_f64().contains(point) }) } diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index a719310..3fe3cfc 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -4,7 +4,10 @@ use crate::{ utils::{Logical, Point, Rectangle}, wayland::output::Output, }; -use std::any::{Any, TypeId}; +use std::{ + any::{Any, TypeId}, + hash::{Hash, Hasher}, +}; use wayland_server::protocol::wl_surface::WlSurface; pub trait RenderElement @@ -21,13 +24,15 @@ where std::any::Any::type_id(self) } fn geometry(&self) -> Rectangle; - fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec>; + fn accumulated_damage( + &self, + for_values: Option>, + ) -> Vec>; fn draw( &self, renderer: &mut R, frame: &mut F, scale: f64, - location: Point, damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error>; @@ -77,7 +82,7 @@ where (&**self as &dyn RenderElement).geometry() } fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { - (&**self as &dyn RenderElement).accumulated_damage(for_values) + (&**self as &dyn RenderElement).accumulated_damage(for_values.map(SpaceOutputTuple::from)) } fn draw( &self, @@ -85,11 +90,11 @@ where renderer: &mut R, frame: &mut F, scale: f64, - location: Point, + _location: Point, damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> { - (&**self as &dyn RenderElement).draw(renderer, frame, scale, location, damage, log) + (&**self as &dyn RenderElement).draw(renderer, frame, scale, damage, log) } } @@ -116,8 +121,11 @@ where bbox } - fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { - damage_from_surface_tree(&self.surface, (0, 0), for_values) + fn accumulated_damage( + &self, + for_values: Option>, + ) -> Vec> { + damage_from_surface_tree(&self.surface, (0, 0), for_values.map(|x| (x.0, x.1))) } fn draw( @@ -125,7 +133,6 @@ where renderer: &mut R, frame: &mut F, scale: f64, - location: Point, damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> { @@ -134,9 +141,40 @@ where frame, &self.surface, scale, - location, + self.position, damage, log, ) } } + +/// Newtype for (&Space, &Output) to provide a `Hash` implementation for damage tracking +#[derive(Debug, PartialEq)] +pub struct SpaceOutputTuple<'a, 'b>(pub &'a Space, pub &'b Output); + +impl<'a, 'b> Hash for SpaceOutputTuple<'a, 'b> { + fn hash(&self, state: &mut H) { + self.0.id.hash(state); + (std::sync::Arc::as_ptr(&self.1.inner) as *const () as usize).hash(state); + } +} + +impl<'a, 'b> SpaceOutputTuple<'a, 'b> { + /// Returns an owned version that produces and equivalent hash + pub fn owned_hash(&self) -> SpaceOutputHash { + SpaceOutputHash( + self.0.id, + std::sync::Arc::as_ptr(&self.1.inner) as *const () as usize, + ) + } +} + +impl<'a, 'b> From<(&'a Space, &'b Output)> for SpaceOutputTuple<'a, 'b> { + fn from((space, output): (&'a Space, &'b Output)) -> SpaceOutputTuple<'a, 'b> { + SpaceOutputTuple(space, output) + } +} + +/// Type to use as an owned hashable value equal to [`SpaceOutputTuple`] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct SpaceOutputHash(usize, usize); diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index d92a6de..0c65e30 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -30,12 +30,7 @@ use self::window::*; crate::utils::ids::id_gen!(next_space_id, SPACE_ID, SPACE_IDS); -#[derive(Debug, thiserror::Error)] -pub enum SpaceError { - #[error("Window is not mapped to this space")] - UnknownWindow, -} - +/// Represents two dimensional plane to map windows and outputs upon. #[derive(Debug)] pub struct Space { pub(super) id: usize, @@ -48,6 +43,12 @@ pub struct Space { pub type DynamicRenderElements = Box::Frame, ::Error, ::TextureId>>; +impl PartialEq for Space { + fn eq(&self, other: &Space) -> bool { + self.id == other.id + } +} + impl Drop for Space { fn drop(&mut self) { SPACE_IDS.lock().unwrap().remove(&self.id); @@ -390,6 +391,10 @@ impl Space { R::Error: 'static, R::Frame: 'static, { + if !self.outputs.contains(output) { + return Err(RenderError::UnmappedOutput); + } + type SpaceElem = dyn SpaceElement::Frame, ::Error, ::TextureId>; @@ -474,14 +479,7 @@ impl Space { damage.dedup(); damage.retain(|rect| rect.overlaps(output_geo)); damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0); - for rect in damage.clone().iter() { - // if this rect was already removed, because it was smaller as another one, - // there is no reason to evaluate this. - if damage.contains(rect) { - // remove every rectangle that is contained in this rectangle - damage.retain(|other| !rect.contains_rect(*other)); - } - } + // merge overlapping rectangles damage = damage.into_iter().fold(Vec::new(), |mut new_damage, rect| { if let Some(existing) = new_damage.iter_mut().find(|other| rect.overlaps(**other)) { *existing = existing.merge(rect); @@ -614,4 +612,6 @@ pub enum RenderError { Rendering(R::Error), #[error("Output has no active mode")] OutputNoMode, + #[error("Output was not mapped to this space")] + UnmappedOutput, } diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs index 79b27f6..0031c76 100644 --- a/src/desktop/utils.rs +++ b/src/desktop/utils.rs @@ -12,7 +12,7 @@ use crate::{ }; use wayland_server::protocol::wl_surface; -use std::{cell::RefCell, sync::Arc}; +use std::cell::RefCell; impl SurfaceState { /// Returns the size of the surface. @@ -96,8 +96,10 @@ pub fn damage_from_surface_tree

( where P: Into>, { + use super::space::SpaceOutputTuple; + let mut damage = Vec::new(); - let key = key.map(|(space, output)| (space.id, Arc::as_ptr(&output.inner) as *const ())); + let key = key.map(|x| SpaceOutputTuple::from(x).owned_hash()); with_surface_tree_upward( surface, location.into(), From 8059bdc5db62d41641442ef289c218d76cc1fb27 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 5 Jan 2022 20:41:46 +0100 Subject: [PATCH 40/52] desktop: docs --- src/backend/renderer/mod.rs | 8 ++-- src/backend/renderer/utils.rs | 20 ++++++++ src/backend/winit/mod.rs | 14 ++++-- src/backend/x11/surface.rs | 2 +- src/desktop/layer.rs | 62 ++++++++++++++++++++++-- src/desktop/mod.rs | 53 ++++++++++++++++++++- src/desktop/popup.rs | 12 +++++ src/desktop/space/element.rs | 28 +++++++++++ src/desktop/space/mod.rs | 89 +++++++++++++++++++++++++++++++++-- src/desktop/utils.rs | 16 +++++++ src/desktop/window.rs | 36 ++++++++++++-- 11 files changed, 317 insertions(+), 23 deletions(-) diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index 95cdb2d..c84c233 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -257,7 +257,7 @@ pub trait Renderer { pub trait ImportShm: Renderer { /// Import a given shm-based buffer into the renderer (see [`buffer_type`]). /// - /// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`]) + /// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`]) /// or implementation-specific functions. /// /// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it. @@ -324,7 +324,7 @@ pub trait ImportEgl: Renderer { /// Import a given wl_drm-based buffer into the renderer (see [`buffer_type`]). /// - /// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`]) + /// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`]) /// or implementation-specific functions. /// /// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it. @@ -372,7 +372,7 @@ pub trait ImportDma: Renderer { /// Import a given raw dmabuf into the renderer. /// - /// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`]) + /// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`]) /// or implementation-specific functions. /// /// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it. @@ -395,7 +395,7 @@ pub trait ImportDma: Renderer { pub trait ImportAll: Renderer { /// Import a given buffer into the renderer. /// - /// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`]) + /// Returns a texture_id, which can be used with [`Frame::render_texture_from_to`] (or [`Frame::render_texture_at`]) /// or implementation-specific functions. /// /// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it. diff --git a/src/backend/renderer/utils.rs b/src/backend/renderer/utils.rs index 31eb4bb..97efd61 100644 --- a/src/backend/renderer/utils.rs +++ b/src/backend/renderer/utils.rs @@ -1,3 +1,5 @@ +//! Utility module for helpers around drawing [`WlSurface`]s with [`Renderer`]s. + use crate::{ backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, utils::{Logical, Physical, Point, Rectangle, Size}, @@ -47,6 +49,15 @@ impl SurfaceState { } } +/// Handler to let smithay take over buffer management. +/// +/// Needs to be called first on the commit-callback of +/// [`crate::wayland::compositor::compositor_init`]. +/// +/// Consumes the buffer of [`SurfaceAttributes`], the buffer will +/// not be accessible anymore, but [`draw_surface_tree`] and other +/// `draw_*` helpers of the [desktop module](`crate::desktop`) will +/// become usable for surfaces handled this way. pub fn on_commit_buffer_handler(surface: &WlSurface) { if !is_sync_subsurface(surface) { with_surface_tree_upward( @@ -69,6 +80,15 @@ pub fn on_commit_buffer_handler(surface: &WlSurface) { } } +/// Draws a surface and its subsurfaces using a given [`Renderer`] and [`Frame`]. +/// +/// - `scale` needs to be equivalent to the fractional scale the rendered result should have. +/// - `location` is the position the surface should be drawn at. +/// - `damage` is the set of regions of the surface that should be drawn. +/// +/// Note: This element will render nothing, if you are not using +/// [`crate::backend::renderer::utils::on_commit_buffer_handler`] +/// to let smithay handle buffer management. pub fn draw_surface_tree( renderer: &mut R, frame: &mut F, diff --git a/src/backend/winit/mod.rs b/src/backend/winit/mod.rs index b6f2f58..d78560a 100644 --- a/src/backend/winit/mod.rs +++ b/src/backend/winit/mod.rs @@ -10,7 +10,8 @@ //! you want on the initialization of the backend. These functions will provide you //! with two objects: //! -//! - a [`WinitGraphicsBackend`], which can give you an implementation of a [`Renderer`] +//! - a [`WinitGraphicsBackend`], which can give you an implementation of a +//! [`Renderer`](crate::backend::renderer::Renderer) //! (or even [`Gles2Renderer`]) through its `renderer` method in addition to further //! functionality to access and manage the created winit-window. //! - a [`WinitEventLoop`], which dispatches some [`WinitEvent`] from the host graphics server. @@ -81,7 +82,7 @@ impl WindowSize { } } -/// Window with an active EGL Context created by `winit`. Implements the [`Renderer`] trait +/// Window with an active EGL Context created by `winit`. #[derive(Debug)] pub struct WinitGraphicsBackend { renderer: Gles2Renderer, @@ -112,7 +113,8 @@ pub struct WinitEventLoop { is_x11: bool, } -/// Create a new [`WinitGraphicsBackend`], which implements the [`Renderer`] trait and a corresponding +/// Create a new [`WinitGraphicsBackend`], which implements the +/// [`Renderer`](crate::backend::renderer::Renderer) trait and a corresponding /// [`WinitEventLoop`]. pub fn init(logger: L) -> Result<(WinitGraphicsBackend, WinitEventLoop), Error> where @@ -127,7 +129,8 @@ where ) } -/// Create a new [`WinitGraphicsBackend`], which implements the [`Renderer`] trait, from a given [`WindowBuilder`] +/// Create a new [`WinitGraphicsBackend`], which implements the +/// [`Renderer`](crate::backend::renderer::Renderer) trait, from a given [`WindowBuilder`] /// struct and a corresponding [`WinitEventLoop`]. pub fn init_from_builder( builder: WindowBuilder, @@ -148,7 +151,8 @@ where ) } -/// Create a new [`WinitGraphicsBackend`], which implements the [`Renderer`] trait, from a given [`WindowBuilder`] +/// Create a new [`WinitGraphicsBackend`], which implements the +/// [`Renderer`](crate::backend::renderer::Renderer) trait, from a given [`WindowBuilder`] /// struct, as well as given [`GlAttributes`] for further customization of the rendering pipeline and a /// corresponding [`WinitEventLoop`]. pub fn init_from_builder_with_gl_attr( diff --git a/src/backend/x11/surface.rs b/src/backend/x11/surface.rs index 1ad2e1f..5a3a2a8 100644 --- a/src/backend/x11/surface.rs +++ b/src/backend/x11/surface.rs @@ -72,7 +72,7 @@ impl X11Surface { /// /// You may bind this buffer to a renderer to render. /// This function will return the same buffer until [`submit`](Self::submit) is called - /// or [`reset_buffers`](Self::reset_buffer) is used to reset the buffers. + /// or [`reset_buffers`](Self::reset_buffers) is used to reset the buffers. pub fn buffer(&mut self) -> Result<(Dmabuf, u8), AllocateBuffersError> { if let Some(new_size) = self.resize.try_iter().last() { self.resize(new_size); diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 4f72e0d..17d05f7 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -23,6 +23,7 @@ use std::{ crate::utils::ids::id_gen!(next_layer_id, LAYER_ID, LAYER_IDS); +/// Map of [`LayerSurface`]s on an [`Output`] #[derive(Debug)] pub struct LayerMap { layers: IndexSet, @@ -30,6 +31,15 @@ pub struct LayerMap { zone: Rectangle, } +/// Retrieve a [`LayerMap`] for a given [`Output`]. +/// +/// If none existed before a new empty [`LayerMap`] is attached +/// to the output and returned on subsequent calls. +/// +/// Note: This function internally uses a [`RefCell`] per +/// [`Output`] as exposed by its return type. Therefor +/// trying to hold on to multiple references of a [`LayerMap`] +/// of the same output using this function *will* result in a panic. pub fn layer_map_for_output(o: &Output) -> RefMut<'_, LayerMap> { let userdata = o.user_data(); let weak_output = Arc::downgrade(&o.inner); @@ -55,6 +65,7 @@ pub enum LayerError { } impl LayerMap { + /// Map a [`LayerSurface`] to this [`LayerMap`]. pub fn map_layer(&mut self, layer: &LayerSurface) -> Result<(), LayerError> { if !self.layers.contains(layer) { if layer @@ -73,6 +84,7 @@ impl LayerMap { Ok(()) } + /// Remove a [`LayerSurface`] from this [`LayerMap`]. pub fn unmap_layer(&mut self, layer: &LayerSurface) { if self.layers.shift_remove(layer) { let _ = layer.user_data().get::().take(); @@ -80,10 +92,15 @@ impl LayerMap { } } + /// Return the area of this output, that is not exclusive to any [`LayerSurface`]s. pub fn non_exclusive_zone(&self) -> Rectangle { self.zone } + /// Returns the geometry of a given mapped layer. + /// + /// If the layer was not previously mapped onto this layer map, + /// this function return `None`. pub fn layer_geometry(&self, layer: &LayerSurface) -> Option> { if !self.layers.contains(layer) { return None; @@ -94,6 +111,7 @@ impl LayerMap { Some(bbox) } + /// Returns a `LayerSurface` under a given point and on a given layer, if any. pub fn layer_under>>( &self, layer: WlrLayer, @@ -106,16 +124,19 @@ impl LayerMap { }) } + /// Iterator over all [`LayerSurface`]s currently mapped. pub fn layers(&self) -> impl DoubleEndedIterator { self.layers.iter() } + /// Iterator over all [`LayerSurface`]s currently mapped on a given layer. pub fn layers_on(&self, layer: WlrLayer) -> impl DoubleEndedIterator { self.layers .iter() .filter(move |l| l.layer().map(|l| l == layer).unwrap_or(false)) } + /// Returns the [`LayerSurface`] matching a given [`WlSurface`], if any. pub fn layer_for_surface(&self, surface: &WlSurface) -> Option<&LayerSurface> { if !surface.as_ref().is_alive() { return None; @@ -126,6 +147,9 @@ impl LayerMap { .find(|w| w.get_surface().map(|x| x == surface).unwrap_or(false)) } + /// Force re-arranging the layers, e.g. when the output size changes. + /// + /// Note: Mapping or unmapping a layer will automatically cause a re-arrangement. pub fn arrange(&mut self) { if let Some(output) = self.output() { let output_rect = Rectangle::from_loc_and_size( @@ -238,6 +262,10 @@ impl LayerMap { self.output.upgrade().map(|inner| Output { inner }) } + /// Cleanup some internally used resources. + /// + /// This function needs to be called periodically (though not necessarily frequently) + /// to be able cleanup internally used resources. pub fn cleanup(&mut self) { self.layers.retain(|layer| layer.alive()) } @@ -260,6 +288,7 @@ pub fn layer_state(layer: &LayerSurface) -> RefMut<'_, LayerState> { }) } +/// A [`LayerSurface`] represents a single layer surface as given by the wlr-layer-shell protocol. #[derive(Debug, Clone)] pub struct LayerSurface(pub(crate) Rc); @@ -292,6 +321,7 @@ impl Drop for LayerSurfaceInner { } impl LayerSurface { + /// Create a new [`LayerSurface`] from a given [`WlrLayerSurface`] and its namespace. pub fn new(surface: WlrLayerSurface, namespace: String) -> LayerSurface { LayerSurface(Rc::new(LayerSurfaceInner { id: next_layer_id(), @@ -301,18 +331,22 @@ impl LayerSurface { })) } + /// Checks if the surface is still alive pub fn alive(&self) -> bool { self.0.surface.alive() } + /// Returns the underlying [`WlrLayerSurface`] pub fn layer_surface(&self) -> &WlrLayerSurface { &self.0.surface } + /// Returns the underlying [`WlSurface`] pub fn get_surface(&self) -> Option<&WlSurface> { self.0.surface.get_surface() } + /// Returns the cached protocol state pub fn cached_state(&self) -> Option { self.0.surface.get_surface().map(|surface| { with_states(surface, |states| { @@ -322,6 +356,7 @@ impl LayerSurface { }) } + /// Returns true, if the surface has indicated, that it is able to process keyboard events. pub fn can_receive_keyboard_focus(&self) -> bool { self.0 .surface @@ -342,6 +377,7 @@ impl LayerSurface { .unwrap_or(false) } + /// Returns the layer this surface resides on, if any yet. pub fn layer(&self) -> Option { self.0.surface.get_surface().map(|surface| { with_states(surface, |states| { @@ -351,12 +387,12 @@ impl LayerSurface { }) } + /// Returns the namespace of this surface pub fn namespace(&self) -> &str { &self.0.namespace } - /// A bounding box over this window and its children. - // TODO: Cache and document when to trigger updates. If possible let space do it + /// Returns the bounding box over this layer surface and its subsurfaces. pub fn bbox(&self) -> Rectangle { if let Some(surface) = self.0.surface.get_surface() { bbox_from_surface_tree(surface, (0, 0)) @@ -365,6 +401,10 @@ impl LayerSurface { } } + /// Returns the bounding box over this layer, it subsurfaces as well as any popups. + /// + /// Note: You need to use a [`PopupManager`] to track popups, otherwise the bounding box + /// will not include the popups. pub fn bbox_with_popups(&self) -> Rectangle { let mut bounding_box = self.bbox(); if let Some(surface) = self.0.surface.get_surface() { @@ -383,6 +423,8 @@ impl LayerSurface { /// Finds the topmost surface under this point if any and returns it together with the location of this /// surface. + /// + /// - `point` needs to be relative to (0,0) of the layer surface. pub fn surface_under>>( &self, point: P, @@ -408,7 +450,11 @@ impl LayerSurface { } } - /// Damage of all the surfaces of this layer + /// Returns the damage of all the surfaces of this layer. + /// + /// If `for_values` is `Some(_)` it will only return the damage on the + /// first call for a given [`Space`] and [`Output`], if the buffer hasn't changed. + /// Subsequent calls will return an empty vector until the buffer is updated again. pub(super) fn accumulated_damage( &self, for_values: Option<(&Space, &Output)>, @@ -443,11 +489,21 @@ impl LayerSurface { } } + /// Returns a [`UserDataMap`] to allow associating arbitrary data with this surface. pub fn user_data(&self) -> &UserDataMap { &self.0.userdata } } +/// Renders a given [`LayerSurface`] using a provided renderer and frame. +/// +/// - `scale` needs to be equivalent to the fractional scale the rendered result should have. +/// - `location` is the position the layer surface should be drawn at. +/// - `damage` is the set of regions of the layer surface that should be drawn. +/// +/// Note: This function will render nothing, if you are not using +/// [`crate::backend::renderer::utils::on_commit_buffer_handler`] +/// to let smithay handle buffer management. pub fn draw_layer( renderer: &mut R, frame: &mut F, diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index 66e8d65..8e2346d 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -1,5 +1,54 @@ -// TODO: Remove - but for now, this makes sure these files are not completely highlighted with warnings -#![allow(missing_docs)] +//! Desktop management helpers +//! +//! This module contains helpers to organize and interact with desktop-style shells. +//! +//! It is therefor a lot more opinionate then for example the [xdg-shell handler](crate::wayland::shell::xdg::xdg_shell_init) +//! and tightly integrates with some protocols (e.g. xdg-shell). +//! +//! The usage of this module is therefor entirely optional and depending on your use-case you might also only want +//! to use a limited set of the helpers provided. +//! +//! ## Helpers +//! +//! ### [`Window`] +//! +//! A window represents what is by the user typically understood as a single application window. +//! +//! Currently it abstracts over xdg-shell toplevels and Xwayland surfaces (TODO). +//! It provides a bunch of methods to calculate and retrieve its size, manage itself, attach additional user_data +//! as well as a [drawing function](`draw_window`) to ease rendering it's related surfaces. +//! +//! Note that a [`Window`] on it's own has no position. For that it needs to be placed inside a [`Space`]. +//! +//! ### [`Space`] +//! +//! A space represents a two-dimensional plane of undefined dimensions. +//! [`Window`]s and [`Output`](crate::wayland::output::Output)s can be mapped onto it. +//! +//! Windows get a position and stacking order through mapping. Outputs become views of a part of the [`Space`] +//! and can be rendered via [`Space::render_output`]. Rendering results of spaces are automatically damage-tracked. +//! +//! ### Layer +//! +//! A [`LayerSurface`] represents a surface as provided by e.g. the layer-shell protocol. +//! It provides similar helper methods as a [`Window`] does to toplevel surfaces. +//! +//! Each [`Output`](crate::wayland::output::Output) can be associated a [`LayerMap`] by calling [`layer_map_for_output`], +//! which [`LayerSurface`]s can be mapped upon. Associated layer maps are automatically rendered by [`Space::render_output`], +//! but a [draw function](`draw_layer`) is also provided for manual layer-surface management. +//! +//! ### Popups +//! +//! Provides a [`PopupManager`], which can be used to automatically keep track of popups and their +//! relations to one-another. Popups are then automatically rendered with their matching toplevel surfaces, +//! when either [`draw_window`], [`draw_layer`] or [`Space::render_output`] is called. +//! +//! ## Remarks +//! +//! Note that the desktop abstractions are concerned with easing rendering different clients and therefor need to be able +//! to manage client buffers to do so. If you plan to use the provided drawing functions, you need to use +//! [`crate::backend::renderer::utils::on_commit_buffer_handler`]. + pub(crate) mod layer; mod popup; pub mod space; diff --git a/src/desktop/popup.rs b/src/desktop/popup.rs index 58aa164..d99f0eb 100644 --- a/src/desktop/popup.rs +++ b/src/desktop/popup.rs @@ -8,6 +8,7 @@ use crate::{ use std::sync::{Arc, Mutex}; use wayland_server::protocol::wl_surface::WlSurface; +/// Helper to track popups. #[derive(Debug)] pub struct PopupManager { unmapped_popups: Vec, @@ -16,6 +17,7 @@ pub struct PopupManager { } impl PopupManager { + /// Create a new [`PopupManager`]. pub fn new>>(logger: L) -> Self { PopupManager { unmapped_popups: Vec::new(), @@ -24,6 +26,7 @@ impl PopupManager { } } + /// Start tracking a new popup. pub fn track_popup(&mut self, kind: PopupKind) -> Result<(), DeadResource> { if kind.parent().is_some() { self.add_popup(kind) @@ -34,6 +37,7 @@ impl PopupManager { } } + /// Needs to be called for [`PopupManager`] to correctly update its internal state. pub fn commit(&mut self, surface: &WlSurface) { if get_role(surface) == Some(XDG_POPUP_ROLE) { if let Some(i) = self @@ -84,6 +88,7 @@ impl PopupManager { }) } + /// Finds the popup belonging to a given [`WlSurface`], if any. pub fn find_popup(&self, surface: &WlSurface) -> Option { self.unmapped_popups .iter() @@ -99,6 +104,7 @@ impl PopupManager { }) } + /// Returns the popups and their relative positions for a given toplevel surface, if any. pub fn popups_for_surface( surface: &WlSurface, ) -> Result)>, DeadResource> { @@ -112,6 +118,8 @@ impl PopupManager { }) } + /// Needs to be called periodically (but not necessarily frequently) + /// to cleanup internal resources. pub fn cleanup(&mut self) { // retain_mut is sadly still unstable self.popup_trees.iter_mut().for_each(|tree| tree.cleanup()); @@ -211,8 +219,10 @@ impl PopupNode { } } +/// Represents a popup surface #[derive(Debug, Clone)] pub enum PopupKind { + /// xdg-shell [`PopupSurface`] Xdg(PopupSurface), } @@ -223,6 +233,7 @@ impl PopupKind { } } + /// Retrieves the underlying [`WlSurface`] pub fn get_surface(&self) -> Option<&WlSurface> { match *self { PopupKind::Xdg(ref t) => t.get_surface(), @@ -235,6 +246,7 @@ impl PopupKind { } } + /// Returns the surface geometry as set by the client using `xdg_surface::set_window_geometry` pub fn geometry(&self) -> Rectangle { let wl_surface = match self.get_surface() { Some(s) => s, diff --git a/src/desktop/space/element.rs b/src/desktop/space/element.rs index 3fe3cfc..ac013c9 100644 --- a/src/desktop/space/element.rs +++ b/src/desktop/space/element.rs @@ -10,6 +10,7 @@ use std::{ }; use wayland_server::protocol::wl_surface::WlSurface; +/// Trait for custom elements to be rendered during [`Space::render_output`]. pub trait RenderElement where R: Renderer + ImportAll, @@ -18,16 +19,33 @@ where T: Texture + 'static, Self: Any + 'static, { + /// Returns an id unique to this element for the type of Self. fn id(&self) -> usize; #[doc(hidden)] fn type_of(&self) -> TypeId { std::any::Any::type_id(self) } + /// Returns the bounding box of this element including its position in the space. fn geometry(&self) -> Rectangle; + /// Returns the damage of the element since it's last update. + /// + /// If you receive `Some(_)` for `for_values` you may cache that you + /// send the damage for this `Space` and `Output` combination once + /// and return an empty vector for subsequent calls until the contents + /// of this element actually change again for optimization reasons. + /// + /// Returning `vec![Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))]` is always + /// correct, but very inefficient. fn accumulated_damage( &self, for_values: Option>, ) -> Vec>; + /// Draws the element using the provided `Frame` and `Renderer`. + /// + /// - `scale` provides the current fractional scale value to render as + /// - `damage` provides the regions you need to re-draw and *may* not + /// be equivalent to the damage returned by `accumulated_damage`. + /// Redrawing other parts of the element is not valid and may cause rendering artifacts. fn draw( &self, renderer: &mut R, @@ -98,9 +116,19 @@ where } } +/// Generic helper for drawing [`WlSurface`]s and their subsurfaces +/// as custom elements via [`RenderElement`]. +/// +/// For example useful for cursor or drag-and-drop surfaces. +/// +/// Note: This element will render nothing, if you are not using +/// [`crate::backend::renderer::utils::on_commit_buffer_handler`] +/// to let smithay handle buffer management. #[derive(Debug)] pub struct SurfaceTree { + /// Surface to be drawn pub surface: WlSurface, + /// Position to draw add pub position: Point, } diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 0c65e30..a149e90 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -1,3 +1,6 @@ +//! This module contains the [`Space`] helper class as well has related +//! rendering helpers to add custom elements or different clients to a space. + use crate::{ backend::renderer::{utils::SurfaceState, Frame, ImportAll, Renderer, Transform}, desktop::{ @@ -40,6 +43,7 @@ pub struct Space { logger: ::slog::Logger, } +/// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups. pub type DynamicRenderElements = Box::Frame, ::Error, ::TextureId>>; @@ -56,6 +60,7 @@ impl Drop for Space { } impl Space { + /// Create a new [`Space`] pub fn new(log: L) -> Space where L: Into>, @@ -68,14 +73,26 @@ impl Space { } } - /// Map window and moves it to top of the stack + /// Map a [`Window`] and move it to top of the stack /// /// This can safely be called on an already mapped window + /// to update its location inside the space. + /// + /// If activate is true it will set the new windows state + /// to be activate and removes that state from every + /// other mapped window. pub fn map_window>>(&mut self, window: &Window, location: P, activate: bool) { self.insert_window(window, activate); window_state(self.id, window).location = location.into(); } + /// Moves an already mapped [`Window`] to top of the stack + /// + /// This function does nothing for unmapped windows. + /// + /// If activate is true it will set the new windows state + /// to be activate and removes that state from every + /// other mapped window. pub fn raise_window(&mut self, window: &Window, activate: bool) { if self.windows.shift_remove(window) { self.insert_window(window, activate); @@ -95,7 +112,9 @@ impl Space { } } - /// Unmap a window from this space by its id + /// Unmap a [`Window`] from this space by. + /// + /// This function does nothing for already unmapped windows pub fn unmap_window(&mut self, window: &Window) { if let Some(map) = window.user_data().get::() { map.borrow_mut().remove(&self.id); @@ -126,6 +145,7 @@ impl Space { }) } + /// Returns the window matching a given surface, if any pub fn window_for_surface(&self, surface: &WlSurface) -> Option<&Window> { if !surface.as_ref().is_alive() { return None; @@ -136,6 +156,7 @@ impl Space { .find(|w| w.toplevel().get_surface().map(|x| x == surface).unwrap_or(false)) } + /// Returns the layer matching a given surface, if any pub fn layer_for_surface(&self, surface: &WlSurface) -> Option { if !surface.as_ref().is_alive() { return None; @@ -146,6 +167,7 @@ impl Space { }) } + /// Returns the geometry of a [`Window`] including its relative position inside the Space. pub fn window_geometry(&self, w: &Window) -> Option> { if !self.windows.contains(w) { return None; @@ -154,6 +176,7 @@ impl Space { Some(window_geo(w, &self.id)) } + /// Returns the bounding box of a [`Window`] including its relative position inside the Space. pub fn window_bbox(&self, w: &Window) -> Option> { if !self.windows.contains(w) { return None; @@ -162,6 +185,14 @@ impl Space { Some(window_rect(w, &self.id)) } + /// Maps an [`Output`] inside the space. + /// + /// Can be safely called on an already mapped + /// [`Output`] to update its scale or location. + /// + /// The scale is the what is rendered for the given output + /// and may be fractional. It is independent from the integer scale + /// reported to clients by the output. pub fn map_output>>(&mut self, output: &Output, scale: f64, location: P) { let mut state = output_state(self.id, output); *state = OutputState { @@ -174,17 +205,28 @@ impl Space { } } + /// Iterate over all mapped [`Output`]s of this space. pub fn outputs(&self) -> impl Iterator { self.outputs.iter() } + /// Unmap an [`Output`] from this space. + /// + /// Does nothing if the output was not previously mapped. pub fn unmap_output(&mut self, output: &Output) { + if !self.outputs.contains(output) { + return; + } if let Some(map) = output.user_data().get::() { map.borrow_mut().remove(&self.id); } self.outputs.retain(|o| o != output); } + /// Returns the geometry of the output including it's relative position inside the space. + /// + /// The size is matching the amount of logical pixels of the space visible on the output + /// given is current mode and render scale. pub fn output_geometry(&self, o: &Output) -> Option> { if !self.outputs.contains(o) { return None; @@ -204,6 +246,10 @@ impl Space { }) } + /// Returns the reder scale of a mapped output. + /// + /// If the output was not previously mapped to the `Space` + /// this function returns `None`. pub fn output_scale(&self, o: &Output) -> Option { if !self.outputs.contains(o) { return None; @@ -213,6 +259,7 @@ impl Space { Some(state.render_scale) } + /// Returns all [`Output`]s a [`Window`] overlaps with. pub fn outputs_for_window(&self, w: &Window) -> Vec { if !self.windows.contains(w) { return Vec::new(); @@ -242,6 +289,11 @@ impl Space { outputs } + /// Refresh some internal values and update client state + /// based on the position of windows and outputs. + /// + /// Needs to be called periodically, at best before every + /// wayland socket flush. pub fn refresh(&mut self) { self.windows.retain(|w| w.toplevel().alive()); @@ -362,8 +414,8 @@ impl Space { } } - /// Automatically calls `Window::refresh` for the window that belongs to the given surface, - /// if managed by this space. + /// Should be called on commit to let the space automatically call [`Window::refresh`] + /// for the window that belongs to the given surface, if managed by this space. pub fn commit(&self, surface: &WlSurface) { if is_sync_subsurface(surface) { return; @@ -377,6 +429,24 @@ impl Space { } } + /// Render a given [`Output`] using a given [`Renderer`]. + /// + /// [`Space`] will render all mapped [`Window`]s, mapped [`LayerSurface`](super::LayerSurface)s + /// of the given [`Output`] and their popups (if tracked by a [`PopupManager`](super::PopupManager)). + /// `clear_color` will be used to fill all unoccupied regions. + /// + /// Rendering using this function will automatically apply damage-tracking. + /// To facilitate this you need to provide age values of the buffers bound to + /// the given `renderer`. If you stop using `Space` temporarily for rendering + /// or apply additional rendering operations, you need to reset the age values + /// accordingly as `Space` will be unable to track your custom rendering operations + /// to avoid rendering artifacts. + /// + /// To add aditional elements without breaking damage-tracking implement the `RenderElement` + /// trait and use `custom_elements` to provide them to this function. `custom_elements are rendered + /// after every other element. + /// + /// Returns a list of updated regions (or `None` if that list would be empty) in case of success. pub fn render_output( &mut self, renderer: &mut R, @@ -467,7 +537,7 @@ impl Space { let new_damage = damage.clone(); // We now add old damage states, if we have an age value if age > 0 && state.old_damage.len() >= age { - // We do not need older states anymore + // We do not need even older states anymore state.old_damage.truncate(age); damage.extend(state.old_damage.iter().flatten().copied()); } else { @@ -582,6 +652,11 @@ impl Space { Ok(Some(new_damage)) } + /// Sends the frame callback to mapped [`Window`]s and [`LayerSurface`]s. + /// + /// If `all` is set this will be send to `all` mapped surfaces. + /// Otherwise only windows and layers previously drawn during the + /// previous frame will be send frame events. pub fn send_frames(&self, all: bool, time: u32) { for window in self.windows.iter().filter(|w| { all || { @@ -606,12 +681,16 @@ impl Space { } } +/// Errors thrown by [`Space::render_output`] #[derive(Debug, thiserror::Error)] pub enum RenderError { + /// The provided [`Renderer`] did return an error during an operation #[error(transparent)] Rendering(R::Error), + /// The given [`Output`] has no set mode #[error("Output has no active mode")] OutputNoMode, + /// The given [`Output`] is not mapped to this [`Space`]. #[error("Output was not mapped to this space")] UnmappedOutput, } diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs index 0031c76..380ae4e 100644 --- a/src/desktop/utils.rs +++ b/src/desktop/utils.rs @@ -1,3 +1,5 @@ +//! Helper functions to ease dealing with surface trees + use crate::{ backend::renderer::utils::SurfaceState, desktop::Space, @@ -53,6 +55,9 @@ impl SurfaceState { } } +/// Returns the bounding box of a given surface and all its subsurfaces. +/// +/// - `location` can be set to offset the returned bounding box. pub fn bbox_from_surface_tree

(surface: &wl_surface::WlSurface, location: P) -> Rectangle where P: Into>, @@ -88,6 +93,12 @@ where bounding_box } +/// Returns the damage rectangles of the current buffer for a given surface and its subsurfaces. +/// +/// - `location` can be set to offset the returned bounding box. +/// - if a `key` is set the damage is only returned on the first call with the given key values. +/// Subsequent calls will return an empty vector until the buffer is updated again and new +/// damage values may be retrieved. pub fn damage_from_surface_tree

( surface: &wl_surface::WlSurface, location: P, @@ -155,6 +166,10 @@ where damage } +/// Returns the (sub-)surface under a given position given a surface, if any. +/// +/// - `point` has to be the position to query, relative to (0, 0) of the given surface + `location`. +/// - `location` can be used to offset the returned point. pub fn under_from_surface_tree

( surface: &wl_surface::WlSurface, point: Point, @@ -197,6 +212,7 @@ where found.into_inner() } +/// Sends frame callbacks for a surface and its subsurfaces with the given `time`. pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) { with_surface_tree_downward( surface, diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 9f43e59..a45d65d 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -19,14 +19,17 @@ use wayland_server::protocol::wl_surface; crate::utils::ids::id_gen!(next_window_id, WINDOW_ID, WINDOW_IDS); +/// Abstraction around different toplevel kinds #[derive(Debug, Clone, PartialEq)] pub enum Kind { + /// xdg-shell [`ToplevelSurface`] Xdg(ToplevelSurface), + /// XWayland surface (TODO) #[cfg(feature = "xwayland")] X11(X11Surface), } -// Big TODO +/// TODO #[derive(Debug, Clone)] pub struct X11Surface { surface: wl_surface::WlSurface, @@ -39,10 +42,12 @@ impl std::cmp::PartialEq for X11Surface { } impl X11Surface { + /// Checks if the surface is still alive. pub fn alive(&self) -> bool { self.surface.as_ref().is_alive() } + /// Returns the underlying [`WlSurface`](wl_surface::WlSurface), if still any. pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { if self.alive() { Some(&self.surface) @@ -53,6 +58,7 @@ impl X11Surface { } impl Kind { + /// Checks if the surface is still alive. pub fn alive(&self) -> bool { match *self { Kind::Xdg(ref t) => t.alive(), @@ -61,6 +67,7 @@ impl Kind { } } + /// Returns the underlying [`WlSurface`](wl_surface::WlSurface), if still any. pub fn get_surface(&self) -> Option<&wl_surface::WlSurface> { match *self { Kind::Xdg(ref t) => t.get_surface(), @@ -84,6 +91,7 @@ impl Drop for WindowInner { } } +/// Represents a single application window #[derive(Debug, Clone)] pub struct Window(pub(super) Rc); @@ -102,6 +110,7 @@ impl Hash for Window { } impl Window { + /// Construct a new [`Window`] from a given compatible toplevel surface pub fn new(toplevel: Kind) -> Window { let id = next_window_id(); @@ -123,7 +132,7 @@ impl Window { .unwrap_or_else(|| self.bbox()) } - /// A bounding box over this window and its children. + /// Returns a bounding box over this window and its children. pub fn bbox(&self) -> Rectangle { if self.0.toplevel.get_surface().is_some() { self.0.bbox.get() @@ -132,6 +141,10 @@ impl Window { } } + /// Returns a bounding box over this window and children including popups. + /// + /// Note: You need to use a [`PopupManager`] to track popups, otherwise the bounding box + /// will not include the popups. pub fn bbox_with_popups(&self) -> Rectangle { let mut bounding_box = self.bbox(); if let Some(surface) = self.0.toplevel.get_surface() { @@ -204,6 +217,8 @@ impl Window { /// Finds the topmost surface under this point if any and returns it together with the location of this /// surface. + /// + /// - `point` should be relative to (0,0) of the window. pub fn surface_under>>( &self, point: P, @@ -230,7 +245,11 @@ impl Window { } } - /// Damage of all the surfaces of this window + /// Damage of all the surfaces of this window. + /// + /// If `for_values` is `Some(_)` it will only return the damage on the + /// first call for a given [`Space`] and [`Output`], if the buffer hasn't changed. + /// Subsequent calls will return an empty vector until the buffer is updated again. pub fn accumulated_damage(&self, for_values: Option<(&Space, &Output)>) -> Vec> { let mut damage = Vec::new(); if let Some(surface) = self.0.toplevel.get_surface() { @@ -255,15 +274,26 @@ impl Window { damage } + /// Returns the underlying toplevel pub fn toplevel(&self) -> &Kind { &self.0.toplevel } + /// Returns a [`UserDataMap`] to allow associating arbitrary data with this window. pub fn user_data(&self) -> &UserDataMap { &self.0.user_data } } +/// Renders a given [`Window`] using a provided renderer and frame. +/// +/// - `scale` needs to be equivalent to the fractional scale the rendered result should have. +/// - `location` is the position the window should be drawn at. +/// - `damage` is the set of regions of the window that should be drawn. +/// +/// Note: This function will render nothing, if you are not using +/// [`crate::backend::renderer::utils::on_commit_buffer_handler`] +/// to let smithay handle buffer management. pub fn draw_window( renderer: &mut R, frame: &mut F, From 58f20fb6c7a718975dec27f97f9051a92d69256e Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 5 Jan 2022 21:06:59 +0100 Subject: [PATCH 41/52] space: Make `RenderError` require Debug --- src/desktop/space/mod.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index a149e90..7153d9d 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -18,7 +18,7 @@ use crate::{ }, }; use indexmap::{IndexMap, IndexSet}; -use std::{cell::RefCell, collections::VecDeque}; +use std::{cell::RefCell, collections::VecDeque, fmt}; use wayland_server::protocol::wl_surface::WlSurface; mod element; @@ -682,7 +682,7 @@ impl Space { } /// Errors thrown by [`Space::render_output`] -#[derive(Debug, thiserror::Error)] +#[derive(thiserror::Error)] pub enum RenderError { /// The provided [`Renderer`] did return an error during an operation #[error(transparent)] @@ -694,3 +694,13 @@ pub enum RenderError { #[error("Output was not mapped to this space")] UnmappedOutput, } + +impl fmt::Debug for RenderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RenderError::Rendering(err) => fmt::Debug::fmt(err, f), + RenderError::OutputNoMode => f.write_str("Output has no active move"), + RenderError::UnmappedOutput => f.write_str("Output was not mapped to this space"), + } + } +} From fafbf300dc25e49ceb9d5c53272b30504837e146 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 5 Jan 2022 21:24:42 +0100 Subject: [PATCH 42/52] desktop: Add missing cfg directives --- src/backend/renderer/utils.rs | 7 ++++++- src/utils/mod.rs | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/backend/renderer/utils.rs b/src/backend/renderer/utils.rs index 97efd61..1b38f01 100644 --- a/src/backend/renderer/utils.rs +++ b/src/backend/renderer/utils.rs @@ -8,7 +8,9 @@ use crate::{ SurfaceAttributes, TraversalAction, }, }; -use std::{cell::RefCell, collections::HashSet}; +use std::cell::RefCell; +#[cfg(feature = "desktop")] +use std::collections::HashSet; use wayland_server::protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface}; #[derive(Default)] @@ -17,6 +19,7 @@ pub(crate) struct SurfaceState { pub(crate) buffer_scale: i32, pub(crate) buffer: Option, pub(crate) texture: Option>, + #[cfg(feature = "desktop")] pub(crate) damage_seen: HashSet, } @@ -33,6 +36,7 @@ impl SurfaceState { } } self.texture = None; + #[cfg(feature = "desktop")] self.damage_seen.clear(); } Some(BufferAssignment::Removed) => { @@ -42,6 +46,7 @@ impl SurfaceState { buffer.release(); }; self.texture = None; + #[cfg(feature = "desktop")] self.damage_seen.clear(); } None => {} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d4d5b38..de3d678 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -6,6 +6,7 @@ pub mod signaling; #[cfg(feature = "x11rb_event_source")] pub mod x11rb; +#[cfg(feature = "desktop")] pub(crate) mod ids; pub mod user_data; From 36d910056cf46e49a4e4eeb9f7add47d821fa7a0 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 5 Jan 2022 21:44:38 +0100 Subject: [PATCH 43/52] anvil: Update to new apis --- anvil/src/drawing.rs | 2 + anvil/src/render.rs | 5 +- anvil/src/udev.rs | 8 ++- anvil/src/winit.rs | 157 ++++++++++++++++++++++--------------------- 4 files changed, 94 insertions(+), 78 deletions(-) diff --git a/anvil/src/drawing.rs b/anvil/src/drawing.rs index 53e9807..cb03ac5 100644 --- a/anvil/src/drawing.rs +++ b/anvil/src/drawing.rs @@ -178,6 +178,7 @@ where buffer_scale, output_scale as f64, Transform::Normal, /* TODO */ + &[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))], 1.0, ) { result = Err(err.into()); @@ -355,6 +356,7 @@ where _ => unreachable!(), }, Rectangle::from_loc_and_size((offset_x, 0.0), (22.0 * output_scale, 35.0 * output_scale)), + &[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))], Transform::Normal, 1.0, ) diff --git a/anvil/src/render.rs b/anvil/src/render.rs index 0d26b30..8e53b7c 100644 --- a/anvil/src/render.rs +++ b/anvil/src/render.rs @@ -24,7 +24,10 @@ pub fn render_layers_and_windows( output_scale: f32, logger: &Logger, ) -> Result<(), SwapBuffersError> { - frame.clear([0.8, 0.8, 0.9, 1.0], None)?; + frame.clear( + [0.8, 0.8, 0.9, 1.0], + &[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))], + )?; for layer in [Layer::Background, Layer::Bottom] { draw_layers( diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index a41c478..9987d32 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -50,7 +50,7 @@ use smithay::{ }, utils::{ signaling::{Linkable, SignalToken, Signaler}, - Logical, Point, + Logical, Point, Rectangle, }, wayland::{ output::{Mode, PhysicalProperties}, @@ -797,6 +797,7 @@ fn render_surface( 1, output_scale as f64, Transform::Normal, + &[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))], 1.0, )?; } @@ -862,7 +863,10 @@ fn initial_render(surface: &mut RenderSurface, renderer: &mut Gles2Renderer) -> renderer .render((1, 1).into(), Transform::Normal, |_, frame| { frame - .clear([0.8, 0.8, 0.9, 1.0], None) + .clear( + [0.8, 0.8, 0.9, 1.0], + &[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))], + ) .map_err(Into::::into) }) .map_err(Into::::into) diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 3eb9efe..eb5c736 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -9,6 +9,7 @@ use smithay::{ }; use smithay::{ backend::{ + renderer::{Renderer, Transform}, winit::{self, WinitEvent}, SwapBuffersError, }, @@ -46,39 +47,39 @@ pub fn run_winit(log: Logger) { let mut event_loop = EventLoop::try_new().unwrap(); let display = Rc::new(RefCell::new(Display::new())); - let (renderer, mut winit) = match winit::init(log.clone()) { + let (backend, mut winit) = match winit::init(log.clone()) { Ok(ret) => ret, Err(err) => { slog::crit!(log, "Failed to initialize Winit backend: {}", err); return; } }; - let renderer = Rc::new(RefCell::new(renderer)); + let backend = Rc::new(RefCell::new(backend)); #[cfg(feature = "egl")] - if renderer + if backend .borrow_mut() .renderer() .bind_wl_display(&display.borrow()) .is_ok() { info!(log, "EGL hardware-acceleration enabled"); - let dmabuf_formats = renderer + let dmabuf_formats = backend .borrow_mut() .renderer() .dmabuf_formats() .cloned() .collect::>(); - let renderer = renderer.clone(); + let backend = backend.clone(); init_dmabuf_global( &mut *display.borrow_mut(), dmabuf_formats, - move |buffer, _| renderer.borrow_mut().renderer().import_dmabuf(buffer).is_ok(), + move |buffer, _| backend.borrow_mut().renderer().import_dmabuf(buffer).is_ok(), log.clone(), ); }; - let size = renderer.borrow().window_size().physical_size; + let size = backend.borrow().window_size().physical_size; /* * Initialize the globals @@ -87,7 +88,7 @@ pub fn run_winit(log: Logger) { let data = WinitData { #[cfg(feature = "debug")] fps_texture: import_bitmap( - renderer.borrow_mut().renderer(), + backend.borrow_mut().renderer(), &image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png) .decode() .unwrap() @@ -153,7 +154,7 @@ pub fn run_winit(log: Logger) { // drawing logic { - let mut renderer = renderer.borrow_mut(); + let mut backend = backend.borrow_mut(); // This is safe to do as with winit we are guaranteed to have exactly one output let (output_geometry, output_scale) = state .output_map @@ -162,82 +163,88 @@ pub fn run_winit(log: Logger) { .map(|output| (output.geometry(), output.scale())) .unwrap(); - let result = renderer - .render(|renderer, frame| { - render_layers_and_windows( - renderer, - frame, - &*state.window_map.borrow(), - output_geometry, - output_scale, - &log, - )?; - - let (x, y) = state.pointer_location.into(); - - // draw the dnd icon if any - { - let guard = state.dnd_icon.lock().unwrap(); - if let Some(ref surface) = *guard { - if surface.as_ref().is_alive() { - draw_dnd_icon( - renderer, - frame, - surface, - (x as i32, y as i32).into(), - output_scale, - &log, - )?; - } - } - } - // draw the cursor as relevant - { - let mut guard = state.cursor_status.lock().unwrap(); - // reset the cursor if the surface is no longer alive - let mut reset = false; - if let CursorImageStatus::Image(ref surface) = *guard { - reset = !surface.as_ref().is_alive(); - } - if reset { - *guard = CursorImageStatus::Default; - } - - // draw as relevant - if let CursorImageStatus::Image(ref surface) = *guard { - cursor_visible = false; - draw_cursor( + let result = backend + .bind() + .and_then(|_| { + backend + .renderer() + .render(size, Transform::Flipped180, |renderer, frame| { + render_layers_and_windows( renderer, frame, - surface, - (x as i32, y as i32).into(), + &*state.window_map.borrow(), + output_geometry, output_scale, &log, )?; - } else { - cursor_visible = true; - } - } - #[cfg(feature = "debug")] - { - let fps = state.backend_data.fps.avg().round() as u32; + let (x, y) = state.pointer_location.into(); - draw_fps( - renderer, - frame, - &state.backend_data.fps_texture, - output_scale as f64, - fps, - )?; - } + // draw the dnd icon if any + { + let guard = state.dnd_icon.lock().unwrap(); + if let Some(ref surface) = *guard { + if surface.as_ref().is_alive() { + draw_dnd_icon( + renderer, + frame, + surface, + (x as i32, y as i32).into(), + output_scale, + &log, + )?; + } + } + } + // draw the cursor as relevant + { + let mut guard = state.cursor_status.lock().unwrap(); + // reset the cursor if the surface is no longer alive + let mut reset = false; + if let CursorImageStatus::Image(ref surface) = *guard { + reset = !surface.as_ref().is_alive(); + } + if reset { + *guard = CursorImageStatus::Default; + } - Ok(()) + // draw as relevant + if let CursorImageStatus::Image(ref surface) = *guard { + cursor_visible = false; + draw_cursor( + renderer, + frame, + surface, + (x as i32, y as i32).into(), + output_scale, + &log, + )?; + } else { + cursor_visible = true; + } + } + + #[cfg(feature = "debug")] + { + let fps = state.backend_data.fps.avg().round() as u32; + + draw_fps( + renderer, + frame, + &state.backend_data.fps_texture, + output_scale as f64, + fps, + )?; + } + + Ok(()) + }) + .map_err(Into::::into) + .and_then(|x| x) }) - .map_err(Into::::into) - .and_then(|x| x); + .and_then(|_| backend.submit(None, 1.0)); - renderer.window().set_cursor_visible(cursor_visible); + backend.window().set_cursor_visible(cursor_visible); if let Err(SwapBuffersError::ContextLost(err)) = result { error!(log, "Critical Rendering Error: {}", err); From 165e88947384ae2407b14ae42c6d9789dd0f5cf3 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 5 Jan 2022 21:59:14 +0100 Subject: [PATCH 44/52] minimal-versions: Bumb thiserror --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6bcd24b..04799b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ rand = "0.8.4" slog = "2" slog-stdlog = { version = "4", optional = true } tempfile = { version = "3.0", optional = true } -thiserror = "1.0.7" +thiserror = "1.0.25" udev = { version = "0.6", optional = true } wayland-commons = { version = "0.29.0", optional = true } wayland-egl = { version = "0.29.0", optional = true } From 66eb0562dfb97b915cbc78335a3c11d83ebb1a10 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 5 Jan 2022 22:05:33 +0100 Subject: [PATCH 45/52] wlcs_anvil: Update to new api --- wlcs_anvil/src/main_loop.rs | 6 +++++- wlcs_anvil/src/renderer.rs | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/wlcs_anvil/src/main_loop.rs b/wlcs_anvil/src/main_loop.rs index c067170..cc1108e 100644 --- a/wlcs_anvil/src/main_loop.rs +++ b/wlcs_anvil/src/main_loop.rs @@ -18,6 +18,7 @@ use smithay::{ Client, Display, }, }, + utils::Rectangle, wayland::{ output::{Mode, PhysicalProperties}, seat::CursorImageStatus, @@ -103,7 +104,10 @@ pub fn run(channel: Channel) { renderer .render((800, 600).into(), Transform::Normal, |renderer, frame| { - frame.clear([0.8, 0.8, 0.9, 1.0])?; + frame.clear( + [0.8, 0.8, 0.9, 1.0], + &[Rectangle::from_loc_and_size((0, 0), (800, 600))], + )?; // draw the windows draw_windows( diff --git a/wlcs_anvil/src/renderer.rs b/wlcs_anvil/src/renderer.rs index 640cf10..0ceb21d 100644 --- a/wlcs_anvil/src/renderer.rs +++ b/wlcs_anvil/src/renderer.rs @@ -97,7 +97,7 @@ impl Frame for DummyFrame { type Error = SwapBuffersError; type TextureId = DummyTexture; - fn clear(&mut self, _color: [f32; 4]) -> Result<(), Self::Error> { + fn clear(&mut self, _color: [f32; 4], _damage: &[Rectangle]) -> Result<(), Self::Error> { Ok(()) } @@ -106,6 +106,7 @@ impl Frame for DummyFrame { _texture: &Self::TextureId, _src: Rectangle, _dst: Rectangle, + _damage: &[Rectangle], _src_transform: Transform, _alpha: f32, ) -> Result<(), Self::Error> { From 811421cd191c03573bf33a4853a68a0cbcf2ad4d Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Wed, 5 Jan 2022 22:06:20 +0100 Subject: [PATCH 46/52] ci: add new desktop feature to matrix --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c13b59e..b78a5bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,6 +130,7 @@ jobs: - backend_session_logind - backend_session_libseat - backend_x11 + - desktop - renderer_gl - wayland_frontend - xwayland From 75c84796f6baddc7caa655b3ae10d6b4411e538f Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld <4404502+Drakulix@users.noreply.github.com> Date: Thu, 6 Jan 2022 00:26:14 +0100 Subject: [PATCH 47/52] space: documentation fixes Co-authored-by: Victor Berger --- src/desktop/layer.rs | 2 +- src/desktop/mod.rs | 8 ++++---- src/desktop/popup.rs | 4 +--- src/desktop/space/mod.rs | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 17d05f7..bd9f4d2 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -307,7 +307,7 @@ impl Hash for LayerSurface { } #[derive(Debug)] -pub struct LayerSurfaceInner { +pub(crate) struct LayerSurfaceInner { pub(crate) id: usize, surface: WlrLayerSurface, namespace: String, diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index 8e2346d..354674d 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -2,7 +2,7 @@ //! //! This module contains helpers to organize and interact with desktop-style shells. //! -//! It is therefor a lot more opinionate then for example the [xdg-shell handler](crate::wayland::shell::xdg::xdg_shell_init) +//! It is therefore a lot more opinionated than for example the [xdg-shell handler](crate::wayland::shell::xdg::xdg_shell_init) //! and tightly integrates with some protocols (e.g. xdg-shell). //! //! The usage of this module is therefor entirely optional and depending on your use-case you might also only want @@ -12,7 +12,7 @@ //! //! ### [`Window`] //! -//! A window represents what is by the user typically understood as a single application window. +//! A window represents what is typically understood by the end-user as a single application window. //! //! Currently it abstracts over xdg-shell toplevels and Xwayland surfaces (TODO). //! It provides a bunch of methods to calculate and retrieve its size, manage itself, attach additional user_data @@ -45,9 +45,9 @@ //! //! ## Remarks //! -//! Note that the desktop abstractions are concerned with easing rendering different clients and therefor need to be able +//! Note that the desktop abstractions are concerned with easing rendering different clients and therefore need to be able //! to manage client buffers to do so. If you plan to use the provided drawing functions, you need to use -//! [`crate::backend::renderer::utils::on_commit_buffer_handler`]. +//! [`on_commit_buffer_handler`](crate::backend::renderer::utils::on_commit_buffer_handler). pub(crate) mod layer; mod popup; diff --git a/src/desktop/popup.rs b/src/desktop/popup.rs index d99f0eb..2dd69c5 100644 --- a/src/desktop/popup.rs +++ b/src/desktop/popup.rs @@ -43,9 +43,7 @@ impl PopupManager { if let Some(i) = self .unmapped_popups .iter() - .enumerate() - .find(|(_, p)| p.get_surface() == Some(surface)) - .map(|(i, _)| i) + .position(|p| p.get_surface() == Some(surface)) { slog::trace!(self.logger, "Popup got mapped"); let popup = self.unmapped_popups.swap_remove(i); diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 7153d9d..3889824 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -112,7 +112,7 @@ impl Space { } } - /// Unmap a [`Window`] from this space by. + /// Unmap a [`Window`] from this space. /// /// This function does nothing for already unmapped windows pub fn unmap_window(&mut self, window: &Window) { From a779e6b5c005337e7a8d4a52f1e08605fd3a790b Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Thu, 6 Jan 2022 18:24:49 +0100 Subject: [PATCH 48/52] docs: Always referr to layer surfaces as such --- src/desktop/layer.rs | 14 +++++++------- src/desktop/mod.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index bd9f4d2..1fe34f7 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -97,9 +97,9 @@ impl LayerMap { self.zone } - /// Returns the geometry of a given mapped layer. + /// Returns the geometry of a given mapped [`LayerSurface`]. /// - /// If the layer was not previously mapped onto this layer map, + /// If the surface was not previously mapped onto this layer map, /// this function return `None`. pub fn layer_geometry(&self, layer: &LayerSurface) -> Option> { if !self.layers.contains(layer) { @@ -111,7 +111,7 @@ impl LayerMap { Some(bbox) } - /// Returns a `LayerSurface` under a given point and on a given layer, if any. + /// Returns a [`LayerSurface`] under a given point and on a given layer, if any. pub fn layer_under>>( &self, layer: WlrLayer, @@ -147,9 +147,9 @@ impl LayerMap { .find(|w| w.get_surface().map(|x| x == surface).unwrap_or(false)) } - /// Force re-arranging the layers, e.g. when the output size changes. + /// Force re-arranging the layer surfaces, e.g. when the output size changes. /// - /// Note: Mapping or unmapping a layer will automatically cause a re-arrangement. + /// Note: Mapping or unmapping a layer surface will automatically cause a re-arrangement. pub fn arrange(&mut self) { if let Some(output) = self.output() { let output_rect = Rectangle::from_loc_and_size( @@ -401,7 +401,7 @@ impl LayerSurface { } } - /// Returns the bounding box over this layer, it subsurfaces as well as any popups. + /// Returns the bounding box over this layer surface, it subsurfaces as well as any popups. /// /// Note: You need to use a [`PopupManager`] to track popups, otherwise the bounding box /// will not include the popups. @@ -450,7 +450,7 @@ impl LayerSurface { } } - /// Returns the damage of all the surfaces of this layer. + /// Returns the damage of all the surfaces of this layer surface. /// /// If `for_values` is `Some(_)` it will only return the damage on the /// first call for a given [`Space`] and [`Output`], if the buffer hasn't changed. diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index 354674d..a905e2e 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -28,7 +28,7 @@ //! Windows get a position and stacking order through mapping. Outputs become views of a part of the [`Space`] //! and can be rendered via [`Space::render_output`]. Rendering results of spaces are automatically damage-tracked. //! -//! ### Layer +//! ### Layer Shell //! //! A [`LayerSurface`] represents a surface as provided by e.g. the layer-shell protocol. //! It provides similar helper methods as a [`Window`] does to toplevel surfaces. From ccec794af7115d6348e794fc30a10fb4944eaf90 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Thu, 6 Jan 2022 18:27:04 +0100 Subject: [PATCH 49/52] desktop: `draw_layer` -> `draw_layer_surface` --- src/desktop/layer.rs | 2 +- src/desktop/mod.rs | 6 +++--- src/desktop/space/layer.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/desktop/layer.rs b/src/desktop/layer.rs index 1fe34f7..1dc4b9a 100644 --- a/src/desktop/layer.rs +++ b/src/desktop/layer.rs @@ -504,7 +504,7 @@ impl LayerSurface { /// Note: This function will render nothing, if you are not using /// [`crate::backend::renderer::utils::on_commit_buffer_handler`] /// to let smithay handle buffer management. -pub fn draw_layer( +pub fn draw_layer_surface( renderer: &mut R, frame: &mut F, layer: &LayerSurface, diff --git a/src/desktop/mod.rs b/src/desktop/mod.rs index a905e2e..9bd4451 100644 --- a/src/desktop/mod.rs +++ b/src/desktop/mod.rs @@ -35,13 +35,13 @@ //! //! Each [`Output`](crate::wayland::output::Output) can be associated a [`LayerMap`] by calling [`layer_map_for_output`], //! which [`LayerSurface`]s can be mapped upon. Associated layer maps are automatically rendered by [`Space::render_output`], -//! but a [draw function](`draw_layer`) is also provided for manual layer-surface management. +//! but a [draw function](`draw_layer_surface`) is also provided for manual layer-surface management. //! //! ### Popups //! //! Provides a [`PopupManager`], which can be used to automatically keep track of popups and their //! relations to one-another. Popups are then automatically rendered with their matching toplevel surfaces, -//! when either [`draw_window`], [`draw_layer`] or [`Space::render_output`] is called. +//! when either [`draw_window`], [`draw_layer_surface`] or [`Space::render_output`] is called. //! //! ## Remarks //! @@ -55,7 +55,7 @@ pub mod space; pub mod utils; mod window; -pub use self::layer::{draw_layer, layer_map_for_output, LayerMap, LayerSurface}; +pub use self::layer::{draw_layer_surface, layer_map_for_output, LayerMap, LayerSurface}; pub use self::popup::*; pub use self::space::Space; pub use self::window::*; diff --git a/src/desktop/space/layer.rs b/src/desktop/space/layer.rs index 53f186f..89a4799 100644 --- a/src/desktop/space/layer.rs +++ b/src/desktop/space/layer.rs @@ -63,7 +63,7 @@ where damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> { - let res = draw_layer(renderer, frame, self, scale, location, damage, log); + let res = draw_layer_surface(renderer, frame, self, scale, location, damage, log); if res.is_ok() { layer_state(space_id, self).drawn = true; } From 14d10e48637914666e0b2ae9aecb0a9be09d1b12 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Thu, 6 Jan 2022 18:29:30 +0100 Subject: [PATCH 50/52] space: `output_under` may return multiple outputs --- src/desktop/space/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 3889824..0565bf1 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -136,10 +136,10 @@ impl Space { }) } - /// Get a reference to the output under a given point, if any - pub fn output_under>>(&self, point: P) -> Option<&Output> { + /// Get a reference to the outputs under a given point + pub fn output_under>>(&self, point: P) -> impl Iterator { let point = point.into(); - self.outputs.iter().rev().find(|o| { + self.outputs.iter().rev().filter(move |o| { let bbox = self.output_geometry(o); bbox.map(|bbox| bbox.to_f64().contains(point)).unwrap_or(false) }) From 75929919ba98f1144e9781e0f303a5f5ffc428d0 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Thu, 6 Jan 2022 18:35:37 +0100 Subject: [PATCH 51/52] space: Clarify state reset on `map_output` --- src/desktop/space/mod.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/desktop/space/mod.rs b/src/desktop/space/mod.rs index 0565bf1..47116b4 100644 --- a/src/desktop/space/mod.rs +++ b/src/desktop/space/mod.rs @@ -193,11 +193,17 @@ impl Space { /// The scale is the what is rendered for the given output /// and may be fractional. It is independent from the integer scale /// reported to clients by the output. + /// + /// *Note:* Remapping an output does reset it's damage memory. pub fn map_output>>(&mut self, output: &Output, scale: f64, location: P) { let mut state = output_state(self.id, output); *state = OutputState { location: location.into(), render_scale: scale, + // keep surfaces, we still need to inform them of leaving, + // if they don't overlap anymore during refresh. + surfaces: state.surfaces.drain(..).collect::>(), + // resets last_seen and old_damage, if remapped ..Default::default() }; if !self.outputs.contains(output) { @@ -289,8 +295,9 @@ impl Space { outputs } - /// Refresh some internal values and update client state - /// based on the position of windows and outputs. + /// Refresh some internal values and update client state, + /// meaning this will handle output enter and leave events + /// for mapped outputs and windows based on their position. /// /// Needs to be called periodically, at best before every /// wayland socket flush. From 9f5bf25b6b648e6ff42a67094c72a963b2a6a2c2 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Thu, 6 Jan 2022 19:24:50 +0100 Subject: [PATCH 52/52] clamp the damage rect to the destination rect this fixes issues when the damage rect is greater than the destination rect, like providing i32::Max as the damage size --- src/backend/renderer/gles2/mod.rs | 15 +++++++++++---- src/utils/geometry.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 4faa4c3..7b73dee 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -1286,11 +1286,18 @@ impl Frame for Gles2Frame { let damage = damage .iter() .map(|rect| { + let rect = rect.to_f64(); + + let rect_constrained_loc = rect + .loc + .constrain(Rectangle::from_extemities((0f64, 0f64), dest.size.to_point())); + let rect_clamped_size = rect.size.clamp((0f64, 0f64), dest.size); + [ - rect.loc.x as f32 / dest.size.w as f32, - rect.loc.y as f32 / dest.size.h as f32, - rect.size.w as f32 / dest.size.w as f32, - rect.size.h as f32 / dest.size.h as f32, + (rect_constrained_loc.x / dest.size.w) as f32, + (rect_constrained_loc.y / dest.size.h) as f32, + (rect_clamped_size.w / dest.size.w) as f32, + (rect_clamped_size.h / dest.size.h) as f32, ] }) .flatten() diff --git a/src/utils/geometry.rs b/src/utils/geometry.rs index dc62786..b4a14a3 100644 --- a/src/utils/geometry.rs +++ b/src/utils/geometry.rs @@ -296,6 +296,23 @@ impl Point { } } +impl Point { + /// Constrain this [`Point`] within a [`Rectangle`] with the same coordinates + /// + /// The [`Point`] returned is guaranteed to be not smaller than the [`Rectangle`] + /// location and not greater than the [`Rectangle`] size. + #[inline] + pub fn constrain(self, rect: impl Into>) -> Point { + let rect = rect.into(); + + Point { + x: self.x.max(rect.loc.x).min(rect.size.w), + y: self.y.max(rect.loc.y).min(rect.size.h), + _kind: std::marker::PhantomData, + } + } +} + impl Point { /// Convert the underlying numerical type to f64 for floating point manipulations #[inline] @@ -543,6 +560,20 @@ impl Size { } } +impl Size { + /// Restrict this [`Size`] to min and max [`Size`] with the same coordinates + pub fn clamp(self, min: impl Into>, max: impl Into>) -> Size { + let min = min.into(); + let max = max.into(); + + Size { + w: self.w.max(min.w).min(max.w), + h: self.h.max(min.h).min(max.h), + _kind: std::marker::PhantomData, + } + } +} + impl Size { /// Convert the underlying numerical type to f64 for floating point manipulations #[inline]