diff options
Diffstat (limited to 'src/render_helpers')
| -rw-r--r-- | src/render_helpers/gradient.rs | 143 | ||||
| -rw-r--r-- | src/render_helpers/mod.rs | 3 | ||||
| -rw-r--r-- | src/render_helpers/primary_gpu_pixel_shader.rs | 97 | ||||
| -rw-r--r-- | src/render_helpers/shaders/gradient_border.frag | 46 | ||||
| -rw-r--r-- | src/render_helpers/shaders/mod.rs | 47 |
5 files changed, 336 insertions, 0 deletions
diff --git a/src/render_helpers/gradient.rs b/src/render_helpers/gradient.rs new file mode 100644 index 00000000..1dbed05a --- /dev/null +++ b/src/render_helpers/gradient.rs @@ -0,0 +1,143 @@ +use std::f32::consts::{self, FRAC_PI_2, PI}; + +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; +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<f64>, + area: Rectangle<i32, Logical>, + gradient_area: Rectangle<i32, Logical>, + color_from: [f32; 4], + color_to: [f32; 4], + mut angle: f32, + ) -> Option<Self> { + let shader = Shaders::get(renderer).gradient_border.clone()?; + let g_offset = (area.loc - gradient_area.loc).to_f64().to_physical(scale); + + let g_size = gradient_area.size.to_f64().to_physical(scale); + let (w, h) = (g_size.w as f32, g_size.h as f32); + let g_area_angle = f32::atan2(h, w); + let g_area_diag = f32::hypot(h, w); + + // Normalize the angle to [0°; 360°). + while angle < 0. { + angle += consts::TAU; + } + while angle >= consts::TAU { + angle -= consts::TAU; + } + + let angle_diag_to_grad = + if (0. ..=FRAC_PI_2).contains(&angle) || (PI..=PI + FRAC_PI_2).contains(&angle) { + angle - g_area_angle + } else { + (PI - angle) - g_area_angle + }; + let g_total = angle_diag_to_grad.cos().abs() * g_area_diag; + + let elem = PixelShaderElement::new( + shader, + area, + None, + 1., + vec![ + Uniform::new("color_from", color_from), + Uniform::new("color_to", color_to), + Uniform::new("angle", angle), + Uniform::new("gradient_offset", (g_offset.x as f32, g_offset.y as f32)), + Uniform::new("gradient_width", w), + Uniform::new("gradient_total", g_total), + ], + 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<f64>) -> Rectangle<i32, Physical> { + self.0.geometry(scale) + } + + fn transform(&self) -> Transform { + self.0.transform() + } + + fn src(&self) -> Rectangle<f64, Buffer> { + self.0.src() + } + + fn damage_since( + &self, + scale: Scale<f64>, + commit: Option<CommitCounter>, + ) -> Vec<Rectangle<i32, Physical>> { + self.0.damage_since(scale, commit) + } + + fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> { + self.0.opaque_regions(scale) + } + + fn alpha(&self) -> f32 { + self.0.alpha() + } + + fn kind(&self) -> Kind { + self.0.kind() + } +} + +impl RenderElement<GlesRenderer> for GradientRenderElement { + fn draw( + &self, + frame: &mut GlesFrame<'_>, + src: Rectangle<f64, Buffer>, + dst: Rectangle<i32, Physical>, + damage: &[Rectangle<i32, Physical>], + ) -> Result<(), GlesError> { + RenderElement::<GlesRenderer>::draw(&self.0, frame, src, dst, damage) + } + + fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> { + self.0.underlying_storage(renderer) + } +} + +impl<'render> RenderElement<TtyRenderer<'render>> for GradientRenderElement { + fn draw( + &self, + frame: &mut TtyFrame<'_, '_>, + src: Rectangle<f64, Buffer>, + dst: Rectangle<i32, Physical>, + damage: &[Rectangle<i32, Physical>], + ) -> Result<(), TtyRendererError<'render>> { + RenderElement::<TtyRenderer<'_>>::draw(&self.0, frame, src, dst, damage) + } + + fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option<UnderlyingStorage> { + self.0.underlying_storage(renderer) + } +} diff --git a/src/render_helpers/mod.rs b/src/render_helpers/mod.rs index c05877f9..7d7ea9c1 100644 --- a/src/render_helpers/mod.rs +++ b/src/render_helpers/mod.rs @@ -6,10 +6,13 @@ use smithay::backend::renderer::sync::SyncPoint; use smithay::backend::renderer::{Bind, ExportMem, Frame, Offscreen, Renderer}; use smithay::utils::{Physical, Rectangle, Scale, Size, Transform}; +pub mod gradient; pub mod offscreen; +pub mod primary_gpu_pixel_shader; pub mod primary_gpu_texture; pub mod render_elements; pub mod renderer; +pub mod shaders; pub fn render_to_texture( renderer: &mut GlesRenderer, diff --git a/src/render_helpers/primary_gpu_pixel_shader.rs b/src/render_helpers/primary_gpu_pixel_shader.rs new file mode 100644 index 00000000..b3b75023 --- /dev/null +++ b/src/render_helpers/primary_gpu_pixel_shader.rs @@ -0,0 +1,97 @@ +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; +use smithay::utils::{Buffer, Physical, Rectangle, Scale, Transform}; + +use super::renderer::AsGlesFrame; +use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError}; + +/// Wrapper for a poxel 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<f64>) -> Rectangle<i32, Physical> { + self.0.geometry(scale) + } + + fn transform(&self) -> Transform { + self.0.transform() + } + + fn src(&self) -> Rectangle<f64, Buffer> { + self.0.src() + } + + fn damage_since( + &self, + scale: Scale<f64>, + commit: Option<CommitCounter>, + ) -> Vec<Rectangle<i32, Physical>> { + self.0.damage_since(scale, commit) + } + + fn opaque_regions(&self, scale: Scale<f64>) -> Vec<Rectangle<i32, Physical>> { + self.0.opaque_regions(scale) + } + + fn alpha(&self) -> f32 { + self.0.alpha() + } + + fn kind(&self) -> Kind { + self.0.kind() + } +} + +impl RenderElement<GlesRenderer> for PrimaryGpuPixelShaderRenderElement { + fn draw( + &self, + frame: &mut GlesFrame<'_>, + src: Rectangle<f64, Buffer>, + dst: Rectangle<i32, Physical>, + damage: &[Rectangle<i32, Physical>], + ) -> Result<(), GlesError> { + let gles_frame = frame.as_gles_frame(); + RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?; + Ok(()) + } + + fn underlying_storage(&self, _renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> { + // If scanout for things other than Wayland buffers is implemented, this will need to take + // the target GPU into account. + None + } +} + +impl<'render> RenderElement<TtyRenderer<'render>> for PrimaryGpuPixelShaderRenderElement { + fn draw( + &self, + frame: &mut TtyFrame<'_, '_>, + src: Rectangle<f64, Buffer>, + dst: Rectangle<i32, Physical>, + damage: &[Rectangle<i32, Physical>], + ) -> Result<(), TtyRendererError<'render>> { + let gles_frame = frame.as_gles_frame(); + RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?; + Ok(()) + } + + fn underlying_storage( + &self, + _renderer: &mut TtyRenderer<'render>, + ) -> Option<UnderlyingStorage> { + // 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/shaders/gradient_border.frag b/src/render_helpers/shaders/gradient_border.frag new file mode 100644 index 00000000..354968cf --- /dev/null +++ b/src/render_helpers/shaders/gradient_border.frag @@ -0,0 +1,46 @@ +precision mediump float; +uniform float alpha; +#if defined(DEBUG_FLAGS) +uniform float tint; +#endif +uniform vec2 size; +varying vec2 v_coords; + +uniform vec4 color_from; +uniform vec4 color_to; +uniform float angle; +uniform vec2 gradient_offset; +uniform float gradient_width; +uniform float gradient_total; + +#define FRAC_PI_2 1.57079632679 +#define PI 3.14159265359 +#define FRAC_3_PI_2 4.71238898038 +#define TAU 6.28318530718 + +void main() { + vec2 coords = v_coords * size + gradient_offset; + + if ((FRAC_PI_2 <= angle && angle < PI) || (FRAC_3_PI_2 <= angle && angle < TAU)) + coords.x -= gradient_width; + + float frag_angle = FRAC_PI_2; + if (coords.x != 0.0) + frag_angle = atan(coords.y, coords.x); + + float angle_frag_to_grad = frag_angle - angle; + + float frac = cos(angle_frag_to_grad) * length(coords) / gradient_total; + if (PI <= angle) + frac += 1.0; + frac = clamp(frac, 0.0, 1.0); + + vec4 out_color = mix(color_from, color_to, frac); + +#if defined(DEBUG_FLAGS) + if (tint == 1.0) + out_color = vec4(0.0, 0.3, 0.0, 0.2) + out_color * 0.8; +#endif + + gl_FragColor = out_color; +} diff --git a/src/render_helpers/shaders/mod.rs b/src/render_helpers/shaders/mod.rs new file mode 100644 index 00000000..72e9fa1c --- /dev/null +++ b/src/render_helpers/shaders/mod.rs @@ -0,0 +1,47 @@ +use smithay::backend::renderer::gles::{GlesPixelProgram, GlesRenderer, UniformName, UniformType}; + +use super::renderer::NiriRenderer; + +pub struct Shaders { + pub gradient_border: Option<GlesPixelProgram>, +} + +impl Shaders { + fn compile(renderer: &mut GlesRenderer) -> Self { + let _span = tracy_client::span!("Shaders::compile"); + + let gradient_border = renderer + .compile_custom_pixel_shader( + include_str!("gradient_border.frag"), + &[ + UniformName::new("color_from", UniformType::_4f), + UniformName::new("color_to", UniformType::_4f), + UniformName::new("angle", UniformType::_1f), + UniformName::new("gradient_offset", UniformType::_2f), + UniformName::new("gradient_width", UniformType::_1f), + UniformName::new("gradient_total", UniformType::_1f), + ], + ) + .map_err(|err| { + warn!("error compiling gradient border shader: {err:?}"); + }) + .ok(); + + Self { gradient_border } + } + + pub fn get(renderer: &mut impl NiriRenderer) -> &Self { + let renderer = renderer.as_gles_renderer(); + let data = renderer.egl_context().user_data(); + data.get() + .expect("shaders::init() must be called when creating the renderer") + } +} + +pub fn init(renderer: &mut GlesRenderer) { + let shaders = Shaders::compile(renderer); + let data = renderer.egl_context().user_data(); + if !data.insert_if_missing(|| shaders) { + error!("shaders were already compiled"); + } +} |
