atagen

monolith

public
Star0
Watch0

Commit 39d2db731e01

tarball

atagen <boss@atagen.co>2026-10-19 10:36 UTC

1 file changed+85-41
scene: give overshoot strips the window's opacity and a stable identity

PlacedTexture took a fresh Id and a constant commit counter every frame, so the strips at fractional scales damaged their area on every frame and an idle screen kept flipping. They now keep one Id per window and strip role and follow the root surface's commit. The strips also ignored the opacity rule, window_elements and effect_elements share a Spot instead of positional scale and opacity, and the CSD inset and drawn size round by edges so the crop decision agrees with pixels().
Modified file: crates/monolith/src/scene.rs+85 additions−41 deletions
@@ -178,16 +178,8 @@ pub fn build(
178178 let effect = anim.effect(id, now);
179179 let first = out.len();
180180 let opacity = state.rule.opacity.unwrap_or(1.0);
181- window_elements(
182- &mut out,
183- renderer,
184- &state.window,
185- r,
186- effect,
187- gliding,
188- scale,
189- opacity,
190- );
181+ let spot = Spot { r, scale, opacity };
182+ window_elements(&mut out, renderer, &state.window, &spot, effect, gliding);
191183 let clip = framed.then(|| corner_clip(r, deco, scale)).flatten();
192184 if let Some(clip) = clip {
193185 clip_window(&mut out, first, clip);
@@ -230,23 +222,20 @@ pub fn build(
230222 /// cropped and one smaller has its last row and column stretched over the
231223 /// rest, keeping content aligned with its neighbours. A gliding window is
232224 /// scaled instead, so the client resizing mid-glide does not jump.
233-#[expect(clippy::too_many_arguments)]
234225 fn window_elements(
235226 out: &mut Vec<Draw>,
236227 renderer: &mut VulkanRenderer,
237228 window: &Window,
238- r: Rect,
229+ spot: &Spot,
239230 effect: Option<Effect>,
240231 scaled: bool,
241- scale: f64,
242- opacity: f32,
243232 ) {
244- let Some(((x, y), (w, h))) = pixels(r, scale) else {
233+ let Spot { scale, opacity, .. } = *spot;
234+ let Some(((x, y), (w, h))) = spot.pixels() else {
245235 return;
246236 };
247237 let geo = window.geometry();
248- let inset = geo.loc.to_physical_precise_round::<_, i32>(scale);
249- let drawn: Size<i32, Physical> = geo.size.to_physical_precise_round(scale);
238+ let (inset, drawn) = edges(geo, scale);
250239 let loc = Point::<i32, Physical>::from((x - inset.x, y - inset.y));
251240 let alpha = effect.map_or(1.0, |e| e.progress) * opacity;
252241 let elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>> = match window.toplevel() {
@@ -261,16 +250,7 @@ fn window_elements(
261250 None => window.render_elements(renderer, loc, Scale::from(scale), alpha),
262251 };
263252 if let Some(effect) = effect {
264- effect_elements(
265- out,
266- renderer,
267- window,
268- elems,
269- ((x, y), (w, h)),
270- effect,
271- scale,
272- opacity,
273- );
253+ effect_elements(out, renderer, window, elems, spot, effect);
274254 return;
275255 }
276256 if scaled && drawn.w > 0 && drawn.h > 0 {
@@ -301,31 +281,43 @@ fn window_elements(
301281 return;
302282 };
303283 let context = renderer.context_id();
304- let Some((tex, buffer_scale, transform, view)) = with_renderer_surface_state(&root, |s| {
305- let texture = s.texture::<VulkanTexture>(context.clone())?.clone();
306- Some((texture, s.buffer_scale(), s.buffer_transform(), s.view()?))
307- })
308- .flatten() else {
284+ let Some((tex, buffer_scale, transform, view, commit)) =
285+ with_renderer_surface_state(&root, |s| {
286+ let texture = s.texture::<VulkanTexture>(context.clone())?.clone();
287+ Some((
288+ texture,
289+ s.buffer_scale(),
290+ s.buffer_transform(),
291+ s.view()?,
292+ s.current_commit(),
293+ ))
294+ })
295+ .flatten()
296+ else {
309297 return;
310298 };
299+ let ids = window.user_data().get_or_insert_threadsafe(StripIds::new);
311300 let (gw, gh) = (drawn.w, drawn.h);
312301 let (cw, ch) = (w.min(gw), h.min(gh));
313302 let ratio = source_ratio(&view);
314303 let geo_src = to_source(&view, geo.to_f64());
315304 let (gx, gy) = (geo_src.loc.x, geo_src.loc.y);
316- let mut strip = |src: Rectangle<f64, Logical>, at: (i32, i32), size: (i32, i32)| {
305+ let mut strip = |id: &Id, src: Rectangle<f64, Logical>, at: (i32, i32), size: (i32, i32)| {
317306 if size.0 >= 1 && size.1 >= 1 {
318307 let bounds = tex.size().to_logical(buffer_scale, transform).to_f64();
319308 let src = src.to_buffer(f64::from(buffer_scale), transform, &bounds);
320- out.push(Draw::Texture(PlacedTexture::new(
321- &tex, transform, src, at, size, None,
322- )));
309+ let mut placed = PlacedTexture::new(&tex, transform, src, at, size, None);
310+ placed.id = id.clone();
311+ placed.commit = commit;
312+ placed.alpha = opacity;
313+ out.push(Draw::Texture(placed));
323314 }
324315 };
325316 let pixel = 1.0 / f64::from(buffer_scale);
326317 let right = gx + geo_src.size.w - pixel;
327318 let bottom = gy + geo_src.size.h - pixel;
328319 strip(
320+ &ids.right,
329321 Rectangle::new(
330322 (right, gy).into(),
331323 (pixel, f64::from(ch) / scale * ratio.1).into(),
@@ -334,6 +326,7 @@ fn window_elements(
334326 (w - gw, ch),
335327 );
336328 strip(
329+ &ids.bottom,
337330 Rectangle::new(
338331 (gx, bottom).into(),
339332 (f64::from(cw) / scale * ratio.0, pixel).into(),
@@ -342,6 +335,7 @@ fn window_elements(
342335 (cw, h - gh),
343336 );
344337 strip(
338+ &ids.corner,
345339 Rectangle::new((right, bottom).into(), (pixel, pixel).into()),
346340 (x + gw, y + gh),
347341 (w - gw, h - gh),
@@ -428,17 +422,18 @@ fn border_width(deco: &Decoration, scale: f64) -> i32 {
428422
429423 /// A window in a spawn transition. Its root surface goes through the effect
430424 /// shader, clamped to fill its rect, and its subsurfaces fade with it.
431-#[expect(clippy::too_many_arguments)]
432425 fn effect_elements(
433426 out: &mut Vec<Draw>,
434427 renderer: &mut VulkanRenderer,
435428 window: &Window,
436429 elems: Vec<WaylandSurfaceRenderElement<VulkanRenderer>>,
437- (at, size): ((i32, i32), (i32, i32)),
430+ spot: &Spot,
438431 effect: Effect,
439- scale: f64,
440- opacity: f32,
441432 ) {
433+ let Spot { scale, opacity, .. } = *spot;
434+ let Some((at, size)) = spot.pixels() else {
435+ return;
436+ };
442437 let Some(root) = crate::wm::root(window) else {
443438 return;
444439 };
@@ -474,10 +469,54 @@ fn effect_elements(
474469 }
475470 }
476471
472+/// Where and how a window is drawn, in logical units and at an output scale.
473+struct Spot {
474+ r: Rect,
475+ scale: f64,
476+ opacity: f32,
477+}
478+
479+impl Spot {
480+ fn pixels(&self) -> Option<((i32, i32), (i32, i32))> {
481+ pixels(self.r, self.scale)
482+ }
483+}
484+
485+/// The inset and size of a window geometry in physical pixels, rounding both
486+/// edges so the size agrees with the rect `pixels` snaps a window to.
487+fn edges(geo: Rectangle<i32, Logical>, scale: f64) -> (Point<i32, Physical>, Size<i32, Physical>) {
488+ let round = |v: i32| (f64::from(v) * scale).round() as i32;
489+ let (x, y) = (round(geo.loc.x), round(geo.loc.y));
490+ let size = (
491+ round(geo.loc.x + geo.size.w) - x,
492+ round(geo.loc.y + geo.size.h) - y,
493+ );
494+ ((x, y).into(), size.into())
495+}
496+
497+/// The ids of the strips drawn past a window that is smaller than its rect,
498+/// kept so an unchanged window does not damage its area every frame.
499+struct StripIds {
500+ right: Id,
501+ bottom: Id,
502+ corner: Id,
503+}
504+
505+impl StripIds {
506+ fn new() -> Self {
507+ Self {
508+ right: Id::new(),
509+ bottom: Id::new(),
510+ corner: Id::new(),
511+ }
512+ }
513+}
514+
477515 /// A texture at a physical rectangle, drawn plainly or through the
478516 /// transition hook in the effect shader.
479517 pub struct PlacedTexture {
480518 id: Id,
519+ commit: CommitCounter,
481520 texture: VulkanTexture,
482521 src: Rectangle<f64, Buffer>,
483522 geometry: Rectangle<i32, Physical>,
@@ -497,6 +536,7 @@ impl PlacedTexture {
497536 ) -> Self {
498537 Self {
499538 id: Id::new(),
539+ commit: CommitCounter::default(),
500540 texture: texture.clone(),
501541 src,
502542 geometry: Rectangle::new(at.into(), size.into()),
@@ -513,13 +553,17 @@ impl Element for PlacedTexture {
513553 }
514554
515555 fn current_commit(&self) -> CommitCounter {
516- CommitCounter::default()
556+ self.commit
517557 }
518558
519559 fn src(&self) -> Rectangle<f64, Buffer> {
520560 self.src
521561 }
522562
563+ fn alpha(&self) -> f32 {
564+ self.alpha
565+ }
566+
523567 fn geometry(&self, _scale: Scale<f64>) -> Rectangle<i32, Physical> {
524568 self.geometry
525569 }