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

94
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,31 +109,57 @@ 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' struct sixel sixel = it->item;
* row. Instead, copy the sixel (with 'row' updated), to a sixel.pos.row = new_row_idx;
* temporary list and remove the original sixel.
* /* Make sure it doesn't cross the wrap-around after being re-based */
* After we've reflowed the grid we'll move the sixels back to int end = (sixel.pos.row + sixel.rows - 1) & (new_rows - 1);
* the "real" sixel list. if (end < sixel.pos.row) {
*/ /* TODO: split instead of destroying */
tll_foreach(grid->sixel_images, it) { sixel_destroy(&it->item);
if (it->item.pos.row == old_row_idx) { } else {
struct sixel six = it->item;
six.pos.row = new_row_idx; /* 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);
int end = (six.pos.row + six.rows - 1) & (new_rows - 1);
if (end < six.pos.row) {
/* TODO: split sixel instead of removing it... */
sixel_destroy(&it->item);
} else
tll_push_back(new_sixels, six);
tll_remove(grid->sixel_images, it);
} }
/* Sixel has been either re-mapped, or destroyed */
tll_remove(old_sixels, it);
#undef rebase_row
} }
#define line_wrap() \ #define line_wrap() \
@ -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

188
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;
}
static bool
verify_sixel_list_order(const struct terminal *term)
{
#if defined(_DEBUG)
int prev_row = INT_MAX;
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);
const int six_start = six->pos.row; assert(row < prev_row);
const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1); if (row >= prev_row)
return false;
prev_row = row;
}
#endif
return true;
}
/* We should never generate scrollback wrapping sixels */ static void
assert(six_end >= six_start); sixel_insert(struct terminal *term, struct sixel sixel)
{
int end_row = rebase_row(term, sixel.pos.row + sixel.rows - 1);
if (row >= six_start && row <= six_end) { tll_foreach(term->grid->sixel_images, it) {
const int col_start = six->pos.col; if (rebase_row(term, it->item.pos.row + it->item.rows - 1) < end_row) {
const int col_end = six->pos.col + six->cols; tll_insert_before(term->grid->sixel_images, it, sixel);
goto out;
if (col < 0 || (col >= col_start && col < col_end)) {
sixel_erase(term, six);
tll_remove(term->grid->sixel_images, it);
}
} }
} }
}
/* TODO: remove */ tll_push_back(term->grid->sixel_images, sixel);
void
sixel_delete_at_row(struct terminal *term, int row)
{
if (likely(tll_length(term->grid->sixel_images) == 0))
return;
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);
out:
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
LOG_DBG("sixel list after insertion:");
tll_foreach(term->grid->sixel_images, it) { 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
sixel_scroll_up(struct terminal *term, int rows)
{
tll_rforeach(term->grid->sixel_images, it) {
struct sixel *six = &it->item; struct sixel *six = &it->item;
const int six_start = six->pos.row; int six_start = rebase_row(term, six->pos.row);
const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1); if (six_start < rows) {
sixel_destroy(six);
/* We should never generate scrollback wrapping sixels */
assert(six_end >= six_start);
if ((start <= six_start && end >= six_start) || /* Crosses sixel start boundary */
(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_remove(term->grid->sixel_images, it);
} } else
break;
} }
verify_sixel_list_order(term);
} }
void void
sixel_delete_in_range(struct terminal *term, int _start, int _end) sixel_scroll_down(struct terminal *term, int rows)
{ {
if (likely(tll_length(term->grid->sixel_images) == 0)) assert(term->grid->num_rows >= rows);
return;
if (_start == _end) { tll_foreach(term->grid->sixel_images, it) {
/* Avoid expensive wrap calculation */ struct sixel *six = &it->item;
return sixel_delete_at_point(
term, (term->grid->offset + _start) & (term->grid->num_rows - 1), -1); 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;
} }
assert(_end >= _start); verify_sixel_list_order(term);
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
_sixel_delete_in_range(term, start, end);
} }
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;