diff --git a/src/vte.cc b/src/vte.cc
index 53714cc4..2929fd36 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -9015,8 +9015,9 @@ VteTerminalPrivate::draw_cells(struct _vte_draw_text_request *items,
                                 for (int j = 0; j < columns; j++) {
                                         _vte_draw_draw_undercurl(m_draw,
                                                                  x + j * column_width,
-                                                                 y + m_undercurl_position,
+                                                                 y,
                                                                  column_width,
+                                                                 m_undercurl_position,
                                                                  m_undercurl_thickness,
                                                                  &dc, VTE_DRAW_OPAQUE);
                                 }
diff --git a/src/vtedraw.cc b/src/vtedraw.cc
index 0b75cb0a..76718680 100644
--- a/src/vtedraw.cc
+++ b/src/vtedraw.cc
@@ -758,6 +758,12 @@ struct _vte_draw {
         GtkBorder char_spacing;
 
 	cairo_t *cr;
+
+        /* Cache the undercurl's input parameters and rendered look */
+        cairo_surface_t *undercurl_surface;
+        cairo_t *undercurl_cr;
+        gint undercurl_width, undercurl_thickness;
+        gdouble undercurl_pos;
 };
 
 struct _vte_draw *
@@ -768,6 +774,8 @@ _vte_draw_new (void)
 	/* Create the structure. */
 	draw = g_slice_new0 (struct _vte_draw);
 
+        draw->undercurl_width = -1;
+
 	_vte_debug_print (VTE_DEBUG_DRAW, "draw_new\n");
 
 	return draw;
@@ -788,6 +796,9 @@ _vte_draw_free (struct _vte_draw *draw)
 		}
 	}
 
+        cairo_surface_destroy (draw->undercurl_surface);
+        cairo_destroy (draw->undercurl_cr);
+
 	g_slice_free (struct _vte_draw, draw);
 }
 
@@ -1677,28 +1688,58 @@ _vte_draw_get_undercurl_height(gint width, int line_width)
 
 void
 _vte_draw_draw_undercurl(struct _vte_draw *draw,
-                         gint x, double y, gint width,
-                         int line_width,
+                         gint x, gint y,
+                         gint width, double pos, int line_width,
                          vte::color::rgb const *color, double alpha)
 {
         double rad = _vte_draw_get_undercurl_rad(width);
-        double yc = y + _vte_draw_get_undercurl_height(width, line_width) / 2;
+        double pos_bottom = pos + _vte_draw_get_undercurl_height(width, line_width);
+        double pos_center = (pos + pos_bottom) / 2;
+
+        gint pos_int = pos;  /* floor */
+        gint pos_bottom_int = pos_bottom + 1;  /* ceil, kind of */
 
         g_assert(draw->cr);
 
         _vte_debug_print (VTE_DEBUG_DRAW,
-                        "draw_undercurl (%d, %f, %d, color=(%d,%d,%d,%.3f))\n",
-                        x,y,width,
+                        "draw_undercurl (%d, %d, %d, %f, color=(%d,%d,%d,%.3f))\n",
+                        x, y, width, pos,
                         color->red, color->green, color->blue,
                         alpha);
 
+        if (G_UNLIKELY (width != draw->undercurl_width ||
+                        !_vte_double_equal(pos, draw->undercurl_pos) ||
+                        line_width != draw->undercurl_thickness)) {
+
+                _vte_debug_print (VTE_DEBUG_DRAW,
+                                  "caching undercurl shape\n");
+
+                cairo_surface_destroy (draw->undercurl_surface);
+                cairo_destroy (draw->undercurl_cr);
+
+                /* Add a line_width of margin horizontally on both sides, for nice antialias overflowing. */
+                draw->undercurl_surface = cairo_image_surface_create (CAIRO_FORMAT_A8,
+                                                                      width + 2 * line_width,
+                                                                      pos_bottom_int - pos_int);
+                draw->undercurl_cr = cairo_create (draw->undercurl_surface);
+
+                cairo_set_operator (draw->undercurl_cr, CAIRO_OPERATOR_OVER);
+                cairo_new_sub_path(draw->undercurl_cr);
+                /* First quarter circle, similar to the left half of the tilde symbol. */
+                cairo_arc (draw->undercurl_cr, line_width + width / 4., pos_center - pos_int + width / 4., rad, M_PI * 5 / 4, M_PI * 7 / 4);
+                /* Second quarter circle, similar to the right half of the tilde symbol. */
+                cairo_arc_negative (draw->undercurl_cr, line_width + width * 3 / 4., pos_center - pos_int - width / 4., rad, M_PI * 3 / 4, M_PI / 4);
+                cairo_set_source_rgb (draw->undercurl_cr, 0.0, 0.0, 0.0);  /* probably not needed at all */
+                cairo_set_line_width (draw->undercurl_cr, line_width);
+                cairo_stroke (draw->undercurl_cr);
+
+                draw->undercurl_width = width;
+                draw->undercurl_pos = pos;
+                draw->undercurl_thickness = line_width;
+        }
+
         cairo_set_operator (draw->cr, CAIRO_OPERATOR_OVER);
         cairo_new_sub_path(draw->cr);
-        /* First quarter circle, similar to the left half of the tilde symbol. */
-        cairo_arc (draw->cr, x + width / 4., yc + width / 4., rad, M_PI * 5 / 4, M_PI * 7 / 4);
-        /* Second quarter circle, similar to the right half of the tilde symbol. */
-        cairo_arc_negative (draw->cr, x + width * 3 / 4., yc - width / 4., rad, M_PI * 3 / 4, M_PI / 4);
         _vte_draw_set_source_color_alpha (draw, color, alpha);
-        cairo_set_line_width (draw->cr, line_width);
-        cairo_stroke (draw->cr);
+        cairo_mask_surface (draw->cr, draw->undercurl_surface, x - line_width, y + pos_int);
 }
diff --git a/src/vtedraw.hh b/src/vtedraw.hh
index 745b8413..bfe69170 100644
--- a/src/vtedraw.hh
+++ b/src/vtedraw.hh
@@ -98,8 +98,8 @@ _vte_draw_get_undercurl_height(gint width, int line_width);
 
 void
 _vte_draw_draw_undercurl(struct _vte_draw *draw,
-                         gint x, double y, gint width,
-                         int line_width,
+                         gint x, gint y,
+                         gint width, double pos, int line_width,
                          vte::color::rgb const *color, double alpha);
 
 G_END_DECLS
