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
This commit is contained in:
Kovid Goyal 2018-02-13 16:31:05 +05:30
parent 807c8f9005
commit 6a51096304
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 41 additions and 6 deletions

View File

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

View File

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

View File

@ -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");

View File

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

View File

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

View File

@ -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);