]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0010: Using Wayland compositor is still slow v9.2.0010
authorChristoffer Aasted <dezzadk@gmail.com>
Sun, 15 Feb 2026 17:21:03 +0000 (17:21 +0000)
committerChristian Brabandt <cb@256bit.org>
Sun, 15 Feb 2026 17:27:24 +0000 (17:27 +0000)
Problem:  Using the Wayland backend in GTK, rendering remains slow due
          to per-line redraws, unnecessary Cairo push/pop groups, and
          scroll operations that allocate new surfaces repeatedly.
Solution: Improve rendering performance (Christoffer Aasted).

This commit does the following:
- Add gui.is_wayland to detect Wayland backend
- Avoid blocking the input loop
- Skip early redraws; let the compositor handle full-screen redraws
- Use CAIRO_OPERATOR_SOURCE to overwrite instead of blend
- Reuse scroll source region for destination scroll region
- Optimize fast scroll-up
- Remove cairo_push_group/pop_group and cairo_clip in scroll path
  to reduce allocations (~50MB saved on 4K fractional scale)

Since Wayland redraws the entire screen between updates (unlike X11),
further performance gains are possible by batching drawing in other code
paths, e.g.:
- message.c: batch lines to avoid scroll
- term.c: batch terminal redraws

These could be refactored later to deferred redraw with Wayland.

closes: #19062

Signed-off-by: Christoffer Aasted <dezzadk@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/gui.c
src/gui.h
src/gui_gtk_x11.c
src/version.c

index 8673ec8d841b2bd67b6267b52f1c32b6cd40a5ae..9eef90f92f7732d06bb265b276e50e47a96fcc9d 100644 (file)
--- a/src/gui.c
+++ b/src/gui.c
@@ -211,7 +211,6 @@ gui_attempt_start(void)
        if (gui_get_x11_windis(&x11_window, &x11_display) == OK)
            set_vim_var_nr(VV_WINDOWID, (long)x11_window);
 # endif
-
        // Display error messages in a dialog now.
        display_errors();
     }
@@ -484,6 +483,9 @@ gui_init_check(void)
     result = OK;
 #else
 # ifdef FEAT_GUI_GTK
