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.
This commit is contained in:
Daniel Eklöf 2020-03-22 20:22:17 +01:00
parent 1224807f50
commit ed987b2de7
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F

242
render.c
View file

@ -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});