From b0cf94047f6cc5a911c72ef088139d5a6ed1553f Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 4 Feb 2022 21:04:17 +0100 Subject: [PATCH] backend: consolidate rendering code --- src/backend/kms/mod.rs | 50 ++++--------- src/backend/mod.rs | 3 +- src/backend/{ => render}/cursor.rs | 110 +++++++++++++++++------------ src/backend/render/mod.rs | 81 +++++++++++++++++++++ src/backend/winit.rs | 57 ++++----------- src/backend/x11.rs | 84 ++++++++++------------ src/shell/workspaces.rs | 22 +++++- 7 files changed, 236 insertions(+), 171 deletions(-) rename src/backend/{ => render}/cursor.rs (78%) create mode 100644 src/backend/render/mod.rs diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 27eb9d64..9a20c490 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only +#[cfg(feature = "debug")] +use crate::state::Fps; use crate::{ - backend::cursor, + backend::render, state::{BackendData, Common, State}, utils::GlobalDrop, }; @@ -77,6 +79,8 @@ pub struct Surface { pending: bool, render_timer: TimerHandle<(dev_t, crtc::Handle)>, render_timer_token: Option, + #[cfg(feature = "debug")] + fps: Fps, } #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] @@ -357,6 +361,8 @@ impl Device { pending: true, render_timer: timer_handle, render_timer_token: Some(timer_token), + #[cfg(feature = "debug")] + fps: Fps::default(), }; self.surfaces.insert(crtc, data); @@ -370,54 +376,28 @@ impl Surface { renderer: &mut Gles2Renderer, state: &mut Common, ) -> Result<()> { - #[allow(unused_mut)] - let mut custom_elements = Vec::new(); - - #[cfg(feature = "debug")] - { - let space = state.spaces.active_space(&self.output); - let size = space.output_geometry(&self.output).unwrap(); - let scale = space.output_scale(&self.output).unwrap(); - let frame = debug_ui(state, &self.fps, size, scale, true); - custom_elements.push( - Box::new(frame) as smithay::desktop::space::DynamicRenderElements - ); - } - - for seat in &state.seats { - if let Some(cursor) = cursor::draw_cursor( - renderer, - seat, - &state.start_time, - true, - ) { - custom_elements.push(cursor) - } - } - - let space = state.spaces.active_space_mut(&self.output); let (buffer, age) = self .surface .next_buffer() .with_context(|| "Failed to allocate buffer")?; + renderer .bind(buffer) .with_context(|| "Failed to bind buffer")?; - match space.render_output( + + match render::render_output( renderer, + age, + state, &self.output, - age as usize, - [0.153, 0.161, 0.165, 1.0], - &*custom_elements, + false, + #[cfg(feature = "debug")] + &mut self.fps ) { Ok(_) => { self.surface .queue_buffer() .with_context(|| "Failed to submit buffer for display")?; - #[cfg(feature = "debug")] - { - self.fps.tick(); - } } Err(err) => { self.surface.reset_buffers(); diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 96dc9af1..b3487e47 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -4,7 +4,8 @@ use crate::state::State; use anyhow::Result; use smithay::reexports::calloop::EventLoop; -pub mod cursor; +pub mod render; + pub mod kms; pub mod winit; pub mod x11; diff --git a/src/backend/cursor.rs b/src/backend/render/cursor.rs similarity index 78% rename from src/backend/cursor.rs rename to src/backend/render/cursor.rs index e3c25910..1d6133ca 100644 --- a/src/backend/cursor.rs +++ b/src/backend/render/cursor.rs @@ -1,31 +1,26 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{ - cell::RefCell, - io::Read, - rc::Rc, - sync::Mutex, -}; +use crate::state::get_dnd_icon; use smithay::{ backend::{ - renderer::{Frame, ImportAll, Renderer, Texture, gles2}, + renderer::{gles2, Frame, ImportAll, Renderer, Texture}, SwapBuffersError, }, - desktop::space::{RenderElement, SpaceOutputTuple, SurfaceTree, DynamicRenderElements}, + desktop::space::{DynamicRenderElements, RenderElement, SpaceOutputTuple, SurfaceTree}, reexports::wayland_server::protocol::wl_surface, - utils::{Logical, Buffer, Point, Rectangle, Size, Transform}, + utils::{Buffer, Logical, Point, Rectangle, Size, Transform}, wayland::{ compositor::{get_role, with_states}, - seat::{Seat, CursorImageAttributes, CursorImageStatus}, + seat::{CursorImageAttributes, CursorImageStatus, Seat}, }, }; +use std::{cell::RefCell, io::Read, rc::Rc, sync::Mutex}; use xcursor::{ parser::{parse_xcursor, Image}, CursorTheme, }; -use crate::state::get_dnd_icon; -static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../resources/cursor.rgba"); +static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/cursor.rgba"); #[derive(Debug, Clone)] pub struct Cursor { @@ -45,7 +40,9 @@ impl Cursor { let theme = CursorTheme::load(&name); let icons = load_icon(&theme) - .map_err(|err| slog_scope::warn!("Unable to load xcursor: {}, using fallback cursor", err)) + .map_err(|err| { + slog_scope::warn!("Unable to load xcursor: {}, using fallback cursor", err) + }) .unwrap_or_else(|_| { vec![Image { size: 32, @@ -69,7 +66,9 @@ impl Cursor { } impl Default for Cursor { - fn default() -> Cursor { Cursor::load() } + fn default() -> Cursor { + Cursor::load() + } } fn nearest_images(size: u32, images: &[Image]) -> impl Iterator { @@ -79,9 +78,9 @@ fn nearest_images(size: u32, images: &[Image]) -> impl Iterator { .min_by_key(|image| (size as i32 - image.size as i32).abs()) .unwrap(); - images - .iter() - .filter(move |image| image.width == nearest_image.width && image.height == nearest_image.height) + images.iter().filter(move |image| { + image.width == nearest_image.width && image.height == nearest_image.height + }) } fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image { @@ -142,7 +141,9 @@ where position -= match ret { Some(h) => h, None => { - slog_scope::warn!("Trying to display as a cursor a surface that does not have the CursorImage role."); + slog_scope::warn!( + "Trying to display as a cursor a surface that does not have the CursorImage role." + ); (0, 0).into() } }; @@ -160,7 +161,9 @@ where T: Texture + 'static, { if get_role(&surface) != Some("dnd_icon") { - slog_scope::warn!("Trying to display as a dnd icon a surface that does not have the DndIcon role."); + slog_scope::warn!( + "Trying to display as a dnd icon a surface that does not have the DndIcon role." + ); } SurfaceTree { surface, @@ -176,7 +179,11 @@ pub struct PointerElement { } impl PointerElement { - pub fn new(texture: T, relative_pointer_pos: Point, new_frame: bool) -> PointerElement { + pub fn new( + texture: T, + relative_pointer_pos: Point, + new_frame: bool, + ) -> PointerElement { let size = texture.size().to_logical(1, Transform::Normal); PointerElement { texture, @@ -202,8 +209,15 @@ where Rectangle::from_loc_and_size(self.position, self.size) } - fn accumulated_damage(&self, _: Option>) -> Vec> { - if self.new_frame { vec![Rectangle::from_loc_and_size((0, 0), self.size)] } else { vec![] } + fn accumulated_damage( + &self, + _: Option>, + ) -> Vec> { + if self.new_frame { + vec![Rectangle::from_loc_and_size((0, 0), self.size)] + } else { + vec![] + } } fn draw( @@ -242,24 +256,15 @@ pub type Textures = Vec<(Image, gles2::Gles2Texture)>; pub fn draw_cursor( renderer: &mut gles2::Gles2Renderer, seat: &Seat, + location: Point, start_time: &std::time::Instant, draw_default: bool, -) -> Option> -{ - let pointer = match seat.get_pointer() { - Some(ptr) => ptr, - None => return None, - }; - let location = pointer.current_location(); - +) -> Option> { // draw the dnd icon if applicable { if let Some(wl_surface) = get_dnd_icon(seat) { if wl_surface.as_ref().is_alive() { - return Some(Box::new(draw_dnd_icon( - wl_surface, - location.to_i32_round(), - ))); + return Some(Box::new(draw_dnd_icon(wl_surface, location))); } } } @@ -284,13 +289,15 @@ pub fn draw_cursor( if let CursorImageStatus::Image(wl_surface) = cursor_status { Some(Box::new(draw_surface_cursor( wl_surface.clone(), - location.to_i32_round(), + location, ))) } else if draw_default { let seat_userdata = seat.user_data(); seat_userdata.insert_if_missing(CursorState::default); let state = seat_userdata.get::().unwrap(); - let frame = state.cursor.get_image(1, start_time.elapsed().as_millis() as u32); + let frame = state + .cursor + .get_image(1, start_time.elapsed().as_millis() as u32); let new_frame = state.current_image.borrow().as_ref() != Some(&frame); let egl_userdata = renderer.egl_context().user_data(); @@ -302,8 +309,13 @@ pub fn draw_cursor( .find_map(|(image, texture)| if image == &frame { Some(texture) } else { None }) .cloned() .unwrap_or_else(|| { - let texture = import_bitmap(renderer, &frame.pixels_rgba, gles2::ffi::RGBA, (frame.width as i32, frame.height as i32)) - .expect("Failed to import cursor bitmap"); + let texture = import_bitmap( + renderer, + &frame.pixels_rgba, + gles2::ffi::RGBA, + (frame.width as i32, frame.height as i32), + ) + .expect("Failed to import cursor bitmap"); pointer_images_ref.push((frame.clone(), texture.clone())); texture }); @@ -312,7 +324,7 @@ pub fn draw_cursor( Some(Box::new(PointerElement::new( pointer_image.clone(), - location.to_i32_round() - hotspot, + location - hotspot, new_frame, ))) } else { @@ -334,8 +346,16 @@ pub fn import_bitmap( let mut tex = 0; gl.GenTextures(1, &mut tex); gl.BindTexture(ffi::TEXTURE_2D, tex); - gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32); - gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32); + gl.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_WRAP_S, + ffi::CLAMP_TO_EDGE as i32, + ); + gl.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_WRAP_T, + ffi::CLAMP_TO_EDGE as i32, + ); gl.TexImage2D( ffi::TEXTURE_2D, 0, @@ -349,10 +369,6 @@ pub fn import_bitmap( ); gl.BindTexture(ffi::TEXTURE_2D, 0); - gles2::Gles2Texture::from_raw( - renderer, - tex, - size, - ) + gles2::Gles2Texture::from_raw(renderer, tex, size) }) -} \ No newline at end of file +} diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs new file mode 100644 index 00000000..4d8d6eda --- /dev/null +++ b/src/backend/render/mod.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::Common; +#[cfg(feature = "debug")] +use crate::{ + debug::{debug_ui, fps_ui}, + state::Fps, +}; + +use smithay::{ + backend::renderer::gles2::Gles2Renderer, + desktop::space::{DynamicRenderElements, RenderError}, + utils::{Logical, Rectangle}, + wayland::output::Output, +}; + +mod cursor; + +pub fn render_output( + renderer: &mut Gles2Renderer, + age: u8, + state: &mut Common, + output: &Output, + hardware_cursor: bool, + #[cfg(feature = "debug")] fps: &mut Fps, +) -> Result>>, RenderError> { + #[cfg(feature = "debug")] + { + fps.start(); + } + + #[allow(unused_mut)] + let mut custom_elements = Vec::>::new(); + + #[cfg(feature = "debug")] + { + let space = state.spaces.active_space(output); + let output_geo = space.output_geometry(output) + .unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0))); + let scale = space.output_scale(output).unwrap(); + + let fps_overlay = fps_ui(state, fps, output_geo, scale); + custom_elements.push(Box::new(fps_overlay)); + + let mut area = state.spaces.global_space(); + area.loc = state.spaces.space_relative_output_geometry((0, 0), output); + //let output_geo = state.spaces.output_geometry(output); + if let Some(debug_overlay) = debug_ui(state, area, scale) { + custom_elements.push(Box::new(debug_overlay)); + } + } + + for seat in &state.seats { + let pointer = match seat.get_pointer() { + Some(ptr) => ptr, + None => continue, + }; + let location = state.spaces.space_relative_output_geometry(pointer.current_location().to_i32_round(), output); + + if let Some(cursor) = + cursor::draw_cursor(renderer, seat, location, &state.start_time, !hardware_cursor) + { + custom_elements.push(cursor) + } + } + + let res = state.spaces.active_space_mut(output).render_output( + renderer, + &output, + age as usize, + [0.153, 0.161, 0.165, 1.0], + &*custom_elements, + ); + + #[cfg(feature = "debug")] + { + fps.end(); + } + + res +} diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 3529d5cc..339f3e0d 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - backend::cursor, + backend::render, input::{set_active_output, Devices}, - state::{BackendData, State, Common}, + state::{BackendData, Common, State}, }; use anyhow::{Context, Result}; use smithay::{ @@ -27,9 +27,7 @@ use smithay::{ use std::{cell::RefCell, rc::Rc}; #[cfg(feature = "debug")] -use crate::{debug::debug_ui, state::Fps}; -#[cfg(feature = "debug")] -use smithay::backend::renderer::gles2::Gles2Renderer; +use crate::state::Fps; pub struct WinitState { // The winit backend currently has no notion of multiple windows @@ -42,52 +40,27 @@ pub struct WinitState { impl WinitState { pub fn render_output(&mut self, state: &mut Common) -> Result<()> { - #[allow(unused_mut)] - let mut custom_elements = Vec::new(); - - #[cfg(feature = "debug")] - { - let space = state.spaces.active_space(&self.output); - let size = space.output_geometry(&self.output).unwrap(); - let scale = space.output_scale(&self.output).unwrap(); - let frame = debug_ui(state, &self.fps, size, scale, true); - custom_elements.push( - Box::new(frame) as smithay::desktop::space::DynamicRenderElements - ); - } - - let space = state.spaces.active_space_mut(&self.output); - let mut backend = self.backend.borrow_mut(); - - for seat in &state.seats { - if let Some(cursor) = cursor::draw_cursor( - backend.renderer(), - seat, - &state.start_time, - false, - ) { - custom_elements.push(cursor) - } - } - + let backend = &mut *self.backend.borrow_mut(); backend.bind().with_context(|| "Failed to bind buffer")?; let age = backend.buffer_age().unwrap_or(0); - match space.render_output( + + match render::render_output( backend.renderer(), + age as u8, + state, &self.output, - age as usize, - [0.153, 0.161, 0.165, 1.0], - &*custom_elements, + true, + #[cfg(feature = "debug")] + &mut self.fps, ) { Ok(damage) => { - space.send_frames(false, state.start_time.elapsed().as_millis() as u32); + state + .spaces + .active_space_mut(&self.output) + .send_frames(true, state.start_time.elapsed().as_millis() as u32); backend .submit(damage.as_ref().map(|x| &**x), 1.0) .with_context(|| "Failed to submit buffer for display")?; - #[cfg(feature = "debug")] - { - self.fps.tick(); - } } Err(err) => { anyhow::bail!("Rendering failed: {}", err); diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 6852995f..561e1d80 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - backend::cursor, + backend::render, input::{set_active_output, Devices}, - state::{BackendData, State, Common}, + state::{BackendData, Common, State}, utils::GlobalDrop, }; use anyhow::{Context, Result}; @@ -37,7 +37,7 @@ use std::{ }; #[cfg(feature = "debug")] -use crate::{debug::debug_ui, state::Fps}; +use crate::state::Fps; pub struct X11State { allocator: Arc>>, @@ -104,7 +104,8 @@ impl X11State { { slog_scope::error!("Error rendering: {}", err); } - surface.pending = false; + surface.dirty = false; + surface.pending = true; } }) .with_context(|| "Failed to add output to event loop")?; @@ -114,6 +115,7 @@ impl X11State { surface, output: output.clone(), render: ping.clone(), + dirty: false, pending: true, #[cfg(feature = "debug")] fps: Fps::default(), @@ -127,8 +129,8 @@ impl X11State { pub fn schedule_render(&mut self, output: &Output) { if let Some(surface) = self.surfaces.iter_mut().find(|s| s.output == *output) { + surface.dirty = true; if !surface.pending { - surface.pending = true; surface.render.ping(); } } @@ -140,6 +142,7 @@ pub struct Surface { surface: X11Surface, output: Output, render: ping::Ping, + dirty: bool, pending: bool, #[cfg(feature = "debug")] fps: Fps, @@ -152,32 +155,6 @@ impl Surface { renderer: &mut Gles2Renderer, state: &mut Common, ) -> Result<()> { - #[allow(unused_mut)] - let mut custom_elements = Vec::new(); - - #[cfg(feature = "debug")] - { - let space = state.spaces.active_space(&self.output); - let size = space.output_geometry(&self.output).unwrap(); - let scale = space.output_scale(&self.output).unwrap(); - let frame = debug_ui(state, &self.fps, size, scale, true); - custom_elements.push( - Box::new(frame) as smithay::desktop::space::DynamicRenderElements - ); - } - - for seat in &state.seats { - if let Some(cursor) = cursor::draw_cursor( - renderer, - seat, - &state.start_time, - false, - ) { - custom_elements.push(cursor) - } - } - - let space = state.spaces.active_space_mut(&self.output); let (buffer, age) = self .surface .buffer() @@ -185,22 +162,24 @@ impl Surface { renderer .bind(buffer) .with_context(|| "Failed to bind buffer")?; - match space.render_output( + + match render::render_output( renderer, + age as u8, + state, &self.output, - age as usize, - [0.153, 0.161, 0.165, 1.0], - &*custom_elements, + true, + #[cfg(feature = "debug")] + &mut self.fps, ) { Ok(_) => { - space.send_frames(false, state.start_time.elapsed().as_millis() as u32); + state + .spaces + .active_space_mut(&self.output) + .send_frames(true, state.start_time.elapsed().as_millis() as u32); self.surface .submit() .with_context(|| "Failed to submit buffer for display")?; - #[cfg(feature = "debug")] - { - self.fps.tick(); - } } Err(err) => { self.surface.reset_buffers(); @@ -287,18 +266,33 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res .iter_mut() .find(|s| s.window.id() == window_id) { - surface.render.ping(); - let output = &surface.output; output.delete_mode(output.current_mode().unwrap()); output.change_current_state(Some(mode), None, None, None); output.set_preferred(mode); layer_map_for_output(output).arrange(); + surface.dirty = true; + if !surface.pending { + surface.render.ping(); + } } - } - + }, + X11Event::Refresh { window_id } | X11Event::PresentCompleted { window_id } => { + if let Some(surface) = state + .backend + .x11() + .surfaces + .iter_mut() + .find(|s| s.window.id() == window_id) + { + if surface.dirty { + surface.render.ping(); + } else { + surface.pending = false; + } + } + }, X11Event::Input(event) => state.process_x11_event(event), - _ => {}, }) .map_err(|_| anyhow::anyhow!("Failed to insert X11 Backend into event loop"))?; diff --git a/src/shell/workspaces.rs b/src/shell/workspaces.rs index 9edda7ea..cfaf68e5 100644 --- a/src/shell/workspaces.rs +++ b/src/shell/workspaces.rs @@ -3,7 +3,7 @@ pub use smithay::{ desktop::Space, reexports::wayland_server::protocol::wl_surface::WlSurface, - utils::{Logical, Rectangle, Size}, + utils::{Logical, Point, Rectangle, Size}, wayland::output::Output, }; use std::{cell::Cell, mem::MaybeUninit}; @@ -126,6 +126,26 @@ impl Workspaces { .size } + pub fn global_space(&self) -> Rectangle { + let size = self.outputs.iter().fold((0, 0), |(w, h), output| { + let size = self.output_size(output); + (w + size.w, std::cmp::max(h, size.h)) + }); + + Rectangle::from_loc_and_size((0, 0), size) + } + + pub fn space_relative_output_geometry( + &self, + global_loc: impl Into>, + output: &Output, + ) -> Point { + match self.mode { + Mode::Global { .. } => global_loc.into(), + Mode::OutputBound => global_loc.into() - self.output_geometry(output).loc, + } + } + pub fn output_geometry(&self, output: &Output) -> Rectangle { // due to our different modes, we cannot just ask the space for the global output coordinates, // because for `Mode::OutputBound` the origin will always be (0, 0)