Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

What changed in TideWM, in plain words, newest first. Releases are grouped by the stretch of work they came from. Every release in full technical detail lives in [DEV_CHANGELOG.md](DEV_CHANGELOG.md).

## 0.90.200 · 2026-10-28 — GPU memory freed while idle

- Textures and copied buffers that TideWM no longer needs are now freed every few seconds, on every GPU, even when nothing on screen is changing. Before, they could wait for the next drawn frame, and on a second GPU they were never swept. GPUs with nothing to free are not woken up.

## 0.90.199 · 2026-10-28 — Screen sharing survives a monitor switching off

- Turning off or unplugging a monitor you are sharing no longer kills the share. OBS, Discord and other apps keep the last picture while it is gone, and the live picture comes back by itself when the monitor returns. If it comes back at a different resolution, the share ends as before and you pick it again.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tidewm"
version = "0.90.199"
version = "0.90.200"
edition = "2021"
license = "GPL-3.0-or-later"
# Verified floor: wayland-protocols 0.32.13 requires 1.86 (1.85 fails to
Expand Down
6 changes: 6 additions & 0 deletions DEV_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Every TideWM release in full technical detail, newest first. For a short summary of what changed for users, see [CHANGELOG.md](CHANGELOG.md). Format loosely follows [Keep a Changelog](https://keepachangelog.com/).

## 0.90.200 — Idle texture-cache sweep on every GPU node

- Smithay frees dropped GL textures, EGL images and buffers only in `GlesRenderer::cleanup`, which runs on `unbind` after each rendered frame and on no-damage/direct-scanout `render_frame` calls, and only for the renderer that drew. Two gaps followed. Anything released after an idle desktop's last frame (a hidden-workspace window closing, a backdrop or capture texture dropped outside a frame) stayed queued until the next frame. Render-only GPU nodes, which hold cross-GPU imports in `GpuManager`, were never swept, because `ImportBridge::cleanup_texture_cache` only reaches the primary renderer.
- The udev maintenance timer (≈1 s idle period, not tied to frames or outputs) now calls `GpuManager::cleanup_texture_cache` (upstream `347b2b30`) at most every 5 s (`TEXTURE_CACHE_CLEANUP_INTERVAL`), through `UdevGpu::cleanup_texture_caches`. It is skipped while the DRM device is paused (VT switched away) and uses `try_borrow_mut`, so it can never collide with a render. Upstream `79bbed5e` makes the context current only when its destruction queue is non-empty, so an idle node is not woken from runtime suspend (Smithay #2178/#2180). This is the call site plan §3 deferred to after the migration, and the basis of Ebb's `low` level.
- No dGPU/iGPU, vendor or output-specific logic; the winit backend is unchanged (single host-owned renderer). Native evidence owed: no unexpected GPU wake or frame stutter, and memory after closing windows on a hidden workspace.

## 0.90.199 — Monitor screencasts outlive a disconnected output

- Native report on `.198`: OBS capture of HDMI-A-2 broke after the monitor was powered off. Session log: `Output disconnected` at 01:59:29; OBS's stream went `streaming` → `paused` at the same second and stayed there, and its `tidewm-screencast-HDMI-A-2` node was gone. Cause (TideWM, not the migration): the compositor-side `FrameRequested` handler called `FrameTarget::close()` when the named output was missing, so the PipeWire worker stopped and removed its node, but the portal session was never told (no `impl.portal.Session` `Closed`), leaving the consumer on a dead stream that a returning monitor could not revive.
Expand Down
28 changes: 28 additions & 0 deletions src/backend/udev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,21 @@ impl UdevGpu {
imported
}

/// Frees textures and imports already released on every GPU node.
///
/// Each rendered frame cleans only the renderer it drew with, so the
/// render-only GPUs that hold cross-GPU imports, and anything dropped
/// after the last frame of an idle desktop, would otherwise wait. Smithay
/// makes a context current only when it has something to destroy, so a
/// node with nothing queued is not woken from runtime suspend.
fn cleanup_texture_caches(&self) {
// The timer never runs inside a render, but stay safe if a borrow
// is ever held across it.
if let Ok(mut manager) = self.manager.try_borrow_mut() {
let _ = manager.cleanup_texture_cache();
}
}

pub(crate) fn prepare_surface(&self, surface: &WlSurface) {
let mut manager = self.manager.borrow_mut();
if let Err(error) = manager.early_import(self.primary_render_node, surface) {
Expand Down Expand Up @@ -1102,9 +1117,19 @@ pub fn init_udev(
// Continuous animation derives its wake period from the fastest live mode;
// scheduled caustics uses its configured deadline directly.
let device_for_timer = Rc::clone(&device);
let mut last_texture_cleanup = Instant::now();
event_loop
.handle()
.insert_source(Timer::immediate(), move |_, _, state: &mut Smallvil| {
// Rate-limited, and never while VT-switched away: GL work on a
// paused device has nothing to free and nothing to gain.
if last_texture_cleanup.elapsed() >= TEXTURE_CACHE_CLEANUP_INTERVAL {
last_texture_cleanup = Instant::now();
let device = device_for_timer.borrow();
if device.drm.is_active() {
device.gpu.cleanup_texture_caches();
}
}
state.update_window_depths();
state.update_urgent_pulses();
state.update_float_physics_full();
Expand Down Expand Up @@ -1669,6 +1694,9 @@ fn handle_connector_change(
/// bindable, so clients that raced a bind still find it (niri uses the same).
const OUTPUT_GLOBAL_REMOVE_DELAY: Duration = Duration::from_secs(10);

/// How often the maintenance timer sweeps every GPU's released textures.
const TEXTURE_CACHE_CLEANUP_INTERVAL: Duration = Duration::from_secs(5);

/// Retracts a disconnected output's `wl_output` global. Must run after every
/// surface has left the output: a client that sees `global_remove` destroys
/// its proxy, and a later `wl_surface.leave` naming it reaches the client as
Expand Down
Loading