diff --git a/src/Surface.zig b/src/Surface.zig index aa75bb0e6..a25b2fe74 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -511,6 +511,7 @@ pub fn init( // Create the renderer thread var render_thread = try renderer.Thread.init( alloc, + config, rt_surface, &self.renderer, &self.renderer_state, @@ -831,23 +832,14 @@ fn changeConfig(self: *Surface, config: *const configpkg.Config) !void { // We need to store our configs in a heap-allocated pointer so that // our messages aren't huge. - var renderer_config_ptr = try self.alloc.create(Renderer.DerivedConfig); - errdefer self.alloc.destroy(renderer_config_ptr); + var renderer_message = try renderer.Message.initChangeConfig(self.alloc, config); + errdefer renderer_message.deinit(); var termio_config_ptr = try self.alloc.create(termio.Impl.DerivedConfig); errdefer self.alloc.destroy(termio_config_ptr); - - // Update our derived configurations for the renderer and termio, - // then send them a message to update. - renderer_config_ptr.* = try Renderer.DerivedConfig.init(self.alloc, config); - errdefer renderer_config_ptr.deinit(); termio_config_ptr.* = try termio.Impl.DerivedConfig.init(self.alloc, config); errdefer termio_config_ptr.deinit(); - _ = self.renderer_thread.mailbox.push(.{ - .change_config = .{ - .alloc = self.alloc, - .ptr = renderer_config_ptr, - }, - }, .{ .forever = {} }); + + _ = self.renderer_thread.mailbox.push(renderer_message, .{ .forever = {} }); _ = self.io_thread.mailbox.push(.{ .change_config = .{ .alloc = self.alloc, diff --git a/src/config.zig b/src/config.zig index 398f5fa92..78e033361 100644 --- a/src/config.zig +++ b/src/config.zig @@ -10,6 +10,7 @@ pub const url = @import("config/url.zig"); pub const CopyOnSelect = Config.CopyOnSelect; pub const Keybinds = Config.Keybinds; pub const MouseShiftCapture = Config.MouseShiftCapture; +pub const CustomShaderAnimation = Config.CustomShaderAnimation; pub const NonNativeFullscreen = Config.NonNativeFullscreen; pub const OptionAsAlt = Config.OptionAsAlt; pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures; diff --git a/src/config/Config.zig b/src/config/Config.zig index 9881580b9..3f093865f 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -815,15 +815,21 @@ keybind: Keybinds = .{}, /// If true (default), the focused terminal surface will run an animation /// loop when custom shaders are used. This uses slightly more CPU (generally /// less than 10%) but allows the shader to animate. This only runs if there -/// are custom shaders. +/// are custom shaders and the terminal is focused. /// /// If this is set to false, the terminal and custom shader will only render /// when the terminal is updated. This is more efficient but the shader will /// not animate. /// +/// This can also be set to "always", which will always run the animation +/// loop regardless of whether the terminal is focused or not. The animation +/// loop will still only run when custom shaders are used. Note that this +/// will use more CPU per terminal surface and can become quite expensive +/// depending on the shader and your terminal usage. +/// /// This value can be changed at runtime and will affect all currently /// open terminals. -@"custom-shader-animation": bool = true, +@"custom-shader-animation": CustomShaderAnimation = .true, /// If anything other than false, fullscreen mode on macOS will not use the /// native fullscreen, but make the window fullscreen without animations and @@ -2079,6 +2085,15 @@ fn equalField(comptime T: type, old: T, new: T) bool { } } +/// Valid values for custom-shader-animation +/// c_int because it needs to be extern compatible +/// If this is changed, you must also update ghostty.h +pub const CustomShaderAnimation = enum(c_int) { + false, + true, + always, +}; + /// Valid values for macos-non-native-fullscreen /// c_int because it needs to be extern compatible /// If this is changed, you must also update ghostty.h diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 9c17702e0..9e9fbaea4 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -155,7 +155,6 @@ pub const DerivedConfig = struct { invert_selection_fg_bg: bool, min_contrast: f32, custom_shaders: std.ArrayListUnmanaged([:0]const u8), - custom_shader_animation: bool, links: link.Set, pub fn init( @@ -218,7 +217,6 @@ pub const DerivedConfig = struct { null, .custom_shaders = custom_shaders, - .custom_shader_animation = config.@"custom-shader-animation", .links = links, .arena = arena, @@ -488,8 +486,7 @@ pub fn threadExit(self: *const Metal) void { /// True if our renderer has animations so that a higher frequency /// timer is used. pub fn hasAnimations(self: *const Metal) bool { - return self.custom_shader_state != null and - self.config.custom_shader_animation; + return self.custom_shader_state != null; } /// Returns the grid size for a given screen size. This is safe to call diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 66fd966ee..af6910c0a 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -252,7 +252,6 @@ pub const DerivedConfig = struct { invert_selection_fg_bg: bool, min_contrast: f32, custom_shaders: std.ArrayListUnmanaged([:0]const u8), - custom_shader_animation: bool, links: link.Set, pub fn init( @@ -315,7 +314,6 @@ pub const DerivedConfig = struct { null, .custom_shaders = custom_shaders, - .custom_shader_animation = config.@"custom-shader-animation", .links = links, .arena = arena, @@ -558,7 +556,7 @@ pub fn threadExit(self: *const OpenGL) void { /// timer is used. pub fn hasAnimations(self: *const OpenGL) bool { const state = self.gl_state orelse return false; - return state.custom != null and self.config.custom_shader_animation; + return state.custom != null; } /// Callback when the focus changes for the terminal this is rendering. diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index 156d6cd6d..178dca14e 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -7,6 +7,7 @@ const builtin = @import("builtin"); const xev = @import("xev"); const renderer = @import("../renderer.zig"); const apprt = @import("../apprt.zig"); +const configpkg = @import("../config.zig"); const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue; const tracy = @import("tracy"); const trace = tracy.trace; @@ -72,6 +73,9 @@ mailbox: *Mailbox, /// Mailbox to send messages to the app thread app_mailbox: App.Mailbox, +/// Configuration we need derived from the main config. +config: DerivedConfig, + flags: packed struct { /// This is true when a blinking cursor should be visible and false /// when it should not be visible. This is toggled on a timer by the @@ -82,11 +86,22 @@ flags: packed struct { has_inspector: bool = false, } = .{}, +pub const DerivedConfig = struct { + custom_shader_animation: configpkg.CustomShaderAnimation, + + pub fn init(config: *const configpkg.Config) DerivedConfig { + return .{ + .custom_shader_animation = config.@"custom-shader-animation", + }; + } +}; + /// Initialize the thread. This does not START the thread. This only sets /// up all the internal state necessary prior to starting the thread. It /// is up to the caller to start the thread with the threadMain entrypoint. pub fn init( alloc: Allocator, + config: *const configpkg.Config, surface: *apprt.Surface, renderer_impl: *renderer.Renderer, state: *renderer.State, @@ -122,6 +137,7 @@ pub fn init( return Thread{ .alloc = alloc, + .config = DerivedConfig.init(config), .loop = loop, .wakeup = wakeup_h, .stop = stop_h, @@ -199,6 +215,7 @@ fn startDrawTimer(self: *Thread) void { // If our renderer doesn't suppoort animations then we never run this. if (!@hasDecl(renderer.Renderer, "hasAnimations")) return; if (!self.renderer.hasAnimations()) return; + if (self.config.custom_shader_animation == .false) return; // Set our active state so it knows we're running. We set this before // even checking the active state in case we have a pending shutdown. @@ -236,8 +253,10 @@ fn drainMailbox(self: *Thread) !void { try self.renderer.setFocus(v); if (!v) { - // Stop the draw timer - self.stopDrawTimer(); + if (self.config.custom_shader_animation != .always) { + // Stop the draw timer + self.stopDrawTimer(); + } // If we're not focused, then we stop the cursor blink if (self.cursor_c.state() == .active and @@ -308,8 +327,9 @@ fn drainMailbox(self: *Thread) !void { }, .change_config => |config| { - defer config.alloc.destroy(config.ptr); - try self.renderer.changeConfig(config.ptr); + defer message.deinit(); + try self.changeConfig(config.thread); + try self.renderer.changeConfig(config.impl); // Stop and start the draw timer to capture the new // hasAnimations value. @@ -322,6 +342,10 @@ fn drainMailbox(self: *Thread) !void { } } +fn changeConfig(self: *Thread, config: *const DerivedConfig) !void { + self.config = config.*; +} + fn wakeupCallback( self_: ?*Thread, _: *xev.Loop, diff --git a/src/renderer/message.zig b/src/renderer/message.zig index 3278a2c1c..73faa0ad7 100644 --- a/src/renderer/message.zig +++ b/src/renderer/message.zig @@ -1,6 +1,7 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const configpkg = @import("../config.zig"); const font = @import("../font/main.zig"); const renderer = @import("../renderer.zig"); const terminal = @import("../terminal/main.zig"); @@ -45,9 +46,42 @@ pub const Message = union(enum) { /// The derived configuration to update the renderer with. change_config: struct { alloc: Allocator, - ptr: *renderer.Renderer.DerivedConfig, + thread: *renderer.Thread.DerivedConfig, + impl: *renderer.Renderer.DerivedConfig, }, /// Activate or deactivate the inspector. inspector: bool, + + /// Initialize a change_config message. + pub fn initChangeConfig(alloc: Allocator, config: *const configpkg.Config) !Message { + const thread_ptr = try alloc.create(renderer.Thread.DerivedConfig); + errdefer alloc.destroy(thread_ptr); + const config_ptr = try alloc.create(renderer.Renderer.DerivedConfig); + errdefer alloc.destroy(config_ptr); + + thread_ptr.* = renderer.Thread.DerivedConfig.init(config); + config_ptr.* = try renderer.Renderer.DerivedConfig.init(alloc, config); + errdefer config_ptr.deinit(); + + return .{ + .change_config = .{ + .alloc = alloc, + .thread = thread_ptr, + .impl = config_ptr, + }, + }; + } + + pub fn deinit(self: *const Message) void { + switch (self.*) { + .change_config => |v| { + v.impl.deinit(); + v.alloc.destroy(v.impl); + v.alloc.destroy(v.thread); + }, + + else => {}, + } + } };