Merge pull request #1522 from mitchellh/metal-occlusion

macOS: send occluded state to renderers, trigger draw on visible
This commit is contained in:
Mitchell Hashimoto
2024-02-13 22:02:00 -08:00
committed by GitHub
3 changed files with 54 additions and 28 deletions

View File

@ -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 /// of focus state. This is used to pause rendering when the surface
/// is not visible, and also re-render when it becomes visible again. /// is not visible, and also re-render when it becomes visible again.
pub fn occlusionCallback(self: *Surface, visible: bool) !void { pub fn occlusionCallback(self: *Surface, visible: bool) !void {
// If we became visible, then we queue a render. This helps scenarios _ = self.renderer_thread.mailbox.push(.{
// where the apprt pauses rendering when the surface is not visible, .visible = visible,
// i.e. macOS with Metal (see issue #1510). }, .{ .forever = {} });
if (visible) try self.queueRender(); try self.queueRender();
} }
pub fn focusCallback(self: *Surface, focused: bool) !void { pub fn focusCallback(self: *Surface, focused: bool) !void {

View File

@ -82,6 +82,10 @@ flags: packed struct {
/// This is true when the inspector is active. /// This is true when the inspector is active.
has_inspector: bool = false, 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 { pub const DerivedConfig = struct {
@ -242,6 +246,23 @@ fn drainMailbox(self: *Thread) !void {
while (self.mailbox.pop()) |message| { while (self.mailbox.pop()) |message| {
log.debug("mailbox message={}", .{message}); log.debug("mailbox message={}", .{message});
switch (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| { .focus => |v| {
// Set it on the renderer // Set it on the renderer
try self.renderer.setFocus(v); try self.renderer.setFocus(v);
@ -341,6 +362,27 @@ fn changeConfig(self: *Thread, config: *const DerivedConfig) !void {
self.config = config.*; 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( fn wakeupCallback(
self_: ?*Thread, self_: ?*Thread,
_: *xev.Loop, _: *xev.Loop,
@ -388,19 +430,8 @@ fn drawCallback(
return .disarm; return .disarm;
}; };
// If we're doing single-threaded GPU calls then we just wake up the // Draw
// app thread to redraw at this point. t.drawFrame();
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});
}
// Only continue if we're still active // Only continue if we're still active
if (t.draw_active) { if (t.draw_active) {
@ -436,18 +467,8 @@ fn renderCallback(
) catch |err| ) catch |err|
log.warn("error rendering err={}", .{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 // Draw
t.renderer.drawFrame(t.surface) catch |err| t.drawFrame();
log.warn("error drawing err={}", .{err});
return .disarm; return .disarm;
} }

View File

@ -13,6 +13,11 @@ pub const Message = union(enum) {
/// the renderer is expected to handle all of these. /// the renderer is expected to handle all of these.
focus: bool, 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 /// Reset the cursor blink by immediately showing the cursor then
/// restarting the timer. /// restarting the timer.
reset_cursor_blink: void, reset_cursor_blink: void,