mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Implement ext-background-effect protocol
This commit is contained in:
@@ -1079,7 +1079,8 @@ pub struct BackgroundEffect {
|
||||
|
||||
/// Whether to blur the background.
|
||||
///
|
||||
/// - `None`: no blur
|
||||
/// - `None`: blur when the window/layer requests it (e.g. through ext-background-effect
|
||||
/// protocol)
|
||||
/// - `Some(false)`: never blur
|
||||
/// - `Some(true)`: always blur
|
||||
pub blur: Option<bool>,
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use smithay::delegate_background_effect;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Rectangle};
|
||||
use smithay::wayland::background_effect::{
|
||||
self, BackgroundEffectSurfaceCachedState, ExtBackgroundEffectHandler,
|
||||
};
|
||||
use smithay::wayland::compositor::{
|
||||
add_post_commit_hook, with_states, RegionAttributes, SurfaceData,
|
||||
};
|
||||
|
||||
use crate::niri::State;
|
||||
use crate::utils::region::region_to_non_overlapping_rects;
|
||||
|
||||
/// Per-surface cache for processed blur region (non-overlapping rects).
|
||||
#[derive(Default)]
|
||||
struct CachedBlurRegionUserData(Mutex<CachedBlurRegionInner>);
|
||||
|
||||
#[derive(Default)]
|
||||
struct CachedBlurRegionInner {
|
||||
/// Whether a region change is pending to be committed.
|
||||
pending_dirty: bool,
|
||||
/// Whether the region must be recomputed.
|
||||
dirty: bool,
|
||||
/// Whether the post-commit hook has been registered for this surface.
|
||||
hook_registered: bool,
|
||||
/// Cached non-overlapping rects in surface-local coordinates.
|
||||
///
|
||||
/// `None` means there's no blur region.
|
||||
rects: Option<Arc<Vec<Rectangle<i32, Logical>>>>,
|
||||
}
|
||||
|
||||
/// Gets the cached blur region for a surface, lazily recomputing if dirty.
|
||||
pub fn get_cached_blur_region(states: &SurfaceData) -> Option<Arc<Vec<Rectangle<i32, Logical>>>> {
|
||||
let cache = states
|
||||
.data_map
|
||||
.get_or_insert_threadsafe(CachedBlurRegionUserData::default);
|
||||
let mut guard = cache.0.lock().unwrap();
|
||||
|
||||
if guard.dirty {
|
||||
guard.dirty = false;
|
||||
recompute_blur_region(states, &mut guard);
|
||||
}
|
||||
|
||||
guard.rects.clone()
|
||||
}
|
||||
|
||||
fn recompute_blur_region(states: &SurfaceData, inner: &mut CachedBlurRegionInner) {
|
||||
let cached = &states.cached_state;
|
||||
|
||||
let rects = if let Some(arc) = &mut inner.rects {
|
||||
if Arc::strong_count(arc) > 1 {
|
||||
debug!("cloning rects due to non-unique reference");
|
||||
}
|
||||
arc
|
||||
} else {
|
||||
inner.rects.insert(Arc::new(Vec::new()))
|
||||
};
|
||||
let rects = Arc::make_mut(rects);
|
||||
|
||||
if cached.has::<BackgroundEffectSurfaceCachedState>() {
|
||||
let mut guard = cached.get::<BackgroundEffectSurfaceCachedState>();
|
||||
if let Some(region) = &guard.current().blur_region {
|
||||
region_to_non_overlapping_rects(region, rects);
|
||||
} else {
|
||||
inner.rects = None;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
inner.rects = None;
|
||||
}
|
||||
|
||||
fn mark_blur_region_pending_dirty(wl_surface: &WlSurface) {
|
||||
let register_hook = with_states(wl_surface, |states| {
|
||||
let cache = states
|
||||
.data_map
|
||||
.get_or_insert_threadsafe(CachedBlurRegionUserData::default);
|
||||
let mut guard = cache.0.lock().unwrap();
|
||||
guard.pending_dirty = true;
|
||||
|
||||
if guard.hook_registered {
|
||||
false
|
||||
} else {
|
||||
guard.hook_registered = true;
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if register_hook {
|
||||
add_post_commit_hook::<State, _>(wl_surface, |_state, _dh, surface| {
|
||||
with_states(surface, |states| {
|
||||
if let Some(cache) = states.data_map.get::<CachedBlurRegionUserData>() {
|
||||
let mut guard = cache.0.lock().unwrap();
|
||||
if guard.pending_dirty {
|
||||
guard.pending_dirty = false;
|
||||
guard.dirty = true;
|
||||
|
||||
crate::render_helpers::background_effect::damage_surface(states);
|
||||
}
|
||||
} else {
|
||||
error!("unexpected missing CachedBlurRegionUserData");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtBackgroundEffectHandler for State {
|
||||
fn capabilities(&self) -> background_effect::Capability {
|
||||
background_effect::Capability::Blur
|
||||
}
|
||||
|
||||
fn set_blur_region(&mut self, wl_surface: WlSurface, _region: RegionAttributes) {
|
||||
mark_blur_region_pending_dirty(&wl_surface);
|
||||
}
|
||||
|
||||
fn unset_blur_region(&mut self, wl_surface: WlSurface) {
|
||||
mark_blur_region_pending_dirty(&wl_surface);
|
||||
}
|
||||
}
|
||||
delegate_background_effect!(State);
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod background_effect;
|
||||
mod compositor;
|
||||
mod layer_shell;
|
||||
mod xdg_shell;
|
||||
|
||||
@@ -233,6 +233,8 @@ impl MappedLayer {
|
||||
.render(ctx.renderer, location, &mut |elem| push(elem.into()));
|
||||
|
||||
let geometry = Rectangle::new(location, self.block_out_buffer.size());
|
||||
let surface_off = Point::new(0., 0.); // No geometry on layer surfaces.
|
||||
let surface_anim_scale = Scale::from(1.);
|
||||
let radius = self.rules.geometry_corner_radius.unwrap_or_default();
|
||||
background_effect::render_for_tile(
|
||||
ctx.as_gles(),
|
||||
@@ -240,9 +242,12 @@ impl MappedLayer {
|
||||
self.scale,
|
||||
false,
|
||||
surface,
|
||||
surface_off,
|
||||
surface_anim_scale,
|
||||
self.blur_config,
|
||||
radius,
|
||||
self.rules.background_effect,
|
||||
should_block_out,
|
||||
xray_pos,
|
||||
&mut |elem| push(elem.into()),
|
||||
);
|
||||
|
||||
@@ -203,6 +203,7 @@ pub trait LayoutElement {
|
||||
_geometry: Rectangle<f64, Logical>,
|
||||
_scale: f64,
|
||||
_clip_to_geometry: bool,
|
||||
_surface_anim_scale: Scale<f64>,
|
||||
_radius: CornerRadius,
|
||||
_xray_pos: XrayPos,
|
||||
_push: &mut dyn FnMut(BackgroundEffectElement),
|
||||
|
||||
@@ -1282,11 +1282,13 @@ impl<W: LayoutElement> Tile<W> {
|
||||
.render(ctx.renderer, location, &mut |elem| push(elem.into()));
|
||||
}
|
||||
|
||||
let surface_anim_scale = animated_window_size / window_size;
|
||||
self.window.render_background_effect(
|
||||
ctx.as_gles(),
|
||||
area,
|
||||
self.scale,
|
||||
clip_to_geometry,
|
||||
surface_anim_scale,
|
||||
radius,
|
||||
xray_pos,
|
||||
&mut |elem| push(elem.into()),
|
||||
|
||||
@@ -71,6 +71,7 @@ use smithay::utils::{
|
||||
ClockSource, IsAlive as _, Logical, Monotonic, Physical, Point, Rectangle, Scale, Size,
|
||||
Transform, SERIAL_COUNTER,
|
||||
};
|
||||
use smithay::wayland::background_effect::BackgroundEffectState;
|
||||
use smithay::wayland::compositor::{
|
||||
with_states, with_surface_tree_downward, CompositorClientState, CompositorHandler,
|
||||
CompositorState, HookId, SurfaceData, TraversalAction,
|
||||
@@ -280,6 +281,7 @@ pub struct Niri {
|
||||
pub screencopy_state: ScreencopyManagerState,
|
||||
pub output_management_state: OutputManagementManagerState,
|
||||
pub viewporter_state: ViewporterState,
|
||||
pub background_effect_state: BackgroundEffectState,
|
||||
pub xdg_foreign_state: XdgForeignState,
|
||||
pub shm_state: ShmState,
|
||||
pub output_manager_state: OutputManagerState,
|
||||
@@ -2329,6 +2331,7 @@ impl Niri {
|
||||
let screencopy_state =
|
||||
ScreencopyManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
|
||||
let viewporter_state = ViewporterState::new::<State>(&display_handle);
|
||||
let background_effect_state = BackgroundEffectState::new::<State>(&display_handle);
|
||||
let xdg_foreign_state = XdgForeignState::new::<State>(&display_handle);
|
||||
|
||||
let is_tty = matches!(backend, Backend::Tty(_));
|
||||
@@ -2512,6 +2515,7 @@ impl Niri {
|
||||
output_management_state,
|
||||
screencopy_state,
|
||||
viewporter_state,
|
||||
background_effect_state,
|
||||
xdg_foreign_state,
|
||||
text_input_state,
|
||||
input_method_state,
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use niri_config::CornerRadius;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Logical, Rectangle};
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale};
|
||||
use smithay::wayland::compositor::{with_states, SurfaceData};
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
|
||||
use crate::handlers::background_effect::get_cached_blur_region;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::damage::ExtraDamage;
|
||||
use crate::render_helpers::xray::{XrayElement, XrayPos};
|
||||
use crate::render_helpers::RenderCtx;
|
||||
use crate::utils::region::TransformedRegion;
|
||||
use crate::utils::surface_geo;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BackgroundEffect {
|
||||
@@ -46,6 +49,10 @@ impl Options {
|
||||
pub struct RenderParams {
|
||||
/// Geometry of the background effect.
|
||||
pub geometry: Rectangle<f64, Logical>,
|
||||
/// Effect subregion, will be clipped to `geometry`.
|
||||
///
|
||||
/// `subregion.iter()` should return `geometry`-relative rectangles.
|
||||
pub subregion: Option<TransformedRegion>,
|
||||
/// Geometry and radius for clipping in the same coordinate space as `geometry`.
|
||||
pub clip: Option<(Rectangle<f64, Logical>, CornerRadius)>,
|
||||
/// Scale to use for rounding to physical pixels.
|
||||
@@ -80,6 +87,11 @@ impl BackgroundEffect {
|
||||
}
|
||||
}
|
||||
|
||||
/// Damage the background effect, for example when a blur subregion changes.
|
||||
pub fn damage(&mut self) {
|
||||
self.damage.damage_all();
|
||||
}
|
||||
|
||||
pub fn update_config(&mut self, config: niri_config::Blur) {
|
||||
if self.blur_config == config {
|
||||
return;
|
||||
@@ -93,9 +105,17 @@ impl BackgroundEffect {
|
||||
&mut self,
|
||||
corner_radius: CornerRadius,
|
||||
effect: niri_config::BackgroundEffect,
|
||||
has_blur_region: bool,
|
||||
) {
|
||||
// If the surface explicitly requests a blur region, default blur to true.
|
||||
let blur = if has_blur_region {
|
||||
effect.blur != Some(false)
|
||||
} else {
|
||||
effect.blur == Some(true)
|
||||
};
|
||||
|
||||
let mut options = Options {
|
||||
blur: effect.blur == Some(true),
|
||||
blur,
|
||||
xray: effect.xray == Some(true),
|
||||
noise: effect.noise,
|
||||
saturation: effect.saturation,
|
||||
@@ -171,6 +191,62 @@ impl BackgroundEffect {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_params_for_tile(
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
scale: f64,
|
||||
clip_to_geometry: bool,
|
||||
block_out: bool,
|
||||
blur_region: Option<Arc<Vec<Rectangle<i32, Logical>>>>,
|
||||
surface_geo: Rectangle<f64, Logical>,
|
||||
surface_anim_scale: Scale<f64>,
|
||||
) -> Option<RenderParams> {
|
||||
// Effects not requested by the surface itself are drawn to match the geometry.
|
||||
let mut clip = true;
|
||||
|
||||
let mut effect_geometry = geometry;
|
||||
let mut subregion = None;
|
||||
if let Some(rects) = blur_region {
|
||||
if rects.is_empty() {
|
||||
// Surface has a set, but empty blur region.
|
||||
return None;
|
||||
} else {
|
||||
// If the surface itself requests the effects, apply different defaults.
|
||||
clip = clip_to_geometry;
|
||||
|
||||
// Use geometry-shaped blur for blocked-out windows to avoid unintentionally
|
||||
// leaking any surface shapes. We render those windows as geometry-shaped solid
|
||||
// rectangles anyway.
|
||||
if block_out {
|
||||
clip = true;
|
||||
} else {
|
||||
let mut surface_geo = surface_geo.upscale(surface_anim_scale);
|
||||
surface_geo.loc += geometry.loc;
|
||||
|
||||
subregion = Some(TransformedRegion {
|
||||
rects,
|
||||
scale: surface_anim_scale,
|
||||
offset: surface_geo.loc,
|
||||
});
|
||||
|
||||
surface_geo = surface_geo
|
||||
.to_physical_precise_round(scale)
|
||||
.to_logical(scale);
|
||||
effect_geometry = surface_geo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This corner radius is reset to self.corner_radius in render().
|
||||
let clip = clip.then_some((geometry, CornerRadius::default()));
|
||||
|
||||
Some(RenderParams {
|
||||
geometry: effect_geometry,
|
||||
subregion,
|
||||
clip,
|
||||
scale,
|
||||
})
|
||||
}
|
||||
|
||||
/// Per-surface background effect stored in its data map.
|
||||
struct SurfaceBackgroundEffect(Mutex<BackgroundEffect>);
|
||||
|
||||
@@ -182,6 +258,12 @@ impl SurfaceBackgroundEffect {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn damage_surface(states: &SurfaceData) {
|
||||
if let Some(effect) = states.data_map.get::<SurfaceBackgroundEffect>() {
|
||||
effect.0.lock().unwrap().damage();
|
||||
}
|
||||
}
|
||||
|
||||
// Silence, Clippy
|
||||
// A Smithay user is talking
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -191,9 +273,12 @@ pub fn render_for_tile(
|
||||
scale: f64,
|
||||
clip_to_geometry: bool,
|
||||
surface: &WlSurface,
|
||||
surface_off: Point<f64, Logical>,
|
||||
surface_anim_scale: Scale<f64>,
|
||||
blur_config: niri_config::Blur,
|
||||
radius: CornerRadius,
|
||||
effect: niri_config::BackgroundEffect,
|
||||
should_block_out: bool,
|
||||
xray_pos: XrayPos,
|
||||
push: &mut dyn FnMut(BackgroundEffectElement),
|
||||
) {
|
||||
@@ -201,19 +286,29 @@ pub fn render_for_tile(
|
||||
let background_effect = SurfaceBackgroundEffect::get(states);
|
||||
let mut background_effect = background_effect.0.lock().unwrap();
|
||||
|
||||
let blur_region = get_cached_blur_region(states);
|
||||
let has_blur_region = blur_region.as_ref().is_some_and(|r| !r.is_empty());
|
||||
|
||||
background_effect.update_config(blur_config);
|
||||
background_effect.update_render_elements(radius, effect);
|
||||
background_effect.update_render_elements(radius, effect, has_blur_region);
|
||||
|
||||
if !background_effect.is_visible() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Effects not requested by the surface itself are drawn to match the geometry.
|
||||
let _ = clip_to_geometry;
|
||||
let params = RenderParams {
|
||||
let mut surface_geo = surface_geo(states).unwrap_or_default().to_f64();
|
||||
surface_geo.loc += surface_off;
|
||||
|
||||
let Some(params) = render_params_for_tile(
|
||||
geometry,
|
||||
clip: Some((geometry, CornerRadius::default())),
|
||||
scale,
|
||||
clip_to_geometry,
|
||||
should_block_out,
|
||||
blur_region,
|
||||
surface_geo,
|
||||
surface_anim_scale,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let xray_pos = xray_pos.offset(params.geometry.loc - geometry.loc);
|
||||
|
||||
@@ -10,7 +10,7 @@ use smithay::backend::renderer::gles::{
|
||||
};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, OpaqueRegions};
|
||||
use smithay::backend::renderer::Color32F;
|
||||
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Transform};
|
||||
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
|
||||
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
use crate::render_helpers::background_effect::RenderParams;
|
||||
@@ -18,6 +18,7 @@ use crate::render_helpers::effect_buffer::EffectBuffer;
|
||||
use crate::render_helpers::renderer::AsGlesFrame as _;
|
||||
use crate::render_helpers::shaders::{mat3_uniform, Shaders};
|
||||
use crate::render_helpers::{RenderCtx, RenderTarget};
|
||||
use crate::utils::region::TransformedRegion;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Xray {
|
||||
@@ -69,6 +70,7 @@ pub struct XrayElement {
|
||||
id: Id,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
subregion: Option<TransformedRegion>,
|
||||
input_to_clip_geo: Mat3,
|
||||
clip_geo_size: Vec2,
|
||||
corner_radius: CornerRadius,
|
||||
@@ -189,6 +191,7 @@ impl Xray {
|
||||
id: background.id().clone(),
|
||||
geometry,
|
||||
src,
|
||||
subregion: params.subregion.clone(),
|
||||
input_to_clip_geo,
|
||||
clip_geo_size,
|
||||
corner_radius,
|
||||
@@ -238,6 +241,7 @@ impl Xray {
|
||||
id: backdrop.id().clone(),
|
||||
geometry: params.geometry,
|
||||
src,
|
||||
subregion: params.subregion.clone(),
|
||||
input_to_clip_geo,
|
||||
clip_geo_size,
|
||||
corner_radius: corner_radius.scaled_by(zoom as f32),
|
||||
@@ -309,6 +313,30 @@ impl RenderElement<GlesRenderer> for XrayElement {
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: avoid reallocating a fresh Vec here somehow.
|
||||
let mut filtered_damage = Vec::new();
|
||||
let damage = if let Some(subregion) = &self.subregion {
|
||||
let src_to_geo = self.geometry.size / self.src.size;
|
||||
|
||||
// Compute crop in geometry coordinates.
|
||||
let mut crop = src;
|
||||
crop.loc -= self.src.loc;
|
||||
crop = crop.upscale(src_to_geo);
|
||||
let mut crop = crop.to_logical(1., Transform::Normal, &Size::default());
|
||||
|
||||
// Then convert to subregion coordinates.
|
||||
crop.loc += self.geometry.loc;
|
||||
|
||||
subregion.filter_damage(crop, dst, damage, &mut filtered_damage);
|
||||
|
||||
if filtered_damage.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
&filtered_damage[..]
|
||||
} else {
|
||||
damage
|
||||
};
|
||||
|
||||
let uniforms = self.program.is_some().then(|| self.compute_uniforms());
|
||||
let uniforms = uniforms.as_ref().map_or(&[][..], |x| &x[..]);
|
||||
|
||||
|
||||
+16
-1
@@ -14,7 +14,9 @@ use bitflags::bitflags;
|
||||
use directories::UserDirs;
|
||||
use git_version::git_version;
|
||||
use niri_config::{Config, OutputName};
|
||||
use smithay::backend::renderer::utils::with_renderer_surface_state;
|
||||
use smithay::backend::renderer::utils::{
|
||||
with_renderer_surface_state, RendererSurfaceStateUserData,
|
||||
};
|
||||
use smithay::input::pointer::CursorIcon;
|
||||
use smithay::output::{self, Output};
|
||||
use smithay::reexports::rustix::time::{clock_gettime, ClockId};
|
||||
@@ -35,6 +37,7 @@ use crate::handlers::KdeDecorationsModeState;
|
||||
use crate::niri::ClientState;
|
||||
|
||||
pub mod id;
|
||||
pub mod region;
|
||||
pub mod scale;
|
||||
pub mod signals;
|
||||
pub mod spawning;
|
||||
@@ -325,6 +328,18 @@ pub fn is_laptop_panel(connector: &str) -> bool {
|
||||
matches!(connector.get(..4), Some("eDP-" | "LVDS" | "DSI-"))
|
||||
}
|
||||
|
||||
/// Returns the geometry of the surface.
|
||||
///
|
||||
/// Returns `None` if the surface isn't mapped.
|
||||
pub fn surface_geo(states: &SurfaceData) -> Option<Rectangle<i32, Logical>> {
|
||||
let data = states.data_map.get::<RendererSurfaceStateUserData>();
|
||||
data.and_then(|d| d.lock().unwrap().view())
|
||||
.map(|view| Rectangle {
|
||||
loc: view.offset,
|
||||
size: view.dst,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_toplevel_role<T>(
|
||||
toplevel: &ToplevelSurface,
|
||||
f: impl FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale};
|
||||
use smithay::wayland::compositor::{RectangleKind, RegionAttributes};
|
||||
|
||||
/// Helper for fractionally transforming an i32 region while preserving adjacent rects.
|
||||
///
|
||||
/// Naively applying floating point transforms may cause adjacent rects to go misaligned due to
|
||||
/// rounding differences. This struct helps apply the transforms in such a way as to preserve
|
||||
/// alignment.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TransformedRegion {
|
||||
/// Non-overlapping rects (usually in surface-local coordinates).
|
||||
pub rects: Arc<Vec<Rectangle<i32, Logical>>>,
|
||||
/// Scale to apply to each rect.
|
||||
pub scale: Scale<f64>,
|
||||
/// Translation to apply to each rect after scaling.
|
||||
pub offset: Point<f64, Logical>,
|
||||
}
|
||||
|
||||
impl TransformedRegion {
|
||||
/// Returns an iterator over the top-left and bottom-right corners of transformed rects.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Point<f64, Logical>, Point<f64, Logical>)> + '_ {
|
||||
self.rects.iter().map(|r| {
|
||||
// Here we start in a happy i32 world where everything lines up, and rectangle loc +
|
||||
// size is exactly equal to the adjacent rectangle's loc.
|
||||
//
|
||||
// Unfortunately, we're about to descend to the floating point hell. And we *really*
|
||||
// want adjacent rects to remain adjacent no matter what. So we'll convert our rects to
|
||||
// their extremities (rather than loc and size), and operate on those. Coordinates from
|
||||
// adjacent rects will undergo exactly the same floating point operations, so when
|
||||
// they're ultimately rounded to physical pixels, they will remain adjacent.
|
||||
let r = r.to_f64();
|
||||
|
||||
let mut a = r.loc;
|
||||
// f64 is enough to represent this i32 addition exactly.
|
||||
let mut b = r.loc + r.size.to_point();
|
||||
|
||||
a = a.upscale(self.scale);
|
||||
b = b.upscale(self.scale);
|
||||
|
||||
a += self.offset;
|
||||
b += self.offset;
|
||||
|
||||
(a, b)
|
||||
})
|
||||
}
|
||||
|
||||
/// Intersects damage with this subregion.
|
||||
pub fn filter_damage(
|
||||
&self,
|
||||
// Same coordinate space as self.iter().
|
||||
crop: Rectangle<f64, Logical>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
filtered: &mut Vec<Rectangle<i32, Physical>>,
|
||||
) {
|
||||
let scale = dst.size.to_f64() / crop.size;
|
||||
|
||||
let cs = crop.size.to_point();
|
||||
|
||||
for (mut a, mut b) in self.iter() {
|
||||
// Convert to dst-relative.
|
||||
a -= crop.loc;
|
||||
b -= crop.loc;
|
||||
|
||||
// Intersect with crop.
|
||||
let ia = Point::new(f64::max(a.x, 0.), f64::max(a.y, 0.));
|
||||
let ib = Point::new(f64::min(b.x, cs.x), f64::min(b.y, cs.y));
|
||||
if ib.x <= ia.x || ib.y <= ia.y {
|
||||
// No intersection.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Round extremities to physical pixels, ensuring that adjacent rectangles stay adjacent
|
||||
// at fractional scales.
|
||||
let ia = ia.to_physical_precise_round(scale);
|
||||
let ib = ib.to_physical_precise_round(scale);
|
||||
|
||||
let r = Rectangle::from_extremities(ia, ib);
|
||||
|
||||
// Intersect with each damage rect.
|
||||
for d in damage {
|
||||
if let Some(intersection) = r.intersection(*d) {
|
||||
filtered.push(intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn region_to_non_overlapping_rects(
|
||||
region: &RegionAttributes,
|
||||
output: &mut Vec<Rectangle<i32, Logical>>,
|
||||
) {
|
||||
let _span = tracy_client::span!("region_to_non_overlapping_rects");
|
||||
|
||||
output.clear();
|
||||
|
||||
// Collect all unique Y coordinates.
|
||||
let ys = BTreeSet::from_iter(
|
||||
region
|
||||
.rects
|
||||
.iter()
|
||||
.flat_map(|(_, r)| [r.loc.y, r.loc.y + r.size.h]),
|
||||
);
|
||||
|
||||
let mut ys = ys.into_iter();
|
||||
let Some(mut lo) = ys.next() else {
|
||||
// The region was empty.
|
||||
return;
|
||||
};
|
||||
|
||||
// Sorted list of non-overlapping [start, end) tuples.
|
||||
let mut spans = Vec::<(i32, i32)>::new();
|
||||
|
||||
// Iterate over Y bands.
|
||||
for hi in ys {
|
||||
spans.clear();
|
||||
|
||||
'region: for (kind, r) in ®ion.rects {
|
||||
// Skip rects that don't overlap with the Y band.
|
||||
if hi <= r.loc.y || r.loc.y + r.size.h <= lo {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut x1 = r.loc.x;
|
||||
let mut x2 = r.loc.x + r.size.w;
|
||||
if x1 == x2 {
|
||||
// Empty rect.
|
||||
continue;
|
||||
}
|
||||
|
||||
match *kind {
|
||||
RectangleKind::Add => {
|
||||
// Iterate over existing spans backwards.
|
||||
for i in (0..spans.len()).rev() {
|
||||
let (start, end) = spans[i];
|
||||
|
||||
// New span is to the right.
|
||||
if end < x1 {
|
||||
spans.insert(i + 1, (x1, x2));
|
||||
continue 'region;
|
||||
}
|
||||
|
||||
// New span is to the left.
|
||||
if x2 < start {
|
||||
continue;
|
||||
}
|
||||
|
||||
// New span overlaps this span; merge them.
|
||||
spans.remove(i);
|
||||
x1 = min(x1, start);
|
||||
x2 = max(x2, end);
|
||||
}
|
||||
|
||||
spans.insert(0, (x1, x2));
|
||||
}
|
||||
RectangleKind::Subtract => {
|
||||
// Iterate over existing spans backwards.
|
||||
for i in (0..spans.len()).rev() {
|
||||
let (start, end) = spans[i];
|
||||
|
||||
// Subtract span is to the right.
|
||||
if end <= x1 {
|
||||
continue 'region;
|
||||
}
|
||||
|
||||
// Subtract span is to the left.
|
||||
if x2 <= start {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Subtract span overlaps this span.
|
||||
spans.remove(i);
|
||||
if x2 < end {
|
||||
spans.insert(i, (x2, end));
|
||||
}
|
||||
if start < x1 {
|
||||
spans.insert(i, (start, x1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (x1, x2) in spans.drain(..) {
|
||||
output.push(Rectangle::from_extremities((x1, lo), (x2, hi)));
|
||||
}
|
||||
|
||||
lo = hi;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use insta::assert_snapshot;
|
||||
use proptest::prelude::*;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Size};
|
||||
use smithay::wayland::compositor::{RectangleKind, RegionAttributes};
|
||||
|
||||
use super::region_to_non_overlapping_rects;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn check(rects: &[(RectangleKind, (i32, i32, i32, i32))]) -> String {
|
||||
let region = RegionAttributes {
|
||||
rects: rects
|
||||
.iter()
|
||||
.map(|(kind, (x1, y1, x2, y2))| {
|
||||
(*kind, Rectangle::from_extremities((*x1, *y1), (*x2, *y2)))
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let mut output = Vec::new();
|
||||
region_to_non_overlapping_rects(®ion, &mut output);
|
||||
let mut s = String::new();
|
||||
for r in &output {
|
||||
let x1 = r.loc.x;
|
||||
let y1 = r.loc.y;
|
||||
let x2 = x1 + r.size.w;
|
||||
let y2 = y1 + r.size.h;
|
||||
writeln!(s, "{x1:2} {y1:2} - {x2:2} {y2:2}").unwrap();
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_region_to_non_overlapping_rects() {
|
||||
use RectangleKind::*;
|
||||
|
||||
// empty_region
|
||||
assert_snapshot!(check(&[]), @"");
|
||||
|
||||
// single_rectangle
|
||||
assert_snapshot!(check(&[(Add, (0, 0, 10, 10))]), @" 0 0 - 10 10");
|
||||
|
||||
// empty_rectangle
|
||||
assert_snapshot!(check(&[(Add, (0, 0, 0, 1))]), @"");
|
||||
assert_snapshot!(check(&[(Add, (0, 0, 1, 0))]), @"");
|
||||
|
||||
// two_non_overlapping
|
||||
assert_snapshot!(
|
||||
check(&[(Add, (0, 0, 5, 10)), (Add, (7, 0, 12, 10))]),
|
||||
@"
|
||||
0 0 - 5 10
|
||||
7 0 - 12 10
|
||||
"
|
||||
);
|
||||
|
||||
// two_overlapping
|
||||
assert_snapshot!(
|
||||
check(&[(Add, (0, 0, 10, 10)), (Add, (5, 5, 15, 15))]),
|
||||
@"
|
||||
0 0 - 10 5
|
||||
0 5 - 15 10
|
||||
5 10 - 15 15
|
||||
"
|
||||
);
|
||||
|
||||
// subtraction
|
||||
assert_snapshot!(
|
||||
check(&[(Add, (0, 0, 20, 20)), (Subtract, (5, 5, 15, 15))]),
|
||||
@"
|
||||
0 0 - 20 5
|
||||
0 5 - 5 15
|
||||
15 5 - 20 15
|
||||
0 15 - 20 20
|
||||
"
|
||||
);
|
||||
|
||||
// adjacent_rectangles
|
||||
assert_snapshot!(
|
||||
check(&[(Add, (0, 0, 10, 10)), (Add, (10, 0, 20, 10))]),
|
||||
@" 0 0 - 20 10"
|
||||
);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn non_overlapping_output(
|
||||
rects in proptest::collection::vec(
|
||||
(
|
||||
prop_oneof![Just(RectangleKind::Add), Just(RectangleKind::Subtract)],
|
||||
(0..20i32, 0..20i32, 0..20i32, 0..20i32),
|
||||
),
|
||||
1..10,
|
||||
)
|
||||
) {
|
||||
let region = RegionAttributes {
|
||||
rects: rects
|
||||
.into_iter()
|
||||
.map(|(kind, (x, y, w, h))| {
|
||||
(kind, Rectangle::new(Point::new(x, y), Size::new(w, h)))
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let mut output: Vec<Rectangle<i32, Logical>> = Vec::new();
|
||||
region_to_non_overlapping_rects(®ion, &mut output);
|
||||
|
||||
for i in 0..output.len() {
|
||||
prop_assert!(!output[i].is_empty());
|
||||
|
||||
// Verify no pair of output rectangles overlaps.
|
||||
for j in (i + 1)..output.len() {
|
||||
prop_assert!(
|
||||
!output[i].overlaps(output[j]),
|
||||
"rectangles overlap: {:?} and {:?}",
|
||||
output[i],
|
||||
output[j],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::cell::{Cell, Ref, RefCell};
|
||||
use std::time::Duration;
|
||||
|
||||
use niri_config::{Color, CornerRadius, GradientInterpolation, WindowRule};
|
||||
use niri_config::{Color, Config, CornerRadius, GradientInterpolation, WindowRule};
|
||||
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
@@ -689,19 +689,24 @@ impl LayoutElement for Mapped {
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
scale: f64,
|
||||
clip_to_geometry: bool,
|
||||
surface_anim_scale: Scale<f64>,
|
||||
radius: CornerRadius,
|
||||
xray_pos: XrayPos,
|
||||
push: &mut dyn FnMut(BackgroundEffectElement),
|
||||
) {
|
||||
let should_block_out = ctx.target.should_block_out(self.rules.block_out_from);
|
||||
background_effect::render_for_tile(
|
||||
ctx,
|
||||
geometry,
|
||||
scale,
|
||||
clip_to_geometry,
|
||||
self.toplevel().wl_surface(),
|
||||
self.buf_loc().to_f64(),
|
||||
surface_anim_scale,
|
||||
self.blur_config,
|
||||
radius,
|
||||
self.rules.background_effect,
|
||||
should_block_out,
|
||||
xray_pos,
|
||||
push,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user