diff options
| author | srdusr <[email protected]> | 2024-07-09 14:43:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-07-09 14:43:00 +0200 |
| commit | d2952af18b907a5b18fccd26468a74aabe49bb2f (patch) | |
| tree | 9bc553b83eed57ff62e2cbe308e496f641d32d5b /crates | |
| parent | 2cc10b0704d7352198374597075ef184253026be (diff) | |
| download | srdwm-d2952af18b907a5b18fccd26468a74aabe49bb2f.tar.gz srdwm-d2952af18b907a5b18fccd26468a74aabe49bb2f.zip | |
Rounded corners on udev/Pixman backend, opt-in and off by default
CPU-side rounded corners for the software-only udev/Pixman renderer,
which has no shader stage to hook the existing GLES version into.
Reads a window's own committed wl_shm buffer, punches premultiplied-
alpha holes into the four corner regions, and hands the masked copy
to MemoryRenderBuffer - the same path already used for titlebar/
border/shadow bitmaps, so it composites through the ordinary unmasked
path and the corners genuinely disappear rather than being painted
over.
Cached per window, invalidated by a per-commit content_epoch counter
rather than rebuilt every frame, so an idle window costs nothing once
masked. general.rounded_corners now defaults per backend instead of
one global true: on for GLES/winit (a real GPU shader, no measurable
cost), off for udev/Pixman (an untested-on-real-hardware CPU cost for
constantly-repainting clients) - WindowManager.rounded_corners_enabled
is Option<bool> so the backend can tell "unset" from "explicitly off".
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/config/src/lib.rs | 10 | ||||
| -rw-r--r-- | crates/core/src/manager.rs | 15 | ||||
| -rw-r--r-- | crates/srdwm/src/main.rs | 5 | ||||
| -rw-r--r-- | crates/wayland/src/elements.rs | 41 | ||||
| -rw-r--r-- | crates/wayland/src/lib.rs | 1 | ||||
| -rw-r--r-- | crates/wayland/src/protocols.rs | 6 | ||||
| -rw-r--r-- | crates/wayland/src/rounded_corners_pixman.rs | 199 | ||||
| -rw-r--r-- | crates/wayland/src/state.rs | 18 | ||||
| -rw-r--r-- | crates/wayland/src/udev.rs | 30 | ||||
| -rw-r--r-- | crates/wayland/src/winit.rs | 9 |
10 files changed, 325 insertions, 9 deletions
diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 43577f8..2c57b0a 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1008,7 +1008,15 @@ fn default_config() -> HashMap<String, ConfigValue> { set("general.animation_duration", Number(200.0)); set("general.shadows", Bool(true)); set("general.resize_margin", Number(6.0)); - set("general.rounded_corners", Bool(true)); + // Deliberately *not* seeded here, unlike every other `general.*` key -- + // its actual default differs by backend (GLES/winit: on; udev/Pixman: + // off, an untested-on-real-hardware CPU cost too real to default to on + // - see `crates/wayland/src/rounded_corners.rs`), and neither backend + // is known yet at the point `default_config` runs. Leaving the key + // genuinely absent (rather than pre-seeded `true`/`false`) is what lets + // `main.rs`'s `apply_general_settings` tell "user never touched this" + // apart from "user explicitly chose a value" and hand the *unset* case + // to whichever backend ends up connecting instead of deciding for it. set("general.focus_follows_mouse", Bool(false)); set("general.mouse_follows_focus", Bool(true)); set("general.auto_raise", Bool(false)); diff --git a/crates/core/src/manager.rs b/crates/core/src/manager.rs index 08284c5..0cca0d3 100644 --- a/crates/core/src/manager.rs +++ b/crates/core/src/manager.rs @@ -72,10 +72,15 @@ pub struct WindowManager { pub resize_margin: i32, /// Whether a decorated window's content rounds its bottom two corners /// to match the titlebar's own curve (an undecorated/CSD window rounds - /// all four). Read from `general.rounded_corners`. GLES-backend-only - /// in practice - see `crates/wayland/src/rounded_corners.rs` - so - /// this is a no-op on the udev/Pixman backend regardless of its value. - pub rounded_corners_enabled: bool, + /// all four). Read from `general.rounded_corners` - `None` when the + /// user's config never touched that key at all (deliberately *not* + /// defaulted in `crates/config`, unlike every other `general.*` key), + /// so each backend can fall back to its own default rather than one + /// baked in here: GLES/winit defaults on, udev/Pixman defaults off + /// (an untested-on-real-hardware per-frame CPU cost for content that + /// redraws constantly - see `crates/wayland/src/rounded_corners.rs`). + /// `Some(_)` only when the user explicitly set it, and wins either way. + pub rounded_corners_enabled: Option<bool>, /// Default decoration colours and border width, read from `theme.colors.*`/ /// `theme.decorations.*`. See `ThemeConfig`'s own doc comment. pub theme: ThemeConfig, @@ -120,7 +125,7 @@ impl WindowManager { animation_duration_ms: 200, shadows_enabled: true, resize_margin: RESIZE_MARGIN, - rounded_corners_enabled: true, + rounded_corners_enabled: None, theme: ThemeConfig::default(), drag: None, resize: None, diff --git a/crates/srdwm/src/main.rs b/crates/srdwm/src/main.rs index 36da572..e4c2177 100644 --- a/crates/srdwm/src/main.rs +++ b/crates/srdwm/src/main.rs @@ -151,7 +151,10 @@ fn apply_general_settings(engine: &Engine, wm: &Rc<RefCell<WindowManager>>) { let duration = engine.get_f64("general.animation_duration", 200.0).max(0.0) as u32; let shadows = engine.get_bool("general.shadows", true); let resize_margin = engine.get_f64("general.resize_margin", srdwm_core::RESIZE_MARGIN as f64).max(1.0) as i32; - let rounded_corners = engine.get_bool("general.rounded_corners", true); + // Genuinely absent, not `false`, when the user's config never sets it + // - see `WindowManager::rounded_corners_enabled`'s doc comment for why + // this can't just be `get_bool(..., true)` like every other flag here. + let rounded_corners = engine.get("general.rounded_corners").and_then(|v| v.as_bool()); // Only the three `theme.*` keys with an unambiguous, already-rendered // counterpart are wired - see `srdwm_core::ThemeConfig`'s doc comment. diff --git a/crates/wayland/src/elements.rs b/crates/wayland/src/elements.rs index c9f1d6c..c8cb10c 100644 --- a/crates/wayland/src/elements.rs +++ b/crates/wayland/src/elements.rs @@ -147,6 +147,47 @@ where render_elements_from_surface_tree(renderer, surface, location, 1.0, alpha, Kind::Unspecified) } +/// Looks up (rebuilding first if stale) the Pixman-backend rounded-corner +/// masked copy of `surface`'s content - see `rounded_corners_pixman`'s +/// module doc comment for what this actually does and why it needs a cache +/// at all. `epoch` is the window's current `CompState::content_epoch` +/// value (bumped once per real commit, in `commit()`); the cached entry is +/// only rebuilt when that no longer matches what it was last built from, so +/// a window that isn't currently repainting costs nothing here beyond one +/// `HashMap` lookup per frame. +/// +/// Free function taking the two fields it needs directly, rather than a +/// `CompState` method, so it can be called from inside `udev.rs`'s render +/// loop alongside the already-live `self.udev.as_mut()` borrow - see that +/// call site. +/// +/// `None` either because masking genuinely isn't possible right now (falls +/// through to `rounded_corners_pixman::masked_content_buffer`'s own `None` +/// cases) or because it hasn't been attempted yet this call; either way the +/// caller's fallback is the same: render `surface`'s content unrounded via +/// [`surface_content_elements`]. +pub(crate) fn rounded_content_buffer<'a>( + cache: &'a mut std::collections::HashMap<srdwm_core::WindowId, (u64, smithay::backend::renderer::element::memory::MemoryRenderBuffer)>, + epoch: u64, + id: srdwm_core::WindowId, + surface: &WlSurface, + radius: f32, + corners: crate::rounded_corners::RoundedCorners, +) -> Option<&'a smithay::backend::renderer::element::memory::MemoryRenderBuffer> { + let stale = cache.get(&id).map(|(built, _)| *built != epoch).unwrap_or(true); + if stale { + match crate::rounded_corners_pixman::masked_content_buffer(surface, radius, corners) { + Some(buf) => { + cache.insert(id, (epoch, buf)); + } + None => { + cache.remove(&id); + } + } + } + cache.get(&id).map(|(_, b)| b) +} + /// Every mapped layer-shell surface on `output` whose [`Layer`] `include` /// accepts, each rendered via [`surface_content_elements`] at full opacity /// - layer-shell surfaces (bars, docks, wallpaper engines) don't have a diff --git a/crates/wayland/src/lib.rs b/crates/wayland/src/lib.rs index 7687812..711006d 100644 --- a/crates/wayland/src/lib.rs +++ b/crates/wayland/src/lib.rs @@ -50,6 +50,7 @@ mod output_management; mod output_power; mod protocols; mod rounded_corners; +mod rounded_corners_pixman; mod screencopy; mod state; mod udev; diff --git a/crates/wayland/src/protocols.rs b/crates/wayland/src/protocols.rs index 9f23c69..f680dc3 100644 --- a/crates/wayland/src/protocols.rs +++ b/crates/wayland/src/protocols.rs @@ -123,6 +123,12 @@ impl CompositorHandler for CompState { if let Some(w) = self.id_to_window.get(&id) { w.on_commit(); } + // See `content_epoch`'s doc comment: this is the only per-commit + // signal the udev backend's rounded-corner mask cache has to + // invalidate itself, since content can change every frame, + // independent of the geometry-driven points `redraw_decoration_ + // buffer` already runs at. + *self.content_epoch.entry(id).or_insert(0) += 1; crate::state::sync_toplevel_metadata(self, id, surface); } self.ensure_layer_initial_configure(surface); diff --git a/crates/wayland/src/rounded_corners_pixman.rs b/crates/wayland/src/rounded_corners_pixman.rs new file mode 100644 index 0000000..1aaa5bc --- /dev/null +++ b/crates/wayland/src/rounded_corners_pixman.rs @@ -0,0 +1,199 @@ +//! Real rounded corners on a window's own client content - udev/Pixman +//! backend. `rounded_corners.rs` covers the GLES/winit backend with a +//! fragment shader; `PixmanRenderer` is software-only and has no shader +//! stage to hook that into at all (`PixmanFrame::render_texture_from_to`'s +//! mask is a hardcoded flat alpha, and its destination image is private to +//! smithay's own module - no public hook for a custom mask picture). +//! +//! The technique here instead bakes the mask into a *copy* of the client's +//! own pixel data before it ever reaches the normal compositing path: read +//! the surface's committed `wl_shm` buffer, punch premultiplied-alpha holes +//! (this codebase's existing BGRA convention - see `decoration:: +//! shadow_bitmap`'s doc comment) into the four corner regions, and hand the +//! result to `MemoryRenderBuffer` - the exact same type and render-element +//! path already used for the titlebar/border/shadow bitmaps. Rendering it +//! through the ordinary unmasked `render_texture_from_to` is what makes the +//! corners actually disappear: a premultiplied-zero source pixel there +//! contributes nothing, leaving whatever was already drawn underneath (the +//! desktop, or another window) showing through - a real cutout, not a +//! flat-colour patch. +//! +//! Deliberately narrow scope, same as the GLES version: only a window's +//! *main* surface (no subsurfaces), and only the two common `wl_shm` +//! formats this compositor's own bitmaps already use (`Argb8888`/ +//! `Xrgb8888`) - anything else, a non-`wl_shm` buffer (dmabuf, a GL +//! client), or a non-identity buffer transform falls back to `None`, which +//! the caller treats as "render this window's content unrounded" rather +//! than an error. +//! +//! Cost, and why this stays default-off on this backend (`general. +//! rounded_corners`, see `WindowManager::rounded_corners_enabled`'s doc +//! comment): unlike the GLES shader, which the GPU evaluates once per pixel +//! at zero extra CPU cost, this masks a full copy of the surface's pixel +//! data on the CPU. The mask math itself only touches the four small +//! corner boxes, but producing a tightly-packed buffer `MemoryRenderBuffer:: +//! from_slice` accepts (it asserts a `width * 4` stride; a client's own SHM +//! stride is often larger, padded for alignment) means copying the whole +//! buffer row by row regardless. The caller is expected to cache the +//! result and only call this again when the surface's content has actually +//! changed (see `CompState::content_epoch`), so the real per-frame cost for +//! idle/static windows is nothing - but a constantly-repainting client +//! (video, a terminal under heavy scrollback) pays this on every commit for +//! as long the feature stays on, which is exactly the untested-on-real- +//! hardware cost the opt-in default exists to avoid forcing on anyone. + +use crate::rounded_corners::RoundedCorners; +use smithay::backend::allocator::Fourcc; +use smithay::backend::renderer::element::memory::MemoryRenderBuffer; +use smithay::backend::renderer::utils::{with_renderer_surface_state, RendererSurfaceState}; +use smithay::reexports::wayland_server::protocol::wl_shm; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::utils::Transform; +use smithay::wayland::shm::{with_buffer_contents, BufferData}; + +/// Builds a rounded-corner-masked copy of `surface`'s own committed content, +/// or `None` if that isn't possible right now - see this module's doc +/// comment for every case that falls back rather than erroring. `radius` is +/// in the same logical-pixel units as `decoration::CORNER_RADIUS`; scaled +/// up to buffer pixels internally using the surface's own buffer scale. +pub(crate) fn masked_content_buffer(surface: &WlSurface, radius: f32, corners: RoundedCorners) -> Option<MemoryRenderBuffer> { + let (buffer, scale, transform) = with_renderer_surface_state(surface, |state: &mut RendererSurfaceState| { + let buffer = state.buffer()?.clone(); + Some((buffer, state.buffer_scale(), state.buffer_transform())) + })??; + // A rotated/flipped buffer would need the mask rotated with it; not + // worth the extra math for a cosmetic, already-narrow-scope pass. + if transform != Transform::Normal { + return None; + } + let radius_px = radius * scale as f32; + + let (data, w, h) = with_buffer_contents(&buffer, move |ptr, len, data: BufferData| -> Option<(Vec<u8>, i32, i32)> { + if !matches!(data.format, wl_shm::Format::Argb8888 | wl_shm::Format::Xrgb8888) { + return None; + } + let (w, h, stride, offset) = (data.width, data.height, data.stride, data.offset); + if w <= 0 || h <= 0 || stride <= 0 || offset < 0 { + return None; + } + let needed = offset as usize + stride as usize * h as usize; + if needed > len { + return None; + } + // SAFETY: `pool.with_data` (inside `with_buffer_contents`) already + // validated `ptr`/`len` cover the whole pool; `needed` above + // re-checks this buffer's own slice sits inside that before a + // single byte is read. + let src = unsafe { std::slice::from_raw_parts(ptr.add(offset as usize), stride as usize * h as usize) }; + + // Repack into a tight `width * 4` stride: `MemoryRenderBuffer:: + // from_slice` computes its own stride from `width` alone and + // asserts the data matches it, so the source's (often padded) SHM + // stride can't be handed through as-is. + let row_bytes = w as usize * 4; + let mut out = vec![0u8; row_bytes * h as usize]; + for y in 0..h as usize { + let src_row = &src[y * stride as usize..y * stride as usize + row_bytes]; + out[y * row_bytes..(y + 1) * row_bytes].copy_from_slice(src_row); + } + + let radius_px = radius_px.min(w as f32 / 2.0).min(h as f32 / 2.0); + apply_corner_mask(&mut out, w, h, row_bytes as i32, radius_px, corners); + Some((out, w, h)) + }) + .ok() + .flatten()?; + + Some(MemoryRenderBuffer::from_slice(&data, Fourcc::Argb8888, (w, h), scale, Transform::Normal, None)) +} + +/// Zeroes (fading over ~2px, matching `rounded_corners::FRAGMENT_SHADER`'s +/// `smoothstep`) every premultiplied BGRA pixel in `buf` that falls outside +/// the rounded rect described by `radius` at each of the four corners +/// `corners` selects. Only walks the four `radius`-sized corner boxes, not +/// the whole image - everywhere else the mask is exactly `1.0`, a no-op. +/// +/// Uses the same `clamp`-then-`distance` construction as the GLSL version +/// rather than a per-corner mirrored center, so one formula handles all +/// four boxes correctly regardless of which edges of the image they sit +/// against. +fn apply_corner_mask(buf: &mut [u8], w: i32, h: i32, stride: i32, radius: f32, corners: RoundedCorners) { + if radius < 1.0 || w <= 0 || h <= 0 { + return; + } + let r = radius.ceil() as i32; + let (wf, hf) = (w as f32, h as f32); + let boxes = [ + (corners.top_left, 0, 0, r.min(w), r.min(h)), + (corners.top_right, (w - r).max(0), 0, w, r.min(h)), + (corners.bottom_left, 0, (h - r).max(0), r.min(w), h), + (corners.bottom_right, (w - r).max(0), (h - r).max(0), w, h), + ]; + for (enabled, x0, y0, x1, y1) in boxes { + if !enabled { + continue; + } + for y in y0..y1 { + let py = y as f32 + 0.5; + let cy = py.clamp(radius, hf - radius); + for x in x0..x1 { + let px = x as f32 + 0.5; + let cx = px.clamp(radius, wf - radius); + let dist = ((px - cx).powi(2) + (py - cy).powi(2)).sqrt(); + if dist <= radius - 1.0 { + continue; + } + let mask = 1.0 - smoothstep(radius - 1.0, radius + 1.0, dist); + let i = (y * stride + x * 4) as usize; + if i + 4 > buf.len() { + continue; + } + for b in &mut buf[i..i + 4] { + *b = (*b as f32 * mask).round() as u8; + } + } + } + } +} + +fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 { + let t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0); + t * t * (3.0 - 2.0 * t) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn corner_mask_leaves_the_interior_untouched() { + let (w, h) = (20, 20); + let stride = w * 4; + let mut buf = vec![200u8; (stride * h) as usize]; + apply_corner_mask(&mut buf, w, h, stride, 6.0, RoundedCorners::ALL); + let center = ((h / 2 * stride) + (w / 2) * 4) as usize; + assert_eq!(&buf[center..center + 4], &[200, 200, 200, 200]); + } + + #[test] + fn corner_mask_zeroes_the_outermost_corner_pixel() { + let (w, h) = (20, 20); + let stride = w * 4; + let mut buf = vec![200u8; (stride * h) as usize]; + apply_corner_mask(&mut buf, w, h, stride, 6.0, RoundedCorners::ALL); + // Pixel (0, 0) is `sqrt(2) * 6 ≈ 8.49` px from the corner's arc + // center at (6, 6) - well past `radius + 1`, so fully masked. + assert_eq!(&buf[0..4], &[0, 0, 0, 0]); + } + + #[test] + fn bottom_only_leaves_the_top_corners_alone() { + let (w, h) = (20, 20); + let stride = w * 4; + let mut buf = vec![200u8; (stride * h) as usize]; + apply_corner_mask(&mut buf, w, h, stride, 6.0, RoundedCorners::BOTTOM_ONLY); + assert_eq!(&buf[0..4], &[200, 200, 200, 200]); + let bottom_left = ((h - 1) * stride) as usize; + assert_eq!(&buf[bottom_left..bottom_left + 4], &[0, 0, 0, 0]); + } +} diff --git a/crates/wayland/src/state.rs b/crates/wayland/src/state.rs index 4416869..5b5b4e0 100644 --- a/crates/wayland/src/state.rs +++ b/crates/wayland/src/state.rs @@ -259,6 +259,22 @@ pub(crate) struct CompState { /// declare on the shared `CompState` even though only one backend ever /// populates it. pub(crate) rounded_corners_program: Option<smithay::backend::renderer::gles::GlesTexProgram>, + /// Bumped once per real `commit()` of a mapped window's surface -- + /// see that handler in `protocols.rs`. The only signal `rounded_content_buffers` + /// needs to know its cached masked copy is stale, since content changes + /// (unlike geometry, which `redraw_decoration_buffer`'s trigger points + /// already cover) can arrive on every single frame for a video or + /// terminal, with nothing else in this struct tracking that. + pub(crate) content_epoch: HashMap<WindowId, u64>, + /// The udev/Pixman-backend rounded-corner masked copy of a window's own + /// content (`rounded_corners_pixman::masked_content_buffer`), paired + /// with the `content_epoch` value it was built from - see + /// `elements::rounded_content_buffer`, which owns rebuilding this. + /// Always empty on the winit backend (GLES rounds via a shader instead, + /// `rounded_corners_program`), but costs nothing to declare here + /// unconditionally, the same call `rounded_corners_program` itself + /// already makes. + pub(crate) rounded_content_buffers: HashMap<WindowId, (u64, MemoryRenderBuffer)>, /// Persistent solid-colour buffers backing a window's other three /// border strips (bottom, left, right - `decoration::border_strips`' /// order past index 0), reused by position every frame rather than @@ -662,6 +678,8 @@ impl CompState { self.shadow_buffers.remove(&id); self.border_side_buffers.remove(&id); self.last_synced_size.remove(&id); + self.content_epoch.remove(&id); + self.rounded_content_buffers.remove(&id); // A window closing (crash, kill, or its own menu's "Close" action // racing ahead of this) while its context menu is still open would // otherwise leave the menu pointing at a dead id - selecting any diff --git a/crates/wayland/src/udev.rs b/crates/wayland/src/udev.rs index 40c0ce5..cdea265 100644 --- a/crates/wayland/src/udev.rs +++ b/crates/wayland/src/udev.rs @@ -207,6 +207,10 @@ impl CompState { // looked up fresh per head (head-local `origin` translation). let ids: Vec<srdwm_core::WindowId> = if locked { Vec::new() } else { self.wm.borrow().visible_windows_front_to_back().map(|w| w.id).collect() }; let focused = self.wm.borrow().focused_id(); + // Default `false` here, unlike winit's `unwrap_or(true)` - see + // `rounded_corners_pixman`'s module doc comment for the CPU cost + // that makes this backend opt-in rather than on by default. + let rounded_corners_enabled = self.wm.borrow().rounded_corners_enabled.unwrap_or(false); let popup_targets = if locked { Vec::new() } else { crate::elements::popup_targets(self) }; // Which heads are eligible, and what each needs, gathered before the @@ -458,7 +462,29 @@ impl CompState { if let Some(surface) = crate::elements::window_wl_surface(dwindow) { let band = if w.decorated { srdwm_core::TITLEBAR_HEIGHT as i32 } else { 0 }; let pos = (geom.x - origin.x, geom.y + band - origin.y); - custom_elements.extend(crate::elements::surface_content_elements(&mut udev.renderer, &surface, pos, w.opacity)); + let mut rounded_elem = None; + if rounded_corners_enabled { + let epoch = self.content_epoch.get(&id).copied().unwrap_or(0); + // Bottom-only for a decorated window, same + // reasoning as `winit.rs`'s identical split: + // the top two corners are already hidden + // under the titlebar band's own rounded + // bitmap. + let corners = if w.decorated { crate::rounded_corners::RoundedCorners::BOTTOM_ONLY } else { crate::rounded_corners::RoundedCorners::ALL }; + if let Some(buffer) = + crate::elements::rounded_content_buffer(&mut self.rounded_content_buffers, epoch, id, &surface, decoration::CORNER_RADIUS as f32, corners) + { + match MemoryRenderBufferRenderElement::from_buffer(&mut udev.renderer, (pos.0 as f64, pos.1 as f64), buffer, Some(w.opacity), None, None, Kind::Unspecified) + { + Ok(elem) => rounded_elem = Some(elem), + Err(e) => log::warn!("udev: failed to import rounded content buffer: {e}"), + } + } + } + match rounded_elem { + Some(elem) => custom_elements.push(crate::elements::OverlayElement::Memory(elem)), + None => custom_elements.extend(crate::elements::surface_content_elements(&mut udev.renderer, &surface, pos, w.opacity)), + } } } occluders.push(geom); @@ -1080,6 +1106,8 @@ impl UdevPlatform { border_top_decorations: HashMap::new(), shadow_buffers: HashMap::new(), rounded_corners_program: None, + content_epoch: HashMap::new(), + rounded_content_buffers: HashMap::new(), border_side_buffers: HashMap::new(), last_synced_size: HashMap::new(), pending: pending.clone(), diff --git a/crates/wayland/src/winit.rs b/crates/wayland/src/winit.rs index 5f072b6..f50d095 100644 --- a/crates/wayland/src/winit.rs +++ b/crates/wayland/src/winit.rs @@ -242,6 +242,8 @@ impl WaylandPlatform { border_top_decorations: HashMap::new(), shadow_buffers: HashMap::new(), rounded_corners_program, + content_epoch: HashMap::new(), + rounded_content_buffers: HashMap::new(), border_side_buffers: HashMap::new(), last_synced_size: HashMap::new(), pending: pending.clone(), @@ -406,7 +408,12 @@ impl WaylandPlatform { // The bar/dock/launcher, skipped entirely for a fullscreen window -- // see `udev.rs`'s matching push for the full reasoning. let hide_top_layers = self.wm.borrow().visible_windows_front_to_back().any(|w| w.fullscreen); - let rounded_corners_enabled = self.wm.borrow().rounded_corners_enabled; + // `None` (the user's config never touched `general.rounded_corners`) + // defaults to *on* here - this backend has an actual GPU shader + // behind the feature (see `rounded_corners.rs`), no untested + // per-frame CPU cost to weigh the way the udev backend's own + // default has to. + let rounded_corners_enabled = self.wm.borrow().rounded_corners_enabled.unwrap_or(true); if !hide_top_layers { custom_elements.extend( crate::elements::output_layer_elements(renderer, &self.output, (0, 0), |layer| matches!(layer, Layer::Top | Layer::Overlay)) |