diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63c5e73..03c9eda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,7 +105,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: update - args: -p libdbus-sys -p calloop:0.6.1 + args: -p libdbus-sys - name: Check uses: actions-rs/cargo@v1 with: @@ -131,6 +131,7 @@ jobs: - backend_session_logind - backend_session_libseat - backend_x11 + - desktop - renderer_gl - wayland_frontend - xwayland diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bb06ed6..e23378b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,6 +4,7 @@ on: push: branches: - master + pull_request: jobs: doc: @@ -33,12 +34,13 @@ jobs: uses: actions-rs/cargo@v1 with: command: doc - args: --no-deps --features "test_all_features" -p smithay -p calloop:0.9.1 -p dbus -p drm -p gbm -p input -p nix:0.22.0 -p udev -p slog -p wayland-server -p wayland-commons:0.29.1 -p wayland-protocols:0.29.1 -p winit -p x11rb + args: --no-deps --features "test_all_features" -p smithay -p calloop -p dbus -p drm -p gbm -p input -p nix -p udev -p slog -p wayland-server -p wayland-commons -p wayland-protocols -p winit -p x11rb - name: Setup index run: cp ./doc_index.html ./target/doc/index.html - name: Deploy + if: ${{ github.event_name == 'push' }} uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 682fe98..110b268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ - `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 +- `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 @@ -45,6 +48,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/Cargo.toml b/Cargo.toml index e959cd1..4b3a592 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 } @@ -39,14 +40,14 @@ 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 } wayland-protocols = { version = "0.29.0", features = ["unstable_protocols", "staging_protocols", "server"], optional = true } wayland-server = { version = "0.29.0", optional = true } wayland-sys = { version = "0.29.0", optional = true } -winit = { version = "0.25.0", optional = true } +winit = { version = "0.26", optional = true } x11rb = { version = "0.9.0", optional = true } xkbcommon = "0.4.0" scan_fmt = { version = "0.2.3", default-features = false } @@ -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/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 4848c45..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])?; + 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 957a419..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}, @@ -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,62 @@ 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, + &[Rectangle::from_loc_and_size((0, 0), (i32::MAX, i32::MAX))], + 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) @@ -873,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]) + .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..2552e04 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,96 @@ 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( + let result = backend + .bind() + .and_then(|_| { + backend + .renderer() + .render( + output_geometry + .size + .to_f64() + .to_physical(output_scale as f64) + .to_i32_round(), + 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, )?; - } - } - } - // 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( - renderer, - frame, - surface, - (x as i32, y as i32).into(), - output_scale, - &log, - )?; - } else { - cursor_visible = true; - } - } + let (x, y) = state.pointer_location.into(); - #[cfg(feature = "debug")] - { - let fps = state.backend_data.fps.avg().round() as u32; + // 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_fps( - renderer, - frame, - &state.backend_data.fps_texture, - output_scale as f64, - fps, - )?; - } + // 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; + } + } - Ok(()) + #[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); 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/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/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(); + } + } } } 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() diff --git a/src/backend/egl/context.rs b/src/backend/egl/context.rs index 8e77b6b..f57cbb3 100644 --- a/src/backend/egl/context.rs +++ b/src/backend/egl/context.rs @@ -1,12 +1,21 @@ //! EGL context related structs -use std::collections::HashSet; -use std::os::raw::c_int; -use std::sync::atomic::Ordering; +use std::{ + collections::HashSet, + os::raw::c_int, + sync::{atomic::Ordering, Arc}, +}; use super::{ffi, wrap_egl_call, Error, MakeCurrentError}; -use crate::backend::allocator::Format as DrmFormat; -use crate::backend::egl::display::{EGLDisplay, PixelFormat}; -use crate::backend::egl::EGLSurface; +use crate::{ + backend::{ + allocator::Format as DrmFormat, + egl::{ + display::{EGLDisplay, PixelFormat}, + EGLSurface, + }, + }, + utils::user_data::UserDataMap, +}; use slog::{info, o, trace}; @@ -17,6 +26,7 @@ pub struct EGLContext { pub(crate) display: EGLDisplay, config_id: ffi::egl::types::EGLConfig, pixel_format: Option, + user_data: Arc, } // EGLContexts can be moved between threads safely unsafe impl Send for EGLContext {} @@ -162,6 +172,11 @@ impl EGLContext { display: display.clone(), config_id, pixel_format, + user_data: if let Some(shared) = shared { + shared.user_data.clone() + } else { + Arc::new(UserDataMap::default()) + }, }) } @@ -241,6 +256,21 @@ impl EGLContext { pub fn dmabuf_texture_formats(&self) -> &HashSet { &self.display.dmabuf_import_formats } + + /// Retrieve user_data associated with this context + /// + /// *Note:* UserData is shared between shared context, if constructed with + /// [`new_shared`](EGLContext::new_shared) or [`new_shared_with_config`](EGLContext::new_shared_with_config). + pub fn user_data(&self) -> &UserDataMap { + &*self.user_data + } + + /// Get a raw handle to the underlying context. + /// + /// The pointer will become invalid, when this struct is destroyed. + pub fn get_context_handle(&self) -> ffi::egl::types::EGLContext { + self.context + } } impl Drop for EGLContext { diff --git a/src/backend/egl/device.rs b/src/backend/egl/device.rs index 5f8b4e0..b375d09 100644 --- a/src/backend/egl/device.rs +++ b/src/backend/egl/device.rs @@ -173,7 +173,9 @@ impl EGLDevice { } /// Returns the pointer to the raw [`EGLDevice`]. - pub fn inner(&self) -> *const c_void { + /// + /// The pointer will become invalid, when this struct is destroyed. + pub fn get_device_handle(&self) -> *const c_void { self.inner } } 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), + _ => {} }; }); } 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..a4a2c24 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)) }; @@ -153,6 +172,16 @@ impl EGLSurface { pub fn resize(&self, width: i32, height: i32, dx: i32, dy: i32) -> bool { self.native.resize(width, height, dx, dy) } + + /// Get a raw handle to the underlying surface + /// + /// *Note*: The surface might get dynamically recreated during swap-buffers + /// causing the pointer to become invalid. + /// + /// The pointer will become invalid, when this struct is destroyed. + pub fn get_surface_handle(&self) -> ffi::egl::types::EGLSurface { + self.surface.load(Ordering::SeqCst) + } } impl Drop for EGLSurface { diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 3a8c49b..adad36f 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}; @@ -44,23 +41,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,45 +159,27 @@ 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 #[allow(dead_code)] destruction_callback_sender: Sender, + min_filter: TextureFilter, + max_filter: TextureFilter, logger_ptr: Option<*mut ::slog::Logger>, logger: ::slog::Logger, _not_send: *mut (), @@ -205,15 +188,25 @@ pub struct Gles2Renderer { /// Handle to the currently rendered frame during [`Gles2Renderer::render`](Renderer::render) pub struct Gles2Frame { current_projection: Matrix3, + transform: Transform, gl: ffi::Gles2, - programs: [Gles2Program; shaders::FRAGMENT_COUNT], + tex_programs: [Gles2TexProgram; shaders::FRAGMENT_COUNT], + solid_program: Gles2SolidProgram, + vbos: [ffi::types::GLuint; 2], + size: Size, + min_filter: TextureFilter, + max_filter: TextureFilter, } 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) + .field("min_filter", &self.min_filter) + .field("max_filter", &self.max_filter) .finish_non_exhaustive() } } @@ -221,14 +214,16 @@ 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("min_filter", &self.min_filter) + .field("max_filter", &self.max_filter) .field("logger", &self.logger) .finish() } @@ -382,9 +377,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 +388,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 +487,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 +511,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), + let renderer = Gles2Renderer { 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,12 +545,13 @@ impl Gles2Renderer { dmabuf_cache: std::collections::HashMap::new(), destruction_callback: rx, destruction_callback_sender: tx, + vbos, + min_filter: TextureFilter::Nearest, + max_filter: TextureFilter::Linear, logger_ptr, logger: log, _not_send: std::ptr::null_mut(), }; - renderer.downscale_filter(TextureFilter::Nearest)?; - renderer.upscale_filter(TextureFilter::Linear)?; renderer.egl.unbind()?; Ok(renderer) } @@ -607,7 +644,6 @@ impl ImportShm for Gles2Renderer { unsafe { self.gl.BindTexture(ffi::TEXTURE_2D, texture.0.texture); - self.gl .TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); self.gl @@ -966,9 +1002,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); @@ -987,12 +1025,25 @@ impl Drop for Gles2Renderer { } impl Gles2Renderer { + /// Get access to the underlying [`EGLContext`]. + /// + /// *Note*: Modifying the context state, might result in rendering issues. + /// The context state is considerd an implementation detail + /// and no guarantee is made about what can or cannot be changed. + /// To make sure a certain modification does not interfere with + /// the renderer's behaviour, check the source. + pub fn egl_context(&self) -> &EGLContext { + &self.egl + } + /// Run custom code in the GL context owned by this renderer. /// - /// *Note*: Any changes to the GL state should be restored at the end of this function. - /// Otherwise this can lead to rendering errors while using functions of this renderer. - /// Relying on any state set by the renderer may break on any smithay update as the - /// details about how this renderer works are considered an implementation detail. + /// The OpenGL state of the renderer is considered an implementation detail + /// and no guarantee is made about what can or cannot be changed, + /// as such you should reset everything you change back to its previous value + /// or check the source code of the version of Smithay you are using to ensure + /// your changes don't interfere with the renderer's behavior. + /// Doing otherwise can lead to rendering errors while using other functions of this renderer. pub fn with_context(&mut self, func: F) -> Result where F: FnOnce(&mut Self, &ffi::Gles2) -> R, @@ -1009,31 +1060,11 @@ impl Renderer for Gles2Renderer { type Frame = Gles2Frame; fn downscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> { - self.make_current()?; - unsafe { - self.gl.TexParameteri( - ffi::TEXTURE_2D, - ffi::TEXTURE_MIN_FILTER, - match filter { - TextureFilter::Nearest => ffi::NEAREST as i32, - TextureFilter::Linear => ffi::LINEAR as i32, - }, - ); - } + self.min_filter = filter; Ok(()) } fn upscale_filter(&mut self, filter: TextureFilter) -> Result<(), Self::Error> { - self.make_current()?; - unsafe { - self.gl.TexParameteri( - ffi::TEXTURE_2D, - ffi::TEXTURE_MAG_FILTER, - match filter { - TextureFilter::Nearest => ffi::NEAREST as i32, - TextureFilter::Linear => ffi::LINEAR as i32, - }, - ); - } + self.max_filter = filter; Ok(()) } @@ -1077,11 +1108,20 @@ 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(), + tex_programs: self.tex_programs.clone(), + solid_program: self.solid_program.clone(), // output transformation passed in by the user - current_projection: transform.matrix() * renderer, + current_projection: flip180 * transform.matrix() * renderer, + transform, + vbos: self.vbos, + size, + min_filter: self.min_filter, + max_filter: self.max_filter, }; let result = rendering(self, &mut frame); @@ -1121,10 +1161,85 @@ 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: &[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 { - self.gl.ClearColor(color[0], color[1], color[2], color[3]); - self.gl.Clear(ffi::COLOR_BUFFER_BIT); + 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(()) @@ -1135,6 +1250,7 @@ impl Frame for Gles2Frame { texture: &Self::TextureId, src: Rectangle, dest: Rectangle, + damage: &[Rectangle], transform: Transform, alpha: f32, ) -> Result<(), Self::Error> { @@ -1158,7 +1274,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 @@ -1170,7 +1286,32 @@ 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| { + 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_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() + .collect::>(); + + self.render_texture(texture, mat, Some(&damage), tex_verts, alpha) + } + + fn transformation(&self) -> Transform { + self.transform } } @@ -1181,6 +1322,7 @@ impl Gles2Frame { &mut self, tex: &Gles2Texture, mut matrix: Matrix3, + instances: Option<&[ffi::types::GLfloat]>, tex_coords: [Vector2; 4], alpha: f32, ) -> Result<(), Gles2Error> { @@ -1197,35 +1339,43 @@ impl Gles2Frame { unsafe { self.gl.ActiveTexture(ffi::TEXTURE0); 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.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_MIN_FILTER, + match self.min_filter { + TextureFilter::Nearest => ffi::NEAREST as i32, + TextureFilter::Linear => ffi::LINEAR as i32, + }, + ); + self.gl.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_MAG_FILTER, + match self.max_filter { + TextureFilter::Nearest => ffi::NEAREST as i32, + TextureFilter::Linear => ffi::LINEAR as i32, + }, + ); + 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, @@ -1234,20 +1384,62 @@ 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(()) } + + /// Projection matrix for this frame + pub fn projection(&self) -> &[f32; 9] { + self.current_projection.as_ref() + } } 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 a975894..b624697 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; @@ -32,6 +32,9 @@ use crate::backend::egl::{ Error as EglError, }; +#[cfg(feature = "wayland_frontend")] +pub mod utils; + #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] /// Possible transformations to two-dimensional planes pub enum Transform { @@ -94,17 +97,47 @@ 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 } } + + /// Transforms a rectangle inside an area of a given size by applying this transformation + pub fn transform_rect_in( + &self, + rect: Rectangle, + area: &Size, + ) -> Rectangle { + let size = self.transform_size(rect.size); + + let loc = match *self { + Transform::Normal => rect.loc, + Transform::_90 => (area.h - rect.loc.y - rect.size.h, rect.loc.x).into(), + Transform::_180 => ( + area.w - rect.loc.x - rect.size.w, + area.h - rect.loc.y - rect.size.h, + ) + .into(), + Transform::_270 => (rect.loc.y, area.w - rect.loc.x - rect.size.w).into(), + Transform::Flipped => (area.w - rect.loc.x - rect.size.w, rect.loc.y).into(), + Transform::Flipped90 => (rect.loc.y, rect.loc.x).into(), + Transform::Flipped180 => (rect.loc.x, area.h - rect.loc.y - rect.size.h).into(), + Transform::Flipped270 => ( + area.h - rect.loc.y - rect.size.h, + area.w - rect.loc.x - rect.size.w, + ) + .into(), + }; + + Rectangle::from_loc_and_size(loc, size) + } } #[cfg(feature = "wayland_frontend")] @@ -171,11 +204,12 @@ 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: &[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. /// (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, @@ -183,6 +217,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( @@ -196,6 +231,7 @@ pub trait Frame { .to_f64() .to_physical(output_scale), ), + damage, src_transform, alpha, ) @@ -209,9 +245,13 @@ pub trait Frame { texture: &Self::TextureId, src: Rectangle, dst: Rectangle, + damage: &[Rectangle], src_transform: Transform, alpha: f32, ) -> Result<(), Self::Error>; + + /// Output transformation that is applied to this frame + fn transformation(&self) -> Transform; } /// Abstraction of commonly used rendering operations for compositors. @@ -250,7 +290,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. @@ -317,7 +357,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. @@ -365,7 +405,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. @@ -388,7 +428,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. @@ -520,3 +560,102 @@ pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::Normal; + + assert_eq!(rect, transform.transform_rect_in(rect, &size)) + } + + #[test] + fn transform_rect_90() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::_90; + + assert_eq!( + Rectangle::from_loc_and_size((30, 10), (40, 30)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_180() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::_180; + + assert_eq!( + Rectangle::from_loc_and_size((30, 30), (30, 40)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_270() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::_270; + + assert_eq!( + Rectangle::from_loc_and_size((20, 30), (40, 30)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_f() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::Flipped; + + assert_eq!( + Rectangle::from_loc_and_size((30, 20), (30, 40)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_f90() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 80)); + let transform = Transform::Flipped90; + + assert_eq!( + Rectangle::from_loc_and_size((20, 10), (40, 30)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_f180() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::Flipped180; + + assert_eq!( + Rectangle::from_loc_and_size((10, 30), (30, 40)), + transform.transform_rect_in(rect, &size) + ) + } + + #[test] + fn transform_rect_f270() { + let rect = Rectangle::::from_loc_and_size((10, 20), (30, 40)); + let size = Size::from((70, 90)); + let transform = Transform::Flipped270; + + assert_eq!( + Rectangle::from_loc_and_size((30, 30), (40, 30)), + transform.transform_rect_in(rect, &size) + ) + } +} diff --git a/src/backend/renderer/utils.rs b/src/backend/renderer/utils.rs new file mode 100644 index 0000000..1b38f01 --- /dev/null +++ b/src/backend/renderer/utils.rs @@ -0,0 +1,223 @@ +//! 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}, + wayland::compositor::{ + is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, + SurfaceAttributes, TraversalAction, + }, +}; +use std::cell::RefCell; +#[cfg(feature = "desktop")] +use std::collections::HashSet; +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>, + #[cfg(feature = "desktop")] + pub(crate) damage_seen: HashSet, +} + +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; + #[cfg(feature = "desktop")] + self.damage_seen.clear(); + } + Some(BufferAssignment::Removed) => { + // remove the contents + self.buffer_dimensions = None; + if let Some(buffer) = self.buffer.take() { + buffer.release(); + }; + self.texture = None; + #[cfg(feature = "desktop")] + self.damage_seen.clear(); + } + None => {} + } + } +} + +/// 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( + 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, + ); + } +} + +/// 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, + surface: &WlSurface, + 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, +{ + let mut result = Ok(()); + let damage = damage + .iter() + .map(|geo| geo.to_f64().to_physical(scale).to_i32_up()) + .collect::>(); + 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 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_or_default() + .to_logical(buffer_scale) + .to_f64() + .to_physical(scale) + .to_i32_round(), + ); + let new_damage = damage + .iter() + .cloned() + .flat_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, + location.to_f64().to_physical(scale).to_i32_round(), + buffer_scale, + scale, + attributes.buffer_transform.into(), + &new_damage, + 1.0, + ) { + result = Err(err); + } + } + } + }, + |_, _, _| true, + ); + + result +} diff --git a/src/backend/winit/mod.rs b/src/backend/winit/mod.rs index c534fed..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. @@ -27,11 +28,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; @@ -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, @@ -90,6 +91,7 @@ pub struct WinitGraphicsBackend { egl: Rc, window: Rc, size: Rc>, + damage_tracking: bool, resize_notification: Rc>>>, } @@ -111,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 @@ -126,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, @@ -147,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( @@ -220,6 +225,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 +236,7 @@ where _display: display, egl, renderer, + damage_tracking, size: size.clone(), resize_notification: resize_notification.clone(), }, @@ -281,27 +291,59 @@ 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())?; - let result = self.renderer.render(size, Transform::Normal, 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 = 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) + } + _ => None, + }; + self.egl.swap_buffers(damage.as_deref_mut())?; + Ok(()) } } 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 new file mode 100644 index 0000000..1dc4b9a --- /dev/null +++ b/src/desktop/layer.rs @@ -0,0 +1,553 @@ +use crate::{ + backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture}, + desktop::{utils::*, PopupManager, Space}, + 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}, + hash::{Hash, Hasher}, + rc::Rc, + sync::{Arc, Mutex, Weak}, +}; + +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, + output: Weak<(Mutex, wayland_server::UserDataMap)>, + 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); + 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_else(|| (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 { + /// Map a [`LayerSurface`] to this [`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(()) + } + + /// 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(); + self.arrange(); + } + } + + /// 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 [`LayerSurface`]. + /// + /// 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) { + return None; + } + let mut bbox = layer.bbox_with_popups(); + let state = layer_state(layer); + bbox.loc += state.location; + Some(bbox) + } + + /// Returns a [`LayerSurface`] under a given point and on a given layer, if any. + 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).unwrap(); + bbox.to_f64().contains(point) + }) + } + + /// 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; + } + + self.layers + .iter() + .find(|w| w.get_surface().map(|x| x == surface).unwrap_or(false)) + } + + /// Force re-arranging the layer surfaces, e.g. when the output size changes. + /// + /// 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( + (0, 0), + output + .current_mode() + .map(|mode| mode.size.to_logical(output.current_scale())) + .unwrap_or_else(|| (0, 0).into()), + ); + let mut zone = output_rect; + 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 + ); + let size_changed = layer + .0 + .surface + .with_pending_state(|state| { + state.size.replace(size).map(|old| old != size).unwrap_or(true) + }) + .unwrap(); + if size_changed { + 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 }) + } + + /// 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()) + } +} + +#[derive(Debug, Default)] +pub struct LayerState { + pub location: Point, +} + +type LayerUserdata = RefCell>; +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| { + if opt.is_none() { + *opt = Some(LayerState::default()); + } + opt.as_mut().unwrap() + }) +} + +/// A [`LayerSurface`] represents a single layer surface as given by the wlr-layer-shell protocol. +#[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(crate) 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 { + /// 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(), + surface, + namespace, + userdata: UserDataMap::new(), + })) + } + + /// 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| { + *states.cached_state.current::() + }) + .unwrap() + }) + } + + /// 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 + .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) + } + + /// 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| { + states.cached_state.current::().layer + }) + .unwrap() + }) + } + + /// Returns the namespace of this surface + pub fn namespace(&self) -> &str { + &self.0.namespace + } + + /// 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)) + } else { + Rectangle::from_loc_and_size((0, 0), (0, 0)) + } + } + + /// 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. + 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. + /// + /// - `point` needs to be relative to (0,0) of the layer surface. + 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() + .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 + } + } + + /// 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. + /// Subsequent calls will return an empty vector until the buffer is updated again. + 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), 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.into_iter().flat_map(|rect| rect.intersection(bbox))); + } + } + } + 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) + } + } + + /// 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_surface( + renderer: &mut R, + frame: &mut F, + layer: &LayerSurface, + scale: f64, + location: P, + damage: &[Rectangle], + log: &slog::Logger, +) -> Result<(), R::Error> +where + R: Renderer + ImportAll, + 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) + .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 new file mode 100644 index 0000000..9bd4451 --- /dev/null +++ b/src/desktop/mod.rs @@ -0,0 +1,61 @@ +//! Desktop management helpers +//! +//! This module contains helpers to organize and interact with desktop-style shells. +//! +//! 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 +//! to use a limited set of the helpers provided. +//! +//! ## Helpers +//! +//! ### [`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 +//! 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 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. +//! +//! 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_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_surface`] or [`Space::render_output`] is called. +//! +//! ## Remarks +//! +//! 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 +//! [`on_commit_buffer_handler`](crate::backend::renderer::utils::on_commit_buffer_handler). + +pub(crate) mod layer; +mod popup; +pub mod space; +pub mod utils; +mod window; + +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/popup.rs b/src/desktop/popup.rs new file mode 100644 index 0000000..2dd69c5 --- /dev/null +++ b/src/desktop/popup.rs @@ -0,0 +1,288 @@ +use crate::{ + utils::{DeadResource, Logical, Point, Rectangle}, + wayland::{ + compositor::{get_role, with_states}, + shell::xdg::{PopupSurface, SurfaceCachedState, XdgPopupSurfaceRoleAttributes, XDG_POPUP_ROLE}, + }, +}; +use std::sync::{Arc, Mutex}; +use wayland_server::protocol::wl_surface::WlSurface; + +/// Helper to track popups. +#[derive(Debug)] +pub struct PopupManager { + unmapped_popups: Vec, + popup_trees: Vec, + logger: ::slog::Logger, +} + +impl PopupManager { + /// Create a new [`PopupManager`]. + pub fn new>>(logger: L) -> Self { + PopupManager { + unmapped_popups: Vec::new(), + popup_trees: Vec::new(), + logger: crate::slog_or_fallback(logger), + } + } + + /// Start tracking a new popup. + 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(()) + } + } + + /// 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 + .unmapped_popups + .iter() + .position(|p| p.get_surface() == Some(surface)) + { + 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); + }) + } + + /// Finds the popup belonging to a given [`WlSurface`], if any. + 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) + }) + } + + /// Returns the popups and their relative positions for a given toplevel surface, if any. + 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() + }) + } + + /// 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()); + 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()); + } +} + +/// Represents a popup surface +#[derive(Debug, Clone)] +pub enum PopupKind { + /// xdg-shell [`PopupSurface`] + Xdg(PopupSurface), +} + +impl PopupKind { + fn alive(&self) -> bool { + match *self { + PopupKind::Xdg(ref t) => t.alive(), + } + } + + /// Retrieves the underlying [`WlSurface`] + 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(), + } + } + + /// 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, + 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, + 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/element.rs b/src/desktop/space/element.rs new file mode 100644 index 0000000..ac013c9 --- /dev/null +++ b/src/desktop/space/element.rs @@ -0,0 +1,208 @@ +use crate::{ + backend::renderer::{Frame, ImportAll, Renderer, Texture}, + desktop::{space::*, utils::*}, + utils::{Logical, Point, Rectangle}, + wayland::output::Output, +}; +use std::{ + any::{Any, TypeId}, + hash::{Hash, Hasher}, +}; +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, + F: Frame, + E: std::error::Error, + 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, + frame: &mut F, + scale: f64, + 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>; + #[allow(clippy::too_many_arguments)] + 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.map(SpaceOutputTuple::from)) + } + 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, damage, log) + } +} + +/// 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, +} + +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>, + ) -> Vec> { + damage_from_surface_tree(&self.surface, (0, 0), for_values.map(|x| (x.0, x.1))) + } + + fn draw( + &self, + renderer: &mut R, + frame: &mut F, + scale: f64, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), R::Error> { + crate::backend::renderer::utils::draw_surface_tree( + renderer, + frame, + &self.surface, + scale, + 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/layer.rs b/src/desktop/space/layer.rs new file mode 100644 index 0000000..89a4799 --- /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_surface(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/mod.rs b/src/desktop/space/mod.rs new file mode 100644 index 0000000..47116b4 --- /dev/null +++ b/src/desktop/space/mod.rs @@ -0,0 +1,713 @@ +//! 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::{ + layer::{layer_map_for_output, LayerSurface}, + window::Window, + }, + utils::{Logical, Point, Rectangle}, + wayland::{ + compositor::{ + get_parent, is_sync_subsurface, with_surface_tree_downward, SubsurfaceCachedState, + TraversalAction, + }, + output::Output, + shell::wlr_layer::Layer as WlrLayer, + }, +}; +use indexmap::{IndexMap, IndexSet}; +use std::{cell::RefCell, collections::VecDeque, fmt}; +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::*; + +crate::utils::ids::id_gen!(next_space_id, SPACE_ID, SPACE_IDS); + +/// Represents two dimensional plane to map windows and outputs upon. +#[derive(Debug)] +pub struct Space { + pub(super) id: usize, + // in z-order, back to front + windows: IndexSet, + outputs: Vec, + logger: ::slog::Logger, +} + +/// Elements rendered by [`Space::render_output`] in addition to windows, layers and popups. +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); + } +} + +impl Space { + /// Create a new [`Space`] + pub fn new(log: L) -> Space + where + L: Into>, + { + Space { + id: next_space_id(), + windows: IndexSet::new(), + outputs: Vec::new(), + logger: crate::slog_or_fallback(log), + } + } + + /// 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); + } + } + + fn insert_window(&mut self, window: &Window, activate: bool) { + self.windows.insert(window.clone()); + + if activate { + window.set_activated(true); + for w in self.windows.iter() { + if w != window { + w.set_activated(false); + } + } + } + } + + /// Unmap a [`Window`] from this space. + /// + /// 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); + } + self.windows.shift_remove(window); + } + + /// Iterate window in z-order back to front + pub fn windows(&self) -> impl DoubleEndedIterator { + self.windows.iter() + } + + /// Get a reference to the window under a given point, if any + 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) + }) + } + + /// 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().filter(move |o| { + let bbox = self.output_geometry(o); + bbox.map(|bbox| bbox.to_f64().contains(point)).unwrap_or(false) + }) + } + + /// 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; + } + + self.windows + .iter() + .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; + } + self.outputs.iter().find_map(|o| { + let map = layer_map_for_output(o); + map.layer_for_surface(surface).cloned() + }) + } + + /// 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; + } + + 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; + } + + 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. + /// + /// *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) { + self.outputs.push(output.clone()); + } + } + + /// 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; + } + + 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, + transform + .transform_size(mode.size) + .to_f64() + .to_logical(state.render_scale) + .to_i32_round(), + ) + }) + } + + /// 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; + } + + let state = output_state(self.id, o); + 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(); + } + + 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 + } + + /// 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. + 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, + ) + } + } + } + } + + /// 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; + } + 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(); + } + } + + /// 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, + output: &Output, + age: usize, + clear_color: [f32; 4], + custom_elements: &[DynamicRenderElements], + ) -> Result>>, RenderError> + where + R: Renderer + ImportAll + 'static, + R::TextureId: 'static, + R::Error: 'static, + R::Frame: 'static, + { + if !self.outputs.contains(output) { + return Err(RenderError::UnmappedOutput); + } + + type SpaceElem = + dyn SpaceElement::Frame, ::Error, ::TextureId>; + + 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); + 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(); + // First add damage for windows gone + for old_toplevel in state + .last_state + .iter() + .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(*geo) + } else { + None + } + }) + .collect::>>() + { + 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 + 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) { + // Add damage for the old position of the window + damage.push(old_geo.unwrap()); + damage.push(geo); + } else { + // window stayed at its place + let loc = element.location(self.id); + damage.extend(element.accumulated_damage(Some((self, output))).into_iter().map( + |mut rect| { + rect.loc += loc; + rect + }, + )); + } + } + + // 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 even 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.dedup(); + damage.retain(|rect| rect.overlaps(output_geo)); + damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0); + // 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); + } else { + new_damage.push(rect); + } + new_damage + }); + + if damage.is_empty() { + return Ok(None); + } + + let output_transform: Transform = output.current_transform().into(); + let res = 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 + 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 windows & layers overlapping with a damage rect. + + 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 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(|d| d.intersection(geo)) + .map(|geo| Rectangle::from_loc_and_size(geo.loc - loc, geo.size)) + .collect::>(); + slog::trace!( + self.logger, + "Rendering toplevel at {:?} with damage {:#?}", + geo, + damage + ); + element.draw( + self.id, + renderer, + frame, + state.render_scale, + loc, + &damage, + &self.logger, + )?; + } + } + + 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(); + 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(|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) + }) + .collect(); + state.old_damage.push_front(new_damage.clone()); + + 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 || { + let mut state = window_state(self.id, w); + std::mem::replace(&mut state.drawn, false) + } + }) { + 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); + } + } + } +} + +/// Errors thrown by [`Space::render_output`] +#[derive(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, +} + +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"), + } + } +} diff --git a/src/desktop/space/output.rs b/src/desktop/space/output.rs new file mode 100644 index 0000000..cb4f825 --- /dev/null +++ b/src/desktop/space/output.rs @@ -0,0 +1,57 @@ +use crate::{ + backend::renderer::{Frame, ImportAll, Renderer, Texture}, + desktop::space::SpaceElement, + utils::{Logical, Point, Rectangle}, + wayland::output::Output, +}; +use indexmap::IndexMap; +use wayland_server::protocol::wl_surface::WlSurface; + +use std::{ + any::TypeId, + cell::{RefCell, RefMut}, + collections::{HashMap, VecDeque}, +}; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct ToplevelId { + t_id: TypeId, + id: usize, +} + +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 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 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| { + m.entry(space).or_default() + }) +} diff --git a/src/desktop/space/window.rs b/src/desktop/space/window.rs new file mode 100644 index 0000000..f1ece6b --- /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 + } +} diff --git a/src/desktop/utils.rs b/src/desktop/utils.rs new file mode 100644 index 0000000..380ae4e --- /dev/null +++ b/src/desktop/utils.rs @@ -0,0 +1,235 @@ +//! Helper functions to ease dealing with surface trees + +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, + }, + output::Output, + }, +}; +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: 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, + }; + + 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_round()) + } +} + +/// 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>, +{ + 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 +} + +/// 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, + key: Option<(&Space, &Output)>, +) -> Vec> +where + P: Into>, +{ + use super::space::SpaceOutputTuple; + + let mut damage = Vec::new(); + let key = key.map(|x| SpaceOutputTuple::from(x).owned_hash()); + 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 key + .as_ref() + .map(|key| !data.damage_seen.contains(key)) + .unwrap_or(true) + && 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 mut data = data.borrow_mut(); + let attributes = states.cached_state.current::(); + + 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; + } + + 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 + })); + + if let Some(key) = key { + data.damage_seen.insert(key); + } + } + } + }, + |_, _, _| true, + ); + 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, + 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() +} + +/// 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, + (), + |_, _, &()| 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 new file mode 100644 index 0000000..a45d65d --- /dev/null +++ b/src/desktop/window.rs @@ -0,0 +1,336 @@ +use crate::{ + backend::renderer::{utils::draw_surface_tree, Frame, ImportAll, Renderer, Texture}, + desktop::{utils::*, PopupManager, Space}, + utils::{Logical, Point, Rectangle}, + wayland::{ + compositor::with_states, + output::Output, + shell::xdg::{SurfaceCachedState, ToplevelSurface}, + }, +}; +use std::{ + cell::Cell, + hash::{Hash, Hasher}, + rc::Rc, +}; +use wayland_commons::user_data::UserDataMap; +use wayland_protocols::xdg_shell::server::xdg_toplevel; +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), +} + +/// 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 { + /// 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) + } else { + None + } + } +} + +impl Kind { + /// Checks if the surface is still alive. + pub fn alive(&self) -> bool { + match *self { + Kind::Xdg(ref t) => t.alive(), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => t.alive(), + } + } + + /// 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(), + #[cfg(feature = "xwayland")] + Kind::X11(ref t) => t.get_surface(), + } + } +} + +#[derive(Debug)] +pub(super) struct WindowInner { + pub(super) id: usize, + toplevel: Kind, + bbox: Cell>, + user_data: UserDataMap, +} + +impl Drop for WindowInner { + fn drop(&mut self) { + WINDOW_IDS.lock().unwrap().remove(&self.id); + } +} + +/// Represents a single application window +#[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 { + /// Construct a new [`Window`] from a given compatible toplevel surface + pub fn new(toplevel: Kind) -> Window { + let id = next_window_id(); + + Window(Rc::new(WindowInner { + id, + toplevel, + bbox: Cell::new(Rectangle::from_loc_and_size((0, 0), (0, 0))), + 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()) + } + + /// 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() + } else { + Rectangle::from_loc_and_size((0, 0), (0, 0)) + } + } + + /// 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() { + for (popup, location) in PopupManager::popups_for_surface(surface) + .ok() + .into_iter() + .flatten() + { + if let Some(surface) = popup.get_surface() { + let offset = self.geometry().loc + location - popup.geometry().loc; + bounding_box = bounding_box.merge(bbox_from_surface_tree(surface, offset)); + } + } + } + bounding_box + } + + /// Activate/Deactivate this window + 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(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); + } + } + } + } + + /// 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. + /// + /// - `point` should be relative to (0,0) of the window. + pub fn surface_under>>( + &self, + 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() + .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, offset)) + { + return Some(result); + } + } + + under_from_surface_tree(surface, point, (0, 0)) + } else { + None + } + } + + /// 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() { + 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 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))); + } + } + } + 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, + window: &Window, + scale: f64, + location: P, + damage: &[Rectangle], + log: &slog::Logger, +) -> Result<(), R::Error> +where + R: Renderer + ImportAll, + 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) + .ok() + .into_iter() + .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 -= offset; + geo + }) + .collect::>(); + draw_surface_tree(renderer, frame, surface, scale, location + offset, &damage, log)?; + } + } + } + Ok(()) +} 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; 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] 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..de3d678 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -6,6 +6,8 @@ pub mod signaling; #[cfg(feature = "x11rb_event_source")] pub mod x11rb; +#[cfg(feature = "desktop")] +pub(crate) mod ids; pub mod user_data; pub use self::geometry::{Buffer, Coordinate, Logical, Physical, Point, Raw, Rectangle, Size}; 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))); } } diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs index 1909ab7..fdfba5e 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}; @@ -100,7 +100,7 @@ pub struct PhysicalProperties { } #[derive(Debug)] -struct Inner { +pub(crate) struct Inner { name: String, log: ::slog::Logger, instances: Vec, @@ -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>, + pub(crate) 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,38 @@ 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 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() + } + /// 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 +315,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 +363,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 +378,7 @@ impl Output { F: FnMut(&WlOutput), { self.inner + .0 .lock() .unwrap() .instances @@ -377,4 +405,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())); diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index e04de82..0872ce8 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -469,6 +469,11 @@ impl KeyboardHandle { .unwrap_or(false) } + /// Check if keyboard has focus + pub fn is_focused(&self) -> bool { + self.arc.internal.borrow_mut().focus.is_some() + } + /// Register a new keyboard to this handler /// /// The keymap will automatically be sent to it 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. 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..bd75ec1 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,11 +106,16 @@ impl Frame for DummyFrame { _texture: &Self::TextureId, _src: Rectangle, _dst: Rectangle, + _damage: &[Rectangle], _src_transform: Transform, _alpha: f32, ) -> Result<(), Self::Error> { Ok(()) } + + fn transformation(&self) -> Transform { + Transform::Normal + } } pub struct DummyTexture {