fix: prevent flicker while shrinking screen by eliminating thread race

Before this fix, if vsync was on the GPU cells buffer could be cleared
for a frame while resizing the terminal down. This was due to the fact
that the surface sent messages for the resize to both the renderer and
the IO thread. If the renderer thread was processed first then the GPU
cells buffer(s) would be cleared and not rebuilt, because the terminal
state would be larger than the GPU cell buffers causing updateFrame to
bail out early, leaving empty cell buffers.

This fixes the problem by changing the origin of the renderer's resize
message to be the IO thread, only after properly updating the terminal
state, to avoid clearing the GPU cells buffers at a time they can't be
successfully rebuilt.
This commit is contained in:
Qwerasd
2024-08-14 19:35:52 -04:00
parent 93c377c6a1
commit 7929e0bc09
4 changed files with 16 additions and 17 deletions

View File

@ -1197,15 +1197,6 @@ fn resize(self: *Surface, size: renderer.ScreenSize) !void {
// Save our screen size
self.screen_size = size;
// Mail the renderer so that it can update the GPU and re-render
_ = self.renderer_thread.mailbox.push(.{
.resize = .{
.screen_size = self.screen_size,
.padding = self.padding,
},
}, .{ .forever = {} });
try self.queueRender();
// Recalculate our grid size. Because Ghostty supports fluid resizing,
// its possible the grid doesn't change at all even if the screen size changes.
// We have to update the IO thread no matter what because we send

View File

@ -2012,11 +2012,13 @@ pub fn setScreenSize(
.cursor_color = old.cursor_color,
};
// Reset our cell contents.
// Reset our cell contents if our grid size has changed.
if (!self.cells.size.equals(grid_size)) {
try self.cells.resize(self.alloc, grid_size);
// Reset our viewport to force a rebuild
self.cells_viewport = null;
}
// If we have custom shaders then we update the state
if (self.custom_shader_state) |*state| {

View File

@ -480,7 +480,7 @@ fn drawCallback(
r: xev.Timer.RunError!void,
) xev.CallbackAction {
_ = r catch unreachable;
const t = self_ orelse {
const t: *Thread = self_ orelse {
// This shouldn't happen so we log it.
log.warn("render callback fired without data set", .{});
return .disarm;
@ -504,7 +504,7 @@ fn renderCallback(
r: xev.Timer.RunError!void,
) xev.CallbackAction {
_ = r catch unreachable;
const t = self_ orelse {
const t: *Thread = self_ orelse {
// This shouldn't happen so we log it.
log.warn("render callback fired without data set", .{});
return .disarm;
@ -543,7 +543,7 @@ fn cursorTimerCallback(
},
};
const t = self_ orelse {
const t: *Thread = self_ orelse {
// This shouldn't happen so we log it.
log.warn("render callback fired without data set", .{});
return .disarm;

View File

@ -378,7 +378,13 @@ pub fn resize(
// immediately for a resize. This is allowed by the spec.
self.terminal.modes.set(.synchronized_output, false);
// Wake up our renderer so any changes will be shown asap
// Mail the renderer so that it can update the GPU and re-render
_ = self.renderer_mailbox.push(.{
.resize = .{
.screen_size = screen_size,
.padding = padding,
},
}, .{ .forever = {} });
self.renderer_wakeup.notify() catch {};
// If we have size reporting enabled we need to send a report.