diff --git a/src/layer/mapped.rs b/src/layer/mapped.rs index 81bc327a..fb81c375 100644 --- a/src/layer/mapped.rs +++ b/src/layer/mapped.rs @@ -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>> = 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); diff --git a/src/layout/tile.rs b/src/layout/tile.rs index 2aff9823..5528cacc 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -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 { 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 Tile { 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 Tile { 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 Tile { 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 Tile { .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.) diff --git a/src/render_helpers/blur/element.rs b/src/render_helpers/blur/element.rs index d9a5c10f..1fae7b3a 100644 --- a/src/render_helpers/blur/element.rs +++ b/src/render_helpers/blur/element.rs @@ -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); + +impl CommitTracker { + pub fn new() -> Self { + Self(Default::default()) + } + + pub fn from_elements<'a, E: Element + 'a>(elems: impl Iterator) -> Self { + Self( + elems + .map(|e| (e.id().clone(), e.current_commit())) + .collect(), + ) + } + + pub fn update<'a, E: Element + 'a>(&mut self, elems: impl Iterator) { + *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>, + alpha_tex: RefCell>, + commit_tracker: RefCell, +} + +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, + corner_radius: CornerRadius, scale: f64, - output_size: Size, - 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, - size: Size, - corner_radius: f32, - loc: Point, - config: Blur, - // FIXME: Use DamageBag and expand it as needed? - commit_counter: CommitCounter, - fx_buffers: EffectsFramebufffersUserData, - alpha_tex: Option, - }, + geometry: Rectangle, + ) -> Option { + 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>, + sample_area: Rectangle, + alpha_tex: Option, + scale: f64, + commit: CommitCounter, + corner_radius: CornerRadius, + geometry: Rectangle, } 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, - loc: Point, - 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, - loc: Point, - corner_radius: f32, - scale: f64, - config: Blur, - zoom: f64, + config: niri_config::Blur, + geometry: Rectangle, alpha_tex: Option, ) -> 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 = 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) -> Point { - match self { - BlurRenderElement::Optimized { tex, .. } => tex.location(scale), - BlurRenderElement::TrueBlur { loc, .. } => *loc, - } + self.commit } fn src(&self) -> Rectangle { - 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, - commit: Option, - ) -> DamageSet { - 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) -> OpaqueRegions { - 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) -> Rectangle { - 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, corner_radius: f32, @@ -336,47 +410,39 @@ impl RenderElement 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, + )), + ); - as RenderElement>::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> { @@ -395,42 +461,14 @@ impl<'render> RenderElement> for BlurRenderElement { ) -> Result<(), TtyRendererError<'render>> { let _span = trace_span!("blur_draw_tty").entered(); - match self { - Self::Optimized { .. } => { - >::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(), - )?; - } - } + >::draw( + self, + frame.as_gles_frame(), + src, + dst, + damage, + opaque_regions, + )?; Ok(()) } diff --git a/src/render_helpers/shaders/blur_finish.frag b/src/render_helpers/shaders/blur_finish.frag index b16a0543..2a521930 100644 --- a/src/render_helpers/shaders/blur_finish.frag +++ b/src/render_helpers/shaders/blur_finish.frag @@ -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; } diff --git a/src/render_helpers/shaders/clipped_surface.frag b/src/render_helpers/shaders/clipped_surface.frag index 6790b38e..42a0a0f6 100644 --- a/src/render_helpers/shaders/clipped_surface.frag +++ b/src/render_helpers/shaders/clipped_surface.frag @@ -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. diff --git a/src/render_helpers/shaders/mod.rs b/src/render_helpers/shaders/mod.rs index 3b2054f7..c039c840 100644 --- a/src/render_helpers/shaders/mod.rs +++ b/src/render_helpers/shaders/mod.rs @@ -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:?}"))