]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0751: GTK3 GUI is slow under Wayland v9.2.0751
authorChristoffer Aasted <dezzadk@gmail.com>
Mon, 29 Jun 2026 21:04:52 +0000 (21:04 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 29 Jun 2026 21:04:52 +0000 (21:04 +0000)
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 <dezzadk@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/doc/gui_x11.txt
src/gui_gtk_x11.c
src/version.c

index 8a084505e81b209cecded153e911901d41791339..d55f35aba7be631e418ebaa8db8a4162dbc8c29a 100644 (file)
@@ -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
index d28ccde863b5e6257924faffeaadf1d7b16eb6eb..d7ea36274f89bd61e62b9c000a3a24654554eb8f 100644 (file)
@@ -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);
 
     }
index 218fdc163a157c0837c941518771e91caa99b666..d4c08b3eb9e7c0e6c8b0080642ea1424fece2934 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    751,
 /**/
     750,
 /**/