mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
remove render timer from window
This commit is contained in:
@ -25,8 +25,6 @@ const Config = @import("config.zig").Config;
|
||||
const input = @import("input.zig");
|
||||
const DevMode = @import("DevMode.zig");
|
||||
|
||||
const RenderTimer = max_timer.MaxTimer(renderTimerCallback);
|
||||
|
||||
const log = std.log.scoped(.window);
|
||||
|
||||
// The preallocation size for the write request pool. This should be big
|
||||
@ -83,9 +81,6 @@ terminal_stream: terminal.Stream(*Window),
|
||||
/// Cursor state.
|
||||
terminal_cursor: Cursor,
|
||||
|
||||
/// Render at least 60fps.
|
||||
render_timer: RenderTimer,
|
||||
|
||||
/// The dimensions of the grid in rows and columns.
|
||||
grid_size: renderer.GridSize,
|
||||
|
||||
@ -500,7 +495,6 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
||||
.terminal_stream = .{ .handler = self },
|
||||
.terminal_cursor = .{ .timer = timer },
|
||||
.grid_size = grid_size,
|
||||
.render_timer = try RenderTimer.init(loop, self, 6, 12),
|
||||
.pty_stream = stream,
|
||||
.config = config,
|
||||
.bg_r = @intToFloat(f32, config.background.r) / 255.0,
|
||||
@ -616,8 +610,6 @@ pub fn destroy(self: *Window) void {
|
||||
}
|
||||
}).callback);
|
||||
|
||||
self.render_timer.deinit();
|
||||
|
||||
// We have to dealloc our window in the close callback because
|
||||
// we can't free some of the memory associated with the window
|
||||
// until the stream is closed.
|
||||
@ -668,6 +660,13 @@ fn queueWrite(self: *Window, data: []const u8) !void {
|
||||
}
|
||||
}
|
||||
|
||||
/// This queues a render operation with the renderer thread. The render
|
||||
/// isn't guaranteed to happen immediately but it will happen as soon as
|
||||
/// practical.
|
||||
fn queueRender(self: *const Window) !void {
|
||||
try self.renderer_thread.wakeup.send();
|
||||
}
|
||||
|
||||
/// The cursor position from glfw directly is in screen coordinates but
|
||||
/// all our internal state works in pixels.
|
||||
fn cursorPosToPixels(self: Window, pos: glfw.Window.CursorPos) glfw.Window.CursorPos {
|
||||
@ -719,7 +718,7 @@ fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
|
||||
const win = window.getUserPointer(Window) orelse return;
|
||||
|
||||
// Resize usually forces a redraw
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render timer in sizeCallback err={}", .{err});
|
||||
|
||||
// Recalculate our grid size
|
||||
@ -758,7 +757,7 @@ fn charCallback(window: glfw.Window, codepoint: u21) void {
|
||||
// If the event was handled by imgui, ignore it.
|
||||
if (imgui.IO.get()) |io| {
|
||||
if (io.cval().WantCaptureKeyboard) {
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render timer err={}", .{err});
|
||||
}
|
||||
} else |_| {}
|
||||
@ -773,7 +772,7 @@ fn charCallback(window: glfw.Window, codepoint: u21) void {
|
||||
// Anytime is character is created, we have to clear the selection
|
||||
if (win.terminal.selection != null) {
|
||||
win.terminal.selection = null;
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render in charCallback err={}", .{err});
|
||||
}
|
||||
|
||||
@ -781,7 +780,7 @@ fn charCallback(window: glfw.Window, codepoint: u21) void {
|
||||
// TODO: detect if we're at the bottom to avoid the render call here.
|
||||
win.terminal.scrollViewport(.{ .bottom = {} }) catch |err|
|
||||
log.err("error scrolling viewport err={}", .{err});
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render in charCallback err={}", .{err});
|
||||
|
||||
// Write the character to the pty
|
||||
@ -806,7 +805,7 @@ fn keyCallback(
|
||||
// If the event was handled by imgui, ignore it.
|
||||
if (imgui.IO.get()) |io| {
|
||||
if (io.cval().WantCaptureKeyboard) {
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render timer err={}", .{err});
|
||||
}
|
||||
} else |_| {}
|
||||
@ -927,7 +926,7 @@ fn keyCallback(
|
||||
|
||||
.toggle_dev_mode => if (DevMode.enabled) {
|
||||
DevMode.instance.visible = !DevMode.instance.visible;
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
win.queueRender() catch unreachable;
|
||||
} else log.warn("dev mode was not compiled into this binary", .{}),
|
||||
}
|
||||
|
||||
@ -1004,7 +1003,7 @@ fn focusCallback(window: glfw.Window, focused: bool) void {
|
||||
// We have to schedule a render because no matter what we're changing
|
||||
// the cursor. If we're focused its reappearing, if we're not then
|
||||
// its changing to hollow and not blinking.
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
win.queueRender() catch unreachable;
|
||||
|
||||
if (focused)
|
||||
win.terminal_cursor.startTimer() catch unreachable
|
||||
@ -1024,7 +1023,7 @@ fn refreshCallback(window: glfw.Window) void {
|
||||
const win = window.getUserPointer(Window) orelse return;
|
||||
|
||||
// The point of this callback is to schedule a render, so do that.
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
win.queueRender() catch unreachable;
|
||||
}
|
||||
|
||||
fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void {
|
||||
@ -1036,7 +1035,7 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void {
|
||||
// If our dev mode window is visible then we always schedule a render on
|
||||
// cursor move because the cursor might touch our windows.
|
||||
if (DevMode.enabled and DevMode.instance.visible) {
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render timer err={}", .{err});
|
||||
|
||||
// If the mouse event was handled by imgui, ignore it.
|
||||
@ -1071,7 +1070,7 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void {
|
||||
// Schedule render since scrolling usually does something.
|
||||
// TODO(perf): we can only schedule render if we know scrolling
|
||||
// did something
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
win.queueRender() catch unreachable;
|
||||
}
|
||||
|
||||
/// The type of action to report for a mouse event.
|
||||
@ -1256,7 +1255,7 @@ fn mouseButtonCallback(
|
||||
// If our dev mode window is visible then we always schedule a render on
|
||||
// cursor move because the cursor might touch our windows.
|
||||
if (DevMode.enabled and DevMode.instance.visible) {
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render timer in cursorPosCallback err={}", .{err});
|
||||
|
||||
// If the mouse event was handled by imgui, ignore it.
|
||||
@ -1326,7 +1325,7 @@ fn mouseButtonCallback(
|
||||
// Selection is always cleared
|
||||
if (win.terminal.selection != null) {
|
||||
win.terminal.selection = null;
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render in mouseButtinCallback err={}", .{err});
|
||||
}
|
||||
}
|
||||
@ -1345,7 +1344,7 @@ fn cursorPosCallback(
|
||||
// If our dev mode window is visible then we always schedule a render on
|
||||
// cursor move because the cursor might touch our windows.
|
||||
if (DevMode.enabled and DevMode.instance.visible) {
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render timer in cursorPosCallback err={}", .{err});
|
||||
|
||||
// If the mouse event was handled by imgui, ignore it.
|
||||
@ -1380,7 +1379,7 @@ fn cursorPosCallback(
|
||||
if (win.mouse.click_state[@enumToInt(input.MouseButton.left)] != .press) return;
|
||||
|
||||
// All roads lead to requiring a re-render at this pont.
|
||||
win.render_timer.schedule() catch |err|
|
||||
win.queueRender() catch |err|
|
||||
log.err("error scheduling render timer in cursorPosCallback err={}", .{err});
|
||||
|
||||
// Convert to pixels from screen coords
|
||||
@ -1533,7 +1532,7 @@ fn cursorTimerCallback(t: *libuv.Timer) void {
|
||||
|
||||
// Swap blink state and schedule a render
|
||||
win.renderer_state.cursor.blink = !win.renderer_state.cursor.blink;
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
win.queueRender() catch unreachable;
|
||||
}
|
||||
|
||||
fn ttyReadAlloc(t: *libuv.Tty, size: usize) ?[]u8 {
|
||||
@ -1582,7 +1581,7 @@ fn ttyRead(t: *libuv.Tty, n: isize, buf: []const u8) void {
|
||||
}
|
||||
|
||||
// Schedule a render
|
||||
win.render_timer.schedule() catch unreachable;
|
||||
win.queueRender() catch unreachable;
|
||||
|
||||
// Process the terminal data. This is an extremely hot part of the
|
||||
// terminal emulator, so we do some abstraction leakage to avoid
|
||||
@ -1639,21 +1638,6 @@ fn ttyWrite(req: *libuv.WriteReq, status: i32) void {
|
||||
//log.info("WROTE: {d}", .{status});
|
||||
}
|
||||
|
||||
fn renderTimerCallback(t: *libuv.Timer) void {
|
||||
const tracy = trace(@src());
|
||||
tracy.color(0x006E7F); // blue-ish
|
||||
defer tracy.end();
|
||||
|
||||
const win = t.getData(Window).?;
|
||||
|
||||
// Trigger a render
|
||||
win.renderer_thread.wakeup.send() catch |err|
|
||||
log.err("error sending render notification err={}", .{err});
|
||||
|
||||
// Record our run
|
||||
win.render_timer.tick();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Stream Callbacks
|
||||
|
||||
@ -1722,7 +1706,7 @@ pub fn eraseDisplay(self: *Window, mode: terminal.EraseDisplay) !void {
|
||||
if (mode == .complete) {
|
||||
// Whenever we erase the full display, scroll to bottom.
|
||||
try self.terminal.scrollViewport(.{ .bottom = {} });
|
||||
try self.render_timer.schedule();
|
||||
try self.queueRender();
|
||||
}
|
||||
|
||||
self.terminal.eraseDisplay(mode);
|
||||
@ -1775,7 +1759,7 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void {
|
||||
self.terminal.modes.reverse_colors = enabled;
|
||||
|
||||
// Schedule a render since we changed colors
|
||||
try self.render_timer.schedule();
|
||||
try self.queueRender();
|
||||
},
|
||||
|
||||
.origin => {
|
||||
@ -1803,7 +1787,7 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void {
|
||||
self.terminal.primaryScreen(opts);
|
||||
|
||||
// Schedule a render since we changed screens
|
||||
try self.render_timer.schedule();
|
||||
try self.queueRender();
|
||||
},
|
||||
|
||||
.bracketed_paste => self.bracketed_paste = true,
|
||||
|
@ -24,6 +24,9 @@ wakeup: libuv.Async,
|
||||
/// This can be used to stop the renderer on the next loop iteration.
|
||||
stop: libuv.Async,
|
||||
|
||||
/// The timer used for rendering
|
||||
render_h: libuv.Timer,
|
||||
|
||||
/// The windo we're rendering to.
|
||||
window: glfw.Window,
|
||||
|
||||
@ -54,7 +57,7 @@ pub fn init(
|
||||
loop.setData(allocPtr);
|
||||
|
||||
// This async handle is used to "wake up" the renderer and force a render.
|
||||
var wakeup_h = try libuv.Async.init(alloc, loop, renderCallback);
|
||||
var wakeup_h = try libuv.Async.init(alloc, loop, wakeupCallback);
|
||||
errdefer wakeup_h.close((struct {
|
||||
fn callback(h: *libuv.Async) void {
|
||||
const loop_alloc = h.loop().getData(Allocator).?.*;
|
||||
@ -71,10 +74,20 @@ pub fn init(
|
||||
}
|
||||
}).callback);
|
||||
|
||||
// The primary timer for rendering.
|
||||
var render_h = try libuv.Timer.init(alloc, loop);
|
||||
errdefer render_h.close((struct {
|
||||
fn callback(h: *libuv.Timer) void {
|
||||
const loop_alloc = h.loop().getData(Allocator).?.*;
|
||||
h.deinit(loop_alloc);
|
||||
}
|
||||
}).callback);
|
||||
|
||||
return Thread{
|
||||
.loop = loop,
|
||||
.wakeup = wakeup_h,
|
||||
.stop = stop_h,
|
||||
.render_h = render_h,
|
||||
.window = window,
|
||||
.renderer = renderer_impl,
|
||||
.state = state,
|
||||
@ -101,6 +114,12 @@ pub fn deinit(self: *Thread) void {
|
||||
h.deinit(handle_alloc);
|
||||
}
|
||||
}).callback);
|
||||
self.render_h.close((struct {
|
||||
fn callback(h: *libuv.Timer) void {
|
||||
const handle_alloc = h.loop().getData(Allocator).?.*;
|
||||
h.deinit(handle_alloc);
|
||||
}
|
||||
}).callback);
|
||||
|
||||
// Run the loop one more time, because destroying our other things
|
||||
// like windows usually cancel all our event loop stuff and we need
|
||||
@ -124,6 +143,10 @@ pub fn threadMain(self: *Thread) void {
|
||||
}
|
||||
|
||||
fn threadMain_(self: *Thread) !void {
|
||||
// Get a copy to our allocator
|
||||
// const alloc_ptr = self.loop.getData(Allocator).?;
|
||||
// const alloc = alloc_ptr.*;
|
||||
|
||||
// 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.
|
||||
@ -135,13 +158,34 @@ fn threadMain_(self: *Thread) !void {
|
||||
self.wakeup.setData(self);
|
||||
defer self.wakeup.setData(null);
|
||||
|
||||
// Set up our timer and start it for rendering
|
||||
self.render_h.setData(self);
|
||||
defer self.render_h.setData(null);
|
||||
try self.wakeup.send();
|
||||
|
||||
// Run
|
||||
log.debug("starting renderer thread", .{});
|
||||
defer log.debug("exiting renderer thread", .{});
|
||||
_ = try self.loop.run(.default);
|
||||
}
|
||||
|
||||
fn renderCallback(h: *libuv.Async) void {
|
||||
fn wakeupCallback(h: *libuv.Async) void {
|
||||
const t = h.getData(Thread) orelse {
|
||||
// This shouldn't happen so we log it.
|
||||
log.warn("render callback fired without data set", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
// If the timer is already active then we don't have to do anything.
|
||||
const active = t.render_h.isActive() catch true;
|
||||
if (active) return;
|
||||
|
||||
// Timer is not active, let's start it
|
||||
t.render_h.start(renderCallback, 10, 0) catch |err|
|
||||
log.warn("render timer failed to start err={}", .{err});
|
||||
}
|
||||
|
||||
fn renderCallback(h: *libuv.Timer) void {
|
||||
const t = h.getData(Thread) orelse {
|
||||
// This shouldn't happen so we log it.
|
||||
log.warn("render callback fired without data set", .{});
|
||||
|
Reference in New Issue
Block a user