Compare commits

...

2 Commits

10 changed files with 574 additions and 529 deletions

View File

@@ -21,8 +21,13 @@ the [original readme](./README_orig.md).
Windows (both floating and tiling), as well as layer surfaces can have blur enabled on them. Blur needs to be enabled
for each window / layer surface explicitly.
Currently, all windows will draw "optimized" or "x-ray" blur, which is rendered separately using only the `bottom` and
`background` layer surfaces.
Tiled windows will always draw "optimized" or "x-ray" blur, which is rendered from a shared texture using only the
`bottom` and `background` layer surfaces, and is updated at a slower rate. Floating windows, as well as `top` or
`overlay` layer surfaces will draw "true" blur by default instead, which is rendered using all available screen
contents.
Note that true blur is rather expensive in terms of GPU load however, so an option exists to also have these surfaces
draw `x-ray` blur instead.
To set global defaults for blur:
@@ -72,17 +77,20 @@ layer-rule {
// note that this will require rendering the blurred surface twice, so if possible,
// prefer using `geometry-corner-radius` instead, for performance reasons.
ignore-alpha 0.45
// will render "x-ray" blur that is only based on `bottom` and `background` layer surfaces,
// even if the window is floating. good for minimal GPU load.
x-ray true
}
}
```
#### Caveats
- Enabling blur currently incurs a noticeable increase in GPU utilization on high frame rates (tested with 240hz)
compared to other compositors with similar functionality (e.g. Hyprland). Investigating this will require some sort of
[GPU profiling](https://github.com/Smithay/smithay/pull/1134).
- There is currently no way to enable "true" blur for floating windows or top / overlay layer surfaces. The
functionality exists in code, but is very slow / buggy at the moment.
- True blur currently only works for horizontal monitor configurations. When using any sort of 90 or 270 degree
transformations, only x-ray blur will be available.
- True blur is rather performance intensive as of right now, since it renders itself on every frame. It is recommended
to only enable it for surfaces that don't take up a lot of screen time (e.g. notifications, dialogs).
- Blur is currently only possible to be enabled through the config. Implementing both
[KDE blur](https://wayland.app/protocols/kde-blur) and
[background effect](https://wayland.app/protocols/ext-background-effect-v1) is planned though.
@@ -152,7 +160,7 @@ Since windows can be grouped on a per-tile basis, column-level tabbing is obsole
options have been removed to improve maintainability. If you have any tabbed-column related options in your niri config,
this fork will fail to parse it.
## Plans
## Future Plans
As of right now, I am trying to keep this fork "as close to upstream as is reasonable", to allow for frequent rebasing
without too many conflicts to solve.
@@ -240,8 +248,6 @@ To use it, simply import the module it provides into your config:
# optional, if using home-manager
home-manager = {
# recommended, as the niri module from this fork overrides the upstream niri package
useGlobalPkgs = true;
users.my-username = {
imports = [
niri.homeManagerModules.default
@@ -249,6 +255,8 @@ To use it, simply import the module it provides into your config:
wayland.windowManager.niri = {
enable = true;
# use the package from `programs.niri.package`, which is set by `niri.nixosModules.default`
package = null;
# fully declarative niri configuration; converted to kdl during rebuild
#
# - simple entries without arguments are declared as `name = [];`

View File

@@ -345,6 +345,7 @@ pub struct Blur {
pub radius: FloatOrInt<0, 1024>,
pub noise: FloatOrInt<0, 1024>,
pub ignore_alpha: FloatOrInt<0, 1>,
pub x_ray: bool,
}
impl Default for Blur {
@@ -355,6 +356,7 @@ impl Default for Blur {
radius: FloatOrInt(0.0),
noise: FloatOrInt(0.0),
ignore_alpha: FloatOrInt(0.0),
x_ray: false,
}
}
}
@@ -366,7 +368,7 @@ impl MergeWith<BlurRule> for Blur {
self.on = false;
}
merge_clone!((self, part), passes, radius, noise, ignore_alpha);
merge_clone!((self, part), passes, radius, noise, ignore_alpha, x_ray);
}
}
@@ -692,6 +694,8 @@ pub struct BlurRule {
pub noise: Option<FloatOrInt<0, 1024>>,
#[knuffel(child, unwrap(argument))]
pub ignore_alpha: Option<FloatOrInt<0, 1>>,
#[knuffel(child, unwrap(argument))]
pub x_ray: Option<bool>,
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
@@ -749,7 +753,7 @@ impl MergeWith<Self> for BlurRule {
fn merge_with(&mut self, part: &Self) {
merge_on_off!((self, part));
merge_clone_opt!((self, part), passes, radius, noise, ignore_alpha);
merge_clone_opt!((self, part), passes, radius, noise, ignore_alpha, x_ray);
}
}

View File

@@ -1,5 +1,5 @@
use niri_config::utils::MergeWith as _;
use niri_config::{Blur, Config, LayerRule};
use niri_config::{Config, LayerRule};
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::surface::{
render_elements_from_surface_tree, WaylandSurfaceRenderElement,
@@ -14,11 +14,10 @@ use super::ResolvedLayerRules;
use crate::animation::Clock;
use crate::layout::shadow::Shadow;
use crate::niri_render_elements;
use crate::render_helpers::blur::element::BlurRenderElement;
use crate::render_helpers::blur::element::{Blur, BlurRenderElement, CommitTracker};
use crate::render_helpers::blur::EffectsFramebufffersUserData;
use crate::render_helpers::clipped_surface::ClippedSurfaceRenderElement;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::shaders::Shaders;
use crate::render_helpers::shadow::ShadowRenderElement;
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
use crate::render_helpers::{render_to_texture, RenderTarget, SplitElements};
@@ -39,7 +38,7 @@ pub struct MappedLayer {
shadow: Shadow,
/// Configuration for this layer's blur.
blur_config: Blur,
blur: Blur,
/// Size (used for blur).
// TODO: move to standalone blur struct
@@ -91,7 +90,7 @@ impl MappedLayer {
scale,
shadow: Shadow::new(shadow_config),
clock,
blur_config,
blur: Blur::new(blur_config),
size: Size::default(),
}
}
@@ -106,7 +105,7 @@ impl MappedLayer {
let mut blur_config = config.layout.blur;
blur_config.on = false;
blur_config.merge_with(&self.rules.blur);
self.blur_config = blur_config;
self.blur.update_config(blur_config);
}
pub fn update_shaders(&mut self) {
@@ -197,8 +196,11 @@ impl MappedLayer {
let alpha = self.rules.opacity.unwrap_or(1.).clamp(0., 1.);
let location = location + self.bob_offset();
// Normal surface elements used to render a texture for the ignore alpha pass inside the
// blur shader.
let mut gles_elems: Option<Vec<LayerSurfaceRenderElement<GlesRenderer>>> = None;
let ignore_alpha = self.rules.blur.ignore_alpha.unwrap_or_default().0;
let mut update_alpha_tex = ignore_alpha > 0.;
if target.should_block_out(self.rules.block_out_from) {
// Round to physical pixels.
@@ -240,98 +242,79 @@ impl MappedLayer {
Kind::ScanoutCandidate,
);
gles_elems = (ignore_alpha > 0.).then(|| {
render_elements_from_surface_tree(
// If there's been an update to our render elements, we need to render them again for
// our blur ignore alpha pass.
if ignore_alpha > 0.
&& self
.blur
.maybe_update_commit_tracker(CommitTracker::from_elements(rv.normal.iter()))
{
gles_elems = Some(render_elements_from_surface_tree(
renderer.as_gles_renderer(),
surface,
buf_pos.to_physical_precise_round(scale),
scale,
alpha,
Kind::ScanoutCandidate,
)
});
));
} else {
update_alpha_tex = false;
}
};
let blur_elem = (self.blur_config.on
&& matches!(self.surface.layer(), Layer::Top | Layer::Overlay))
let blur_elem = (matches!(self.surface.layer(), Layer::Top | Layer::Overlay)
&& !target.should_block_out(self.rules.block_out_from))
.then(|| {
let fx_buffers = fx_buffers?;
let fx_buffers = fx_buffers.borrow();
// TODO: respect sync point?
let alpha_tex = (ignore_alpha > 0.)
.then(|| {
gles_elems.and_then(|gles_elems| {
let transform = fx_buffers.transform();
let alpha_tex = gles_elems
.and_then(|gles_elems| {
let fx_buffers = fx_buffers.borrow();
render_to_texture(
renderer.as_gles_renderer(),
transform.transform_size(fx_buffers.output_size()),
self.scale.into(),
Transform::Normal,
Fourcc::Abgr8888,
gles_elems.into_iter(),
)
.inspect_err(|e| warn!("failed to render alpha tex: {e:?}"))
.ok()
})
let transform = fx_buffers.transform();
render_to_texture(
renderer.as_gles_renderer(),
transform.transform_size(fx_buffers.output_size()),
self.scale.into(),
Transform::Normal,
Fourcc::Abgr8888,
gles_elems.into_iter(),
)
.inspect_err(|e| warn!("failed to render alpha tex for layer surface: {e:?}"))
.ok()
})
.flatten()
.map(|r| r.0);
let radius = self.rules.geometry_corner_radius.unwrap_or_default();
if update_alpha_tex {
if let Some(alpha_tex) = alpha_tex {
self.blur.set_alpha_tex(alpha_tex);
} else {
self.blur.clear_alpha_tex();
}
}
let blur_sample_area = Rectangle::new(location, self.size).to_i32_round();
let elem = BlurRenderElement::new_optimized(
renderer,
&fx_buffers,
blur_sample_area,
location.to_physical_precise_round(self.scale),
self.rules
.geometry_corner_radius
.unwrap_or_default()
.top_left,
self.scale,
self.blur_config,
);
let geo = Rectangle::new(location, blur_sample_area.size.to_f64());
let clip_to_geometry =
ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radius);
let clip_shader = (alpha_tex.is_some() || clip_to_geometry)
.then(|| Shaders::get(renderer).clipped_surface.clone())
.flatten();
let elem = if let Some(clip_shader) = clip_shader {
let view_src = blur_sample_area.to_f64();
let buf_size = fx_buffers
.output_size()
.to_f64()
.to_logical(self.scale)
.to_i32_round();
ClippedSurfaceRenderElement::new(
elem,
view_src,
buf_size,
self.scale.into(),
geo,
clip_shader,
radius,
alpha_tex,
ignore_alpha,
)
.into()
} else {
elem.into()
};
Some(elem)
Some(
self.blur
.render(
fx_buffers,
blur_sample_area,
self.rules.geometry_corner_radius.unwrap_or_default(),
self.scale,
geo,
!self.rules.blur.x_ray.unwrap_or_default(),
blur_sample_area.loc.to_f64(),
)
.map(Into::into),
)
})
.flatten()
.flatten()
.into_iter();
let location = location.to_physical_precise_round(scale).to_logical(scale);

View File

@@ -43,6 +43,7 @@ impl ResolvedLayerRules {
radius: None,
noise: None,
ignore_alpha: None,
x_ray: None,
},
shadow: ShadowRule {
off: false,

View File

@@ -3,7 +3,7 @@ use std::rc::Rc;
use std::sync::atomic::Ordering;
use niri_config::utils::MergeWith as _;
use niri_config::{Blur, Color, CornerRadius, GradientInterpolation};
use niri_config::{Color, CornerRadius, GradientInterpolation};
use niri_ipc::WindowLayout;
use portable_atomic::AtomicU8;
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
@@ -22,7 +22,7 @@ use crate::animation::{Animation, Clock};
use crate::layout::tab_indicator::{TabIndicator, TabIndicatorRenderElement, TabInfo};
use crate::layout::SizingMode;
use crate::niri_render_elements;
use crate::render_helpers::blur::element::BlurRenderElement;
use crate::render_helpers::blur::element::{Blur, BlurRenderElement};
use crate::render_helpers::blur::EffectsFramebufffersUserData;
use crate::render_helpers::border::BorderRenderElement;
use crate::render_helpers::clipped_surface::{ClippedSurfaceRenderElement, RoundedCornerDamage};
@@ -408,8 +408,7 @@ pub struct Tile<W: LayoutElement> {
window_size_override: WindowSizeOverride,
/// This tile's blur settings.
// TODO: create a proper struct for this, like e.g. Shadow
blur_config: Blur,
blur: Blur,
/// Clock for driving animations.
pub(super) clock: Clock,
@@ -499,7 +498,7 @@ impl<W: LayoutElement> Tile<W> {
border: FocusRing::new(border_config.into()),
focus_ring: FocusRing::new(focus_ring_config),
shadow: Shadow::new(shadow_config),
blur_config,
blur: Blur::new(blur_config),
sizing_mode,
fullscreen_backdrop: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]),
restore_to_floating: false,
@@ -568,7 +567,7 @@ impl<W: LayoutElement> Tile<W> {
let mut blur_config = self.options.layout.blur;
blur_config.on = false;
blur_config.merge_with(&rules.blur);
self.blur_config = blur_config;
self.blur.update_config(blur_config);
}
pub fn update_shaders(&mut self) {
@@ -1768,6 +1767,7 @@ impl<W: LayoutElement> Tile<W> {
let mut rounded_corner_damage = None;
let has_border_shader = BorderRenderElement::has_shader(renderer);
let geo = Rectangle::new(window_render_loc, window_size);
let animated_geo = Rectangle::new(window_render_loc, animated_window_size);
let clip_shader = clip_to_geometry
.then(|| Shaders::get(renderer).clipped_surface.clone())
.flatten();
@@ -1927,49 +1927,21 @@ impl<W: LayoutElement> Tile<W> {
.then(|| self.focus_ring.render(renderer, location).map(Into::into));
let rv = rv.chain(elem.into_iter().flatten());
let blur_elem = self
.blur_config
.on
.then(|| {
let fx_buffers = fx_buffers?;
let fx_buffers = fx_buffers.borrow();
let elem = BlurRenderElement::new_optimized(
renderer,
&fx_buffers,
blur_sample_area.to_i32_round(),
window_render_loc.to_physical(self.scale).to_i32_round(),
radius.top_left,
self.scale,
self.blur_config,
);
let elem = if clip_to_geometry {
let view_src = blur_sample_area;
let buf_size = fx_buffers
.output_size()
.to_f64()
.to_logical(self.scale)
.to_i32_round();
ClippedSurfaceRenderElement::new(
elem,
view_src,
buf_size,
scale,
geo,
clip_shader?.clone(),
let blur_elem = fx_buffers
.and_then(|fx_buffers| {
self.blur
.render(
fx_buffers,
blur_sample_area.to_i32_round(),
radius,
None,
0.,
self.scale,
animated_geo,
self.focused_window().is_floating()
&& !self.focused_window().rules().blur.x_ray.unwrap_or_default(),
window_render_loc,
)
.into()
} else {
elem.into()
};
Some(elem)
.map(Into::into)
})
.flatten()
.into_iter();
let elem = (expanded_progress < 1.)

View File

@@ -317,7 +317,6 @@ pub(super) unsafe fn get_main_buffer_blur(
supports_instancing: bool,
// dst is the region that we want blur on
dst: Rectangle<i32, Physical>,
is_tty: bool,
alpha_tex: Option<&GlesTexture>,
) -> Result<GlesTexture, GlesError> {
let tex_size = fx_buffers
@@ -335,6 +334,10 @@ pub(super) unsafe fn get_main_buffer_blur(
dst
};
// let dst_expanded = fx_buffers
// .transform()
// .transform_rect_in(dst_expanded, &tex_size);
let mut prev_fbo = 0;
gl.GetIntegerv(ffi::FRAMEBUFFER_BINDING, &mut prev_fbo as *mut _);
@@ -374,52 +377,33 @@ pub(super) unsafe fn get_main_buffer_blur(
// as the bound fbo size, so blitting uses dst immediately
gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, sample_fbo);
if is_tty {
let src_x0 = dst_expanded.loc.x;
let src_y0 = dst_expanded.loc.y;
let src_x1 = dst_expanded.loc.x + dst_expanded.size.w;
let src_y1 = dst_expanded.loc.y + dst_expanded.size.h;
let dst_x0 = src_x0;
let dst_y0 = src_y0;
let dst_x1 = src_x1;
let dst_y1 = src_y1;
let dst_x0 = dst_expanded.loc.x;
let dst_y0 = dst_expanded.loc.y;
let dst_x1 = dst_expanded.loc.x + dst_expanded.size.w;
let dst_y1 = dst_expanded.loc.y + dst_expanded.size.h;
gl.BlitFramebuffer(
src_x0,
src_y0,
src_x1,
src_y1,
dst_x0,
dst_y0,
dst_x1,
dst_y1,
ffi::COLOR_BUFFER_BIT,
ffi::LINEAR,
);
} else {
let fb_height = tex_size.h;
let src_expanded = fx_buffers
.transform()
.invert()
.transform_rect_in(dst_expanded, &tex_size);
let dst_y0 = dst_expanded.loc.y + dst_expanded.size.h;
let dst_y1 = dst_expanded.loc.y;
let src_x0 = src_expanded.loc.x;
let src_y0 = src_expanded.loc.y;
let src_x1 = src_expanded.loc.x + src_expanded.size.w;
let src_y1 = src_expanded.loc.y + src_expanded.size.h;
let src_x0 = dst_expanded.loc.x;
let src_x1 = dst_expanded.loc.x + dst_expanded.size.w;
let src_y0 = fb_height - dst_y0;
let src_y1 = fb_height - dst_y1;
gl.BlitFramebuffer(
src_x0,
src_y0,
src_x1,
src_y1,
src_x0,
dst_y0,
src_x1,
dst_y1,
ffi::COLOR_BUFFER_BIT,
ffi::LINEAR,
);
}
gl.BlitFramebuffer(
src_x0,
src_y0,
src_x1,
src_y1,
dst_x0,
dst_y0,
dst_x1,
dst_y1,
ffi::COLOR_BUFFER_BIT,
ffi::LINEAR,
);
if gl.GetError() == ffi::INVALID_OPERATION {
error!("TrueBlur needs GLES3.0 for blitting");

View File

@@ -1,60 +1,224 @@
// Originally ported from https://github.com/nferhat/fht-compositor/blob/main/src/renderer/blur/element.rs
use niri_config::Blur;
use std::cell::RefCell;
use std::collections::HashMap;
use smithay::backend::renderer::element::texture::TextureRenderElement;
use glam::{Mat3, Vec2};
use niri_config::CornerRadius;
use pango::glib::property::PropertySet;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions};
use smithay::backend::renderer::Renderer;
use smithay::backend::renderer::gles::{
ffi, GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform,
};
use smithay::backend::renderer::utils::{CommitCounter, OpaqueRegions};
use smithay::backend::renderer::Texture;
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
use crate::render_helpers::blur::EffectsFramebufffersUserData;
use crate::render_helpers::render_data::RendererData;
use crate::render_helpers::renderer::{AsGlesFrame, NiriRenderer};
use crate::render_helpers::shaders::Shaders;
use crate::render_helpers::renderer::AsGlesFrame;
use crate::render_helpers::shaders::{mat3_uniform, Shaders};
use super::optimized_blur_texture_element::OptimizedBlurTextureElement;
use super::{CurrentBuffer, EffectsFramebuffers};
#[derive(Debug)]
pub enum BlurRenderElement {
/// Use optimized blur, aka X-ray blur.
///
/// This technique relies on [`EffectsFramebuffers::optimized_blur`] to be populated. It will
/// render this texture no matter what is below the blur render element.
#[derive(Debug, Clone)]
enum BlurVariant {
Optimized {
tex: OptimizedBlurTextureElement,
corner_radius: f32,
noise: f32,
scale: f64,
output_size: Size<i32, Physical>,
config: Blur,
output_transform: Transform,
texture: GlesTexture,
},
/// Use true blur.
///
/// When using this technique, the compositor will blur the current framebuffer contents that
/// are below the [`BlurElement`] in order to display them. This adds an additional render step
/// but provides true results with the blurred contents.
TrueBlur {
// we are just a funny texture element that generates the texture on RenderElement::draw
id: Id,
scale: f64,
transform: Transform,
src: Rectangle<f64, Logical>,
size: Size<i32, Logical>,
corner_radius: f32,
loc: Point<i32, Physical>,
config: Blur,
// FIXME: Use DamageBag and expand it as needed?
commit_counter: CommitCounter,
True {
fx_buffers: EffectsFramebufffersUserData,
alpha_tex: Option<GlesTexture>,
config: niri_config::Blur,
},
}
/// Used for tracking commit counters of a collection of elements.
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub struct CommitTracker(HashMap<Id, CommitCounter>);
impl CommitTracker {
pub fn new() -> Self {
Self(Default::default())
}
pub fn from_elements<'a, E: Element + 'a>(elems: impl Iterator<Item = &'a E>) -> Self {
Self(
elems
.map(|e| (e.id().clone(), e.current_commit()))
.collect(),
)
}
pub fn update<'a, E: Element + 'a>(&mut self, elems: impl Iterator<Item = &'a E>) {
*self = Self::from_elements(elems);
}
}
#[derive(Debug)]
pub struct Blur {
config: niri_config::Blur,
inner: RefCell<Option<BlurRenderElement>>,
alpha_tex: RefCell<Option<GlesTexture>>,
commit_tracker: RefCell<CommitTracker>,
}
impl Blur {
pub fn new(config: niri_config::Blur) -> Self {
Self {
config,
inner: Default::default(),
alpha_tex: Default::default(),
commit_tracker: Default::default(),
}
}
pub fn maybe_update_commit_tracker(&self, other: CommitTracker) -> bool {
if self.commit_tracker.borrow().eq(&other) {
false
} else {
self.commit_tracker.set(other);
true
}
}
pub fn update_config(&mut self, config: niri_config::Blur) {
if self.config != config {
self.inner.set(None);
}
self.config = config;
}
pub fn clear_alpha_tex(&self) {
if let Some(inner) = self.inner.borrow_mut().as_mut() {
if self.alpha_tex.borrow().is_some() {
inner.damage_all();
}
}
self.alpha_tex.set(None);
}
pub fn set_alpha_tex(&self, alpha_tex: GlesTexture) {
self.alpha_tex.set(Some(alpha_tex));
self.inner.set(None);
}
#[allow(clippy::too_many_arguments)]
pub fn render(
&self,
fx_buffers: EffectsFramebufffersUserData,
sample_area: Rectangle<i32, Logical>,
corner_radius: CornerRadius,
scale: f64,
geometry: Rectangle<f64, Logical>,
mut true_blur: bool,
render_loc: Point<f64, Logical>,
) -> Option<BlurRenderElement> {
if !self.config.on {
return None;
}
// FIXME: true blur is broken on 90/270 transformed monitors
if !matches!(
fx_buffers.borrow().transform(),
Transform::Normal | Transform::Flipped180,
) {
true_blur = false;
}
let mut inner = self.inner.borrow_mut();
let Some(inner) = inner.as_mut() else {
let elem = BlurRenderElement::new(
&fx_buffers.borrow(),
sample_area,
corner_radius,
scale,
self.config,
geometry,
self.alpha_tex.borrow().clone(),
if true_blur {
BlurVariant::True {
fx_buffers: fx_buffers.clone(),
config: self.config,
}
} else {
BlurVariant::Optimized {
texture: fx_buffers.borrow().optimized_blur.clone(),
}
},
render_loc,
);
*inner = Some(elem.clone());
return Some(elem);
};
if true_blur != matches!(&inner.variant, BlurVariant::True { .. }) {
inner.variant = if true_blur {
BlurVariant::True {
fx_buffers: fx_buffers.clone(),
config: self.config,
}
} else {
BlurVariant::Optimized {
texture: fx_buffers.borrow().optimized_blur.clone(),
}
};
inner.damage_all();
}
let fx_buffers = fx_buffers.borrow();
if inner.sample_area == sample_area
&& inner.geometry == geometry
&& inner.scale == scale
&& inner.corner_radius == corner_radius
&& inner.render_loc == render_loc
{
if !matches!(&inner.variant, BlurVariant::Optimized { texture }
if texture.size().w == fx_buffers.output_size().w
&& texture.size().h == fx_buffers.output_size().h)
{
// If we are true blur, or if our output size changed, we need to re-render.
// PERF: is there a better solution for true blur?
inner.damage_all();
}
return Some(inner.clone());
}
inner.render_loc = render_loc;
inner.sample_area = sample_area;
inner.alpha_tex = self.alpha_tex.borrow().clone();
inner.scale = scale;
inner.geometry = geometry;
inner.damage_all();
inner.update_uniforms(&fx_buffers, &self.config);
Some(inner.clone())
}
}
#[derive(Clone, Debug)]
pub struct BlurRenderElement {
id: Id,
uniforms: Vec<Uniform<'static>>,
sample_area: Rectangle<i32, Logical>,
alpha_tex: Option<GlesTexture>,
scale: f64,
commit: CommitCounter,
corner_radius: CornerRadius,
geometry: Rectangle<f64, Logical>,
variant: BlurVariant,
render_loc: Point<f64, Logical>,
}
impl BlurRenderElement {
/// Create a new [`BlurElement`]. You are supposed to put this **below** the translucent surface
/// that you want to blur. `area` is assumed to be relative to the `output` you are rendering
@@ -65,165 +229,142 @@ impl BlurRenderElement {
/// - Display outdated/wrong contents
/// - Not display anything since the buffer will be empty.
#[allow(clippy::too_many_arguments)]
pub fn new_optimized(
renderer: &mut impl NiriRenderer,
fn new(
fx_buffers: &EffectsFramebuffers,
sample_area: Rectangle<i32, Logical>,
loc: Point<i32, Physical>,
corner_radius: f32,
corner_radius: CornerRadius,
scale: f64,
config: Blur,
config: niri_config::Blur,
geometry: Rectangle<f64, Logical>,
alpha_tex: Option<GlesTexture>,
variant: BlurVariant,
render_loc: Point<f64, Logical>,
) -> Self {
let texture = fx_buffers.optimized_blur.clone();
let scaled = sample_area.to_f64().upscale(scale);
let texture = TextureRenderElement::from_static_texture(
Id::new(),
renderer.as_gles_renderer().context_id(),
loc.to_f64(),
texture,
1,
Transform::Normal,
Some(1.0),
Some(scaled),
Some(scaled.size.to_i32_ceil()),
// NOTE: Since this is "optimized" blur, anything below the window will not be
// rendered
Some(vec![Rectangle::new(
scaled.loc.to_i32_ceil(),
scaled.size.to_i32_ceil(),
)
.to_buffer(1, Transform::Normal, &sample_area.size)]),
Kind::Unspecified,
);
Self::Optimized {
tex: texture.into(),
corner_radius,
noise: config.noise.0 as f32,
let mut this = Self {
id: Id::new(),
uniforms: Vec::with_capacity(7),
alpha_tex,
sample_area,
scale,
output_size: fx_buffers.output_size,
output_transform: fx_buffers.transform,
config,
}
corner_radius,
geometry,
commit: CommitCounter::default(),
variant,
render_loc,
};
this.update_uniforms(fx_buffers, &config);
this
}
#[allow(clippy::too_many_arguments)]
pub fn new_true(
fx_buffers: EffectsFramebufffersUserData,
sample_area: Rectangle<i32, Logical>,
loc: Point<i32, Physical>,
corner_radius: f32,
scale: f64,
config: Blur,
zoom: f64,
alpha_tex: Option<GlesTexture>,
) -> Self {
let mut final_sample_area = sample_area.to_f64().upscale(zoom);
let center = (fx_buffers.borrow().output_size.to_f64().to_logical(scale) / 2.).to_point();
final_sample_area.loc.x = center.x - (center.x - sample_area.loc.x as f64) * zoom;
final_sample_area.loc.y = center.y - (center.y - sample_area.loc.y as f64) * zoom;
fn update_uniforms(&mut self, fx_buffers: &EffectsFramebuffers, config: &niri_config::Blur) {
let transform = Transform::Normal;
Self::TrueBlur {
id: Id::new(),
scale,
src: final_sample_area,
transform: Transform::Normal,
size: sample_area.size,
corner_radius,
loc,
config,
fx_buffers,
commit_counter: CommitCounter::default(),
alpha_tex,
}
let elem_geo: Rectangle<i32, _> = self.sample_area.to_physical_precise_round(self.scale);
let elem_geo_loc = Vec2::new(elem_geo.loc.x as f32, elem_geo.loc.y as f32);
let elem_geo_size = Vec2::new(elem_geo.size.w as f32, elem_geo.size.h as f32);
let view_src = self.sample_area; // CORRECT
let buf_size = fx_buffers.output_size().to_f64().to_logical(self.scale); // CORRECT
let buf_size = Vec2::new(buf_size.w as f32, buf_size.h as f32);
let geo = self.geometry.to_physical_precise_round(self.scale);
let geo_loc = Vec2::new(geo.loc.x, geo.loc.y);
let geo_size = Vec2::new(geo.size.w, geo.size.h);
let src_loc = Vec2::new(view_src.loc.x as f32, view_src.loc.y as f32);
let src_size = Vec2::new(view_src.size.w as f32, view_src.size.h as f32);
let transform_matrix = Mat3::from_translation(Vec2::new(0.5, 0.5))
* Mat3::from_cols_array(transform.matrix().as_ref())
* Mat3::from_translation(-Vec2::new(0.5, 0.5));
// FIXME: y_inverted
let input_to_geo = transform_matrix * Mat3::from_scale(elem_geo_size / geo_size)
* Mat3::from_translation((elem_geo_loc - geo_loc) / elem_geo_size)
// Apply viewporter src.
* Mat3::from_scale(buf_size / src_size)
* Mat3::from_translation(-src_loc / buf_size);
self.uniforms = vec![
Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)),
Uniform::new("geo_size", geo_size.to_array()),
Uniform::new("niri_scale", self.scale as f32),
Uniform::new("noise", config.noise.0 as f32),
mat3_uniform("input_to_geo", input_to_geo),
Uniform::new(
"ignore_alpha",
if self.alpha_tex.is_some() {
config.ignore_alpha.0 as f32
} else {
0.
},
),
Uniform::new("alpha_tex", if self.alpha_tex.is_some() { 1 } else { 0 }),
];
}
fn damage_all(&mut self) {
self.commit.increment()
}
}
impl Element for BlurRenderElement {
fn id(&self) -> &Id {
match self {
BlurRenderElement::Optimized { tex, .. } => tex.id(),
BlurRenderElement::TrueBlur { id, .. } => id,
}
&self.id
}
fn current_commit(&self) -> CommitCounter {
match self {
BlurRenderElement::Optimized { tex, .. } => tex.current_commit(),
BlurRenderElement::TrueBlur { commit_counter, .. } => *commit_counter,
}
}
fn location(&self, scale: Scale<f64>) -> Point<i32, Physical> {
match self {
BlurRenderElement::Optimized { tex, .. } => tex.location(scale),
BlurRenderElement::TrueBlur { loc, .. } => *loc,
}
self.commit
}
fn src(&self) -> Rectangle<f64, Buffer> {
match self {
BlurRenderElement::Optimized { tex, .. } => tex.src(),
BlurRenderElement::TrueBlur {
src,
transform,
size,
scale,
..
} => src.to_buffer(*scale, *transform, &size.to_f64()),
}
self.sample_area.to_f64().to_buffer(
self.scale,
Transform::Normal,
&self.sample_area.size.to_f64(),
)
}
fn transform(&self) -> Transform {
match self {
BlurRenderElement::Optimized { tex, .. } => tex.transform(),
BlurRenderElement::TrueBlur { transform, .. } => *transform,
}
}
fn damage_since(
&self,
scale: Scale<f64>,
commit: Option<CommitCounter>,
) -> DamageSet<i32, Physical> {
match self {
BlurRenderElement::Optimized { tex, .. } => tex.damage_since(scale, commit),
BlurRenderElement::TrueBlur { config, .. } => {
let passes = config.passes;
let radius = config.radius.0 as f32;
// Since the blur element samples from around itself, we must expand the damage it
// induces to include any potential changes.
let mut geometry = Rectangle::from_size(self.geometry(scale).size);
let size = (2f32.powi(passes as i32 + 1) * radius).ceil() as i32;
geometry.loc -= Point::from((size, size));
geometry.size += Size::from((size, size)).upscale(2);
// FIXME: Damage tracking?
DamageSet::from_slice(&[geometry])
}
}
Transform::Normal
}
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
match self {
BlurRenderElement::Optimized { tex, .. } => tex.opaque_regions(scale),
BlurRenderElement::TrueBlur { .. } => {
// Since we are rendering as true blur, we will draw whatever is behind the window
OpaqueRegions::default()
}
if self.alpha_tex.is_some() || matches!(&self.variant, BlurVariant::True { .. }) {
return OpaqueRegions::default();
}
let geometry = self.geometry(scale);
let CornerRadius {
top_left,
top_right,
bottom_right,
bottom_left,
} = self.corner_radius.scaled_by(scale.x as f32);
let largest_radius = top_left.max(top_right).max(bottom_right).max(bottom_left);
let rect = Rectangle::new(
Point::new(top_left.ceil() as i32, top_left.ceil() as i32),
(geometry.size.to_f64()
- Size::new(largest_radius.ceil() as f64, largest_radius.ceil() as f64) * 2.)
.to_i32_ceil(),
);
OpaqueRegions::from_slice(&[rect])
}
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
match self {
BlurRenderElement::Optimized { tex, .. } => tex.geometry(scale),
BlurRenderElement::TrueBlur { loc, size, .. } => {
Rectangle::new(*loc, size.to_physical_precise_round(scale))
}
}
Rectangle::new(
self.render_loc.to_physical_precise_round(scale),
self.sample_area
.to_f64()
.to_physical_precise_round(scale)
.size,
)
}
fn alpha(&self) -> f32 {
@@ -235,96 +376,6 @@ impl Element for BlurRenderElement {
}
}
#[allow(clippy::too_many_arguments)]
fn draw_true_blur(
fx_buffers: &mut EffectsFramebuffers,
gles_frame: &mut GlesFrame,
config: &Blur,
scale: f64,
dst: Rectangle<i32, Physical>,
corner_radius: f32,
src: Rectangle<f64, Buffer>,
damage: &[Rectangle<i32, Physical>],
opaque_regions: &[Rectangle<i32, Physical>],
alpha: f32,
is_tty: bool,
alpha_tex: Option<&GlesTexture>,
) -> Result<(), GlesError> {
fx_buffers.current_buffer = CurrentBuffer::Normal;
let shaders = Shaders::get_from_frame(gles_frame).blur.clone();
let vbos = RendererData::get_from_frame(gles_frame).vbos;
let supports_instancing = gles_frame
.capabilities()
.contains(&smithay::backend::renderer::gles::Capability::Instancing);
let debug = !gles_frame.debug_flags().is_empty();
let projection_matrix = glam::Mat3::from_cols_array(gles_frame.projection());
// Update the blur buffers.
// We use gl ffi directly to circumvent some stuff done by smithay
let blurred_texture = gles_frame.with_context(|gl| unsafe {
super::get_main_buffer_blur(
gl,
&mut *fx_buffers,
&shaders,
*config,
projection_matrix,
scale as i32,
&vbos,
debug,
supports_instancing,
dst,
is_tty,
alpha_tex,
)
})??;
let program = Shaders::get_from_frame(gles_frame).blur_finish.clone();
let additional_uniforms = vec![
Uniform::new(
"geo",
[
dst.loc.x as f32,
dst.loc.y as f32,
dst.size.w as f32,
dst.size.h as f32,
],
),
Uniform::new("alpha", alpha),
Uniform::new("noise", config.noise.0 as f32),
Uniform::new("corner_radius", corner_radius),
Uniform::new(
"output_size",
[
fx_buffers.output_size.w as f32,
fx_buffers.output_size.h as f32,
],
),
Uniform::new(
"ignore_alpha",
if alpha_tex.is_some() {
config.ignore_alpha.0 as f32
} else {
0.
},
),
Uniform::new("alpha_tex", 1),
];
gles_frame.render_texture_from_to(
&blurred_texture,
src,
dst,
damage,
opaque_regions,
Transform::Normal,
alpha,
program.as_ref(),
&additional_uniforms,
)
}
impl RenderElement<GlesRenderer> for BlurRenderElement {
fn draw(
&self,
@@ -336,46 +387,83 @@ impl RenderElement<GlesRenderer> for BlurRenderElement {
) -> Result<(), GlesError> {
let _span = trace_span!("blur_draw_gles").entered();
match self {
Self::Optimized { tex, scale, .. } => {
let downscaled_dst = Rectangle::new(
dst.loc,
Size::from((
(dst.size.w as f64 / *scale) as i32,
(dst.size.h as f64 / *scale) as i32,
)),
);
let downscaled_dst = Rectangle::new(
dst.loc,
Size::from((
(dst.size.w as f64 / self.scale) as i32,
(dst.size.h as f64 / self.scale) as i32,
)),
);
<TextureRenderElement<GlesTexture> as RenderElement<GlesRenderer>>::draw(
&tex.0,
gles_frame,
let program = Shaders::get_from_frame(gles_frame)
.blur_finish
.clone()
.expect("should be compiled");
if let Some(alpha_tex) = &self.alpha_tex {
gles_frame.with_context(|gl| unsafe {
gl.ActiveTexture(ffi::TEXTURE1);
gl.BindTexture(ffi::TEXTURE_2D, alpha_tex.tex_id());
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MIN_FILTER, ffi::LINEAR as i32);
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_MAG_FILTER, ffi::LINEAR as i32);
})?;
}
match &self.variant {
BlurVariant::Optimized { texture } => gles_frame.render_texture_from_to(
texture,
src,
downscaled_dst,
damage,
opaque_regions,
Transform::Normal,
1.,
Some(&program),
&self.uniforms,
),
BlurVariant::True { fx_buffers, config } => {
let mut fx_buffers = fx_buffers.borrow_mut();
fx_buffers.current_buffer = CurrentBuffer::Normal;
let shaders = Shaders::get_from_frame(gles_frame).blur.clone();
let vbos = RendererData::get_from_frame(gles_frame).vbos;
let supports_instancing = gles_frame
.capabilities()
.contains(&smithay::backend::renderer::gles::Capability::Instancing);
let debug = !gles_frame.debug_flags().is_empty();
let projection_matrix = glam::Mat3::from_cols_array(gles_frame.projection());
// Update the blur buffers.
// We use gl ffi directly to circumvent some stuff done by smithay
let blurred_texture = gles_frame.with_context(|gl| unsafe {
super::get_main_buffer_blur(
gl,
&mut fx_buffers,
&shaders,
*config,
projection_matrix,
self.scale as i32,
&vbos,
debug,
supports_instancing,
downscaled_dst,
self.alpha_tex.as_ref(),
)
})??;
gles_frame.render_texture_from_to(
&blurred_texture,
src,
downscaled_dst,
damage,
opaque_regions,
fx_buffers.transform(),
1.,
Some(&program),
&self.uniforms,
)
}
Self::TrueBlur {
fx_buffers,
scale,
corner_radius,
config,
alpha_tex,
..
} => draw_true_blur(
&mut fx_buffers.borrow_mut(),
gles_frame,
config,
*scale,
dst,
*corner_radius,
src,
damage,
opaque_regions,
self.alpha(),
false,
alpha_tex.as_ref(),
),
}
}
@@ -395,42 +483,14 @@ impl<'render> RenderElement<TtyRenderer<'render>> for BlurRenderElement {
) -> Result<(), TtyRendererError<'render>> {
let _span = trace_span!("blur_draw_tty").entered();
match self {
Self::Optimized { .. } => {
<BlurRenderElement as RenderElement<GlesRenderer>>::draw(
self,
frame.as_gles_frame(),
src,
dst,
damage,
opaque_regions,
)?;
}
Self::TrueBlur {
fx_buffers,
scale,
corner_radius,
config,
alpha_tex,
..
} => {
draw_true_blur(
&mut fx_buffers.borrow_mut(),
frame.as_gles_frame(),
config,
*scale,
dst,
*corner_radius,
src,
damage,
opaque_regions,
self.alpha(),
true,
alpha_tex.as_ref(),
)?;
}
}
<BlurRenderElement as RenderElement<GlesRenderer>>::draw(
self,
frame.as_gles_frame(),
src,
dst,
damage,
opaque_regions,
)?;
Ok(())
}

View File

@@ -17,10 +17,46 @@ uniform samplerExternalOES tex;
uniform sampler2D tex;
#endif
#if defined(EXTERNAL)
uniform samplerExternalOES alpha_tex;
#else
uniform sampler2D alpha_tex;
#endif
uniform float alpha;
varying vec2 v_coords;
uniform vec4 corner_radius;
uniform mat3 input_to_geo;
uniform vec2 geo_size;
uniform float niri_scale;
uniform float noise;
uniform float ignore_alpha;
float rounding_alpha(vec2 coords, vec2 size) {
vec2 center;
float radius;
if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
radius = corner_radius.x;
center = vec2(radius, radius);
} else if (size.x - corner_radius.y < coords.x && coords.y < corner_radius.y) {
radius = corner_radius.y;
center = vec2(size.x - radius, radius);
} else if (size.x - corner_radius.z < coords.x && size.y - corner_radius.z < coords.y) {
radius = corner_radius.z;
center = vec2(size.x - radius, size.y - radius);
} else if (coords.x < corner_radius.w && size.y - corner_radius.w < coords.y) {
radius = corner_radius.w;
center = vec2(radius, size.y - radius);
} else {
return 1.0;
}
float dist = distance(coords, center);
float half_px = 0.5 / niri_scale;
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
}
// Noise function copied from hyprland.
// I like the effect it gave, can be tweaked further
@@ -42,6 +78,8 @@ void main() {
}
}
vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0);
// Sample the texture.
vec4 color = texture2D(tex, v_coords);
@@ -49,6 +87,18 @@ void main() {
color = vec4(color.rgb, 1.0);
#endif
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);
} else {
// Apply corner rounding inside geometry.
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
@@ -57,11 +107,8 @@ void main() {
color.rgb += noiseAmount * noise;
}
color *= alpha;
if (color.a <= 0.0) {
discard;
}
color *= alpha;
gl_FragColor = color;
}

View File

@@ -13,12 +13,6 @@ uniform samplerExternalOES tex;
uniform sampler2D tex;
#endif
#if defined(EXTERNAL)
uniform samplerExternalOES alpha_tex;
#else
uniform sampler2D alpha_tex;
#endif
uniform float alpha;
varying vec2 v_coords;
@@ -31,7 +25,6 @@ uniform float niri_scale;
uniform vec2 geo_size;
uniform vec4 corner_radius;
uniform mat3 input_to_geo;
uniform float ignore_alpha;
float rounding_alpha(vec2 coords, vec2 size) {
vec2 center;
@@ -59,17 +52,6 @@ float rounding_alpha(vec2 coords, vec2 size) {
}
void main() {
if (alpha <= 0.0) {
discard;
}
if (ignore_alpha > 0.0) {
vec4 alpha_color = texture2D(alpha_tex, v_coords);
if (alpha_color.a < ignore_alpha) {
discard;
}
}
vec3 coords_geo = input_to_geo * vec3(v_coords, 1.0);
// Sample the texture.

View File

@@ -88,8 +88,6 @@ impl Shaders {
UniformName::new("geo_size", UniformType::_2f),
UniformName::new("corner_radius", UniformType::_4f),
UniformName::new("input_to_geo", UniformType::Matrix3x3),
UniformName::new("alpha_tex", UniformType::_1i),
UniformName::new("ignore_alpha", UniformType::_1f),
],
)
.map_err(|err| {
@@ -109,6 +107,12 @@ impl Shaders {
&[
UniformName::new("alpha", UniformType::_1f),
UniformName::new("noise", UniformType::_1f),
UniformName::new("niri_scale", UniformType::_1f),
UniformName::new("geo_size", UniformType::_2f),
UniformName::new("corner_radius", UniformType::_4f),
UniformName::new("input_to_geo", UniformType::Matrix3x3),
UniformName::new("alpha_tex", UniformType::_1i),
UniformName::new("ignore_alpha", UniformType::_1f),
],
)
.map_err(|e| warn!("error compiling clipped surface shader: {e:?}"))