From 6a51096304e017043cf2113b3070802b301dd627 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Feb 2018 16:31:05 +0530 Subject: [PATCH] Enable syncing of redraws to monitor refresh rate Now, when large changes, such as scrolling occur, the OpenGL back buffer swapping is synchronized to the monitor refresh rate (if supported by the OS). This eliminates tearing artifacts when scrolling the screen fast. Fixes #318 --- README.asciidoc | 9 +++++++++ kitty/child-monitor.c | 5 ++++- kitty/glfw.c | 8 ++++++-- kitty/screen.c | 15 +++++++++++++-- kitty/screen.h | 7 +++++++ kitty/state.h | 3 ++- 6 files changed, 41 insertions(+), 6 deletions(-) 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);