render: more fine-grained wayland surface damage tracking

Before this patch. Wayland surface damage tracking was done on a
per-row basis. That is, even if just one cell was updated, the entire
row was "damaged".

Now, damage is per cell. This hopefully results in lower latencies in
many use cases, and especially on high DPI monitors.
This commit is contained in:
Daniel Eklöf 2023-10-07 16:23:09 +02:00
parent 1c9d98d57e
commit c50b1f9900
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
4 changed files with 75 additions and 50 deletions

View file

@ -98,6 +98,9 @@
* Command line configuration overrides are now applied even if the * Command line configuration overrides are now applied even if the
configuration file does not exist or can't be configuration file does not exist or can't be
parsed. ([#1495][1495]). parsed. ([#1495][1495]).
* Wayland surface damage is now more fine-grained. This should result
in lower latencies in many use cases, especially on high DPI
monitors.
[1391]: https://codeberg.org/dnkl/foot/issues/1391 [1391]: https://codeberg.org/dnkl/foot/issues/1391
[1448]: https://codeberg.org/dnkl/foot/pulls/1448 [1448]: https://codeberg.org/dnkl/foot/pulls/1448

View file

@ -459,8 +459,8 @@ draw_cursor(const struct terminal *term, const struct cell *cell,
} }
static int static int
render_cell(struct terminal *term, pixman_image_t *pix, render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damage,
struct row *row, int col, int row_no, bool has_cursor) struct row *row, int row_no, int col, bool has_cursor)
{ {
struct cell *cell = &row->cells[col]; struct cell *cell = &row->cells[col];
if (cell->attrs.clean) if (cell->attrs.clean)
@ -716,6 +716,12 @@ render_cell(struct terminal *term, pixman_image_t *pix,
&clip, x, y, &clip, x, y,
render_width, term->cell_height); render_width, term->cell_height);
pixman_image_set_clip_region32(pix, &clip); pixman_image_set_clip_region32(pix, &clip);
if (damage != NULL) {
pixman_region32_union_rect(
damage, damage, x, y, render_width, term->cell_height);
}
pixman_region32_fini(&clip); pixman_region32_fini(&clip);
/* Background */ /* Background */
@ -842,11 +848,11 @@ draw_cursor:
} }
static void static void
render_row(struct terminal *term, pixman_image_t *pix, struct row *row, render_row(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damage,
int row_no, int cursor_col) struct row *row, int row_no, int cursor_col)
{ {
for (int col = term->cols - 1; col >= 0; col--) for (int col = term->cols - 1; col >= 0; col--)
render_cell(term, pix, row, col, row_no, cursor_col == col); render_cell(term, pix, damage, row, row_no, col, cursor_col == col);
} }
static void static void
@ -918,13 +924,13 @@ render_margin(struct terminal *term, struct buffer *buf,
/* Ensure the updated regions are copied to the next frame's /* Ensure the updated regions are copied to the next frame's
* buffer when we're double buffering */ * buffer when we're double buffering */
pixman_region32_union_rect( pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, 0, term->width, term->margins.top); &buf->dirty[0], &buf->dirty[0], 0, 0, term->width, term->margins.top);
pixman_region32_union_rect( pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, bmargin, term->width, term->margins.bottom); &buf->dirty[0], &buf->dirty[0], 0, bmargin, term->width, term->margins.bottom);
pixman_region32_union_rect( pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, 0, term->margins.left, term->height); &buf->dirty[0], &buf->dirty[0], 0, 0, term->margins.left, term->height);
pixman_region32_union_rect( pixman_region32_union_rect(
&buf->dirty, &buf->dirty, &buf->dirty[0], &buf->dirty[0],
rmargin, 0, term->margins.right, term->height); rmargin, 0, term->margins.right, term->height);
if (apply_damage) { if (apply_damage) {
@ -1060,7 +1066,7 @@ grid_render_scroll(struct terminal *term, struct buffer *buf,
* last frames damage (see reapply_old_damage() * last frames damage (see reapply_old_damage()
*/ */
pixman_region32_union_rect( pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, dst_y, buf->width, height); &buf->dirty[0], &buf->dirty[0], 0, dst_y, buf->width, height);
} }
static void static void
@ -1137,7 +1143,7 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf,
* last frames damage (see reapply_old_damage() * last frames damage (see reapply_old_damage()
*/ */
pixman_region32_union_rect( pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, dst_y, buf->width, height); &buf->dirty[0], &buf->dirty[0], 0, dst_y, buf->width, height);
} }
static void static void
@ -1278,7 +1284,7 @@ render_sixel(struct terminal *term, pixman_image_t *pix,
if (!sixel->opaque) { if (!sixel->opaque) {
/* TODO: multithreading */ /* TODO: multithreading */
int cursor_col = cursor->row == term_row_no ? cursor->col : -1; int cursor_col = cursor->row == term_row_no ? cursor->col : -1;
render_row(term, pix, row, term_row_no, cursor_col); render_row(term, pix, NULL, row, term_row_no, cursor_col);
} else { } else {
for (int col = sixel->pos.col; for (int col = sixel->pos.col;
col < min(sixel->pos.col + sixel->cols, term->cols); col < min(sixel->pos.col + sixel->cols, term->cols);
@ -1293,7 +1299,7 @@ render_sixel(struct terminal *term, pixman_image_t *pix,
if ((last_row_needs_erase && last_row) || if ((last_row_needs_erase && last_row) ||
(last_col_needs_erase && last_col)) (last_col_needs_erase && last_col))
{ {
render_cell(term, pix, row, col, term_row_no, cursor_col == col); render_cell(term, pix, NULL, row, term_row_no, col, cursor_col == col);
} else { } else {
cell->attrs.clean = 1; cell->attrs.clean = 1;
cell->attrs.confined = 1; cell->attrs.confined = 1;
@ -1464,7 +1470,7 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat,
break; break;
row->cells[col_idx + i] = *cell; row->cells[col_idx + i] = *cell;
render_cell(term, buf->pix[0], row, col_idx + i, row_idx, false); render_cell(term, buf->pix[0], NULL, row, row_idx, col_idx + i, false);
} }
int start = seat->ime.preedit.cursor.start - ime_ofs; int start = seat->ime.preedit.cursor.start - ime_ofs;
@ -1791,7 +1797,8 @@ render_worker_thread(void *_ctx)
struct row *row = grid_row_in_view(term->grid, row_no); struct row *row = grid_row_in_view(term->grid, row_no);
int cursor_col = cursor.row == row_no ? cursor.col : -1; int cursor_col = cursor.row == row_no ? cursor.col : -1;
render_row(term, buf->pix[my_id], row, row_no, cursor_col); render_row(term, buf->pix[my_id], &buf->dirty[my_id],
row, row_no, cursor_col);
break; break;
} }
@ -2737,11 +2744,11 @@ reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old
* current frames scroll damage *first*. This is done later, * current frames scroll damage *first*. This is done later,
* when rendering the frame. * when rendering the frame.
*/ */
pixman_region32_subtract(&dirty, &old->dirty, &dirty); pixman_region32_subtract(&dirty, &old->dirty[0], &dirty);
pixman_image_set_clip_region32(new->pix[0], &dirty); pixman_image_set_clip_region32(new->pix[0], &dirty);
} else { } else {
/* Copy *all* of last frames damaged areas */ /* Copy *all* of last frames damaged areas */
pixman_image_set_clip_region32(new->pix[0], &old->dirty); pixman_image_set_clip_region32(new->pix[0], &old->dirty[0]);
} }
pixman_image_composite32( pixman_image_composite32(
@ -2966,28 +2973,14 @@ grid_render(struct terminal *term)
xassert(tll_length(term->render.workers.queue) == 0); xassert(tll_length(term->render.workers.queue) == 0);
} }
int first_dirty_row = -1; pixman_region32_t damage;
pixman_region32_init(&damage);
for (int r = 0; r < term->rows; r++) { for (int r = 0; r < term->rows; r++) {
struct row *row = grid_row_in_view(term->grid, r); struct row *row = grid_row_in_view(term->grid, r);
if (!row->dirty) { 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.surf, x, y, width, height);
pixman_region32_union_rect(
&buf->dirty, &buf->dirty, 0, y, buf->width, height);
}
first_dirty_row = -1;
continue; continue;
}
if (first_dirty_row < 0)
first_dirty_row = r;
row->dirty = false; row->dirty = false;
@ -2995,21 +2988,12 @@ grid_render(struct terminal *term)
tll_push_back(term->render.workers.queue, r); tll_push_back(term->render.workers.queue, r);
else { else {
/* TODO: damage region */
int cursor_col = cursor.row == r ? cursor.col : -1; int cursor_col = cursor.row == r ? cursor.col : -1;
render_row(term, buf->pix[0], row, r, cursor_col); render_row(term, buf->pix[0], &damage, row, r, cursor_col);
} }
} }
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 = (term->rows - first_dirty_row) * term->cell_height;
wl_surface_damage_buffer(term->window->surface.surf, x, y, width, height);
pixman_region32_union_rect(&buf->dirty, &buf->dirty, 0, y, buf->width, height);
}
/* Signal workers the frame is done */ /* Signal workers the frame is done */
if (term->render.workers.count > 0) { if (term->render.workers.count > 0) {
for (size_t i = 0; i < term->render.workers.count; i++) for (size_t i = 0; i < term->render.workers.count; i++)
@ -3021,6 +3005,25 @@ grid_render(struct terminal *term)
term->render.workers.buf = NULL; term->render.workers.buf = NULL;
} }
for (size_t i = 0; i < term->render.workers.count; i++)
pixman_region32_union(&damage, &damage, &buf->dirty[i + 1]);
pixman_region32_union(&buf->dirty[0], &buf->dirty[0], &damage);
{
int box_count = 0;
pixman_box32_t *boxes = pixman_region32_rectangles(&damage, &box_count);
for (size_t i = 0; i < box_count; i++) {
wl_surface_damage_buffer(
term->window->surface.surf,
boxes[i].x1, boxes[i].y1,
boxes[i].x2 - boxes[i].x1, boxes[i].y2 - boxes[i].y1);
}
}
pixman_region32_fini(&damage);
render_overlay(term); render_overlay(term);
render_ime_preedit(term, buf); render_ime_preedit(term, buf);
render_scrollback_position(term); render_scrollback_position(term);

