diff --git a/pkg/macos/video/display_link.zig b/pkg/macos/video/display_link.zig index 7d6b437f9..dc984422d 100644 --- a/pkg/macos/video/display_link.zig +++ b/pkg/macos/video/display_link.zig @@ -1,21 +1,19 @@ -const std = @import("std"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; const c = @import("c.zig").c; pub const DisplayLink = opaque { pub const Error = error{ + CreationFailed, InvalidOperation, }; - pub fn createWithActiveCGDisplays() Allocator.Error!*DisplayLink { + pub fn createWithActiveCGDisplays() Error!*DisplayLink { var result: ?*DisplayLink = null; if (c.CVDisplayLinkCreateWithActiveCGDisplays( @ptrCast(&result), ) != c.kCVReturnSuccess) - return error.OutOfMemory; + return error.CreationFailed; - return result orelse error.OutOfMemory; + return result orelse error.CreationFailed; } pub fn release(self: *DisplayLink) void { diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index 4b2b741d8..e3e2ceccf 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -501,7 +501,7 @@ fn drainMailbox(self: *Thread) !void { .macos_display_id => |v| { if (@hasDecl(rendererpkg.Renderer, "setMacOSDisplayID")) { - try self.renderer.setMacOSDisplayID(v); + try self.renderer.setMacOSDisplayID(v, &self.draw_now); } }, } diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index 88f73f46e..2882c9c8e 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); -const xev = @import("xev"); +const global = @import("../global.zig"); +const xev = global.xev; const wuffs = @import("wuffs"); const apprt = @import("../apprt.zig"); const configpkg = @import("../config.zig"); @@ -27,7 +28,6 @@ const ArenaAllocator = std.heap.ArenaAllocator; const Terminal = terminal.Terminal; const Health = renderer.Health; const compat_file = @import("../lib/compat/file.zig"); -const global = @import("../global.zig"); const getConstraint = @import("../font/nerd_font_attributes.zig").getConstraint; @@ -117,6 +117,9 @@ pub fn Renderer(comptime GraphicsAPI: type) type { /// True if the window is focused focused: bool, + /// True if the window is visible. + visible: bool, + /// Flag to indicate that our focus state changed for custom /// shaders to update their state. custom_shader_focused_changed: bool = false, @@ -693,15 +696,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; }; - const display_link: ?DisplayLink = switch (builtin.os.tag) { - .macos => if (options.config.vsync) - try macos.video.DisplayLink.createWithActiveCGDisplays() - else - null, - else => null, - }; - errdefer if (display_link) |v| v.release(); - var result: Self = .{ .alloc = alloc, .config = options.config, @@ -709,6 +703,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .grid_metrics = font_critical.metrics, .size = options.size, .focused = true, + .visible = true, .scrollbar = .zero, .scrollbar_dirty = false, .last_bottom_node = null, @@ -787,7 +782,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Graphics API stuff .api = api, .swap_chain = swap_chain, - .display_link = display_link, }; try result.initShaders(); @@ -907,16 +901,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // If we don't support a display link we have no work to do. if (comptime DisplayLink == void) return; - // This is when we know our "self" pointer is stable so we can - // setup the display link. To setup the display link we set our - // callback and we can start it immediately. - const display_link = self.display_link orelse return; - try display_link.setOutputCallback( - xev.Async, - &displayLinkCallback, - &thr.draw_now, - ); - display_link.start() catch {}; + self.syncDisplayLink(null, &thr.draw_now); } /// Called by renderer.Thread when it exits the main loop. @@ -1003,13 +988,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type { } /// Called when we get an updated display ID for our display link. - pub fn setMacOSDisplayID(self: *Self, id: u32) !void { + pub fn setMacOSDisplayID( + self: *Self, + id: u32, + draw_now: *xev.Async, + ) !void { if (comptime DisplayLink == void) return; - const display_link = self.display_link orelse return; - log.info("updating display link display id={}", .{id}); - display_link.setCurrentCGDisplay(id) catch |err| { - log.warn("error setting display link display id err={}", .{err}); - }; + self.syncDisplayLink(id, draw_now); } /// True if our renderer has animations so that a higher frequency @@ -1038,33 +1023,65 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Flag that we need to update our custom shaders self.custom_shader_focused_changed = true; - // If we're not focused, then we want to stop the display link - // because it is a waste of resources and we can move to pure - // change-driven updates. - if (comptime DisplayLink != void) link: { - const display_link = self.display_link orelse break :link; - if (focus) { - display_link.start() catch {}; - } else { - display_link.stop() catch {}; - } - } + self.syncDisplayLink(null, null); } /// Callback when the window is visible or occluded. /// /// Must be called on the render thread. pub fn setVisible(self: *Self, visible: bool) void { + self.visible = visible; + self.syncDisplayLink(null, null); + } + + /// Create or update the display link and match it to the current + /// surface state. + fn syncDisplayLink( + self: *Self, + display_id: ?u32, + draw_now: ?*xev.Async, + ) void { + if (comptime DisplayLink == void) return; + + const display_link = self.display_link orelse display_link: { + if (!self.config.vsync) return; + const callback = draw_now orelse return; + const result = macos.video.DisplayLink.createWithActiveCGDisplays() catch |err| { + // A locked macOS session can temporarily have no active + // displays. Rendering can continue without vsync and a + // later display update will retry this method. + log.warn("error creating display link; using fallback rendering err={}", .{err}); + return; + }; + result.setOutputCallback( + xev.Async, + &displayLinkCallback, + callback, + ) catch |err| { + log.warn("error configuring display link err={}", .{err}); + result.release(); + return; + }; + + self.display_link = result; + log.info("created display link", .{}); + break :display_link result; + }; + + if (display_id) |id| { + log.info("updating display link display id={}", .{id}); + display_link.setCurrentCGDisplay(id) catch |err| { + log.warn("error setting display link display id err={}", .{err}); + }; + } + // If we're not visible, then we want to stop the display link // because it is a waste of resources and we can move to pure // change-driven updates. - if (comptime DisplayLink != void) link: { - const display_link = self.display_link orelse break :link; - if (visible and self.focused) { - display_link.start() catch {}; - } else { - display_link.stop() catch {}; - } + if (self.visible and self.focused) { + display_link.start() catch {}; + } else { + display_link.stop() catch {}; } }