Commit 96db497859dc
tarball9 files changed+305-38
@@ -6,7 +6,7 @@ use monolith_config::{Animation, AnimationStyle};
66
use monolith_layout::{OutputId, Rect, WindowId};77
use monolith_motion::{CubicBezier, Edges, Position, Timeline, group_initial_slope};88
use monolith_planner::{Config as PlanConfig, Plan, Style};9
-use monolith_render_vk::VulkanTexture;9
+use monolith_render_vk::{Effect, Transition, VulkanTexture};1010
use smithay::utils::{Logical, Rectangle, Transform};1111
use std::collections::HashMap;1212
@@ -17,6 +17,7 @@ pub struct Animator {
1717
tracks: HashMap<OutputId, Track>,1818
glides: HashMap<WindowId, Glide>,1919
ghosts: Vec<Ghost>,20
+ spawns: HashMap<WindowId, u64>,2021
}2122
2223
/// A closed tiled window that stays in its output's plan, shrinking to@@ -30,6 +31,7 @@ pub struct Ghost {
3031
pub transform: Transform,3132
/// Window geometry inside the buffer.3233
pub geometry: Rectangle<i32, Logical>,34
+ pub born: u64,3335
}3436
3537
struct Track {@@ -56,6 +58,7 @@ impl Animator {
5658
tracks: HashMap::new(),5759
glides: HashMap::new(),5860
ghosts: Vec::new(),61
+ spawns: HashMap::new(),5962
}6063
}6164
@@ -201,13 +204,44 @@ impl Animator {
201204
&self.ghosts202205
}203206
207
+ pub fn spawned(&mut self, id: WindowId, now: u64) {208
+ if self.phase_ns() > 0 {209
+ self.spawns.insert(id, now);210
+ }211
+ }212
+213
+ /// The transition effect on a window, lasting one phase from its spawn.214
+ pub fn effect(&self, id: WindowId, now: u64) -> Option<Effect> {215
+ let start = *self.spawns.get(&id)?;216
+ self.progress(start, now).map(|progress| Effect {217
+ kind: Transition::Spawn,218
+ progress,219
+ })220
+ }221
+222
+ pub fn ghost_effect(&self, g: &Ghost, now: u64) -> Effect {223
+ Effect {224
+ kind: Transition::Close,225
+ progress: self.progress(g.born, now).unwrap_or(1.0),226
+ }227
+ }228
+229
+ fn progress(&self, start: u64, now: u64) -> Option<f32> {230
+ let p = now.saturating_sub(start) as f64 / self.phase_ns() as f64;231
+ (p < 1.0).then_some(p as f32)232
+ }233
+204234
pub fn active(&self, now: u64) -> bool {205235
self.tracks.values().any(|t| t.timeline.end_ns() > now)206236
|| self.glides.values().any(|g| g.timeline.end_ns() > now)237
+ || !self.spawns.is_empty()207238
}208239
209240
pub fn prune(&mut self, now: u64) {210241
self.glides.retain(|_, g| g.timeline.end_ns() > now);242
+ let phase = self.phase_ns();243
+ self.spawns244
+ .retain(|_, start| now.saturating_sub(*start) < phase);211245
let tracks = &self.tracks;212246
self.ghosts.retain(|g| {213247
tracks@@ -3,15 +3,17 @@
33
44
use crate::anim::Animator;55
use monolith_layout::{Layout, Rect, WindowId};6
-use monolith_render_vk::{VulkanRenderer, VulkanTexture};6
+use monolith_render_vk::{Effect, VulkanFrame, VulkanRenderer, VulkanTexture};77
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;88
use smithay::backend::renderer::element::texture::TextureRenderElement;99
use smithay::backend::renderer::element::utils::CropRenderElement;10
-use smithay::backend::renderer::element::{AsRenderElements, Id, Kind};10
+use smithay::backend::renderer::element::{AsRenderElements, Element, Id, Kind, RenderElement};11
+use smithay::backend::renderer::utils::CommitCounter;1112
use smithay::backend::renderer::utils::with_renderer_surface_state;12
-use smithay::backend::renderer::{ContextId, Renderer};13
+use smithay::backend::renderer::{ContextId, Renderer, Texture};1314
use smithay::desktop::Window;14
-use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size, Transform};15
+use smithay::utils::user_data::UserDataMap;16
+use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};1517
use std::collections::HashMap;1618
1719
smithay::backend::renderer::element::render_elements! {@@ -19,6 +21,7 @@ smithay::backend::renderer::element::render_elements! {
1921
Surface=WaylandSurfaceRenderElement<VulkanRenderer>,2022
Cropped=CropRenderElement<WaylandSurfaceRenderElement<VulkanRenderer>>,2123
Texture=TextureRenderElement<VulkanTexture>,24
+ Effect=EffectElement,2225
}2326
2427
/// Elements for every output, front to back.@@ -64,10 +67,9 @@ pub fn build(
6467
let mut out = Vec::new();6568
for (id, r) in tiled.into_iter().chain(above).rev() {6669
if let Some(window) = windows.get(&id) {67
- window_elements(&mut out, renderer, window, r);70
+ window_elements(&mut out, renderer, window, r, anim.effect(id, now));6871
}6972
}70
- let context = renderer.context_id();7173
for (g, r) in ghosts.into_iter().rev() {7274
let Some((at, size)) = pixels(r) else {7375
continue;@@ -76,15 +78,15 @@ pub fn build(
7678
g.geometry.loc.to_f64(),7779
(f64::from(size.0), f64::from(size.1)).into(),7880
);79
- out.push(texture(80
- &context,81
+ out.push(Draw::Effect(EffectElement::new(8182
&g.texture,8283
g.scale,8384
g.transform,8485
src,8586
at,8687
size,87
- ));88
+ anim.ghost_effect(g, now),89
+ )));8890
}8991
out9092
}@@ -92,14 +94,25 @@ pub fn build(
9294
/// Draws `window` filling `r`. A buffer bigger than `r` is cropped, and one9395
/// smaller has its last row and column stretched over the rest, so a resize9496
/// in flight never shows a scaled or missing client.95
-fn window_elements(out: &mut Vec<Draw>, renderer: &mut VulkanRenderer, window: &Window, r: Rect) {97
+fn window_elements(98
+ out: &mut Vec<Draw>,99
+ renderer: &mut VulkanRenderer,100
+ window: &Window,101
+ r: Rect,102
+ effect: Option<Effect>,103
+) {96104
let Some(((x, y), (w, h))) = pixels(r) else {97105
return;98106
};99107
let geo = window.geometry();100108
let loc = Point::<i32, Physical>::from((x - geo.loc.x, y - geo.loc.y));109
+ let alpha = effect.map_or(1.0, |e| e.progress);101110
let elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>> =102
- window.render_elements(renderer, loc, Scale::from(1.0), 1.0);111
+ window.render_elements(renderer, loc, Scale::from(1.0), alpha);112
+ if let Some(effect) = effect {113
+ effect_elements(out, renderer, window, elems, ((x, y), (w, h)), effect);114
+ return;115
+ }103116
if (w, h) == (geo.size.w, geo.size.h) {104117
out.extend(elems.into_iter().map(Draw::Surface));105118
return;@@ -182,3 +195,120 @@ fn texture(
182195
Kind::Unspecified,183196
))184197
}198
+199
+/// A window in a spawn transition. Its root surface goes through the effect200
+/// shader, clamped to fill its rect, and its subsurfaces and popups fade with it.201
+fn effect_elements(202
+ out: &mut Vec<Draw>,203
+ renderer: &mut VulkanRenderer,204
+ window: &Window,205
+ elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>>,206
+ (at, size): ((i32, i32), (i32, i32)),207
+ effect: Effect,208
+) {209
+ let Some(root) = window.toplevel().map(|t| t.wl_surface().clone()) else {210
+ return;211
+ };212
+ let root_id = Id::from_wayland_resource(&root);213
+ let crop = Rectangle::new(at.into(), size.into());214
+ let context = renderer.context_id();215
+ let root_tex = with_renderer_surface_state(&root, |s| {216
+ s.texture::<VulkanTexture>(context.clone())217
+ .cloned()218
+ .map(|t| (t, s.buffer_scale(), s.buffer_transform()))219
+ })220
+ .flatten();221
+ out.extend(222
+ elems223
+ .into_iter()224
+ .filter(|e| root_tex.is_none() || *e.id() != root_id)225
+ .filter_map(|e| CropRenderElement::from_element(e, 1.0, crop))226
+ .map(Draw::Cropped),227
+ );228
+ if let Some((tex, scale, transform)) = root_tex {229
+ let geo = window.geometry();230
+ let src = Rectangle::new(231
+ geo.loc.to_f64(),232
+ (f64::from(size.0), f64::from(size.1)).into(),233
+ );234
+ out.push(Draw::Effect(EffectElement::new(235
+ &tex, scale, transform, src, at, size, effect,236
+ )));237
+ }238
+}239
+240
+/// A texture drawn through the transition hook in the effect shader.241
+pub struct EffectElement {242
+ id: Id,243
+ texture: VulkanTexture,244
+ src: Rectangle<f64, Buffer>,245
+ geometry: Rectangle<i32, Physical>,246
+ transform: Transform,247
+ effect: Effect,248
+}249
+250
+impl EffectElement {251
+ fn new(252
+ texture: &VulkanTexture,253
+ scale: i32,254
+ transform: Transform,255
+ src: Rectangle<f64, Logical>,256
+ at: (i32, i32),257
+ size: (i32, i32),258
+ effect: Effect,259
+ ) -> Self {260
+ let bounds = texture.size().to_logical(scale, transform).to_f64();261
+ Self {262
+ id: Id::new(),263
+ texture: texture.clone(),264
+ src: src.to_buffer(f64::from(scale), transform, &bounds),265
+ geometry: Rectangle::new(at.into(), size.into()),266
+ transform,267
+ effect,268
+ }269
+ }270
+}271
+272
+impl Element for EffectElement {273
+ fn id(&self) -> &Id {274
+ &self.id275
+ }276
+277
+ fn current_commit(&self) -> CommitCounter {278
+ CommitCounter::default()279
+ }280
+281
+ fn src(&self) -> Rectangle<f64, Buffer> {282
+ self.src283
+ }284
+285
+ fn geometry(&self, _scale: Scale<f64>) -> Rectangle<i32, Physical> {286
+ self.geometry287
+ }288
+289
+ fn transform(&self) -> Transform {290
+ self.transform291
+ }292
+}293
+294
+impl RenderElement<VulkanRenderer> for EffectElement {295
+ fn draw(296
+ &self,297
+ frame: &mut VulkanFrame<'_, '_>,298
+ src: Rectangle<f64, Buffer>,299
+ dst: Rectangle<i32, Physical>,300
+ damage: &[Rectangle<i32, Physical>],301
+ _opaque_regions: &[Rectangle<i32, Physical>],302
+ _cache: Option<&UserDataMap>,303
+ ) -> Result<(), monolith_render_vk::Error> {304
+ frame.render_effect(305
+ &self.texture,306
+ src,307
+ dst,308
+ damage,309
+ self.transform,310
+ 1.0,311
+ self.effect,312
+ )313
+ }314
+}@@ -210,6 +210,7 @@ impl State {
210210
self.layout.scratchpad_add(id, name);211211
}212212
self.windows.insert(id, window);213
+ self.anim.spawned(id, now_ns());213214
if self.layout.visible(id) {214215
self.layout.focus(id);215216
}@@ -271,6 +272,7 @@ impl State {
271272
scale,272273
transform,273274
geometry: window.geometry(),275
+ born: now_ns(),274276
});275277
}276278
}@@ -4,7 +4,7 @@ use std::process::Command;
44
fn main() {55
let out = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR"));66
println!("cargo:rerun-if-changed=shaders");7
- for name in ["quad.vert", "solid.frag", "texture.frag"] {7
+ for name in ["quad.vert", "solid.frag", "texture.frag", "effect.frag"] {88
let status = Command::new("glslangValidator")99
.args(["-V", "--target-env", "vulkan1.3", "-o"])1010
.arg(out.join(format!("{name}.spv")))@@ -0,0 +1,27 @@
1
+#version 4502
+#extension GL_GOOGLE_include_directive : require3
+#include "push.glsl"4
+5
+layout(set = 0, binding = 0) uniform sampler2D tex;6
+layout(location = 0) in vec2 v_uv;7
+layout(location = 0) out vec4 out_color;8
+9
+const int MOVE = 0;10
+const int SPAWN = 1;11
+const int CLOSE = 2;12
+13
+// The transition hook. Custom shaders will replace this function.14
+vec4 effect(vec4 color, vec2 uv, float progress, int kind) {15
+ if (kind == SPAWN) {16
+ return color * progress;17
+ }18
+ if (kind == CLOSE) {19
+ return color * (1.0 - progress);20
+ }21
+ return color;22
+}23
+24
+void main() {25
+ vec4 color = texture(tex, v_uv) * pc.color.a;26
+ out_color = effect(color, v_uv, pc.effect.x, int(pc.effect.y));27
+}@@ -5,4 +5,5 @@ layout(push_constant) uniform Push {
55
vec4 color;66
vec4 dst;77
vec2 uv[4];8
+ vec4 effect;89
} pc;@@ -90,6 +90,70 @@ impl<'frame, 'buffer> VulkanFrame<'frame, 'buffer> {
9090
.collect()9191
}9292
93
+ /// Draws a texture through the transition hook in `shaders/effect.frag`.94
+ #[expect(clippy::too_many_arguments)]95
+ pub fn render_effect(96
+ &mut self,97
+ texture: &VulkanTexture,98
+ src: Rectangle<f64, Buffer>,99
+ dst: Rectangle<i32, Physical>,100
+ damage: &[Rectangle<i32, Physical>],101
+ src_transform: Transform,102
+ alpha: f32,103
+ effect: Effect,104
+ ) -> Result<(), Error> {105
+ let params = [effect.progress, effect.kind as u32 as f32, 0.0, 0.0];106
+ self.draw_texture(107
+ texture,108
+ src,109
+ dst,110
+ damage,111
+ src_transform,112
+ alpha,113
+ Kind::Effect,114
+ params,115
+ )116
+ }117
+118
+ #[expect(clippy::too_many_arguments)]119
+ fn draw_texture(120
+ &mut self,121
+ texture: &VulkanTexture,122
+ src: Rectangle<f64, Buffer>,123
+ dst: Rectangle<i32, Physical>,124
+ damage: &[Rectangle<i32, Physical>],125
+ src_transform: Transform,126
+ alpha: f32,127
+ kind: Kind,128
+ effect: [f32; 4],129
+ ) -> Result<(), Error> {130
+ if !Arc::ptr_eq(&texture.0.shared, self.renderer.shared()) {131
+ return Err(Error::ForeignTexture);132
+ }133
+ if dst.size.w <= 0 || dst.size.h <= 0 || Arc::ptr_eq(&texture.0, &self.target.0) {134
+ return Ok(());135
+ }136
+ let scissors = self.scissors(dst, damage);137
+ if scissors.is_empty() {138
+ return Ok(());139
+ }140
+ let uvs = texture_uvs(src, dst.size, texture.size(), src_transform);141
+ let (fb, uv) = to_framebuffer(dst, uvs, self.output, self.transform);142
+ let push = Push {143
+ dst: self.ndc(fb),144
+ uv,145
+ color: [1.0, 1.0, 1.0, alpha],146
+ effect,147
+ };148
+ self.ops.push(Op::Draw {149
+ kind,150
+ texture: Some(texture.clone()),151
+ push,152
+ scissors,153
+ });154
+ Ok(())155
+ }156
+93157
fn ndc(&self, r: Rectangle<i32, Physical>) -> [f32; 4] {94158
let fb = self.fb_size();95159
let (w, h) = (fb.w as f32, fb.h as f32);@@ -136,6 +200,7 @@ impl Frame for VulkanFrame<'_, '_> {
136200
dst: self.ndc(fb),137201
uv: [[0.0; 2]; 4],138202
color: color.components(),203
+ effect: [0.0; 4],139204
};140205
self.ops.push(Op::Draw {141206
kind: Kind::Solid,@@ -156,30 +221,16 @@ impl Frame for VulkanFrame<'_, '_> {
156221
src_transform: Transform,157222
alpha: f32,158223
) -> Result<(), Error> {159
- if !Arc::ptr_eq(&texture.0.shared, self.renderer.shared()) {160
- return Err(Error::ForeignTexture);161
- }162
- if dst.size.w <= 0 || dst.size.h <= 0 || Arc::ptr_eq(&texture.0, &self.target.0) {163
- return Ok(());164
- }165
- let scissors = self.scissors(dst, damage);166
- if scissors.is_empty() {167
- return Ok(());168
- }169
- let uvs = texture_uvs(src, dst.size, texture.size(), src_transform);170
- let (fb, uv) = to_framebuffer(dst, uvs, self.output, self.transform);171
- let push = Push {172
- dst: self.ndc(fb),173
- uv,174
- color: [1.0, 1.0, 1.0, alpha],175
- };176
- self.ops.push(Op::Draw {177
- kind: Kind::Texture,178
- texture: Some(texture.clone()),179
- push,180
- scissors,181
- });182
- Ok(())224
+ self.draw_texture(225
+ texture,226
+ src,227
+ dst,228
+ damage,229
+ src_transform,230
+ alpha,231
+ Kind::Texture,232
+ [0.0; 4],233
+ )183234
}184235
185236
fn transformation(&self) -> Transform {@@ -199,3 +250,17 @@ impl Frame for VulkanFrame<'_, '_> {
199250
self.renderer.submit(self.target, ops)200251
}201252
}253
+254
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]255
+pub enum Transition {256
+ Move = 0,257
+ Spawn = 1,258
+ Close = 2,259
+}260
+261
+/// A window transition for the effect shader, with `progress` in [0, 1].262
+#[derive(Clone, Copy, Debug, PartialEq)]263
+pub struct Effect {264
+ pub kind: Transition,265
+ pub progress: f32,266
+}@@ -16,6 +16,6 @@ mod transform;
1616
1717
pub use dmabuf::VulkanAllocator;1818
pub use error::Error;19
-pub use frame::VulkanFrame;19
+pub use frame::{Effect, Transition, VulkanFrame};2020
pub use image::{VulkanImage, VulkanMapping, VulkanTarget, VulkanTexture};2121
pub use renderer::VulkanRenderer;@@ -13,6 +13,8 @@ pub(crate) struct Push {
1313
pub color: [f32; 4],1414
pub dst: [f32; 4],1515
pub uv: [[f32; 2]; 4],16
+ /// Transition progress and kind, read only by `shaders/effect.frag`.17
+ pub effect: [f32; 4],1618
}1719
1820
impl Push {@@ -25,6 +27,7 @@ impl Push {
2527
pub(crate) enum Kind {2628
Solid,2729
Texture,30
+ Effect,2831
}2932
3033
pub(crate) struct Pipelines {@@ -34,12 +37,14 @@ pub(crate) struct Pipelines {
3437
vert: vk::ShaderModule,3538
solid: vk::ShaderModule,3639
texture: vk::ShaderModule,40
+ effect: vk::ShaderModule,3741
cache: HashMap<(vk::Format, Kind), vk::Pipeline>,3842
}3943
4044
const QUAD_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/quad.vert.spv"));4145
const SOLID_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/solid.frag.spv"));4246
const TEXTURE_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/texture.frag.spv"));47
+const EFFECT_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/effect.frag.spv"));4348
4449
impl Pipelines {4550
pub fn new(shared: &Arc<Shared>) -> Result<Self, Error> {@@ -83,6 +88,7 @@ impl Pipelines {
8388
vert: module(QUAD_VERT)?,8489
solid: module(SOLID_FRAG)?,8590
texture: module(TEXTURE_FRAG)?,91
+ effect: module(EFFECT_FRAG)?,8692
cache: HashMap::new(),8793
})8894
}@@ -95,6 +101,7 @@ impl Pipelines {
95101
let frag = match kind {96102
Kind::Solid => self.solid,97103
Kind::Texture => self.texture,104
+ Kind::Effect => self.effect,98105
};99106
let stages = [100107
vk::PipelineShaderStageCreateInfo::default()@@ -162,6 +169,7 @@ impl Drop for Pipelines {
162169
d.destroy_shader_module(self.vert, None);163170
d.destroy_shader_module(self.solid, None);164171
d.destroy_shader_module(self.texture, None);172
+ d.destroy_shader_module(self.effect, None);165173
d.destroy_pipeline_layout(self.layout, None);166174
d.destroy_descriptor_set_layout(self.set_layout, None);167175
}