atagen

monolith

public
Star0
Watch0
8ffa3fa4d4a9331f2638c1598a9f3c107e148296
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/monolith/src/scene.rsRust631 lines20.1 KB
1
//! What a frame draws. The space holds final geometry for input, while the
2
//! scene draws windows where the animator says they are right now.
3
4
use crate::wm::{LastFrame, Wm};
5
use monolith_config::Decoration;
6
use monolith_layout::{OutputId, Rect};
7
use monolith_render_vk::{Clip, Effect, VulkanFrame, VulkanRenderer, VulkanTexture};
8
use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;
9
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
10
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
11
use smithay::backend::renderer::element::surface::render_elements_from_surface_tree;
12
use smithay::backend::renderer::element::texture::TextureRenderElement;
13
use smithay::backend::renderer::element::utils::{CropRenderElement, RescaleRenderElement};
14
use smithay::backend::renderer::element::{AsRenderElements, Element, Id, Kind, RenderElement};
15
use smithay::backend::renderer::utils::with_renderer_surface_state;
16
use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions};
17
use smithay::backend::renderer::{ContextId, Renderer, Texture};
18
use smithay::desktop::{PopupManager, Window};
19
use smithay::output::Output;
20
use smithay::utils::user_data::UserDataMap;
21
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
22
use smithay::wayland::compositor::{TraversalAction, with_surface_tree_downward};
23
use smithay::wayland::shell::wlr_layer::Layer;
24
use smithay::xwayland::X11Surface;
25
26
smithay::backend::renderer::element::render_elements! {
27
    pub Draw<=VulkanRenderer>;
28
    Surface=WaylandSurfaceRenderElement<VulkanRenderer>,
29
    Cropped=CropRenderElement<WaylandSurfaceRenderElement<VulkanRenderer>>,
30
    Texture=TextureRenderElement<VulkanTexture>,
31
    Effect=EffectElement,
32
    Border=SolidColorRenderElement,
33
    Memory=MemoryRenderBufferRenderElement<VulkanRenderer>,
34
    Rescaled=RescaleRenderElement<WaylandSurfaceRenderElement<VulkanRenderer>>,
35
    Clipped=ClipElement,
36
}
37
38
/// Elements for output `o` in its own coordinates, front to back.
39
#[profiling::function]
40
pub fn build(
41
    wm: &mut Wm,
42
    o: OutputId,
43
    renderer: &mut VulkanRenderer,
44
    now: u64,
45
    deco: &Decoration,
46
    output: Option<&Output>,
47
    unmanaged: &[X11Surface],
48
) -> Vec<Draw> {
49
    let crate::wm::Parts {
50
        layout,
51
        anim,
52
        windows,
53
        last_frames,
54
    } = wm.parts();
55
    let Some(area) = layout.output(o).map(|out| out.area) else {
56
        return Vec::new();
57
    };
58
    let local = |r: Rect| Rect::new(r.x - area.x, r.y - area.y, r.w, r.h);
59
    let mut ghosts = Vec::new();
60
    let mut tiled = Vec::new();
61
    let mut above = Vec::new();
62
    let mut fading = Vec::new();
63
    for g in anim.ghosts().iter().filter(|g| g.output == o) {
64
        let Some(frame) = last_frames.get(&g.id) else {
65
            continue;
66
        };
67
        match g.fixed {
68
            Some(r) => fading.push((g, frame, local(r))),
69
            None => {
70
                if let Some(r) = anim.tiled(o, g.id, now) {
71
                    ghosts.push((g, frame, local(r)));
72
                }
73
            }
74
        }
75
    }
76
    let arr = layout.arrange(o);
77
    let fullscreen_at_rest = arr
78
        .fullscreen
79
        .is_some_and(|w| anim.gliding(w, now).is_none());
80
    for p in &arr.tiled {
81
        if Some(p.window) == arr.fullscreen || fullscreen_at_rest {
82
            continue;
83
        }
84
        match anim.gliding(p.window, now) {
85
            Some(r) => above.push((p.window, local(r), true, true)),
86
            None => {
87
                let r = anim.tiled(o, p.window, now).unwrap_or(p.rect);
88
                tiled.push((p.window, local(r), true, false));
89
            }
90
        }
91
    }
92
    for &(w, r) in &arr.floating {
93
        let glide = anim.gliding(w, now);
94
        above.push((w, local(glide.unwrap_or(r)), true, glide.is_some()));
95
    }
96
    if let Some(w) = arr.fullscreen {
97
        let glide = anim.gliding(w, now);
98
        above.push((w, local(glide.unwrap_or(area)), false, glide.is_some()));
99
    }
100
    let focused = layout.focused();
101
    let mut out = Vec::new();
102
    for x in unmanaged.iter().rev() {
103
        let Some(surface) = x.wl_surface() else {
104
            continue;
105
        };
106
        let loc = x.geometry().loc;
107
        let at = Point::<i32, Physical>::from((loc.x - area.x as i32, loc.y - area.y as i32));
108
        out.extend(
109
            render_elements_from_surface_tree(renderer, &surface, at, 1.0, 1.0, Kind::Unspecified)
110
                .into_iter()
111
                .map(Draw::Surface),
112
        );
113
    }
114
    if let Some(output) = output {
115
        crate::layers::elements(&mut out, renderer, output, Layer::Overlay);
116
        if !fullscreen_at_rest {
117
            crate::layers::elements(&mut out, renderer, output, Layer::Top);
118
        }
119
    }
120
    for (g, frame, r) in fading.into_iter().rev() {
121
        out.extend(ghost_element(frame, r, anim.ghost_effect(g, now), deco));
122
    }
123
    for (id, r, framed, gliding) in tiled.into_iter().chain(above).rev() {
124
        let Some(state) = windows.get_mut(id) else {
125
            continue;
126
        };
127
        let effect = anim.effect(id, now);
128
        let first = out.len();
129
        window_elements(&mut out, renderer, &state.window, r, effect, gliding);
130
        let clip = framed.then(|| corner_clip(r, deco)).flatten();
131
        if let Some(clip) = clip {
132
            clip_window(&mut out, first, &state.window, clip);
133
        }
134
        if framed && deco.border > 0 {
135
            let color = if focused == Some(id) {
136
                deco.focused.0
137
            } else {
138
                deco.unfocused.0
139
            };
140
            let alpha = effect.map_or(1.0, |e| e.progress);
141
            let borders = &mut state.borders;
142
            let first = out.len();
143
            border_elements(&mut out, borders, r, deco, color, alpha);
144
            if let Some(clip) = clip {
145
                ring_borders(&mut out, first, clip, deco.border as i32);
146
            }
147
        }
148
    }
149
    for (g, frame, r) in ghosts.into_iter().rev() {
150
        out.extend(ghost_element(frame, r, anim.ghost_effect(g, now), deco));
151
    }
152
    if let Some(output) = output {
153
        crate::layers::elements(&mut out, renderer, output, Layer::Bottom);
154
        crate::layers::elements(&mut out, renderer, output, Layer::Background);
155
    }
156
    out
157
}
158
159
/// Draws `window` filling `r`. In a planned move a buffer bigger than `r` is
160
/// cropped and one smaller has its last row and column stretched over the
161
/// rest, keeping content aligned with its neighbours. A gliding window is
162
/// scaled instead, so the client resizing mid-glide does not jump.
163
fn window_elements(
164
    out: &mut Vec<Draw>,
165
    renderer: &mut VulkanRenderer,
166
    window: &Window,
167
    r: Rect,
168
    effect: Option<Effect>,
169
    scaled: bool,
170
) {
171
    let Some(((x, y), (w, h))) = pixels(r) else {
172
        return;
173
    };
174
    let geo = window.geometry();
175
    let loc = Point::<i32, Physical>::from((x - geo.loc.x, y - geo.loc.y));
176
    let alpha = effect.map_or(1.0, |e| e.progress);
177
    let elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>> =
178
        window.render_elements(renderer, loc, Scale::from(1.0), alpha);
179
    if let Some(effect) = effect {
180
        effect_elements(out, renderer, window, elems, ((x, y), (w, h)), effect);
181
        return;
182
    }
183
    if scaled && geo.size.w > 0 && geo.size.h > 0 {
184
        let scale = Scale::from((
185
            f64::from(w) / f64::from(geo.size.w),
186
            f64::from(h) / f64::from(geo.size.h),
187
        ));
188
        let origin = Point::<i32, Physical>::from((x, y));
189
        out.extend(
190
            elems
191
                .into_iter()
192
                .map(|e| Draw::Rescaled(RescaleRenderElement::from_element(e, origin, scale))),
193
        );
194
        return;
195
    }
196
    if (w, h) == (geo.size.w, geo.size.h) {
197
        out.extend(elems.into_iter().map(Draw::Surface));
198
        return;
199
    }
200
    let crop = Rectangle::new((x, y).into(), (w, h).into());
201
    out.extend(
202
        elems
203
            .into_iter()
204
            .filter_map(|e| CropRenderElement::from_element(e, 1.0, crop))
205
            .map(Draw::Cropped),
206
    );
207
    let Some(root) = crate::wm::root(window) else {
208
        return;
209
    };
210
    let context = renderer.context_id();
211
    let Some((tex, scale, transform)) = with_renderer_surface_state(&root, |s| {
212
        s.texture::<VulkanTexture>(context.clone())
213
            .cloned()
214
            .map(|t| (t, s.buffer_scale(), s.buffer_transform()))
215
    })
216
    .flatten() else {
217
        return;
218
    };
219
    let (gw, gh) = (geo.size.w, geo.size.h);
220
    let (cw, ch) = (w.min(gw), h.min(gh));
221
    let (gx, gy) = (geo.loc.x as f64, geo.loc.y as f64);
222
    let mut strip = |src: Rectangle<f64, Logical>, at: (i32, i32), size: (i32, i32)| {
223
        if size.0 >= 1 && size.1 >= 1 {
224
            out.push(texture(&context, &tex, scale, transform, src, at, size));
225
        }
226
    };
227
    let right = gx + f64::from(gw) - 1.0;
228
    let bottom = gy + f64::from(gh) - 1.0;
229
    strip(
230
        Rectangle::new((right, gy).into(), (1.0, f64::from(ch)).into()),
231
        (x + gw, y),
232
        (w - gw, ch),
233
    );
234
    strip(
235
        Rectangle::new((gx, bottom).into(), (f64::from(cw), 1.0).into()),
236
        (x, y + gh),
237
        (cw, h - gh),
238
    );
239
    strip(
240
        Rectangle::new((right, bottom).into(), (1.0, 1.0).into()),
241
        (x + gw, y + gh),
242
        (w - gw, h - gh),
243
    );
244
}
245
246
/// Four strips just outside `r`, in the gap, so the client keeps its size.
247
/// With rounded corners they reach in by the largest radius, to cover the
248
/// band the ring curves through inside the window's corners.
249
fn border_elements(
250
    out: &mut Vec<Draw>,
251
    borders: &mut Option<[SolidColorBuffer; 4]>,
252
    r: Rect,
253
    deco: &Decoration,
254
    color: [f32; 4],
255
    alpha: f32,
256
) {
257
    let Some(((x, y), (w, h))) = pixels(r) else {
258
        return;
259
    };
260
    let b = deco.border as i32;
261
    let reach = deco.radius.0.into_iter().max().unwrap_or(0) as i32;
262
    let reach = reach.min(w / 2).min(h / 2);
263
    let strips = [
264
        ((x - b, y - b), (w + 2 * b, b + reach)),
265
        ((x - b, y + h - reach), (w + 2 * b, b + reach)),
266
        ((x - b, y + reach), (b + reach, h - 2 * reach)),
267
        ((x + w - reach, y + reach), (b + reach, h - 2 * reach)),
268
    ];
269
    let buffers = borders
270
        .get_or_insert_with(|| std::array::from_fn(|_| SolidColorBuffer::new((0, 0), color)));
271
    for (buffer, (at, size)) in buffers.iter_mut().zip(strips) {
272
        buffer.update(size, color);
273
        out.push(Draw::Border(SolidColorRenderElement::from_buffer(
274
            buffer,
275
            at,
276
            1.0,
277
            alpha,
278
            Kind::Unspecified,
279
        )));
280
    }
281
}
282
283
/// Snaps a logical rect to whole pixels by rounding its edges, so neighbours
284
/// that share an edge stay flush.
285
fn pixels(r: Rect) -> Option<((i32, i32), (i32, i32))> {
286
    let (x, y) = (r.x.round() as i32, r.y.round() as i32);
287
    let w = r.right().round() as i32 - x;
288
    let h = r.bottom().round() as i32 - y;
289
    (w >= 1 && h >= 1).then_some(((x, y), (w, h)))
290
}
291
292
fn texture(
293
    context: &ContextId<VulkanTexture>,
294
    texture: &VulkanTexture,
295
    scale: i32,
296
    transform: Transform,
297
    src: Rectangle<f64, Logical>,
298
    at: (i32, i32),
299
    size: (i32, i32),
300
) -> Draw {
301
    Draw::Texture(TextureRenderElement::from_static_texture(
302
        Id::new(),
303
        context.clone(),
304
        Point::<f64, Physical>::from((f64::from(at.0), f64::from(at.1))),
305
        texture.clone(),
306
        scale,
307
        transform,
308
        None,
309
        Some(src),
310
        Some(Size::from(size)),
311
        None,
312
        Kind::Unspecified,
313
    ))
314
}
315
316
/// A window in a spawn transition. Its root surface goes through the effect
317
/// shader, clamped to fill its rect, and its subsurfaces and popups fade with it.
318
fn effect_elements(
319
    out: &mut Vec<Draw>,
320
    renderer: &mut VulkanRenderer,
321
    window: &Window,
322
    elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>>,
323
    (at, size): ((i32, i32), (i32, i32)),
324
    effect: Effect,
325
) {
326
    let Some(root) = crate::wm::root(window) else {
327
        return;
328
    };
329
    let root_id = Id::from_wayland_resource(&root);
330
    let crop = Rectangle::new(at.into(), size.into());
331
    let context = renderer.context_id();
332
    let root_tex = with_renderer_surface_state(&root, |s| {
333
        s.texture::<VulkanTexture>(context.clone())
334
            .cloned()
335
            .map(|t| (t, s.buffer_scale(), s.buffer_transform()))
336
    })
337
    .flatten();
338
    out.extend(
339
        elems
340
            .into_iter()
341
            .filter(|e| root_tex.is_none() || *e.id() != root_id)
342
            .filter_map(|e| CropRenderElement::from_element(e, 1.0, crop))
343
            .map(Draw::Cropped),
344
    );
345
    if let Some((tex, scale, transform)) = root_tex {
346
        let geo = window.geometry();
347
        let src = Rectangle::new(
348
            geo.loc.to_f64(),
349
            (f64::from(size.0), f64::from(size.1)).into(),
350
        );
351
        out.push(Draw::Effect(EffectElement::new(
352
            &tex, scale, transform, src, at, size, effect,
353
        )));
354
    }
355
}
356
357
/// A texture drawn through the transition hook in the effect shader.
358
pub struct EffectElement {
359
    id: Id,
360
    texture: VulkanTexture,
361
    src: Rectangle<f64, Buffer>,
362
    geometry: Rectangle<i32, Physical>,
363
    transform: Transform,
364
    effect: Effect,
365
}
366
367
impl EffectElement {
368
    fn new(
369
        texture: &VulkanTexture,
370
        scale: i32,
371
        transform: Transform,
372
        src: Rectangle<f64, Logical>,
373
        at: (i32, i32),
374
        size: (i32, i32),
375
        effect: Effect,
376
    ) -> Self {
377
        let bounds = texture.size().to_logical(scale, transform).to_f64();
378
        Self {
379
            id: Id::new(),
380
            texture: texture.clone(),
381
            src: src.to_buffer(f64::from(scale), transform, &bounds),
382
            geometry: Rectangle::new(at.into(), size.into()),
383
            transform,
384
            effect,
385
        }
386
    }
387
}
388
389
impl Element for EffectElement {
390
    fn id(&self) -> &Id {
391
        &self.id
392
    }
393
394
    fn current_commit(&self) -> CommitCounter {
395
        CommitCounter::default()
396
    }
397
398
    fn src(&self) -> Rectangle<f64, Buffer> {
399
        self.src
400
    }
401
402
    fn geometry(&self, _scale: Scale<f64>) -> Rectangle<i32, Physical> {
403
        self.geometry
404
    }
405
406
    fn transform(&self) -> Transform {
407
        self.transform
408
    }
409
}
410
411
impl RenderElement<VulkanRenderer> for EffectElement {
412
    fn draw(
413
        &self,
414
        frame: &mut VulkanFrame<'_, '_>,
415
        src: Rectangle<f64, Buffer>,
416
        dst: Rectangle<i32, Physical>,
417
        damage: &[Rectangle<i32, Physical>],
418
        _opaque_regions: &[Rectangle<i32, Physical>],
419
        _cache: Option<&UserDataMap>,
420
    ) -> Result<(), monolith_render_vk::Error> {
421
        frame.render_effect(
422
            &self.texture,
423
            src,
424
            dst,
425
            damage,
426
            self.transform,
427
            1.0,
428
            self.effect,
429
        )
430
    }
431
}
432
433
fn ghost_element(frame: &LastFrame, r: Rect, effect: Effect, deco: &Decoration) -> Option<Draw> {
434
    let (at, size) = pixels(r)?;
435
    let src = Rectangle::new(
436
        frame.geometry.loc.to_f64(),
437
        (f64::from(size.0), f64::from(size.1)).into(),
438
    );
439
    let ghost = Draw::Effect(EffectElement::new(
440
        &frame.texture,
441
        frame.scale,
442
        frame.transform,
443
        src,
444
        at,
445
        size,
446
        effect,
447
    ));
448
    Some(match corner_clip(r, deco) {
449
        Some(clip) => Draw::Clipped(ClipElement::new(ghost, clip)),
450
        None => ghost,
451
    })
452
}
453
454
/// The rounded outline of a framed window at `r`, if it has one.
455
fn corner_clip(r: Rect, deco: &Decoration) -> Option<Clip> {
456
    let radii = deco.radius.0;
457
    if radii == [0; 4] {
458
        return None;
459
    }
460
    let (at, size) = pixels(r)?;
461
    Some(Clip {
462
        rect: Rectangle::new(at.into(), size.into()),
463
        radii: radii.map(|r| r as f32),
464
        ring: 0.0,
465
    })
466
}
467
468
/// Clips what a window drew from `first` on, leaving its popups whole.
469
fn clip_window(out: &mut [Draw], first: usize, window: &Window, clip: Clip) {
470
    let mut popups = std::collections::HashSet::new();
471
    if let Some(root) = crate::wm::root(window) {
472
        for (popup, _) in PopupManager::popups_for_surface(&root) {
473
            with_surface_tree_downward(
474
                popup.wl_surface(),
475
                (),
476
                |_, _, _| TraversalAction::DoChildren(()),
477
                |s, _, _| {
478
                    popups.insert(Id::from_wayland_resource(s));
479
                },
480
                |_, _, _| true,
481
            );
482
        }
483
    }
484
    for draw in &mut out[first..] {
485
        if !popups.contains(draw.id()) {
486
            wrap(draw, clip);
487
        }
488
    }
489
}
490
491
/// Turns the border strips from `first` on into a ring around the window's
492
/// rounded outline.
493
fn ring_borders(out: &mut [Draw], first: usize, window: Clip, b: i32) {
494
    let grow = |r: f32| if r > 0.0 { r + b as f32 } else { 0.0 };
495
    let ring = Clip {
496
        rect: Rectangle::new(
497
            window.rect.loc - Point::from((b, b)),
498
            window.rect.size + Size::from((2 * b, 2 * b)),
499
        ),
500
        radii: window.radii.map(grow),
501
        ring: b as f32,
502
    };
503
    for draw in &mut out[first..] {
504
        wrap(draw, ring);
505
    }
506
}
507
508
fn wrap(draw: &mut Draw, clip: Clip) {
509
    let inner = std::mem::replace(draw, Draw::Clipped(ClipElement::empty()));
510
    *draw = Draw::Clipped(ClipElement::new(inner, clip));
511
}
512
513
/// Another element drawn through a rounded clip.
514
pub struct ClipElement {
515
    inner: Option<Box<Draw>>,
516
    clip: Clip,
517
    id: Id,
518
}
519
520
impl ClipElement {
521
    fn new(inner: Draw, clip: Clip) -> Self {
522
        Self {
523
            id: inner.id().clone(),
524
            inner: Some(Box::new(inner)),
525
            clip,
526
        }
527
    }
528
529
    /// A placeholder while an element is moved into its wrapper.
530
    fn empty() -> Self {
531
        Self {
532
            inner: None,
533
            clip: Clip {
534
                rect: Rectangle::default(),
535
                radii: [0.0; 4],
536
                ring: 0.0,
537
            },
538
            id: Id::new(),
539
        }
540
    }
541
542
    fn inner(&self) -> &Draw {
543
        self.inner.as_deref().expect("only placeholders are empty")
544
    }
545
}
546
547
impl Element for ClipElement {
548
    fn id(&self) -> &Id {
549
        &self.id
550
    }
551
552
    fn current_commit(&self) -> CommitCounter {
553
        self.inner().current_commit()
554
    }
555
556
    fn location(&self, scale: Scale<f64>) -> Point<i32, Physical> {
557
        self.inner().location(scale)
558
    }
559
560
    fn src(&self) -> Rectangle<f64, Buffer> {
561
        self.inner().src()
562
    }
563
564
    fn transform(&self) -> Transform {
565
        self.inner().transform()
566
    }
567
568
    fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
569
        self.inner().geometry(scale)
570
    }
