Merge branch 'sixel-performance'

This commit is contained in:
Daniel Eklöf 2020-06-30 17:41:59 +02:00
commit 08309537ce
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
6 changed files with 202 additions and 149 deletions

View file

@ -63,24 +63,24 @@
`0x0` terminal size (https://codeberg.org/dnkl/foot/issues/20). `0x0` terminal size (https://codeberg.org/dnkl/foot/issues/20).
* Glyphs overflowing into surrounding cells * Glyphs overflowing into surrounding cells
(https://codeberg.org/dnkl/foot/issues/21). (https://codeberg.org/dnkl/foot/issues/21).
* Sixel images being erased when printing text next to them.
* Crash when last rendered cursor cell had scrolled off screen and * Crash when last rendered cursor cell had scrolled off screen and
`\E[J3` was executed. `\E[J3` was executed.
* Assert (debug builds) when an `\e]4` OSC escape was not followed by * Assert (debug builds) when an `\e]4` OSC escape was not followed by
a `;`. a `;`.
* Window title always being set to "foot" on reset.
* Terminfo entry `kb2` (center keypad key); it is now set to `\EOu`
(which is what foot emits) instead of the incorrect value `\EOE`.
* Palette re-use in sixel images. Previously, the palette was reset * Palette re-use in sixel images. Previously, the palette was reset
after each image. after each image.
* Do not auto-resize a sixel image for which the cllent has specified * Do not auto-resize a sixel image for which the cllent has specified
a size. This fixes an issue where an image would incorrectly a size. This fixes an issue where an image would incorrectly
overflow into the cell row beneath. overflow into the cell row beneath.
* Window title always being set to "foot" on reset.
* Erase scrolled out sixel image that crossed the scrollback wrap
around boundary.
* Text printed, or other sixel images drawn, on top of a sixel image * Text printed, or other sixel images drawn, on top of a sixel image
no longer erases the entire image, only the part(s) covered by the no longer erases the entire image, only the part(s) covered by the
new text or image. new text or image.
* Terminfo entry `kb2` (center keypad key); it is now set to `\EOu` * Sixel images being erased when printing text next to them.
(which is what foot emits) instead of the incorrect value `\EOE`. * Sixel handling when resizing window.
* Sixel handling when scrollback wraps around.
### Security ### Security

92
grid.c
View file

@ -76,7 +76,10 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols,
* at the output that is *oldest* */ * at the output that is *oldest* */
int offset = grid->offset + old_screen_rows; int offset = grid->offset + old_screen_rows;
tll(struct sixel) new_sixels = tll_init(); tll(struct sixel) old_sixels = tll_init();
tll_foreach(grid->sixel_images, it)
tll_push_back(old_sixels, it->item);
tll_free(grid->sixel_images);
/* Turn cursor coordinates into grid absolute coordinates */ /* Turn cursor coordinates into grid absolute coordinates */
struct coord cursor = grid->cursor.point; struct coord cursor = grid->cursor.point;
@ -106,33 +109,59 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols,
if (old_row == NULL) if (old_row == NULL)
continue; continue;
/* /* Map sixels on current "old" row to current "new row" */
* Update 'row' in all sixels that *begin* at current row tll_foreach(old_sixels, it) {
* if (it->item.pos.row != old_row_idx)
* Since we might end up pushing the sixel down, we can't continue;
* simply update the row inline - we'd then end up pushing the
* sixel down again, when we reach the next 'old'
* row. Instead, copy the sixel (with 'row' updated), to a
* temporary list and remove the original sixel.
*
* After we've reflowed the grid we'll move the sixels back to
* the "real" sixel list.
*/
tll_foreach(grid->sixel_images, it) {
if (it->item.pos.row == old_row_idx) {
struct sixel six = it->item;
six.pos.row = new_row_idx;
int end = (six.pos.row + six.rows - 1) & (new_rows - 1); struct sixel sixel = it->item;
if (end < six.pos.row) { sixel.pos.row = new_row_idx;
/* TODO: split sixel instead of removing it... */
/* Make sure it doesn't cross the wrap-around after being re-based */
int end = (sixel.pos.row + sixel.rows - 1) & (new_rows - 1);
if (end < sixel.pos.row) {
/* TODO: split instead of destroying */
sixel_destroy(&it->item); sixel_destroy(&it->item);
} else } else {
tll_push_back(new_sixels, six);
tll_remove(grid->sixel_images, it); /* Insert sixel into the *sorted* list. */
/* Based on rebase_row() in sixel.c */
/* Uses 'old' offset to ensure old sixels are treated as such */
#define rebase_row(t, row) \
(((row) - (grid->offset + new_screen_rows) + new_rows) & (new_rows - 1))
int end_row = rebase_row(term, sixel.pos.row + sixel.rows - 1);
/*
* TODO: this is basically sixel_insert(), except we
* cannot use it since:
*
* a) we don't have a 'term' reference
* b) the grid hasn't been fully * updated yet
* (e.g. grid->num_rows is invalid etc).
*/
bool inserted = false;
tll_foreach(grid->sixel_images, it2) {
const struct sixel *s = &it2->item;
if (rebase_row(term, s->pos.row + s->rows - 1) < end_row) {
tll_insert_before(grid->sixel_images, it2, sixel);
inserted = true;
break;
} }
} }
if (!inserted)
tll_push_back(grid->sixel_images, sixel);
}
/* Sixel has been either re-mapped, or destroyed */
tll_remove(old_sixels, it);
#undef rebase_row
}
#define line_wrap() \ #define line_wrap() \
do { \ do { \
new_col_idx = 0; \ new_col_idx = 0; \
@ -145,6 +174,12 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols,
} else { \ } else { \
memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); \ memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); \
new_row->linebreak = false; \ new_row->linebreak = false; \
tll_foreach(grid->sixel_images, it) { \
if (it->item.pos.row == new_row_idx) { \
sixel_destroy(&it->item); \
tll_remove(grid->sixel_images, it); \
} \
} \
} \ } \
} while(0) } while(0)
@ -279,15 +314,10 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols,
grid->cursor.lcf = false; grid->cursor.lcf = false;
grid->saved_cursor.lcf = false; grid->saved_cursor.lcf = false;
/* Destroy any non-moved sixels */ /* Free sixels we failed to "map" to the new grid */
tll_foreach(grid->sixel_images, it) tll_foreach(old_sixels, it)
sixel_destroy(&it->item); sixel_destroy(&it->item);
tll_free(grid->sixel_images); tll_free(old_sixels);
/* Move updated sixels back */
tll_foreach(new_sixels, it)
tll_push_back(grid->sixel_images, it->item);
tll_free(new_sixels);
tll_free(tracking_points); tll_free(tracking_points);
} }

