mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Merge pull request #2833 from ghostty-org/push-wrstnvxstmpx
renderer: set QoS class of the renderer thread on macOS
This commit is contained in:
@ -62,6 +62,47 @@ pub fn appSupportDir(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const SetQosClassError = error{
|
||||||
|
// The thread can't have its QoS class changed usually because
|
||||||
|
// a different pthread API was called that makes it an invalid
|
||||||
|
// target.
|
||||||
|
ThreadIncompatible,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Set the QoS class of the running thread.
|
||||||
|
///
|
||||||
|
/// https://developer.apple.com/documentation/apple-silicon/tuning-your-code-s-performance-for-apple-silicon?preferredLanguage=occ
|
||||||
|
pub fn setQosClass(class: QosClass) !void {
|
||||||
|
return switch (std.posix.errno(pthread_set_qos_class_self_np(
|
||||||
|
class,
|
||||||
|
0,
|
||||||
|
))) {
|
||||||
|
.SUCCESS => {},
|
||||||
|
.PERM => error.ThreadIncompatible,
|
||||||
|
|
||||||
|
// EPERM is the only known error that can happen based on
|
||||||
|
// the man pages for pthread_set_qos_class_self_np. I haven't
|
||||||
|
// checked the XNU source code to see if there are other
|
||||||
|
// possible errors.
|
||||||
|
else => @panic("unexpected pthread_set_qos_class_self_np error"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html#//apple_ref/doc/uid/TP40013929-CH35-SW1
|
||||||
|
pub const QosClass = enum(c_uint) {
|
||||||
|
user_interactive = 0x21,
|
||||||
|
user_initiated = 0x19,
|
||||||
|
default = 0x15,
|
||||||
|
utility = 0x11,
|
||||||
|
background = 0x09,
|
||||||
|
unspecified = 0x00,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "c" fn pthread_set_qos_class_self_np(
|
||||||
|
qos_class: QosClass,
|
||||||
|
relative_priority: c_int,
|
||||||
|
) c_int;
|
||||||
|
|
||||||
pub const NSOperatingSystemVersion = extern struct {
|
pub const NSOperatingSystemVersion = extern struct {
|
||||||
major: i64,
|
major: i64,
|
||||||
minor: i64,
|
minor: i64,
|
||||||
|
@ -4,8 +4,10 @@ pub const Thread = @This();
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const assert = std.debug.assert;
|
||||||
const xev = @import("xev");
|
const xev = @import("xev");
|
||||||
const crash = @import("../crash/main.zig");
|
const crash = @import("../crash/main.zig");
|
||||||
|
const internal_os = @import("../os/main.zig");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const configpkg = @import("../config.zig");
|
const configpkg = @import("../config.zig");
|
||||||
@ -92,6 +94,10 @@ flags: packed struct {
|
|||||||
/// This is true when the view is visible. This is used to determine
|
/// This is true when the view is visible. This is used to determine
|
||||||
/// if we should be rendering or not.
|
/// if we should be rendering or not.
|
||||||
visible: bool = true,
|
visible: bool = true,
|
||||||
|
|
||||||
|
/// This is true when the view is focused. This defaults to true
|
||||||
|
/// and it is up to the apprt to set the correct value.
|
||||||
|
focused: bool = true,
|
||||||
} = .{},
|
} = .{},
|
||||||
|
|
||||||
pub const DerivedConfig = struct {
|
pub const DerivedConfig = struct {
|
||||||
@ -199,6 +205,9 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
};
|
};
|
||||||
defer crash.sentry.thread_state = null;
|
defer crash.sentry.thread_state = null;
|
||||||
|
|
||||||
|
// Setup our thread QoS
|
||||||
|
self.setQosClass();
|
||||||
|
|
||||||
// Run our loop start/end callbacks if the renderer cares.
|
// Run our loop start/end callbacks if the renderer cares.
|
||||||
const has_loop = @hasDecl(renderer.Renderer, "loopEnter");
|
const has_loop = @hasDecl(renderer.Renderer, "loopEnter");
|
||||||
if (has_loop) try self.renderer.loopEnter(self);
|
if (has_loop) try self.renderer.loopEnter(self);
|
||||||
@ -237,6 +246,36 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
_ = try self.loop.run(.until_done);
|
_ = try self.loop.run(.until_done);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setQosClass(self: *const Thread) void {
|
||||||
|
// Thread QoS classes are only relevant on macOS.
|
||||||
|
if (comptime !builtin.target.isDarwin()) return;
|
||||||
|
|
||||||
|
const class: internal_os.macos.QosClass = class: {
|
||||||
|
// If we aren't visible (our view is fully occluded) then we
|
||||||
|
// always drop our rendering priority down because it's just
|
||||||
|
// mostly wasted work.
|
||||||
|
//
|
||||||
|
// The renderer itself should be doing this as well (for example
|
||||||
|
// Metal will stop our DisplayLink) but this also helps with
|
||||||
|
// general forced updates and CPU usage i.e. a rebuild cells call.
|
||||||
|
if (!self.flags.visible) break :class .utility;
|
||||||
|
|
||||||
|
// If we're not focused, but we're visible, then we set a higher
|
||||||
|
// than default priority because framerates still matter but it isn't
|
||||||
|
// as important as when we're focused.
|
||||||
|
if (!self.flags.focused) break :class .user_initiated;
|
||||||
|
|
||||||
|
// We are focused and visible, we are the definition of user interactive.
|
||||||
|
break :class .user_interactive;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (internal_os.macos.setQosClass(class)) {
|
||||||
|
log.debug("thread QoS class set class={}", .{class});
|
||||||
|
} else |err| {
|
||||||
|
log.warn("error setting QoS class err={}", .{err});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn startDrawTimer(self: *Thread) void {
|
fn startDrawTimer(self: *Thread) void {
|
||||||
// If our renderer doesn't support animations then we never run this.
|
// If our renderer doesn't support animations then we never run this.
|
||||||
if (!@hasDecl(renderer.Renderer, "hasAnimations")) return;
|
if (!@hasDecl(renderer.Renderer, "hasAnimations")) return;
|
||||||
@ -273,10 +312,16 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
switch (message) {
|
switch (message) {
|
||||||
.crash => @panic("crash request, crashing intentionally"),
|
.crash => @panic("crash request, crashing intentionally"),
|
||||||
|
|
||||||
.visible => |v| {
|
.visible => |v| visible: {
|
||||||
|
// If our state didn't change we do nothing.
|
||||||
|
if (self.flags.visible == v) break :visible;
|
||||||
|
|
||||||
// Set our visible state
|
// Set our visible state
|
||||||
self.flags.visible = v;
|
self.flags.visible = v;
|
||||||
|
|
||||||
|
// Visibility affects our QoS class
|
||||||
|
self.setQosClass();
|
||||||
|
|
||||||
// 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.
|
||||||
@ -293,7 +338,16 @@ fn drainMailbox(self: *Thread) !void {
|
|||||||
// check the visible state themselves to control their behavior.
|
// check the visible state themselves to control their behavior.
|
||||||
},
|
},
|
||||||
|
|
||||||
.focus => |v| {
|
.focus => |v| focus: {
|
||||||
|
// If our state didn't change we do nothing.
|
||||||
|
if (self.flags.focused == v) break :focus;
|
||||||
|
|
||||||
|
// Set our state
|
||||||
|
self.flags.focused = v;
|
||||||
|
|
||||||
|
// Focus affects our QoS class
|
||||||
|
self.setQosClass();
|
||||||
|
|
||||||
// Set it on the renderer
|
// Set it on the renderer
|
||||||
try self.renderer.setFocus(v);
|
try self.renderer.setFocus(v);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user