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
+50 -13
View File
@@ -477,7 +477,7 @@ pub struct HotkeyOverlay {
pub skip_at_startup: bool,
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Animations {
#[knuffel(child)]
pub off: bool,
@@ -593,19 +593,25 @@ impl Default for WindowMovementAnim {
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WindowResizeAnim(pub Animation);
#[derive(Debug, Clone, PartialEq)]
pub struct WindowResizeAnim {
pub anim: Animation,
pub custom_shader: Option<String>,
}
impl Default for WindowResizeAnim {
fn default() -> Self {
Self(Animation {
Self {
anim: Animation {
off: false,
kind: AnimationKind::Spring(SpringParams {
damping_ratio: 1.,
stiffness: 800,
epsilon: 0.0001,
}),
})
},
custom_shader: None,
}
}
}
@@ -1191,7 +1197,9 @@ where
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
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>,
) -> Result<Self, DecodeError<S>> {
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>,
) -> Result<Self, DecodeError<S>> {
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>,
) -> Result<Self, DecodeError<S>> {
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>,
) -> Result<Self, DecodeError<S>> {
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>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
let default = Self::default().0;
Ok(Self(Animation::decode_node(node, ctx, default)?))
let default = Self::default().anim;
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>,
) -> Result<Self, DecodeError<S>> {
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>,
ctx: &mut knuffel::decode::Context<S>,
default: Self,
mut process_children: impl FnMut(
&knuffel::ast::SpannedNode<S>,
&mut knuffel::decode::Context<S>,
) -> Result<bool, DecodeError<S>>,
) -> Result<Self, DecodeError<S>> {
#[derive(Default, PartialEq)]
struct OptionalEasingParams {
@@ -1360,6 +1395,7 @@ impl Animation {
easing_params.curve = Some(parse_arg_node("curve", child, ctx)?);
}
name_str => {
if !process_children(child, ctx)? {
ctx.emit_error(DecodeError::unexpected(
child,
"node",
@@ -1368,6 +1404,7 @@ impl Animation {
}
}
}
}
let kind = if let Some(spring_params) = spring_params {
// Configured spring.
+9 -2
View File
@@ -491,8 +491,15 @@ impl Tty {
warn!("error binding wl-display in EGL: {err:?}");
}
resources::init(renderer.as_gles_renderer());
shaders::init(renderer.as_gles_renderer());
let gles_renderer = 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.
let primary_formats = renderer.dmabuf_formats().collect::<HashSet<_>>();
+6
View File
@@ -135,6 +135,12 @@ impl Winit {
resources::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);
}
+1 -1
View File
@@ -238,7 +238,7 @@ impl Options {
center_focused_column: layout.center_focused_column,
preset_widths,
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::niri_render_elements;
use crate::render_helpers::crossfade::CrossfadeRenderElement;
use crate::render_helpers::offscreen::OffscreenRenderElement;
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
use crate::render_helpers::renderer::NiriRenderer;
use crate::render_helpers::resize::ResizeRenderElement;
use crate::render_helpers::shaders::Shaders;
use crate::render_helpers::snapshot::RenderSnapshot;
use crate::render_helpers::{render_to_encompassing_texture, RenderTarget, ToRenderElement};
@@ -73,7 +73,7 @@ niri_render_elements! {
FocusRing = FocusRingRenderElement,
SolidColor = SolidColorRenderElement,
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 = max(change.x.abs(), change.y.abs());
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 {
anim,
size_from,
@@ -527,12 +527,12 @@ impl<W: LayoutElement> Tile<W> {
let gles_renderer = renderer.as_gles_renderer();
// If we're resizing, try to render a crossfade, or a fallback.
let mut crossfade = None;
let mut crossfade_fallback = None;
// If we're resizing, try to render a shader, or a fallback.
let mut resize_shader = None;
let mut resize_fallback = None;
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) {
let window_elements =
self.window
@@ -548,7 +548,7 @@ impl<W: LayoutElement> Tile<W> {
.ok();
if let Some((texture_current, _sync_point, texture_current_geo)) = current {
let elem = CrossfadeRenderElement::new(
let elem = ResizeRenderElement::new(
gles_renderer,
area,
scale,
@@ -556,20 +556,21 @@ impl<W: LayoutElement> Tile<W> {
resize.snapshot.size,
(texture_current, texture_current_geo),
window_size,
resize.anim.value() as f32,
resize.anim.clamped_value().clamp(0., 1.) as f32,
alpha,
)
.expect("we checked the crossfade shader above");
.expect("we checked the resize shader above");
self.window
.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.]);
crossfade_fallback = Some(
resize_fallback = Some(
SolidColorRenderElement::from_buffer(
&fallback_buffer,
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.
let mut window = None;
if crossfade.is_none() && crossfade_fallback.is_none() {
if resize_shader.is_none() && resize_fallback.is_none() {
window = Some(
self.window
.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()
.chain(crossfade_fallback)
.chain(resize_fallback)
.chain(window.into_iter().flatten());
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..] {
col.animate_move_from_with_config(
offset,
self.options.animations.window_resize.0,
self.options.animations.window_resize.anim,
);
}
} else {
for col in &mut self.columns[..=col_idx] {
col.animate_move_from_with_config(
-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
// is especially important for always-centered view.
let config = if started_resize_anim {
self.options.animations.window_resize.0
self.options.animations.window_resize.anim
} else {
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..] {
tile.animate_move_y_from_with_config(
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 tracing_subscriber::EnvFilter;
const DEFAULT_LOG_FILTER: &str = "niri=debug,smithay::backend::renderer::gles=error";
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Set backtrace defaults if not set.
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);
tracing_subscriber::fmt()
.compact()
+12 -1
View File
@@ -107,7 +107,9 @@ use crate::protocols::gamma_control::GammaControlManagerState;
use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState};
use crate::pw_utils::{Cast, PipeWire};
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::ui::config_error_notification::ConfigErrorNotification;
use crate::ui::exit_confirm_dialog::ExitConfirmDialog;
@@ -895,6 +897,15 @@ impl State {
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;
// 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;
pub mod crossfade;
pub mod gradient;
pub mod offscreen;
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 render_elements;
pub mod renderer;
pub mod resize;
pub mod resources;
pub mod shaders;
pub mod snapshot;
@@ -120,6 +120,12 @@ impl PixelWithTexturesProgram {
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 {
+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 super::primary_gpu_pixel_shader_with_textures::PixelWithTexturesProgram;
@@ -5,7 +7,8 @@ use super::renderer::NiriRenderer;
pub struct Shaders {
pub gradient_border: Option<GlesPixelProgram>,
pub crossfade: Option<PixelWithTexturesProgram>,
pub resize: Option<PixelWithTexturesProgram>,
pub custom_resize: RefCell<Option<PixelWithTexturesProgram>>,
}
impl Shaders {
@@ -28,26 +31,29 @@ impl Shaders {
})
.ok();
let crossfade = PixelWithTexturesProgram::compile(
let resize = PixelWithTexturesProgram::compile(
renderer,
include_str!("crossfade.frag"),
include_str!("resize.frag"),
&[
UniformName::new("tex_from_loc", UniformType::_2f),
UniformName::new("tex_from_size", UniformType::_2f),
UniformName::new("tex_to_loc", UniformType::_2f),
UniformName::new("tex_to_size", UniformType::_2f),
UniformName::new("amount", UniformType::_1f),
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_from", "tex_to"],
&["tex_prev", "tex_next"],
)
.map_err(|err| {
warn!("error compiling crossfade shader: {err:?}");
warn!("error compiling resize shader: {err:?}");
})
.ok();
Self {
gradient_border,
crossfade,
resize,
custom_resize: RefCell::new(None),
}
}
@@ -57,6 +63,20 @@ impl Shaders {
data.get()
.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) {
@@ -66,3 +86,36 @@ pub fn init(renderer: &mut GlesRenderer) {
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`
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;
}