From 42cef79c699c0f03b4bb99c4278169c48d9a5bd0 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Wed, 1 May 2024 19:06:08 +0400 Subject: Implement rounded window corners --- Cargo.lock | 4 +- niri-config/src/lib.rs | 189 ++++++++ niri-visual-tests/src/cases/gradient_angle.rs | 33 +- niri-visual-tests/src/cases/gradient_area.rs | 34 +- src/layout/focus_ring.rs | 154 +++++-- src/layout/tile.rs | 132 +++++- src/render_helpers/border.rs | 168 +++++++ src/render_helpers/clipped_surface.rs | 269 +++++++++++ src/render_helpers/gradient.rs | 135 ------ src/render_helpers/mod.rs | 6 +- src/render_helpers/primary_gpu_pixel_shader.rs | 97 ---- .../primary_gpu_pixel_shader_with_textures.rs | 436 ------------------ src/render_helpers/resize.rs | 21 +- src/render_helpers/shader_element.rs | 506 +++++++++++++++++++++ src/render_helpers/shaders/border.frag | 87 ++++ src/render_helpers/shaders/clipped_surface.frag | 77 ++++ src/render_helpers/shaders/gradient_border.frag | 35 -- src/render_helpers/shaders/mod.rs | 67 ++- src/render_helpers/shaders/resize-epilogue.frag | 9 - src/render_helpers/shaders/resize-prelude.frag | 22 - src/render_helpers/shaders/resize_epilogue.frag | 27 ++ src/render_helpers/shaders/resize_prelude.frag | 51 +++ src/window/mod.rs | 16 +- wiki/Configuration:-Window-Rules.md | 74 +++ wiki/examples/resize-custom-shader.frag | 164 ------- wiki/examples/resize_custom_shader.frag | 164 +++++++ wiki/img/border-radius-clip.png | 3 + wiki/img/clip-to-geometry.png | 3 + wiki/img/different-corner-radius.png | 3 + wiki/img/geometry-corner-radius.png | 3 + 30 files changed, 1984 insertions(+), 1005 deletions(-) create mode 100644 src/render_helpers/border.rs create mode 100644 src/render_helpers/clipped_surface.rs delete mode 100644 src/render_helpers/gradient.rs delete mode 100644 src/render_helpers/primary_gpu_pixel_shader.rs delete mode 100644 src/render_helpers/primary_gpu_pixel_shader_with_textures.rs create mode 100644 src/render_helpers/shader_element.rs create mode 100644 src/render_helpers/shaders/border.frag create mode 100644 src/render_helpers/shaders/clipped_surface.frag delete mode 100644 src/render_helpers/shaders/gradient_border.frag delete mode 100644 src/render_helpers/shaders/resize-epilogue.frag delete mode 100644 src/render_helpers/shaders/resize-prelude.frag create mode 100644 src/render_helpers/shaders/resize_epilogue.frag create mode 100644 src/render_helpers/shaders/resize_prelude.frag delete mode 100644 wiki/examples/resize-custom-shader.frag create mode 100644 wiki/examples/resize_custom_shader.frag create mode 100644 wiki/img/border-radius-clip.png create mode 100644 wiki/img/clip-to-geometry.png create mode 100644 wiki/img/different-corner-radius.png create mode 100644 wiki/img/geometry-corner-radius.png diff --git a/Cargo.lock b/Cargo.lock index a211f438..4e0d3015 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3170,7 +3170,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/Smithay/smithay.git#7544c4d4b9ef89e2945bb626dd47518fedc5f660" +source = "git+https://github.com/Smithay/smithay.git#219b783609dab41114b8202e8be8e46523c7f4b5" dependencies = [ "appendlist", "bitflags 2.5.0", @@ -3242,7 +3242,7 @@ dependencies = [ [[package]] name = "smithay-drm-extras" version = "0.1.0" -source = "git+https://github.com/Smithay/smithay.git#7544c4d4b9ef89e2945bb626dd47518fedc5f660" +source = "git+https://github.com/Smithay/smithay.git#219b783609dab41114b8202e8be8e46523c7f4b5" dependencies = [ "drm", "edid-rs", diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index ca789528..f4728e61 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -722,6 +722,10 @@ pub struct WindowRule { pub draw_border_with_background: Option, #[knuffel(child, unwrap(argument))] pub opacity: Option, + #[knuffel(child)] + pub geometry_corner_radius: Option, + #[knuffel(child, unwrap(argument))] + pub clip_to_geometry: Option, #[knuffel(child, unwrap(argument))] pub block_out_from: Option, } @@ -748,6 +752,25 @@ impl PartialEq for Match { } } +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct CornerRadius { + pub top_left: f32, + pub top_right: f32, + pub bottom_right: f32, + pub bottom_left: f32, +} + +impl From for [f32; 4] { + fn from(value: CornerRadius) -> Self { + [ + value.top_left, + value.top_right, + value.bottom_right, + value.bottom_left, + ] + } +} + #[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)] pub enum BlockOutFrom { Screencast, @@ -1079,6 +1102,53 @@ impl BorderRule { } } +impl CornerRadius { + pub fn fit_to(self, width: f32, height: f32) -> Self { + // Like in CSS: https://drafts.csswg.org/css-backgrounds/#corner-overlap + let reduction = f32::min( + f32::min( + width / (self.top_left + self.top_right), + width / (self.bottom_left + self.bottom_right), + ), + f32::min( + height / (self.top_left + self.bottom_left), + height / (self.top_right + self.bottom_right), + ), + ); + let reduction = f32::min(1., reduction); + + Self { + top_left: self.top_left * reduction, + top_right: self.top_right * reduction, + bottom_right: self.bottom_right * reduction, + bottom_left: self.bottom_left * reduction, + } + } + + pub fn expanded_by(self, width: f32) -> Self { + // Preserve zero radius. + if self == Self::default() { + return self; + } + + Self { + top_left: self.top_left + width, + top_right: self.top_right + width, + bottom_right: self.bottom_right + width, + bottom_left: self.bottom_left + width, + } + } + + pub fn scaled_by(self, scale: f32) -> Self { + Self { + top_left: self.top_left * scale, + top_right: self.top_right * scale, + bottom_right: self.bottom_right * scale, + bottom_left: self.bottom_left * scale, + } + } +} + impl FromStr for Color { type Err = miette::Error; @@ -1606,6 +1676,125 @@ where } } +impl knuffel::Decode for CornerRadius +where + S: knuffel::traits::ErrorSpan, +{ + fn decode_node( + node: &knuffel::ast::SpannedNode, + ctx: &mut knuffel::decode::Context, + ) -> Result> { + // Check for unexpected type name. + if let Some(type_name) = &node.type_name { + ctx.emit_error(DecodeError::unexpected( + type_name, + "type name", + "no type name expected for this node", + )); + } + + let decode_radius = |ctx: &mut knuffel::decode::Context, + val: &knuffel::ast::Value| { + // Check for unexpected type name. + if let Some(typ) = &val.type_name { + ctx.emit_error(DecodeError::TypeName { + span: typ.span().clone(), + found: Some((**typ).clone()), + expected: knuffel::errors::ExpectedType::no_type(), + rust_type: "str", + }); + } + + // Decode both integers and floats. + let radius = match *val.literal { + knuffel::ast::Literal::Int(ref x) => f32::from(match x.try_into() { + Ok(x) => x, + Err(err) => { + ctx.emit_error(DecodeError::conversion(&val.literal, err)); + 0i16 + } + }), + knuffel::ast::Literal::Decimal(ref x) => match x.try_into() { + Ok(x) => x, + Err(err) => { + ctx.emit_error(DecodeError::conversion(&val.literal, err)); + 0. + } + }, + _ => { + ctx.emit_error(DecodeError::scalar_kind( + knuffel::decode::Kind::Int, + &val.literal, + )); + 0. + } + }; + + if radius < 0. { + ctx.emit_error(DecodeError::conversion(&val.literal, "radius must be >= 0")); + } + + radius + }; + + // Get the first argument. + let mut iter_args = node.arguments.iter(); + let val = iter_args + .next() + .ok_or_else(|| DecodeError::missing(node, "additional argument is required"))?; + + let top_left = decode_radius(ctx, val); + + let mut rv = CornerRadius { + top_left, + top_right: top_left, + bottom_right: top_left, + bottom_left: top_left, + }; + + if let Some(val) = iter_args.next() { + rv.top_right = decode_radius(ctx, val); + + let val = iter_args.next().ok_or_else(|| { + DecodeError::missing(node, "either 1 or 4 arguments are required") + })?; + rv.bottom_right = decode_radius(ctx, val); + + let val = iter_args.next().ok_or_else(|| { + DecodeError::missing(node, "either 1 or 4 arguments are required") + })?; + rv.bottom_left = decode_radius(ctx, val); + + // Check for unexpected following arguments. + if let Some(val) = iter_args.next() { + ctx.emit_error(DecodeError::unexpected( + &val.literal, + "argument", + "unexpected argument", + )); + } + } + + // Check for unexpected properties and children. + for name in node.properties.keys() { + ctx.emit_error(DecodeError::unexpected( + name, + "property", + format!("unexpected property `{}`", name.escape_default()), + )); + } + for child in node.children.as_ref().map(|lst| &lst[..]).unwrap_or(&[]) { + ctx.emit_error(DecodeError::unexpected( + child, + "node", + format!("unexpected node `{}`", child.node_name.escape_default()), + )); + } + + Ok(rv) + } +} + impl knuffel::Decode for Binds where S: knuffel::traits::ErrorSpan, diff --git a/niri-visual-tests/src/cases/gradient_angle.rs b/niri-visual-tests/src/cases/gradient_angle.rs index adbedff4..aada9862 100644 --- a/niri-visual-tests/src/cases/gradient_angle.rs +++ b/niri-visual-tests/src/cases/gradient_angle.rs @@ -3,7 +3,8 @@ use std::sync::atomic::Ordering; use std::time::Duration; use niri::animation::ANIMATION_SLOWDOWN; -use niri::render_helpers::gradient::GradientRenderElement; +use niri::render_helpers::border::BorderRenderElement; +use niri_config::CornerRadius; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; use smithay::utils::{Logical, Physical, Rectangle, Scale, Size}; @@ -60,17 +61,23 @@ impl TestCase for GradientAngle { let size = (size.w - a * 2, size.h - b * 2); let area = Rectangle::from_loc_and_size((a, b), size); - GradientRenderElement::new( - renderer, - Scale::from(1.), - area, - area, - [1., 0., 0., 1.], - [0., 1., 0., 1.], - self.angle - FRAC_PI_2, - ) - .into_iter() - .map(|elem| Box::new(elem) as _) - .collect() + BorderRenderElement::shader(renderer) + .map(|shader| { + BorderRenderElement::new( + shader.clone(), + Scale::from(1.), + area, + area, + [1., 0., 0., 1.], + [0., 1., 0., 1.], + self.angle - FRAC_PI_2, + area, + 0., + CornerRadius::default(), + ) + }) + .into_iter() + .map(|elem| Box::new(elem) as _) + .collect() } } diff --git a/niri-visual-tests/src/cases/gradient_area.rs b/niri-visual-tests/src/cases/gradient_area.rs index d47fb2ce..22aac0f0 100644 --- a/niri-visual-tests/src/cases/gradient_area.rs +++ b/niri-visual-tests/src/cases/gradient_area.rs @@ -4,8 +4,8 @@ use std::time::Duration; use niri::animation::ANIMATION_SLOWDOWN; use niri::layout::focus_ring::FocusRing; -use niri::render_helpers::gradient::GradientRenderElement; -use niri_config::Color; +use niri::render_helpers::border::BorderRenderElement; +use niri_config::{Color, CornerRadius}; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size}; @@ -85,7 +85,7 @@ impl TestCase for GradientArea { let g_loc = ((size.w - g_size.w) / 2, (size.h - g_size.h) / 2); let g_area = Rectangle::from_loc_and_size(g_loc, g_size); - self.border.update(g_size, true); + self.border.update(g_size, true, CornerRadius::default()); rv.extend( self.border .render( @@ -98,17 +98,23 @@ impl TestCase for GradientArea { ); rv.extend( - GradientRenderElement::new( - renderer, - Scale::from(1.), - area, - g_area, - [1., 0., 0., 1.], - [0., 1., 0., 1.], - FRAC_PI_4, - ) - .into_iter() - .map(|elem| Box::new(elem) as _), + BorderRenderElement::shader(renderer) + .map(|shader| { + BorderRenderElement::new( + shader.clone(), + Scale::from(1.), + area, + g_area, + [1., 0., 0., 1.], + [0., 1., 0., 1.], + FRAC_PI_4, + area, + 0., + CornerRadius::default(), + ) + }) + .into_iter() + .map(|elem| Box::new(elem) as _), ); rv diff --git a/src/layout/focus_ring.rs b/src/layout/focus_ring.rs index f8a70d93..d52bb08e 100644 --- a/src/layout/focus_ring.rs +++ b/src/layout/focus_ring.rs @@ -1,30 +1,32 @@ +use std::cmp::{max, min}; use std::iter::zip; use arrayvec::ArrayVec; -use niri_config::GradientRelativeTo; +use niri_config::{CornerRadius, GradientRelativeTo}; use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; use smithay::backend::renderer::element::Kind; use smithay::utils::{Logical, Point, Rectangle, Scale, Size}; use crate::niri_render_elements; -use crate::render_helpers::gradient::GradientRenderElement; +use crate::render_helpers::border::BorderRenderElement; use crate::render_helpers::renderer::NiriRenderer; #[derive(Debug)] pub struct FocusRing { - buffers: [SolidColorBuffer; 4], - locations: [Point; 4], - sizes: [Size; 4], + buffers: [SolidColorBuffer; 8], + locations: [Point; 8], + sizes: [Size; 8], full_size: Size, is_active: bool, is_border: bool, + radius: CornerRadius, config: niri_config::FocusRing, } niri_render_elements! { FocusRingRenderElement => { SolidColor = SolidColorRenderElement, - Gradient = GradientRenderElement, + Gradient = BorderRenderElement, } } @@ -37,6 +39,7 @@ impl FocusRing { full_size: Default::default(), is_active: false, is_border: false, + radius: Default::default(), config, } } @@ -45,24 +48,69 @@ impl FocusRing { self.config = config; } - pub fn update(&mut self, win_size: Size, is_border: bool) { + pub fn update(&mut self, win_size: Size, is_border: bool, radius: CornerRadius) { let width = i32::from(self.config.width); self.full_size = win_size + Size::from((width * 2, width * 2)); + let radius = radius.fit_to(self.full_size.w as f32, self.full_size.h as f32); + if is_border { - self.sizes[0] = Size::from((win_size.w + width * 2, width)); - self.sizes[1] = Size::from((win_size.w + width * 2, width)); - self.sizes[2] = Size::from((width, win_size.h)); - self.sizes[3] = Size::from((width, win_size.h)); + let top_left = max(width, radius.top_left.ceil() as i32); + let top_right = min( + self.full_size.w - top_left, + max(width, radius.top_right.ceil() as i32), + ); + let bottom_left = min( + self.full_size.h - top_left, + max(width, radius.bottom_left.ceil() as i32), + ); + let bottom_right = min( + self.full_size.h - top_right, + min( + self.full_size.w - bottom_left, + max(width, radius.bottom_right.ceil() as i32), + ), + ); + + // Top edge. + self.sizes[0] = Size::from((win_size.w + width * 2 - top_left - top_right, width)); + self.locations[0] = Point::from((-width + top_left, -width)); + + // Bottom edge. + self.sizes[1] = + Size::from((win_size.w + width * 2 - bottom_left - bottom_right, width)); + self.locations[1] = Point::from((-width + bottom_left, win_size.h)); + + // Left edge. + self.sizes[2] = Size::from((width, win_size.h + width * 2 - top_left - bottom_left)); + self.locations[2] = Point::from((-width, -width + top_left)); + + // Right edge. + self.sizes[3] = Size::from((width, win_size.h + width * 2 - top_right - bottom_right)); + self.locations[3] = Point::from((win_size.w, -width + top_right)); + + // Top-left corner. + self.sizes[4] = Size::from((top_left, top_left)); + self.locations[4] = Point::from((-width, -width)); + + // Top-right corner. + self.sizes[5] = Size::from((top_right, top_right)); + self.locations[5] = Point::from((win_size.w + width - top_right, -width)); + + // Bottom-right corner. + self.sizes[6] = Size::from((bottom_right, bottom_right)); + self.locations[6] = Point::from(( + win_size.w + width - bottom_right, + win_size.h + width - bottom_right, + )); + + // Bottom-left corner. + self.sizes[7] = Size::from((bottom_left, bottom_left)); + self.locations[7] = Point::from((-width, win_size.h + width - bottom_left)); for (buf, size) in zip(&mut self.buffers, self.sizes) { buf.resize(size); } - - self.locations[0] = Point::from((-width, -width)); - self.locations[1] = Point::from((-width, win_size.h)); - self.locations[2] = Point::from((-width, 0)); - self.locations[3] = Point::from((win_size.w, 0)); } else { self.sizes[0] = self.full_size; self.buffers[0].resize(self.sizes[0]); @@ -70,6 +118,7 @@ impl FocusRing { } self.is_border = is_border; + self.radius = radius; } pub fn set_active(&mut self, is_active: bool) { @@ -93,38 +142,83 @@ impl FocusRing { scale: Scale, view_size: Size, ) -> impl Iterator { - let mut rv = ArrayVec::<_, 4>::new(); + let mut rv = ArrayVec::<_, 8>::new(); if self.config.off { return rv.into_iter(); } + let border_width = -self.locations[0].y; + + // If drawing as a border with width = 0, then there's nothing to draw. + if self.is_border && border_width == 0 { + return rv.into_iter(); + } + let gradient = if self.is_active { self.config.active_gradient } else { self.config.inactive_gradient }; + let color = if self.is_active { + self.config.active_color + } else { + self.config.inactive_color + }; - let full_rect = Rectangle::from_loc_and_size(location + self.locations[0], self.full_size); + let offset = Point::from((border_width, border_width)); + let full_rect = Rectangle::from_loc_and_size(location - offset, self.full_size); let view_rect = Rectangle::from_loc_and_size((0, 0), view_size); + let border_width = if self.is_border { + // HACK: increase the border width used for the inner rounded corners a tiny bit to + // reduce background bleed. + border_width as f32 + 0.5 / scale.x as f32 + } else { + 0. + }; + let shader = BorderRenderElement::shader(renderer); + let mut push = |buffer, location: Point, size: Size| { - let elem = gradient.and_then(|gradient| { + let elem = if let Some(gradient) = gradient { let gradient_area = match gradient.relative_to { GradientRelativeTo::Window => full_rect, GradientRelativeTo::WorkspaceView => view_rect, }; - GradientRenderElement::new( - renderer, - scale, - Rectangle::from_loc_and_size(location, size), - gradient_area, - gradient.from.into(), - gradient.to.into(), - ((gradient.angle as f32) - 90.).to_radians(), - ) - .map(Into::into) - }); + shader.cloned().map(|shader| { + BorderRenderElement::new( + shader, + scale, + Rectangle::from_loc_and_size(location, size), + gradient_area, + gradient.from.into(), + gradient.to.into(), + ((gradient.angle as f32) - 90.).to_radians(), + full_rect, + border_width, + self.radius, + ) + .into() + }) + } else if self.radius != CornerRadius::default() { + shader.cloned().map(|shader| { + BorderRenderElement::new( + shader, + scale, + Rectangle::from_loc_and_size(location, size), + full_rect, + color.into(), + color.into(), + 0., + full_rect, + border_width, + self.radius, + ) + .into() + }) + } else { + None + }; let elem = elem.unwrap_or_else(|| { SolidColorRenderElement::from_buffer( diff --git a/src/layout/tile.rs b/src/layout/tile.rs index b5b1a674..f0e770f4 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -3,6 +3,7 @@ use std::cmp::max; use std::rc::Rc; use std::time::Duration; +use niri_config::CornerRadius; use smithay::backend::allocator::Fourcc; use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; use smithay::backend::renderer::element::utils::RescaleRenderElement; @@ -17,6 +18,8 @@ use super::{ }; use crate::animation::Animation; use crate::niri_render_elements; +use crate::render_helpers::border::BorderRenderElement; +use crate::render_helpers::clipped_surface::ClippedSurfaceRenderElement; use crate::render_helpers::offscreen::OffscreenRenderElement; use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::resize::ResizeRenderElement; @@ -76,6 +79,8 @@ niri_render_elements! { SolidColor = SolidColorRenderElement, Offscreen = RescaleRenderElement, Resize = ResizeRenderElement, + Border = BorderRenderElement, + ClippedSurface = ClippedSurfaceRenderElement, } } @@ -205,13 +210,26 @@ impl Tile { } } - let draw_border_with_background = self - .window - .rules() + let rules = self.window.rules(); + + let draw_border_with_background = rules .draw_border_with_background .unwrap_or_else(|| !self.window.has_ssd()); - self.border - .update(self.animated_window_size(), !draw_border_with_background); + let border_width = self.effective_border_width().unwrap_or(0); + let radius = if self.is_fullscreen { + CornerRadius::default() + } else { + rules + .geometry_corner_radius + .map_or(CornerRadius::default(), |radius| { + radius.expanded_by(border_width as f32) + }) + }; + self.border.update( + self.animated_window_size(), + !draw_border_with_background, + radius, + ); self.border.set_active(is_active); let draw_focus_ring_with_background = if self.effective_border_width().is_some() { @@ -219,8 +237,19 @@ impl Tile { } else { draw_border_with_background }; - self.focus_ring - .update(self.animated_tile_size(), !draw_focus_ring_with_background); + let radius = if self.is_fullscreen { + CornerRadius::default() + } else if self.effective_border_width().is_some() { + radius + } else { + rules.geometry_corner_radius.unwrap_or_default() + } + .expanded_by(self.focus_ring.width() as f32); + self.focus_ring.update( + self.animated_tile_size(), + !draw_focus_ring_with_background, + radius, + ); self.focus_ring.set_active(is_active); } @@ -573,6 +602,11 @@ impl Tile { .map_err(|err| warn!("error rendering window to texture: {err:?}")) .ok(); + let rules = self.window.rules(); + let clip_to_geometry = + !self.is_fullscreen && rules.clip_to_geometry == Some(true); + let corner_radius = rules.geometry_corner_radius.unwrap_or_default(); + if let Some((texture_current, _sync_point, texture_current_geo)) = current { let elem = ResizeRenderElement::new( shader, @@ -584,8 +618,12 @@ impl Tile { window_size, resize.anim.value() as f32, resize.anim.clamped_value().clamp(0., 1.) as f32, + corner_radius, + clip_to_geometry, alpha, ); + // FIXME: with split popups, this will use the resize element ID for + // popups, but we want the real IDs. self.window .set_offscreen_element_id(Some(elem.id().clone())); resize_shader = Some(elem.into()); @@ -610,14 +648,77 @@ impl Tile { } // If we're not resizing, render the window itself. - let mut window = None; + let mut window_surface = None; + let mut window_popups = None; if resize_shader.is_none() && resize_fallback.is_none() { - window = Some( - self.window - .render(renderer, window_render_loc, scale, alpha, target) - .into_iter() - .map(Into::into), - ); + let window = self + .window + .render(renderer, window_render_loc, scale, alpha, target); + + let geo = Rectangle::from_loc_and_size(window_render_loc, window_size); + + let rules = self.window.rules(); + let clip_to_geometry = !self.is_fullscreen && rules.clip_to_geometry == Some(true); + let radius = rules + .geometry_corner_radius + .unwrap_or_default() + .fit_to(window_size.w as f32, window_size.h as f32); + + let clip_shader = ClippedSurfaceRenderElement::shader(renderer).cloned(); + let border_shader = BorderRenderElement::shader(renderer).cloned(); + + 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. + if clip_to_geometry { + if let Some(shader) = clip_shader.clone() { + if ClippedSurfaceRenderElement::will_clip(&elem, scale, geo, radius) { + return ClippedSurfaceRenderElement::new( + elem, + scale, + geo, + shader.clone(), + radius, + ) + .into(); + } + } + } + + // Otherwise, render it normally. + LayoutElementRenderElement::Wayland(elem).into() + } + LayoutElementRenderElement::SolidColor(elem) => { + // In this branch we're rendering a blocked-out window with a solid + // color. We need to render it with a rounded corner shader even if + // clip_to_geometry is false, because in this case we're assuming that + // the unclipped window CSD already has corners rounded to the + // user-provided radius, so our blocked-out rendering should match that + // radius. + if radius != CornerRadius::default() { + if let Some(shader) = border_shader.clone() { + return BorderRenderElement::new( + shader, + scale, + elem.geometry(Scale::from(1.)).to_logical(1), + Rectangle::from_loc_and_size(Point::from((0, 0)), geo.size), + elem.color(), + elem.color(), + 0., + elem.geometry(Scale::from(1.)).to_logical(1), + 0., + radius, + ) + .into(); + } + } + + // Otherwise, render the solid color as is. + LayoutElementRenderElement::SolidColor(elem).into() + } + })); + + window_popups = Some(window.popups.into_iter().map(Into::into)); } let rv = resize_popups @@ -625,7 +726,8 @@ impl Tile { .flatten() .chain(resize_shader) .chain(resize_fallback) - .chain(window.into_iter().flatten()); + .chain(window_popups.into_iter().flatten()) + .chain(window_surface.into_iter().flatten()); let elem = self.is_fullscreen.then(|| { SolidColorRenderElement::from_buffer( diff --git a/src/render_helpers/border.rs b/src/render_helpers/border.rs new file mode 100644 index 00000000..bf72a370 --- /dev/null +++ b/src/render_helpers/border.rs @@ -0,0 +1,168 @@ +use std::collections::HashMap; + +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, Uniform}; +use smithay::backend::renderer::utils::{CommitCounter, DamageSet}; +use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform}; + +use super::renderer::NiriRenderer; +use super::shader_element::{ShaderProgram, ShaderRenderElement}; +use super::shaders::{mat3_uniform, Shaders}; +use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError}; + +/// Renders a wide variety of borders and border parts. +/// +/// This includes: +/// * sub- or super-rect of an angled linear gradient like CSS linear-gradient(angle, a, b). +/// * corner rounding. +/// * as a background rectangle and as parts of a border line. +#[derive(Debug)] +pub struct BorderRenderElement(ShaderRenderElement); + +impl BorderRenderElement { + #[allow(clippy::too_many_arguments)] + pub fn new( + shader: ShaderProgram, + scale: Scale, + area: Rectangle, + gradient_area: Rectangle, + color_from: [f32; 4], + color_to: [f32; 4], + angle: f32, + geometry: Rectangle, + border_width: f32, + corner_radius: CornerRadius, + ) -> Self { + let grad_offset = (area.loc - gradient_area.loc).to_f64().to_physical(scale); + + let grad_dir = Vec2::from_angle(angle); + + let grad_area_size = gradient_area.size.to_f64().to_physical(scale); + let (w, h) = (grad_area_size.w as f32, grad_area_size.h as f32); + + let mut grad_area_diag = Vec2::new(w, h); + if (grad_dir.x < 0. && 0. <= grad_dir.y) || (0. <= grad_dir.x && grad_dir.y < 0.) { + grad_area_diag.x = -w; + } + + let mut grad_vec = grad_area_diag.project_onto(grad_dir); + if grad_dir.y <= 0. { + grad_vec = -grad_vec; + } + + let area_physical = area.to_physical_precise_round(scale); + let area_loc = Vec2::new(area_physical.loc.x, area_physical.loc.y); + let area_size = Vec2::new(area_physical.size.w, area_physical.size.h); + + 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 input_to_geo = + Mat3::from_scale(area_size) * Mat3::from_translation((area_loc - geo_loc) / area_size); + let corner_radius = corner_radius.scaled_by(scale.x as f32); + let border_width = border_width * scale.x as f32; + + let elem = ShaderRenderElement::new( + shader, + HashMap::new(), + area, + area.size.to_f64().to_buffer(scale, Transform::Normal), + None, + 1., + vec![ + Uniform::new("color_from", color_from), + Uniform::new("color_to", color_to), + Uniform::new("grad_offset", (grad_offset.x as f32, grad_offset.y as f32)), + Uniform::new("grad_width", w), + Uniform::new("grad_vec", grad_vec.to_array()), + mat3_uniform("input_to_geo", input_to_geo), + Uniform::new("geo_size", geo_size.to_array()), + Uniform::new("outer_radius", <[f32; 4]>::from(corner_radius)), + Uniform::new("border_width", border_width), + ], + Kind::Unspecified, + ); + Self(elem) + } + + pub fn shader(renderer: &mut impl NiriRenderer) -> Option<&ShaderProgram> { + Shaders::get(renderer).border.as_ref() + } +} + +impl Element for BorderRenderElement { + fn id(&self) -> &Id { + self.0.id() + } + + fn current_commit(&self) -> CommitCounter { + self.0.current_commit() + } + + fn geometry(&self, scale: Scale) -> Rectangle { + self.0.geometry(scale) + } + + fn transform(&self) -> Transform { + self.0.transform() + } + + fn src(&self) -> Rectangle { + self.0.src() + } + + fn damage_since( + &self, + scale: Scale, + commit: Option, + ) -> DamageSet { + self.0.damage_since(scale, commit) + } + + fn opaque_regions(&self, scale: Scale) -> Vec> { + self.0.opaque_regions(scale) + } + + fn alpha(&self) -> f32 { + self.0.alpha() + } + + fn kind(&self) -> Kind { + self.0.kind() + } +} + +impl RenderElement for BorderRenderElement { + fn draw( + &self, + frame: &mut GlesFrame<'_>, + src: Rectangle, + dst: Rectangle, + damage: &[Rectangle], + ) -> Result<(), GlesError> { + RenderElement::::draw(&self.0, frame, src, dst, damage) + } + + fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option { + self.0.underlying_storage(renderer) + } +} + +impl<'render> RenderElement> for BorderRenderElement { + fn draw( + &self, + frame: &mut TtyFrame<'_, '_>, + src: Rectangle, + dst: Rectangle, + damage: &[Rectangle], + ) -> Result<(), TtyRendererError<'render>> { + RenderElement::>::draw(&self.0, frame, src, dst, damage) + } + + fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option { + self.0.underlying_storage(renderer) + } +} diff --git a/src/render_helpers/clipped_surface.rs b/src/render_helpers/clipped_surface.rs new file mode 100644 index 00000000..69c2a012 --- /dev/null +++ b/src/render_helpers/clipped_surface.rs @@ -0,0 +1,269 @@ +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}; +use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform}; + +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, + input_to_geo: Mat3, +} + +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().unwrap(); + let buf_size = Vec2::new(buf_size.w as f32, buf_size.h as f32); + + let view = elem.view().unwrap(); + 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); + + Self { + inner: elem, + program, + corner_radius, + geometry, + input_to_geo, + } + } + + 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.to_f64(), corner_radius); + let corners = corners + .into_iter() + .map(|rect| rect.to_physical_precise_round(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::from_loc_and_size(geo.loc, (top_left, top_left)), + Rectangle::from_loc_and_size( + (geo.loc.x + geo.size.w - top_right, geo.loc.y), + (top_right, top_right), + ), + Rectangle::from_loc_and_size( + ( + geo.loc.x + geo.size.w - bottom_right, + geo.loc.y + geo.size.h - bottom_right, + ), + (bottom_right, bottom_right), + ), + Rectangle::from_loc_and_size( + (geo.loc.x, geo.loc.y + geo.size.h - bottom_left), + (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) -> Vec> { + 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.to_f64(), self.corner_radius); + + let elem_loc = self.geometry(scale).loc; + let corners = corners.into_iter().map(|rect| { + let mut rect = rect.to_physical_precise_round(scale); + rect.loc -= elem_loc; + rect + }); + + 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], + ) -> Result<(), GlesError> { + frame.override_default_tex_program( + self.program.clone(), + vec![ + Uniform::new( + "geo_size", + (self.geometry.size.w as f32, self.geometry.size.h as f32), + ), + Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)), + mat3_uniform("input_to_geo", self.input_to_geo), + ], + ); + RenderElement::::draw(&self.inner, frame, src, dst, damage)?; + 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], + ) -> Result<(), TtyRendererError<'render>> { + frame.as_gles_frame().override_default_tex_program( + self.program.clone(), + vec![ + Uniform::new( + "geo_size", + (self.geometry.size.w as f32, self.geometry.size.h as f32), + ), + Uniform::new("corner_radius", <[f32; 4]>::from(self.corner_radius)), + mat3_uniform("input_to_geo", self.input_to_geo), + ], + ); + RenderElement::draw(&self.inner, frame, src, dst, damage)?; + 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 + } +} diff --git a/src/render_helpers/gradient.rs b/src/render_helpers/gradient.rs deleted file mode 100644 index 958c2f9e..00000000 --- a/src/render_helpers/gradient.rs +++ /dev/null @@ -1,135 +0,0 @@ -use glam::Vec2; -use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage}; -use smithay::backend::renderer::gles::element::PixelShaderElement; -use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, Uniform}; -use smithay::backend::renderer::utils::{CommitCounter, DamageSet}; -use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Transform}; - -use super::primary_gpu_pixel_shader::PrimaryGpuPixelShaderRenderElement; -use super::renderer::NiriRenderer; -use super::shaders::Shaders; -use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError}; - -/// Renders a sub- or super-rect of an angled linear gradient like CSS linear-gradient(angle, a, b). -#[derive(Debug)] -pub struct GradientRenderElement(PrimaryGpuPixelShaderRenderElement); - -impl GradientRenderElement { - pub fn new( - renderer: &mut impl NiriRenderer, - scale: Scale, - area: Rectangle, - gradient_area: Rectangle, - color_from: [f32; 4], - color_to: [f32; 4], - angle: f32, - ) -> Option { - let shader = Shaders::get(renderer).gradient_border.clone()?; - let grad_offset = (area.loc - gradient_area.loc).to_f64().to_physical(scale); - - let grad_dir = Vec2::from_angle(angle); - - let grad_area_size = gradient_area.size.to_f64().to_physical(scale); - let (w, h) = (grad_area_size.w as f32, grad_area_size.h as f32); - - let mut grad_area_diag = Vec2::new(w, h); - if (grad_dir.x < 0. && 0. <= grad_dir.y) || (0. <= grad_dir.x && grad_dir.y < 0.) { - grad_area_diag.x = -w; - } - - let mut grad_vec = grad_area_diag.project_onto(grad_dir); - if grad_dir.y <= 0. { - grad_vec = -grad_vec; - } - - let elem = PixelShaderElement::new( - shader, - area, - None, - 1., - vec![ - Uniform::new("color_from", color_from), - Uniform::new("color_to", color_to), - Uniform::new("grad_offset", (grad_offset.x as f32, grad_offset.y as f32)), - Uniform::new("grad_width", w), - Uniform::new("grad_vec", grad_vec.to_array()), - ], - Kind::Unspecified, - ); - Some(Self(PrimaryGpuPixelShaderRenderElement(elem))) - } -} - -impl Element for GradientRenderElement { - fn id(&self) -> &Id { - self.0.id() - } - - fn current_commit(&self) -> CommitCounter { - self.0.current_commit() - } - - fn geometry(&self, scale: Scale) -> Rectangle { - self.0.geometry(scale) - } - - fn transform(&self) -> Transform { - self.0.transform() - } - - fn src(&self) -> Rectangle { - self.0.src() - } - - fn damage_since( - &self, - scale: Scale, - commit: Option, - ) -> DamageSet { - self.0.damage_since(scale, commit) - } - - fn opaque_regions(&self, scale: Scale) -> Vec> { - self.0.opaque_regions(scale) - } - - fn alpha(&self) -> f32 { - self.0.alpha() - } - - fn kind(&self) -> Kind { - self.0.kind() - } -} - -impl RenderElement for GradientRenderElement { - fn draw( - &self, - frame: &mut GlesFrame<'_>, - src: Rectangle, - dst: Rectangle, - damage: &[Rectangle], - ) -> Result<(), GlesError> { - RenderElement::::draw(&self.0, frame, src, dst, damage) - } - - fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option { - self.0.underlying_storage(renderer) - } -} - -impl<'render> RenderElement> for GradientRenderElement { - fn draw( - &self, - frame: &mut TtyFrame<'_, '_>, - src: Rectangle, - dst: Rectangle, - damage: &[Rectangle], - ) -> Result<(), TtyRendererError<'render>> { - RenderElement::>::draw(&self.0, frame, src, dst, damage) - } - - fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option { - self.0.underlying_storage(renderer) - } -} diff --git a/src/render_helpers/mod.rs b/src/render_helpers/mod.rs index 16abe1ae..ebc4b49e 100644 --- a/src/render_helpers/mod.rs +++ b/src/render_helpers/mod.rs @@ -16,15 +16,15 @@ use smithay::wayland::shm; use self::primary_gpu_texture::PrimaryGpuTextureRenderElement; -pub mod gradient; +pub mod border; +pub mod clipped_surface; pub mod offscreen; -pub mod primary_gpu_pixel_shader; -pub mod primary_gpu_pixel_shader_with_textures; pub mod primary_gpu_texture; pub mod render_elements; pub mod renderer; pub mod resize; pub mod resources; +pub mod shader_element; pub mod shaders; pub mod snapshot; pub mod surface; diff --git a/src/render_helpers/primary_gpu_pixel_shader.rs b/src/render_helpers/primary_gpu_pixel_shader.rs deleted file mode 100644 index 79abc283..00000000 --- a/src/render_helpers/primary_gpu_pixel_shader.rs +++ /dev/null @@ -1,97 +0,0 @@ -use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage}; -use smithay::backend::renderer::gles::element::PixelShaderElement; -use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer}; -use smithay::backend::renderer::utils::{CommitCounter, DamageSet}; -use smithay::utils::{Buffer, Physical, Rectangle, Scale, Transform}; - -use super::renderer::AsGlesFrame; -use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError}; - -/// Wrapper for a pixel shader from the primary GPU for rendering with the primary GPU. -#[derive(Debug)] -pub struct PrimaryGpuPixelShaderRenderElement(pub PixelShaderElement); - -impl Element for PrimaryGpuPixelShaderRenderElement { - fn id(&self) -> &Id { - self.0.id() - } - - fn current_commit(&self) -> CommitCounter { - self.0.current_commit() - } - - fn geometry(&self, scale: Scale) -> Rectangle { - self.0.geometry(scale) - } - - fn transform(&self) -> Transform { - self.0.transform() - } - - fn src(&self) -> Rectangle { - self.0.src() - } - - fn damage_since( - &self, - scale: Scale, - commit: Option, - ) -> DamageSet { - self.0.damage_since(scale, commit) - } - - fn opaque_regions(&self, scale: Scale) -> Vec> { - self.0.opaque_regions(scale) - } - - fn alpha(&self) -> f32 { - self.0.alpha() - } - - fn kind(&self) -> Kind { - self.0.kind() - } -} - -impl RenderElement for PrimaryGpuPixelShaderRenderElement { - fn draw( - &self, - frame: &mut GlesFrame<'_>, - src: Rectangle, - dst: Rectangle, - damage: &[Rectangle], - ) -> Result<(), GlesError> { - let gles_frame = frame.as_gles_frame(); - RenderElement::::draw(&self.0, gles_frame, src, dst, damage)?; - 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 PrimaryGpuPixelShaderRenderElement { - fn draw( - &self, - frame: &mut TtyFrame<'_, '_>, - src: Rectangle, - dst: Rectangle, - damage: &[Rectangle], - ) -> Result<(), TtyRendererError<'render>> { - let gles_frame = frame.as_gles_frame(); - RenderElement::::draw(&self.0, gles_frame, src, dst, damage)?; - 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 - } -} diff --git a/src/render_helpers/primary_gpu_pixel_shader_with_textures.rs b/src/render_helpers/primary_gpu_pixel_shader_with_textures.rs deleted file mode 100644 index e088a965..00000000 --- a/src/render_helpers/primary_gpu_pixel_shader_with_textures.rs +++ /dev/null @@ -1,436 +0,0 @@ -use std::collections::HashMap; -use std::ffi::{CStr, CString}; -use std::rc::Rc; - -use glam::{Mat3, Vec2}; -use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage}; -use smithay::backend::renderer::gles::{ - ffi, link_program, Capability, GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform, - UniformDesc, UniformName, -}; -use smithay::backend::renderer::utils::CommitCounter; -use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size}; - -use super::renderer::AsGlesFrame; -use super::resources::Resources; -use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError}; - -/// Wrapper for a pixel shader from the primary GPU for rendering with the primary GPU. -/// -/// The shader accepts textures as input. -#[derive(Debug)] -pub struct PrimaryGpuPixelShaderWithTexturesRenderElement { - shader: PixelWithTexturesProgram, - textures: HashMap, - id: Id, - commit_counter: CommitCounter, - area: Rectangle, - size: Size, - opaque_regions: Vec>, - alpha: f32, - additional_uniforms: Vec>, - kind: Kind, -} - -#[derive(Debug, Clone)] -pub struct PixelWithTexturesProgram(Rc); - -#[derive(Debug)] -struct PixelWithTexturesProgramInner { - program: ffi::types::GLuint, - uniform_tex_matrix: ffi::types::GLint, - uniform_matrix: ffi::types::GLint, - uniform_size: ffi::types::GLint, - uniform_alpha: ffi::types::GLint, - attrib_vert: ffi::types::GLint, - attrib_vert_position: ffi::types::GLint, - additional_uniforms: HashMap, - texture_uniforms: HashMap, -} - -unsafe fn compile_program( - gl: &ffi::Gles2, - src: &str, - additional_uniforms: &[UniformName<'_>], - texture_uniforms: &[&str], - // destruction_callback_sender: Sender, -) -> Result { - let shader = src; - - let program = unsafe { link_program(gl, include_str!("shaders/texture.vert"), shader)? }; - - let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated"); - let vert_position = CStr::from_bytes_with_nul(b"vert_position\0").expect("NULL terminated"); - let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated"); - let tex_matrix = CStr::from_bytes_with_nul(b"tex_matrix\0").expect("NULL terminated"); - let size = CStr::from_bytes_with_nul(b"niri_size\0").expect("NULL terminated"); - let alpha = CStr::from_bytes_with_nul(b"niri_alpha\0").expect("NULL terminated"); - - Ok(PixelWithTexturesProgram(Rc::new( - PixelWithTexturesProgramInner { - program, - uniform_matrix: gl - .GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar), - uniform_tex_matrix: gl - .GetUniformLocation(program, tex_matrix.as_ptr() as *const ffi::types::GLchar), - uniform_size: gl - .GetUniformLocation(program, size.as_ptr() as *const ffi::types::GLchar), - uniform_alpha: gl - .GetUniformLocation(program, alpha.as_ptr() as *const ffi::types::GLchar), - attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar), - attrib_vert_position: gl - .GetAttribLocation(program, vert_position.as_ptr() as *const ffi::types::GLchar), - additional_uniforms: additional_uniforms - .iter() - .map(|uniform| { - let name = - CString::new(uniform.name.as_bytes()).expect("Interior null in name"); - let location = - gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar); - ( - uniform.name.clone().into_owned(), - UniformDesc { - location, - type_: uniform.type_, - }, - ) - }) - .collect(), - texture_uniforms: texture_uniforms - .iter() - .map(|name_| { - let name = CString::new(name_.as_bytes()).expect("Interior null in name"); - let location = - gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar); - (name_.to_string(), location) - }) - .collect(), - }, - ))) -} - -impl PixelWithTexturesProgram { - pub fn compile( - renderer: &mut GlesRenderer, - src: &str, - additional_uniforms: &[UniformName<'_>], - texture_uniforms: &[&str], - ) -> Result { - renderer.with_context(move |gl| unsafe { - compile_program(gl, src, additional_uniforms, texture_uniforms) - })? - } - - pub fn destroy(self, renderer: &mut GlesRenderer) -> Result<(), GlesError> { - renderer.with_context(move |gl| unsafe { - gl.DeleteProgram(self.0.program); - }) - } -} - -impl PrimaryGpuPixelShaderWithTexturesRenderElement { - #[allow(clippy::too_many_arguments)] - pub fn new( - shader: PixelWithTexturesProgram, - textures: HashMap, - area: Rectangle, - size: Size, - opaque_regions: Option>>, - alpha: f32, - additional_uniforms: Vec>, - kind: Kind, - ) -> Self { - Self { - shader, - textures, - id: Id::new(), - commit_counter: CommitCounter::default(), - area, - size, - opaque_regions: opaque_regions.unwrap_or_default(), - alpha, - additional_uniforms: additional_uniforms - .into_iter() - .map(|u| u.into_owned()) - .collect(), - kind, - } - } -} - -impl Element for PrimaryGpuPixelShaderWithTexturesRenderElement { - fn id(&self) -> &Id { - &self.id - } - - fn current_commit(&self) -> CommitCounter { - self.commit_counter - } - - fn src(&self) -> Rectangle { - Rectangle::from_loc_and_size((0., 0.), self.size.to_f64()) - } - - fn geometry(&self, scale: Scale) -> Rectangle { - self.area.to_physical_precise_round(scale) - } - - fn opaque_regions(&self, scale: Scale) -> Vec> { - self.opaque_regions - .iter() - .map(|region| region.to_physical_precise_round(scale)) - .collect() - } - - fn alpha(&self) -> f32 { - 1.0 - } - - fn kind(&self) -> Kind { - self.kind - } -} - -impl RenderElement for PrimaryGpuPixelShaderWithTexturesRenderElement { - fn draw( - &self, - frame: &mut GlesFrame<'_>, - src: Rectangle, - dest: