mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-22 19:56:08 +03:00
Merge pull request #1728 from mitchellh/vsync
macOS: add window-vsync option for vsync
This commit is contained in:
@ -615,6 +615,19 @@ keybind: Keybinds = .{},
|
|||||||
/// given a certain viewport size and grid cell size.
|
/// given a certain viewport size and grid cell size.
|
||||||
@"window-padding-balance": bool = false,
|
@"window-padding-balance": bool = false,
|
||||||
|
|
||||||
|
/// Synchronize rendering with the screen refresh rate. If true, this will
|
||||||
|
/// minimize tearing and align redraws with the screen but may cause input
|
||||||
|
/// latency. If false, this will maximize redraw frequency but may cause tearing,
|
||||||
|
/// and under heavy load may use more CPU and power.
|
||||||
|
///
|
||||||
|
/// This defaults to false because out of the box a lot of users prefer to
|
||||||
|
/// feel like the terminal is as responsive as possible.
|
||||||
|
///
|
||||||
|
/// Changing this value at runtime will only affect new terminals.
|
||||||
|
///
|
||||||
|
/// This setting is only supported currently on macOS.
|
||||||
|
@"window-vsync": bool = false,
|
||||||
|
|
||||||
/// If true, new windows and tabs will inherit the working directory of the
|
/// If true, new windows and tabs will inherit the working directory of the
|
||||||
/// previously focused window. If no window was previously focused, the default
|
/// previously focused window. If no window was previously focused, the default
|
||||||
/// working directory will be used (the `working-directory` option).
|
/// working directory will be used (the `working-directory` option).
|
||||||
|
@ -320,6 +320,7 @@ pub const DerivedConfig = struct {
|
|||||||
min_contrast: f32,
|
min_contrast: f32,
|
||||||
custom_shaders: std.ArrayListUnmanaged([:0]const u8),
|
custom_shaders: std.ArrayListUnmanaged([:0]const u8),
|
||||||
links: link.Set,
|
links: link.Set,
|
||||||
|
vsync: bool,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
alloc_gpa: Allocator,
|
alloc_gpa: Allocator,
|
||||||
@ -382,6 +383,7 @@ pub const DerivedConfig = struct {
|
|||||||
|
|
||||||
.custom_shaders = custom_shaders,
|
.custom_shaders = custom_shaders,
|
||||||
.links = links,
|
.links = links,
|
||||||
|
.vsync = config.@"window-vsync",
|
||||||
|
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
};
|
};
|
||||||
@ -473,7 +475,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
};
|
};
|
||||||
layer.setProperty("device", gpu_state.device.value);
|
layer.setProperty("device", gpu_state.device.value);
|
||||||
layer.setProperty("opaque", options.config.background_opacity >= 1);
|
layer.setProperty("opaque", options.config.background_opacity >= 1);
|
||||||
layer.setProperty("displaySyncEnabled", false); // disable v-sync
|
layer.setProperty("displaySyncEnabled", options.config.vsync);
|
||||||
|
|
||||||
// Make our view layer-backed with our Metal layer. On iOS views are
|
// Make our view layer-backed with our Metal layer. On iOS views are
|
||||||
// always layer backed so we don't need to do this. But on iOS the
|
// always layer backed so we don't need to do this. But on iOS the
|
||||||
@ -561,14 +563,13 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
var cells = try mtl_cell.Contents.init(alloc);
|
var cells = try mtl_cell.Contents.init(alloc);
|
||||||
errdefer cells.deinit(alloc);
|
errdefer cells.deinit(alloc);
|
||||||
|
|
||||||
const display_link: ?DisplayLink = null;
|
const display_link: ?DisplayLink = switch (builtin.os.tag) {
|
||||||
// Note(mitchellh): if/when we ever want to add vsync, we can use this
|
.macos => if (options.config.vsync)
|
||||||
// display link to trigger rendering. We don't need this if vsync is off
|
try macos.video.DisplayLink.createWithActiveCGDisplays()
|
||||||
// because any change will trigger a redraw immediately.
|
else
|
||||||
// const display_link: ?DisplayLink = switch (builtin.os.tag) {
|
null,
|
||||||
// .macos => try macos.video.DisplayLink.createWithActiveCGDisplays(),
|
else => null,
|
||||||
// else => null,
|
};
|
||||||
// };
|
|
||||||
errdefer if (display_link) |v| v.release();
|
errdefer if (display_link) |v| v.release();
|
||||||
|
|
||||||
return Metal{
|
return Metal{
|
||||||
@ -721,6 +722,15 @@ pub fn hasAnimations(self: *const Metal) bool {
|
|||||||
return self.custom_shader_state != null;
|
return self.custom_shader_state != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if our renderer is using vsync. If true, the renderer or apprt
|
||||||
|
/// is responsible for triggering draw_now calls to the render thread. That
|
||||||
|
/// is the only way to trigger a drawFrame.
|
||||||
|
pub fn hasVsync(self: *const Metal) bool {
|
||||||
|
if (comptime DisplayLink == void) return false;
|
||||||
|
const display_link = self.display_link orelse return false;
|
||||||
|
return display_link.isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the grid size for a given screen size. This is safe to call
|
/// Returns the grid size for a given screen size. This is safe to call
|
||||||
/// on any thread.
|
/// on any thread.
|
||||||
fn gridSize(self: *Metal) ?renderer.GridSize {
|
fn gridSize(self: *Metal) ?renderer.GridSize {
|
||||||
|
@ -572,6 +572,14 @@ pub fn hasAnimations(self: *const OpenGL) bool {
|
|||||||
return state.custom != null;
|
return state.custom != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See Metal
|
||||||
|
pub fn hasVsync(self: *const OpenGL) bool {
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
// OpenGL currently never has vsync
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// Callback when the focus changes for the terminal this is rendering.
|
/// Callback when the focus changes for the terminal this is rendering.
|
||||||
///
|
///
|
||||||
/// Must be called on the render thread.
|
/// Must be called on the render thread.
|
||||||
|
@ -270,7 +270,7 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
// If we became visible then we immediately trigger a draw.
|
// If we became visible then we immediately trigger a draw.
|
||||||
// We don't need to update frame data because that should
|
// We don't need to update frame data because that should
|
||||||
// still be happening.
|
// still be happening.
|
||||||
if (v) self.drawFrame();
|
if (v) self.drawFrame(false);
|
||||||
|
|
||||||
// Notify the renderer so it can update any state.
|
// Notify the renderer so it can update any state.
|
||||||
self.renderer.setVisible(v);
|
self.renderer.setVisible(v);
|
||||||
@ -391,10 +391,14 @@ fn changeConfig(self: *Thread, config: *const DerivedConfig) !void {
|
|||||||
|
|
||||||
/// Trigger a draw. This will not update frame data or anything, it will
|
/// Trigger a draw. This will not update frame data or anything, it will
|
||||||
/// just trigger a draw/paint.
|
/// just trigger a draw/paint.
|
||||||
fn drawFrame(self: *Thread) void {
|
fn drawFrame(self: *Thread, now: bool) void {
|
||||||
// If we're invisible, we do not draw.
|
// If we're invisible, we do not draw.
|
||||||
if (!self.flags.visible) return;
|
if (!self.flags.visible) return;
|
||||||
|
|
||||||
|
// If the renderer is managing a vsync on its own, we only draw
|
||||||
|
// when we're forced to via now.
|
||||||
|
if (!now and self.renderer.hasVsync()) return;
|
||||||
|
|
||||||
// If we're doing single-threaded GPU calls then we just wake up the
|
// If we're doing single-threaded GPU calls then we just wake up the
|
||||||
// app thread to redraw at this point.
|
// app thread to redraw at this point.
|
||||||
if (renderer.Renderer == renderer.OpenGL and
|
if (renderer.Renderer == renderer.OpenGL and
|
||||||
@ -464,7 +468,7 @@ fn drawNowCallback(
|
|||||||
|
|
||||||
// Draw immediately
|
// Draw immediately
|
||||||
const t = self_.?;
|
const t = self_.?;
|
||||||
t.drawFrame();
|
t.drawFrame(true);
|
||||||
|
|
||||||
return .rearm;
|
return .rearm;
|
||||||
}
|
}
|
||||||
@ -483,7 +487,7 @@ fn drawCallback(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
t.drawFrame();
|
t.drawFrame(false);
|
||||||
|
|
||||||
// Only continue if we're still active
|
// Only continue if we're still active
|
||||||
if (t.draw_active) {
|
if (t.draw_active) {
|
||||||
@ -518,9 +522,7 @@ fn renderCallback(
|
|||||||
t.flags.cursor_blink_visible,
|
t.flags.cursor_blink_visible,
|
||||||
) catch |err|
|
) catch |err|
|
||||||
log.warn("error rendering err={}", .{err});
|
log.warn("error rendering err={}", .{err});
|
||||||
|
t.drawFrame(false);
|
||||||
// Draw immediately
|
|
||||||
t.drawFrame();
|
|
||||||
|
|
||||||
return .disarm;
|
return .disarm;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user