From 6694a2d9946d98d5752d84b22c45879cab5c9abe Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:10:00 +0200 Subject: Fix terminal content disappearing on resize: don't cache a blank content mask Reported live: "terminal output/everything disappears when i sometimes resize terminal." masked_content_buffer (the udev/Pixman rounded-corner content-masking path, live on this machine via general.rounded_corners) rendered a window's whole surface tree into an off-screen buffer and returned Some(bytes) unconditionally, with no check for whether that tree actually produced any drawable elements. content_epoch bumps on every commit, and a fast interactive resize is a rapid-fire sequence of commits - real odds that one races ahead of the client's own texture import, making the off-screen render legitimately come back empty. That blank result got returned as Some and cached under the new epoch the same as a correct one would, and rounded_content_buffer only rebuilds on the next epoch change - so the blank buffer stayed on screen, fully transparent, until the window's next real content change, indefinite for an idle terminal. masked_content_buffer now returns None when the element tree is empty, before doing the render+readback at all - the same "give up unmasked" pattern already used for a genuine renderer error. rounded_content_buffer drops rather than replaces its cache entry on None, so the render loop falls back to unmasked content for that one frame and retries the masked path on the next. Scoped to the udev/Pixman backend; winit masks via a GLES shader with no equivalent failure mode. --- docs/TODO.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'docs') diff --git a/docs/TODO.md b/docs/TODO.md index 06b3885..4201375 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -13,6 +13,18 @@ that has the full story. Keep this list current as items close or open; update the source doc's own entry too, don't let this drift into a second stale copy the way `PANEL_SUPPORT_TODO.md` did. +## Real bug, root-caused and fixed: a resize's own rapid-fire commits could get a window's rounded-corner content mask cached as blank, hiding real content until the next real content change (2026-08-25) + +Reported live: "terminal output/everything disappears when i sometimes resize terminal." `general.rounded_corners = true` in this machine's own live config, so the udev/Pixman content-masking path (`rounded_corners_pixman.rs`) is what's actually in play, not just a rarely-hit opt-in. + +Root cause in `masked_content_buffer`: it renders `surface`'s whole subsurface tree into a private off-screen buffer via `render_elements_from_surface_tree`, then unconditionally proceeds to composite and return `Some(bytes)` - with no check for whether that tree actually produced any drawable elements at all. `content_epoch` (the cache-invalidation key `rounded_content_buffer` uses to decide whether to call this again) bumps on *every* commit, unconditionally - and a fast interactive resize is exactly a rapid-fire sequence of commits. If one of those commits races ahead of the client's own texture import (real and reachable under that rate, not a one-in-a-million window), `render_elements_from_surface_tree` legitimately returns empty, and the old code rendered and returned a fully transparent buffer as if it were this window's real, current content - which then got cached under the new epoch, same as a correct result would. Since the cache only rebuilds on the *next* epoch change, that blank buffer stayed on screen, fully transparent, until the window's next real content change - indefinite for an idle, already-settled terminal, reading exactly as "the content disappeared." + +Fixed by treating an empty element tree the same as any other failed render (a genuine `create_buffer`/`bind`/`render_output`/etc. error already returned `None` here, "give up unmasked" - this closes the one gap in that pattern): `masked_content_buffer` now returns `None` before doing the expensive render+readback at all when `elements.is_empty()`. `rounded_content_buffer` already drops rather than replaces its cached entry on `None`, so the render loop falls back to the ordinary unmasked `surface_content_elements` path for that one frame (square corners for a frame, not blank), and naturally retries the masked path on the very next frame since a dropped cache entry always reads as stale. + +Scoped to the udev/Pixman backend only - the winit/GLES backend masks via a fragment shader (`rounded_corners.rs`), a different mechanism with no equivalent cache-a-blank-result failure mode. Built, full test suite (193 core / 106 wayland) and clippy clean; installed, pending a live restart and a fast-resize test to confirm. + +Related, lower-priority, not fixed this pass: `WindowManager::resizing_window()` - what gates skipping the (expensive) masking pass entirely during a resize, elsewhere in this same render loop - is only `Some` during an interactive mouse-drag resize, not a tiling reflow, keybind resize, or snap. Every commit during one of those runs the full masking pipeline synchronously, the same cost this file's own module doc comment already flags as the reason masking stays default-off - already tracked as "resizing is very laggy" elsewhere in this file, not a new finding, but worth noting as a second contributor to the same commit-storm pressure that made the blank-cache race reachable in the first place. + ## Real bug, root-caused and fixed: `DecorationSignature` was missing three of `render_titlebar`'s own inputs, so a live change to any of them would never invalidate the cache (2026-08-24) Found by a full-pipeline audit requested directly ("please do a deep dive into our codebase") after several rounds of live-reported rendering issues. `redraw_decoration_buffer`'s call to `decoration::render_titlebar` (`state/lifecycle.rs`) passes `theme.button_glyph_always`/`theme.button_order`/`theme.traffic_light_buttons` as three of its arguments, but `DecorationSignature` (`state/mod.rs`) - whose own doc comment states its goal outright, "one signature covering every input this function reads" - never included any of the three. `title_centered`/`buttons_left` were already in the struct specifically for this reason (their own doc comments say so explicitly: "there's no `srd set` for this yet, but nothing here assumes there never will be"), making the omission of the other three look like a straightforward miss rather than a deliberate exclusion. -- cgit v1.2.3