571
572
    fn damage_since(
573
        &self,
574
        scale: Scale<f64>,
575
        commit: Option<CommitCounter>,
576
    ) -> DamageSet<i32, Physical> {
577
        self.inner().damage_since(scale, commit)
578
    }
579
580
    /// A ring hides nothing behind it, and a rounded window hides nothing
581
    /// behind its corners.
582
    fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
583
        if self.clip.ring > 0.0 {
584
            return OpaqueRegions::default();
585
        }
586
        let loc = self.geometry(scale).loc;
587
        let c = self.clip.rect;
588
        let (right, bottom) = (c.loc.x + c.size.w, c.loc.y + c.size.h);
589
        let [tl, tr, br, bl] = self.clip.radii.map(|r| r.ceil() as i32);
590
        let corners = [
591
            Rectangle::new(c.loc, (tl, tl).into()),
592
            Rectangle::new((right - tr, c.loc.y).into(), (tr, tr).into()),
593
            Rectangle::new((right - br, bottom - br).into(), (br, br).into()),
594
            Rectangle::new((c.loc.x, bottom - bl).into(), (bl, bl).into()),
595
        ]
596
        .map(|mut r| {
597
            r.loc -= loc;
598
            r
599
        });
600
        Rectangle::subtract_rects_many(self.inner().opaque_regions(scale), corners)
601
            .into_iter()
602
            .collect()
603
    }
604
605
    fn alpha(&self) -> f32 {
606
        self.inner().alpha()
607
    }
608
609
    fn kind(&self) -> Kind {
610
        self.inner().kind()
611
    }
612
}
613
614
impl RenderElement<VulkanRenderer> for ClipElement {
615
    fn draw(
616
        &self,
617
        frame: &mut VulkanFrame<'_, '_>,
618
        src: Rectangle<f64, Buffer>,
619
        dst: Rectangle<i32, Physical>,
620
        damage: &[Rectangle<i32, Physical>],
621
        opaque_regions: &[Rectangle<i32, Physical>],
622
        cache: Option<&UserDataMap>,
623
    ) -> Result<(), monolith_render_vk::Error> {
624
        frame.set_clip(Some(self.clip));
625
        let drawn = self
626
            .inner()
627
            .draw(frame, src, dst, damage, opaque_regions, cache);
628
        frame.set_clip(None);
629
        drawn
630
    }
631
}