selection/render: sync cells' 'selected' bit before rendering

For performance reasons, we track whether a cell is selected or not
using a bit in a cell's attributes.

This makes it easy for the renderer to determine if the cells should
be rendered as selected or not - it just have to look at the
'selected' bit instead of doing a complex range check against the
current selection.

This works nicely in most cases. But, if the cell is updated, the
'selected' bit is cleared. This results in the renderer rendering the
cell normally, i.e. _not_ selected.

Checking for this, and re-setting the 'selected' bit when the cell is
updated (printed to) is way too expensive as it is in the hot path.

Instead, sync the 'selected' bits just before rendering. This isn't so
bad as it may sound; if there is no selection this is a no-op. Even if
there is a selection, only those cells whose 'selected' bit have been
cleared are dirtied (and thus re-rendered) - these cells would have
been re-rendered anyway.
This commit is contained in:
Daniel Eklöf 2020-05-16 21:36:08 +02:00
parent a1c95562fb
commit 1a8ccb0ffa
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
4 changed files with 31 additions and 0 deletions

View file

@ -30,6 +30,7 @@
`shift` to start a selection. This selection is now finalized only
when the mouse button is released - not as soon as `shift` is
released.
* Selected cells did not appear selected if modified.
### Security

View file

@ -1354,6 +1354,25 @@ grid_render(struct terminal *term)
/* Reset clip region since scrolling may have instantiated a new pixman image */
pixman_image_set_clip_region(buf->pix, &clip);
/*
* Ensure selected cells have their 'selected' bit set. This is
* normally "automatically" true - the bit is set when the
* selection is made.
*
* However, if the cell is updated (printed to) while the
* selection is active, the 'selected' bit is cleared. Checking
* for this and re-setting the bit in term_print() is too
* expensive performance wise.
*
* Instead, we synchronize the selection bits here and now. This
* makes the performance impact linear to the number of selected
* cells rather than to the number of updated cells.
*
* (note that selection_dirty_cells() will not set the dirty flag
* on cells where the 'selected' bit is already set)
*/
selection_dirty_cells(term);
if (term->render.workers.count > 0) {
term->render.workers.buf = buf;

View file

@ -403,6 +403,16 @@ selection_update(struct terminal *term, int col, int row)
selection_modify(term, term->selection.start, new_end);
}
void
selection_dirty_cells(struct terminal *term)
{
if (term->selection.start.row == -1 || term->selection.end.row == -1)
return;
foreach_selected(
term, term->selection.start, term->selection.end, &mark_selected, NULL);
}
static void
selection_extend_normal(struct terminal *term, int col, int row, uint32_t serial)
{

View file

@ -13,6 +13,7 @@ void selection_start(
struct terminal *term, int col, int row, enum selection_kind kind);
void selection_update(struct terminal *term, int col, int row);
void selection_finalize(struct terminal *term, uint32_t serial);
void selection_dirty_cells(struct terminal *term);
void selection_cancel(struct terminal *term);
void selection_extend(struct terminal *term, int col, int row, uint32_t serial);