feat(blur,clipped_surface): move responsibility for masking content alpha out of blur

Closes #3
This commit is contained in:
2025-11-28 15:08:10 +01:00
parent 9ad3a6931f
commit 9effba3dd5
9 changed files with 191 additions and 182 deletions

View File

@@ -16,7 +16,9 @@ use crate::layout::shadow::Shadow;
use crate::niri_render_elements;
use crate::render_helpers::blur::element::BlurRenderElement;
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};
@@ -59,6 +61,7 @@ niri_render_elements! {
SolidColor = SolidColorRenderElement,
Shadow = ShadowRenderElement,
Blur = BlurRenderElement,
ClippedBlur = ClippedSurfaceRenderElement<BlurRenderElement>,
}
}
@@ -195,6 +198,7 @@ impl MappedLayer {
let location = location + self.bob_offset();
let mut gles_elems: Option<Vec<LayerSurfaceRenderElement<GlesRenderer>>> = None;
let ignore_alpha = self.rules.blur.ignore_alpha.unwrap_or_default().0;
if target.should_block_out(self.rules.block_out_from) {
// Round to physical pixels.
@@ -236,14 +240,16 @@ impl MappedLayer {
Kind::ScanoutCandidate,
);
gles_elems = Some(render_elements_from_surface_tree(
renderer.as_gles_renderer(),
surface,
buf_pos.to_physical_precise_round(scale),
scale,
alpha,
Kind::ScanoutCandidate,
));
gles_elems = (ignore_alpha > 0.).then(|| {
render_elements_from_surface_tree(
renderer.as_gles_renderer(),
surface,
buf_pos.to_physical_precise_round(scale),
scale,
alpha,
Kind::ScanoutCandidate,
)
});
};
let blur_elem = (self.blur_config.on
@@ -253,39 +259,77 @@ impl MappedLayer {
let fx_buffers = fx_buffers.borrow();
// TODO: respect sync point?
let alpha_tex = gles_elems
.and_then(|gles_elems| {
let transform = fx_buffers.transform();
let alpha_tex = (ignore_alpha > 0.)
.then(|| {
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: {e:?}"))
.ok()
})
})
.flatten()
.map(|r| r.0);
Some(
BlurRenderElement::new_optimized(
renderer,
&fx_buffers,
Rectangle::new(location, self.size).to_i32_round(),
location.to_physical_precise_round(self.scale),
self.rules
.geometry_corner_radius
.unwrap_or_default()
.top_left,
self.scale,
self.blur_config,
let radius = self.rules.geometry_corner_radius.unwrap_or_default();
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(),
)
.into()
} else {
elem.into()
};
Some(elem)
})
.flatten()
.into_iter();

View File

@@ -428,7 +428,8 @@ niri_render_elements! {
Border = BorderRenderElement,
Shadow = ShadowRenderElement,
Blur = BlurRenderElement,
ClippedSurface = ClippedSurfaceRenderElement<WaylandSurfaceRenderElement<R>, R>,
BlurClippedSurface = ClippedSurfaceRenderElement<BlurRenderElement>,
ClippedSurface = ClippedSurfaceRenderElement<WaylandSurfaceRenderElement<R>>,
Offscreen = OffscreenRenderElement,
ExtraDamage = ExtraDamage,
TabIndicator = TabIndicatorRenderElement,
@@ -1766,6 +1767,10 @@ impl<W: LayoutElement> Tile<W> {
let mut window_popups = None;
let mut rounded_corner_damage = None;
let has_border_shader = BorderRenderElement::has_shader(renderer);
let geo = Rectangle::new(window_render_loc, window_size);
let clip_shader = clip_to_geometry
.then(|| Shaders::get(renderer).clipped_surface.clone())
.flatten();
if resize_shader.is_none() && resize_fallback.is_none() {
let window = self.window.focused_window().render(
renderer,
@@ -1775,16 +1780,15 @@ impl<W: LayoutElement> Tile<W> {
target,
);
let geo = Rectangle::new(window_render_loc, window_size);
let radius = radius.fit_to(window_size.w as f32, window_size.h as f32);
let clip_shader = Shaders::get(renderer).clipped_surface.clone();
if clip_to_geometry && clip_shader.is_some() {
let damage = self.rounded_corner_damage.element();
rounded_corner_damage = Some(damage.with_location(window_render_loc).into());
}
let clip_shader = clip_shader.clone();
window_surface = Some(window.normal.into_iter().map(move |elem| match elem {
LayoutElementRenderElement::Wayland(elem) => {
// If we should clip to geometry, render a clipped window.
@@ -1801,6 +1805,8 @@ impl<W: LayoutElement> Tile<W> {
geo,
shader.clone(),
radius,
None,
0.,
)
.into();
}
@@ -1928,19 +1934,40 @@ impl<W: LayoutElement> Tile<W> {
let fx_buffers = fx_buffers?;
let fx_buffers = fx_buffers.borrow();
Some(
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 = 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(),
radius,
None,
0.,
)
.into(),
)
.into()
} else {
elem.into()
};
Some(elem)
})
.flatten()
.into_iter();

View File

@@ -81,7 +81,7 @@ use smithay::wayland::dmabuf::DmabufState;
use smithay::wayland::fractional_scale::FractionalScaleManagerState;
use smithay::wayland::idle_inhibit::IdleInhibitManagerState;
use smithay::wayland::idle_notify::IdleNotifierState;
use smithay::wayland::input_method::{InputMethodManagerState, InputMethodSeat};
use smithay::wayland::input_method::InputMethodManagerState;
use smithay::wayland::keyboard_shortcuts_inhibit::{
KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor,
};

View File

@@ -4,9 +4,7 @@ use niri_config::Blur;
use smithay::backend::renderer::element::texture::TextureRenderElement;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{
ffi, GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform,
};
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::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
@@ -34,7 +32,6 @@ pub enum BlurRenderElement {
output_size: Size<i32, Physical>,
config: Blur,
output_transform: Transform,
alpha_tex: Option<GlesTexture>,
},
/// Use true blur.
///
@@ -76,7 +73,6 @@ impl BlurRenderElement {
corner_radius: f32,
scale: f64,
config: Blur,
alpha_tex: Option<GlesTexture>,
) -> Self {
let texture = fx_buffers.optimized_blur.clone();
@@ -109,7 +105,6 @@ impl BlurRenderElement {
scale,
output_size: fx_buffers.output_size,
output_transform: fx_buffers.transform,
alpha_tex,
config,
}
}
@@ -342,16 +337,7 @@ impl RenderElement<GlesRenderer> for BlurRenderElement {
let _span = trace_span!("blur_draw_gles").entered();
match self {
Self::Optimized {
tex,
corner_radius,
noise,
scale,
output_size,
output_transform,
config,
alpha_tex,
} => {
Self::Optimized { tex, scale, .. } => {
let downscaled_dst = Rectangle::new(
dst.loc,
Size::from((
@@ -360,66 +346,14 @@ impl RenderElement<GlesRenderer> for BlurRenderElement {
)),
);
let program = Shaders::get_from_frame(gles_frame).blur_finish.clone();
let gles_frame: &mut GlesFrame = gles_frame;
let geo = output_transform.transform_rect_in(dst, output_size);
gles_frame.override_default_tex_program(
program.unwrap(),
vec![
Uniform::new(
"geo",
[
geo.loc.x as f32,
geo.loc.y as f32,
geo.size.w as f32,
geo.size.h as f32,
],
),
Uniform::new("corner_radius", *corner_radius),
Uniform::new("output_size", [output_size.w as f32, output_size.h as f32]),
Uniform::new("noise", *noise),
Uniform::new("alpha", self.alpha()),
Uniform::new(
"ignore_alpha",
if alpha_tex.is_some() {
config.ignore_alpha.0 as f32
} else {
0.
},
),
Uniform::new("alpha_tex", 1),
],
);
if let Some(alpha_tex) = 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,
);
})?;
}
let res = <TextureRenderElement<GlesTexture> as RenderElement<GlesRenderer>>::draw(
<TextureRenderElement<GlesTexture> as RenderElement<GlesRenderer>>::draw(
&tex.0,
gles_frame,
src,
downscaled_dst,
damage,
opaque_regions,
);
gles_frame.clear_tex_program_override();
res
)
}
Self::TrueBlur {
fx_buffers,

View File

@@ -1,31 +1,28 @@
use std::marker::PhantomData;
use glam::{Mat3, Vec2};
use niri_config::CornerRadius;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{
GlesError, GlesFrame, GlesRenderer, GlesTexProgram, Uniform,
ffi, GlesError, GlesFrame, GlesRenderer, GlesTexProgram, GlesTexture, Uniform,
};
use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions};
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
use super::damage::ExtraDamage;
use super::renderer::{AsGlesFrame as _, NiriRenderer};
use super::renderer::AsGlesFrame as _;
use super::shaders::mat3_uniform;
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
#[derive(Debug)]
pub struct ClippedSurfaceRenderElement<E, R>
pub struct ClippedSurfaceRenderElement<E>
where
E: RenderElement<R>,
R: NiriRenderer,
E: Element,
{
inner: E,
program: GlesTexProgram,
corner_radius: CornerRadius,
geometry: Rectangle<f64, Logical>,
uniforms: Vec<Uniform<'static>>,
marker: PhantomData<R>,
alpha_tex: Option<GlesTexture>,
}
#[derive(Debug, Default, Clone)]
@@ -34,11 +31,11 @@ pub struct RoundedCornerDamage {
corner_radius: CornerRadius,
}
impl<E, R> ClippedSurfaceRenderElement<E, R>
impl<E> ClippedSurfaceRenderElement<E>
where
E: RenderElement<R>,
R: NiriRenderer,
E: Element,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
elem: E,
view_src: Rectangle<f64, Logical>,
@@ -47,6 +44,8 @@ where
geometry: Rectangle<f64, Logical>,
program: GlesTexProgram,
corner_radius: CornerRadius,
alpha_tex: Option<GlesTexture>,
ignore_alpha: f64,
) -> Self {
let elem_geo = elem.geometry(scale);
@@ -80,20 +79,25 @@ where
* Mat3::from_scale(buf_size / src_size)
* Mat3::from_translation(-src_loc / buf_size);
let uniforms = vec![
let mut uniforms = vec![
Uniform::new("niri_scale", scale.x as f32),
Uniform::new("geo_size", (geometry.size.w as f32, geometry.size.h as f32)),
Uniform::new("corner_radius", <[f32; 4]>::from(corner_radius)),
mat3_uniform("input_to_geo", input_to_geo),
];
if alpha_tex.is_some() && ignore_alpha > 0. {
uniforms.push(Uniform::new("alpha_tex", 1));
uniforms.push(Uniform::new("ignore_alpha", ignore_alpha as f32));
}
Self {
inner: elem,
program,
corner_radius,
geometry,
uniforms,
marker: PhantomData,
alpha_tex,
}
}
@@ -148,10 +152,9 @@ where
}
}
impl<E, R> Element for ClippedSurfaceRenderElement<E, R>
impl<E> Element for ClippedSurfaceRenderElement<E>
where
E: RenderElement<R>,
R: NiriRenderer,
E: Element,
{
fn id(&self) -> &Id {
self.inner.id()
@@ -226,7 +229,7 @@ where
}
}
impl<E> RenderElement<GlesRenderer> for ClippedSurfaceRenderElement<E, GlesRenderer>
impl<E> RenderElement<GlesRenderer> for ClippedSurfaceRenderElement<E>
where
E: RenderElement<GlesRenderer>,
{
@@ -239,6 +242,16 @@ where
opaque_regions: &[Rectangle<i32, Physical>],
) -> Result<(), GlesError> {
frame.override_default_tex_program(self.program.clone(), self.uniforms.clone());
if let Some(alpha_tex) = &self.alpha_tex {
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);
})?;
}
RenderElement::<GlesRenderer>::draw(&self.inner, frame, src, dst, damage, opaque_regions)?;
frame.clear_tex_program_override();
Ok(())
@@ -251,8 +264,7 @@ where
}
}
impl<'render, E> RenderElement<TtyRenderer<'render>>
for ClippedSurfaceRenderElement<E, TtyRenderer<'render>>
impl<'render, E> RenderElement<TtyRenderer<'render>> for ClippedSurfaceRenderElement<E>
where
E: RenderElement<TtyRenderer<'render>>,
{
@@ -267,6 +279,16 @@ where
frame
.as_gles_frame()
.override_default_tex_program(self.program.clone(), self.uniforms.clone());
if let Some(alpha_tex) = &self.alpha_tex {
frame.as_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);
})?;
}
RenderElement::draw(&self.inner, frame, src, dst, damage, opaque_regions)?;
frame.as_gles_frame().clear_tex_program_override();
Ok(())

