perf(blur): only re-render optimized blur when actually necessary

This commit is contained in:
2025-11-28 21:42:23 +01:00
parent 92e5525753
commit bbe38aada7
6 changed files with 413 additions and 393 deletions

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,76 @@ 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 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: {e:?}"))
.ok()
})
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,
)
.map(Into::into),
)
})
.flatten()
.flatten()
.into_iter();
let location = location.to_physical_precise_round(scale).to_logical(scale);

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,18 @@ 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.borrow(),
blur_sample_area.to_i32_round(),
radius,
None,
0.,
self.scale,
animated_geo,
)
.into()
} else {
elem.into()
};
Some(elem)
.map(Into::into)
})
.flatten()
.into_iter();
let elem = (expanded_progress < 1.)

View File

@@ -1,58 +1,163 @@
// 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};
/// 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 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.
Optimized {
tex: OptimizedBlurTextureElement,
corner_radius: f32,
noise: f32,
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: &EffectsFramebuffers,
sample_area: Rectangle<i32, Logical>,
corner_radius: CornerRadius,
scale: f64,
output_size: Size<i32, Physical>,
config: Blur,
output_transform: Transform,
},
/// 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,
fx_buffers: EffectsFramebufffersUserData,
alpha_tex: Option<GlesTexture>,
},
geometry: Rectangle<f64, Logical>,
) -> Option<BlurRenderElement> {
if !self.config.on {
return None;
}
let mut inner = self.inner.borrow_mut();
let Some(inner) = inner.as_mut() else {
let elem = BlurRenderElement::new(
fx_buffers,
sample_area,
corner_radius,
scale,
self.config,
geometry,
self.alpha_tex.borrow().clone(),
);
*inner = Some(elem.clone());
return Some(elem);
};
if inner.sample_area == sample_area
&& inner.geometry == geometry
&& inner.scale == scale
&& inner.corner_radius == corner_radius
&& fx_buffers.output_size().w == inner.texture.size().w
&& fx_buffers.output_size().h == inner.texture.size().h
{
return Some(inner.clone());
}
inner.texture = fx_buffers.optimized_blur.clone();
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,
texture: GlesTexture,
uniforms: Vec<Uniform<'static>>,
sample_area: Rectangle<i32, Logical>,
alpha_tex: Option<GlesTexture>,
scale: f64,
commit: CommitCounter,
corner_radius: CornerRadius,
geometry: Rectangle<f64, Logical>,
}
impl BlurRenderElement {
@@ -65,165 +170,133 @@ 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,
pub fn new(
fx_buffers: &EffectsFramebuffers,
sample_area: Rectangle<i32, Logical>,
loc: Point<i32, Physical>,
corner_radius: f32,
corner_radius: CornerRadius,
scale: f64,
config: Blur,
) -> 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,
scale,
output_size: fx_buffers.output_size,
output_transform: fx_buffers.transform,
config,
}
}
#[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,
config: niri_config::Blur,
geometry: Rectangle<f64, Logical>,
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;
Self::TrueBlur {
let mut this = Self {
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(),
texture: fx_buffers.optimized_blur.clone(),
uniforms: Vec::with_capacity(7),
alpha_tex,
}
sample_area,
scale,
corner_radius,
geometry,
commit: CommitCounter::default(),
};
this.update_uniforms(fx_buffers, &config);
this
}
fn update_uniforms(&mut self, fx_buffers: &EffectsFramebuffers, config: &niri_config::Blur) {
let transform = Transform::Normal;
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() {
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))
}
}
self.sample_area.to_f64().to_physical_precise_round(scale)
}
fn alpha(&self) -> f32 {
@@ -235,11 +308,12 @@ impl Element for BlurRenderElement {
}
}
#[allow(unused)]
#[allow(clippy::too_many_arguments)]
fn draw_true_blur(
fx_buffers: &mut EffectsFramebuffers,
gles_frame: &mut GlesFrame,
config: &Blur,
config: &niri_config::Blur,
scale: f64,
dst: Rectangle<i32, Physical>,
corner_radius: f32,
@@ -336,47 +410,39 @@ 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,
src,
downscaled_dst,
damage,
opaque_regions,
)
}
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(),
),
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);
})?;
}
gles_frame.render_texture_from_to(
&self.texture,
src,
downscaled_dst,
damage,
opaque_regions,
Transform::Normal,
1.0,
Some(&program),
&self.uniforms,
)
}
fn underlying_storage(&self, _: &mut GlesRenderer) -> Option<UnderlyingStorage<'_>> {
@@ -395,42 +461,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:?}"))