mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Implement custom shader for window-open
This commit is contained in:
+37
-17
@@ -537,18 +537,24 @@ impl Default for WorkspaceSwitchAnim {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct WindowOpenAnim(pub Animation);
|
pub struct WindowOpenAnim {
|
||||||
|
pub anim: Animation,
|
||||||
|
pub custom_shader: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for WindowOpenAnim {
|
impl Default for WindowOpenAnim {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(Animation {
|
Self {
|
||||||
off: false,
|
anim: Animation {
|
||||||
kind: AnimationKind::Easing(EasingParams {
|
off: false,
|
||||||
duration_ms: 150,
|
kind: AnimationKind::Easing(EasingParams {
|
||||||
curve: AnimationCurve::EaseOutExpo,
|
duration_ms: 150,
|
||||||
}),
|
curve: AnimationCurve::EaseOutExpo,
|
||||||
})
|
}),
|
||||||
|
},
|
||||||
|
custom_shader: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1411,10 +1417,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;
|
||||||
Ok(false)
|
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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2414,10 +2431,13 @@ mod tests {
|
|||||||
curve: AnimationCurve::EaseOutExpo,
|
curve: AnimationCurve::EaseOutExpo,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
window_open: WindowOpenAnim(Animation {
|
window_open: WindowOpenAnim {
|
||||||
off: true,
|
anim: Animation {
|
||||||
..WindowOpenAnim::default().0
|
off: true,
|
||||||
}),
|
..WindowOpenAnim::default().anim
|
||||||
|
},
|
||||||
|
custom_shader: None,
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
environment: Environment(vec![
|
environment: Environment(vec![
|
||||||
|
|||||||
@@ -508,6 +508,9 @@ impl Tty {
|
|||||||
if let Some(src) = config.animations.window_close.custom_shader.as_deref() {
|
if let Some(src) = config.animations.window_close.custom_shader.as_deref() {
|
||||||
shaders::set_custom_close_program(gles_renderer, Some(src));
|
shaders::set_custom_close_program(gles_renderer, Some(src));
|
||||||
}
|
}
|
||||||
|
if let Some(src) = config.animations.window_open.custom_shader.as_deref() {
|
||||||
|
shaders::set_custom_open_program(gles_renderer, Some(src));
|
||||||
|
}
|
||||||
drop(config);
|
drop(config);
|
||||||
|
|
||||||
niri.layout.update_shaders();
|
niri.layout.update_shaders();
|
||||||
|
|||||||
@@ -143,6 +143,9 @@ impl Winit {
|
|||||||
if let Some(src) = config.animations.window_close.custom_shader.as_deref() {
|
if let Some(src) = config.animations.window_close.custom_shader.as_deref() {
|
||||||
shaders::set_custom_close_program(renderer, Some(src));
|
shaders::set_custom_close_program(renderer, Some(src));
|
||||||
}
|
}
|
||||||
|
if let Some(src) = config.animations.window_open.custom_shader.as_deref() {
|
||||||
|
shaders::set_custom_open_program(renderer, Some(src));
|
||||||
|
}
|
||||||
drop(config);
|
drop(config);
|
||||||
|
|
||||||
niri.layout.update_shaders();
|
niri.layout.update_shaders();
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ use crate::window::ResolvedWindowRules;
|
|||||||
pub mod closing_window;
|
pub mod closing_window;
|
||||||
pub mod focus_ring;
|
pub mod focus_ring;
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
|
pub mod opening_window;
|
||||||
pub mod tile;
|
pub mod tile;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use glam::{Mat3, Vec2};
|
||||||
|
use smithay::backend::allocator::Fourcc;
|
||||||
|
use smithay::backend::renderer::element::texture::TextureRenderElement;
|
||||||
|
use smithay::backend::renderer::element::utils::{
|
||||||
|
Relocate, RelocateRenderElement, RescaleRenderElement,
|
||||||
|
};
|
||||||
|
use smithay::backend::renderer::element::{Id, Kind, RenderElement};
|
||||||
|
use smithay::backend::renderer::gles::{GlesRenderer, Uniform};
|
||||||
|
use smithay::backend::renderer::{Renderer as _, Texture};
|
||||||
|
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
||||||
|
|
||||||
|
use crate::animation::Animation;
|
||||||
|
use crate::niri_render_elements;
|
||||||
|
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||||
|
use crate::render_helpers::render_to_encompassing_texture;
|
||||||
|
use crate::render_helpers::shader_element::ShaderRenderElement;
|
||||||
|
use crate::render_helpers::shaders::{mat3_uniform, ProgramType, Shaders};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OpenAnimation {
|
||||||
|
anim: Animation,
|
||||||
|
random_seed: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
niri_render_elements! {
|
||||||
|
OpeningWindowRenderElement => {
|
||||||
|
Texture = RelocateRenderElement<RescaleRenderElement<PrimaryGpuTextureRenderElement>>,
|
||||||
|
Shader = ShaderRenderElement,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenAnimation {
|
||||||
|
pub fn new(anim: Animation) -> Self {
|
||||||
|
Self {
|
||||||
|
anim,
|
||||||
|
random_seed: fastrand::f32(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance_animations(&mut self, current_time: Duration) {
|
||||||
|
self.anim.set_current_time(current_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_done(&self) -> bool {
|
||||||
|
self.anim.is_done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't depend on view_rect here, because the result of window opening can be snapshot and
|
||||||
|
// then rendered elsewhere.
|
||||||
|
pub fn render(
|
||||||
|
&self,
|
||||||
|
renderer: &mut GlesRenderer,
|
||||||
|
elements: &[impl RenderElement<GlesRenderer>],
|
||||||
|
geo_size: Size<i32, Logical>,
|
||||||
|
location: Point<i32, Logical>,
|
||||||
|
scale: Scale<f64>,
|
||||||
|
) -> anyhow::Result<OpeningWindowRenderElement> {
|
||||||
|
let progress = self.anim.value();
|
||||||
|
let clamped_progress = self.anim.clamped_value().clamp(0., 1.);
|
||||||
|
|
||||||
|
let (texture, _sync_point, geo) = render_to_encompassing_texture(
|
||||||
|
renderer,
|
||||||
|
scale,
|
||||||
|
Transform::Normal,
|
||||||
|
Fourcc::Abgr8888,
|
||||||
|
elements,
|
||||||
|
)
|
||||||
|
.context("error rendering to texture")?;
|
||||||
|
|
||||||
|
let offset = geo.loc.to_f64().to_logical(scale);
|
||||||
|
let texture_size = geo.size.to_f64().to_logical(scale);
|
||||||
|
|
||||||
|
if Shaders::get(renderer).program(ProgramType::Open).is_some() {
|
||||||
|
let mut area = Rectangle::from_loc_and_size(location.to_f64() + offset, texture_size);
|
||||||
|
|
||||||
|
// Expand the area a bit to allow for more varied effects.
|
||||||
|
let mut target_size = area.size.upscale(1.5);
|
||||||
|
target_size.w = f64::max(area.size.w + 1000., target_size.w);
|
||||||
|
target_size.h = f64::max(area.size.h + 1000., target_size.h);
|
||||||
|
let diff = target_size.to_point() - area.size.to_point();
|
||||||
|
area.loc -= diff.downscale(2.);
|
||||||
|
area.size += diff.to_size();
|
||||||
|
|
||||||
|
let area = area.to_i32_up();
|
||||||
|
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 geo_loc = Vec2::new(location.x as f32, location.y as f32);
|
||||||
|
let geo_size = Vec2::new(geo_size.w as f32, geo_size.h as f32);
|
||||||
|
|
||||||
|
let input_to_geo = Mat3::from_scale(area_size / geo_size)
|
||||||
|
* Mat3::from_translation((area_loc - geo_loc) / area_size);
|
||||||
|
|
||||||
|
let tex_scale = Vec2::new(scale.x as f32, scale.y as f32);
|
||||||
|
let tex_loc = Vec2::new(offset.x as f32, offset.y as f32);
|
||||||
|
let tex_size = Vec2::new(texture.width() as f32, texture.height() as f32) / tex_scale;
|
||||||
|
|
||||||
|
let geo_to_tex =
|
||||||
|
Mat3::from_translation(-tex_loc / tex_size) * Mat3::from_scale(geo_size / tex_size);
|
||||||
|
|
||||||
|
return Ok(ShaderRenderElement::new(
|
||||||
|
ProgramType::Open,
|
||||||
|
area.size,
|
||||||
|
None,
|
||||||
|
1.,
|
||||||
|
vec![
|
||||||
|
mat3_uniform("niri_input_to_geo", input_to_geo),
|
||||||
|
Uniform::new("niri_geo_size", geo_size.to_array()),
|
||||||
|
mat3_uniform("niri_geo_to_tex", geo_to_tex),
|
||||||
|
Uniform::new("niri_progress", progress as f32),
|
||||||
|
Uniform::new("niri_clamped_progress", clamped_progress as f32),
|
||||||
|
Uniform::new("niri_random_seed", self.random_seed),
|
||||||
|
],
|
||||||
|
HashMap::from([(String::from("niri_tex"), texture.clone())]),
|
||||||
|
Kind::Unspecified,
|
||||||
|
)
|
||||||
|
.with_location(area.loc)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let elem = TextureRenderElement::from_static_texture(
|
||||||
|
Id::new(),
|
||||||
|
renderer.id(),
|
||||||
|
Point::from((0., 0.)),
|
||||||
|
texture.clone(),
|
||||||
|
scale.x as i32,
|
||||||
|
Transform::Normal,
|
||||||
|
Some(clamped_progress as f32),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Kind::Unspecified,
|
||||||
|
);
|
||||||
|
|
||||||
|
let elem = PrimaryGpuTextureRenderElement(elem);
|
||||||
|
|
||||||
|
let center = geo_size.to_point().to_f64().downscale(2.);
|
||||||
|
let elem = RescaleRenderElement::from_element(
|
||||||
|
elem,
|
||||||
|
(center - offset).to_physical_precise_round(scale),
|
||||||
|
(progress / 2. + 0.5).max(0.),
|
||||||
|
);
|
||||||
|
|
||||||
|
let elem = RelocateRenderElement::from_element(
|
||||||
|
elem,
|
||||||
|
(location.to_f64() + offset).to_physical_precise_round(scale),
|
||||||
|
Relocate::Relative,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(elem.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
-44
@@ -5,12 +5,12 @@ use std::time::Duration;
|
|||||||
use niri_config::CornerRadius;
|
use niri_config::CornerRadius;
|
||||||
use smithay::backend::allocator::Fourcc;
|
use smithay::backend::allocator::Fourcc;
|
||||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||||
use smithay::backend::renderer::element::utils::RescaleRenderElement;
|
|
||||||
use smithay::backend::renderer::element::{Element, Kind};
|
use smithay::backend::renderer::element::{Element, Kind};
|
||||||
use smithay::backend::renderer::gles::GlesRenderer;
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
||||||
|
|
||||||
use super::focus_ring::{FocusRing, FocusRingRenderElement};
|
use super::focus_ring::{FocusRing, FocusRingRenderElement};
|
||||||
|
use super::opening_window::{OpenAnimation, OpeningWindowRenderElement};
|
||||||
use super::{
|
use super::{
|
||||||
LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot, Options,
|
LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot, Options,
|
||||||
RESIZE_ANIMATION_THRESHOLD,
|
RESIZE_ANIMATION_THRESHOLD,
|
||||||
@@ -20,7 +20,6 @@ use crate::niri_render_elements;
|
|||||||
use crate::render_helpers::border::BorderRenderElement;
|
use crate::render_helpers::border::BorderRenderElement;
|
||||||
use crate::render_helpers::clipped_surface::{ClippedSurfaceRenderElement, RoundedCornerDamage};
|
use crate::render_helpers::clipped_surface::{ClippedSurfaceRenderElement, RoundedCornerDamage};
|
||||||
use crate::render_helpers::damage::ExtraDamage;
|
use crate::render_helpers::damage::ExtraDamage;
|
||||||
use crate::render_helpers::offscreen::OffscreenRenderElement;
|
|
||||||
use crate::render_helpers::renderer::NiriRenderer;
|
use crate::render_helpers::renderer::NiriRenderer;
|
||||||
use crate::render_helpers::resize::ResizeRenderElement;
|
use crate::render_helpers::resize::ResizeRenderElement;
|
||||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||||
@@ -54,7 +53,7 @@ pub struct Tile<W: LayoutElement> {
|
|||||||
fullscreen_size: Size<i32, Logical>,
|
fullscreen_size: Size<i32, Logical>,
|
||||||
|
|
||||||
/// The animation upon opening a window.
|
/// The animation upon opening a window.
|
||||||
open_animation: Option<Animation>,
|
open_animation: Option<OpenAnimation>,
|
||||||
|
|
||||||
/// The animation of the window resizing.
|
/// The animation of the window resizing.
|
||||||
resize_animation: Option<ResizeAnimation>,
|
resize_animation: Option<ResizeAnimation>,
|
||||||
@@ -80,7 +79,7 @@ niri_render_elements! {
|
|||||||
LayoutElement = LayoutElementRenderElement<R>,
|
LayoutElement = LayoutElementRenderElement<R>,
|
||||||
FocusRing = FocusRingRenderElement,
|
FocusRing = FocusRingRenderElement,
|
||||||
SolidColor = SolidColorRenderElement,
|
SolidColor = SolidColorRenderElement,
|
||||||
Offscreen = RescaleRenderElement<OffscreenRenderElement>,
|
Opening = OpeningWindowRenderElement,
|
||||||
Resize = ResizeRenderElement,
|
Resize = ResizeRenderElement,
|
||||||
Border = BorderRenderElement,
|
Border = BorderRenderElement,
|
||||||
ClippedSurface = ClippedSurfaceRenderElement<R>,
|
ClippedSurface = ClippedSurfaceRenderElement<R>,
|
||||||
@@ -201,9 +200,9 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn advance_animations(&mut self, current_time: Duration) {
|
pub fn advance_animations(&mut self, current_time: Duration) {
|
||||||
if let Some(anim) = &mut self.open_animation {
|
if let Some(open) = &mut self.open_animation {
|
||||||
anim.set_current_time(current_time);
|
open.advance_animations(current_time);
|
||||||
if anim.is_done() {
|
if open.is_done() {
|
||||||
self.open_animation = None;
|
self.open_animation = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,16 +298,12 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_open_animation(&mut self) {
|
pub fn start_open_animation(&mut self) {
|
||||||
self.open_animation = Some(Animation::new(
|
self.open_animation = Some(OpenAnimation::new(Animation::new(
|
||||||
0.,
|
0.,
|
||||||
1.,
|
1.,
|
||||||
0.,
|
0.,
|
||||||
self.options.animations.window_open.0,
|
self.options.animations.window_open.anim,
|
||||||
));
|
)));
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_animation(&self) -> &Option<Animation> {
|
|
||||||
&self.open_animation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize_animation(&self) -> Option<&Animation> {
|
pub fn resize_animation(&self) -> Option<&Animation> {
|
||||||
@@ -797,39 +792,34 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
) -> impl Iterator<Item = TileRenderElement<R>> {
|
) -> impl Iterator<Item = TileRenderElement<R>> {
|
||||||
let _span = tracy_client::span!("Tile::render");
|
let _span = tracy_client::span!("Tile::render");
|
||||||
|
|
||||||
if let Some(anim) = &self.open_animation {
|
let mut open_anim_elem = None;
|
||||||
|
let mut window_elems = None;
|
||||||
|
|
||||||
|
if let Some(open) = &self.open_animation {
|
||||||
let renderer = renderer.as_gles_renderer();
|
let renderer = renderer.as_gles_renderer();
|
||||||
let elements = self.render_inner(renderer, location, scale, focus_ring, target);
|
let elements =
|
||||||
|
self.render_inner(renderer, Point::from((0, 0)), scale, focus_ring, target);
|
||||||
let elements = elements.collect::<Vec<TileRenderElement<_>>>();
|
let elements = elements.collect::<Vec<TileRenderElement<_>>>();
|
||||||
|
match open.render(renderer, &elements, self.tile_size(), location, scale) {
|
||||||
let elem = OffscreenRenderElement::new(
|
Ok(elem) => {
|
||||||
renderer,
|
self.window()
|
||||||
scale.x as i32,
|
.set_offscreen_element_id(Some(elem.id().clone()));
|
||||||
&elements,
|
open_anim_elem = Some(elem.into());
|
||||||
anim.clamped_value().clamp(0., 1.) as f32,
|
}
|
||||||
);
|
Err(err) => {
|
||||||
self.window()
|
warn!("error rendering window opening animation: {err:?}");
|
||||||
.set_offscreen_element_id(Some(elem.id().clone()));
|
}
|
||||||
|
}
|
||||||
let mut center = location;
|
|
||||||
center.x += self.tile_size().w / 2;
|
|
||||||
center.y += self.tile_size().h / 2;
|
|
||||||
|
|
||||||
Some(TileRenderElement::Offscreen(
|
|
||||||
RescaleRenderElement::from_element(
|
|
||||||
elem,
|
|
||||||
center.to_physical_precise_round(scale),
|
|
||||||
(anim.value() / 2. + 0.5).max(0.),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.into_iter()
|
|
||||||
.chain(None.into_iter().flatten())
|
|
||||||
} else {
|
|
||||||
self.window().set_offscreen_element_id(None);
|
|
||||||
|
|
||||||
let elements = self.render_inner(renderer, location, scale, focus_ring, target);
|
|
||||||
None.into_iter().chain(Some(elements).into_iter().flatten())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if open_anim_elem.is_none() {
|
||||||
|
self.window().set_offscreen_element_id(None);
|
||||||
|
window_elems = Some(self.render_inner(renderer, location, scale, focus_ring, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
open_anim_elem
|
||||||
|
.into_iter()
|
||||||
|
.chain(window_elems.into_iter().flatten())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_unmap_snapshot_if_empty(
|
pub fn store_unmap_snapshot_if_empty(
|
||||||
|
|||||||
+10
@@ -951,6 +951,16 @@ impl State {
|
|||||||
shaders_changed = true;
|
shaders_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.animations.window_open.custom_shader
|
||||||
|
!= old_config.animations.window_open.custom_shader
|
||||||
|
{
|
||||||
|
let src = config.animations.window_open.custom_shader.as_deref();
|
||||||
|
self.backend.with_primary_renderer(|renderer| {
|
||||||
|
shaders::set_custom_open_program(renderer, src);
|
||||||
|
});
|
||||||
|
shaders_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if config.debug != old_config.debug {
|
if config.debug != old_config.debug {
|
||||||
debug_config_changed = true;
|
debug_config_changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub struct Shaders {
|
|||||||
pub resize: Option<ShaderProgram>,
|
pub resize: Option<ShaderProgram>,
|
||||||
pub custom_resize: RefCell<Option<ShaderProgram>>,
|
pub custom_resize: RefCell<Option<ShaderProgram>>,
|
||||||
pub custom_close: RefCell<Option<ShaderProgram>>,
|
pub custom_close: RefCell<Option<ShaderProgram>>,
|
||||||
|
pub custom_open: RefCell<Option<ShaderProgram>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -22,6 +23,7 @@ pub enum ProgramType {
|
|||||||
Border,
|
Border,
|
||||||
Resize,
|
Resize,
|
||||||
Close,
|
Close,
|
||||||
|
Open,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shaders {
|
impl Shaders {
|
||||||
@@ -75,6 +77,7 @@ impl Shaders {
|
|||||||
resize,
|
resize,
|
||||||
custom_resize: RefCell::new(None),
|
custom_resize: RefCell::new(None),
|
||||||
custom_close: RefCell::new(None),
|
custom_close: RefCell::new(None),
|
||||||
|
custom_open: RefCell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +108,13 @@ impl Shaders {
|
|||||||
self.custom_close.replace(program)
|
self.custom_close.replace(program)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace_custom_open_program(
|
||||||
|
&self,
|
||||||
|
program: Option<ShaderProgram>,
|
||||||
|
) -> Option<ShaderProgram> {
|
||||||
|
self.custom_open.replace(program)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn program(&self, program: ProgramType) -> Option<ShaderProgram> {
|
pub fn program(&self, program: ProgramType) -> Option<ShaderProgram> {
|
||||||
match program {
|
match program {
|
||||||
ProgramType::Border => self.border.clone(),
|
ProgramType::Border => self.border.clone(),
|
||||||
@@ -114,6 +124,7 @@ impl Shaders {
|
|||||||
.clone()
|
.clone()
|
||||||
.or_else(|| self.resize.clone()),
|
.or_else(|| self.resize.clone()),
|
||||||
ProgramType::Close => self.custom_close.borrow().clone(),
|
ProgramType::Close => self.custom_close.borrow().clone(),
|
||||||
|
ProgramType::Open => self.custom_open.borrow().clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,6 +227,49 @@ pub fn set_custom_close_program(renderer: &mut GlesRenderer, src: Option<&str>)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile_open_program(
|
||||||
|
renderer: &mut GlesRenderer,
|
||||||
|
src: &str,
|
||||||
|
) -> Result<ShaderProgram, GlesError> {
|
||||||
|
let mut program = include_str!("open_prelude.frag").to_string();
|
||||||
|
program.push_str(src);
|
||||||
|
program.push_str(include_str!("open_epilogue.frag"));
|
||||||
|
|
||||||
|
ShaderProgram::compile(
|
||||||
|
renderer,
|
||||||
|
&program,
|
||||||
|
&[
|
||||||
|
UniformName::new("niri_input_to_geo", UniformType::Matrix3x3),
|
||||||
|
UniformName::new("niri_geo_size", UniformType::_2f),
|
||||||
|
UniformName::new("niri_geo_to_tex", UniformType::Matrix3x3),
|
||||||
|
UniformName::new("niri_progress", UniformType::_1f),
|
||||||
|
UniformName::new("niri_clamped_progress", UniformType::_1f),
|
||||||
|
UniformName::new("niri_random_seed", UniformType::_1f),
|
||||||
|
],
|
||||||
|
&["niri_tex"],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_custom_open_program(renderer: &mut GlesRenderer, src: Option<&str>) {
|
||||||
|
let program = if let Some(src) = src {
|
||||||
|
match compile_open_program(renderer, src) {
|
||||||
|
Ok(program) => Some(program),
|
||||||
|
Err(err) => {
|
||||||
|
warn!("error compiling custom open shader: {err:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(prev) = Shaders::get(renderer).replace_custom_open_program(program) {
|
||||||
|
if let Err(err) = prev.destroy(renderer) {
|
||||||
|
warn!("error destroying previous custom open shader: {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mat3_uniform(name: &str, mat: Mat3) -> Uniform {
|
pub fn mat3_uniform(name: &str, mat: Mat3) -> Uniform {
|
||||||
Uniform::new(
|
Uniform::new(
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 coords_geo = niri_input_to_geo * vec3(niri_v_coords, 1.0);
|
||||||
|
vec3 size_geo = vec3(niri_geo_size, 1.0);
|
||||||
|
|
||||||
|
vec4 color = open_color(coords_geo, size_geo);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
#if defined(DEBUG_FLAGS)
|
||||||
|
uniform float niri_tint;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
varying vec2 niri_v_coords;
|
||||||
|
uniform vec2 niri_size;
|
||||||
|
|
||||||
|
uniform mat3 niri_input_to_geo;
|
||||||
|
uniform vec2 niri_geo_size;
|
||||||
|
|
||||||
|
uniform sampler2D niri_tex;
|
||||||
|
uniform mat3 niri_geo_to_tex;
|
||||||
|
|
||||||
|
uniform float niri_progress;
|
||||||
|
uniform float niri_clamped_progress;
|
||||||
|
uniform float niri_random_seed;
|
||||||
|
|
||||||
|
uniform float niri_alpha;
|
||||||
|
|
||||||
@@ -152,6 +152,48 @@ animations {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### `custom-shader`
|
||||||
|
|
||||||
|
<sup>Since: 0.1.6, experimental</sup>
|
||||||
|
|
||||||
|
You can write a custom shader for drawing the window during an open animation.
|
||||||
|
|
||||||
|
See [this example shader](./examples/open_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.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
> Custom shaders do not have a backwards compatibility guarantee.
|
||||||
|
> I may need to change their interface as I'm developing new features.
|
||||||
|
|
||||||
|
Example: open will fill the current geometry with a solid gradient that gradually fades in.
|
||||||
|
|
||||||
|
```
|
||||||
|
animations {
|
||||||
|
window-open {
|
||||||
|
duration-ms 250
|
||||||
|
curve "linear"
|
||||||
|
|
||||||
|
custom-shader r"
|
||||||
|
vec4 open_color(vec3 coords_geo, vec3 size_geo) {
|
||||||
|
vec4 color = vec4(0.0);
|
||||||
|
|
||||||
|
if (0.0 <= coords_geo.x && coords_geo.x <= 1.0
|
||||||
|
&& 0.0 <= coords_geo.y && coords_geo.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_geo.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return color * niri_clamped_progress;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### `window-close`
|
#### `window-close`
|
||||||
|
|
||||||
<sup>Since: 0.1.5</sup>
|
<sup>Since: 0.1.5</sup>
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
// Your shader must contain one function (see the bottom of this file).
|
||||||
|
//
|
||||||
|
// It should not contain any uniform definitions or anything else, as niri
|
||||||
|
// provides them for you.
|
||||||
|
//
|
||||||
|
// All symbols defined by niri will have a niri_ prefix, so don't use it for
|
||||||
|
// your own variables and functions.
|
||||||
|
|
||||||
|
// The function that you must define looks like this:
|
||||||
|
vec4 open_color(vec3 coords_geo, vec3 size_geo) {
|
||||||
|
vec4 color = /* ...compute the color... */;
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It takes as input:
|
||||||
|
//
|
||||||
|
// * coords_geo: coordinates of the current pixel relative to the window
|
||||||
|
// geometry.
|
||||||
|
//
|
||||||
|
// These are homogeneous (the Z component is equal to 1) and scaled in such a
|
||||||
|
// way that the 0 to 1 coordinates lie within the window geometry. Pixels
|
||||||
|
// outside the window geometry will have coordinates below 0 or above 1.
|
||||||
|
//
|
||||||
|
// The window geometry is its "visible bounds" from the user's perspective.
|
||||||
|
//
|
||||||
|
// The shader runs over an area of unspecified size and location, so you must
|
||||||
|
// expect and handle coordinates outside the [0, 1] range. The area will be
|
||||||
|
// larger than the final window size to accommodate more varied effects.
|
||||||
|
//
|
||||||
|
// * size_geo: size of the window geometry in logical pixels.
|
||||||
|
//
|
||||||
|
// It is homogeneous (the Z component is equal to 1).
|
||||||
|
//
|
||||||
|
// The function must return the color of the pixel (with premultiplied alpha).
|
||||||
|
// The pixel color will be further processed by niri (for example, to apply the
|
||||||
|
// final opacity from window rules).
|
||||||
|
|
||||||
|
// Now let's go over the uniforms that niri defines.
|
||||||
|
//
|
||||||
|
// You should only rely on the uniforms documented here. Any other uniforms can
|
||||||
|
// change or be removed without notice.
|
||||||
|
|
||||||
|
// The window texture.
|
||||||
|
uniform sampler2D niri_tex;
|
||||||
|
|
||||||
|
// Matrix that converts geometry coordinates into the 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 niri_geo_to_tex;
|
||||||
|
|
||||||
|
|
||||||
|
// Unclamped progress of the animation.
|
||||||
|
//
|
||||||
|
// Goes from 0 to 1 but may overshoot and oscillate.
|
||||||
|
uniform float niri_progress;
|
||||||
|
|
||||||
|
// Clamped progress of the animation.
|
||||||
|
//
|
||||||
|
// Goes from 0 to 1, but will stop at 1 as soon as it first reaches 1. Will not
|
||||||
|
// overshoot or oscillate.
|
||||||
|
uniform float niri_clamped_progress;
|
||||||
|
|
||||||
|
// Random float in [0; 1), consistent for the duration of the animation.
|
||||||
|
uniform float niri_random_seed;
|
||||||
|
|
||||||
|
// Now let's look at some examples. You can copy everything below this line
|
||||||
|
// into your custom-shader to experiment.
|
||||||
|
|
||||||
|
// Example: fill the current geometry with a solid vertical gradient and
|
||||||
|
// gradually make opaque.
|
||||||
|
vec4 solid_gradient(vec3 coords_geo, vec3 size_geo) {
|
||||||
|
vec4 color = vec4(0.0);
|
||||||
|
|
||||||
|
// Paint only the area inside the current geometry.
|
||||||
|
if (0.0 <= coords_geo.x && coords_geo.x <= 1.0
|
||||||
|
&& 0.0 <= coords_geo.y && coords_geo.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_geo.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make it opaque.
|
||||||
|
color *= niri_clamped_progress;
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: gradually scale up and make opaque, equivalent to the default
|
||||||
|
// opening animation.
|
||||||
|
vec4 default_open(vec3 coords_geo, vec3 size_geo) {
|
||||||
|
// Scale up the window.
|
||||||
|
float scale = max(0.0, (niri_progress / 2.0 + 0.5));
|
||||||
|
coords_geo = vec3((coords_geo.xy - vec2(0.5)) / scale + vec2(0.5), 1.0);
|
||||||
|
|
||||||
|
// Get color from the window texture.
|
||||||
|
vec3 coords_tex = niri_geo_to_tex * coords_geo;
|
||||||
|
vec4 color = texture2D(niri_tex, coords_tex.st);
|
||||||
|
|
||||||
|
// Make the window opaque.
|
||||||
|
color *= niri_clamped_progress;
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the function that you must define.
|
||||||
|
vec4 open_color(vec3 coords_geo, vec3 size_geo) {
|
||||||
|
// You can pick one of the example functions or write your own.
|
||||||
|
return solid_gradient(coords_geo, size_geo);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user