diff --git a/README.asciidoc b/README.asciidoc index e73f117ec..5b48c2eb5 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -348,6 +348,15 @@ glyph in video RAM so that font rendering is not a bottleneck. Interaction with child programs takes place in a separate thread from rendering, to improve smoothness. +kitty goes to great lengths to reduce latency. In order to minimize latency, +kitty dynamically decides whether to sync updates to the monitor refresh rate +or not. When doing latency sensitive activities such as typing or selecting +with the mouse, kitty does not sync to the monitor refresh rate, as that limits +latency to a minimum of `17ms` for common monitors. However, when +scrolling or making other large scale changes to the screen, kitty syncs to the +monitor refresh rate, to avoid +link:https://en.wikipedia.org/wiki/Screen_tearing[screen tearing]. + There are two parameters you can tune to adjust the performance. ``repaint_delay`` and ``input_delay``. These control the artificial delays introduced into the render loop to reduce CPU usage. See the link:kitty/kitty.conf[config file] for details. diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 6589d77dd..81cf2c308 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -604,6 +604,7 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id) Tab *tab = os_window->tabs + os_window->active_tab; BorderRects *br = &tab->border_rects; draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height); + bool needs_vsync = false; if (TD.screen && os_window->num_tabs > 1) draw_cells(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window, true); for (unsigned int i = 0; i < tab->num_windows; i++) { Window *w = tab->windows + i; @@ -617,9 +618,11 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id) double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at); set_maximum_wait(bell_left); } + if (!needs_vsync && (WD.screen->render_activity.large_change || WD.screen->render_activity.chars_written > 100)) needs_vsync = true; + WD.screen->render_activity.large_change = false; WD.screen->render_activity.chars_written = 0; } } - swap_window_buffers(os_window); + swap_window_buffers(os_window, needs_vsync); br->is_dirty = false; os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id; os_window->focused_at_last_render = os_window->is_focused; diff --git a/kitty/glfw.c b/kitty/glfw.c index 2cdeb0f27..6716d6a4a 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -626,7 +626,11 @@ hide_mouse(OSWindow *w) { } void -swap_window_buffers(OSWindow *w) { +swap_window_buffers(OSWindow *w, bool needs_vsync) { + if (needs_vsync != w->vsync_enabled) { + glfwSwapInterval(needs_vsync ? 1 : 0); + w->vsync_enabled = needs_vsync; + } glfwSwapBuffers(w->handle); } @@ -739,7 +743,7 @@ os_window_swap_buffers(PyObject UNUSED *self, PyObject *args) { for (size_t i = 0; i < global_state.num_os_windows; i++) { OSWindow *w = global_state.os_windows + i; if (w->id == os_window_id) { - swap_window_buffers(w); Py_RETURN_NONE; + swap_window_buffers(w, false); Py_RETURN_NONE; } } PyErr_SetString(PyExc_ValueError, "no such OSWindow"); diff --git a/kitty/screen.c b/kitty/screen.c index 9a4a10cf5..1dc1952ce 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -123,6 +123,7 @@ screen_reset(Screen *self) { init_tabstops(self->alt_tabstops, self->columns); cursor_reset(self->cursor); self->is_dirty = true; + self->render_activity.large_change = true; screen_cursor_position(self, 1, 1); set_dynamic_color(self, 110, NULL); set_dynamic_color(self, 111, NULL); @@ -202,6 +203,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { self->cursor->y = num_content_lines; if (self->cursor->y >= self->lines) { self->cursor->y = self->lines - 1; screen_index(self); } } + self->render_activity.large_change = true; return true; } @@ -354,6 +356,7 @@ screen_draw(Screen *self, uint32_t och) { line_right_shift(self->linebuf->line, self->cursor->x, char_width); } line_set_char(self->linebuf->line, self->cursor->x, ch, char_width, self->cursor, false); + self->render_activity.chars_written += 1; self->cursor->x++; if (char_width == 2) { line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor, true); @@ -488,6 +491,7 @@ screen_toggle_screen_buffer(Screen *self) { } screen_history_scroll(self, SCROLL_FULL, false); self->is_dirty = true; + self->render_activity.large_change = true; } void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI @@ -724,7 +728,8 @@ screen_cursor_to_column(Screen *self, unsigned int column) { self->history_line_added_count++; \ } \ linebuf_clear_line(self->linebuf, bottom); \ - self->is_dirty = true; + self->is_dirty = true; \ + self->render_activity.large_change = true; void screen_index(Screen *self) { @@ -749,7 +754,8 @@ screen_scroll(Screen *self, unsigned int count) { linebuf_reverse_index(self->linebuf, top, bottom); \ linebuf_clear_line(self->linebuf, top); \ INDEX_GRAPHICS(1) \ - self->is_dirty = true; + self->is_dirty = true; \ + self->render_activity.large_change = true; void screen_reverse_index(Screen *self) { @@ -974,6 +980,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) { linebuf_mark_line_dirty(self->linebuf, i); } self->is_dirty = true; + self->render_activity.large_change = true; } if (how != 2) { screen_erase_in_line(self, how, private); @@ -983,6 +990,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) { if (self->scrolled_by != 0) { self->scrolled_by = 0; self->scroll_changed = true; + self->render_activity.large_change = true; } } } @@ -994,6 +1002,7 @@ screen_insert_lines(Screen *self, unsigned int count) { if (top <= self->cursor->y && self->cursor->y <= bottom) { linebuf_insert_lines(self->linebuf, count, self->cursor->y, bottom); self->is_dirty = true; + self->render_activity.large_change = true; screen_carriage_return(self); } } @@ -1005,6 +1014,7 @@ screen_delete_lines(Screen *self, unsigned int count) { if (top <= self->cursor->y && self->cursor->y <= bottom) { linebuf_delete_lines(self->linebuf, count, self->cursor->y, bottom); self->is_dirty = true; + self->render_activity.large_change = true; screen_carriage_return(self); } } @@ -1702,6 +1712,7 @@ screen_history_scroll(Screen *self, int amt, bool upwards) { if (new_scroll != self->scrolled_by) { self->scrolled_by = new_scroll; self->scroll_changed = true; + self->render_activity.large_change = true; return true; } return false; diff --git a/kitty/screen.h b/kitty/screen.h index 1a2b3c1ce..39c9ef16c 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -51,6 +51,12 @@ typedef struct { } SavemodesBuffer; +typedef struct { + bool large_change; + unsigned int chars_written; +} RenderActivitity; + + typedef struct { PyObject_HEAD @@ -84,6 +90,7 @@ typedef struct { pthread_mutex_t read_buf_lock, write_buf_lock; CursorRenderInfo cursor_render_info; + RenderActivitity render_activity; } Screen; diff --git a/kitty/state.h b/kitty/state.h index 0f68c8094..d2a781841 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -114,6 +114,7 @@ typedef struct { bool is_damaged; uint32_t offscreen_texture_id; unsigned int clear_count; + bool vsync_enabled; } OSWindow; @@ -156,7 +157,7 @@ bool should_os_window_be_rendered(OSWindow* w); void set_dpi_from_os_window(OSWindow *w); void wakeup_main_loop(); void event_loop_wait(double timeout); -void swap_window_buffers(OSWindow *w); +void swap_window_buffers(OSWindow *w, bool); void make_window_context_current(OSWindow *w); void hide_mouse(OSWindow *w); void destroy_os_window(OSWindow *w);