mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Merge pull request #1727 from mitchellh/metal-link2
macOS: Unleash the Framerate
This commit is contained in:
@ -504,6 +504,7 @@ ghostty_app_t ghostty_surface_app(ghostty_surface_t);
|
||||
bool ghostty_surface_transparent(ghostty_surface_t);
|
||||
bool ghostty_surface_needs_confirm_quit(ghostty_surface_t);
|
||||
void ghostty_surface_refresh(ghostty_surface_t);
|
||||
void ghostty_surface_draw(ghostty_surface_t);
|
||||
void ghostty_surface_set_content_scale(ghostty_surface_t, double, double);
|
||||
void ghostty_surface_set_focus(ghostty_surface_t, bool);
|
||||
void ghostty_surface_set_occlusion(ghostty_surface_t, bool);
|
||||
@ -541,6 +542,10 @@ uintptr_t ghostty_surface_pwd(ghostty_surface_t, char*, uintptr_t);
|
||||
bool ghostty_surface_has_selection(ghostty_surface_t);
|
||||
uintptr_t ghostty_surface_selection(ghostty_surface_t, char*, uintptr_t);
|
||||
|
||||
#ifdef __APPLE__
|
||||
void ghostty_surface_set_display_id(ghostty_surface_t, uint32_t);
|
||||
#endif
|
||||
|
||||
ghostty_inspector_t ghostty_surface_inspector(ghostty_surface_t);
|
||||
void ghostty_inspector_free(ghostty_surface_t);
|
||||
void ghostty_inspector_set_focus(ghostty_inspector_t, bool);
|
||||
|
@ -112,6 +112,11 @@ extension Ghostty {
|
||||
selector: #selector(onUpdateRendererHealth),
|
||||
name: Ghostty.Notification.didUpdateRendererHealth,
|
||||
object: self)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(windowDidChangeScreen),
|
||||
name: NSWindow.didChangeScreenNotification,
|
||||
object: nil)
|
||||
|
||||
// Setup our surface. This will also initialize all the terminal IO.
|
||||
let surface_cfg = baseConfig ?? SurfaceConfiguration()
|
||||
@ -322,6 +327,19 @@ extension Ghostty {
|
||||
healthy = health == GHOSTTY_RENDERER_HEALTH_OK
|
||||
}
|
||||
|
||||
@objc private func windowDidChangeScreen(notification: SwiftUI.Notification) {
|
||||
guard let window = self.window else { return }
|
||||
guard let object = notification.object as? NSWindow, window == object else { return }
|
||||
guard let screen = window.screen else { return }
|
||||
guard let surface = self.surface else { return }
|
||||
|
||||
// When the window changes screens, we need to update libghostty with the screen
|
||||
// ID. If vsync is enabled, this will be used with the CVDisplayLink to ensure
|
||||
// the proper refresh rate is going.
|
||||
let id = (screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as! NSNumber).uint32Value
|
||||
ghostty_surface_set_display_id(surface, id)
|
||||
}
|
||||
|
||||
// MARK: - NSView
|
||||
|
||||
override func viewDidMoveToWindow() {
|
||||
@ -439,7 +457,7 @@ extension Ghostty {
|
||||
|
||||
override func updateLayer() {
|
||||
guard let surface = self.surface else { return }
|
||||
ghostty_surface_refresh(surface);
|
||||
ghostty_surface_draw(surface);
|
||||
}
|
||||
|
||||
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
|
||||
|
@ -36,13 +36,25 @@ pub const DisplayLink = opaque {
|
||||
return c.CVDisplayLinkIsRunning(@ptrCast(self)) != 0;
|
||||
}
|
||||
|
||||
pub fn setCurrentCGDisplay(
|
||||
self: *DisplayLink,
|
||||
display_id: c.CGDirectDisplayID,
|
||||
) Error!void {
|
||||
if (c.CVDisplayLinkSetCurrentCGDisplay(
|
||||
@ptrCast(self),
|
||||
display_id,
|
||||
) != c.kCVReturnSuccess)
|
||||
return error.InvalidOperation;
|
||||
}
|
||||
|
||||
// Note: this purposely throws away a ton of arguments I didn't need.
|
||||
// It would be trivial to refactor this into Zig types and properly
|
||||
// pass this through.
|
||||
pub fn setOutputCallback(
|
||||
self: *DisplayLink,
|
||||
comptime callbackFn: *const fn (*DisplayLink, ?*anyopaque) void,
|
||||
userinfo: ?*anyopaque,
|
||||
comptime Userdata: type,
|
||||
comptime callbackFn: *const fn (*DisplayLink, ?*Userdata) void,
|
||||
userinfo: ?*Userdata,
|
||||
) Error!void {
|
||||
if (c.CVDisplayLinkSetOutputCallback(
|
||||
@ptrCast(self),
|
||||
@ -60,7 +72,10 @@ pub const DisplayLink = opaque {
|
||||
_ = flagsIn;
|
||||
_ = flagsOut;
|
||||
|
||||
callbackFn(displayLink, inner_userinfo);
|
||||
callbackFn(
|
||||
displayLink,
|
||||
@alignCast(@ptrCast(inner_userinfo)),
|
||||
);
|
||||
return c.kCVReturnSuccess;
|
||||
}
|
||||
}).callback),
|
||||
|
@ -553,6 +553,13 @@ pub fn close(self: *Surface) void {
|
||||
self.rt_surface.close(self.needsConfirmQuit());
|
||||
}
|
||||
|
||||
/// Forces the surface to render. This is useful for when the surface
|
||||
/// is in the middle of animation (such as a resize, etc.) or when
|
||||
/// the render timer is managed manually by the apprt.
|
||||
pub fn draw(self: *Surface) !void {
|
||||
try self.renderer_thread.draw_now.notify();
|
||||
}
|
||||
|
||||
/// Activate the inspector. This will begin collecting inspection data.
|
||||
/// This will not affect the GUI. The GUI must use performAction to
|
||||
/// show/hide the inspector UI.
|
||||
|
@ -653,6 +653,13 @@ pub const Surface = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn draw(self: *Surface) void {
|
||||
self.core_surface.draw() catch |err| {
|
||||
log.err("error in draw err={}", .{err});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn updateContentScale(self: *Surface, x: f64, y: f64) void {
|
||||
// We are an embedded API so the caller can send us all sorts of
|
||||
// garbage. We want to make sure that the float values are valid
|
||||
@ -1525,6 +1532,12 @@ pub const CAPI = struct {
|
||||
surface.refresh();
|
||||
}
|
||||
|
||||
/// Tell the surface that it needs to schedule a render
|
||||
/// call as soon as possible (NOW if possible).
|
||||
export fn ghostty_surface_draw(surface: *Surface) void {
|
||||
surface.draw();
|
||||
}
|
||||
|
||||
/// Update the size of a surface. This will trigger resize notifications
|
||||
/// to the pty and the renderer.
|
||||
export fn ghostty_surface_set_size(surface: *Surface, w: u32, h: u32) void {
|
||||
@ -1724,6 +1737,15 @@ pub const CAPI = struct {
|
||||
|
||||
// Inspector Metal APIs are only available on Apple systems
|
||||
usingnamespace if (builtin.target.isDarwin()) struct {
|
||||
export fn ghostty_surface_set_display_id(ptr: *Surface, display_id: u32) void {
|
||||
const surface = &ptr.core_surface;
|
||||
_ = surface.renderer_thread.mailbox.push(
|
||||
.{ .macos_display_id = display_id },
|
||||
.{ .forever = {} },
|
||||
);
|
||||
surface.renderer_thread.wakeup.notify() catch {};
|
||||
}
|
||||
|
||||
export fn ghostty_inspector_metal_init(ptr: *Inspector, device: objc.c.id) bool {
|
||||
return ptr.initMetal(objc.Object.fromId(device));
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ const objc = @import("objc");
|
||||
const macos = @import("macos");
|
||||
const imgui = @import("imgui");
|
||||
const glslang = @import("glslang");
|
||||
const xev = @import("xev");
|
||||
const apprt = @import("../apprt.zig");
|
||||
const configpkg = @import("../config.zig");
|
||||
const font = @import("../font/main.zig");
|
||||
@ -42,6 +43,11 @@ const InstanceBuffer = mtl_buffer.Buffer(u16);
|
||||
|
||||
const ImagePlacementList = std.ArrayListUnmanaged(mtl_image.Placement);
|
||||
|
||||
const DisplayLink = switch (builtin.os.tag) {
|
||||
.macos => *macos.video.DisplayLink,
|
||||
else => void,
|
||||
};
|
||||
|
||||
// Get native API access on certain platforms so we can do more customization.
|
||||
const glfwNative = glfw.Native(.{
|
||||
.cocoa = builtin.os.tag == .macos,
|
||||
@ -92,8 +98,10 @@ current_background_color: terminal.color.RGB,
|
||||
/// cells goes into a separate shader.
|
||||
cells: mtl_cell.Contents,
|
||||
|
||||
/// If this is true, we do a full cell rebuild on the next frame.
|
||||
cells_rebuild: bool = true,
|
||||
/// Set to true after rebuildCells is called. This can be used
|
||||
/// to determine if any possible changes have been made to the
|
||||
/// cells for the draw call.
|
||||
cells_rebuilt: bool = false,
|
||||
|
||||
/// The current GPU uniform values.
|
||||
uniforms: mtl_shaders.Uniforms,
|
||||
@ -115,6 +123,11 @@ shaders: Shaders, // Compiled shaders
|
||||
/// Metal objects
|
||||
layer: objc.Object, // CAMetalLayer
|
||||
|
||||
/// The CVDisplayLink used to drive the rendering loop in sync
|
||||
/// with the display. This is void on platforms that don't support
|
||||
/// a display link.
|
||||
display_link: ?DisplayLink = null,
|
||||
|
||||
/// Custom shader state. This is only set if we have custom shaders.
|
||||
custom_shader_state: ?CustomShaderState = null,
|
||||
|
||||
@ -545,9 +558,19 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
};
|
||||
};
|
||||
|
||||
const cells = try mtl_cell.Contents.init(alloc);
|
||||
var cells = try mtl_cell.Contents.init(alloc);
|
||||
errdefer cells.deinit(alloc);
|
||||
|
||||
const display_link: ?DisplayLink = null;
|
||||
// Note(mitchellh): if/when we ever want to add vsync, we can use this
|
||||
// display link to trigger rendering. We don't need this if vsync is off
|
||||
// because any change will trigger a redraw immediately.
|
||||
// const display_link: ?DisplayLink = switch (builtin.os.tag) {
|
||||
// .macos => try macos.video.DisplayLink.createWithActiveCGDisplays(),
|
||||
// else => null,
|
||||
// };
|
||||
errdefer if (display_link) |v| v.release();
|
||||
|
||||
return Metal{
|
||||
.alloc = alloc,
|
||||
.config = options.config,
|
||||
@ -581,6 +604,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
|
||||
// Metal stuff
|
||||
.layer = layer,
|
||||
.display_link = display_link,
|
||||
.custom_shader_state = custom_shader_state,
|
||||
.gpu_state = gpu_state,
|
||||
};
|
||||
@ -589,6 +613,13 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
||||
pub fn deinit(self: *Metal) void {
|
||||
self.gpu_state.deinit();
|
||||
|
||||
if (DisplayLink != void) {
|
||||
if (self.display_link) |display_link| {
|
||||
display_link.stop() catch {};
|
||||
display_link.release();
|
||||
}
|
||||
}
|
||||
|
||||
self.cells.deinit(self.alloc);
|
||||
|
||||
self.font_shaper.deinit();
|
||||
@ -635,6 +666,55 @@ pub fn threadExit(self: *const Metal) void {
|
||||
// Metal requires no per-thread state.
|
||||
}
|
||||
|
||||
/// Called by renderer.Thread when it starts the main loop.
|
||||
pub fn loopEnter(self: *Metal, thr: *renderer.Thread) !void {
|
||||
// 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 {};
|
||||
}
|
||||
|
||||
/// Called by renderer.Thread when it exits the main loop.
|
||||
pub fn loopExit(self: *const Metal) void {
|
||||
// If we don't support a display link we have no work to do.
|
||||
if (comptime DisplayLink == void) return;
|
||||
|
||||
// Stop our display link. If this fails its okay it just means
|
||||
// that we either never started it or the view its attached to
|
||||
// is gone which is fine.
|
||||
const display_link = self.display_link orelse return;
|
||||
display_link.stop() catch {};
|
||||
}
|
||||
|
||||
fn displayLinkCallback(
|
||||
_: *macos.video.DisplayLink,
|
||||
ud: ?*xev.Async,
|
||||
) void {
|
||||
const draw_now = ud orelse return;
|
||||
draw_now.notify() catch |err| {
|
||||
log.err("error notifying draw_now err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
/// Called when we get an updated display ID for our display link.
|
||||
pub fn setMacOSDisplayID(self: *Metal, id: u32) !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});
|
||||
};
|
||||
}
|
||||
|
||||
/// True if our renderer has animations so that a higher frequency
|
||||
/// timer is used.
|
||||
pub fn hasAnimations(self: *const Metal) bool {
|
||||
@ -659,6 +739,35 @@ fn gridSize(self: *Metal) ?renderer.GridSize {
|
||||
/// Must be called on the render thread.
|
||||
pub fn setFocus(self: *Metal, focus: bool) !void {
|
||||
self.focused = focus;
|
||||
|
||||
// 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 {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback when the window is visible or occluded.
|
||||
///
|
||||
/// Must be called on the render thread.
|
||||
pub fn setVisible(self: *Metal, visible: bool) void {
|
||||
// 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 {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the new font size.
|
||||
@ -720,6 +829,9 @@ pub fn updateFrame(
|
||||
preedit: ?renderer.State.Preedit,
|
||||
cursor_style: ?renderer.CursorStyle,
|
||||
color_palette: terminal.color.Palette,
|
||||
|
||||
/// If true, rebuild the full screen.
|
||||
full_rebuild: bool,
|
||||
};
|
||||
|
||||
// Update all our data as tightly as possible within the mutex.
|
||||
@ -790,17 +902,21 @@ pub fn updateFrame(
|
||||
|
||||
// If we have any terminal dirty flags set then we need to rebuild
|
||||
// the entire screen. This can be optimized in the future.
|
||||
const full_rebuild: bool = rebuild: {
|
||||
{
|
||||
const Int = @typeInfo(terminal.Terminal.Dirty).Struct.backing_integer.?;
|
||||
const v: Int = @bitCast(state.terminal.flags.dirty);
|
||||
if (v > 0) self.cells_rebuild = true;
|
||||
if (v > 0) break :rebuild true;
|
||||
}
|
||||
{
|
||||
const Int = @typeInfo(terminal.Screen.Dirty).Struct.backing_integer.?;
|
||||
const v: Int = @bitCast(state.terminal.screen.dirty);
|
||||
if (v > 0) self.cells_rebuild = true;
|
||||
if (v > 0) break :rebuild true;
|
||||
}
|
||||
|
||||
break :rebuild false;
|
||||
};
|
||||
|
||||
// Reset the dirty flags in the terminal and screen. We assume
|
||||
// that our rebuild will be successful since so we optimize for
|
||||
// success and reset while we hold the lock. This is much easier
|
||||
@ -825,6 +941,7 @@ pub fn updateFrame(
|
||||
.preedit = preedit,
|
||||
.cursor_style = cursor_style,
|
||||
.color_palette = state.terminal.color_palette.colors,
|
||||
.full_rebuild = full_rebuild,
|
||||
};
|
||||
};
|
||||
defer {
|
||||
@ -834,6 +951,7 @@ pub fn updateFrame(
|
||||
|
||||
// Build our GPU cells
|
||||
try self.rebuildCells(
|
||||
critical.full_rebuild,
|
||||
&critical.screen,
|
||||
critical.mouse,
|
||||
critical.preedit,
|
||||
@ -875,6 +993,12 @@ pub fn updateFrame(
|
||||
pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
||||
_ = surface;
|
||||
|
||||
// If our cells are not rebuilt, do a no-op draw. This means
|
||||
// that no possible new data can exist that would warrant a full
|
||||
// GPU update, our existing drawable is valid.
|
||||
if (!self.cells_rebuilt) return;
|
||||
self.cells_rebuilt = false;
|
||||
|
||||
// Wait for a frame to be available.
|
||||
const frame = self.gpu_state.nextFrame();
|
||||
errdefer self.gpu_state.releaseFrame();
|
||||
@ -1695,6 +1819,7 @@ pub fn setScreenSize(
|
||||
/// memory and doesn't touch the GPU.
|
||||
fn rebuildCells(
|
||||
self: *Metal,
|
||||
rebuild: bool,
|
||||
screen: *terminal.Screen,
|
||||
mouse: renderer.State.Mouse,
|
||||
preedit: ?renderer.State.Preedit,
|
||||
@ -1735,6 +1860,9 @@ fn rebuildCells(
|
||||
};
|
||||
} else null;
|
||||
|
||||
// If we are doing a full rebuild, then we clear the entire cell buffer.
|
||||
if (rebuild) self.cells.reset();
|
||||
|
||||
// Go row-by-row to build the cells. We go row by row because we do
|
||||
// font shaping by row. In the future, we will also do dirty tracking
|
||||
// by row.
|
||||
@ -1743,12 +1871,14 @@ fn rebuildCells(
|
||||
while (row_it.next()) |row| {
|
||||
y = y - 1;
|
||||
|
||||
if (!rebuild) {
|
||||
// Only rebuild if we are doing a full rebuild or this row is dirty.
|
||||
// if (row.isDirty()) std.log.warn("dirty y={}", .{y});
|
||||
if (!self.cells_rebuild and !row.isDirty()) continue;
|
||||
if (!row.isDirty()) continue;
|
||||
|
||||
// If we're rebuilding a row, then we always clear the cells
|
||||
// Clear the cells if the row is dirty
|
||||
self.cells.clear(y);
|
||||
}
|
||||
|
||||
// True if we want to do font shaping around the cursor. We want to
|
||||
// do font shaping as long as the cursor is enabled.
|
||||
@ -1892,8 +2022,8 @@ fn rebuildCells(
|
||||
}
|
||||
}
|
||||
|
||||
// We always mark our rebuild flag as false since we're done.
|
||||
self.cells_rebuild = false;
|
||||
// Update that our cells rebuilt
|
||||
self.cells_rebuilt = true;
|
||||
|
||||
// Log some things
|
||||
// log.debug("rebuildCells complete cached_runs={}", .{
|
||||
|
@ -579,6 +579,14 @@ pub fn setFocus(self: *OpenGL, focus: bool) !void {
|
||||
self.focused = focus;
|
||||
}
|
||||
|
||||
/// Callback when the window is visible or occluded.
|
||||
///
|
||||
/// Must be called on the render thread.
|
||||
pub fn setVisible(self: *OpenGL, visible: bool) void {
|
||||
_ = self;
|
||||
_ = visible;
|
||||
}
|
||||
|
||||
/// Set the new font size.
|
||||
///
|
||||
/// Must be called on the render thread.
|
||||
|
@ -14,7 +14,7 @@ const App = @import("../App.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const log = std.log.scoped(.renderer_thread);
|
||||
|
||||
const DRAW_INTERVAL = 33; // 30 FPS
|
||||
const DRAW_INTERVAL = 8; // 120 FPS
|
||||
const CURSOR_BLINK_INTERVAL = 600;
|
||||
|
||||
/// The type used for sending messages to the IO thread. For now this is
|
||||
@ -50,6 +50,11 @@ draw_h: xev.Timer,
|
||||
draw_c: xev.Completion = .{},
|
||||
draw_active: bool = false,
|
||||
|
||||
/// This async is used to force a draw immediately. This does not
|
||||
/// coalesce like the wakeup does.
|
||||
draw_now: xev.Async,
|
||||
draw_now_c: xev.Completion = .{},
|
||||
|
||||
/// The timer used for cursor blinking
|
||||
cursor_h: xev.Timer,
|
||||
cursor_c: xev.Completion = .{},
|
||||
@ -129,6 +134,10 @@ pub fn init(
|
||||
var draw_h = try xev.Timer.init();
|
||||
errdefer draw_h.deinit();
|
||||
|
||||
// Draw now async, see comments.
|
||||
var draw_now = try xev.Async.init();
|
||||
errdefer draw_now.deinit();
|
||||
|
||||
// Setup a timer for blinking the cursor
|
||||
var cursor_timer = try xev.Timer.init();
|
||||
errdefer cursor_timer.deinit();
|
||||
@ -137,7 +146,7 @@ pub fn init(
|
||||
var mailbox = try Mailbox.create(alloc);
|
||||
errdefer mailbox.destroy(alloc);
|
||||
|
||||
return Thread{
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.config = DerivedConfig.init(config),
|
||||
.loop = loop,
|
||||
@ -145,6 +154,7 @@ pub fn init(
|
||||
.stop = stop_h,
|
||||
.render_h = render_h,
|
||||
.draw_h = draw_h,
|
||||
.draw_now = draw_now,
|
||||
.cursor_h = cursor_timer,
|
||||
.surface = surface,
|
||||
.renderer = renderer_impl,
|
||||
@ -161,6 +171,7 @@ pub fn deinit(self: *Thread) void {
|
||||
self.wakeup.deinit();
|
||||
self.render_h.deinit();
|
||||
self.draw_h.deinit();
|
||||
self.draw_now.deinit();
|
||||
self.cursor_h.deinit();
|
||||
self.loop.deinit();
|
||||
|
||||
@ -180,6 +191,11 @@ pub fn threadMain(self: *Thread) void {
|
||||
fn threadMain_(self: *Thread) !void {
|
||||
defer log.debug("renderer thread exited", .{});
|
||||
|
||||
// Run our loop start/end callbacks if the renderer cares.
|
||||
const has_loop = @hasDecl(renderer.Renderer, "loopEnter");
|
||||
if (has_loop) try self.renderer.loopEnter(self);
|
||||
defer if (has_loop) self.renderer.loopExit();
|
||||
|
||||
// Run our thread start/end callbacks. This is important because some
|
||||
// renderers have to do per-thread setup. For example, OpenGL has to set
|
||||
// some thread-local state since that is how it works.
|
||||
@ -189,6 +205,7 @@ fn threadMain_(self: *Thread) !void {
|
||||
// Start the async handlers
|
||||
self.wakeup.wait(&self.loop, &self.wakeup_c, Thread, self, wakeupCallback);
|
||||
self.stop.wait(&self.loop, &self.stop_c, Thread, self, stopCallback);
|
||||
self.draw_now.wait(&self.loop, &self.draw_now_c, Thread, self, drawNowCallback);
|
||||
|
||||
// Send an initial wakeup message so that we render right away.
|
||||
try self.wakeup.notify();
|
||||
@ -255,6 +272,9 @@ fn drainMailbox(self: *Thread) !void {
|
||||
// still be happening.
|
||||
if (v) self.drawFrame();
|
||||
|
||||
// Notify the renderer so it can update any state.
|
||||
self.renderer.setVisible(v);
|
||||
|
||||
// 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
|
||||
@ -355,6 +375,12 @@ fn drainMailbox(self: *Thread) !void {
|
||||
},
|
||||
|
||||
.inspector => |v| self.flags.has_inspector = v,
|
||||
|
||||
.macos_display_id => |v| {
|
||||
if (@hasDecl(renderer.Renderer, "setMacOSDisplayID")) {
|
||||
try self.renderer.setMacOSDisplayID(v);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -402,18 +428,43 @@ fn wakeupCallback(
|
||||
t.drainMailbox() catch |err|
|
||||
log.err("error draining mailbox err={}", .{err});
|
||||
|
||||
// If the timer is already active then we don't have to do anything.
|
||||
if (t.render_c.state() == .active) return .rearm;
|
||||
// Render immediately
|
||||
_ = renderCallback(t, undefined, undefined, {});
|
||||
|
||||
// Timer is not active, let's start it
|
||||
t.render_h.run(
|
||||
&t.loop,
|
||||
&t.render_c,
|
||||
10,
|
||||
Thread,
|
||||
t,
|
||||
renderCallback,
|
||||
);
|
||||
// The below is not used anymore but if we ever want to introduce
|
||||
// a configuration to introduce a delay to coalesce renders, we can
|
||||
// use this.
|
||||
//
|
||||
// // If the timer is already active then we don't have to do anything.
|
||||
// if (t.render_c.state() == .active) return .rearm;
|
||||
//
|
||||
// // Timer is not active, let's start it
|
||||
// t.render_h.run(
|
||||
// &t.loop,
|
||||
// &t.render_c,
|
||||
// 10,
|
||||
// Thread,
|
||||
// t,
|
||||
// renderCallback,
|
||||
// );
|
||||
|
||||
return .rearm;
|
||||
}
|
||||
|
||||
fn drawNowCallback(
|
||||
self_: ?*Thread,
|
||||
_: *xev.Loop,
|
||||
_: *xev.Completion,
|
||||
r: xev.Async.WaitError!void,
|
||||
) xev.CallbackAction {
|
||||
_ = r catch |err| {
|
||||
log.err("error in draw now err={}", .{err});
|
||||
return .rearm;
|
||||
};
|
||||
|
||||
// Draw immediately
|
||||
const t = self_.?;
|
||||
t.drawFrame();
|
||||
|
||||
return .rearm;
|
||||
}
|
||||
@ -468,7 +519,7 @@ fn renderCallback(
|
||||
) catch |err|
|
||||
log.warn("error rendering err={}", .{err});
|
||||
|
||||
// Draw
|
||||
// Draw immediately
|
||||
t.drawFrame();
|
||||
|
||||
return .disarm;
|
||||
|
@ -69,6 +69,9 @@ pub const Message = union(enum) {
|
||||
/// Activate or deactivate the inspector.
|
||||
inspector: bool,
|
||||
|
||||
/// The macOS display ID has changed for the window.
|
||||
macos_display_id: u32,
|
||||
|
||||
/// Initialize a change_config message.
|
||||
pub fn initChangeConfig(alloc: Allocator, config: *const configpkg.Config) !Message {
|
||||
const thread_ptr = try alloc.create(renderer.Thread.DerivedConfig);
|
||||
|
@ -124,6 +124,13 @@ pub const Contents = struct {
|
||||
self.text.shrinkAndFree(alloc, text_reserved_len);
|
||||
}
|
||||
|
||||
/// Reset the cell contents to an empty state without resizing.
|
||||
pub fn reset(self: *Contents) void {
|
||||
@memset(self.map, .{});
|
||||
self.bgs.clearRetainingCapacity();
|
||||
self.text.shrinkRetainingCapacity(text_reserved_len);
|
||||
}
|
||||
|
||||
/// Returns the slice of fg cell contents to sync with the GPU.
|
||||
pub fn fgCells(self: *const Contents) []const mtl_shaders.CellText {
|
||||
const start: usize = if (self.cursor) 0 else 1;
|
||||
|
Reference in New Issue
Block a user