View file

@ -795,8 +795,42 @@ render_sixel(struct terminal *term, pixman_image_t *pix,
static void static void
render_sixel_images(struct terminal *term, pixman_image_t *pix) render_sixel_images(struct terminal *term, pixman_image_t *pix)
{ {
tll_foreach(term->grid->sixel_images, it) if (likely(tll_length(term->grid->sixel_images)) == 0)
return;
const int scrollback_end
= (term->grid->offset + term->rows) & (term->grid->num_rows - 1);
const int view_start
= (term->grid->view
- scrollback_end
+ term->grid->num_rows) & (term->grid->num_rows - 1);
const int view_end = view_start + term->rows - 1;
//LOG_DBG("SIXELS: %zu images, view=%d-%d",
// tll_length(term->grid->sixel_images), view_start, view_end);
tll_foreach(term->grid->sixel_images, it) {
const struct sixel *six = &it->item;
const int start
= (six->pos.row
- scrollback_end
+ term->grid->num_rows) & (term->grid->num_rows - 1);
const int end = start + six->rows - 1;
//LOG_DBG(" sixel: %d-%d", start, end);
if (start > view_end) {
/* Sixel starts after view ends, no need to try to render it */
continue;
} else if (end < view_start) {
/* Image ends before view starts. Since the image list is
* sorted, we can safely stop here */
break;
}
render_sixel(term, pix, &it->item); render_sixel(term, pix, &it->item);
}
} }
static void static void

198
sixel.c
View file

@ -98,102 +98,92 @@ sixel_erase(struct terminal *term, struct sixel *sixel)
sixel_destroy(sixel); sixel_destroy(sixel);
} }
/* Row numbers are absolute */ static int
static void rebase_row(const struct terminal *term, int abs_row)
sixel_delete_at_point(struct terminal *term, int row, int col)
{ {
assert(row >= 0); int scrollback_start = term->grid->offset + term->rows;
assert(row < term->grid->num_rows); int rebased_row = abs_row - scrollback_start + term->grid->num_rows;
assert(col < term->grid->num_cols);
if (likely(tll_length(term->grid->sixel_images) == 0)) rebased_row &= term->grid->num_rows - 1;
return; return rebased_row;
tll_foreach(term->grid->sixel_images, it) {
struct sixel *six = &it->item;
const int six_start = six->pos.row;
const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1);
/* We should never generate scrollback wrapping sixels */
assert(six_end >= six_start);
if (row >= six_start && row <= six_end) {
const int col_start = six->pos.col;
const int col_end = six->pos.col + six->cols;
if (col < 0 || (col >= col_start && col < col_end)) {
sixel_erase(term, six);
tll_remove(term->grid->sixel_images, it);
}
}
}
} }
/* TODO: remove */ static bool
void verify_sixel_list_order(const struct terminal *term)
sixel_delete_at_row(struct terminal *term, int row)
{ {
if (likely(tll_length(term->grid->sixel_images) == 0)) #if defined(_DEBUG)
return; int prev_row = INT_MAX;
sixel_delete_at_point(
term, (term->grid->offset + row) & (term->grid->num_rows - 1), -1);
}
/* Row numbers are absolute */
static void
_sixel_delete_in_range(struct terminal *term, int start, int end)
{
assert(end >= start);
assert(start >= 0);
assert(start < term->grid->num_rows);
assert(end >= 0);
assert(end < term->grid->num_rows);
tll_foreach(term->grid->sixel_images, it) { tll_foreach(term->grid->sixel_images, it) {
struct sixel *six = &it->item; int row = rebase_row(term, it->item.pos.row + it->item.rows - 1);
assert(row < prev_row);
if (row >= prev_row)
return false;
prev_row = row;
}
#endif
return true;
}
const int six_start = six->pos.row; static void
const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1); sixel_insert(struct terminal *term, struct sixel sixel)
{
int end_row = rebase_row(term, sixel.pos.row + sixel.rows - 1);
/* We should never generate scrollback wrapping sixels */ tll_foreach(term->grid->sixel_images, it) {
assert(six_end >= six_start); if (rebase_row(term, it->item.pos.row + it->item.rows - 1) < end_row) {
tll_insert_before(term->grid->sixel_images, it, sixel);
if ((start <= six_start && end >= six_start) || /* Crosses sixel start boundary */ goto out;
(start <= six_end && end >= six_end) || /* Crosses sixel end boundary */
(start >= six_start && end <= six_end)) /* Fully within sixel range */
{
sixel_erase(term, six);
tll_remove(term->grid->sixel_images, it);
} }
} }
tll_push_back(term->grid->sixel_images, sixel);
out:
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
LOG_DBG("sixel list after insertion:");
tll_foreach(term->grid->sixel_images, it) {
LOG_DBG(" rows=%d+%d", it->item.pos.row, it->item.rows);
}
#else
verify_sixel_list_order(term);
#endif
} }
void void
sixel_delete_in_range(struct terminal *term, int _start, int _end) sixel_scroll_up(struct terminal *term, int rows)
{ {
if (likely(tll_length(term->grid->sixel_images) == 0)) tll_rforeach(term->grid->sixel_images, it) {
return; struct sixel *six = &it->item;
if (_start == _end) { int six_start = rebase_row(term, six->pos.row);
/* Avoid expensive wrap calculation */ if (six_start < rows) {
return sixel_delete_at_point( sixel_destroy(six);
term, (term->grid->offset + _start) & (term->grid->num_rows - 1), -1); tll_remove(term->grid->sixel_images, it);
}
assert(_end >= _start);
const int lines = _end - _start + 1;
const int start = (term->grid->offset + _start) & (term->grid->num_rows - 1);
const int end = (start + lines - 1) & (term->grid->num_rows - 1);
const bool wraps = end < start;
if (wraps) {
int rows_to_wrap_around = term->grid->num_rows - start;
assert(lines - rows_to_wrap_around > 0);
_sixel_delete_in_range(term, start, term->grid->num_rows);
_sixel_delete_in_range(term, 0, lines - rows_to_wrap_around);
} else } else
_sixel_delete_in_range(term, start, end); break;
}
verify_sixel_list_order(term);
}
void
sixel_scroll_down(struct terminal *term, int rows)
{
assert(term->grid->num_rows >= rows);
tll_foreach(term->grid->sixel_images, it) {
struct sixel *six = &it->item;
int six_end = rebase_row(term, six->pos.row + six->rows - 1);
if (six_end >= term->grid->num_rows - rows) {
sixel_destroy(six);
tll_remove(term->grid->sixel_images, it);
} else
break;
}
verify_sixel_list_order(term);
} }
static void static void
@ -295,7 +285,7 @@ sixel_overwrite(struct terminal *term, struct sixel *six,
PIXMAN_a8r8g8b8, PIXMAN_a8r8g8b8,
imgs[i].width, imgs[i].height, imgs[i].width, imgs[i].height,
imgs[i].data, imgs[i].width * sizeof(uint32_t)); imgs[i].data, imgs[i].width * sizeof(uint32_t));
tll_push_front(term->grid->sixel_images, imgs[i]); sixel_insert(term, imgs[i]);
} }
} }
@ -304,24 +294,27 @@ static void
_sixel_overwrite_by_rectangle( _sixel_overwrite_by_rectangle(
struct terminal *term, int row, int col, int height, int width) struct terminal *term, int row, int col, int height, int width)
{ {
assert(row + height <= term->grid->num_rows);
assert(row >= 0); assert(row >= 0);
assert(row + height <= term->grid->num_rows); assert(row + height <= term->grid->num_rows);
assert(col >= 0); assert(col >= 0);
assert(col + width <= term->grid->num_cols); assert(col + width <= term->grid->num_cols);
/* We don't handle rectangle wrapping around */
assert(row + height <= term->grid->num_rows);
const int start = row; const int start = row;
const int end = row + height - 1; const int end = row + height - 1;
const int scrollback_rel_start = rebase_row(term, start);
tll_foreach(term->grid->sixel_images, it) { tll_foreach(term->grid->sixel_images, it) {
struct sixel *six = &it->item; struct sixel *six = &it->item;
const int six_start = six->pos.row; const int six_start = six->pos.row;
const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1); const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1);
const int six_scrollback_rel_end = rebase_row(term, six_end);
if (six_scrollback_rel_end < scrollback_rel_start) {
/* All remaining sixels are *before* our rectangle */
break;
}
/* We should never generate scrollback wrapping sixels */ /* We should never generate scrollback wrapping sixels */
assert(six_end >= six_start); assert(six_end >= six_start);
@ -365,20 +358,23 @@ sixel_overwrite_by_rectangle(
_sixel_overwrite_by_rectangle(term, start, col, height, width); _sixel_overwrite_by_rectangle(term, start, col, height, width);
} }
/* Row numbers are absolute */ /* Row numbers are relative to grid offset */
static void void
_sixel_overwrite_by_row(struct terminal *term, int row, int col, int width) sixel_overwrite_by_row(struct terminal *term, int _row, int col, int width)
{ {
assert(col >= 0); assert(col >= 0);
assert(row >= 0); assert(_row >= 0);
assert(row < term->grid->num_rows); assert(_row < term->rows);
assert(col >= 0); assert(col >= 0);
assert(col + width <= term->grid->num_cols); assert(col + width <= term->grid->num_cols);
if (likely(tll_length(term->grid->sixel_images) == 0)) if (likely(tll_length(term->grid->sixel_images) == 0))
return; return;
const int row = (term->grid->offset + _row) & (term->grid->num_rows - 1);
const int scrollback_rel_row = rebase_row(term, row);
tll_foreach(term->grid->sixel_images, it) { tll_foreach(term->grid->sixel_images, it) {
struct sixel *six = &it->item; struct sixel *six = &it->item;
const int six_start = six->pos.row; const int six_start = six->pos.row;
@ -387,6 +383,13 @@ _sixel_overwrite_by_row(struct terminal *term, int row, int col, int width)
/* We should never generate scrollback wrapping sixels */ /* We should never generate scrollback wrapping sixels */
assert(six_end >= six_start); assert(six_end >= six_start);
const int six_scrollback_rel_end = rebase_row(term, six_end);
if (six_scrollback_rel_end < scrollback_rel_row) {
/* All remaining sixels are *before* "our" row */
break;
}
if (row >= six_start && row <= six_end) { if (row >= six_start && row <= six_end) {
const int col_start = six->pos.col; const int col_start = six->pos.col;
const int col_end = six->pos.col + six->cols - 1; const int col_end = six->pos.col + six->cols - 1;
@ -404,19 +407,10 @@ _sixel_overwrite_by_row(struct terminal *term, int row, int col, int width)
} }
void void
sixel_overwrite_by_row(struct terminal *term, int row, int col, int width) sixel_overwrite_at_cursor(struct terminal *term, int width)
{
_sixel_overwrite_by_row(
term,
(term->grid->offset + row) & (term->grid->num_rows - 1),
col, width);
}
void
sixel_overwrite_at_cursor(struct terminal *term)
{ {
sixel_overwrite_by_row( sixel_overwrite_by_row(
term, term->grid->cursor.point.row, term->grid->cursor.point.col, 1); term, term->grid->cursor.point.row, term->grid->cursor.point.col, width);
} }
void void
@ -475,7 +469,7 @@ sixel_unhook(struct terminal *term)
term_formfeed(term); term_formfeed(term);
render_refresh(term); render_refresh(term);
tll_push_back(term->grid->sixel_images, image); sixel_insert(term, image);
pixel_row_idx += height; pixel_row_idx += height;
pixel_rows_left -= height; pixel_rows_left -= height;

13
sixel.h
View file

@ -13,15 +13,8 @@ void sixel_unhook(struct terminal *term);
void sixel_destroy(struct sixel *sixel); void sixel_destroy(struct sixel *sixel);
void sixel_destroy_all(struct terminal *term); void sixel_destroy_all(struct terminal *term);
/* void sixel_scroll_up(struct terminal *term, int rows);
* Deletes all sixels that are touched by the specified row(s). Used void sixel_scroll_down(struct terminal *term, int rows);
* when scrolling, to competely remove sixels that has either
* completely, or partly scrolled out of history.
*
* Row numbers are relative to the current grid offset.
*/
void sixel_delete_in_range(struct terminal *term, int row_start, int row_end);
void sixel_delete_at_row(struct terminal *term, int row);
/* /*
* Remove sixel data from the specified location. Used when printing * Remove sixel data from the specified location. Used when printing
@ -33,7 +26,7 @@ void sixel_delete_at_row(struct terminal *term, int row);
void sixel_overwrite_by_rectangle( void sixel_overwrite_by_rectangle(
struct terminal *term, int row, int col, int height, int width); struct terminal *term, int row, int col, int height, int width);
void sixel_overwrite_by_row(struct terminal *term, int row, int col, int width); void sixel_overwrite_by_row(struct terminal *term, int row, int col, int width);
void sixel_overwrite_at_cursor(struct terminal *term); void sixel_overwrite_at_cursor(struct terminal *term, int width);
void sixel_colors_report_current(struct terminal *term); void sixel_colors_report_current(struct terminal *term);
void sixel_colors_reset(struct terminal *term); void sixel_colors_reset(struct terminal *term);

View file

@ -1802,6 +1802,8 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows
} }
} }
sixel_scroll_up(term, rows);
bool view_follows = term->grid->view == term->grid->offset; bool view_follows = term->grid->view == term->grid->offset;
term->grid->offset += rows; term->grid->offset += rows;
term->grid->offset &= term->grid->num_rows - 1; term->grid->offset &= term->grid->num_rows - 1;
@ -1823,7 +1825,6 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows
for (int r = region.end - rows; r < region.end; r++) for (int r = region.end - rows; r < region.end; r++)
erase_line(term, grid_row_and_alloc(term->grid, r)); erase_line(term, grid_row_and_alloc(term->grid, r));
sixel_delete_in_range(term, region.end - rows, region.end - 1);
term_damage_scroll(term, DAMAGE_SCROLL, region, rows); term_damage_scroll(term, DAMAGE_SCROLL, region, rows);
term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
@ -1864,6 +1865,8 @@ term_scroll_reverse_partial(struct terminal *term,
} }
} }
sixel_scroll_down(term, rows);
bool view_follows = term->grid->view == term->grid->offset; bool view_follows = term->grid->view == term->grid->offset;
term->grid->offset -= rows; term->grid->offset -= rows;
while (term->grid->offset < 0) while (term->grid->offset < 0)
@ -1890,7 +1893,6 @@ term_scroll_reverse_partial(struct terminal *term,
for (int r = region.start; r < region.start + rows; r++) for (int r = region.start; r < region.start + rows; r++)
erase_line(term, grid_row_and_alloc(term->grid, r)); erase_line(term, grid_row_and_alloc(term->grid, r));
sixel_delete_in_range(term, region.start, region.start + rows - 1);
term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows); term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows);
term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
@ -2407,7 +2409,7 @@ term_print(struct terminal *term, wchar_t wc, int width)
print_linewrap(term); print_linewrap(term);
print_insert(term, width); print_insert(term, width);
sixel_overwrite_at_cursor(term); sixel_overwrite_at_cursor(term, width);
/* *Must* get current cell *after* linewrap+insert */ /* *Must* get current cell *after* linewrap+insert */
struct row *row = term->grid->cur_row; struct row *row = term->grid->cur_row;