Commit: patch 9.2.0751: GTK3 GUI is slow under Wayland

1 view
Skip to first unread message

Christian Brabandt

unread,
5:15 PM (4 hours ago) 5:15 PM
to vim...@googlegroups.com
patch 9.2.0751: GTK3 GUI is slow under Wayland

Commit: https://github.com/vim/vim/commit/1f2f568f9147ca68d8772869fd6b6f56901847b3
Author: Christoffer Aasted <dez...@gmail.com>
Date: Mon Jun 29 21:04:52 2026 +0000

patch 9.2.0751: GTK3 GUI is slow under Wayland

Problem: GTK3 GUI redraws are slow under Wayland because each draw
operation queues its own draw area.
Solution: Defer redraws under Wayland: coalesce dirty rectangles and
flush them once in gui_mch_flush(), route draw calls through a
queue_draw_area() wrapper (Christoffer Aasted)

closes: #20528

Signed-off-by: Christoffer Aasted <dez...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/gui_x11.txt b/runtime/doc/gui_x11.txt
index 8a084505e..d55f35aba 100644
--- a/runtime/doc/gui_x11.txt
+++ b/runtime/doc/gui_x11.txt
@@ -1,4 +1,4 @@
-*gui_x11.txt* For Vim version 9.2. Last change: 2026 Jun 24
+*gui_x11.txt* For Vim version 9.2. Last change: 2026 June 29


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -353,8 +353,12 @@ to the GTK documentation, however little there is, on how to do this.
See https://www.manpagez.com/html/gtk2/gtk2-2.24.24/gtk2-Resource-Files.php
for more information.
*gtk3-slow*
-If you are using GTK3 and Vim appears to be slow, try setting the environment
-variable $GDK_RENDERING to "image".
+If the GTK3 GUI is slow, try: >
+
+ $ GDK_RENDERING=image gvim
+
+Small font, high resolution, many 'lines' (vertical screen) and UI scaling all
+contribute to a slower redraw.

*gtk4-slow*
If the GTK4 GUI is slow, freezes, or does not accept keyboard input right
diff --git a/src/gui_gtk_x11.c b/src/gui_gtk_x11.c
index d28ccde86..d7ea36274 100644
--- a/src/gui_gtk_x11.c
+++ b/src/gui_gtk_x11.c
@@ -3745,6 +3745,54 @@ gui_gtk_set_dnd_targets(void)
GDK_ACTION_COPY | GDK_ACTION_MOVE);
}

+#ifdef GDK_WINDOWING_WAYLAND
+static struct {
+ int left;
+ int top;
+ int right;
+ int bottom;
+ bool active;
+} wl_dirty_rect = {0, 0, 0, 0, false};
+
+ static void
+wl_queue_dirty_area(int x, int y, int width, int height)
+{
+ if (!wl_dirty_rect.active)
+ {
+ wl_dirty_rect.left = x;
+ wl_dirty_rect.top = y;
+ wl_dirty_rect.right = x + width;
+ wl_dirty_rect.bottom = y + height;
+ wl_dirty_rect.active = true;
+ }
+ else
+ {
+ // Expand to append further changes
+ if (x < wl_dirty_rect.left) wl_dirty_rect.left = x;
+ if (y < wl_dirty_rect.top) wl_dirty_rect.top = y;
+ if (x + width > wl_dirty_rect.right) wl_dirty_rect.right = x + width;
+ if (y + height > wl_dirty_rect.bottom) wl_dirty_rect.bottom = y + height;
+ }
+}
+
+ static void
+wl_flush(void)
+{
+ if (!wl_dirty_rect.active)
+ return;
+ int draw_x = wl_dirty_rect.left;
+ int draw_y = wl_dirty_rect.top;
+ int draw_w = wl_dirty_rect.right - wl_dirty_rect.left;
+ int draw_h = wl_dirty_rect.bottom - wl_dirty_rect.top;
+
+ if (draw_w > 0 && draw_h > 0)
+ {
+ gtk_widget_queue_draw_area(gui.drawarea, draw_x, draw_y, draw_w, draw_h);
+ }
+ wl_dirty_rect.active = false;
+}
+#endif
+
/*
* Initialize the GUI. Create all the windows, set up all the callbacks etc.
* Returns OK for success, FAIL when the GUI can't be started.
@@ -4425,7 +4473,7 @@ form_configure_event(GtkWidget *widget UNUSED,
// too small. Schedule a corrective resize (now that offsets are
// known) so the window actually fits the geometry Vim has just set.
if ((mch_csd_height > 0 || mch_csd_width > 0) && gtk_socket_id == 0)
- g_idle_add(startup_resize_correction_cb, NULL);
+ g_idle_add_full(GTK_PRIORITY_RESIZE, startup_resize_correction_cb, NULL, NULL);
}
clear_resize_hists();
#endif
@@ -6221,6 +6269,21 @@ gui_gtk_draw_string(int row, int col, char_u *s, int len, int flags)
return len_sum;
}

+ static void
+queue_draw_area(
+ int x,
+ int y,
+ int width,
+ int height)
+{
+#ifdef GDK_WINDOWING_WAYLAND
+ if (gui.is_wayland)
+ wl_queue_dirty_area(x, y, width, height);
+ else
+#endif
+ gtk_widget_queue_draw_area(gui.drawarea, x, y, width, height);
+}
+
int
gui_gtk_draw_string_ext(
int row,
@@ -6473,7 +6536,7 @@ skipitall:

#if GTK_CHECK_VERSION(3,0,0)
cairo_destroy(cr);
- gtk_widget_queue_draw_area(gui.drawarea, area.x, area.y,
+ queue_draw_area(area.x, area.y,
area.width, area.height);
#else
gdk_gc_set_clip_rectangle(gui.text_gc, NULL);
@@ -6617,7 +6680,7 @@ gui_mch_invert_rectangle(int r, int c, int nr, int nc)

cairo_destroy(cr);

- gtk_widget_queue_draw_area(gui.drawarea, rect.x, rect.y,
+ queue_draw_area(rect.x, rect.y,
rect.width, rect.height);
#else
GdkGCValues values;
@@ -6772,7 +6835,19 @@ gui_mch_update(void)
int cnt = 0; // prevent endless loop
while (g_main_context_pending(NULL) && !vim_is_input_buf_full()
&& ++cnt < 100)
+ {
+#ifdef GDK_WINDOWING_WAYLAND
+ if (gui.is_wayland)
+ {
+ int prio = 0;
+ g_main_context_prepare(NULL, &prio);
+ // peek internal scheduling of redraw, honors 'lazyredraw'
+ if (prio == GDK_PRIORITY_REDRAW && redrawing())
+ gui_may_flush(); // prepares redraw: g_main_context_iteration
+ }
+#endif
g_main_context_iteration(NULL, TRUE);
+ }
}

static timeout_cb_type
@@ -6905,11 +6980,17 @@ theend:
gui_mch_flush(void)
{
if (gui.mainwin != NULL && gtk_widget_get_realized(gui.mainwin))
+ {
+#ifdef GDK_WINDOWING_WAYLAND
+ if (gui.is_wayland)
+ return wl_flush();
+#endif
#if GTK_CHECK_VERSION(2,4,0)
gdk_display_flush(gtk_widget_get_display(gui.mainwin));
#else
gdk_display_sync(gtk_widget_get_display(gui.mainwin));
#endif
+ }
}

/*
@@ -6961,7 +7042,7 @@ gui_mch_clear_block(int row1arg, int col1arg, int row2arg, int col2arg)
cairo_fill(cr);
cairo_destroy(cr);

- gtk_widget_queue_draw_area(gui.drawarea,
+ queue_draw_area(
rect.x, rect.y, rect.width, rect.height);
}
#else // !GTK_CHECK_VERSION(3,0,0)
@@ -6998,7 +7079,7 @@ gui_gtk_window_clear(GdkWindow *win)
cairo_fill(cr);
cairo_destroy(cr);

- gtk_widget_queue_draw_area(gui.drawarea,
+ queue_draw_area(
rect.x, rect.y, rect.width, rect.height);
}
#else
@@ -7058,7 +7139,7 @@ gui_mch_draw_popup_image(
cairo_popup_image_paint(wp, gui.surface, x, y,
src_x, src_y, draw_w, draw_h);
if (gui.drawarea != NULL)
- gtk_widget_queue_draw_area(gui.drawarea, x, y, draw_w, draw_h);
+ queue_draw_area(x, y, draw_w, draw_h);
# else
cairo_popup_image_paint(wp, gui.drawarea->window, x, y,
src_x, src_y, draw_w, draw_h);
@@ -7114,49 +7195,35 @@ gui_gtk_surface_copy_rect(int dest_x, int dest_y,
cairo_t * const cr = cairo_create(gui.surface);

# ifdef GDK_WINDOWING_WAYLAND
- /*
- Following optimizations are temporary until all callers are refactored
- to wayland deferred redraw; .. then it could be removed.
- */
static cairo_surface_t *scroll_scratch = NULL;
- static int scratch_w = 0;
- static int scratch_h = 0;
- int last_row = Rows - 1;
- int last_row_y = last_row * gui.char_height;
+ static int scratch_w = 0, scratch_h = 0;
+ int last_row = Rows - 1, last_row_y = last_row * gui.char_height;
bool last_row_overlap = (dest_y + height) > last_row_y;
if (gui.is_wayland && ( !(State & MODE_CMDLINE) || !last_row_overlap) )
{
- /*
- scrolling up
- */
- if (dest_y < src_y)
+ if (dest_y < src_y) // scroll up
{
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
- cairo_set_source_surface(cr, gui.surface,
- src_x - dest_x,
+ cairo_set_source_surface(cr, gui.surface, src_x - dest_x,
dest_y - src_y);
cairo_rectangle(cr, dest_x, dest_y, width, height);
cairo_clip(cr);
cairo_paint(cr);
}
else
- {
- // reusing surface when scrolling, only realloc if larger
+ { // reuse surface when scrolling, only realloc if larger
if (scroll_scratch == NULL || width > scratch_w || height > scratch_h)
{
cairo_surface_destroy(scroll_scratch); // safe even if NULL
scroll_scratch = cairo_surface_create_similar(gui.surface,
cairo_surface_get_content(gui.surface), width, height);
- scratch_w = width;
- scratch_h = height;
+ scratch_w = width, scratch_h = height;
}
-
// capture scroll source region
cairo_t *tcr = cairo_create(scroll_scratch);
cairo_set_source_surface(tcr, gui.surface, -src_x, -src_y);
cairo_paint(tcr);
cairo_destroy(tcr);
-
// reuse scroll source region
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_rectangle(cr, dest_x, dest_y, width, height);
@@ -7176,7 +7243,6 @@ gui_gtk_surface_copy_rect(int dest_x, int dest_y,
cairo_pop_group_to_source(cr);
cairo_paint(cr);
}
-
cairo_destroy(cr);
}
#endif
@@ -7200,7 +7266,7 @@ gui_mch_delete_lines(int row, int num_lines)
gui_clear_block(
gui.scroll_region_bot - num_lines + 1, gui.scroll_region_left,
gui.scroll_region_bot, gui.scroll_region_right);
- gtk_widget_queue_draw_area(gui.drawarea,
+ queue_draw_area(
FILL_X(gui.scroll_region_left), FILL_Y(row),
gui.char_width * ncols + 1, gui.char_height * nrows);
#else
@@ -7246,7 +7312,7 @@ gui_mch_insert_lines(int row, int num_lines)
gui_clear_block(
row, gui.scroll_region_left,
row + num_lines - 1, gui.scroll_region_right);
- gtk_widget_queue_draw_area(gui.drawarea,
+ queue_draw_area(
FILL_X(gui.scroll_region_left), FILL_Y(row),
gui.char_width * ncols + 1, gui.char_height * nrows);
#else
@@ -7739,7 +7805,7 @@ gui_mch_drawsign(int row, int col, int typenr)
cairo_surface_destroy(bg_surf);
cairo_destroy(cr);

- gtk_widget_queue_draw_area(gui.drawarea,
+ queue_draw_area(
FILL_X(col), FILL_Y(col), width, height);

}
diff --git a/src/version.c b/src/version.c
index 218fdc163..d4c08b3eb 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 751,
/**/
750,
/**/
Reply all
Reply to author
Forward
0 new messages