Subject: [PATCH] pgtk: Use cairo_set_source_surface()

From: pwychowaniec <[email-masked]>
Date: Sat, 09 Aug 2025 12:21:52 +0000

Hi!

I'm submitting a patch that improves performance of previewing large images in Emacs; more details in the commit message.

Cheers,
Patryk.

From 723810dbe466df6191aecee5a7eb59963f5cf3db Mon Sep 17 00:00:00 2001
From: Patryk Wychowaniec <[email-masked]>
Date: Sat, 9 Aug 2025 14:12:28 +0200
Subject: [PATCH] pgtk: Use cairo_set_source_surface()

`cairo_set_source_surface()` seems to be about two orders of magnitude
faster than `cairo_set_source()`, so let's use it to draw images within
`pgtk_cr_draw_image()`.

Today, if you open a relatively large image[1] in emacs-pgtk, the entire
interface will slow down to a crawl every time it needs to be redrawn -
with the changes here, the framerate is smooth and consistent.

[1] say, 1600x1200
---
 src/dispextern.h |  1 +
 src/image.c      | 12 ++++++++++++
 src/pgtkterm.c   | 43 ++++++++++++++++++++++++++++++++++++-------
 3 files changed, 49 insertions(+), 7 deletions(-)

diff --git a/src/dispextern.h b/src/dispextern.h
index 19ab104d2e6..db863d0fab0 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -3180,6 +3180,7 @@ reset_mouse_highlight (Mouse_HLInfo *hlinfo)
 
 #ifdef USE_CAIRO
   void *cr_data;
+  void *cr_surface;
 #endif
 #ifdef HAVE_X_WINDOWS
   /* X images of the image, corresponding to the above Pixmaps.
diff --git a/src/image.c b/src/image.c
index c5caaf739e7..dfdfa84e7cc 100644
--- a/src/image.c
+++ b/src/image.c
@@ -349,7 +349,13 @@ cr_put_image_to_cr_data (struct image *img)
       cairo_surface_destroy (surface);
     }
 
+  if (img->cr_surface)
+    {
+      cairo_surface_destroy (img->cr_surface);
+    }
+
   img->cr_data = pattern;
+  img->cr_surface = NULL;
 }
 
 #endif	/* USE_CAIRO */
@@ -2124,6 +2130,12 @@ image_clear_image_1 (struct frame *f, struct image *img, int flags)
       cairo_pattern_destroy (img->cr_data);
       img->cr_data = NULL;
     }
+
+  if (img->cr_surface)
+    {
+      cairo_surface_destroy (img->cr_surface);
+      img->cr_surface = NULL;
+    }
 #endif	/* USE_CAIRO */
 }
 
diff --git a/src/pgtkterm.c b/src/pgtkterm.c
index a7be2d90899..a86987e13b6 100644
--- a/src/pgtkterm.c
+++ b/src/pgtkterm.c
@@ -2174,8 +2174,8 @@ pgtk_draw_glyph_string_bg_rect (struct glyph_string *s, int x, int y, int w,
 
 static void
 pgtk_cr_draw_image (struct frame *f, Emacs_GC *gc, cairo_pattern_t *image,
-		    int src_x, int src_y, int width, int height,
-		    int dest_x, int dest_y, bool overlay_p)
+		    cairo_surface_t **surface, int src_x, int src_y, int width,
+		    int height, int dest_x, int dest_y, bool overlay_p)
 {
   cairo_t *cr = pgtk_begin_cr_clip (f);
 
@@ -2190,12 +2190,40 @@ pgtk_cr_draw_image (struct frame *f, Emacs_GC *gc, cairo_pattern_t *image,
 
   cairo_translate (cr, dest_x - src_x, dest_y - src_y);
 
-  cairo_surface_t *surface;
-  cairo_pattern_get_surface (image, &surface);
-  cairo_format_t format = cairo_image_surface_get_format (surface);
+  cairo_surface_t *image_surface;
+  cairo_pattern_get_surface (image, &image_surface);
+  cairo_format_t format = cairo_image_surface_get_format (image_surface);
+
   if (format != CAIRO_FORMAT_A8 && format != CAIRO_FORMAT_A1)
     {
-      cairo_set_source (cr, image);
+      /* Since drawing surfaces is much faster than drawing patterns,
+	 let's try to convert this image into a surface and cache it.  */
+      if (surface)
+	{
+	  if (!*surface)
+	    {
+	      int image_width = cairo_image_surface_get_width (image_surface);
+	      int image_height = cairo_image_surface_get_height (image_surface);
+
+	      *surface = cairo_surface_create_similar_image (image_surface,
+							     format,
+							     image_width,
+							     image_height);
+
+	      cairo_t *cr2 = cairo_create (*surface);
+
+	      cairo_set_source (cr2, image);
+	      cairo_paint (cr2);
+	      cairo_destroy (cr2);
+	    }
+
+	  cairo_set_source_surface (cr, *surface, 0, 0);
+	}
+      else
+	{
+	  cairo_set_source (cr, image);
+	}
+
       cairo_fill (cr);
     }
   else
@@ -2235,6 +2263,7 @@ pgtk_draw_image_foreground (struct glyph_string *s)
       cairo_t *cr = pgtk_begin_cr_clip (s->f);
       pgtk_set_glyph_string_clipping (s, cr);
       pgtk_cr_draw_image (s->f, &s->xgcv, s->img->cr_data,
+			  (cairo_surface_t **) &s->img->cr_surface,
 			  s->slice.x, s->slice.y, s->slice.width, s->slice.height,
 			  x, y, true);
       if (!s->img->mask)
@@ -3641,7 +3670,7 @@ pgtk_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
 			   : FRAME_X_OUTPUT (f)->cursor_color)
 			: face->foreground);
       gcv.background = face->background;
-      pgtk_cr_draw_image (f, &gcv, fringe_bmp[p->which], 0, p->dh,
+      pgtk_cr_draw_image (f, &gcv, fringe_bmp[p->which], NULL, 0, p->dh,
 			  p->wd, p->h, p->x, p->y, p->overlay_p);
     }
 
-- 
2.50.1