use glam::{Mat3, Vec2}; use niri_config::CornerRadius; use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement; use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage}; use smithay::backend::renderer::gles::{ GlesError, GlesFrame, GlesRenderer, GlesTexProgram, 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::shaders::{mat3_uniform, Shaders}; use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError}; #[derive(Debug)] pub struct ClippedSurfaceRenderElement { inner: WaylandSurfaceRenderElement, program: GlesTexProgram, corner_radius: CornerRadius, geometry: Rectangle, uniforms: Vec>, } #[derive(Debug, Default, Clone)] pub struct RoundedCornerDamage { damage: ExtraDamage, corner_radius: CornerRadius, } impl ClippedSurfaceRenderElement { pub fn new( elem: WaylandSurfaceRenderElement, scale: Scale, geometry: Rectangle, program: GlesTexProgram, corner_radius: CornerRadius, ) -> Self { let elem_geo = elem.geometry(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 geo = geometry.to_physical_precise_round(scale); let geo_loc = Vec2::new(geo.loc.x, geo.loc.y); let geo_size = Vec2::new(geo.size.w, geo.size.h); let buf_size = elem.buffer_size(); let buf_size = Vec2::new(buf_size.w as f32, buf_size.h as f32); let view = elem.view(); 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 = elem.transform(); // HACK: ??? for some reason flipped ones are fine. let transform = match transform { Transform::_90 => Transform::_270, Transform::_270 => Transform::_90, x => x, }; 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); let 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), ]; Self { inner: elem, program, corner_radius, geometry, uniforms, } } pub fn shader(renderer: &mut R) -> Option<&GlesTexProgram> { Shaders::get(renderer).clipped_surface.as_ref() } pub fn will_clip( elem: &WaylandSurfaceRenderElement, scale: Scale, geometry: Rectangle, corner_radius: CornerRadius, ) -> bool { let elem_geo = elem.geometry(scale); let geo = geometry.to_physical_precise_round(scale); if corner_radius == CornerRadius::default() { !geo.contains_rect(elem_geo) } else { let corners = Self::rounded_corners(geometry, corner_radius); let corners = corners .into_iter() .map(|rect| rect.to_physical_precise_up(scale)); let geo = Rectangle::subtract_rects_many([geo], corners); !Rectangle::subtract_rects_many([elem_geo], geo).is_empty() } } fn rounded_corners( geo: Rectangle, corner_radius: CornerRadius, ) -> [Rectangle; 4] { let top_left = corner_radius.top_left as f64; let top_right = corner_radius.top_right as f64; let bottom_right = corner_radius.bottom_right as f64; let bottom_left = corner_radius.bottom_left as f64; [ Rectangle::new(geo.loc, Size::from((top_left, top_left))), Rectangle::new( Point::from((geo.loc.x + geo.size.w - top_right, geo.loc.y)), Size::from((top_right, top_right)), ), Rectangle::new( Point::from(( geo.loc.x + geo.size.w - bottom_right, geo.loc.y + geo.size.h - bottom_right, )), Size::from((bottom_right, bottom_right)), ), Rectangle::new( Point::from((geo.loc.x, geo.loc.y + geo.size.h - bottom_left)), Size::from((bottom_left, bottom_left)), ), ] } } impl Element for ClippedSurfaceRenderElement { fn id(&self) -> &Id { self.inner.id() } fn current_commit(&self) -> CommitCounter { self.inner.current_commit() } fn geometry(&self, scale: Scale) -> Rectangle { self.inner.geometry(scale) } fn src(&self) -> Rectangle { self.inner.src() } fn transform(&self) -> Transform { self.inner.transform() } fn damage_since( &self, scale: Scale, commit: Option, ) -> DamageSet { // FIXME: radius changes need to cause damage. let damage = self.inner.damage_since(scale, commit); // Intersect with geometry, since we're clipping by it. let mut geo = self.geometry.to_physical_precise_round(scale); geo.loc -= self.geometry(scale).loc; damage .into_iter() .filter_map(|rect| rect.intersection(geo)) .collect() } fn opaque_regions(&self, scale: Scale) -> OpaqueRegions { let regions = self.inner.opaque_regions(scale); // Intersect with geometry, since we're clipping by it. let mut geo = self.geometry.to_physical_precise_round(scale); geo.loc -= self.geometry(scale).loc; let regions = regions .into_iter() .filter_map(|rect| rect.intersection(geo)); // Subtract the rounded corners. if self.corner_radius == CornerRadius::default() { regions.collect() } else { let corners = Self::rounded_corners(self.geometry, self.corner_radius); let elem_loc = self.geometry(scale).loc; let corners = corners.into_iter().map(|rect| { let mut rect = rect.to_physical_precise_up(scale); rect.loc -= elem_loc; rect }); OpaqueRegions::from_slice(&Rectangle::subtract_rects_many(regions, corners)) } } fn alpha(&self) -> f32 { self.inner.alpha() } fn kind(&self) -> Kind { self.inner.kind() } } impl RenderElement for ClippedSurfaceRenderElement { fn draw( &self, frame: &mut GlesFrame<'_, '_>, src: Rectangle, dst: Rectangle, damage: &[Rectangle], opaque_regions: &[Rectangle], ) -> Result<(), GlesError> { frame.override_default_tex_program(self.program.clone(), self.uniforms.clone()); RenderElement::::draw(&self.inner, frame, src, dst, damage, opaque_regions)?; frame.clear_tex_program_override(); Ok(()) } fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option> { // If scanout for things other than Wayland buffers is implemented, this will need to take // the target GPU into account. None } } impl<'render> RenderElement> for ClippedSurfaceRenderElement> { fn draw( &self, frame: &mut TtyFrame<'render, '_, '_>, src: Rectangle, dst: Rectangle, damage: &[Rectangle], opaque_regions: &[Rectangle], ) -> Result<(), TtyRendererError<'render>> { frame .as_gles_frame() .override_default_tex_program(self.program.clone(), self.uniforms.clone()); RenderElement::draw(&self.inner, frame, src, dst, damage, opaque_regions)?; frame.as_gles_frame().clear_tex_program_override(); Ok(()) } fn underlying_storage( &self, _renderer: &mut TtyRenderer<'render>, ) -> Option> { // If scanout for things other than Wayland buffers is implemented, this will need to take // the target GPU into account. None } } impl RoundedCornerDamage { pub fn set_size(&mut self, size: Size) { self.damage.set_size(size); } pub fn set_corner_radius(&mut self, corner_radius: CornerRadius) { if self.corner_radius == corner_radius { return; } // FIXME: make the damage granular. self.corner_radius = corner_radius; self.damage.damage_all(); } pub fn element(&self) -> ExtraDamage { self.damage.clone() } }