aboutsummaryrefslogtreecommitdiff
path: root/src/render_helpers
diff options
context:
space:
mode:
Diffstat (limited to 'src/render_helpers')
-rw-r--r--src/render_helpers/mod.rs1
-rw-r--r--src/render_helpers/shaders/mod.rs24
-rw-r--r--src/render_helpers/shaders/shadow.frag142
-rw-r--r--src/render_helpers/shadow.rs257
4 files changed, 424 insertions, 0 deletions
diff --git a/src/render_helpers/mod.rs b/src/render_helpers/mod.rs
index 98ccf19e..45e6e527 100644
--- a/src/render_helpers/mod.rs
+++ b/src/render_helpers/mod.rs
@@ -31,6 +31,7 @@ pub mod resize;
pub mod resources;
pub mod shader_element;
pub mod shaders;
+pub mod shadow;
pub mod snapshot;
pub mod solid_color;
pub mod surface;
diff --git a/src/render_helpers/shaders/mod.rs b/src/render_helpers/shaders/mod.rs
index 91ba32d1..ddc58cdb 100644
--- a/src/render_helpers/shaders/mod.rs
+++ b/src/render_helpers/shaders/mod.rs
@@ -11,6 +11,7 @@ use super::shader_element::ShaderProgram;
pub struct Shaders {
pub border: Option<ShaderProgram>,
+ pub shadow: Option<ShaderProgram>,
pub clipped_surface: Option<GlesTexProgram>,
pub resize: Option<ShaderProgram>,
pub custom_resize: RefCell<Option<ShaderProgram>>,
@@ -21,6 +22,7 @@ pub struct Shaders {
#[derive(Debug, Clone, Copy)]
pub enum ProgramType {
Border,
+ Shadow,
Resize,
Close,
Open,
@@ -53,6 +55,26 @@ impl Shaders {
})
.ok();
+ let shadow = ShaderProgram::compile(
+ renderer,
+ include_str!("shadow.frag"),
+ &[
+ UniformName::new("shadow_color", UniformType::_4f),
+ UniformName::new("sigma", UniformType::_1f),
+ UniformName::new("input_to_geo", UniformType::Matrix3x3),
+ UniformName::new("geo_size", UniformType::_2f),
+ UniformName::new("corner_radius", UniformType::_4f),
+ UniformName::new("window_input_to_geo", UniformType::Matrix3x3),
+ UniformName::new("window_geo_size", UniformType::_2f),
+ UniformName::new("window_corner_radius", UniformType::_4f),
+ ],
+ &[],
+ )
+ .map_err(|err| {
+ warn!("error compiling shadow shader: {err:?}");
+ })
+ .ok();
+
let clipped_surface = renderer
.compile_custom_texture_shader(
include_str!("clipped_surface.frag"),
@@ -76,6 +98,7 @@ impl Shaders {
Self {
border,
+ shadow,
clipped_surface,
resize,
custom_resize: RefCell::new(None),
@@ -121,6 +144,7 @@ impl Shaders {
pub fn program(&self, program: ProgramType) -> Option<ShaderProgram> {
match program {
ProgramType::Border => self.border.clone(),
+ ProgramType::Shadow => self.shadow.clone(),
ProgramType::Resize => self
.custom_resize
.borrow()
diff --git a/src/render_helpers/shaders/shadow.frag b/src/render_helpers/shaders/shadow.frag
new file mode 100644
index 00000000..3912d71f
--- /dev/null
+++ b/src/render_helpers/shaders/shadow.frag
@@ -0,0 +1,142 @@
+precision highp float;
+
+#if defined(DEBUG_FLAGS)
+uniform float niri_tint;
+#endif
+
+uniform float niri_alpha;
+uniform float niri_scale;
+
+uniform vec2 niri_size;
+varying vec2 niri_v_coords;
+
+uniform vec4 shadow_color;
+uniform float sigma;
+
+uniform mat3 input_to_geo;
+uniform vec2 geo_size;
+uniform vec4 corner_radius;
+
+uniform mat3 window_input_to_geo;
+uniform vec2 window_geo_size;
+uniform vec4 window_corner_radius;
+
+// Based on: https://madebyevan.com/shaders/fast-rounded-rectangle-shadows/
+//
+// License: CC0 (http://creativecommons.org/publicdomain/zero/1.0/)
+
+// A standard gaussian function, used for weighting samples
+float gaussian(float x, float sigma) {
+ const float pi = 3.141592653589793;
+ return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * pi) * sigma);
+}
+
+// This approximates the error function, needed for the gaussian integral
+vec2 erf(vec2 x) {
+ vec2 s = sign(x), a = abs(x);
+ x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
+ x *= x;
+ return s - s / (x * x);
+}
+
+// Return the blurred mask along the x dimension
+float roundedBoxShadowX(float x, float y, float sigma, float corner, vec2 halfSize) {
+ float delta = min(halfSize.y - corner - abs(y), 0.0);
+ float curved = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta));
+ vec2 integral = 0.5 + 0.5 * erf((x + vec2(-curved, curved)) * (sqrt(0.5) / sigma));
+ return integral.y - integral.x;
+}
+
+// Return the mask for the shadow of a box from lower to upper
+float roundedBoxShadow(vec2 lower, vec2 upper, vec2 point, float sigma, float corner) {
+ // Center everything to make the math easier
+ vec2 center = (lower + upper) * 0.5;
+ vec2 halfSize = (upper - lower) * 0.5;
+ point -= center;
+
+ // The signal is only non-zero in a limited range, so don't waste samples
+ float low = point.y - halfSize.y;
+ float high = point.y + halfSize.y;
+ float start = clamp(-3.0 * sigma, low, high);
+ float end = clamp(3.0 * sigma, low, high);
+
+ // Accumulate samples (we can get away with surprisingly few samples)
+ float step = (end - start) / 4.0;
+ float y = start + step * 0.5;
+ float value = 0.0;
+ for (int i = 0; i < 4; i++) {
+ value += roundedBoxShadowX(point.x, point.y - y, sigma, corner, halfSize) * gaussian(y, sigma) * step;
+ y += step;
+ }
+
+ return value;
+}
+
+float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
+ vec2 center;
+ float radius;
+
+ if (coords.x < corner_radius.x && coords.y < corner_radius.x) {
+ radius = corner_radius.x;
+ center = vec2(radius, radius);
+ } else if (size.x - corner_radius.y < coords.x && coords.y < corner_radius.y) {
+ radius = corner_radius.y;
+ center = vec2(size.x - radius, radius);
+ } else if (size.x - corner_radius.z < coords.x && size.y - corner_radius.z < coords.y) {
+ radius = corner_radius.z;
+ center = vec2(size.x - radius, size.y - radius);
+ } else if (coords.x < corner_radius.w && size.y - corner_radius.w < coords.y) {
+ radius = corner_radius.w;
+ center = vec2(radius, size.y - radius);
+ } else {
+ return 1.0;
+ }
+
+ float dist = distance(coords, center);
+ float half_px = 0.5 / niri_scale;
+ return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
+}
+
+void main() {
+ vec3 coords_geo = input_to_geo * vec3(niri_v_coords, 1.0);
+ vec3 coords_window_geo = window_input_to_geo * vec3(niri_v_coords, 1.0);
+
+ vec4 color = shadow_color;
+
+ float shadow_value;
+ if (sigma < 0.1) {
+ // With low enough sigma just draw a rounded rectangle.
+ shadow_value = rounding_alpha(coords_geo.xy, geo_size, corner_radius);
+ } else {
+ shadow_value = roundedBoxShadow(
+ vec2(0.0, 0.0),
+ geo_size,
+ coords_geo.xy,
+ sigma,
+ // FIXME: figure out how to blur with different corner radii.
+ //
+ // GTK seems to call blurring separately for the rect and for the 4 corners:
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-4-16/gsk/gpu/shaders/gskgpuboxshadow.glsl
+ corner_radius.x
+ );
+ }
+ color = color * shadow_value;
+
+ // Cut out the inside of the window geometry if requested.
+ if (window_geo_size != vec2(0.0, 0.0)) {
+ if (0.0 <= coords_window_geo.x && coords_window_geo.x <= window_geo_size.x
+ && 0.0 <= coords_window_geo.y && coords_window_geo.y <= window_geo_size.y) {
+ float alpha = rounding_alpha(coords_window_geo.xy, window_geo_size, window_corner_radius);
+ color = color * (1.0 - alpha);
+ }
+ }
+
+ color = color * niri_alpha;
+
+#if defined(DEBUG_FLAGS)
+ if (niri_tint == 1.0)
+ color = vec4(0.0, 0.2, 0.0, 0.2) + color * 0.8;
+#endif
+
+ gl_FragColor = color;
+}
diff --git a/src/render_helpers/shadow.rs b/src/render_helpers/shadow.rs
new file mode 100644
index 00000000..b98202e4
--- /dev/null
+++ b/src/render_helpers/shadow.rs
@@ -0,0 +1,257 @@
+use std::collections::HashMap;
+
+use glam::{Mat3, Vec2};
+use niri_config::{Color, 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, OpaqueRegions};
+use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
+
+use super::renderer::NiriRenderer;
+use super::shader_element::ShaderRenderElement;
+use super::shaders::{mat3_uniform, ProgramType, Shaders};
+use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
+
+/// Renders a rounded rectangle shadow.
+#[derive(Debug, Clone)]
+pub struct ShadowRenderElement {
+ inner: ShaderRenderElement,
+ params: Parameters,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+struct Parameters {
+ size: Size<f64, Logical>,
+ geometry: Rectangle<f64, Logical>,
+ color: Color,
+ sigma: f32,
+ corner_radius: CornerRadius,
+ // Should only be used for visual improvements, i.e. corner radius anti-aliasing.
+ scale: f32,
+
+ window_geometry: Rectangle<f64, Logical>,
+ window_corner_radius: CornerRadius,
+}
+
+impl ShadowRenderElement {
+ #[allow(clippy::too_many_arguments)]
+ pub fn new(
+ size: Size<f64, Logical>,
+ geometry: Rectangle<f64, Logical>,
+ color: Color,
+ sigma: f32,
+ corner_radius: CornerRadius,
+ scale: f32,
+ window_geometry: Rectangle<f64, Logical>,
+ window_corner_radius: CornerRadius,
+ ) -> Self {
+ let inner = ShaderRenderElement::empty(ProgramType::Shadow, Kind::Unspecified);
+ let mut rv = Self {
+ inner,
+ params: Parameters {
+ size,
+ geometry,
+ color,
+ sigma,
+ corner_radius,
+ scale,
+ window_geometry,
+ window_corner_radius,
+ },
+ };
+ rv.update_inner();
+ rv
+ }
+
+ pub fn empty() -> Self {
+ let inner = ShaderRenderElement::empty(ProgramType::Shadow, Kind::Unspecified);
+ Self {
+ inner,
+ params: Parameters {
+ size: Default::default(),
+ geometry: Default::default(),
+ color: Default::default(),
+ sigma: 0.,
+ corner_radius: Default::default(),
+ scale: 1.,
+ window_geometry: Default::default(),
+ window_corner_radius: Default::default(),
+ },
+ }
+ }
+
+ pub fn damage_all(&mut self) {
+ self.inner.damage_all();
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ pub fn update(
+ &mut self,
+ size: Size<f64, Logical>,
+ geometry: Rectangle<f64, Logical>,
+ color: Color,
+ sigma: f32,
+ corner_radius: CornerRadius,
+ scale: f32,
+ window_geometry: Rectangle<f64, Logical>,
+ window_corner_radius: CornerRadius,
+ ) {
+ let params = Parameters {
+ size,
+ geometry,
+ color,
+ sigma,
+ corner_radius,
+ scale,
+ window_geometry,
+ window_corner_radius,
+ };
+ if self.params == params {
+ return;
+ }
+
+ self.params = params;
+ self.update_inner();
+ }
+
+ fn update_inner(&mut self) {
+ let Parameters {
+ size,
+ geometry,
+ color,
+ sigma,
+ corner_radius,
+ scale,
+ window_geometry,
+ window_corner_radius,
+ } = self.params;
+
+ let area_size = Vec2::new(size.w as f32, size.h as f32);
+
+ let geo_loc = Vec2::new(geometry.loc.x as f32, geometry.loc.y as f32);
+ let geo_size = Vec2::new(geometry.size.w as f32, geometry.size.h as f32);
+
+ let input_to_geo =
+ Mat3::from_scale(area_size) * Mat3::from_translation(-geo_loc / area_size);
+
+ let window_geo_loc = Vec2::new(window_geometry.loc.x as f32, window_geometry.loc.y as f32);
+ let window_geo_size =
+ Vec2::new(window_geometry.size.w as f32, window_geometry.size.h as f32);
+
+ let window_input_to_geo =
+ Mat3::from_scale(area_size) * Mat3::from_translation(-window_geo_loc / area_size);
+
+ self.inner.update(
+ size,
+ None,
+ scale,
+ vec![
+ Uniform::new("shadow_color", color.to_array_premul()),
+ Uniform::new("sigma", sigma),
+ mat3_uniform("input_to_geo", input_to_geo),
+ Uniform::new("geo_size", geo_size.to_array()),
+ Uniform::new("corner_radius", <[f32; 4]>::from(corner_radius)),
+ mat3_uniform("window_input_to_geo", window_input_to_geo),
+ Uniform::new("window_geo_size", window_geo_size.to_array()),
+ Uniform::new(
+ "window_corner_radius",
+ <[f32; 4]>::from(window_corner_radius),
+ ),
+ ],
+ HashMap::new(),
+ );
+ }
+
+ pub fn with_location(mut self, location: Point<f64, Logical>) -> Self {
+ self.inner = self.inner.with_location(location);
+ self
+ }
+
+ pub fn has_shader(renderer: &mut impl NiriRenderer) -> bool {
+ Shaders::get(renderer)
+ .program(ProgramType::Shadow)
+ .is_some()
+ }
+}
+
+impl Default for ShadowRenderElement {
+ fn default() -> Self {
+ Self::empty()
+ }
+}
+
+impl Element for ShadowRenderElement {
+ fn id(&self) -> &Id {
+ self.inner.id()
+ }
+
+ fn current_commit(&self) -> CommitCounter {
+ self.inner.current_commit()
+ }
+
+ fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
+ self.inner.geometry(scale)
+ }
+
+ fn transform(&self) -> Transform {
+ self.inner.transform()
+ }
+
+ fn src(&self) -> Rectangle<f64, Buffer> {
+ self.inner.src()
+ }
+
+ fn damage_since(
+ &self,
+ scale: Scale<f64>,
+ commit: Option<CommitCounter>,
+ ) -> DamageSet<i32, Physical> {
+ self.inner.damage_since(scale, commit)
+ }
+
+ fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
+ self.inner.opaque_regions(scale)
+ }
+
+ fn alpha(&self) -> f32 {
+ self.inner.alpha()
+ }
+
+ fn kind(&self) -> Kind {
+ self.inner.kind()
+ }
+}
+
+impl RenderElement<GlesRenderer> for ShadowRenderElement {
+ fn draw(
+ &self,
+ frame: &mut GlesFrame<'_>,
+ src: Rectangle<f64, Buffer>,
+ dst: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ opaque_regions: &[Rectangle<i32, Physical>],
+ ) -> Result<(), GlesError> {
+ RenderElement::<GlesRenderer>::draw(&self.inner, frame, src, dst, damage, opaque_regions)
+ }
+
+ fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
+ self.inner.underlying_storage(renderer)
+ }
+}
+
+impl<'render> RenderElement<TtyRenderer<'render>> for ShadowRenderElement {
+ fn draw(
+ &self,
+ frame: &mut TtyFrame<'_, '_>,
+ src: Rectangle<f64, Buffer>,
+ dst: Rectangle<i32, Physical>,
+ damage: &[Rectangle<i32, Physical>],
+ opaque_regions: &[Rectangle<i32, Physical>],
+ ) -> Result<(), TtyRendererError<'render>> {
+ RenderElement::<TtyRenderer<'_>>::draw(&self.inner, frame, src, dst, damage, opaque_regions)
+ }
+
+ fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option<UnderlyingStorage> {
+ self.inner.underlying_storage(renderer)
+ }
+}