Improve performance when being forced to double buffer #488

Manually merged
dnkl merged 12 commits from double-buffering into master 9 months ago
  1. 3
      CHANGELOG.md
  2. 312
      render.c
  3. 45
      shm.c
  4. 7
      shm.h

3
CHANGELOG.md

@ -69,6 +69,9 @@
(https://codeberg.org/dnkl/foot/issues/466).
* Background alpha no longer applied to palette or RGB colors that
matches the background color.
* Improved performance on compositors that does not release shm
buffers immediately, e.g. KWin
(https://codeberg.org/dnkl/foot/issues/478).
### Deprecated

312
render.c

@ -759,6 +759,18 @@ render_margin(struct terminal *term, struct buffer *buf,
if (term->render.urgency)
render_urgency(term, buf);
/* Ensure the updated regions are copied to the next frame's
* buffer when we're double buffering */
pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, 0, term->width, term->margins.top);
pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, bmargin, term->width, term->margins.bottom);
pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, 0, term->margins.left, term->height);
pixman_region32_union_rect(
&buf->dirty, &buf->dirty,
rmargin, 0, term->margins.right, term->height);
if (apply_damage) {
/* Top */
wl_surface_damage_buffer(
@ -2025,13 +2037,150 @@ static const struct wl_callback_listener frame_listener = {
.done = &frame_callback,
};
static void
force_full_repaint(struct terminal *term, struct buffer *buf)
{
tll_free(term->grid->scroll_damage);
render_margin(term, buf, 0, term->rows, true);
term_damage_view(term);
}
static void
reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old)
{
static int counter = 0;
static bool have_warned = false;
if (!have_warned && ++counter > 5) {
LOG_WARN("compositor is not releasing buffers immediately; "
"expect lower rendering performance");
have_warned = true;
}
if (new->age > 1) {
memcpy(new->mmapped, old->mmapped, new->size);
return;
}
/*
* TODO: remove this frames damage from the region we copy from
* the old frame.
*
* - this frames dirty region is only valid *after* weve applied
* its scroll damage.
* - last frames dirty region is only valid *before* weve
* applied this frames scroll damage.
*
* Can we transform one of the regions? Its not trivial, since
* scroll damage isnt just about counting lines; there may be
* multiple damage records, each with different scrolling regions.
*/
pixman_region32_t dirty;
pixman_region32_init(&dirty);
bool full_repaint_needed = true;
for (int r = 0; r < term->rows; r++) {
const struct row *row = grid_row_in_view(term->grid, r);
bool row_all_dirty = true;
for (int c = 0; c < term->cols; c++) {
if (row->cells[c].attrs.clean) {
row_all_dirty = false;
full_repaint_needed = false;
break;
}
}
if (row_all_dirty) {
pixman_region32_union_rect(
&dirty, &dirty,
term->margins.left,
term->margins.top + r * term->cell_height,
term->width - term->margins.left - term->margins.right,
term->cell_height);
}
}
if (full_repaint_needed) {
force_full_repaint(term, new);
return;
}
for (size_t i = 0; i < old->scroll_damage_count; i++) {
const struct damage *dmg = &old->scroll_damage[i];
switch (dmg->type) {
case DAMAGE_SCROLL:
if (term->grid->view == term->grid->offset)
grid_render_scroll(term, new, dmg);
break;
case DAMAGE_SCROLL_REVERSE:
if (term->grid->view == term->grid->offset)
grid_render_scroll_reverse(term, new, dmg);
break;
case DAMAGE_SCROLL_IN_VIEW:
grid_render_scroll(term, new, dmg);
break;
case DAMAGE_SCROLL_REVERSE_IN_VIEW:
grid_render_scroll_reverse(term, new, dmg);
break;
}
}
if (tll_length(term->grid->scroll_damage) == 0) {
pixman_region32_subtract(&dirty, &old->dirty, &dirty);
pixman_image_set_clip_region32(new->pix[0], &dirty);
} else
pixman_image_set_clip_region32(new->pix[0], &old->dirty);
pixman_image_composite32(
PIXMAN_OP_SRC, old->pix[0], NULL, new->pix[0],
0, 0, 0, 0, 0, 0, term->width, term->height);
pixman_image_set_clip_region32(new->pix[0], NULL);
pixman_region32_fini(&dirty);
}
static void
dirty_old_cursor(struct terminal *term)
{
if (term->render.last_cursor.row != NULL && !term->render.last_cursor.hidden) {
struct row *row = term->render.last_cursor.row;
struct cell *cell = &row->cells[term->render.last_cursor.col];
cell->attrs.clean = 0;
row->dirty = true;
}
/* Remember current cursor position, for the next frame */
term->render.last_cursor.row = grid_row(term->grid, term->grid->cursor.point.row);
term->render.last_cursor.col = term->grid->cursor.point.col;
term->render.last_cursor.hidden = term->hide_cursor;
}
static void
dirty_cursor(struct terminal *term)
{
if (term->hide_cursor)
return;
const struct coord *cursor = &term->grid->cursor.point;
struct row *row = grid_row(term->grid, cursor->row);
struct cell *cell = &row->cells[cursor->col];
cell->attrs.clean = 0;
row->dirty = true;
}
static void
grid_render(struct terminal *term)
{
if (term->is_shutting_down)
return;
struct timeval start_time;
struct timeval start_time, start_double_buffering = {0}, stop_double_buffering = {0};
if (term->conf->tweak.render_timer_osd || term->conf->tweak.render_timer_log)
gettimeofday(&start_time, NULL);
@ -2042,66 +2191,76 @@ grid_render(struct terminal *term)
struct buffer *buf = shm_get_buffer(
term->wl->shm, term->width, term->height, cookie, true, 1 + term->render.workers.count);
/* If we resized the window, or is flashing, or just stopped flashing */
if (term->render.last_buf != buf ||
/* Dirty old and current cursor cell, to ensure they’re repainted */
dirty_old_cursor(term);
dirty_cursor(term);
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 ||
term->render.margins)
{
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 &&
!term->render.margins)
{
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;
}
force_full_repaint(term, buf);
}
xassert(term->render.last_buf->size == buf->size);
memcpy(buf->mmapped, term->render.last_buf->mmapped, buf->size);
}
else if (buf->age > 0) {
LOG_DBG("buffer age: %u", buf->age);
else {
tll_free(term->grid->scroll_damage);
render_margin(term, buf, 0, term->rows, true);
term_damage_view(term);
}
xassert(term->render.last_buf != NULL);
xassert(term->render.last_buf != buf);
xassert(term->render.last_buf->width == buf->width);
xassert(term->render.last_buf->height == buf->height);
term->render.last_buf = buf;
term->render.was_flashing = term->flash.active;
term->render.was_searching = term->is_searching;
gettimeofday(&start_double_buffering, NULL);
reapply_old_damage(term, buf, term->render.last_buf);
gettimeofday(&stop_double_buffering, NULL);
}
tll_foreach(term->grid->scroll_damage, it) {
switch (it->item.type) {
case DAMAGE_SCROLL:
if (term->grid->view == term->grid->offset)
if (term->render.last_buf != NULL) {
free(term->render.last_buf->scroll_damage);
term->render.last_buf->scroll_damage = NULL;
}
term->render.last_buf = buf;
term->render.was_flashing = term->flash.active;
term->render.was_searching = term->is_searching;
buf->age = 0;
xassert(buf->scroll_damage == NULL);
buf->scroll_damage_count = tll_length(term->grid->scroll_damage);
buf->scroll_damage = xmalloc(
buf->scroll_damage_count * sizeof(buf->scroll_damage[0]));
{
size_t i = 0;
tll_foreach(term->grid->scroll_damage, it) {
buf->scroll_damage[i++] = it->item;
switch (it->item.type) {
case DAMAGE_SCROLL:
if (term->grid->view == term->grid->offset)
grid_render_scroll(term, buf, &it->item);
break;
case DAMAGE_SCROLL_REVERSE:
if (term->grid->view == term->grid->offset)
grid_render_scroll_reverse(term, buf, &it->item);
break;
case DAMAGE_SCROLL_IN_VIEW:
grid_render_scroll(term, buf, &it->item);
break;
break;
case DAMAGE_SCROLL_REVERSE:
if (term->grid->view == term->grid->offset)
case DAMAGE_SCROLL_REVERSE_IN_VIEW:
grid_render_scroll_reverse(term, buf, &it->item);
break;
case DAMAGE_SCROLL_IN_VIEW:
grid_render_scroll(term, buf, &it->item);
break;
break;
}
case DAMAGE_SCROLL_REVERSE_IN_VIEW:
grid_render_scroll_reverse(term, buf, &it->item);
break;
tll_remove(term->grid->scroll_damage, it);
}
tll_remove(term->grid->scroll_damage, it);
}
/*
@ -2123,29 +2282,6 @@ grid_render(struct terminal *term)
*/
selection_dirty_cells(term);
/* Mark old cursor cell as dirty, to force it to be re-rendered */
if (term->render.last_cursor.row != NULL && !term->render.last_cursor.hidden) {
struct row *row = term->render.last_cursor.row;
struct cell *cell = &row->cells[term->render.last_cursor.col];
cell->attrs.clean = 0;
row->dirty = true;
}
/* Remember current cursor position, for the next frame */
term->render.last_cursor.row = grid_row(term->grid, term->grid->cursor.point.row);
term->render.last_cursor.col = term->grid->cursor.point.col;
term->render.last_cursor.hidden = term->hide_cursor;
/* Mark current cursor cell as dirty, to ensure it is rendered */
if (!term->hide_cursor) {
const struct coord *cursor = &term->grid->cursor.point;
struct row *row = grid_row(term->grid, cursor->row);
struct cell *cell = &row->cells[cursor->col];
cell->attrs.clean = 0;
row->dirty = true;
}
/* Translate offset-relative row to view-relative, unless cursor
* is hidden, then we just set it to -1 */
struct coord cursor = {-1, -1};
@ -2173,12 +2309,15 @@ grid_render(struct terminal *term)
if (!row->dirty) {
if (first_dirty_row >= 0) {
int x = term->margins.left;
int y = term->margins.top + first_dirty_row * term->cell_height;
int width = term->width - term->margins.left - term->margins.right;
int height = (r - first_dirty_row) * term->cell_height;
wl_surface_damage_buffer(
term->window->surface,
term->margins.left,
term->margins.top + first_dirty_row * term->cell_height,
term->width - term->margins.left - term->margins.right,
(r - first_dirty_row) * term->cell_height);
term->window->surface, x, y, width, height);
pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, y, buf->width, height);
}
first_dirty_row = -1;
continue;
@ -2199,12 +2338,13 @@ grid_render(struct terminal *term)
}
if (first_dirty_row >= 0) {
wl_surface_damage_buffer(
term->window->surface,
term->margins.left,
term->margins.top + first_dirty_row * term->cell_height,
term->width - term->margins.left - term->margins.right,
(term->rows - first_dirty_row) * term->cell_height);
int x = term->margins.left;
int y = term->margins.top + first_dirty_row * term->cell_height;
int width = term->width - term->margins.left - term->margins.right;
int height = (term->rows - first_dirty_row) * term->cell_height;
wl_surface_damage_buffer(term->window->surface, x, y, width, height);
pixman_region32_union_rect(&buf->dirty, &buf->dirty, 0, y, buf->width, height);
}
/* Signal workers the frame is done */
@ -2242,10 +2382,16 @@ grid_render(struct terminal *term)
struct timeval render_time;
timersub(&end_time, &start_time, &render_time);
struct timeval double_buffering_time;
timersub(&stop_double_buffering, &start_double_buffering, &double_buffering_time);
if (term->conf->tweak.render_timer_log) {
LOG_INFO("frame rendered in %llds %lld µs",
LOG_INFO("frame rendered in %llds %lld µs "
"(%llds %lld µs double buffering)",
(long long)render_time.tv_sec,
(long long)render_time.tv_usec);
(long long)render_time.tv_usec,
(long long)double_buffering_time.tv_sec,
(long long)double_buffering_time.tv_usec);
}
if (term->conf->tweak.render_timer_osd)

45
shm.c

@ -30,6 +30,8 @@
#define TIME_SCROLL 0
#define FORCED_DOUBLE_BUFFERING 0
/*
* Maximum memfd size allowed.
*
@ -100,6 +102,9 @@ buffer_destroy(struct buffer *buf)
buf->real_mmapped = MAP_FAILED;
buf->pool = NULL;
buf->fd = -1;
free(buf->scroll_damage);
pixman_region32_fini(&buf->dirty);
}
void
@ -222,6 +227,8 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
tll_remove(buffers, it);
}
struct buffer *cached = NULL;
tll_foreach(buffers, it) {
if (it->item.width != width)
continue;
@ -230,16 +237,30 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
if (it->item.cookie != cookie)
continue;
if (!it->item.busy) {
LOG_DBG("cookie=%lx: re-using buffer from cache (buf=%p)",
cookie, (void *)&it->item);
it->item.busy = true;
it->item.purge = false;
xassert(it->item.pix_instances == pix_instances);
return &it->item;
}
if (it->item.busy)
it->item.age++;
else
#if FORCED_DOUBLE_BUFFERING
if (it->item.age == 0)
it->item.age++;
else
#endif
{
LOG_DBG("cookie=%lx: re-using buffer from cache (buf=%p)",
cookie, (void *)&it->item);
it->item.busy = true;
it->item.purge = false;
pixman_region32_clear(&it->item.dirty);
free(it->item.scroll_damage);
it->item.scroll_damage = NULL;
xassert(it->item.pix_instances == pix_instances);
cached = &it->item;
}
}
if (cached != NULL)
return cached;
/* Purge old buffers associated with this cookie */
tll_foreach(buffers, it) {
if (it->item.cookie != cookie)
@ -376,14 +397,16 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie,
.scrollable = scrollable,
.real_mmapped = real_mmapped,
.mmap_size = memfd_size,
.offset = 0}
)
);
.offset = 0,
.age = 1234, /* Force a full repaint */
}));
struct buffer *ret = &tll_back(buffers);
if (!instantiate_offset(shm, ret, initial_offset))
goto err;
pixman_region32_init(&ret->dirty);
#if defined(MEASURE_SHM_ALLOCS) && MEASURE_SHM_ALLOCS
{
size_t currently_alloced = 0;

7
shm.h

@ -7,6 +7,8 @@
#include <pixman.h>
#include <wayland-client.h>
#include "terminal.h"
struct buffer {
unsigned long cookie;
@ -32,6 +34,11 @@ struct buffer {
bool scrollable;
bool purge; /* True if this buffer should be destroyed */
unsigned age;
struct damage *scroll_damage;
size_t scroll_damage_count;
pixman_region32_t dirty;
};
struct buffer *shm_get_buffer(

Loading…
Cancel
Save