14
shm.c
View file

@ -157,7 +157,9 @@ buffer_destroy(struct buffer_private *buf)
pool_unref(buf->pool); pool_unref(buf->pool);
buf->pool = NULL; buf->pool = NULL;
pixman_region32_fini(&buf->public.dirty); for (size_t i = 0; i < buf->public.pix_instances; i++)
pixman_region32_fini(&buf->public.dirty[i]);
free(buf->public.dirty);
free(buf); free(buf);
} }
@ -476,7 +478,12 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
else else
tll_push_front(chain->bufs, buf); tll_push_front(chain->bufs, buf);
pixman_region32_init(&buf->public.dirty); buf->public.dirty = malloc(
chain->pix_instances * sizeof(buf->public.dirty[0]));
for (size_t j = 0; j < chain->pix_instances; j++)
pixman_region32_init(&buf->public.dirty[j]);
pool->ref_count++; pool->ref_count++;
offset += buf->size; offset += buf->size;
bufs[i] = &buf->public; bufs[i] = &buf->public;
@ -585,7 +592,8 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height)
if (cached != NULL) { if (cached != NULL) {
LOG_DBG("re-using buffer %p from cache", (void *)cached); LOG_DBG("re-using buffer %p from cache", (void *)cached);
cached->busy = true; cached->busy = true;
pixman_region32_clear(&cached->public.dirty); for (size_t i = 0; i < cached->public.pix_instances; i++)
pixman_region32_clear(&cached->public.dirty[i]);
xassert(cached->public.pix_instances == chain->pix_instances); xassert(cached->public.pix_instances == chain->pix_instances);
return &cached->public; return &cached->public;
} }

13
shm.h
View file

@ -24,7 +24,18 @@ struct buffer {
unsigned age; unsigned age;
pixman_region32_t dirty; /*
* First item in the array is used to track frame-to-frame
* damage. This is used when re-applying damage from the last
* frame, when the compositor doesn't release buffers immediately
* (forcing us to double buffer)
*
* The remaining items are used to track surface damage. Each
* worker thread adds its own cell damage to "its" region. When
* the frame is done, all damage is converted to a single region,
* which is then used in calls to wl_surface_damage_buffer().
*/
pixman_region32_t *dirty;
}; };
void shm_fini(void); void shm_fini(void);