atagen

monolith

public
Star0
Watch0

Commit 188ed0425658

tarball

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

3 files changed+63-28
scene: map strips and effects through the surface view

Window strips, spawn effects and ghosts sampled the texture in surface coordinates as if they were its own, which only holds without a viewport destination. A fractional scale client sets one, so the stretched last column came from inside the window.
Modified file: crates/monolith/src/scene.rs+51 additions−20 deletions
@@ -12,7 +12,7 @@ use smithay::backend::renderer::element::surface::render_elements_from_surface_t
1212 use smithay::backend::renderer::element::utils::{CropRenderElement, RescaleRenderElement};
1313 use smithay::backend::renderer::element::{AsRenderElements, Element, Id, Kind, RenderElement};
1414 use smithay::backend::renderer::utils::with_renderer_surface_state;
15-use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions};
15+use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions, SurfaceView};
1616 use smithay::backend::renderer::{Frame, Renderer, Texture};
1717 use smithay::desktop::Window;
1818 use smithay::output::Output;
@@ -280,17 +280,18 @@ fn window_elements(
280280 return;
281281 };
282282 let context = renderer.context_id();
283- let Some((tex, buffer_scale, transform)) = with_renderer_surface_state(&root, |s| {
284- s.texture::<VulkanTexture>(context.clone())
285- .cloned()
286- .map(|t| (t, s.buffer_scale(), s.buffer_transform()))
283+ let Some((tex, buffer_scale, transform, view)) = with_renderer_surface_state(&root, |s| {
284+ let texture = s.texture::<VulkanTexture>(context.clone())?.clone();
285+ Some((texture, s.buffer_scale(), s.buffer_transform(), s.view()?))
287286 })
288287 .flatten() else {
289288 return;
290289 };
291290 let (gw, gh) = (drawn.w, drawn.h);
292291 let (cw, ch) = (w.min(gw), h.min(gh));
293- let (gx, gy) = (f64::from(geo.loc.x), f64::from(geo.loc.y));
292+ let ratio = source_ratio(&view);
293+ let geo_src = to_source(&view, geo.to_f64());
294+ let (gx, gy) = (geo_src.loc.x, geo_src.loc.y);
294295 let mut strip = |src: Rectangle<f64, Logical>, at: (i32, i32), size: (i32, i32)| {
295296 if size.0 >= 1 && size.1 >= 1 {
296297 let bounds = tex.size().to_logical(buffer_scale, transform).to_f64();
@@ -301,15 +302,21 @@ fn window_elements(
301302 }
302303 };
303304 let pixel = 1.0 / f64::from(buffer_scale);
304- let right = gx + f64::from(geo.size.w) - pixel;
305- let bottom = gy + f64::from(geo.size.h) - pixel;
305+ let right = gx + geo_src.size.w - pixel;
306+ let bottom = gy + geo_src.size.h - pixel;
306307 strip(
307- Rectangle::new((right, gy).into(), (pixel, f64::from(ch) / scale).into()),
308+ Rectangle::new(
309+ (right, gy).into(),
310+ (pixel, f64::from(ch) / scale * ratio.1).into(),
311+ ),
308312 (x + gw, y),
309313 (w - gw, ch),
310314 );
311315 strip(
312- Rectangle::new((gx, bottom).into(), (f64::from(cw) / scale, pixel).into()),
316+ Rectangle::new(
317+ (gx, bottom).into(),
318+ (f64::from(cw) / scale * ratio.0, pixel).into(),
319+ ),
313320 (x, y + gh),
314321 (cw, h - gh),
315322 );
@@ -367,6 +374,25 @@ fn pixels(r: Rect, scale: f64) -> Option<((i32, i32), (i32, i32))> {
367374 (w >= 1 && h >= 1).then_some(((x, y), (w, h)))
368375 }
369376
377+/// How many logical units of the texture one logical unit of the surface
378+/// covers, which a viewport destination makes differ from 1.
379+fn source_ratio(view: &SurfaceView) -> (f64, f64) {
380+ (
381+ view.src.size.w / f64::from(view.dst.w.max(1)),
382+ view.src.size.h / f64::from(view.dst.h.max(1)),
383+ )
384+}
385+
386+/// Maps a rectangle in surface coordinates to the logical coordinates of the
387+/// texture behind them.
388+pub fn to_source(view: &SurfaceView, r: Rectangle<f64, Logical>) -> Rectangle<f64, Logical> {
389+ let (rx, ry) = source_ratio(view);
390+ Rectangle::new(
391+ (view.src.loc.x + r.loc.x * rx, view.src.loc.y + r.loc.y * ry).into(),
392+ (r.size.w * rx, r.size.h * ry).into(),
393+ )
394+}
395+
370396 pub fn to_physical(p: (f64, f64), scale: f64) -> Point<i32, Physical> {
371397 ((p.0 * scale).round() as i32, (p.1 * scale).round() as i32).into()
372398 }
@@ -397,9 +423,8 @@ fn effect_elements(
397423 let crop = Rectangle::new(at.into(), size.into());
398424 let context = renderer.context_id();
399425 let root_tex = with_renderer_surface_state(&root, |s| {
400- s.texture::<VulkanTexture>(context.clone())
401- .cloned()
402- .map(|t| (t, s.buffer_scale(), s.buffer_transform()))
426+ let texture = s.texture::<VulkanTexture>(context.clone())?.clone();
427+ Some((texture, s.buffer_scale(), s.buffer_transform(), s.view()?))
403428 })
404429 .flatten();
405430 out.extend(
@@ -409,11 +434,14 @@ fn effect_elements(
409434 .filter_map(|e| CropRenderElement::from_element(e, scale, crop))
410435 .map(Draw::Cropped),
411436 );
412- if let Some((tex, buffer_scale, transform)) = root_tex {
437+ if let Some((tex, buffer_scale, transform, view)) = root_tex {
413438 let geo = window.geometry();
414- let src = Rectangle::new(
415- geo.loc.to_f64(),
416- (f64::from(size.0) / scale, f64::from(size.1) / scale).into(),
439+ let src = to_source(
440+ &view,
441+ Rectangle::new(
442+ geo.loc.to_f64(),
443+ (f64::from(size.0) / scale, f64::from(size.1) / scale).into(),
444+ ),
417445 );
418446 let bounds = tex.size().to_logical(buffer_scale, transform).to_f64();
419447 let src = src.to_buffer(f64::from(buffer_scale), transform, &bounds);
@@ -516,9 +544,12 @@ fn ghost_element(
516544 scale: f64,
517545 ) -> Option<Draw> {
518546 let (at, size) = pixels(r, scale)?;
519- let src = Rectangle::new(
520- frame.geometry.loc.to_f64(),
521- (f64::from(size.0) / scale, f64::from(size.1) / scale).into(),
547+ let src = to_source(
548+ &frame.view,
549+ Rectangle::new(
550+ frame.geometry.loc.to_f64(),
551+ (f64::from(size.0) / scale, f64::from(size.1) / scale).into(),
552+ ),
522553 );
523554 let bounds = frame
524555 .texture
Modified file: crates/monolith/src/windows.rs+8 additions−8 deletions
@@ -434,14 +434,14 @@ impl State {
434434 // below already holds.
435435 let geometry = window.geometry();
436436 with_renderer_surface_state(&surface, |s| {
437- s.texture::<VulkanTexture>(context.clone())
438- .cloned()
439- .map(|texture| LastFrame {
440- texture,
441- scale: s.buffer_scale(),
442- transform: s.buffer_transform(),
443- geometry,
444- })
437+ let texture = s.texture::<VulkanTexture>(context.clone())?.clone();
438+ Some(LastFrame {
439+ texture,
440+ scale: s.buffer_scale(),
441+ transform: s.buffer_transform(),
442+ geometry,
443+ view: s.view()?,
444+ })
445445 })
446446 .flatten()
447447 }
Modified file: crates/monolith/src/wm.rs+4 additions
@@ -7,6 +7,7 @@ use monolith_layout::{Layout, OutputId, Rect, WindowId};
77 use monolith_render_vk::VulkanTexture;
88 use slotmap::SecondaryMap;
99 use smithay::backend::renderer::element::solid::SolidColorBuffer;
10+use smithay::backend::renderer::utils::SurfaceView;
1011 use smithay::desktop::{PopupManager, Space, Window};
1112 use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState;
1213 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
@@ -39,6 +40,9 @@ pub struct LastFrame {
3940 pub transform: Transform,
4041 /// Window geometry inside the buffer.
4142 pub geometry: Rectangle<i32, Logical>,
43+ /// Where the surface's own coordinates sit in the texture, which a
44+ /// viewport makes more than a scale.
45+ pub view: SurfaceView,
4246 }
4347
4448 pub struct Parts<'a> {