render: handle compositors that does buffer swapping

Not all compositors support buffer re-use. I.e. they will call the
frame callback *before* the previous buffer has been
released. Effectively causing us to swap between two buffers.

Previously, this made us enter an infinite re-render loop, since we
considered the window 'dirty' (and in need of re-draw) when the buffer
is different from last redraw.

Now, we detect the buffer swapping case; size must match, and we must
not have any other condition that require a full repaint.

In this case, we can memcpy() the old buffer to the new one, without
dirtying the entire grid. We then update only the dirty cells (and
scroll damage).

Note that there was a bug here, where we erased the old
cursor *before* checking for a new buffer. This worked when the buffer
had *not* changed.

Now that we need to handle the case where it *has* changed, we must do
the memcpy() *before* we erase the cursor, or the re-painted cell is
lost.

This makes foot work on Plasma, without burning CPU. The memcpy() does
incur a performance penalty, but we're still (much) faster than
e.g. konsole. In fact, we're still mostly on par with Alacritty.
This commit is contained in:
Daniel Eklöf 2019-09-27 19:33:45 +02:00
parent b87bf0dd9d
commit 67905c6000
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F

View file

@ -444,40 +444,29 @@ grid_render(struct terminal *term)
bool all_clean = tll_length(term->grid->scroll_damage) == 0; bool all_clean = tll_length(term->grid->scroll_damage) == 0;
/* Erase old cursor (if we rendered a cursor last time) */
if (term->render.last_cursor.cell != NULL) {
struct cell *cell = term->render.last_cursor.cell;
struct coord at = term->render.last_cursor.in_view;
if (cell->attrs.clean) {
cell->attrs.clean = 0;
render_cell(term, pix, cell, at.col, at.row, false);
wl_surface_damage_buffer(
term->wl.surface,
term->x_margin + at.col * term->cell_width,
term->y_margin + at.row * term->cell_height,
term->cell_width, term->cell_height);
}
term->render.last_cursor.cell = NULL;
if (term->render.last_cursor.actual.col != term->cursor.col ||
term->render.last_cursor.actual.row != term->cursor.row)
{
/* Detect cursor movement - we don't dirty cells touched
* by the cursor, since only the final cell matters. */
all_clean = false;
}
}
if (term->flash.active)
term_damage_view(term);
/* If we resized the window, or is flashing, or just stopped flashing */ /* If we resized the window, or is flashing, or just stopped flashing */
if (term->render.last_buf != buf || if (term->render.last_buf != buf ||
term->flash.active || term->render.was_flashing || term->flash.active || term->render.was_flashing ||
term->is_searching != term->render.was_searching) term->is_searching != term->render.was_searching)
{ {
if (term->render.last_buf != NULL &&
term->render.last_buf->width == buf->width &&
term->render.last_buf->height == buf->height &&
!term->flash.active &&
!term->render.was_flashing &&
term->is_searching == term->render.was_searching)
{
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.");
has_warned = true;
}
assert(term->render.last_buf->size == buf->size);
memcpy(buf->mmapped, term->render.last_buf->mmapped, buf->size);
}
else {
/* Fill area outside the cell grid with the default background color */ /* Fill area outside the cell grid with the default background color */
int rmargin = term->x_margin + term->cols * term->cell_width; int rmargin = term->x_margin + term->cols * term->cell_width;
int bmargin = term->y_margin + term->rows * term->cell_height; int bmargin = term->y_margin + term->rows * term->cell_height;
@ -508,12 +497,39 @@ grid_render(struct terminal *term)
/* Force a full grid refresh */ /* Force a full grid refresh */
term_damage_view(term); term_damage_view(term);
}
term->render.last_buf = buf; term->render.last_buf = buf;
term->render.was_flashing = term->flash.active; term->render.was_flashing = term->flash.active;
term->render.was_searching = term->is_searching; term->render.was_searching = term->is_searching;
} }
/* Erase old cursor (if we rendered a cursor last time) */
if (term->render.last_cursor.cell != NULL) {
struct cell *cell = term->render.last_cursor.cell;
struct coord at = term->render.last_cursor.in_view;
if (cell->attrs.clean) {
cell->attrs.clean = 0;
render_cell(term, pix, cell, at.col, at.row, false);
wl_surface_damage_buffer(
term->wl.surface,
term->x_margin + at.col * term->cell_width,
term->y_margin + at.row * term->cell_height,
term->cell_width, term->cell_height);
}
term->render.last_cursor.cell = NULL;
if (term->render.last_cursor.actual.col != term->cursor.col ||
term->render.last_cursor.actual.row != term->cursor.row)
{
/* Detect cursor movement - we don't dirty cells touched
* by the cursor, since only the final cell matters. */
all_clean = false;
}
}
tll_foreach(term->grid->scroll_damage, it) { tll_foreach(term->grid->scroll_damage, it) {
switch (it->item.type) { switch (it->item.type) {
case DAMAGE_SCROLL: case DAMAGE_SCROLL: