From ed987b2de799306d6147fb4fe471178d8d127bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 22 Mar 2020 20:22:17 +0100 Subject: [PATCH] render: use shm_scroll() when we believe it will be faster shm_scroll() is fast when memmove() is slow. That is, when scrolling a *small* amount of lines, shm_scroll() is fast. Conversely, when scrolling a *large* number of lines, memmove() is fast. For now, assume the two methods perform _roughly_ the same given a certain number of bytes they have to touch. This means we should use shm_scroll() when number of scroll lines is less than half the screen. Otherwise we use memmove. Since we need to repair the bottom scroll region after a shm_scroll, those lines are added to the count when determining which method to use. TODO: * Check if it's worth to do shm scrolling when we have a top scroll region. * Do more performance testing with a) various amount of scrolling lines, and b) larger bottom scroll regions. --- render.c | 242 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 187 insertions(+), 55 deletions(-) diff --git a/render.c b/render.c index d8a26060..7189c9ba 100644 --- a/render.c +++ b/render.c @@ -22,6 +22,9 @@ #include "selection.h" #include "shm.h" +#define TIME_FRAME_RENDERING 0 +#define TIME_SCROLL_DAMAGE 0 + #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) @@ -475,30 +478,181 @@ draw_cursor: return cell_cols; } +static void +render_margin(struct terminal *term, struct buffer *buf, int start_line, int end_line) +{ + /* Fill area outside the cell grid with the default background color */ + int rmargin = term->width - term->margins.right; + int bmargin = term->height - term->margins.bottom; + + uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg; + pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha); + if (term->is_searching) + pixman_color_dim(&bg); + + if (start_line == 0) { + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix, &bg, 1, + &(pixman_rectangle16_t){0, 0, term->width, term->margins.top}); + wl_surface_damage_buffer( + term->window->surface, 0, 0, term->width, term->margins.top); + } + + if (end_line == term->rows) { + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix, &bg, 1, + &(pixman_rectangle16_t){0, bmargin, term->width, term->margins.bottom}); + wl_surface_damage_buffer( + term->window->surface, 0, bmargin, term->width, term->margins.bottom); + } + + pixman_image_fill_rectangles( + PIXMAN_OP_SRC, buf->pix, &bg, 2, + (pixman_rectangle16_t[]){ + {0, term->margins.top + start_line * term->cell_height, + term->margins.left, (end_line - start_line) * term->cell_height}, /* Left */ + {rmargin, term->margins.top, term->margins.right, (end_line - start_line) * term->cell_height}, /* Right */ + }); + + wl_surface_damage_buffer( + term->window->surface, 0, term->margins.top, term->margins.left, (end_line - start_line) * term->cell_height); + wl_surface_damage_buffer( + term->window->surface, rmargin, term->margins.top, term->margins.right, (end_line - start_line) * term->cell_height); +} + static void grid_render_scroll(struct terminal *term, struct buffer *buf, const struct damage *dmg) { - int dst_y = term->margins.top + (dmg->scroll.region.start + 0) * term->cell_height; - int src_y = term->margins.top + (dmg->scroll.region.start + dmg->scroll.lines) * term->cell_height; int height = (dmg->scroll.region.end - dmg->scroll.region.start - dmg->scroll.lines) * term->cell_height; - LOG_DBG("damage: SCROLL: %d-%d by %d lines (dst-y: %d, src-y: %d, " - "height: %d, stride: %d, mmap-size: %zu)", - dmg->scroll.region.start, dmg->scroll.region.end, - dmg->scroll.lines, - dst_y, src_y, height, buf->stride, - buf->size); + LOG_DBG( + "damage: SCROLL: %d-%d by %d lines", + dmg->scroll.region.start, dmg->scroll.region.end, dmg->scroll.lines); - if (height > 0) { + if (height <= 0) + return; + +#if TIME_SCROLL_DAMAGE + struct timeval start_time; + gettimeofday(&start_time, NULL); +#endif + + int dst_y = term->margins.top + (dmg->scroll.region.start + 0) * term->cell_height; + int src_y = term->margins.top + (dmg->scroll.region.start + dmg->scroll.lines) * term->cell_height; + + /* + * SHM scrolling can be *much* faster, but it depends on how many + * lines we're scrolling, and how much repairing we need to do. + * + * In short, scrolling a *large* number of rows is faster with a + * memmove, while scrolling a *small* number of lines is faster + * with SHM scrolling. + * + * However, since we need to restore the bottom scrolling region + * when SHM scrolling, we also need to take this into account. + * + * Finally, restoring the window margins is a *huge* performance + * hit when scrolling a large number of lines (in addition to the + * sloweness of SHM scrolling as method). + * + * So, we need to figure out when to SHM scroll, and when to + * memmove. + * + * For now, assume that the both methods perform rougly the same, + * given an equal number of bytes to move/allocate, and use the + * method that results in the least amount of bytes to touch. + * + * Since number of lines directly translates to bytes, we can + * simply count lines. + * + * SHM scrolling needs to first "move" (punch hole + allocate) + * dmg->scroll.lines number of lines, and then we need to restore + * the bottom scroll region. + * + * If the total number of lines is less than half the screen - use + * SHM. Otherwise use memmove. + */ + bool try_shm_scroll = + dmg->scroll.region.start == 0 && + dmg->scroll.lines + (term->rows - dmg->scroll.region.end) < term->rows / 2; + + bool did_shm_scroll = false; + + //try_shm_scroll = false; + //try_shm_scroll = true; + + if (try_shm_scroll) { + did_shm_scroll = shm_scroll( + term->wl->shm, buf, dmg->scroll.lines * term->cell_height); + + /* + * When SHM scrolling succeeded, the scrolled in area is made + * up of newly allocated, zero-initialized memory. Thus we'll + * need to both copy the bottom scrolling region, and + * re-render the window margins. + * + * This is different from when we scroll with a simple + * memmove, since in that case, the scrolling region and + * margins are *copied*, and thus the original region+margin + * remains in place. + */ + + if (!did_shm_scroll) + LOG_DBG("fast scroll failed"); + } else { + LOG_DBG( + "skipping SHM scroll " + "(region.start=%d, bottom-region-lines=%d, term-lines=%d)", + dmb->scroll.region.start, scroll_region_lines, term->rows); + } + + if (did_shm_scroll) { + + /* Restore bottom scrolling region */ + if (dmg->scroll.region.end < term->rows) { + int src = dmg->scroll.region.end - dmg->scroll.lines; + int dst = dmg->scroll.region.end; + size_t amount = max(0, term->rows - dmg->scroll.region.end); + + LOG_DBG("memmoving %zu lines of scroll region", amount); + + assert(src >= 0); + + uint8_t *raw = buf->mmapped; + memmove(raw + dst * term->cell_height * buf->stride, + raw + src * term->cell_height * buf->stride, + amount * term->cell_height * buf->stride); + } + + /* Restore margins */ + render_margin( + term, buf, dmg->scroll.region.end - dmg->scroll.lines, term->rows); + } + + /* Fallback for when we either cannot do SHM scrolling, or it failed */ + if (!did_shm_scroll) { uint8_t *raw = buf->mmapped; memmove(raw + dst_y * buf->stride, raw + src_y * buf->stride, height * buf->stride); - - wl_surface_damage_buffer( - term->window->surface, term->margins.left, dst_y, term->width - term->margins.left - term->margins.right, height); } + +#if TIME_SCROLL_DAMAGE + struct timeval end_time; + gettimeofday(&end_time, NULL); + + struct timeval memmove_time; + timersub(&end_time, &start_time, &memmove_time); + LOG_INFO("scrolled %dKB (%d lines) using %s in %lds %ldus", + height * buf->stride / 1024, dmg->scroll.lines, + did_shm_scroll ? "SHM" : try_shm_scroll ? "memmove (SHM failed)" : "memmove", + memmove_time.tv_sec, memmove_time.tv_usec); +#endif + + wl_surface_damage_buffer( + term->window->surface, term->margins.left, dst_y, + term->width - term->margins.left - term->margins.right, height); } static void @@ -1056,8 +1210,6 @@ grid_render(struct terminal *term) if (term->is_shutting_down) return; -#define TIME_FRAME_RENDERING 0 - #if TIME_FRAME_RENDERING struct timeval start_time; gettimeofday(&start_time, NULL); @@ -1070,12 +1222,7 @@ grid_render(struct terminal *term) struct buffer *buf = shm_get_buffer( term->wl->shm, term->width, term->height, cookie); - wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0); - - pixman_image_t *pix = buf->pix; - pixman_region16_t clip; - pixman_region_init_rect(&clip, term->margins.left, term->margins.top, term->cols * term->cell_width, term->rows * term->cell_height); - pixman_image_set_clip_region(pix, &clip); + pixman_image_set_clip_region(buf->pix, NULL); /* If we resized the window, or is flashing, or just stopped flashing */ if (term->render.last_buf != buf || @@ -1091,7 +1238,10 @@ grid_render(struct terminal *term) { static bool has_warned = false; if (!has_warned) { - LOG_WARN("it appears your Wayland compositor does not support buffer re-use for SHM clients; expect lower performance."); + LOG_WARN( + "it appears your Wayland compositor does not support " + "buffer re-use for SHM clients; expect lower " + "performance."); has_warned = true; } @@ -1100,35 +1250,7 @@ grid_render(struct terminal *term) } else { - /* Fill area outside the cell grid with the default background color */ - int rmargin = term->width - term->margins.right; - int bmargin = term->height - term->margins.bottom; - - uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg; - pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, term->colors.alpha); - if (term->is_searching) - pixman_color_dim(&bg); - - pixman_image_set_clip_region(pix, NULL); - pixman_image_fill_rectangles( - PIXMAN_OP_SRC, pix, &bg, 4, - (pixman_rectangle16_t[]){ - {0, 0, term->width, term->margins.top}, /* Top */ - {0, 0, term->margins.left, term->height}, /* Left */ - {rmargin, 0, term->margins.right, term->height}, /* Right */ - {0, bmargin, term->width, term->margins.bottom}}); /* Bottom */ - pixman_image_set_clip_region(pix, &clip); - - wl_surface_damage_buffer( - term->window->surface, 0, 0, term->width, term->margins.top); - wl_surface_damage_buffer( - term->window->surface, 0, 0, term->margins.left, term->height); - wl_surface_damage_buffer( - term->window->surface, rmargin, 0, term->margins.right, term->height); - wl_surface_damage_buffer( - term->window->surface, 0, bmargin, term->width, term->margins.bottom); - - /* Force a full grid refresh */ + render_margin(term, buf, 0, term->rows); term_damage_view(term); } @@ -1137,6 +1259,13 @@ grid_render(struct terminal *term) term->render.was_searching = term->is_searching; } + pixman_region16_t clip; + pixman_region_init_rect( + &clip, + term->margins.left, term->margins.top, + term->cols * term->cell_width, term->rows * term->cell_height); + pixman_image_set_clip_region(buf->pix, &clip); + /* Erase old cursor (if we rendered a cursor last time) */ if (term->render.last_cursor.cell != NULL) { @@ -1147,7 +1276,7 @@ grid_render(struct terminal *term) /* If cell is already dirty, it will be rendered anyway */ if (cell->attrs.clean) { cell->attrs.clean = 0; - int cols = render_cell(term, pix, cell, at.col, at.row, false); + int cols = render_cell(term, buf->pix, cell, at.col, at.row, false); wl_surface_damage_buffer( term->window->surface, @@ -1181,6 +1310,9 @@ grid_render(struct terminal *term) tll_remove(term->grid->scroll_damage, it); } + pixman_image_set_clip_region(buf->pix, &clip); + wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0); + if (term->render.workers.count > 0) { term->render.workers.buf = buf; @@ -1220,7 +1352,7 @@ grid_render(struct terminal *term) if (!row->dirty) continue; - render_row(term, pix, row, r); + render_row(term, buf->pix, row, r); row->dirty = false; wl_surface_damage_buffer( @@ -1274,7 +1406,7 @@ grid_render(struct terminal *term) cell->attrs.clean = 0; term->render.last_cursor.cell = cell; int cols_updated = render_cell( - term, pix, cell, term->cursor.point.col, view_aligned_row, true); + term, buf->pix, cell, term->cursor.point.col, view_aligned_row, true); wl_surface_damage_buffer( term->window->surface, @@ -1283,13 +1415,13 @@ grid_render(struct terminal *term) cols_updated * term->cell_width, term->cell_height); } - render_sixel_images(term, pix); + render_sixel_images(term, buf->pix); if (term->flash.active) { /* Note: alpha is pre-computed in each color component */ /* TODO: dim while searching */ pixman_image_fill_rectangles( - PIXMAN_OP_OVER, pix, + PIXMAN_OP_OVER, buf->pix, &(pixman_color_t){.red=0x7fff, .green=0x7fff, .blue=0, .alpha=0x7fff}, 1, &(pixman_rectangle16_t){0, 0, term->width, term->height});