// Taken and modified from niri, licensed GPL-3. use std::borrow::{Borrow, BorrowMut}; use cgmath::{Matrix3, Vector2}; use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform}; use smithay::{ backend::renderer::{ ImportAll, ImportMem, Renderer, element::{ Element, Id, Kind, RenderElement, UnderlyingStorage, surface::WaylandSurfaceRenderElement, }, gles::{GlesFrame, GlesRenderer, GlesTexProgram, Uniform, UniformValue}, utils::{CommitCounter, DamageSet, OpaqueRegions}, }, utils::user_data::UserDataMap, }; use crate::backend::render::element::AsGlowRenderer; pub static CLIPPING_SHADER: &str = include_str!("./shaders/clipped_surface.frag"); pub struct ClippingShader(pub GlesTexProgram); impl ClippingShader { pub fn get(renderer: &R) -> GlesTexProgram { Borrow::::borrow(renderer.glow_renderer()) .egl_context() .user_data() .get::() .expect("Custom Shaders not initialized") .0 .clone() } } #[derive(Debug)] pub struct ClippedSurfaceRenderElement where R: Renderer + ImportAll + ImportMem, { inner: WaylandSurfaceRenderElement, program: GlesTexProgram, radius: [u8; 4], geometry: Rectangle, uniforms: Vec>, } impl ClippedSurfaceRenderElement where R: Renderer + ImportAll + ImportMem, { pub fn new( renderer: &mut R, elem: WaylandSurfaceRenderElement, scale: Scale, geometry: Rectangle, radius: [u8; 4], ) -> Self where R: AsGlowRenderer, { let elem_geo = elem.geometry(scale); let geo: Rectangle = geometry.to_physical_precise_round(scale); let buf_size = elem.buffer_size(); let view = elem.view(); let transform = elem.transform(); let transform_matrix = Matrix3::::from_translation(Vector2::new(0.5, 0.5)) * transform.matrix() * Matrix3::::from_translation(-Vector2::new(0.5, 0.5)); let geo_scale = { let Scale { x, y } = elem_geo.size.to_f64() / geo.size.to_f64(); Matrix3::from_nonuniform_scale(x as f32, y as f32) }; let geo_translation = { let offset = (elem_geo.loc - geo.loc).to_f64(); Matrix3::from_translation(Vector2::new( (offset.x / elem_geo.size.w as f64) as f32, (offset.y / elem_geo.size.h as f64) as f32, )) }; let buf_scale = { let Scale { x, y } = buf_size.to_f64() / view.src.size.to_f64(); Matrix3::from_nonuniform_scale(x as f32, y as f32) }; let buf_translation = Matrix3::from_translation(Vector2::new( (view.src.loc.x / buf_size.w as f64) as f32, (view.src.loc.y / buf_size.h as f64) as f32, )); let input_to_geo = transform_matrix * geo_scale * geo_translation * buf_scale * buf_translation; let uniforms = vec![ Uniform::new("geo_size", (geometry.size.w as f32, geometry.size.h as f32)), Uniform::new( "corner_radius", [ radius[3] as f32, radius[1] as f32, radius[0] as f32, radius[2] as f32, ], ), Uniform::new( "input_to_geo", UniformValue::Matrix3x3 { matrices: vec![*AsRef::<[f32; 9]>::as_ref(&input_to_geo)], transpose: false, }, ), ]; Self { inner: elem, program: ClippingShader::get(renderer), radius, geometry, uniforms, } } pub fn will_clip( elem: &WaylandSurfaceRenderElement, scale: Scale, geometry: Rectangle, radius: [u8; 4], ) -> bool { let elem_geo = elem.geometry(scale); let geo = geometry.to_physical_precise_round(scale); let corners = Self::rounded_corners(geometry, 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, radius: [u8; 4], ) -> [Rectangle; 4] { let top_left = radius[3] as f64; let top_right = radius[1] as f64; let bottom_right = radius[0] as f64; let bottom_left = radius[2] 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 where R: Renderer + ImportAll + ImportMem, { 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. let corners = Self::rounded_corners(self.geometry, self.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 where R: AsGlowRenderer + Renderer + ImportAll + ImportMem, R::TextureId: 'static, { fn draw( &self, frame: &mut R::Frame<'_, '_>, src: Rectangle, dst: Rectangle, damage: &[Rectangle], opaque_regions: &[Rectangle], cache: Option<&UserDataMap>, ) -> Result<(), R::Error> { BorrowMut::::borrow_mut(::glow_frame_mut(frame)) .override_default_tex_program(self.program.clone(), self.uniforms.clone()); self.inner .draw(frame, src, dst, damage, opaque_regions, cache)?; BorrowMut::::borrow_mut(::glow_frame_mut(frame)) .clear_tex_program_override(); Ok(()) } fn underlying_storage(&self, _renderer: &mut R) -> Option> { None } }