Implement window-resize custom-shader

This commit is contained in:
Ivan Molodetskikh
2024-04-21 20:10:35 +04:00
parent 2ecbb3f6f8
commit 49f5402669
17 changed files with 616 additions and 252 deletions
+61 -24
View File
@@ -477,7 +477,7 @@ pub struct HotkeyOverlay {
pub skip_at_startup: bool, pub skip_at_startup: bool,
} }
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)] #[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Animations { pub struct Animations {
#[knuffel(child)] #[knuffel(child)]
pub off: bool, pub off: bool,
@@ -593,19 +593,25 @@ impl Default for WindowMovementAnim {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct WindowResizeAnim(pub Animation); pub struct WindowResizeAnim {
pub anim: Animation,
pub custom_shader: Option<String>,
}
impl Default for WindowResizeAnim { impl Default for WindowResizeAnim {
fn default() -> Self { fn default() -> Self {
Self(Animation { Self {
off: false, anim: Animation {
kind: AnimationKind::Spring(SpringParams { off: false,
damping_ratio: 1., kind: AnimationKind::Spring(SpringParams {
stiffness: 800, damping_ratio: 1.,
epsilon: 0.0001, stiffness: 800,
}), epsilon: 0.0001,
}) }),
},
custom_shader: None,
}
} }
} }
@@ -1191,7 +1197,9 @@ where
ctx: &mut knuffel::decode::Context<S>, ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> { ) -> Result<Self, DecodeError<S>> {
let default = Self::default().0; let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?)) Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
} }
} }
@@ -1204,7 +1212,9 @@ where
ctx: &mut knuffel::decode::Context<S>, ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> { ) -> Result<Self, DecodeError<S>> {
let default = Self::default().0; let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?)) Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
} }
} }
@@ -1217,7 +1227,9 @@ where
ctx: &mut knuffel::decode::Context<S>, ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> { ) -> Result<Self, DecodeError<S>> {
let default = Self::default().0; let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?)) Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
} }
} }
@@ -1230,7 +1242,9 @@ where
ctx: &mut knuffel::decode::Context<S>, ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> { ) -> Result<Self, DecodeError<S>> {
let default = Self::default().0; let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?)) Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
} }
} }
@@ -1243,7 +1257,9 @@ where
ctx: &mut knuffel::decode::Context<S>, ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> { ) -> Result<Self, DecodeError<S>> {
let default = Self::default().0; let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?)) Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
} }
} }
@@ -1255,8 +1271,21 @@ where
node: &knuffel::ast::SpannedNode<S>, node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>, ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> { ) -> Result<Self, DecodeError<S>> {
let default = Self::default().0; let default = Self::default().anim;
Ok(Self(Animation::decode_node(node, ctx, default)?)) let mut custom_shader = None;
let anim = Animation::decode_node(node, ctx, default, |child, ctx| {
if &**child.node_name == "custom-shader" {
custom_shader = parse_arg_node("custom-shader", child, ctx)?;
Ok(true)
} else {
Ok(false)
}
})?;
Ok(Self {
anim,
custom_shader,
})
} }
} }
@@ -1269,7 +1298,9 @@ where
ctx: &mut knuffel::decode::Context<S>, ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> { ) -> Result<Self, DecodeError<S>> {
let default = Self::default().0; let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?)) Ok(Self(Animation::decode_node(node, ctx, default, |_, _| {
Ok(false)
})?))
} }
} }
@@ -1278,6 +1309,10 @@ impl Animation {
node: &knuffel::ast::SpannedNode<S>, node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>, ctx: &mut knuffel::decode::Context<S>,
default: Self, default: Self,
mut process_children: impl FnMut(
&knuffel::ast::SpannedNode<S>,
&mut knuffel::decode::Context<S>,
) -> Result<bool, DecodeError<S>>,
) -> Result<Self, DecodeError<S>> { ) -> Result<Self, DecodeError<S>> {
#[derive(Default, PartialEq)] #[derive(Default, PartialEq)]
struct OptionalEasingParams { struct OptionalEasingParams {
@@ -1360,11 +1395,13 @@ impl Animation {
easing_params.curve = Some(parse_arg_node("curve", child, ctx)?); easing_params.curve = Some(parse_arg_node("curve", child, ctx)?);
} }
name_str => { name_str => {
ctx.emit_error(DecodeError::unexpected( if !process_children(child, ctx)? {
child, ctx.emit_error(DecodeError::unexpected(
"node", child,
format!("unexpected node `{}`", name_str.escape_default()), "node",
)); format!("unexpected node `{}`", name_str.escape_default()),
));
}
} }
} }
} }
+9 -2
View File
@@ -491,8 +491,15 @@ impl Tty {
warn!("error binding wl-display in EGL: {err:?}"); warn!("error binding wl-display in EGL: {err:?}");
} }
resources::init(renderer.as_gles_renderer()); let gles_renderer = renderer.as_gles_renderer();
shaders::init(renderer.as_gles_renderer()); resources::init(gles_renderer);
shaders::init(gles_renderer);
let config = self.config.borrow();
if let Some(src) = config.animations.window_resize.custom_shader.as_deref() {
shaders::set_custom_resize_program(gles_renderer, Some(src));
}
drop(config);
// Create the dmabuf global. // Create the dmabuf global.
let primary_formats = renderer.dmabuf_formats().collect::<HashSet<_>>(); let primary_formats = renderer.dmabuf_formats().collect::<HashSet<_>>();
+6
View File
@@ -135,6 +135,12 @@ impl Winit {
resources::init(renderer); resources::init(renderer);
shaders::init(renderer); shaders::init(renderer);
let config = self.config.borrow();
if let Some(src) = config.animations.window_resize.custom_shader.as_deref() {
shaders::set_custom_resize_program(renderer, Some(src));
}
drop(config);
niri.add_output(self.output.clone(), None, false); niri.add_output(self.output.clone(), None, false);
} }
+1 -1
View File
@@ -238,7 +238,7 @@ impl Options {
center_focused_column: layout.center_focused_column, center_focused_column: layout.center_focused_column,
preset_widths, preset_widths,
default_width, default_width,
animations: config.animations, animations: config.animations.clone(),
} }
} }
} }
+16 -15
View File
@@ -16,10 +16,10 @@ use super::{
}; };
use crate::animation::Animation; use crate::animation::Animation;
use crate::niri_render_elements; use crate::niri_render_elements;
use crate::render_helpers::crossfade::CrossfadeRenderElement;
use crate::render_helpers::offscreen::OffscreenRenderElement; use crate::render_helpers::offscreen::OffscreenRenderElement;
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement; use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::resize::ResizeRenderElement;
use crate::render_helpers::shaders::Shaders; use crate::render_helpers::shaders::Shaders;
use crate::render_helpers::snapshot::RenderSnapshot; use crate::render_helpers::snapshot::RenderSnapshot;
use crate::render_helpers::{render_to_encompassing_texture, RenderTarget, ToRenderElement}; use crate::render_helpers::{render_to_encompassing_texture, RenderTarget, ToRenderElement};
@@ -73,7 +73,7 @@ niri_render_elements! {
FocusRing = FocusRingRenderElement, FocusRing = FocusRingRenderElement,
SolidColor = SolidColorRenderElement, SolidColor = SolidColorRenderElement,
Offscreen = RescaleRenderElement<OffscreenRenderElement>, Offscreen = RescaleRenderElement<OffscreenRenderElement>,
Crossfade = CrossfadeRenderElement, Resize = ResizeRenderElement,
} }
} }
@@ -154,7 +154,7 @@ impl<W: LayoutElement> Tile<W> {
let change = self.window.size().to_point() - size_from.to_point(); let change = self.window.size().to_point() - size_from.to_point();
let change = max(change.x.abs(), change.y.abs()); let change = max(change.x.abs(), change.y.abs());
if change > RESIZE_ANIMATION_THRESHOLD { if change > RESIZE_ANIMATION_THRESHOLD {
let anim = Animation::new(0., 1., 0., self.options.animations.window_resize.0); let anim = Animation::new(0., 1., 0., self.options.animations.window_resize.anim);
self.resize_animation = Some(ResizeAnimation { self.resize_animation = Some(ResizeAnimation {
anim, anim,
size_from, size_from,
@@ -527,12 +527,12 @@ impl<W: LayoutElement> Tile<W> {
let gles_renderer = renderer.as_gles_renderer(); let gles_renderer = renderer.as_gles_renderer();
// If we're resizing, try to render a crossfade, or a fallback. // If we're resizing, try to render a shader, or a fallback.
let mut crossfade = None; let mut resize_shader = None;
let mut crossfade_fallback = None; let mut resize_fallback = None;
if let Some(resize) = &self.resize_animation { if let Some(resize) = &self.resize_animation {
if Shaders::get(gles_renderer).crossfade.is_some() { if Shaders::get(gles_renderer).resize().is_some() {
if let Some(texture_from) = resize.snapshot.texture(gles_renderer, scale, target) { if let Some(texture_from) = resize.snapshot.texture(gles_renderer, scale, target) {
let window_elements = let window_elements =
self.window self.window
@@ -548,7 +548,7 @@ impl<W: LayoutElement> Tile<W> {
.ok(); .ok();
if let Some((texture_current, _sync_point, texture_current_geo)) = current { if let Some((texture_current, _sync_point, texture_current_geo)) = current {
let elem = CrossfadeRenderElement::new( let elem = ResizeRenderElement::new(
gles_renderer, gles_renderer,
area, area,
scale, scale,
@@ -556,20 +556,21 @@ impl<W: LayoutElement> Tile<W> {
resize.snapshot.size, resize.snapshot.size,
(texture_current, texture_current_geo), (texture_current, texture_current_geo),
window_size, window_size,
resize.anim.value() as f32,
resize.anim.clamped_value().clamp(0., 1.) as f32, resize.anim.clamped_value().clamp(0., 1.) as f32,
alpha, alpha,
) )
.expect("we checked the crossfade shader above"); .expect("we checked the resize shader above");
self.window self.window
.set_offscreen_element_id(Some(elem.id().clone())); .set_offscreen_element_id(Some(elem.id().clone()));
crossfade = Some(elem.into()); resize_shader = Some(elem.into());
} }
} }
} }
if crossfade.is_none() { if resize_shader.is_none() {
let fallback_buffer = SolidColorBuffer::new(area.size, [1., 0., 0., 1.]); let fallback_buffer = SolidColorBuffer::new(area.size, [1., 0., 0., 1.]);
crossfade_fallback = Some( resize_fallback = Some(
SolidColorRenderElement::from_buffer( SolidColorRenderElement::from_buffer(
&fallback_buffer, &fallback_buffer,
area.loc.to_physical_precise_round(scale), area.loc.to_physical_precise_round(scale),
@@ -585,7 +586,7 @@ impl<W: LayoutElement> Tile<W> {
// If we're not resizing, render the window itself. // If we're not resizing, render the window itself.
let mut window = None; let mut window = None;
if crossfade.is_none() && crossfade_fallback.is_none() { if resize_shader.is_none() && resize_fallback.is_none() {
window = Some( window = Some(
self.window self.window
.render(renderer, window_render_loc, scale, alpha, target) .render(renderer, window_render_loc, scale, alpha, target)
@@ -594,9 +595,9 @@ impl<W: LayoutElement> Tile<W> {
); );
} }
let rv = crossfade let rv = resize_shader
.into_iter() .into_iter()
.chain(crossfade_fallback) .chain(resize_fallback)
.chain(window.into_iter().flatten()); .chain(window.into_iter().flatten());
let elem = self.effective_border_width().map(|width| { let elem = self.effective_border_width().map(|width| {
+4 -4
View File
@@ -1118,14 +1118,14 @@ impl<W: LayoutElement> Workspace<W> {
for col in &mut self.columns[col_idx + 1..] { for col in &mut self.columns[col_idx + 1..] {
col.animate_move_from_with_config( col.animate_move_from_with_config(
offset, offset,
self.options.animations.window_resize.0, self.options.animations.window_resize.anim,
); );
} }
} else { } else {
for col in &mut self.columns[..=col_idx] { for col in &mut self.columns[..=col_idx] {
col.animate_move_from_with_config( col.animate_move_from_with_config(
-offset, -offset,
self.options.animations.window_resize.0, self.options.animations.window_resize.anim,
); );
} }
} }
@@ -1148,7 +1148,7 @@ impl<W: LayoutElement> Workspace<W> {
// Synchronize the horizontal view movement with the resize so that it looks nice. This // Synchronize the horizontal view movement with the resize so that it looks nice. This
// is especially important for always-centered view. // is especially important for always-centered view.
let config = if started_resize_anim { let config = if started_resize_anim {
self.options.animations.window_resize.0 self.options.animations.window_resize.anim
} else { } else {
self.options.animations.horizontal_view_movement.0 self.options.animations.horizontal_view_movement.0
}; };
@@ -2356,7 +2356,7 @@ impl<W: LayoutElement> Column<W> {
for tile in &mut self.tiles[tile_idx + 1..] { for tile in &mut self.tiles[tile_idx + 1..] {
tile.animate_move_y_from_with_config( tile.animate_move_y_from_with_config(
offset, offset,
self.options.animations.window_resize.0, self.options.animations.window_resize.anim,
); );
} }
} }
+3 -1
View File
@@ -29,6 +29,8 @@ use smithay::reexports::calloop::EventLoop;
use smithay::reexports::wayland_server::Display; use smithay::reexports::wayland_server::Display;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
const DEFAULT_LOG_FILTER: &str = "niri=debug,smithay::backend::renderer::gles=error";
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
// Set backtrace defaults if not set. // Set backtrace defaults if not set.
if env::var_os("RUST_BACKTRACE").is_none() { if env::var_os("RUST_BACKTRACE").is_none() {
@@ -50,7 +52,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
); );
} }
let directives = env::var("RUST_LOG").unwrap_or_else(|_| "niri=debug".to_owned()); let directives = env::var("RUST_LOG").unwrap_or_else(|_| DEFAULT_LOG_FILTER.to_owned());
let env_filter = EnvFilter::builder().parse_lossy(directives); let env_filter = EnvFilter::builder().parse_lossy(directives);
tracing_subscriber::fmt() tracing_subscriber::fmt()
.compact() .compact()
+12 -1
View File
@@ -107,7 +107,9 @@ use crate::protocols::gamma_control::GammaControlManagerState;
use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState}; use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState};
use crate::pw_utils::{Cast, PipeWire}; use crate::pw_utils::{Cast, PipeWire};
use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::{render_to_shm, render_to_texture, render_to_vec, RenderTarget}; use crate::render_helpers::{
render_to_shm, render_to_texture, render_to_vec, shaders, RenderTarget,
};
use crate::scroll_tracker::ScrollTracker; use crate::scroll_tracker::ScrollTracker;
use crate::ui::config_error_notification::ConfigErrorNotification; use crate::ui::config_error_notification::ConfigErrorNotification;
use crate::ui::exit_confirm_dialog::ExitConfirmDialog; use crate::ui::exit_confirm_dialog::ExitConfirmDialog;
@@ -895,6 +897,15 @@ impl State {
window_rules_changed = true; window_rules_changed = true;
} }
if config.animations.window_resize.custom_shader
!= old_config.animations.window_resize.custom_shader
{
let src = config.animations.window_resize.custom_shader.as_deref();
self.backend.with_primary_renderer(|renderer| {
shaders::set_custom_resize_program(renderer, src);
});
}
*old_config = config; *old_config = config;
// Release the borrow. // Release the borrow.
-161
View File
@@ -1,161 +0,0 @@
use std::collections::HashMap;
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::PrimaryGpuPixelShaderWithTexturesRenderElement;
use super::renderer::AsGlesFrame;
use super::shaders::Shaders;
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
#[derive(Debug)]
pub struct CrossfadeRenderElement(PrimaryGpuPixelShaderWithTexturesRenderElement);
impl CrossfadeRenderElement {
#[allow(clippy::too_many_arguments)]
pub fn new(
renderer: &mut GlesRenderer,
area: Rectangle<i32, Logical>,
scale: Scale<f64>,
texture_from: (GlesTexture, Rectangle<i32, Physical>),
size_from: Size<i32, Logical>,
texture_to: (GlesTexture, Rectangle<i32, Physical>),
size_to: Size<i32, Logical>,
amount: f32,
result_alpha: f32,
) -> Option<Self> {
let (texture_from, texture_from_geo) = texture_from;
let (texture_to, texture_to_geo) = texture_to;
let scale_from = area.size.to_f64() / size_from.to_f64();
let scale_to = area.size.to_f64() / size_to.to_f64();
let tex_from_geo = texture_from_geo.to_f64().upscale(scale_from);
let tex_to_geo = texture_to_geo.to_f64().upscale(scale_to);
let combined_geo = tex_from_geo.merge(tex_to_geo);
let size = combined_geo
.size
.to_logical(1.)
.to_buffer(1., Transform::Normal);
let area = Rectangle::from_loc_and_size(
area.loc + combined_geo.loc.to_logical(scale).to_i32_round(),
combined_geo.size.to_logical(scale).to_i32_round(),
);
let tex_from_loc = (tex_from_geo.loc - combined_geo.loc)
.downscale((combined_geo.size.w, combined_geo.size.h));
let tex_to_loc = (tex_to_geo.loc - combined_geo.loc)
.downscale((combined_geo.size.w, combined_geo.size.h));
let tex_from_size = tex_from_geo.size / combined_geo.size;
let tex_to_size = tex_to_geo.size / combined_geo.size;
Shaders::get(renderer).crossfade.clone().map(|shader| {
Self(PrimaryGpuPixelShaderWithTexturesRenderElement::new(
shader,
HashMap::from([
(String::from("tex_from"), texture_from),
(String::from("tex_to"), texture_to),
]),
area,
size,
None,
result_alpha,
vec![
Uniform::new(
"tex_from_loc",
(tex_from_loc.x as f32, tex_from_loc.y as f32),
),
Uniform::new(
"tex_from_size",
(tex_from_size.x as f32, tex_from_size.y as f32),
),
Uniform::new("tex_to_loc", (tex_to_loc.x as f32, tex_to_loc.y as f32)),
Uniform::new("tex_to_size", (tex_to_size.x as f32, tex_to_size.y as f32)),
Uniform::new("amount", amount),
],
Kind::Unspecified,
))
})
}
}
impl Element for CrossfadeRenderElement {
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>,
) -> DamageSet<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 CrossfadeRenderElement {
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)?;
Ok(())
}
fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
self.0.underlying_storage(renderer)
}
}
impl<'render> RenderElement<TtyRenderer<'render>> for CrossfadeRenderElement {
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> {
self.0.underlying_storage(renderer)
}
}
+1 -1
View File
@@ -16,7 +16,6 @@ use smithay::wayland::shm;
use self::primary_gpu_texture::PrimaryGpuTextureRenderElement; use self::primary_gpu_texture::PrimaryGpuTextureRenderElement;
pub mod crossfade;
pub mod gradient; pub mod gradient;
pub mod offscreen; pub mod offscreen;
pub mod primary_gpu_pixel_shader; pub mod primary_gpu_pixel_shader;
@@ -24,6 +23,7 @@ pub mod primary_gpu_pixel_shader_with_textures;
pub mod primary_gpu_texture; pub mod primary_gpu_texture;
pub mod render_elements; pub mod render_elements;
pub mod renderer; pub mod renderer;
pub mod resize;
pub mod resources; pub mod resources;
pub mod shaders; pub mod shaders;
pub mod snapshot; pub mod snapshot;
@@ -120,6 +120,12 @@ impl PixelWithTexturesProgram {
compile_program(gl, src, additional_uniforms, texture_uniforms) 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 { impl PrimaryGpuPixelShaderWithTexturesRenderElement {
+199
View File
@@ -0,0 +1,199 @@
use std::collections::HashMap;
use glam::{Mat3, Vec2};
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{
GlesError, GlesFrame, GlesRenderer, GlesTexture, Uniform, UniformValue,
};
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::PrimaryGpuPixelShaderWithTexturesRenderElement;
use super::renderer::AsGlesFrame;
use super::shaders::Shaders;
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
#[derive(Debug)]
pub struct ResizeRenderElement(PrimaryGpuPixelShaderWithTexturesRenderElement);
impl ResizeRenderElement {
#[allow(clippy::too_many_arguments)]
pub fn new(
renderer: &mut GlesRenderer,
area: Rectangle<i32, Logical>,
scale: Scale<f64>,
texture_prev: (GlesTexture, Rectangle<i32, Physical>),
size_prev: Size<i32, Logical>,
texture_next: (GlesTexture, Rectangle<i32, Physical>),
size_next: Size<i32, Logical>,
progress: f32,
clamped_progress: f32,
result_alpha: f32,
) -> Option<Self> {
let curr_geo = area;
let (texture_prev, tex_prev_geo) = texture_prev;
let (texture_next, tex_next_geo) = texture_next;
let scale_prev = area.size.to_f64() / size_prev.to_f64();
let scale_next = area.size.to_f64() / size_next.to_f64();
// Compute the area necessary to fit a crossfade.
let tex_prev_geo_scaled = tex_prev_geo.to_f64().upscale(scale_prev);
let tex_next_geo_scaled = tex_next_geo.to_f64().upscale(scale_next);
let combined_geo = tex_prev_geo_scaled.merge(tex_next_geo_scaled);
let size = combined_geo
.size
.to_logical(1.)
.to_buffer(1., Transform::Normal);
let area = Rectangle::from_loc_and_size(
area.loc + combined_geo.loc.to_logical(scale).to_i32_round(),
combined_geo.size.to_logical(scale).to_i32_round(),
);
// Convert Smithay types into glam types.
let area_loc = Vec2::new(area.loc.x as f32, area.loc.y as f32);
let area_size = Vec2::new(area.size.w as f32, area.size.h as f32);
let curr_geo_loc = Vec2::new(curr_geo.loc.x as f32, curr_geo.loc.y as f32);
let curr_geo_size = Vec2::new(curr_geo.size.w as f32, curr_geo.size.h as f32);
let tex_prev_geo_loc = Vec2::new(tex_prev_geo.loc.x as f32, tex_prev_geo.loc.y as f32);
let tex_prev_geo_size = Vec2::new(tex_prev_geo.size.w as f32, tex_prev_geo.size.h as f32);
let tex_next_geo_loc = Vec2::new(tex_next_geo.loc.x as f32, tex_next_geo.loc.y as f32);
let tex_next_geo_size = Vec2::new(tex_next_geo.size.w as f32, tex_next_geo.size.h as f32);
let size_prev = Vec2::new(size_prev.w as f32, size_prev.h as f32);
let size_next = Vec2::new(size_next.w as f32, size_next.h as f32);
let scale = Vec2::new(scale.x as f32, scale.y as f32);
// Compute the transformation matrices.
let input_to_curr_geo = Mat3::from_scale(area_size / curr_geo_size)
* Mat3::from_translation((area_loc - curr_geo_loc) / area_size);
let input_to_prev_geo = Mat3::from_scale(area_size / size_prev)
* Mat3::from_translation((area_loc - curr_geo_loc) / area_size);
let input_to_next_geo = Mat3::from_scale(area_size / size_next)
* Mat3::from_translation((area_loc - curr_geo_loc) / area_size);
let geo_to_tex_prev = Mat3::from_translation(-tex_prev_geo_loc / tex_prev_geo_size)
* Mat3::from_scale(size_prev / tex_prev_geo_size * scale);
let geo_to_tex_next = Mat3::from_translation(-tex_next_geo_loc / tex_next_geo_size)
* Mat3::from_scale(size_next / tex_next_geo_size * scale);
// Create the shader.
let make_uniform = |name, mat: Mat3| {
Uniform::new(
name,
UniformValue::Matrix3x3 {
matrices: vec![mat.to_cols_array()],
transpose: false,
},
)
};
Shaders::get(renderer).resize().map(|shader| {
Self(PrimaryGpuPixelShaderWithTexturesRenderElement::new(
shader,
HashMap::from([
(String::from("tex_prev"), texture_prev),
(String::from("tex_next"), texture_next),
]),
area,
size,
None,
result_alpha,
vec![
make_uniform("input_to_curr_geo", input_to_curr_geo),
make_uniform("input_to_prev_geo", input_to_prev_geo),
make_uniform("input_to_next_geo", input_to_next_geo),
make_uniform("geo_to_tex_prev", geo_to_tex_prev),
make_uniform("geo_to_tex_next", geo_to_tex_next),
Uniform::new("progress", progress),
Uniform::new("clamped_progress", clamped_progress),
],
Kind::Unspecified,
))
})
}
}
impl Element for ResizeRenderElement {
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>,
) -> DamageSet<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 ResizeRenderElement {
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)?;
Ok(())
}
fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
self.0.underlying_storage(renderer)
}
}
impl<'render> RenderElement<TtyRenderer<'render>> for ResizeRenderElement {
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> {
self.0.underlying_storage(renderer)
}
}
-31
View File
@@ -1,31 +0,0 @@
#version 100
precision mediump float;
uniform sampler2D tex_from;
uniform vec2 tex_from_loc;
uniform vec2 tex_from_size;
uniform sampler2D tex_to;
uniform vec2 tex_to_loc;
uniform vec2 tex_to_size;
uniform float alpha;
uniform float amount;
uniform vec2 size;
varying vec2 v_coords;
void main() {
vec2 coords_from = (v_coords - tex_from_loc) / tex_from_size;
vec2 coords_to = (v_coords - tex_to_loc) / tex_to_size;
vec4 color_from = texture2D(tex_from, coords_from);
vec4 color_to = texture2D(tex_to, coords_to);
vec4 color = mix(color_from, color_to, amount);
color = color * alpha;
gl_FragColor = color;
}
+64 -11
View File
@@ -1,3 +1,5 @@
use std::cell::RefCell;
use smithay::backend::renderer::gles::{GlesPixelProgram, GlesRenderer, UniformName, UniformType}; use smithay::backend::renderer::gles::{GlesPixelProgram, GlesRenderer, UniformName, UniformType};
use super::primary_gpu_pixel_shader_with_textures::PixelWithTexturesProgram; use super::primary_gpu_pixel_shader_with_textures::PixelWithTexturesProgram;
@@ -5,7 +7,8 @@ use super::renderer::NiriRenderer;
pub struct Shaders { pub struct Shaders {
pub gradient_border: Option<GlesPixelProgram>, pub gradient_border: Option<GlesPixelProgram>,
pub crossfade: Option<PixelWithTexturesProgram>, pub resize: Option<PixelWithTexturesProgram>,
pub custom_resize: RefCell<Option<PixelWithTexturesProgram>>,
} }
impl Shaders { impl Shaders {
@@ -28,26 +31,29 @@ impl Shaders {
}) })
.ok(); .ok();
let crossfade = PixelWithTexturesProgram::compile( let resize = PixelWithTexturesProgram::compile(
renderer, renderer,
include_str!("crossfade.frag"), include_str!("resize.frag"),
&[ &[
UniformName::new("tex_from_loc", UniformType::_2f), UniformName::new("input_to_curr_geo", UniformType::Matrix3x3),
UniformName::new("tex_from_size", UniformType::_2f), UniformName::new("input_to_prev_geo", UniformType::Matrix3x3),
UniformName::new("tex_to_loc", UniformType::_2f), UniformName::new("input_to_next_geo", UniformType::Matrix3x3),
UniformName::new("tex_to_size", UniformType::_2f), UniformName::new("geo_to_tex_prev", UniformType::Matrix3x3),
UniformName::new("amount", UniformType::_1f), UniformName::new("geo_to_tex_next", UniformType::Matrix3x3),
UniformName::new("progress", UniformType::_1f),
UniformName::new("clamped_progress", UniformType::_1f),
], ],
&["tex_from", "tex_to"], &["tex_prev", "tex_next"],
) )
.map_err(|err| { .map_err(|err| {
warn!("error compiling crossfade shader: {err:?}"); warn!("error compiling resize shader: {err:?}");
}) })
.ok(); .ok();
Self { Self {
gradient_border, gradient_border,
crossfade, resize,
custom_resize: RefCell::new(None),
} }
} }
@@ -57,6 +63,20 @@ impl Shaders {
data.get() data.get()
.expect("shaders::init() must be called when creating the renderer") .expect("shaders::init() must be called when creating the renderer")
} }
pub fn replace_custom_resize_program(
&self,
program: Option<PixelWithTexturesProgram>,
) -> Option<PixelWithTexturesProgram> {
self.custom_resize.replace(program)
}
pub fn resize(&self) -> Option<PixelWithTexturesProgram> {
self.custom_resize
.borrow()
.clone()
.or_else(|| self.resize.clone())
}
} }
pub fn init(renderer: &mut GlesRenderer) { pub fn init(renderer: &mut GlesRenderer) {
@@ -66,3 +86,36 @@ pub fn init(renderer: &mut GlesRenderer) {
error!("shaders were already compiled"); error!("shaders were already compiled");
} }
} }
pub fn set_custom_resize_program(renderer: &mut GlesRenderer, src: Option<&str>) {
let program = if let Some(src) = src {
match PixelWithTexturesProgram::compile(
renderer,
src,
&[
UniformName::new("input_to_curr_geo", UniformType::Matrix3x3),
UniformName::new("input_to_prev_geo", UniformType::Matrix3x3),
UniformName::new("input_to_next_geo", UniformType::Matrix3x3),
UniformName::new("geo_to_tex_prev", UniformType::Matrix3x3),
UniformName::new("geo_to_tex_next", UniformType::Matrix3x3),
UniformName::new("progress", UniformType::_1f),
UniformName::new("clamped_progress", UniformType::_1f),
],
&["tex_prev", "tex_next"],
) {
Ok(program) => Some(program),
Err(err) => {
warn!("error compiling custom resize shader: {err:?}");
return;
}
}
} else {
None
};
if let Some(prev) = Shaders::get(renderer).replace_custom_resize_program(program) {
if let Err(err) = prev.destroy(renderer) {
warn!("error destroying previous custom resize shader: {err:?}");
}
}
}
+41
View File
@@ -0,0 +1,41 @@
#version 100
precision mediump float;
varying vec2 v_coords;
uniform vec2 size;
uniform mat3 input_to_curr_geo;
uniform mat3 input_to_prev_geo;
uniform mat3 input_to_next_geo;
uniform sampler2D tex_prev;
uniform mat3 geo_to_tex_prev;
uniform sampler2D tex_next;
uniform mat3 geo_to_tex_next;
uniform float progress;
uniform float clamped_progress;
uniform float alpha;
vec4 crossfade() {
vec3 coords_curr_geo = input_to_curr_geo * vec3(v_coords, 1.0);
vec3 coords_tex_prev = geo_to_tex_prev * coords_curr_geo;
vec4 color_prev = texture2D(tex_prev, vec2(coords_tex_prev));
vec3 coords_tex_next = geo_to_tex_next * coords_curr_geo;
vec4 color_next = texture2D(tex_next, vec2(coords_tex_next));
vec4 color = mix(color_prev, color_next, clamped_progress);
return color;
}
void main() {
vec4 color = crossfade();
gl_FragColor = color * alpha;
}
+41
View File
@@ -224,6 +224,47 @@ animations {
} }
``` ```
##### `custom-shader`
<sup>Since: 0.1.6, experimental</sup>
You can write a custom shader for drawing the window during a resize animation.
See [this example shader](./examples/resize-custom-shader.frag) for a full documentation with several animations to experiment with.
If a custom shader fails to compile, niri will print a warning and fall back to the default, or previous successfully compiled shader.
> [!NOTE]
>
> Custom shaders do not have a backwards compatibility guarantee.
> I may need to change their interface as I'm developing new features.
```
animations {
window-resize {
spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
custom-shader r"
#version 100
precision mediump float;
varying vec2 v_coords;
uniform mat3 input_to_curr_geo;
uniform sampler2D tex_next;
uniform mat3 geo_to_tex_next;
uniform float alpha;
void main() {
vec3 coords_curr_geo = input_to_curr_geo * vec3(v_coords, 1.0);
vec3 coords_tex_next = geo_to_tex_next * coords_curr_geo;
vec4 color = texture2D(tex_next, vec2(coords_tex_next));
gl_FragColor = color * alpha;
}
"
}
}
```
#### `config-notification-open-close` #### `config-notification-open-close`
The open/close animation of the config parse error and new default config notifications. The open/close animation of the config parse error and new default config notifications.
+152
View File
@@ -0,0 +1,152 @@
#version 100
precision mediump float;
// Coordinates of the current pixel.
//
// These range from 0 to 1 over the whole area of the shader. The location and
// the size of the area are unspecified, but niri will make it large enough to
// accomodate a crossfade.
//
// You very likely want to convert these coordinates to geometry coordinates
// before using them (see below).
varying vec2 v_coords;
// Pixel size of the whole area of the shader.
uniform vec2 size;
// Matrix that converts the input v_coords into coordinates inside the current
// window geometry.
//
// The window geometry is its "visible bounds" from the user's perspective.
// After applying this matrix, the 0 to 1 coordinate range will correspond to
// the current geometry (in the middle of a resize), and pixels outside the
// geometry will have coordinates below 0 or above 1.
uniform mat3 input_to_curr_geo;
// Matrix that converts the input v_coords into coordinates inside the previous
// (before resize) window geometry.
uniform mat3 input_to_prev_geo;
// Matrix that converts the input v_coords into coordinates inside the next
// (after resize) window geometry.
uniform mat3 input_to_next_geo;
// Previous (before resize) window texture.
uniform sampler2D tex_prev;
// Matrix that converts geometry coordinates into the previous window texture
// coordinates.
//
// The window texture can and will go outside the geometry (for client-side
// decoration shadows for example), which is why this matrix is necessary.
uniform mat3 geo_to_tex_prev;
// Next (after resize) window texture.
uniform sampler2D tex_next;
// Matrix that converts geometry coordinates into the next window texture
// coordinates.
uniform mat3 geo_to_tex_next;
// Unclamped progress of the resize.
//
// Goes from 0 to 1 but may overshoot and oscillate.
uniform float progress;
// Clamped progress of the resize.
//
// Goes from 0 to 1, but will stop at 1 as soon as it first reaches 1. Will not
// overshoot or oscillate.
uniform float clamped_progress;
// Additional opacity to apply to the final color.
uniform float alpha;
// Example: fill the current geometry with a solid vertical gradient.
vec4 solid_gradient() {
vec3 coords = input_to_curr_geo * vec3(v_coords, 1.0);
vec4 color = vec4(0.0);
// Paint only the area inside the current geometry.
if (0.0 <= coords.x && coords.x <= 1.0
&& 0.0 <= coords.y && coords.y <= 1.0)
{
vec4 from = vec4(1.0, 0.0, 0.0, 1.0);
vec4 to = vec4(0.0, 1.0, 0.0, 1.0);
color = mix(from, to, coords.y);
}
return color;
}
// Example: crossfade between previous and next texture, stretched to the
// current geometry.
vec4 crossfade() {
vec3 coords_curr_geo = input_to_curr_geo * vec3(v_coords, 1.0);
vec3 coords_tex_prev = geo_to_tex_prev * coords_curr_geo;
vec4 color_prev = texture2D(tex_prev, vec2(coords_tex_prev));
vec3 coords_tex_next = geo_to_tex_next * coords_curr_geo;
vec4 color_next = texture2D(tex_next, vec2(coords_tex_next));
vec4 color = mix(color_prev, color_next, clamped_progress);
return color;
}
// Example: next texture, stretched to the current geometry.
vec4 stretch_next() {
vec3 coords_curr_geo = input_to_curr_geo * vec3(v_coords, 1.0);
vec3 coords_tex_next = geo_to_tex_next * coords_curr_geo;
vec4 color = texture2D(tex_next, vec2(coords_tex_next));
return color;
}
// Example: next texture, stretched to the current geometry if smaller, and
// cropped if bigger.
vec4 stretch_or_crop_next() {
vec3 coords_curr_geo = input_to_curr_geo * vec3(v_coords, 1.0);
vec3 coords_next_geo = input_to_next_geo * vec3(v_coords, 1.0);
vec3 coords_stretch = geo_to_tex_next * coords_curr_geo;
vec3 coords_crop = geo_to_tex_next * coords_next_geo;
// If the crop coord is smaller than the stretch coord, then the next
// texture size is bigger than the current geometry, which means that we
// can crop.
vec3 coords = coords_stretch;
if (coords_crop.x < coords_stretch.x)
coords.x = coords_crop.x;
if (coords_crop.y < coords_stretch.y)
coords.y = coords_crop.y;
vec4 color = texture2D(tex_next, vec2(coords));
// However, when we crop, we also want to crop out anything outside the
// current geometry. This is because the area of the shader is unspecified
// and usually bigger than the current geometry, so if we don't fill pixels
// outside with transparency, the texture will leak out.
//
// When stretching, this is not an issue because the area outside will
// correspond to client-side decoration shadows, which are already supposed
// to be outside.
if (coords_crop.x < coords_stretch.x
&& (coords_curr_geo.x < 0.0 || 1.0 < coords_curr_geo.x))
color = vec4(0.0);
if (coords_crop.y < coords_stretch.y
&& (coords_curr_geo.y < 0.0 || 1.0 < coords_curr_geo.y))
color = vec4(0.0);
return color;
}
// The main entry point of the shader.
void main() {
// You can pick one of the example functions or write your own.
vec4 color = stretch_or_crop_next();
gl_FragColor = color * alpha;
}