View File

@@ -28,30 +28,9 @@ varying vec2 v_coords;
uniform vec4 geo;
uniform vec2 output_size;
uniform float corner_radius;
uniform float noise;
uniform float ignore_alpha;
float rounding_alpha(vec2 coords, vec2 size, float radius) {
vec2 center;
if (coords.x < radius && coords.y < radius) {
center = vec2(radius, radius);
} else if (coords.x > size.x - radius && coords.y < radius) {
center = vec2(size.x - radius, radius);
} else if (coords.x > size.x - radius && coords.y > size.y - radius) {
center = vec2(size.x - radius, size.y - radius);
} else if (coords.x < radius && coords.y > size.y - radius) {
center = vec2(radius, size.y - radius);
} else {
return 1.0;
}
float dist = distance(coords, center);
float half_px = 0.5 ;
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
float hash(vec2 p) {
@@ -79,19 +58,10 @@ void main() {
color = vec4(color.rgb, 1.0);
#endif
// This shader exists to make blur rounding correct.
//
// Since we are scr-ing a texture that is the size of the output, the v_coords are always
// relative to the output. This corresponds to gl_FragCoord.
vec2 size = geo.zw;
// NOTE: this is incorrect when rendering in winit, since y is inverted,
// but on tty produces the correct result, which is all that matters
vec2 loc = gl_FragCoord.xy - geo.xy;
if (noise > 0.0) {
// Add noise fx
// This can be used to achieve a glass look
float noiseHash = hash(loc / size);
float noiseHash = hash(v_coords);
float noiseAmount = (mod(noiseHash, 1.0) - 0.5);
color.rgb += noiseAmount * noise;
}
@@ -102,11 +72,6 @@ void main() {
discard;
}
// Apply corner rounding inside geometry.
if (corner_radius > 0.0) {
color *= rounding_alpha(loc, size, corner_radius);
}
gl_FragColor = color;
}

View File

@@ -13,6 +13,12 @@ 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;
@@ -25,6 +31,7 @@ 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;
@@ -52,6 +59,17 @@ 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,6 +88,8 @@ 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| {
@@ -105,13 +107,8 @@ impl Shaders {
.compile_custom_texture_shader(
include_str!("blur_finish.frag"),
&[
UniformName::new("output_size", UniformType::_2f),
UniformName::new("corner_radius", UniformType::_1f),
UniformName::new("alpha", UniformType::_1f),
UniformName::new("noise", UniformType::_1f),
UniformName::new("geo", UniformType::_4f),
UniformName::new("ignore_alpha", UniformType::_1f),
UniformName::new("alpha_tex", UniformType::_1i),
],
)
.map_err(|e| warn!("error compiling clipped surface shader: {e:?}"))

View File

@@ -112,7 +112,7 @@ pub enum MruCloseRequest {
niri_render_elements! {
ThumbnailRenderElement<R> => {
LayoutElement = LayoutElementRenderElement<R>,
ClippedSurface = ClippedSurfaceRenderElement<WaylandSurfaceRenderElement<R>, R>,
ClippedSurface = ClippedSurfaceRenderElement<WaylandSurfaceRenderElement<R>>,
Border = BorderRenderElement,
}
}
@@ -401,6 +401,8 @@ impl Thumbnail {
geo,
shader.clone(),
radius,
None,
0.,
);
return ThumbnailRenderElement::ClippedSurface(elem);
}