remove render timer from window

This commit is contained in:
Mitchell Hashimoto
2022-10-24 10:50:42 -07:00
parent 536f5c4487
commit c0f96f591b
2 changed files with 72 additions and 44 deletions

View File

@ -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,

View File

@ -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", .{});