From 88d30bb30a02459a97e49fbb0817748422d6f65a Mon Sep 17 00:00:00 2001 From: Mike Bommarito Date: Wed, 20 May 2026 22:50:03 -0400 Subject: [PATCH 1/2] gtk: wire occlusionCallback to GLArea map/unmap Calls core_surface.occlusionCallback(visible) from the existing glareaMap/glareaUnmap handlers (added in #12698) so the renderer thread learns when a surface is off-screen. Co-Authored-By: Claude Opus 4.7 --- src/apprt/gtk/class/surface.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 2e05d7b12..3c9293a82 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -3283,6 +3283,7 @@ pub const Surface = extern struct { self: *Self, ) callconv(.c) void { self.updateMapped(true); + self.updateOcclusion(true); } fn glareaUnmap( @@ -3290,6 +3291,7 @@ pub const Surface = extern struct { self: *Self, ) callconv(.c) void { self.updateMapped(false); + self.updateOcclusion(false); } fn updateMapped(self: *Self, mapped: bool) void { @@ -3298,6 +3300,13 @@ pub const Surface = extern struct { self.as(gobject.Object).notifyByPspec(properties.mapped.impl.param_spec); } + fn updateOcclusion(self: *Self, visible: bool) void { + const surface = self.core() orelse return; + surface.occlusionCallback(visible) catch |err| { + log.warn("error in occlusion callback err={}", .{err}); + }; + } + fn glareaRender( _: *gtk.GLArea, _: *gdk.GLContext, From 14d9e600acf274f9b04da0dd2695d092aa93c3b6 Mon Sep 17 00:00:00 2001 From: Mike Bommarito Date: Wed, 20 May 2026 22:50:03 -0400 Subject: [PATCH 2/2] renderer: skip updateFrame when surface is not visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit renderCallback early-returns while !flags.visible to avoid the cell rebuild for hidden surfaces (tab switch, minimize, etc.). The .visible → true mailbox handler now runs updateFrame before drawFrame so the first frame after re-show isn't stale. Co-Authored-By: Claude Opus 4.7 --- src/renderer/Thread.zig | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index 508721379..488642199 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -360,10 +360,16 @@ fn drainMailbox(self: *Thread) !void { // Visibility affects our QoS class self.setQosClass(); - // If we became visible then we immediately trigger a draw. - // We don't need to update frame data because that should - // still be happening. - if (v) self.drawFrame(false); + // If we became visible then we immediately rebuild cells + // (renderCallback skips updateFrame while invisible) and draw. + if (v) { + self.renderer.updateFrame( + self.state, + self.flags.cursor_blink_visible, + ) catch |err| + log.warn("error rendering on visibility regain err={}", .{err}); + self.drawFrame(false); + } // Notify the renderer so it can update any state. self.renderer.setVisible(v); @@ -606,6 +612,10 @@ fn renderCallback( return .disarm; }; + // If we're not visible there's no point spending CPU rebuilding cells — + // we'll catch up when the .visible mailbox message flips us back on. + if (!t.flags.visible) return .disarm; + // Update our frame data t.renderer.updateFrame( t.state,