git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitd6642f51b93947264d814fcad3d43090d1516cc7
parent9239261a97
authorIsaac Freund <[email protected]>
date2025-03-23 14:40
Window: fix frame perfection on close

 river/Window.zig        | 21 +++++++++------------
 river/WindowManager.zig | 11 +++++++++--
 river/XdgToplevel.zig   |  2 +-
 3 files changed, 19 insertions(+), 15 deletions(-)

diff --git a/river/Window.zig b/river/Window.zig
index 9373195..adc6737 100644
--- a/river/Window.zig
+++ b/river/Window.zig
@@ -274,10 +274,11 @@ pub fn create(impl: Impl) error{OutOfMemory}!*Window {
     return window;
 }
 
-/// If saved buffers of the window are currently in use by a transaction,
-/// mark this window for destruction when the transaction completes. Otherwise
-/// destroy immediately.
-pub fn destroy(window: *Window, when: enum { lazy, assert }) void {
+/// It's safe to destroy the window after we no longer need the saved buffers for frame perfection.
+/// We no longer need the saved buffers after the windowing update sequence in
+/// which the closed event was sent is completed and the following rendering update
+/// sequence is completed as well.
+pub fn destroy(window: *Window, when: enum { lazy, force }) void {
     assert(window.impl == .none);
     assert(!window.mapped);
 
@@ -305,9 +306,10 @@ pub fn destroy(window: *Window, when: enum { lazy, assert }) void {
         }
     }
 
-    // If there are still saved buffers, then this window needs to be kept
-    // around until the current transaction completes. This function will be
-    // called again in WindowManager.commitTransaction()
+    if (when == .force) {
+        window.surfaces.dropSaved();
+    }
+
     if (!window.surfaces.saved.node.enabled) {
         window.tree.node.destroy();
         window.popup_tree.node.destroy();
@@ -317,11 +319,6 @@ pub fn destroy(window: *Window, when: enum { lazy, assert }) void {
         window.node.deinit();
 
         util.gpa.destroy(window);
-    } else {
-        switch (when) {
-            .lazy => {},
-            .assert => unreachable,
-        }
     }
 }
 
diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index 674259d..6f2efb9 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -399,8 +399,15 @@ fn updateRenderingFinish(wm: *WindowManager) void {
     {
         var it = wm.windows.safeIterator(.forward);
         while (it.next()) |window| {
-            window.surfaces.dropSaved();
-            if (window.destroying) window.destroy(.assert);
+            // If a window is unmapped during a rendering update, we need to retain the saved
+            // buffers until after the next windowing update (in which the closed event will
+            // be sent) for frame perfection.
+            if (window.windowing_scheduled.state != .closing) {
+                window.surfaces.dropSaved();
+            }
+            if (window.destroying) {
+                window.destroy(.force);
+            }
         }
     }
 
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index 411b530..b781c19 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -81,7 +81,7 @@ pub fn create(wlr_toplevel: *wlr.XdgToplevel) error{OutOfMemory}!void {
         .window = undefined,
         .wlr_toplevel = wlr_toplevel,
     } });
-    errdefer window.destroy(.assert);
+    errdefer window.destroy(.force);
 
     const toplevel = &window.impl.toplevel;