diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 717fe54e..f737afe1 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -19,6 +19,7 @@ use crate::{ shadow::{SHADOW_SHADER, ShadowShader}, wayland::{ SurfaceRenderElement, + blur_effect::BlurShaders, clipped_surface::{CLIPPING_SHADER, ClippingShader}, push_render_elements_from_surface_tree, }, @@ -425,6 +426,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { UniformName::new("geo_size", UniformType::_2f), UniformName::new("corner_radius", UniformType::_4f), UniformName::new("input_to_geo", UniformType::Matrix3x3), + UniformName::new("noise", UniformType::_1f), ], )?; let shadow_shader = renderer.compile_custom_pixel_shader( @@ -440,6 +442,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { UniformName::new("window_corner_radius", UniformType::_4f), ], )?; + let blur_shaders = BlurShaders::compile(renderer)?; let egl_context = renderer.egl_context(); egl_context @@ -457,6 +460,7 @@ pub fn init_shaders(renderer: &mut GlesRenderer) -> Result<(), GlesError> { egl_context .user_data() .insert_if_missing(|| ShadowShader(shadow_shader)); + egl_context.user_data().insert_if_missing(|| blur_shaders); Ok(()) } diff --git a/src/backend/render/shaders/blur_downsample.frag b/src/backend/render/shaders/blur_downsample.frag new file mode 100644 index 00000000..1fa24beb --- /dev/null +++ b/src/backend/render/shaders/blur_downsample.frag @@ -0,0 +1,26 @@ +#version 100 + +//_DEFINES_ + +precision highp float; + +varying vec2 v_coords; + +uniform sampler2D tex; +uniform vec2 half_pixel; +uniform float offset; + +uniform float alpha; +#if defined(DEBUG_FLAGS) +uniform float tint; +#endif + +void main() { + vec4 sum = texture2D(tex, v_coords) * 4.0; + sum += texture2D(tex, v_coords - half_pixel * offset); + sum += texture2D(tex, v_coords + half_pixel * offset); + sum += texture2D(tex, v_coords + vec2(half_pixel.x, -half_pixel.y) * offset); + sum += texture2D(tex, v_coords - vec2(half_pixel.x, -half_pixel.y) * offset); + + gl_FragColor = sum / sum.a; +} diff --git a/src/backend/render/shaders/blur_upsample.frag b/src/backend/render/shaders/blur_upsample.frag new file mode 100644 index 00000000..ac026d19 --- /dev/null +++ b/src/backend/render/shaders/blur_upsample.frag @@ -0,0 +1,27 @@ +#version 100 + +precision highp float; + +varying vec2 v_coords; + +uniform sampler2D tex; +uniform vec2 half_pixel; +uniform float offset; + +uniform float alpha; +#if defined(DEBUG_FLAGS) +uniform float tint; +#endif + +void main() { + vec4 sum = texture2D(tex, v_coords + vec2(-half_pixel.x * 2.0, 0.0) * offset); + sum += texture2D(tex, v_coords + vec2(-half_pixel.x, half_pixel.y) * offset) * 2.0; + sum += texture2D(tex, v_coords + vec2(0.0, half_pixel.y * 2.0) * offset); + sum += texture2D(tex, v_coords + vec2(half_pixel.x, half_pixel.y) * offset) * 2.0; + sum += texture2D(tex, v_coords + vec2(half_pixel.x * 2.0, 0.0) * offset); + sum += texture2D(tex, v_coords + vec2(half_pixel.x, -half_pixel.y) * offset) * 2.0; + sum += texture2D(tex, v_coords + vec2(0.0, -half_pixel.y * 2.0) * offset); + sum += texture2D(tex, v_coords + vec2(-half_pixel.x, -half_pixel.y) * offset) * 2.0; + + gl_FragColor = sum / sum.a; +} diff --git a/src/backend/render/shaders/clipped_surface.frag b/src/backend/render/shaders/clipped_surface.frag index dd863d23..d99d1a56 100644 --- a/src/backend/render/shaders/clipped_surface.frag +++ b/src/backend/render/shaders/clipped_surface.frag @@ -25,6 +25,7 @@ uniform float tint; uniform vec2 geo_size; uniform vec4 corner_radius; uniform mat3 input_to_geo; +uniform float noise; float rounding_alpha(vec2 coords, vec2 size) { vec2 center; @@ -51,15 +52,26 @@ float rounding_alpha(vec2 coords, vec2 size) { return 1.0 - smoothstep(radius - half_px, radius + half_px, dist); } -void main() { - vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0); +float hash(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * 727.727); + p3 += dot(p3, p3.xyz + 33.33); + return fract((p3.x + p3.y) * p3.z); +} + +void main() { + if (alpha <= 0.0) { + discard; + } - // Sample the texture. vec4 color = texture2D(tex, v_coords); #if defined(NO_ALPHA) color = vec4(color.rgb, 1.0); #endif + if (color.a <= 0.0) { + discard; + } + vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0); if (coords_geo.x < 0.0 || 1.0 < coords_geo.x || coords_geo.y < 0.0 || 1.0 < coords_geo.y) { // Clip outside geometry. color = vec4(0.0); @@ -68,8 +80,20 @@ void main() { color = color * rounding_alpha(coords_geo.xy * geo_size, geo_size); } + if (color.a <= 0.0) { + discard; + } + + if (noise > 0.0) { + // Add noise fx + // This can be used to achieve a glass look + float noiseHash = hash(v_coords); + float noiseAmount = (mod(noiseHash, 1.0) - 0.5); + color.rgb += noiseAmount * noise; + } + // Apply final alpha and tint. - color = color * alpha; + color *= alpha; #if defined(DEBUG_FLAGS) if (tint == 1.0) diff --git a/src/backend/render/wayland/blur_effect.rs b/src/backend/render/wayland/blur_effect.rs new file mode 100644 index 00000000..895cd19e --- /dev/null +++ b/src/backend/render/wayland/blur_effect.rs @@ -0,0 +1,578 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + sync::{ + Arc, Mutex, + atomic::{AtomicBool, Ordering}, + }, +}; + +use cgmath::{Matrix3, SquareMatrix, Vector2}; +use smithay::{ + backend::{ + allocator::Fourcc, + renderer::{ + Bind, BlitFrame, Color32F, ContextId, Frame, FrameContext, ImportAll, Offscreen, + Renderer, Texture, TextureFilter, + element::{Element, Id, Kind, RenderElement}, + gles::{ + GlesError, GlesFrame, GlesRenderer, GlesTexProgram, GlesTexture, Uniform, + UniformName, UniformType, UniformValue, ffi, + }, + sync::SyncPoint, + utils::{CommitCounter, DamageSet}, + }, + }, + utils::{ + Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform, user_data::UserDataMap, + }, + wayland::compositor::SurfaceData, +}; + +use crate::{ + backend::render::{element::AsGlowRenderer, wayland::clipped_surface::ClippingShader}, + wayland::handlers::background_effect::ComputedBlurRegionCachedState, +}; + +pub static BLUR_DOWNSAMPLE_SHADER: &str = include_str!("../shaders/blur_downsample.frag"); +pub static BLUR_UPSAMPLE_SHADER: &str = include_str!("../shaders/blur_upsample.frag"); + +const PASSES: usize = 3; +const OFFSET: f64 = 1.1; +const NOISE: f32 = 0.03; + +#[derive(Debug, Clone)] +pub struct BlurShaders { + down: GlesTexProgram, + up: GlesTexProgram, +} + +impl BlurShaders { + pub fn compile(renderer: &mut GlesRenderer) -> Result { + let up = renderer.compile_custom_texture_shader( + BLUR_UPSAMPLE_SHADER, + &[ + UniformName::new("half_pixel", UniformType::_2f), + UniformName::new("offset", UniformType::_1f), + ], + )?; + let down = renderer.compile_custom_texture_shader( + BLUR_DOWNSAMPLE_SHADER, + &[ + UniformName::new("half_pixel", UniformType::_2f), + UniformName::new("offset", UniformType::_1f), + ], + )?; + + Ok(BlurShaders { up, down }) + } + + pub fn get(renderer: &R) -> Self { + Borrow::::borrow(renderer.glow_renderer()) + .egl_context() + .user_data() + .get::() + .expect("Custom Shaders not initialized") + .clone() + } +} + +type BlurTexture = Mutex>; + +struct BlurState { + id: Id, + renderer_id: Option>, + src: Size, + offset: f64, + passes: usize, + region: Vec>, + dirty: Arc, +} + +unsafe impl Send for BlurState {} +unsafe impl Sync for BlurState {} + +impl BlurState { + fn new() -> Self { + BlurState { + id: Id::new(), + renderer_id: None, + src: Size::new(0., 0.), + offset: 0., + passes: 0, + region: Vec::new(), + dirty: Arc::new(AtomicBool::new(true)), + } + } +} + +pub struct BlurElement { + id: Id, + src: Size, + geometry: Rectangle, + scaling_shaders: BlurShaders, + render_shader: GlesTexProgram, + dirty: Arc, + region: Vec>, + offset: f64, + passes: usize, + uniforms: Vec>, +} + +impl BlurElement { + pub fn from_surface( + renderer: &mut R, + states: &SurfaceData, + geometry: Rectangle, + output_scale: f64, + radii: [u8; 4], + ) -> Result, R::Error> { + let mut blur_region_state = states.cached_state.get::(); + let Some(region) = blur_region_state.current().blur_region.as_ref() else { + return Ok(None); + }; + + let geo = geometry.to_physical_precise_round(output_scale); + let mut extended_geo = geo; + let radius = OFFSET * 2.0f64.powf(PASSES as f64); + extended_geo.loc -= Point::::new(radius, radius); + extended_geo.size += Size::::new(radius, radius).upscale(2.); + + // compute input_to_geo so that it crops the extended capture radius + let geo_scale = { + let Scale { x, y } = geo.size / extended_geo.size; + Matrix3::from_nonuniform_scale(x as f32, y as f32) + .invert() + .unwrap() + }; + let geo_translation = { + let offset = geo.loc - extended_geo.loc; + Matrix3::from_translation(Vector2::new( + (offset.x / extended_geo.size.w) as f32, + (offset.y / extended_geo.size.h) as f32, + )) + .invert() + .unwrap() + }; + let input_to_geo = geo_scale * geo_translation; + + let uniforms = vec![ + Uniform::new("geo_size", (geometry.size.w as f32, geometry.size.h as f32)), + Uniform::new( + "corner_radius", + [ + radii[3] as f32, + radii[1] as f32, + radii[0] as f32, + radii[2] as f32, + ], + ), + Uniform::new( + "input_to_geo", + UniformValue::Matrix3x3 { + matrices: vec![*AsRef::<[f32; 9]>::as_ref(&input_to_geo)], + transpose: false, + }, + ), + Uniform::new("noise", UniformValue::_1f(NOISE)), + ]; + + let state = states + .data_map + .get_or_insert_threadsafe::, _>(|| Mutex::new(BlurState::new())); + + Ok(Some(Self::from_state( + renderer, + extended_geo.to_logical(output_scale), + ®ion, + output_scale, + &mut *state.lock().unwrap(), + uniforms, + )?)) + } + + fn from_state( + renderer: &mut R, + geometry: Rectangle, + region: &Vec>, + output_scale: f64, + state: &mut BlurState, + uniforms: Vec>, + ) -> Result { + let renderer_id = renderer.glow_renderer().context_id(); + let src = geometry.size.to_buffer(output_scale, Transform::Normal); + + let dirty = !(state + .renderer_id + .as_ref() + .is_some_and(|id| id == &renderer_id) + && &state.region == region + && state.src == src); + state.renderer_id = Some(renderer_id); + state.offset = OFFSET * output_scale; // we want the blur result to be stable across scaling; + state.passes = PASSES; + state.region = region.clone(); + state.src = src; + if dirty { + state.dirty.store(dirty, Ordering::Release); + } + + Ok(BlurElement { + id: state.id.clone(), + src, + geometry, + scaling_shaders: BlurShaders::get(renderer), + render_shader: ClippingShader::get(renderer), + dirty: state.dirty.clone(), + offset: state.offset, + passes: state.passes, + region: region + .iter() + .map(|rect| rect.to_physical_precise_round(output_scale)) + .collect(), + uniforms, + }) + } +} + +impl Element for BlurElement { + fn id(&self) -> &Id { + &self.id + } + + fn current_commit(&self) -> CommitCounter { + Default::default() + } + + fn src(&self) -> Rectangle { + Rectangle::from_size(self.src) + } + + fn geometry(&self, scale: Scale) -> Rectangle { + self.geometry.to_physical_precise_round(scale) + } + + fn transform(&self) -> Transform { + Transform::Normal + } + + fn damage_since( + &self, + scale: Scale, + commit: Option, + ) -> DamageSet { + if commit.is_none() || self.dirty.load(Ordering::Acquire) { + DamageSet::from_slice(&[self.geometry.to_physical_precise_round(scale)]) + } else { + DamageSet::default() + } + } + + fn alpha(&self) -> f32 { + 1.0 + } + + fn kind(&self) -> Kind { + Kind::default() + } + + fn is_framebuffer_effect(&self) -> bool { + true + } +} + +impl RenderElement for BlurElement { + fn capture_framebuffer( + &self, + frame: &mut ::Frame<'_, '_>, + src: Rectangle, + dst: Rectangle, + cache: &UserDataMap, + ) -> Result<(), ::Error> { + let glow_frame = ::glow_frame_mut(frame); + let gles_frame = BorrowMut::>::borrow_mut(glow_frame); + let transform = gles_frame.transformation(); + let tex_size = self.src.to_i32_round(); + + let texture_ref = cache.get_or_insert_threadsafe(BlurTexture::default); + let mut texture_entry = texture_ref.lock().unwrap(); + if self.dirty.swap(false, Ordering::Acquire) { + texture_entry.take(); + } + + let mut renderer = gles_frame.renderer(); + if texture_entry.is_none() { + *texture_entry = Some( + renderer + .as_mut() + .create_buffer(Fourcc::Abgr8888, tex_size) + .map_err(R::from_gles_error)?, + ); + } + let texture = texture_entry.as_mut().unwrap(); + let mut off_texture = renderer + .as_mut() + .create_buffer(Fourcc::Abgr8888, tex_size) + .map_err(R::from_gles_error)?; + std::mem::drop(renderer); + + let sync = blit_from_active_fb(gles_frame, src, dst, transform, texture) + .map_err(R::from_gles_error)?; + gles_frame.wait(&sync).map_err(R::from_gles_error)?; + + let mut textures = [texture, &mut off_texture]; + render_blur( + gles_frame.renderer().as_mut(), + &self.scaling_shaders, + &mut textures, + self.offset, + self.passes, + ) + .map_err(R::from_gles_error)?; + + Ok(()) + } + + fn draw( + &self, + frame: &mut R::Frame<'_, '_>, + src: Rectangle, + dst: Rectangle, + damage: &[Rectangle], + opaque_regions: &[Rectangle], + cache: Option<&UserDataMap>, + ) -> Result<(), R::Error> { + let glow_frame = ::glow_frame_mut(frame); + let damage = self + .region + .iter() + .flat_map(|rect| damage.iter().flat_map(|r| r.intersection(*rect))) + .collect::>(); + let cache = cache.expect("Framebuffer element without cache?"); + let Some(texture) = cache.get::() else { + return Err(R::from_gles_error(GlesError::BlitError)); + }; + let texture_ref = texture.lock().unwrap(); + + BorrowMut::>::borrow_mut(glow_frame) + .render_texture_from_to( + texture_ref + .as_ref() + .ok_or(R::from_gles_error(GlesError::BlitError))?, + src, + dst, + &damage, + opaque_regions, + Transform::Normal, + 1.0, + Some(&self.render_shader), + &self.uniforms, + ) + .map_err(R::from_gles_error) + } +} + +fn blit_from_active_fb( + frame: &mut GlesFrame<'_, '_>, + src: Rectangle, + dst: Rectangle, + transform: Transform, + to_texture: &mut GlesTexture, +) -> Result { + if transform != Transform::Normal { + let tex_size = to_texture + .size() + .to_logical(1, Transform::Normal) + .to_physical(1); + let mut renderer = frame.renderer(); + let mut tmp_texture = renderer.as_mut().create_buffer( + Fourcc::Abgr8888, + dst.size.to_logical(1).to_buffer(1, Transform::Normal), + )?; + let mut fb_tmp = renderer.as_mut().bind(&mut tmp_texture)?; + let mut fb = renderer.as_mut().bind(to_texture)?; + std::mem::drop(renderer); + + frame.blit_to( + &mut fb_tmp, + dst, + Rectangle::from_size(dst.size), + TextureFilter::Linear, + )?; + std::mem::drop(fb_tmp); + + let mut renderer = frame.renderer(); + let mut frame = renderer + .as_mut() + .render(&mut fb, tex_size, Transform::Normal)?; + Frame::render_texture_from_to( + &mut frame, + &tmp_texture, + Rectangle::from_size( + dst.size + .to_logical(1) + .to_buffer(1, Transform::Normal) + .to_f64(), + ), + src.to_logical(1., Transform::Normal, &src.size) + .to_physical(1.) + .to_i32_round(), + &[Rectangle::from_size(dst.size)], + &[Rectangle::from_size(dst.size)], + transform, + 1.0, + )?; + std::mem::drop(tmp_texture); + frame.finish() + } else { + let mut fb = frame.renderer().as_mut().bind(to_texture)?; + + frame + .blit_to( + &mut fb, + dst, + src.to_logical(1., Transform::Normal, &src.size) + .to_physical(1.) + .to_i32_round(), + TextureFilter::Linear, + ) + .map(|_| SyncPoint::signaled()) + } +} + +fn render_blur( + renderer: &mut GlesRenderer, + shaders: &BlurShaders, + textures: &mut [&mut GlesTexture; 2], + offset: f64, + passes: usize, +) -> Result<(), GlesError> { + for i in 0..passes { + let tex_size = textures[0].size(); + let [src_tex, target_tex] = textures; + let mut fb = renderer.bind(*target_tex)?; + + let adjusted_tex_size = tex_size.downscale(1 << i); + let target_tex_size = tex_size + .downscale(1 << i + 1) + .to_logical(1, Transform::Normal) + .to_physical(1); + let half_pixel = [ + 0.5 / (adjusted_tex_size.w as f32), + 0.5 / (adjusted_tex_size.h as f32), + ]; + + let mut frame = renderer.render( + &mut fb, + tex_size.to_logical(1, Transform::Normal).to_physical(1), + Transform::Normal, + )?; + frame.clear( + Color32F::new(0., 0., 0., 0.), + &[Rectangle::from_size( + tex_size.to_logical(1, Transform::Normal).to_physical(1), + )], + )?; + frame.with_context(|gl| unsafe { + 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, + ); + })?; + frame.render_texture_from_to( + src_tex, + Rectangle::from_size(adjusted_tex_size.to_f64()), + Rectangle::from_size(target_tex_size), + &[Rectangle::from_size(target_tex_size)], + &[Rectangle::from_size(target_tex_size)], + Transform::Normal, + 1.0, + Some(&shaders.down), + &[ + Uniform::new("half_pixel", half_pixel), + Uniform::new("offset", (offset / (1 << i) as f64) as f32), + ], + )?; + frame.with_context(|gl| unsafe { + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::REPEAT as i32); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::REPEAT as i32); + })?; + let sync = frame.finish()?; + std::mem::drop(fb); + renderer.wait(&sync)?; + + textures.swap(0, 1); + } + + for i in 0..passes { + let tex_size = textures[0].size(); + let [src_tex, target_tex] = textures; + let mut fb = renderer.bind(*target_tex)?; + + let adjusted_tex_size = tex_size.downscale(1 << (passes - i)); + let target_tex_size = tex_size + .downscale(1 << (passes - i - 1)) + .to_logical(1, Transform::Normal) + .to_physical(1); + let half_pixel = [ + 0.5 / (adjusted_tex_size.w as f32), + 0.5 / (adjusted_tex_size.h as f32), + ]; + + let mut frame = renderer.render( + &mut fb, + tex_size.to_logical(1, Transform::Normal).to_physical(1), + Transform::Normal, + )?; + frame.clear( + Color32F::new(0., 0., 0., 0.), + &[Rectangle::from_size( + tex_size.to_logical(1, Transform::Normal).to_physical(1), + )], + )?; + frame.with_context(|gl| unsafe { + 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, + ); + })?; + frame.render_texture_from_to( + src_tex, + Rectangle::from_size(adjusted_tex_size.to_f64()), + Rectangle::from_size(target_tex_size), + &[Rectangle::from_size(target_tex_size)], + &[Rectangle::from_size(target_tex_size)], + Transform::Normal, + 1.0, + Some(&shaders.up), + &[ + Uniform::new("half_pixel", half_pixel), + Uniform::new("offset", (offset / (1 << (passes - i)) as f64) as f32), + ], + )?; + frame.with_context(|gl| unsafe { + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::REPEAT as i32); + gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::REPEAT as i32); + })?; + let sync = frame.finish()?; + std::mem::drop(fb); + renderer.wait(&sync)?; + + textures.swap(0, 1); + } + + // textures always end up the right way around with `self.texture` containing our final render, + // since we render PASSES * 2 (downscale and upscale), so the number of swaps is always even. + Ok(()) +} diff --git a/src/backend/render/wayland/clipped_surface.rs b/src/backend/render/wayland/clipped_surface.rs index 37eee7f8..92e64ad0 100644 --- a/src/backend/render/wayland/clipped_surface.rs +++ b/src/backend/render/wayland/clipped_surface.rs @@ -111,6 +111,7 @@ where transpose: false, }, ), + Uniform::new("noise", UniformValue::_1f(0.0)), ]; Self { diff --git a/src/backend/render/wayland/mod.rs b/src/backend/render/wayland/mod.rs index d766552a..b9e07a80 100644 --- a/src/backend/render/wayland/mod.rs +++ b/src/backend/render/wayland/mod.rs @@ -12,13 +12,16 @@ use smithay::{ use tracing::warn; use crate::backend::render::{ - element::AsGlowRenderer, wayland::clipped_surface::ClippedSurfaceRenderElement, + element::AsGlowRenderer, + wayland::{blur_effect::BlurElement, clipped_surface::ClippedSurfaceRenderElement}, }; +pub mod blur_effect; pub mod clipped_surface; render_elements! { pub SurfaceRenderElement where R: AsGlowRenderer + ImportAll; + Blur=BlurElement, Clipped=ClippedSurfaceRenderElement, Wayland=WaylandSurfaceRenderElement, } @@ -67,6 +70,7 @@ pub fn push_render_elements_from_surface_tree( let mut location = *location; let kind = kind.eval(states); let data = states.data_map.get::(); + let mut blur = Ok(None); if let Some(data) = data { let has_view = if let Some(view) = data.lock().unwrap().view() { @@ -82,6 +86,9 @@ pub fn push_render_elements_from_surface_tree( renderer, surface, states, location, alpha, kind, ) { Ok(Some(surface)) => { + blur = BlurElement::from_surface( + renderer, states, geometry, scale.x, radii, + ); let elem: SurfaceRenderElement = if radii.iter().any(|r| *r != 0) && should_clip && ClippedSurfaceRenderElement::will_clip( @@ -113,6 +120,16 @@ pub fn push_render_elements_from_surface_tree( if surface == main_surface { passed_main = true; } + + if let Ok(Some(elem)) = blur { + if let Some(push_below) = push_below.as_mut() + && passed_main + { + push_below(elem.into()); + } else { + push_above(elem.into()); + } + } }, |_, _, _| true, ); diff --git a/src/state.rs b/src/state.rs index 68824ea7..f377010c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -73,6 +73,7 @@ use smithay::{ utils::{Clock, Monotonic, Point}, wayland::{ alpha_modifier::AlphaModifierState, + background_effect::BackgroundEffectState, compositor::{CompositorClientState, CompositorState, SurfaceData}, cursor_shape::CursorShapeManagerState, dmabuf::{DmabufFeedback, DmabufGlobal, DmabufState}, @@ -281,6 +282,7 @@ pub struct Common { pub a11y_state: A11yState, pub dbus_state: DBusState, pub keyboard_layout_state: KeyboardLayoutState, + pub background_effect_state: BackgroundEffectState, // shell-related wayland state pub xdg_shell_state: XdgShellState, @@ -686,6 +688,8 @@ impl State { FixesState::new::(dh); let keyboard_layout_state = KeyboardLayoutState::new::(&dh, client_not_sandboxed); + let background_effect_state = BackgroundEffectState::new::(dh); + let idle_notifier_state = IdleNotifierState::::new(dh, handle.clone()); let idle_inhibit_manager_state = IdleInhibitManagerState::new::(dh); let idle_inhibiting_surfaces = HashSet::new(); @@ -789,6 +793,7 @@ impl State { xdg_activation_state, xdg_foreign_state, workspace_state, + background_effect_state, a11y_state, xwayland_scale: None, xwayland_state: None, diff --git a/src/wayland/handlers/background_effect.rs b/src/wayland/handlers/background_effect.rs new file mode 100644 index 00000000..28682e03 --- /dev/null +++ b/src/wayland/handlers/background_effect.rs @@ -0,0 +1,59 @@ +use smithay::{ + reexports::wayland_server::{DisplayHandle, protocol::wl_surface::WlSurface}, + utils::{Logical, Rectangle}, + wayland::{ + background_effect::{Capability, ExtBackgroundEffectHandler}, + compositor::{Cacheable, RectangleKind, RegionAttributes, with_states}, + }, +}; + +use crate::state::State; + +#[derive(Debug, Clone, Default)] +pub struct ComputedBlurRegionCachedState { + /// Region of the surface that will have its background blurred. + pub blur_region: Option>>, +} + +impl Cacheable for ComputedBlurRegionCachedState { + fn commit(&mut self, _dh: &DisplayHandle) -> Self { + self.clone() + } + + fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) { + *into = self; + } +} + +impl ExtBackgroundEffectHandler for State { + fn capabilities(&self) -> Capability { + Capability::Blur + } + + fn set_blur_region(&mut self, surface: WlSurface, region: RegionAttributes) { + with_states(&surface, |states| { + let mut blur_state = states.cached_state.get::(); + + blur_state.pending().blur_region = Some({ + let (added, subtracted) = region + .rects + .iter() + .cloned() + .partition::, _>(|(op, _)| matches!(op, RectangleKind::Add)); + let added = added.into_iter().map(|(_, rect)| rect).collect::>(); + Rectangle::subtract_rects_many_in_place( + added, + subtracted.into_iter().map(|(_, rect)| rect), + ) + }) + }) + } + + fn unset_blur_region(&mut self, surface: WlSurface) { + with_states(&surface, |states| { + let mut blur_state = states.cached_state.get::(); + + blur_state.pending().blur_region.take(); + }) + } +} diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index c6461a2e..651eadc9 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pub mod a11y; +pub mod background_effect; pub mod buffer; pub mod compositor; pub mod corner_radius;