+#  ifdef GDK_WINDOWING_WAYLAND
+    gui.is_wayland = FALSE;
+#  endif
     /*
      * Note: Don't call gtk_init_check() before fork, it will be called after
      * the fork. When calling it before fork, it make vim hang for a while.
index da06e2ce09e52402ee8627f88c94d90112740e13..2069e7486573459a9f473ad552e7b349090856ec 100644 (file)
--- a/src/gui.h
+++ b/src/gui.h
@@ -389,6 +389,9 @@ typedef struct Gui
     char_u     *browse_fname;      // file name from filedlg
 
     guint32    event_time;
+# ifdef GDK_WINDOWING_WAYLAND
+    _Bool      is_wayland;           // active gdk backend in gtk is wayland
+# endif
 #endif // FEAT_GUI_GTK
 
 #if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_MSWIN)
index 015a2c7f654bc740287a759b7c29311761ee91e8..edf6b705d5ef4b7413bee50c5b4254b0d3f1d34d 100644 (file)
@@ -59,6 +59,9 @@ extern void bonobo_dock_item_set_behavior(BonoboDockItem *dock_item, BonoboDockI
 #if defined(FEAT_GUI_GTK)
 # if GTK_CHECK_VERSION(3,0,0)
 #  include <gdk/gdkkeysyms-compat.h>
+#  ifdef GDK_WINDOWING_WAYLAND
+#   include <gdk/gdkwayland.h>
+#  endif
 #  include <gtk/gtkx.h>
 # else
 #  include <gdk/gdkkeysyms.h>
@@ -2039,11 +2042,6 @@ scroll_event(GtkWidget *widget,
 # if !GTK_CHECK_VERSION(3,22,0)
     static guint32 last_smooth_event_time;
 # endif
-# define DT_X11     1
-# define DT_WAYLAND 2
-    static int display_type;
-    if (!display_type)
-       display_type = gui_mch_get_display() ? DT_X11 : DT_WAYLAND;
 #endif
 
     if (gtk_socket_id != 0 && !gtk_widget_has_focus(widget))
@@ -2101,7 +2099,7 @@ scroll_event(GtkWidget *widget,
 #if GTK_CHECK_VERSION(3,4,0)
     // on x11, despite not requested, when we copy into primary clipboard,
     // we'll get smooth events. Unsmooth ones will also come along.
-    if (event->direction == GDK_SCROLL_SMOOTH && display_type == DT_WAYLAND)
+    if (event->direction == GDK_SCROLL_SMOOTH && gui.is_wayland)
     {
        while (acc_x >= 1.0)
        { // right
@@ -2128,12 +2126,10 @@ scroll_event(GtkWidget *widget,
                    FALSE, vim_modifiers);
        }
     }
-    else if (event->direction == GDK_SCROLL_SMOOTH && display_type == DT_X11)
+    else if (event->direction == GDK_SCROLL_SMOOTH && X_DISPLAY)
        // for X11 we deal with unsmooth events, and so ignore the smooth ones
        ;
     else
-# undef DT_X11
-# undef DT_WAYLAND
 #endif
        gui_send_mouse_event(button, (int)event->x, (int)event->y,
                FALSE, vim_modifiers);
@@ -4004,6 +4000,12 @@ gui_mch_init(void)
     gui.surface = NULL;
 #endif
 
+#ifdef GDK_WINDOWING_WAYLAND
+    GdkDisplay *d = gdk_display_get_default();
+    if (GDK_IS_WAYLAND_DISPLAY(d))
+       gui.is_wayland = TRUE;
+#endif
+
     // Determine which events we will filter.
     gint event_mask =
        GDK_EXPOSURE_MASK |
@@ -6619,6 +6621,11 @@ gui_mch_draw_part_cursor(int w, int h, guicolor_T color)
     void
 gui_mch_update(void)
 {
+#ifdef GDK_WINDOWING_WAYLAND
+    // avoid early redraws; compositor does redraw
+    if (gui.is_wayland)
+       return;
+#endif
     int cnt = 0;       // prevent endless loop
     while (g_main_context_pending(NULL) && !vim_is_input_buf_full()
                                                                && ++cnt < 100)
@@ -6718,7 +6725,14 @@ gui_mch_wait_for_chars(long wtime)
         * situations, sort of race condition).
         */
        if (!input_available())
-           g_main_context_iteration(NULL, TRUE);
+       {
+#ifdef GDK_WINDOWING_WAYLAND
+           if (gui.is_wayland)
+               g_main_context_iteration(NULL, FALSE);
+           else
+#endif
+               g_main_context_iteration(NULL, TRUE);
+       }
 
        // Got char, return immediately
        if (input_available())
@@ -6909,13 +6923,69 @@ gui_gtk_surface_copy_rect(int dest_x, int dest_y,
 {
     cairo_t * const cr = cairo_create(gui.surface);
 
-    cairo_rectangle(cr, dest_x, dest_y, width, height);
-    cairo_clip(cr);
-    cairo_push_group(cr);
-    cairo_set_source_surface(cr, gui.surface, dest_x - src_x, dest_y - src_y);
-    cairo_paint(cr);
-    cairo_pop_group_to_source(cr);
-    cairo_paint(cr);
+# 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;
+    _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)
+       {
+           cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+           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
+           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;
+           }
+
+           // 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);
+           cairo_clip(cr);
+           cairo_set_source_surface(cr, scroll_scratch, dest_x, dest_y);
+           cairo_paint(cr);
+       }
+    }
+    else
+# endif
+    {
+       cairo_rectangle(cr, dest_x, dest_y, width, height);
+       cairo_clip(cr);
+       cairo_push_group(cr);
+       cairo_set_source_surface(cr, gui.surface, dest_x - src_x, dest_y - src_y);
+       cairo_paint(cr);
+       cairo_pop_group_to_source(cr);
+       cairo_paint(cr);
+    }
 
     cairo_destroy(cr);
 }
index 1c7ecd3b170296e8c444ab1b4fecdc3c92f4c530..ad4b50cf771e0a415d582ef0a796d817cfb1a460 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    10,
 /**/
     9,
 /**/