From c8b342ae51cba357f21a3f6c77f6a527981101b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 7 May 2021 18:18:35 +0200 Subject: [PATCH 01/12] =?UTF-8?q?shm:=20track=20busy=20buffers=E2=80=99=20?= =?UTF-8?q?age,=20and=20add=20compile-time=20option=20to=20force=20double?= =?UTF-8?q?=20buffering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By default, age all matching buffers that are busy (i.e. in use by the compositor). This allows us to detect whether we can apply the current frame’s damage directly, or if we need to prepare the buffer first (e.g. copy old buffer, or re-apply last frame’s damage etc). --- render.c | 6 ++++++ shm.c | 37 ++++++++++++++++++++++++++----------- shm.h | 2 ++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/render.c b/render.c index 9bcd11ba..43b6ee6f 100644 --- a/render.c +++ b/render.c @@ -2048,6 +2048,11 @@ grid_render(struct terminal *term) term->is_searching != term->render.was_searching || term->render.margins) { + if (buf->age > 0) { + LOG_DBG("compositor double buffers (age=%d): last=%p, cur=%p", + buf->age, (void*)term->render.last_buf, (void*)buf); + } + if (term->render.last_buf != NULL && term->render.last_buf->width == buf->width && term->render.last_buf->height == buf->height && @@ -2080,6 +2085,7 @@ grid_render(struct terminal *term) term->render.was_searching = term->is_searching; } + buf->age = 0; tll_foreach(term->grid->scroll_damage, it) { switch (it->item.type) { case DAMAGE_SCROLL: diff --git a/shm.c b/shm.c index 2ec581c4..a39c7c34 100644 --- a/shm.c +++ b/shm.c @@ -30,6 +30,8 @@ #define TIME_SCROLL 0 +#define FORCED_DOUBLE_BUFFERING 0 + /* * Maximum memfd size allowed. * @@ -222,6 +224,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 +234,27 @@ 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; + 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,9 +391,9 @@ 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 = 0, + })); struct buffer *ret = &tll_back(buffers); if (!instantiate_offset(shm, ret, initial_offset)) diff --git a/shm.h b/shm.h index 2e819369..d0d79435 100644 --- a/shm.h +++ b/shm.h @@ -32,6 +32,8 @@ struct buffer { bool scrollable; bool purge; /* True if this buffer should be destroyed */ + + int age; }; struct buffer *shm_get_buffer( From 434c9c3a347e3afc96a6f2e1e8f2dae9379efcc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 7 May 2021 20:20:47 +0200 Subject: [PATCH 02/12] shm: add damage tracking to buffer --- shm.c | 5 +++++ shm.h | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/shm.c b/shm.c index a39c7c34..8bcff7c5 100644 --- a/shm.c +++ b/shm.c @@ -102,6 +102,9 @@ buffer_destroy(struct buffer *buf) buf->real_mmapped = MAP_FAILED; buf->pool = NULL; buf->fd = -1; + + tll_free(buf->scroll_damage); + pixman_region32_fini(&buf->dirty); } void @@ -399,6 +402,8 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, 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; diff --git a/shm.h b/shm.h index d0d79435..034b87e1 100644 --- a/shm.h +++ b/shm.h @@ -7,6 +7,8 @@ #include #include +#include "terminal.h" + struct buffer { unsigned long cookie; @@ -34,6 +36,8 @@ struct buffer { bool purge; /* True if this buffer should be destroyed */ int age; + tll (struct damage) scroll_damage; + pixman_region32_t dirty; }; struct buffer *shm_get_buffer( From 15bfeea745fa9657b3802b2959153cc7f4fe017d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 7 May 2021 20:21:27 +0200 Subject: [PATCH 03/12] =?UTF-8?q?render:=20wip:=20re-apply=20last=20frame?= =?UTF-8?q?=E2=80=99s=20damage=20when=20forced=20to=20double=20buffer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we are forced to swap between two buffers, re-apply the old frame’s damage to the current buffer, before applying the current frame’s damage. First, while applying this frame’s scroll damage, copy it to the buffer’s scroll damage list (so that we can access it via term->render.last_buf). Also, when iterating and rendering the grid, build a pixman region of the damaged regions. This is currently done on a per-row basis. This is also stored in the buffer. Now, when being forced to double buffer, first iterate the old buffer’s damage, and re-apply it to the current buffer. Then, composite the old buffer on top of the current buffer, using the old frame’s damage region as clip region. This effectively copies everything that was rendered to the last frame. Remember, this is on a per-row basis. Then we go on and render the frame as usual. Note that it would be _really_ nice if we could subtract the current frame’s damage region from the clip region (no point in copying areas we’re going to overwrite anyway). Unfortunately, that’s harder than it looks; the current frame’s damage region is only valid *after* this frame’s scroll damage have been applied, while the last frame’s damage region is only valid *before* it’s been applied. Translating one to the other isn’t easy, since scroll damage isn’t just about counting lines - there may be multiple scroll damage records, each with its own scrolling region. This creates very complex scenarios. --- render.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 12 deletions(-) diff --git a/render.c b/render.c index 43b6ee6f..3c97df97 100644 --- a/render.c +++ b/render.c @@ -2042,6 +2042,29 @@ 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); + /* 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; + } + /* If we resized the window, or is flashing, or just stopped flashing */ if (term->render.last_buf != buf || term->flash.active || term->render.was_flashing || @@ -2071,7 +2094,91 @@ grid_render(struct terminal *term) } xassert(term->render.last_buf->size == buf->size); +#if 0 memcpy(buf->mmapped, term->render.last_buf->mmapped, buf->size); + tll_free(term->render.last_buf->scroll_damage); +#else + +#if 0 + /* + * TODO: remove this frame’s damage from the region we + * copy from the old frame. + * + * - this frame’s dirty region is only valid *after* we’ve + * applied its scroll damage. + * - last frame’s dirty region is only valid *before* + * we’ve applied this frame’s scroll damage. + * + * Can we transform one of the regions? It’s not trivial, + * since scroll damage isn’t 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 UNUSED = 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); + } + } +#endif + tll_foreach(term->render.last_buf->scroll_damage, it) { + 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; + + case DAMAGE_SCROLL_REVERSE_IN_VIEW: + grid_render_scroll_reverse(term, buf, &it->item); + break; + } + + tll_remove(term->render.last_buf->scroll_damage, it); + } + +#if 0 + pixman_region32_subtract(&dirty, &term->render.last_buf->dirty, &dirty); + pixman_image_set_clip_region32(buf->pix[0], &dirty); +#else + pixman_image_set_clip_region32(buf->pix[0], &term->render.last_buf->dirty); +#endif + + pixman_image_composite32( + PIXMAN_OP_SRC, term->render.last_buf->pix[0], NULL, buf->pix[0], + 0, 0, 0, 0, 0, 0, term->width, term->height); + + pixman_image_set_clip_region32(buf->pix[0], NULL); +#if 0 + pixman_region32_fini(&dirty); +#endif +#endif } else { @@ -2085,8 +2192,15 @@ grid_render(struct terminal *term) term->render.was_searching = term->is_searching; } + if (term->render.last_buf != NULL) + tll_free(term->render.last_buf->scroll_damage); + buf->age = 0; + xassert(tll_length(buf->scroll_damage) == 0); + tll_foreach(term->grid->scroll_damage, it) { + tll_push_back(buf->scroll_damage, it->item); + switch (it->item.type) { case DAMAGE_SCROLL: if (term->grid->view == term->grid->offset) @@ -2129,6 +2243,7 @@ grid_render(struct terminal *term) */ selection_dirty_cells(term); +#if 0 /* 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; @@ -2151,7 +2266,7 @@ grid_render(struct terminal *term) cell->attrs.clean = 0; row->dirty = true; } - +#endif /* Translate offset-relative row to view-relative, unless cursor * is hidden, then we just set it to -1 */ struct coord cursor = {-1, -1}; @@ -2173,18 +2288,22 @@ grid_render(struct terminal *term) xassert(tll_length(term->render.workers.queue) == 0); } + pixman_region32_clear(&buf->dirty); int first_dirty_row = -1; for (int r = 0; r < term->rows; r++) { struct row *row = grid_row_in_view(term->grid, r); 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, x, y, width, height); } first_dirty_row = -1; continue; @@ -2205,12 +2324,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, x, y, width, height); } /* Signal workers the frame is done */ From a1d2044d75050e72b8150d2f11147cf58a533c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 8 May 2021 09:18:45 +0200 Subject: [PATCH 04/12] =?UTF-8?q?render:=20subtract=20current=20frame?= =?UTF-8?q?=E2=80=99s=20damage=20when=20there=E2=80=99s=20no=20scroll=20da?= =?UTF-8?q?mage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When re-applying the previous frame’s damage (due to us being forced to double buffer), subtract the current frame’s damage from the region-to-copy when there’s no scroll damage on the current frame. When the current frame doesn’t have any scroll damage, the current frame’s damage is in the same coordinate system as the previous frame’s damage, and we can safely remove it from the region we copy from. --- render.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/render.c b/render.c index 3c97df97..42b2f9ea 100644 --- a/render.c +++ b/render.c @@ -2099,7 +2099,6 @@ grid_render(struct terminal *term) tll_free(term->render.last_buf->scroll_damage); #else -#if 0 /* * TODO: remove this frame’s damage from the region we * copy from the old frame. @@ -2138,7 +2137,7 @@ grid_render(struct terminal *term) term->cell_height); } } -#endif + tll_foreach(term->render.last_buf->scroll_damage, it) { switch (it->item.type) { case DAMAGE_SCROLL: @@ -2163,21 +2162,18 @@ grid_render(struct terminal *term) tll_remove(term->render.last_buf->scroll_damage, it); } -#if 0 - pixman_region32_subtract(&dirty, &term->render.last_buf->dirty, &dirty); - pixman_image_set_clip_region32(buf->pix[0], &dirty); -#else - pixman_image_set_clip_region32(buf->pix[0], &term->render.last_buf->dirty); -#endif + if (tll_length(term->grid->scroll_damage) == 0) { + pixman_region32_subtract(&dirty, &term->render.last_buf->dirty, &dirty); + pixman_image_set_clip_region32(buf->pix[0], &dirty); + } else + pixman_image_set_clip_region32(buf->pix[0], &term->render.last_buf->dirty); pixman_image_composite32( PIXMAN_OP_SRC, term->render.last_buf->pix[0], NULL, buf->pix[0], 0, 0, 0, 0, 0, 0, term->width, term->height); pixman_image_set_clip_region32(buf->pix[0], NULL); -#if 0 pixman_region32_fini(&dirty); -#endif #endif } From 34becf0df0915b3ed9534057c6e8c96d88625789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 8 May 2021 10:25:14 +0200 Subject: [PATCH 05/12] render: code cleanup, log double buffering time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Break out cursor cell dirtying to separate functions * Break out handling of double buffering * Handle buffers with age > 1 (we’re swapping between more than 2 buffers) * Detect full screen repaints, and skip re-applying old frame’s damage * Use an allocated array insted of a tll list for old frame’s scroll damage * When logging frame rendering time, including the amount used for double buffering. --- render.c | 376 ++++++++++++++++++++++++++++--------------------------- shm.c | 2 +- shm.h | 5 +- 3 files changed, 195 insertions(+), 188 deletions(-) diff --git a/render.c b/render.c index 42b2f9ea..8ab30f1b 100644 --- a/render.c +++ b/render.c @@ -2026,23 +2026,108 @@ static const struct wl_callback_listener frame_listener = { }; static void -grid_render(struct terminal *term) +force_full_repaint(struct terminal *term, struct buffer *buf) { - if (term->is_shutting_down) + 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) +{ + if (new->age > 1) { + LOG_WARN("copying the entire old buffer"); + memcpy(new->mmapped, old->mmapped, new->size); return; + } - struct timeval start_time; - if (term->conf->tweak.render_timer_osd || term->conf->tweak.render_timer_log) - gettimeofday(&start_time, NULL); + /* + * TODO: remove this frame’s damage from the region we copy from + * the old frame. + * + * - this frame’s dirty region is only valid *after* we’ve applied + * its scroll damage. + * - last frame’s dirty region is only valid *before* we’ve + * applied this frame’s scroll damage. + * + * Can we transform one of the regions? It’s not trivial, since + * scroll damage isn’t just about counting lines; there may be + * multiple damage records, each with different scrolling regions. + */ + pixman_region32_t dirty; + pixman_region32_init(&dirty); - xassert(term->width > 0); - xassert(term->height > 0); + bool full_repaint_needed = true; - unsigned long cookie = shm_cookie_grid(term); - struct buffer *buf = shm_get_buffer( - term->wl->shm, term->width, term->height, cookie, true, 1 + term->render.workers.count); + for (int r = 0; r < term->rows; r++) { + const struct row *row = grid_row_in_view(term->grid, r); - /* Mark old cursor cell as dirty, to force it to be re-rendered */ + 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]; @@ -2054,170 +2139,108 @@ grid_render(struct terminal *term) 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; +static void +dirty_cursor(struct terminal *term) +{ + if (term->hide_cursor) + return; - struct row *row = grid_row(term->grid, cursor->row); - struct cell *cell = &row->cells[cursor->col]; - cell->attrs.clean = 0; - row->dirty = true; - } + const struct coord *cursor = &term->grid->cursor.point; - /* If we resized the window, or is flashing, or just stopped flashing */ - if (term->render.last_buf != buf || + 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, 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); + + xassert(term->width > 0); + xassert(term->height > 0); + + unsigned long cookie = shm_cookie_grid(term); + struct buffer *buf = shm_get_buffer( + term->wl->shm, term->width, term->height, cookie, true, 1 + term->render.workers.count); + + /* 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->flash.active || term->render.was_flashing || term->is_searching != term->render.was_searching || term->render.margins) { - if (buf->age > 0) { - LOG_DBG("compositor double buffers (age=%d): last=%p, cur=%p", - buf->age, (void*)term->render.last_buf, (void*)buf); - } - - 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; - } - - xassert(term->render.last_buf->size == buf->size); -#if 0 - memcpy(buf->mmapped, term->render.last_buf->mmapped, buf->size); - tll_free(term->render.last_buf->scroll_damage); -#else - - /* - * TODO: remove this frame’s damage from the region we - * copy from the old frame. - * - * - this frame’s dirty region is only valid *after* we’ve - * applied its scroll damage. - * - last frame’s dirty region is only valid *before* - * we’ve applied this frame’s scroll damage. - * - * Can we transform one of the regions? It’s not trivial, - * since scroll damage isn’t 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 UNUSED = 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); - } - } - - tll_foreach(term->render.last_buf->scroll_damage, it) { - 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; - - case DAMAGE_SCROLL_REVERSE_IN_VIEW: - grid_render_scroll_reverse(term, buf, &it->item); - break; - } - - tll_remove(term->render.last_buf->scroll_damage, it); - } - - if (tll_length(term->grid->scroll_damage) == 0) { - pixman_region32_subtract(&dirty, &term->render.last_buf->dirty, &dirty); - pixman_image_set_clip_region32(buf->pix[0], &dirty); - } else - pixman_image_set_clip_region32(buf->pix[0], &term->render.last_buf->dirty); - - pixman_image_composite32( - PIXMAN_OP_SRC, term->render.last_buf->pix[0], NULL, buf->pix[0], - 0, 0, 0, 0, 0, 0, term->width, term->height); - - pixman_image_set_clip_region32(buf->pix[0], NULL); - pixman_region32_fini(&dirty); -#endif - } - - else { - tll_free(term->grid->scroll_damage); - render_margin(term, buf, 0, term->rows, true); - term_damage_view(term); - } - - term->render.last_buf = buf; - term->render.was_flashing = term->flash.active; - term->render.was_searching = term->is_searching; + force_full_repaint(term, buf); } - if (term->render.last_buf != NULL) - tll_free(term->render.last_buf->scroll_damage); + else if (buf->age > 0) { + LOG_DBG("buffer age: %u", buf->age); + xassert(term->render.last_buf != buf); + + if (term->render.last_buf->width == buf->width && + term->render.last_buf->height == buf->height) + { + gettimeofday(&start_double_buffering, NULL); + reapply_old_damage(term, buf, term->render.last_buf); + gettimeofday(&stop_double_buffering, NULL); + } else + force_full_repaint(term, buf); + } + + 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(tll_length(buf->scroll_damage) == 0); - tll_foreach(term->grid->scroll_damage, it) { - tll_push_back(buf->scroll_damage, it->item); + 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])); - switch (it->item.type) { - case DAMAGE_SCROLL: - if (term->grid->view == term->grid->offset) + { + 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; + break; + } - case DAMAGE_SCROLL_IN_VIEW: - grid_render_scroll(term, buf, &it->item); - 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); } /* @@ -2239,30 +2262,6 @@ grid_render(struct terminal *term) */ selection_dirty_cells(term); -#if 0 - /* 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; - } -#endif /* Translate offset-relative row to view-relative, unless cursor * is hidden, then we just set it to -1 */ struct coord cursor = {-1, -1}; @@ -2285,6 +2284,7 @@ grid_render(struct terminal *term) } pixman_region32_clear(&buf->dirty); + int first_dirty_row = -1; for (int r = 0; r < term->rows; r++) { struct row *row = grid_row_in_view(term->grid, r); @@ -2299,7 +2299,7 @@ grid_render(struct terminal *term) wl_surface_damage_buffer( term->window->surface, x, y, width, height); pixman_region32_union_rect( - &buf->dirty, &buf->dirty, x, y, width, height); + &buf->dirty, &buf->dirty, 0, y, buf->width, height); } first_dirty_row = -1; continue; @@ -2326,7 +2326,7 @@ grid_render(struct terminal *term) 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, x, y, width, height); + pixman_region32_union_rect(&buf->dirty, &buf->dirty, 0, y, buf->width, height); } /* Signal workers the frame is done */ @@ -2364,10 +2364,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) diff --git a/shm.c b/shm.c index 8bcff7c5..b8572cdd 100644 --- a/shm.c +++ b/shm.c @@ -103,7 +103,7 @@ buffer_destroy(struct buffer *buf) buf->pool = NULL; buf->fd = -1; - tll_free(buf->scroll_damage); + free(buf->scroll_damage); pixman_region32_fini(&buf->dirty); } diff --git a/shm.h b/shm.h index 034b87e1..385a0e30 100644 --- a/shm.h +++ b/shm.h @@ -35,8 +35,9 @@ struct buffer { bool scrollable; bool purge; /* True if this buffer should be destroyed */ - int age; - tll (struct damage) scroll_damage; + unsigned age; + struct damage *scroll_damage; + size_t scroll_damage_count; pixman_region32_t dirty; }; From 9b339a35f05fc9439c82f28605f5fd662bd8734f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 8 May 2021 20:52:06 +0200 Subject: [PATCH 06/12] =?UTF-8?q?render:=20warn=20if=20we=E2=80=99re=20for?= =?UTF-8?q?ced=20to=20double=20buffer=20at=20least=205=20times?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/render.c b/render.c index 8ab30f1b..c65cc7e9 100644 --- a/render.c +++ b/render.c @@ -2036,8 +2036,15 @@ force_full_repaint(struct terminal *term, struct buffer *buf) 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) { - LOG_WARN("copying the entire old buffer"); memcpy(new->mmapped, old->mmapped, new->size); return; } From 0433ba8477792f01d8a501d572967efb69b10d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 9 May 2021 00:06:01 +0200 Subject: [PATCH 07/12] shm: free scroll damage before returning cached buffer --- shm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shm.c b/shm.c index b8572cdd..654ecc59 100644 --- a/shm.c +++ b/shm.c @@ -250,6 +250,8 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, cookie, (void *)&it->item); it->item.busy = true; it->item.purge = false; + free(it->item.scroll_damage); + it->item.scroll_damage = NULL; xassert(it->item.pix_instances == pix_instances); cached = &it->item; } From 37bbf44f6da1d273af718c504e1aeab5e39be493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 May 2021 17:48:28 +0200 Subject: [PATCH 08/12] =?UTF-8?q?shm:=20set=20=E2=80=98age=E2=80=99=20in?= =?UTF-8?q?=20newly=20allocated=20buffers=20to=20something=20large?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures we don’t try to do a partial update of fresh buffer. --- shm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shm.c b/shm.c index 654ecc59..4b503d22 100644 --- a/shm.c +++ b/shm.c @@ -397,7 +397,7 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, .real_mmapped = real_mmapped, .mmap_size = memfd_size, .offset = 0, - .age = 0, + .age = 1234, /* Force a full repaint */ })); struct buffer *ret = &tll_back(buffers); From 945a346596b163e459d7bd4896258f8546c861b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 May 2021 17:49:15 +0200 Subject: [PATCH 09/12] =?UTF-8?q?shm:=20clear=20buffer=E2=80=99s=20dirty?= =?UTF-8?q?=20region=20before=20returning=20a=20cached=20buffer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A non-busy buffer should always have an empty dirty region --- render.c | 2 -- shm.c | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/render.c b/render.c index c65cc7e9..a7297412 100644 --- a/render.c +++ b/render.c @@ -2290,8 +2290,6 @@ grid_render(struct terminal *term) xassert(tll_length(term->render.workers.queue) == 0); } - pixman_region32_clear(&buf->dirty); - int first_dirty_row = -1; for (int r = 0; r < term->rows; r++) { struct row *row = grid_row_in_view(term->grid, r); diff --git a/shm.c b/shm.c index 4b503d22..ed20af3a 100644 --- a/shm.c +++ b/shm.c @@ -250,6 +250,7 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie, 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); From 51dec651f4f0a4fce9a708360d759574d5dddcc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 May 2021 17:56:12 +0200 Subject: [PATCH 10/12] =?UTF-8?q?render:=20add=20margins=20to=20buffer?= =?UTF-8?q?=E2=80=99s=20dirty=20region=20when=20rendering=20margins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/render.c b/render.c index a7297412..d9cd1bca 100644 --- a/render.c +++ b/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( From dc4f60fd4f8f5d0543a0d1853fbaebc07fcc9a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 May 2021 17:56:35 +0200 Subject: [PATCH 11/12] =?UTF-8?q?render:=20always=20do=20a=20full=20repain?= =?UTF-8?q?t=20if=20last=20buffer=E2=80=99s=20dimension=20doesn=E2=80=99t?= =?UTF-8?q?=20match?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- render.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/render.c b/render.c index d9cd1bca..1ba3516a 100644 --- a/render.c +++ b/render.c @@ -2196,6 +2196,8 @@ grid_render(struct terminal *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) @@ -2205,16 +2207,15 @@ grid_render(struct terminal *term) else if (buf->age > 0) { LOG_DBG("buffer age: %u", buf->age); - xassert(term->render.last_buf != buf); - if (term->render.last_buf->width == buf->width && - term->render.last_buf->height == buf->height) - { - gettimeofday(&start_double_buffering, NULL); - reapply_old_damage(term, buf, term->render.last_buf); - gettimeofday(&stop_double_buffering, NULL); - } else - force_full_repaint(term, buf); + 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); + + gettimeofday(&start_double_buffering, NULL); + reapply_old_damage(term, buf, term->render.last_buf); + gettimeofday(&stop_double_buffering, NULL); } if (term->render.last_buf != NULL) { From 07d0acbabfbf13534e045f20d5f119c4e201537b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 12 May 2021 20:07:18 +0200 Subject: [PATCH 12/12] changelog: improved performance when double buffering --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d75ee569..ebc55631 100644 --- a/CHANGELOG.md +++ b/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