[vte] draw: Optimise invalidation and painting



commit 4b53da3627efead28ee2034c9ad368e37c378da1
Author: Christian Persch <chpe src gnome org>
Date:   Thu May 30 23:06:56 2019 +0200

    draw: Optimise invalidation and painting
    
    Cells may contain content that draws outside the cell area. Limit this
    to only allow drawing up to the underline position of the line above,
    and the overline position of the line below, by clipping the cairo
    context to that area before drawing each line.
    
    Only paint lines that are in the invalidated region (or which due to
    overdrawing may draw into the invalidated region).
    
    https://gitlab.gnome.org/GNOME/vte/issues/56

 src/vte.cc         | 183 ++++++++++++++++-------------------------------------
 src/vtedraw.cc     |  16 +++++
 src/vtedraw.hh     |  22 +++++++
 src/vteinternal.hh |  19 +++++-
 4 files changed, 109 insertions(+), 131 deletions(-)
---
diff --git a/src/vte.cc b/src/vte.cc
index 2989f243..cd9a565e 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -284,8 +284,9 @@ Terminal::invalidate_rows(vte::grid::row_t row_start,
         int xend = m_column_count * m_cell_width + 1;
         rect.width = xend - rect.x;
 
-        rect.y = row_to_pixel(row_start) - 1;
-        int yend = row_to_pixel(row_end + 1) + 1;
+        /* Always add at least VTE_LINE_WIDTH pixels so the outline block cursor fits */
+        rect.y = row_to_pixel(row_start) - std::max(cell_overflow_top(), VTE_LINE_WIDTH);
+        int yend = row_to_pixel(row_end + 1) + std::max(cell_overflow_bottom(), VTE_LINE_WIDTH);
         rect.height = yend - rect.y;
 
        _vte_debug_print (VTE_DEBUG_UPDATES,
@@ -8803,17 +8804,18 @@ Terminal::draw_cells_with_attributes(struct _vte_draw_text_request *items,
  * attributes and bundling them together. */
 void
 Terminal::draw_rows(VteScreen *screen_,
-                              vte::grid::row_t start_row,
-                              vte::grid::row_t end_row,
-                              gint start_y,
-                              gint column_width,
-                              gint row_height)
+                    cairo_region_t const* region,
+                    vte::grid::row_t start_row,
+                    vte::grid::row_t end_row,
+                    gint start_y, /* must be the start of a row */
+                    gint column_width,
+                    gint row_height)
 {
         struct _vte_draw_text_request *items;
         vte::grid::row_t row;
         vte::grid::column_t i, j, col;
-        long y;
-        guint fore, nfore, back, nback, deco, ndeco;
+        int y;
+        guint fore = VTE_DEFAULT_FG, nfore, back = VTE_DEFAULT_BG, nback, deco = VTE_DEFAULT_FG, ndeco;
         gboolean hyperlink = FALSE, nhyperlink, hilite = FALSE, nhilite;
         gboolean selected;
         uint32_t attr = 0, nattr;
@@ -8832,8 +8834,21 @@ Terminal::draw_rows(VteScreen *screen_,
          * chopped off by another cell's background, not even across changes of the
          * background or any other attribute.
          * Process each row independently. */
-        for (row = start_row, y = start_y; row < end_row; row++, y += row_height) {
+        int const rect_width = get_allocated_width();
+
+        /* The rect contains the area of the row, and is moved row-wise in the loop. */
+        auto rect = cairo_rectangle_int_t{-m_padding.left, start_y, rect_width, row_height};
+        for (row = start_row, y = start_y;
+             row < end_row;
+             row++, y += row_height, rect.y = y /* same as rect.y += row_height */) {
+                /* Check whether we need to draw this row at all */
+                if (cairo_region_contains_rectangle(region, &rect) == CAIRO_REGION_OVERLAP_OUT)
+                        continue;
+
                row_data = find_row_data(row);
+                if (row_data == nullptr)
+                        continue; /* Skip row. FIXME: just paint this row empty? */
+
                 i = j = 0;
                 /* Walk the line.
                  * Locate runs of identical bg colors within a row, and paint each run as a single 
rectangle. */
@@ -8873,13 +8888,28 @@ Terminal::draw_rows(VteScreen *screen_,
         }
 
 
-        /* Render the text. */
-        for (row = start_row, y = start_y; row < end_row; row++, y += row_height) {
-                row_data = find_row_data(row);
-                if (row_data == NULL) {
-                        /* Skip row. */
+        /* Render the text.
+         * The rect contains the area of the row (enlarged a bit at the top and bottom
+         * to allow the text to overdraw a bit), and is moved row-wise in the loop.
+         */
+        rect = cairo_rectangle_int_t{-m_padding.left,
+                                     start_y - cell_overflow_top(),
+                                     rect_width,
+                                     row_height + cell_overflow_top() + cell_overflow_bottom()};
+
+        for (row = start_row, y = start_y;
+             row < end_row;
+             row++, y += row_height, rect.y += row_height) {
+                /* Check whether we need to draw this row at all */
+                if (cairo_region_contains_rectangle(region, &rect) == CAIRO_REGION_OVERLAP_OUT)
                         continue;
-                }
+
+                row_data = find_row_data(row);
+                if (row_data == NULL)
+                        continue; /* Skip row. */
+
+                /* Ensure that drawing is restricted to the cell (plus the overdraw area) */
+                _vte_draw_autoclip_t clipper{m_draw, &rect};
 
                 /* Walk the line.
                  * Locate runs of identical attributes within a row, and draw each run using a single 
draw_cells() call. */
@@ -8988,75 +9018,6 @@ Terminal::draw_rows(VteScreen *screen_,
         }
 }
 
-void
-Terminal::expand_rectangle(cairo_rectangle_int_t& rect) const
-{
-       /* increase the paint by one pixel on all sides to force the
-        * inclusion of neighbouring cells */
-        vte::grid::row_t row = pixel_to_row(MAX(0, rect.y - 1));
-        /* Both the value given by MIN() and row_stop are exclusive.
-         * _vte_terminal_pixel_to_row expects an actual value corresponding
-         * to the bottom visible pixel, hence the - 1 + 1 magic. */
-        vte::grid::row_t row_stop = pixel_to_row(MIN(rect.height + rect.y + 1, 
m_view_usable_extents.height()) - 1) + 1;
-        if (row_stop <= row)
-                return;
-
-        vte::grid::column_t col = MAX(0, (rect.x - 1) / m_cell_width);
-        vte::grid::column_t col_stop = MIN(howmany(rect.width + rect.x + 1, m_cell_width), m_column_count);
-        if (col_stop <= col)
-                return;
-
-        cairo_rectangle_int_t old_rect = rect;
-        rect.x = col * m_cell_width;
-        rect.width = (col_stop - col) * m_cell_width;
-        rect.y = row_to_pixel(row);
-        rect.height = (row_stop - row) * m_cell_height;
-
-        _vte_debug_print (VTE_DEBUG_UPDATES,
-                          "expand_rectangle"
-                          "    (%d,%d)x(%d,%d) pixels,"
-                          " (%ld,%ld)x(%ld,%ld) cells"
-                          " [(%d,%d)x(%d,%d) pixels]\n",
-                          old_rect.x, old_rect.y, old_rect.width, old_rect.height,
-                          col, row, col_stop - col, row_stop - row,
-                          rect.x, rect.y, rect.width, rect.height);
-}
-
-void
-Terminal::paint_area(GdkRectangle const* area)
-{
-        vte::grid::row_t row, row_stop;
-
-        row = pixel_to_row(MAX(0, area->y));
-        /* Both the value given by MIN() and row_stop are exclusive.
-         * _vte_terminal_pixel_to_row expects an actual value corresponding
-         * to the bottom visible pixel, hence the - 1 + 1 magic. */
-        row_stop = pixel_to_row(MIN(area->height + area->y,
-                                    get_allocated_height() - m_padding.top - m_padding.bottom) - 1) + 1;
-       if (row_stop <= row) {
-               return;
-       }
-       _vte_debug_print (VTE_DEBUG_UPDATES,
-                       "paint_area"
-                       "       (%d,%d)x(%d,%d) pixels,"
-                       " (%ld,%ld)x(%ld,%ld) cells"
-                       " [(%ld,%ld)x(%ld,%ld) pixels]\n",
-                       area->x, area->y, area->width, area->height,
-                        0L, row, m_column_count, row_stop - row,
-                        0L,
-                       row * m_cell_height,
-                        m_column_count * m_cell_width,
-                       (row_stop - row) * m_cell_height);
-
-       /* Now we're ready to draw the text.  Iterate over the rows we
-        * need to draw. */
-       draw_rows(m_screen,
-                             row, row_stop,
-                             row_to_pixel(row),
-                             m_cell_width,
-                             m_cell_height);
-}
-
 void
 Terminal::paint_cursor()
 {
@@ -9335,36 +9296,6 @@ Terminal::widget_draw(cairo_t *cr)
         /* Transform to view coordinates */
         cairo_region_translate(region, -m_padding.left, -m_padding.top);
 
-        cairo_rectangle_int_t *rectangles;
-        int n, n_rectangles;
-        n_rectangles = cairo_region_num_rectangles (region);
-        rectangles = g_new(cairo_rectangle_int_t, n_rectangles);
-        for (n = 0; n < n_rectangles; n++) {
-                cairo_region_get_rectangle (region, n, &rectangles[n]);
-        }
-
-        /* don't bother to enlarge an invalidate all */
-        if (!(n_rectangles == 1
-              && rectangles[0].width == allocated_width
-              && rectangles[0].height == allocated_height)) {
-                cairo_region_t *rr = cairo_region_create ();
-                /* Expand the rectangles so that they cover whole cells,
-                 * to avoid overlapping XY bands.
-                 */
-                for (n = 0; n < n_rectangles; n++) {
-                        expand_rectangle(rectangles[n]);
-                        cairo_region_union_rectangle(rr, &rectangles[n]);
-                }
-                g_free(rectangles);
-
-                n_rectangles = cairo_region_num_rectangles (rr);
-                rectangles = g_new (cairo_rectangle_int_t, n_rectangles);
-                for (n = 0; n < n_rectangles; n++) {
-                        cairo_region_get_rectangle(rr, n, &rectangles[n]);
-                }
-                cairo_region_destroy(rr);
-        }
-
         /* Whether blinking text should be visible now */
         m_text_blink_state = true;
         text_blink_enabled_now = m_text_blink_mode & (m_has_focus ? VTE_TEXT_BLINK_FOCUSED : 
VTE_TEXT_BLINK_UNFOCUSED);
@@ -9377,20 +9308,14 @@ Terminal::widget_draw(cairo_t *cr)
         m_text_to_blink = false;
 
         /* and now paint them */
-        for (n = 0; n < n_rectangles; n++) {
-                /* paint_area() paints more than asked to (entire rows). Without an individual
-                 * cropping rectangle around each invocation we might end up with text getting
-                 * overstriked with itself, thus appearing bolder. See vte#4.
-                 * TODO: refactor so that paint_area() is called at most once for each row, see vte#56. */
-                cairo_save(cr);
-                cairo_rectangle(cr, rectangles[n].x, rectangles[n].y, rectangles[n].width, 
rectangles[n].height);
-                cairo_clip(cr);
-
-                paint_area(&rectangles[n]);
-
-                cairo_restore(cr);
-        }
-        g_free (rectangles);
+        auto const first_row = first_displayed_row();
+        draw_rows(m_screen,
+                  region,
+                  first_row,
+                  last_displayed_row() + 1,
+                  row_to_pixel(first_row),
+                  m_cell_width,
+                  m_cell_height);
 
        paint_im_preedit_string();
 
diff --git a/src/vtedraw.cc b/src/vtedraw.cc
index 4f56f611..215e27ee 100644
--- a/src/vtedraw.cc
+++ b/src/vtedraw.cc
@@ -812,6 +812,22 @@ _vte_draw_set_cairo (struct _vte_draw *draw,
         }
 }
 
+void
+_vte_draw_clip(struct _vte_draw *draw,
+               cairo_rectangle_int_t const* rect)
+{
+        cairo_save(draw->cr);
+        cairo_rectangle(draw->cr,
+                        rect->x, rect->y, rect->width, rect->height);
+        cairo_clip(draw->cr);
+}
+
+void
+_vte_draw_unclip(struct _vte_draw *draw)
+{
+        cairo_restore(draw->cr);
+}
+
 static void
 _vte_draw_set_source_color_alpha (struct _vte_draw *draw,
                                   vte::color::rgb const* color,
diff --git a/src/vtedraw.hh b/src/vtedraw.hh
index f10af608..7352d376 100644
--- a/src/vtedraw.hh
+++ b/src/vtedraw.hh
@@ -55,6 +55,11 @@ void _vte_draw_free(struct _vte_draw *draw);
 void _vte_draw_set_cairo(struct _vte_draw *draw,
                          cairo_t *cr);
 
+void _vte_draw_clip(struct _vte_draw *draw,
+                    cairo_rectangle_int_t const* rect);
+
+void _vte_draw_unclip(struct _vte_draw *draw);
+
 void _vte_draw_clear(struct _vte_draw *draw,
                     gint x, gint y, gint width, gint height,
                      vte::color::rgb const* color, double alpha);
@@ -102,4 +107,21 @@ _vte_draw_draw_undercurl(struct _vte_draw *draw,
 
 G_END_DECLS
 
+class _vte_draw_autoclip_t {
+private:
+        struct _vte_draw* m_draw;
+public:
+        _vte_draw_autoclip_t(struct _vte_draw* draw,
+                             cairo_rectangle_int_t const* rect)
+                : m_draw{draw}
+        {
+                _vte_draw_clip(m_draw, rect);
+        }
+
+        ~_vte_draw_autoclip_t()
+        {
+                _vte_draw_unclip(m_draw);
+        }
+};
+
 #endif
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index 616d576c..f321105e 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -512,6 +512,22 @@ public:
         glong m_cell_width;
         glong m_cell_height;
 
+        /* We allow the cell's text to draw a bit outside the cell at the top
+         * and bottom. The following two functions return how much is the
+         * maximally allowed overdraw (in px).
+         */
+        inline constexpr auto cell_overflow_top() const noexcept
+        {
+                /* Allow overdrawing up into the underline of the cell on top */
+                return int(m_cell_height - m_underline_position);
+        }
+
+        inline constexpr auto cell_overflow_bottom() const noexcept
+        {
+                /* Allow overdrawing up into the overline of the cell on bottom */
+                return int(m_overline_position + m_overline_thickness);
+        }
+
        /* Data used when rendering the text which reflects server resources
         * and data, which should be dropped when unrealizing and (re)created
         * when realizing. */
@@ -738,8 +754,6 @@ public:
                                 int blink_time,
                                 int blink_timeout) noexcept;
 
-        void expand_rectangle(cairo_rectangle_int_t& rect) const;
-        void paint_area(GdkRectangle const* area);
         void paint_cursor();
         void paint_im_preedit_string();
         void draw_cells(struct _vte_draw_text_request *items,
@@ -770,6 +784,7 @@ public:
                                         int column_width,
                                         int height);
         void draw_rows(VteScreen *screen,
+                       cairo_region_t const* region,
                        vte::grid::row_t start_row,
                        long row_count,
                        gint start_y,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]