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 --- 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 +++ 16 files changed, 1247 insertions(+), 766 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 (limited to 'src/render_helpers') 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: Rectangle, - damage: &[Rectangle], - ) -> Result<(), GlesError> { - let frame = frame.as_gles_frame(); - - let Some(resources) = Resources::get(frame) else { - return Ok(()); - }; - let mut resources = resources.borrow_mut(); - - let supports_instancing = frame.capabilities().contains(&Capability::Instancing); - - // prepare the vertices - resources.vertices.clear(); - if supports_instancing { - resources.vertices.extend(damage.iter().flat_map(|rect| { - let dest_size = dest.size; - - let rect_constrained_loc = rect - .loc - .constrain(Rectangle::from_extemities((0, 0), dest_size.to_point())); - let rect_clamped_size = rect.size.clamp( - (0, 0), - (dest_size.to_point() - rect_constrained_loc).to_size(), - ); - - let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size); - [ - rect.loc.x as f32, - rect.loc.y as f32, - rect.size.w as f32, - rect.size.h as f32, - ] - })); - } else { - resources.vertices.extend(damage.iter().flat_map(|rect| { - let dest_size = dest.size; - - let rect_constrained_loc = rect - .loc - .constrain(Rectangle::from_extemities((0, 0), dest_size.to_point())); - let rect_clamped_size = rect.size.clamp( - (0, 0), - (dest_size.to_point() - rect_constrained_loc).to_size(), - ); - - let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size); - // Add the 4 f32s per damage rectangle for each of the 6 vertices. - (0..6).flat_map(move |_| { - [ - rect.loc.x as f32, - rect.loc.y as f32, - rect.size.w as f32, - rect.size.h as f32, - ] - }) - })); - } - - if resources.vertices.is_empty() { - return Ok(()); - } - - // dest position and scale - let mut matrix = Mat3::from_translation(Vec2::new(dest.loc.x as f32, dest.loc.y as f32)); - - let scale = src.size.to_f64() / dest.size.to_f64(); - let tex_matrix = Mat3::from_scale(Vec2::new(scale.x as f32, scale.y as f32)); - let tex_matrix = - Mat3::from_translation(Vec2::new(src.loc.x as f32, src.loc.y as f32)) * tex_matrix; - let tex_matrix = Mat3::from_scale(Vec2::new( - (1.0f64 / self.size.w) as f32, - (1.0f64 / self.size.h) as f32, - )) * tex_matrix; - - //apply output transformation - matrix = Mat3::from_cols_array(frame.projection()) * matrix; - - let program = &self.shader.0; - - // render - frame.with_context(move |gl| -> Result<(), GlesError> { - unsafe { - for (i, texture) in self.textures.values().enumerate() { - gl.ActiveTexture(ffi::TEXTURE0 + i as u32); - gl.BindTexture(ffi::TEXTURE_2D, texture.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); - gl.TexParameteri( - ffi::TEXTURE_2D, - ffi::TEXTURE_WRAP_S, - ffi::CLAMP_TO_BORDER as i32, - ); - gl.TexParameteri( - ffi::TEXTURE_2D, - ffi::TEXTURE_WRAP_T, - ffi::CLAMP_TO_BORDER as i32, - ); - } - - gl.UseProgram(program.program); - - for (i, name) in self.textures.keys().enumerate() { - gl.Uniform1i(program.texture_uniforms[name], i as i32); - } - - gl.UniformMatrix3fv( - program.uniform_matrix, - 1, - ffi::FALSE, - matrix.as_ref().as_ptr(), - ); - gl.UniformMatrix3fv( - program.uniform_tex_matrix, - 1, - ffi::FALSE, - tex_matrix.as_ref().as_ptr(), - ); - gl.Uniform2f(program.uniform_size, dest.size.w as f32, dest.size.h as f32); - gl.Uniform1f(program.uniform_alpha, self.alpha); - - for uniform in &self.additional_uniforms { - let desc = - program - .additional_uniforms - .get(&*uniform.name) - .ok_or_else(|| { - GlesError::UnknownUniform(uniform.name.clone().into_owned()) - })?; - uniform.value.set(gl, desc)?; - } - - gl.EnableVertexAttribArray(program.attrib_vert as u32); - gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[0]); - gl.VertexAttribPointer( - program.attrib_vert as u32, - 2, - ffi::FLOAT, - ffi::FALSE, - 0, - std::ptr::null(), - ); - - // vert_position - gl.EnableVertexAttribArray(program.attrib_vert_position as u32); - gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[1]); - gl.BufferData( - ffi::ARRAY_BUFFER, - (std::mem::size_of::() * resources.vertices.len()) - as isize, - resources.vertices.as_ptr() as *const _, - ffi::STREAM_DRAW, - ); - - gl.VertexAttribPointer( - program.attrib_vert_position as u32, - 4, - ffi::FLOAT, - ffi::FALSE, - 0, - std::ptr::null(), - ); - - let damage_len = damage.len() as i32; - if supports_instancing { - gl.VertexAttribDivisor(program.attrib_vert as u32, 0); - gl.VertexAttribDivisor(program.attrib_vert_position as u32, 1); - gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len); - } else { - // When we have more than 10 rectangles, draw them in batches of 10. - for i in 0..(damage_len - 1) / 10 { - gl.DrawArrays(ffi::TRIANGLES, 0, 6); - - // Set damage pointer to the next 10 rectangles. - let offset = - (i + 1) as usize * 6 * 4 * std::mem::size_of::(); - gl.VertexAttribPointer( - program.attrib_vert_position as u32, - 4, - ffi::FLOAT, - ffi::FALSE, - 0, - offset as *const _, - ); - } - - // Draw the up to 10 remaining rectangles. - let count = ((damage_len - 1) % 10 + 1) * 6; - gl.DrawArrays(ffi::TRIANGLES, 0, count); - } - - gl.BindBuffer(ffi::ARRAY_BUFFER, 0); - gl.BindTexture(ffi::TEXTURE_2D, 0); - gl.ActiveTexture(ffi::TEXTURE0); - gl.BindTexture(ffi::TEXTURE_2D, 0); - gl.DisableVertexAttribArray(program.attrib_vert as u32); - gl.DisableVertexAttribArray(program.attrib_vert_position as u32); - } - - Ok(()) - })??; - - 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 PrimaryGpuPixelShaderWithTexturesRenderElement -{ - fn draw( - &self, - frame: &mut TtyFrame<'_, '_>, - src: Rectangle, - dst: Rectangle, - damage: &[Rectangle], - ) -> Result<(), TtyRendererError<'render>> { - let frame = frame.as_gles_frame(); - - RenderElement::::draw(self, 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/resize.rs b/src/render_helpers/resize.rs index 9641c8d8..8ba897ef 100644 --- a/src/render_helpers/resize.rs +++ b/src/render_helpers/resize.rs @@ -1,25 +1,24 @@ 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, GlesTexture, Uniform}; use smithay::backend::renderer::utils::{CommitCounter, DamageSet}; use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size, Transform}; -use super::primary_gpu_pixel_shader_with_textures::{ - PixelWithTexturesProgram, PrimaryGpuPixelShaderWithTexturesRenderElement, -}; use super::renderer::{AsGlesFrame, NiriRenderer}; +use super::shader_element::{ShaderProgram, ShaderRenderElement}; use super::shaders::{mat3_uniform, Shaders}; use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError}; #[derive(Debug)] -pub struct ResizeRenderElement(PrimaryGpuPixelShaderWithTexturesRenderElement); +pub struct ResizeRenderElement(ShaderRenderElement); impl ResizeRenderElement { #[allow(clippy::too_many_arguments)] pub fn new( - shader: PixelWithTexturesProgram, + shader: ShaderProgram, area: Rectangle, scale: Scale, texture_prev: (GlesTexture, Rectangle), @@ -28,6 +27,8 @@ impl ResizeRenderElement { size_next: Size, progress: f32, clamped_progress: f32, + corner_radius: CornerRadius, + clip_to_geometry: bool, result_alpha: f32, ) -> Self { let curr_geo = area; @@ -84,9 +85,13 @@ impl ResizeRenderElement { * Mat3::from_scale(size_next / tex_next_geo_size * scale); let curr_geo_size = curr_geo_size * scale; + let corner_radius = corner_radius + .scaled_by(scale.x) + .fit_to(curr_geo_size.x, curr_geo_size.y); + let clip_to_geometry = if clip_to_geometry { 1. } else { 0. }; // Create the shader. - Self(PrimaryGpuPixelShaderWithTexturesRenderElement::new( + Self(ShaderRenderElement::new( shader, HashMap::from([ (String::from("niri_tex_prev"), texture_prev), @@ -105,12 +110,14 @@ impl ResizeRenderElement { mat3_uniform("niri_geo_to_tex_next", geo_to_tex_next), Uniform::new("niri_progress", progress), Uniform::new("niri_clamped_progress", clamped_progress), + Uniform::new("niri_corner_radius", <[f32; 4]>::from(corner_radius)), + Uniform::new("niri_clip_to_geometry", clip_to_geometry), ], Kind::Unspecified, )) } - pub fn shader(renderer: &mut impl NiriRenderer) -> Option { + pub fn shader(renderer: &mut impl NiriRenderer) -> Option { Shaders::get(renderer).resize() } } diff --git a/src/render_helpers/shader_element.rs b/src/render_helpers/shader_element.rs new file mode 100644 index 00000000..c17764ee --- /dev/null +++ b/src/render_helpers/shader_element.rs @@ -0,0 +1,506 @@ +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::backend::renderer::DebugFlags; +use smithay::utils::{Buffer, Logical, Physical, Rectangle, Scale, Size}; + +use super::renderer::AsGlesFrame; +use super::resources::Resources; +use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError}; + +/// Renders a shader with optional texture input, on the primary GPU. +#[derive(Debug)] +pub struct ShaderRenderElement { + shader: ShaderProgram, + 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 ShaderProgram(Rc); + +#[derive(Debug)] +struct ShaderProgramInner { + normal: ShaderProgramInternal, + debug: ShaderProgramInternal, + uniform_tint: ffi::types::GLint, +} + +#[derive(Debug)] +struct ShaderProgramInternal { + 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 = format!("#version 100\n{}", src); + let program = unsafe { link_program(gl, include_str!("shaders/texture.vert"), &shader)? }; + let debug_shader = format!("#version 100\n#define DEBUG_FLAGS\n{}", src); + let debug_program = + unsafe { link_program(gl, include_str!("shaders/texture.vert"), &debug_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"); + let tint = CStr::from_bytes_with_nul(b"niri_tint\0").expect("NULL terminated"); + + Ok(ShaderProgram(Rc::new(ShaderProgramInner { + normal: ShaderProgramInternal { + 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(), + }, + debug: ShaderProgramInternal { + program: debug_program, + uniform_matrix: gl + .GetUniformLocation(debug_program, matrix.as_ptr() as *const ffi::types::GLchar), + uniform_tex_matrix: gl.GetUniformLocation( + debug_program, + tex_matrix.as_ptr() as *const ffi::types::GLchar, + ), + uniform_size: gl + .GetUniformLocation(debug_program, size.as_ptr() as *const ffi::types::GLchar), + uniform_alpha: gl + .GetUniformLocation(debug_program, alpha.as_ptr() as *const ffi::types::GLchar), + attrib_vert: gl + .GetAttribLocation(debug_program, vert.as_ptr() as *const ffi::types::GLchar), + attrib_vert_position: gl.GetAttribLocation( + debug_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( + debug_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( + debug_program, + name.as_ptr() as *const ffi::types::GLchar, + ); + (name_.to_string(), location) + }) + .collect(), + }, + uniform_tint: gl + .GetUniformLocation(debug_program, tint.as_ptr() as *const ffi::types::GLchar), + }))) +} + +impl ShaderProgram { + 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.normal.program); + gl.DeleteProgram(self.0.debug.program); + }) + } +} + +impl ShaderRenderElement { + #[allow(clippy::too_many_arguments)] + pub fn new( + shader: ShaderProgram, + 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 ShaderRenderElement { + 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 ShaderRenderElement { + fn draw( + &self, + frame: &mut GlesFrame<'_>, + src: Rectangle, + dest: Rectangle, + damage: &[Rectangle], + ) -> Result<(), GlesError> { + let frame = frame.as_gles_frame(); + + let Some(resources) = Resources::get(frame) else { + return Ok(()); + }; + let mut resources = resources.borrow_mut(); + + let supports_instancing = frame.capabilities().contains(&Capability::Instancing); + + // prepare the vertices + resources.vertices.clear(); + if supports_instancing { + resources.vertices.extend(damage.iter().flat_map(|rect| { + let dest_size = dest.size; + + let rect_constrained_loc = rect + .loc + .constrain(Rectangle::from_extemities((0, 0), dest_size.to_point())); + let rect_clamped_size = rect.size.clamp( + (0, 0), + (dest_size.to_point() - rect_constrained_loc).to_size(), + ); + + let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size); + [ + rect.loc.x as f32, + rect.loc.y as f32, + rect.size.w as f32, + rect.size.h as f32, + ] + })); + } else { + resources.vertices.extend(damage.iter().flat_map(|rect| { + let dest_size = dest.size; + + let rect_constrained_loc = rect + .loc + .constrain(Rectangle::from_extemities((0, 0), dest_size.to_point())); + let rect_clamped_size = rect.size.clamp( + (0, 0), + (dest_size.to_point() - rect_constrained_loc).to_size(), + ); + + let rect = Rectangle::from_loc_and_size(rect_constrained_loc, rect_clamped_size); + // Add the 4 f32s per damage rectangle for each of the 6 vertices. + (0..6).flat_map(move |_| { + [ + rect.loc.x as f32, + rect.loc.y as f32, + rect.size.w as f32, + rect.size.h as f32, + ] + }) + })); + } + + if resources.vertices.is_empty() { + return Ok(()); + } + + // dest position and scale + let mut matrix = Mat3::from_translation(Vec2::new(dest.loc.x as f32, dest.loc.y as f32)); + + let scale = src.size.to_f64() / dest.size.to_f64(); + let tex_matrix = Mat3::from_scale(Vec2::new(scale.x as f32, scale.y as f32)); + let tex_matrix = + Mat3::from_translation(Vec2::new(src.loc.x as f32, src.loc.y as f32)) * tex_matrix; + let tex_matrix = Mat3::from_scale(Vec2::new( + (1.0f64 / self.size.w) as f32, + (1.0f64 / self.size.h) as f32, + )) * tex_matrix; + + //apply output transformation + matrix = Mat3::from_cols_array(frame.projection()) * matrix; + + let has_debug = !frame.debug_flags().is_empty(); + let has_tint = frame.debug_flags().contains(DebugFlags::TINT); + + let program = if has_debug { + &self.shader.0.debug + } else { + &self.shader.0.normal + }; + + // render + frame.with_context(move |gl| -> Result<(), GlesError> { + unsafe { + for (i, texture) in self.textures.values().enumerate() { + gl.ActiveTexture(ffi::TEXTURE0 + i as u32); + gl.BindTexture(ffi::TEXTURE_2D, texture.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); + gl.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_WRAP_S, + ffi::CLAMP_TO_BORDER as i32, + ); + gl.TexParameteri( + ffi::TEXTURE_2D, + ffi::TEXTURE_WRAP_T, + ffi::CLAMP_TO_BORDER as i32, + ); + } + + gl.UseProgram(program.program); + + for (i, name) in self.textures.keys().enumerate() { + gl.Uniform1i(program.texture_uniforms[name], i as i32); + } + + gl.UniformMatrix3fv( + program.uniform_matrix, + 1, + ffi::FALSE, + matrix.as_ref().as_ptr(), + ); + gl.UniformMatrix3fv( + program.uniform_tex_matrix, + 1, + ffi::FALSE, + tex_matrix.as_ref().as_ptr(), + ); + gl.Uniform2f(program.uniform_size, dest.size.w as f32, dest.size.h as f32); + gl.Uniform1f(program.uniform_alpha, self.alpha); + + let tint = if has_tint { 1.0f32 } else { 0.0f32 }; + if has_debug { + gl.Uniform1f(self.shader.0.uniform_tint, tint); + } + + for uniform in &self.additional_uniforms { + let desc = + program + .additional_uniforms + .get(&*uniform.name) + .ok_or_else(|| { + GlesError::UnknownUniform(uniform.name.clone().into_owned()) + })?; + uniform.value.set(gl, desc)?; + } + + gl.EnableVertexAttribArray(program.attrib_vert as u32); + gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[0]); + gl.VertexAttribPointer( + program.attrib_vert as u32, + 2, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); + + // vert_position + gl.EnableVertexAttribArray(program.attrib_vert_position as u32); + gl.BindBuffer(ffi::ARRAY_BUFFER, resources.vbos[1]); + gl.BufferData( + ffi::ARRAY_BUFFER, + (std::mem::size_of::() * resources.vertices.len()) + as isize, + resources.vertices.as_ptr() as *const _, + ffi::STREAM_DRAW, + ); + + gl.VertexAttribPointer( + program.attrib_vert_position as u32, + 4, + ffi::FLOAT, + ffi::FALSE, + 0, + std::ptr::null(), + ); + + let damage_len = damage.len() as i32; + if supports_instancing { + gl.VertexAttribDivisor(program.attrib_vert as u32, 0); + gl.VertexAttribDivisor(program.attrib_vert_position as u32, 1); + gl.DrawArraysInstanced(ffi::TRIANGLE_STRIP, 0, 4, damage_len); + } else { + // When we have more than 10 rectangles, draw them in batches of 10. + for i in 0..(damage_len - 1) / 10 { + gl.DrawArrays(ffi::TRIANGLES, 0, 6); + + // Set damage pointer to the next 10 rectangles. + let offset = + (i + 1) as usize * 6 * 4 * std::mem::size_of::(); + gl.VertexAttribPointer( + program.attrib_vert_position as u32, + 4, + ffi::FLOAT, + ffi::FALSE, + 0, + offset as *const _, + ); + } + + // Draw the up to 10 remaining rectangles. + let count = ((damage_len - 1) % 10 + 1) * 6; + gl.DrawArrays(ffi::TRIANGLES, 0, count); + } + + gl.BindBuffer(ffi::ARRAY_BUFFER, 0); + gl.BindTexture(ffi::TEXTURE_2D, 0); + gl.ActiveTexture(ffi::TEXTURE0); + gl.BindTexture(ffi::TEXTURE_2D, 0); + gl.DisableVertexAttribArray(program.attrib_vert as u32); + gl.DisableVertexAttribArray(program.attri