renderer: clear renderstate memory periodically

pull/9662/head
Mitchell Hashimoto 2025-11-21 09:02:59 -08:00
parent 2ecaf4a595
commit 82f5c1a13c
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
2 changed files with 31 additions and 0 deletions

View File

@ -206,6 +206,12 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
/// The render state we update per loop.
terminal_state: terminal.RenderState = .empty,
/// The number of frames since the last terminal state reset.
/// We reset the terminal state after ~100,000 frames (about 10 to
/// 15 minutes at 120Hz) to prevent wasted memory buildup from
/// a large screen.
terminal_state_frame_count: usize = 0,
/// Swap chain which maintains multiple copies of the state needed to
/// render a frame, so that we can start building the next frame while
/// the previous frame is still being processed on the GPU.
@ -1062,6 +1068,18 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
state: *renderer.State,
cursor_blink_visible: bool,
) !void {
// We fully deinit and reset the terminal state every so often
// so that a particularly large terminal state doesn't cause
// the renderer to hold on to retained memory.
//
// Frame count is ~12 minutes at 120Hz.
const max_terminal_state_frame_count = 100_000;
if (self.terminal_state_frame_count >= max_terminal_state_frame_count) {
self.terminal_state.deinit(self.alloc);
self.terminal_state = .empty;
}
self.terminal_state_frame_count += 1;
// Create an arena for all our temporary allocations while rebuilding
var arena = ArenaAllocator.init(self.alloc);
defer arena.deinit();

View File

@ -30,6 +30,19 @@ const Terminal = @import("Terminal.zig");
/// Rather than a generic clone that tries to clone all screen state per call
/// (within a region), a stateful approach that optimizes for only what a
/// renderer needs to do makes more sense.
///
/// To use this, initialize the render state to empty, then call `update`
/// on each frame to update the state to the latest terminal state.
///
/// var state: RenderState = .empty;
/// defer state.deinit(alloc);
/// state.update(alloc, &terminal);
///
/// Note: the render state retains as much memory as possible between updates
/// to prevent future allocations. If a very large frame is rendered once,
/// the render state will retain that much memory until deinit. To avoid
/// waste, it is recommended that the caller `deinit` and start with an
/// empty render state every so often.
pub const RenderState = struct {
/// The current screen dimensions. It is possible that these don't match
/// the renderer's current dimensions in grid cells because resizing