diff --git a/src/Surface.zig b/src/Surface.zig index 61e881cf4..53981eb70 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1563,10 +1563,10 @@ pub fn textCallback(self: *Surface, text: []const u8) !void { /// of focus state. This is used to pause rendering when the surface /// is not visible, and also re-render when it becomes visible again. pub fn occlusionCallback(self: *Surface, visible: bool) !void { - // If we became visible, then we queue a render. This helps scenarios - // where the apprt pauses rendering when the surface is not visible, - // i.e. macOS with Metal (see issue #1510). - if (visible) try self.queueRender(); + _ = self.renderer_thread.mailbox.push(.{ + .visible = visible, + }, .{ .forever = {} }); + try self.queueRender(); } pub fn focusCallback(self: *Surface, focused: bool) !void { diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index 2ad4145cf..113f6761c 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -82,6 +82,10 @@ flags: packed struct { /// This is true when the inspector is active. has_inspector: bool = false, + + /// This is true when the view is visible. This is used to determine + /// if we should be rendering or not. + visible: bool = true, } = .{}, pub const DerivedConfig = struct { @@ -242,6 +246,23 @@ fn drainMailbox(self: *Thread) !void { while (self.mailbox.pop()) |message| { log.debug("mailbox message={}", .{message}); switch (message) { + .visible => |v| { + // Set our visible state + self.flags.visible = v; + + // 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(); + + // Note that we're explicitly today not stopping any + // cursor timers, draw timers, etc. These things have very + // little resource cost and properly maintaining their active + // state across different transitions is going to be bug-prone, + // so its easier to just let them keep firing and have them + // check the visible state themselves to control their behavior. + }, + .focus => |v| { // Set it on the renderer try self.renderer.setFocus(v); @@ -341,6 +362,27 @@ fn changeConfig(self: *Thread, config: *const DerivedConfig) !void { self.config = config.*; } +/// Trigger a draw. This will not update frame data or anything, it will +/// just trigger a draw/paint. +fn drawFrame(self: *Thread) void { + // If we're invisible, we do not draw. + if (!self.flags.visible) return; + + // If we're doing single-threaded GPU calls then we just wake up the + // app thread to redraw at this point. + if (renderer.Renderer == renderer.OpenGL and + renderer.OpenGL.single_threaded_draw) + { + _ = self.app_mailbox.push( + .{ .redraw_surface = self.surface }, + .{ .instant = {} }, + ); + } else { + self.renderer.drawFrame(self.surface) catch |err| + log.warn("error drawing err={}", .{err}); + } +} + fn wakeupCallback( self_: ?*Thread, _: *xev.Loop, @@ -388,19 +430,8 @@ fn drawCallback( return .disarm; }; - // If we're doing single-threaded GPU calls then we just wake up the - // app thread to redraw at this point. - if (renderer.Renderer == renderer.OpenGL and - renderer.OpenGL.single_threaded_draw) - { - _ = t.app_mailbox.push( - .{ .redraw_surface = t.surface }, - .{ .instant = {} }, - ); - } else { - t.renderer.drawFrame(t.surface) catch |err| - log.warn("error drawing err={}", .{err}); - } + // Draw + t.drawFrame(); // Only continue if we're still active if (t.draw_active) { @@ -436,18 +467,8 @@ fn renderCallback( ) catch |err| log.warn("error rendering err={}", .{err}); - // If we're doing single-threaded GPU calls then we also wake up the - // app thread to redraw at this point. - if (renderer.Renderer == renderer.OpenGL and - renderer.OpenGL.single_threaded_draw) - { - _ = t.app_mailbox.push(.{ .redraw_surface = t.surface }, .{ .instant = {} }); - return .disarm; - } - // Draw - t.renderer.drawFrame(t.surface) catch |err| - log.warn("error drawing err={}", .{err}); + t.drawFrame(); return .disarm; } diff --git a/src/renderer/message.zig b/src/renderer/message.zig index 73faa0ad7..b215bd4d2 100644 --- a/src/renderer/message.zig +++ b/src/renderer/message.zig @@ -13,6 +13,11 @@ pub const Message = union(enum) { /// the renderer is expected to handle all of these. focus: bool, + /// A change in the view occlusion state. This can be used to determine + /// if the window is visible or not. A window can be not visible (occluded) + /// and still have focus. + visible: bool, + /// Reset the cursor blink by immediately showing the cursor then /// restarting the timer. reset_cursor_blink: void,