From 31b308836fa7d1cd04d0f28757c63109daa71bd3 Mon Sep 17 00:00:00 2001 From: Victor Brekenfeld Date: Tue, 7 Dec 2021 18:34:10 +0100 Subject: [PATCH] renderer: Use fine grained damage for rendering Up until now we always redrew the whole windows, which worked fine - except for some transparency issues - but left performance on the table. So now we use instancing to render only the actually damaged regions inside the window, which also fixes our few rendering issues. We also use a shader now for clearing the screen to use instancing as well and lower the amount of draw calls at the begin of a frame for clearing parts of it. And while we are add it we remove the last rendering artifacts by optimizing the damage vector by deduplicating and merging rectangles. --- src/backend/renderer/gles2/mod.rs | 297 +++++++++++++++++++------- src/backend/renderer/gles2/shaders.rs | 61 +++++- src/backend/renderer/mod.rs | 5 +- src/backend/renderer/utils/mod.rs | 27 ++- src/desktop/space.rs | 54 ++++- src/desktop/window.rs | 21 +- 6 files changed, 374 insertions(+), 91 deletions(-) diff --git a/src/backend/renderer/gles2/mod.rs b/src/backend/renderer/gles2/mod.rs index 762100b..00d2344 100644 --- a/src/backend/renderer/gles2/mod.rs +++ b/src/backend/renderer/gles2/mod.rs @@ -44,23 +44,27 @@ pub mod ffi { include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); } -// This static is used to assign every created Renderer a unique ID (until is overflows...). -// -// This id is used to differenciate between user_data of different renderers, because one -// cannot assume, that resources between two renderers are (and even can be) shared. -static RENDERER_COUNTER: AtomicUsize = AtomicUsize::new(0); - #[derive(Debug, Clone)] -struct Gles2Program { +struct Gles2TexProgram { program: ffi::types::GLuint, uniform_tex: ffi::types::GLint, uniform_matrix: ffi::types::GLint, uniform_invert_y: ffi::types::GLint, uniform_alpha: ffi::types::GLint, + attrib_vert: ffi::types::GLint, attrib_position: ffi::types::GLint, attrib_tex_coords: ffi::types::GLint, } +#[derive(Debug, Clone)] +struct Gles2SolidProgram { + program: ffi::types::GLuint, + uniform_matrix: ffi::types::GLint, + uniform_color: ffi::types::GLint, + attrib_vert: ffi::types::GLint, + attrib_position: ffi::types::GLint, +} + /// A handle to a GLES2 texture #[derive(Debug, Clone)] pub struct Gles2Texture(Rc); @@ -158,40 +162,20 @@ struct Gles2Buffer { _dmabuf: Dmabuf, } -#[cfg(feature = "wayland_frontend")] -struct BufferEntry { - id: u32, - buffer: wl_buffer::WlBuffer, -} - -#[cfg(feature = "wayland_frontend")] -impl std::hash::Hash for BufferEntry { - fn hash(&self, hasher: &mut H) { - self.id.hash(hasher); - } -} -#[cfg(feature = "wayland_frontend")] -impl PartialEq for BufferEntry { - fn eq(&self, other: &Self) -> bool { - self.buffer == other.buffer - } -} -#[cfg(feature = "wayland_frontend")] -impl Eq for BufferEntry {} - /// A renderer utilizing OpenGL ES 2 pub struct Gles2Renderer { - id: usize, buffers: Vec, target_buffer: Option, target_surface: Option>, extensions: Vec, - programs: [Gles2Program; shaders::FRAGMENT_COUNT], + tex_programs: [Gles2TexProgram; shaders::FRAGMENT_COUNT], + solid_program: Gles2SolidProgram, #[cfg(feature = "wayland_frontend")] dmabuf_cache: std::collections::HashMap, egl: EGLContext, #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] egl_reader: Option, + vbos: [ffi::types::GLuint; 2], gl: ffi::Gles2, destruction_callback: Receiver, // This field is only accessed if the image or wayland_frontend features are active @@ -206,14 +190,19 @@ pub struct Gles2Renderer { pub struct Gles2Frame { current_projection: Matrix3, gl: ffi::Gles2, - programs: [Gles2Program; shaders::FRAGMENT_COUNT], + tex_programs: [Gles2TexProgram; shaders::FRAGMENT_COUNT], + solid_program: Gles2SolidProgram, + vbos: [ffi::types::GLuint; 2], + size: Size, } impl fmt::Debug for Gles2Frame { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Gles2Frame") .field("current_projection", &self.current_projection) - .field("programs", &self.programs) + .field("tex_programs", &self.tex_programs) + .field("solid_program", &self.solid_program) + .field("size", &self.size) .finish_non_exhaustive() } } @@ -221,12 +210,12 @@ impl fmt::Debug for Gles2Frame { impl fmt::Debug for Gles2Renderer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Gles2Renderer") - .field("id", &self.id) .field("buffers", &self.buffers) .field("target_buffer", &self.target_buffer) .field("target_surface", &self.target_surface) .field("extensions", &self.extensions) - .field("programs", &self.programs) + .field("tex_programs", &self.tex_programs) + .field("solid_program", &self.solid_program) // ffi::Gles2 does not implement Debug .field("egl", &self.egl) .field("logger", &self.logger) @@ -382,9 +371,10 @@ unsafe fn link_program( Ok(program) } -unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result { +unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result { let program = link_program(gl, shaders::VERTEX_SHADER, frag)?; + let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated"); let position = CStr::from_bytes_with_nul(b"position\0").expect("NULL terminated"); let tex_coords = CStr::from_bytes_with_nul(b"tex_coords\0").expect("NULL terminated"); let tex = CStr::from_bytes_with_nul(b"tex\0").expect("NULL terminated"); @@ -392,17 +382,35 @@ unsafe fn texture_program(gl: &ffi::Gles2, frag: &'static str) -> Result Result { + let program = link_program(gl, shaders::VERTEX_SHADER_SOLID, shaders::FRAGMENT_SHADER_SOLID)?; + + let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated"); + let color = CStr::from_bytes_with_nul(b"color\0").expect("NULL terminated"); + let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated"); + let position = CStr::from_bytes_with_nul(b"position\0").expect("NULL terminated"); + + Ok(Gles2SolidProgram { + program, + uniform_matrix: gl.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar), + uniform_color: gl.GetUniformLocation(program, color.as_ptr() as *const ffi::types::GLchar), + attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar), + attrib_position: gl.GetAttribLocation(program, position.as_ptr() as *const ffi::types::GLchar), + }) +} + impl Gles2Renderer { /// Creates a new OpenGL ES 2 renderer from a given [`EGLContext`](crate::backend::egl::EGLBuffer). /// @@ -473,6 +481,16 @@ impl Gles2Renderer { if gl_version < version::GLES_3_0 && !exts.iter().any(|ext| ext == "GL_EXT_unpack_subimage") { return Err(Gles2Error::GLExtensionNotSupported(&["GL_EXT_unpack_subimage"])); } + // required for instanced damage rendering + if gl_version < version::GLES_3_0 + && !(exts.iter().any(|ext| ext == "GL_EXT_instanced_arrays") + && exts.iter().any(|ext| ext == "GL_EXT_draw_instanced")) + { + return Err(Gles2Error::GLExtensionNotSupported(&[ + "GL_EXT_instanced_arrays", + "GL_EXT_draw_instanced", + ])); + } let logger = if exts.iter().any(|ext| ext == "GL_KHR_debug") { let logger = Box::into_raw(Box::new(log.clone())); @@ -487,21 +505,33 @@ impl Gles2Renderer { (gl, exts, logger) }; - let programs = [ + let tex_programs = [ texture_program(&gl, shaders::FRAGMENT_SHADER_ABGR)?, texture_program(&gl, shaders::FRAGMENT_SHADER_XBGR)?, texture_program(&gl, shaders::FRAGMENT_SHADER_EXTERNAL)?, ]; + let solid_program = solid_program(&gl)?; + + let mut vbos = [0; 2]; + gl.GenBuffers(2, vbos.as_mut_ptr()); + gl.BindBuffer(ffi::ARRAY_BUFFER, vbos[0]); + gl.BufferData( + ffi::ARRAY_BUFFER, + (std::mem::size_of::() * VERTS.len()) as isize, + VERTS.as_ptr() as *const _, + ffi::STATIC_DRAW, + ); + gl.BindBuffer(ffi::ARRAY_BUFFER, 0); let (tx, rx) = channel(); let mut renderer = Gles2Renderer { - id: RENDERER_COUNTER.fetch_add(1, Ordering::SeqCst), gl, egl: context, #[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))] egl_reader: None, extensions: exts, - programs, + tex_programs, + solid_program, target_buffer: None, target_surface: None, buffers: Vec::new(), @@ -509,6 +539,7 @@ impl Gles2Renderer { dmabuf_cache: std::collections::HashMap::new(), destruction_callback: rx, destruction_callback_sender: tx, + vbos, logger_ptr, logger: log, _not_send: std::ptr::null_mut(), @@ -966,9 +997,11 @@ impl Drop for Gles2Renderer { unsafe { if self.egl.make_current().is_ok() { self.gl.BindFramebuffer(ffi::FRAMEBUFFER, 0); - for program in &self.programs { + for program in &self.tex_programs { self.gl.DeleteProgram(program.program); } + self.gl.DeleteProgram(self.solid_program.program); + self.gl.DeleteBuffers(2, self.vbos.as_ptr()); if self.extensions.iter().any(|ext| ext == "GL_KHR_debug") { self.gl.Disable(ffi::DEBUG_OUTPUT); @@ -1082,9 +1115,12 @@ impl Renderer for Gles2Renderer { let mut frame = Gles2Frame { gl: self.gl.clone(), - programs: self.programs.clone(), + tex_programs: self.tex_programs.clone(), + solid_program: self.solid_program.clone(), // output transformation passed in by the user current_projection: flip180 * transform.matrix() * renderer, + vbos: self.vbos, + size, }; let result = rendering(self, &mut frame); @@ -1124,17 +1160,85 @@ impl Frame for Gles2Frame { type Error = Gles2Error; type TextureId = Gles2Texture; - fn clear(&mut self, color: [f32; 4], at: Option>) -> Result<(), Self::Error> { + fn clear(&mut self, color: [f32; 4], at: &[Rectangle]) -> Result<(), Self::Error> { + if at.is_empty() { + return Ok(()); + } + + let mut mat = Matrix3::::identity(); + mat = mat * Matrix3::from_translation(Vector2::new(0.0, 0.0)); + mat = mat * Matrix3::from_nonuniform_scale(self.size.w as f32, self.size.h as f32); + mat = self.current_projection * mat; + + let damage = at + .iter() + .map(|rect| { + [ + rect.loc.x as f32 / self.size.w as f32, + rect.loc.y as f32 / self.size.h as f32, + rect.size.w as f32 / self.size.w as f32, + rect.size.h as f32 / self.size.h as f32, + ] + }) + .flatten() + .collect::>(); + unsafe { - if let Some(rect) = at { - self.gl.Enable(ffi::SCISSOR_TEST); - self.gl.Scissor(rect.loc.x, rect.loc.y, rect.size.w, rect.size.h); - } - self.gl.ClearColor(color[0], color[1], color[2], color[3]); - self.gl.Clear(ffi::COLOR_BUFFER_BIT); - if at.is_some() { - self.gl.Disable(ffi::SCISSOR_TEST); - } + self.gl.UseProgram(self.solid_program.program); + self.gl.Uniform4f( + self.solid_program.uniform_color, + color[0], + color[1], + color[2], + color[3], + ); + self.gl + .UniformMatrix3fv(self.solid_program.uniform_matrix, 1, ffi::FALSE, mat.as_ptr()); + + self.gl + .EnableVertexAttribArray(self.solid_program.attrib_vert as u32); + self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[0]); + self.gl.VertexAttribPointer( + self.solid_program.attrib_vert as u32, + 2, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); + + self.gl + .EnableVertexAttribArray(self.solid_program.attrib_position as u32); + self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[1]); + self.gl.BufferData( + ffi::ARRAY_BUFFER, + (std::mem::size_of::() * damage.len()) as isize, + damage.as_ptr() as *const _, + ffi::STREAM_DRAW, + ); + + self.gl.VertexAttribPointer( + self.solid_program.attrib_position as u32, + 4, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); + self.gl + .VertexAttribDivisor(self.solid_program.attrib_vert as u32, 0); + + self.gl + .VertexAttribDivisor(self.solid_program.attrib_position as u32, 1); + + self.gl + .DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, at.len() as i32); + + self.gl.BindBuffer(ffi::ARRAY_BUFFER, 0); + self.gl + .DisableVertexAttribArray(self.solid_program.attrib_vert as u32); + self.gl + .DisableVertexAttribArray(self.solid_program.attrib_position as u32); } Ok(()) @@ -1145,6 +1249,7 @@ impl Frame for Gles2Frame { texture: &Self::TextureId, src: Rectangle, dest: Rectangle, + damage: &[Rectangle], transform: Transform, alpha: f32, ) -> Result<(), Self::Error> { @@ -1168,7 +1273,7 @@ impl Frame for Gles2Frame { let texture_mat = Matrix3::from_nonuniform_scale(tex_size.w as f32, tex_size.h as f32) .invert() .unwrap(); - let verts = [ + let tex_verts = [ (texture_mat * Vector3::new((src.loc.x + src.size.w) as f32, src.loc.y as f32, 0.0)).truncate(), // top-right (texture_mat * Vector3::new(src.loc.x as f32, src.loc.y as f32, 0.0)).truncate(), // top-left (texture_mat @@ -1180,7 +1285,21 @@ impl Frame for Gles2Frame { .truncate(), // bottom-right (texture_mat * Vector3::new(src.loc.x as f32, (src.loc.y + src.size.h) as f32, 0.0)).truncate(), // bottom-left ]; - self.render_texture(texture, mat, verts, alpha) + + let damage = damage + .iter() + .map(|rect| { + [ + rect.loc.x as f32 / dest.size.w as f32, + rect.loc.y as f32 / dest.size.h as f32, + rect.size.w as f32 / dest.size.w as f32, + rect.size.h as f32 / dest.size.h as f32, + ] + }) + .flatten() + .collect::>(); + + self.render_texture(texture, mat, Some(&damage), tex_verts, alpha) } } @@ -1191,6 +1310,7 @@ impl Gles2Frame { &mut self, tex: &Gles2Texture, mut matrix: Matrix3, + instances: Option<&[ffi::types::GLfloat]>, tex_coords: [Vector2; 4], alpha: f32, ) -> Result<(), Gles2Error> { @@ -1209,33 +1329,27 @@ impl Gles2Frame { self.gl.BindTexture(target, tex.0.texture); self.gl .TexParameteri(target, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32); - self.gl.UseProgram(self.programs[tex.0.texture_kind].program); + self.gl.UseProgram(self.tex_programs[tex.0.texture_kind].program); self.gl - .Uniform1i(self.programs[tex.0.texture_kind].uniform_tex, 0); + .Uniform1i(self.tex_programs[tex.0.texture_kind].uniform_tex, 0); self.gl.UniformMatrix3fv( - self.programs[tex.0.texture_kind].uniform_matrix, + self.tex_programs[tex.0.texture_kind].uniform_matrix, 1, ffi::FALSE, matrix.as_ptr(), ); self.gl.Uniform1i( - self.programs[tex.0.texture_kind].uniform_invert_y, + self.tex_programs[tex.0.texture_kind].uniform_invert_y, if tex.0.y_inverted { 1 } else { 0 }, ); self.gl - .Uniform1f(self.programs[tex.0.texture_kind].uniform_alpha, alpha); + .Uniform1f(self.tex_programs[tex.0.texture_kind].uniform_alpha, alpha); + self.gl + .EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32); self.gl.VertexAttribPointer( - self.programs[tex.0.texture_kind].attrib_position as u32, - 2, - ffi::FLOAT, - ffi::FALSE, - 0, - VERTS.as_ptr() as *const _, - ); - self.gl.VertexAttribPointer( - self.programs[tex.0.texture_kind].attrib_tex_coords as u32, + self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32, 2, ffi::FLOAT, ffi::FALSE, @@ -1244,18 +1358,55 @@ impl Gles2Frame { ); self.gl - .EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32); - self.gl - .EnableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32); + .EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_vert as u32); + self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[0]); + self.gl.VertexAttribPointer( + self.solid_program.attrib_vert as u32, + 2, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); - self.gl.DrawArrays(ffi::TRIANGLE_STRIP, 0, 4); + let damage = instances.unwrap_or(&[0.0, 0.0, 1.0, 1.0]); + self.gl + .EnableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_position as u32); + self.gl.BindBuffer(ffi::ARRAY_BUFFER, self.vbos[1]); + self.gl.BufferData( + ffi::ARRAY_BUFFER, + (std::mem::size_of::() * damage.len()) as isize, + damage.as_ptr() as *const _, + ffi::STREAM_DRAW, + ); + + let count = (damage.len() / 4) as i32; + self.gl.VertexAttribPointer( + self.tex_programs[tex.0.texture_kind].attrib_position as u32, + 4, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); self.gl - .DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_position as u32); + .VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_vert as u32, 0); self.gl - .DisableVertexAttribArray(self.programs[tex.0.texture_kind].attrib_tex_coords as u32); + .VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32, 0); + self.gl + .VertexAttribDivisor(self.tex_programs[tex.0.texture_kind].attrib_position as u32, 1); + self.gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, count); + + self.gl.BindBuffer(ffi::ARRAY_BUFFER, 0); self.gl.BindTexture(target, 0); + self.gl + .DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_tex_coords as u32); + self.gl + .DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_vert as u32); + self.gl + .DisableVertexAttribArray(self.tex_programs[tex.0.texture_kind].attrib_position as u32); } Ok(()) diff --git a/src/backend/renderer/gles2/shaders.rs b/src/backend/renderer/gles2/shaders.rs index f17ef6c..229ec22 100644 --- a/src/backend/renderer/gles2/shaders.rs +++ b/src/backend/renderer/gles2/shaders.rs @@ -2,29 +2,48 @@ * OpenGL Shaders */ pub const VERTEX_SHADER: &str = r#" + #version 100 uniform mat3 matrix; uniform bool invert_y; -attribute vec2 position; + +attribute vec2 vert; attribute vec2 tex_coords; +attribute vec4 position; + varying vec2 v_tex_coords; + +mat2 scale(vec2 scale_vec){ + return mat2( + scale_vec.x, 0.0, + 0.0, scale_vec.y + ); +} + void main() { - gl_Position = vec4(matrix * vec3(position, 1.0), 1.0); if (invert_y) { v_tex_coords = vec2(tex_coords.x, 1.0 - tex_coords.y); } else { v_tex_coords = tex_coords; } -}"#; + + vec2 transform_translation = position.xy; + vec2 transform_scale = position.zw; + v_tex_coords = (vec3((tex_coords * scale(transform_scale)) + transform_translation, 1.0)).xy; + gl_Position = vec4(matrix * vec3((vert * scale(transform_scale)) + transform_translation, 1.0), 1.0); +} +"#; pub const FRAGMENT_COUNT: usize = 3; pub const FRAGMENT_SHADER_ABGR: &str = r#" #version 100 + precision mediump float; uniform sampler2D tex; uniform float alpha; varying vec2 v_tex_coords; + void main() { gl_FragColor = texture2D(tex, v_tex_coords) * alpha; } @@ -32,10 +51,12 @@ void main() { pub const FRAGMENT_SHADER_XBGR: &str = r#" #version 100 + precision mediump float; uniform sampler2D tex; uniform float alpha; varying vec2 v_tex_coords; + void main() { gl_FragColor = vec4(texture2D(tex, v_tex_coords).rgb, 1.0) * alpha; } @@ -44,11 +65,45 @@ void main() { pub const FRAGMENT_SHADER_EXTERNAL: &str = r#" #version 100 #extension GL_OES_EGL_image_external : require + precision mediump float; uniform samplerExternalOES tex; uniform float alpha; varying vec2 v_tex_coords; + void main() { gl_FragColor = texture2D(tex, v_tex_coords) * alpha; } "#; + +pub const VERTEX_SHADER_SOLID: &str = r#" +#version 100 + +uniform mat3 matrix; +attribute vec2 vert; +attribute vec4 position; + +mat2 scale(vec2 scale_vec){ + return mat2( + scale_vec.x, 0.0, + 0.0, scale_vec.y + ); +} + +void main() { + vec2 transform_translation = position.xy; + vec2 transform_scale = position.zw; + gl_Position = vec4(matrix * vec3((vert * scale(transform_scale)) + transform_translation, 1.0), 1.0); +} +"#; + +pub const FRAGMENT_SHADER_SOLID: &str = r#" +#version 100 + +precision mediump float; +uniform vec4 color; + +void main() { + gl_FragColor = color; +} +"#; diff --git a/src/backend/renderer/mod.rs b/src/backend/renderer/mod.rs index c71d770..e02de36 100644 --- a/src/backend/renderer/mod.rs +++ b/src/backend/renderer/mod.rs @@ -173,7 +173,7 @@ pub trait Frame { /// /// This operation is only valid in between a `begin` and `finish`-call. /// If called outside this operation may error-out, do nothing or modify future rendering results in any way. - fn clear(&mut self, color: [f32; 4], at: Option>) -> Result<(), Self::Error>; + fn clear(&mut self, color: [f32; 4], at: &[Rectangle]) -> Result<(), Self::Error>; /// Render a texture to the current target as a flat 2d-plane at a given /// position and applying the given transformation with the given alpha value. @@ -185,6 +185,7 @@ pub trait Frame { texture_scale: i32, output_scale: f64, src_transform: Transform, + damage: &[Rectangle], alpha: f32, ) -> Result<(), Self::Error> { self.render_texture_from_to( @@ -198,6 +199,7 @@ pub trait Frame { .to_f64() .to_physical(output_scale), ), + damage, src_transform, alpha, ) @@ -211,6 +213,7 @@ pub trait Frame { texture: &Self::TextureId, src: Rectangle, dst: Rectangle, + damage: &[Rectangle], src_transform: Transform, alpha: f32, ) -> Result<(), Self::Error>; diff --git a/src/backend/renderer/utils/mod.rs b/src/backend/renderer/utils/mod.rs index e9d4723..6949e88 100644 --- a/src/backend/renderer/utils/mod.rs +++ b/src/backend/renderer/utils/mod.rs @@ -1,6 +1,6 @@ use crate::{ backend::renderer::{buffer_dimensions, Frame, ImportAll, Renderer, Texture}, - utils::{Logical, Physical, Point, Size}, + utils::{Buffer, Logical, Physical, Point, Rectangle, Size}, wayland::compositor::{ is_sync_subsurface, with_surface_tree_upward, BufferAssignment, Damage, SubsurfaceCachedState, SurfaceAttributes, TraversalAction, @@ -66,12 +66,14 @@ pub fn on_commit_buffer_handler(surface: &WlSurface) { } } +/// TODO pub fn draw_surface_tree( renderer: &mut R, frame: &mut F, surface: &WlSurface, scale: f64, location: Point, + damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> where @@ -81,6 +83,10 @@ where T: Texture + 'static, { let mut result = Ok(()); + let damage = damage + .iter() + .map(|geo| geo.to_f64().to_physical(scale).to_i32_round()) + .collect::>(); with_surface_tree_upward( surface, location, @@ -137,15 +143,33 @@ where if let Some(data) = states.data_map.get::>() { let mut data = data.borrow_mut(); let buffer_scale = data.buffer_scale; + let buffer_dimensions = data.buffer_dimensions; let attributes = states.cached_state.current::(); if let Some(texture) = data.texture.as_mut().and_then(|x| x.downcast_mut::()) { // we need to re-extract the subsurface offset, as the previous closure // only passes it to our children + let mut surface_offset = (0, 0).into(); if states.role == Some("subsurface") { let current = states.cached_state.current::(); + surface_offset = current.location; location += current.location; } + let rect = Rectangle::::from_loc_and_size( + surface_offset.to_f64().to_physical(scale).to_i32_round(), + buffer_dimensions.unwrap(), + ); + let new_damage = damage + .iter() + .cloned() + .filter(|geo| geo.overlaps(rect)) + .map(|geo| geo.intersection(rect)) + .map(|mut geo| { + geo.loc -= rect.loc; + geo + }) + .collect::>(); + // TODO: Take wp_viewporter into account if let Err(err) = frame.render_texture_at( texture, @@ -153,6 +177,7 @@ where buffer_scale, scale, attributes.buffer_transform.into(), + &new_damage, 1.0, ) { result = Err(err); diff --git a/src/desktop/space.rs b/src/desktop/space.rs index 82dfe7d..aa2253c 100644 --- a/src/desktop/space.rs +++ b/src/desktop/space.rs @@ -450,6 +450,7 @@ impl Space { } // Optimize the damage for rendering + damage.dedup(); damage.retain(|rect| rect.overlaps(output_geo)); damage.retain(|rect| rect.size.h > 0 && rect.size.w > 0); for rect in damage.clone().iter() { @@ -460,6 +461,18 @@ impl Space { damage.retain(|other| !rect.contains_rect(*other)); } } + damage = damage.into_iter().fold(Vec::new(), |mut new_damage, rect| { + if let Some(existing) = new_damage.iter_mut().find(|other| rect.overlaps(**other)) { + *existing = existing.merge(rect); + } else { + new_damage.push(rect); + } + new_damage + }); + + if damage.is_empty() { + return Ok(false); + } let output_transform: Transform = output.current_transform().into(); if let Err(err) = renderer.render( @@ -471,13 +484,14 @@ impl Space { output_transform, |renderer, frame| { // First clear all damaged regions - for geo in &damage { - slog::trace!(self.logger, "Clearing at {:?}", geo); - frame.clear( - clear_color, - Some(geo.to_f64().to_physical(state.render_scale).to_i32_ceil()), - )?; - } + slog::trace!(self.logger, "Clearing at {:#?}", damage); + frame.clear( + clear_color, + &damage + .iter() + .map(|geo| geo.to_f64().to_physical(state.render_scale).to_i32_round()) + .collect::>(), + )?; // Then re-draw all window overlapping with a damage rect. for window in self.windows.iter() { @@ -485,8 +499,27 @@ impl Space { let mut loc = window_loc(window, &self.id); if damage.iter().any(|geo| wgeo.overlaps(*geo)) { loc -= output_geo.loc; - slog::trace!(self.logger, "Rendering window at {:?}", wgeo); - draw_window(renderer, frame, window, state.render_scale, loc, &self.logger)?; + let win_damage = damage + .iter() + .filter(|geo| geo.overlaps(wgeo)) + .map(|geo| geo.intersection(wgeo)) + .map(|geo| Rectangle::from_loc_and_size(geo.loc - wgeo.loc, geo.size)) + .collect::>(); + slog::trace!( + self.logger, + "Rendering window at {:?} with damage {:#?}", + wgeo, + damage + ); + draw_window( + renderer, + frame, + window, + state.render_scale, + loc, + &win_damage, + &self.logger, + )?; window_state(self.id, window).drawn = true; } } @@ -512,8 +545,7 @@ impl Space { .collect(); state.old_damage.push_front(new_damage); - // Return if we actually rendered something - Ok(!damage.is_empty()) + Ok(true) } pub fn send_frames(&self, all: bool, time: u32) { diff --git a/src/desktop/window.rs b/src/desktop/window.rs index 3da2cb4..6e8a5bc 100644 --- a/src/desktop/window.rs +++ b/src/desktop/window.rs @@ -471,6 +471,7 @@ pub fn draw_window( window: &Window, scale: f64, location: Point, + damage: &[Rectangle], log: &slog::Logger, ) -> Result<(), R::Error> where @@ -480,14 +481,30 @@ where T: Texture + 'static, { if let Some(surface) = window.toplevel().get_surface() { - draw_surface_tree(renderer, frame, surface, scale, location, log)?; + draw_surface_tree(renderer, frame, surface, scale, location, damage, log)?; for (popup, p_location) in PopupManager::popups_for_surface(surface) .ok() .into_iter() .flatten() { if let Some(surface) = popup.get_surface() { - draw_surface_tree(renderer, frame, surface, scale, location + p_location, log)?; + let damage = damage + .iter() + .cloned() + .map(|mut geo| { + geo.loc -= p_location; + geo + }) + .collect::>(); + draw_surface_tree( + renderer, + frame, + surface, + scale, + location + p_location, + &damage, + log, + )?; } } }