element/wayland: Add blur background-effect

This commit is contained in:
Victoria Brekenfeld 2026-03-04 19:02:29 +01:00 committed by Victoria Brekenfeld
parent e4c0716951
commit a9bf89d8b2
10 changed files with 747 additions and 5 deletions

View file

@ -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(())
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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)

View file

@ -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<BlurShaders, GlesError> {
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<R: AsGlowRenderer>(renderer: &R) -> Self {
Borrow::<GlesRenderer>::borrow(renderer.glow_renderer())
.egl_context()
.user_data()
.get::<BlurShaders>()
.expect("Custom Shaders not initialized")
.clone()
}
}
type BlurTexture = Mutex<Option<GlesTexture>>;
struct BlurState {
id: Id,
renderer_id: Option<ContextId<GlesTexture>>,
src: Size<f64, Buffer>,
offset: f64,
passes: usize,
region: Vec<Rectangle<i32, Logical>>,
dirty: Arc<AtomicBool>,
}
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<f64, Buffer>,
geometry: Rectangle<f64, Logical>,
scaling_shaders: BlurShaders,
render_shader: GlesTexProgram,
dirty: Arc<AtomicBool>,
region: Vec<Rectangle<i32, Physical>>,
offset: f64,
passes: usize,
uniforms: Vec<Uniform<'static>>,
}
impl BlurElement {
pub fn from_surface<R: ImportAll + AsGlowRenderer>(
renderer: &mut R,
states: &SurfaceData,
geometry: Rectangle<f64, Logical>,
output_scale: f64,
radii: [u8; 4],
) -> Result<Option<Self>, R::Error> {
let mut blur_region_state = states.cached_state.get::<ComputedBlurRegionCachedState>();
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::<f64, Physical>::new(radius, radius);
extended_geo.size += Size::<f64, Physical>::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<BlurState>, _>(|| Mutex::new(BlurState::new()));
Ok(Some(Self::from_state(
renderer,
extended_geo.to_logical(output_scale),
&region,
output_scale,
&mut *state.lock().unwrap(),
uniforms,
)?))
}
fn from_state<R: AsGlowRenderer>(
renderer: &mut R,
geometry: Rectangle<f64, Logical>,
region: &Vec<Rectangle<i32, Logical>>,
output_scale: f64,
state: &mut BlurState,
uniforms: Vec<Uniform<'static>>,
) -> Result<Self, R::Error> {
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<f64, Buffer> {
Rectangle::from_size(self.src)
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
self.geometry.to_physical_precise_round(scale)
}
fn transform(&self) -> Transform {
Transform::Normal
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> DamageSet<i32, Physical> {
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<R: Renderer + AsGlowRenderer> RenderElement<R> for BlurElement {
fn capture_framebuffer(
&self,
frame: &mut <R>::Frame<'_, '_>,
src: Rectangle<f64, Buffer>,
dst: Rectangle<i32, Physical>,
cache: &UserDataMap,
) -> Result<(), <R>::Error> {
let glow_frame = <R as AsGlowRenderer>::glow_frame_mut(frame);
let gles_frame = BorrowMut::<GlesFrame<'_, '_>>::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<f64, Buffer>,
dst: Rectangle<i32, Physical>,
damage: &[Rectangle<i32, Physical>],
opaque_regions: &[Rectangle<i32, Physical>],
cache: Option<&UserDataMap>,
) -> Result<(), R::Error> {
let glow_frame = <R as AsGlowRenderer>::glow_frame_mut(frame);
let damage = self
.region
.iter()
.flat_map(|rect| damage.iter().flat_map(|r| r.intersection(*rect)))
.collect::<Vec<_>>();
let cache = cache.expect("Framebuffer element without cache?");
let Some(texture) = cache.get::<BlurTexture>() else {
return Err(R::from_gles_error(GlesError::BlitError));
};
let texture_ref = texture.lock().unwrap();
BorrowMut::<GlesFrame<'_, '_>>::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<f64, Buffer>,
dst: Rectangle<i32, Physical>,
transform: Transform,
to_texture: &mut GlesTexture,
) -> Result<SyncPoint, GlesError> {
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(())
}

View file

@ -111,6 +111,7 @@ where
transpose: false,
},
),
Uniform::new("noise", UniformValue::_1f(0.0)),
];
Self {

View file

@ -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<R> where R: AsGlowRenderer + ImportAll;
Blur=BlurElement,
Clipped=ClippedSurfaceRenderElement<R>,
Wayland=WaylandSurfaceRenderElement<R>,
}
@ -67,6 +70,7 @@ pub fn push_render_elements_from_surface_tree<R>(
let mut location = *location;
let kind = kind.eval(states);
let data = states.data_map.get::<RendererSurfaceStateUserData>();
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<R>(
renderer, surface, states, location, alpha, kind,
) {
Ok(Some(surface)) => {
blur = BlurElement::from_surface(
renderer, states, geometry, scale.x, radii,
);
let elem: SurfaceRenderElement<R> = if radii.iter().any(|r| *r != 0)
&& should_clip
&& ClippedSurfaceRenderElement::will_clip(
@ -113,6 +120,16 @@ pub fn push_render_elements_from_surface_tree<R>(
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,
);

View file

@ -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::<Self>(dh);
let keyboard_layout_state = KeyboardLayoutState::new::<State, _>(&dh, client_not_sandboxed);
let background_effect_state = BackgroundEffectState::new::<Self>(dh);
let idle_notifier_state = IdleNotifierState::<Self>::new(dh, handle.clone());
let idle_inhibit_manager_state = IdleInhibitManagerState::new::<State>(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,

View file

@ -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<Vec<Rectangle<i32, Logical>>>,
}
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::<ComputedBlurRegionCachedState>();
blur_state.pending().blur_region = Some({
let (added, subtracted) = region
.rects
.iter()
.cloned()
.partition::<Vec<_>, _>(|(op, _)| matches!(op, RectangleKind::Add));
let added = added.into_iter().map(|(_, rect)| rect).collect::<Vec<_>>();
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::<ComputedBlurRegionCachedState>();
blur_state.pending().blur_region.take();
})
}
}

View file

@ -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;