aboutsummaryrefslogtreecommitdiff
path: root/src/render_helpers
diff options
context:
space:
mode:
Diffstat (limited to 'src/render_helpers')
-rw-r--r--src/render_helpers/gradient.rs143
-rw-r--r--src/render_helpers/mod.rs3
-rw-r--r--src/render_helpers/primary_gpu_pixel_shader.rs97
-rw-r--r--src/render_helpers/shaders/gradient_border.frag46
-rw-r--r--src/render_helpers/shaders/mod.rs47
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");
+ }
+}