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).
* Glyphs overflowing into surrounding cells
(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
`\E[J3` was executed.
* Assert (debug builds) when an `\e]4` OSC escape was not followed by
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
after each image.
* Do not auto-resize a sixel image for which the cllent has specified
a size. This fixes an issue where an image would incorrectly
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
no longer erases the entire image, only the part(s) covered by the
new text or image.
* Terminfo entry `kb2` (center keypad key); it is now set to `\EOu`
(which is what foot emits) instead of the incorrect value `\EOE`.
* Sixel images being erased when printing text next to them.
* Sixel handling when resizing window.
* Sixel handling when scrollback wraps around.
### 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* */
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 */
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)
continue;
/*
* Update 'row' in all sixels that *begin* at current row
*
* Since we might end up pushing the sixel down, we can't
* 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;
/* Map sixels on current "old" row to current "new row" */
tll_foreach(old_sixels, it) {
if (it->item.pos.row != old_row_idx)
continue;
struct sixel sixel = it->item;
sixel.pos.row = new_row_idx;
/* 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);
} else {
/* 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() \
@ -145,6 +174,12 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols,
} else { \
memset(new_row->cells, 0, new_cols * sizeof(new_row->cells[0])); \
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)
@ -279,15 +314,10 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols,
grid->cursor.lcf = false;
grid->saved_cursor.lcf = false;
/* Destroy any non-moved sixels */
tll_foreach(grid->sixel_images, it)
/* Free sixels we failed to "map" to the new grid */
tll_foreach(old_sixels, it)
sixel_destroy(&it->item);
tll_free(grid->sixel_images);
/* Move updated sixels back */
tll_foreach(new_sixels, it)
tll_push_back(grid->sixel_images, it->item);
tll_free(new_sixels);
tll_free(old_sixels);
tll_free(tracking_points);
}

View file

@ -795,8 +795,42 @@ render_sixel(struct terminal *term, pixman_image_t *pix,
static void
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);
}
}
static void

188
sixel.c
View file

@ -98,102 +98,92 @@ sixel_erase(struct terminal *term, struct sixel *sixel)
sixel_destroy(sixel);
}
/* Row numbers are absolute */
static void
sixel_delete_at_point(struct terminal *term, int row, int col)
static int
rebase_row(const struct terminal *term, int abs_row)
{
assert(row >= 0);
assert(row < term->grid->num_rows);
assert(col < term->grid->num_cols);
int scrollback_start = term->grid->offset + term->rows;
int rebased_row = abs_row - scrollback_start + term->grid->num_rows;
if (likely(tll_length(term->grid->sixel_images) == 0))
return;
rebased_row &= term->grid->num_rows - 1;
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) {
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);
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;
}
/* We should never generate scrollback wrapping sixels */
assert(six_end >= six_start);
static void
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) {
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);
}
tll_foreach(term->grid->sixel_images, it) {
if (rebase_row(term, it->item.pos.row + it->item.rows - 1) < end_row) {
tll_insert_before(term->grid->sixel_images, it, sixel);
goto out;
}
}
}
/* TODO: remove */
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);
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
sixel_scroll_up(struct terminal *term, int rows)
{
tll_rforeach(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 ((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);
int six_start = rebase_row(term, six->pos.row);
if (six_start < rows) {
sixel_destroy(six);
tll_remove(term->grid->sixel_images, it);
}
} else
break;
}
verify_sixel_list_order(term);
}
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))
return;
assert(term->grid->num_rows >= rows);
if (_start == _end) {
/* Avoid expensive wrap calculation */
return sixel_delete_at_point(
term, (term->grid->offset + _start) & (term->grid->num_rows - 1), -1);
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;
}
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
_sixel_delete_in_range(term, start, end);
verify_sixel_list_order(term);
}
static void
@ -295,7 +285,7 @@ sixel_overwrite(struct terminal *term, struct sixel *six,
PIXMAN_a8r8g8b8,
imgs[i].width, imgs[i].height,
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(
struct terminal *term, int row, int col, int height, int width)
{
assert(row + height <= term->grid->num_rows);
assert(row >= 0);
assert(row + height <= term->grid->num_rows);
assert(col >= 0);
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 end = row + height - 1;
const int scrollback_rel_start = rebase_row(term, start);
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);
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 */
assert(six_end >= six_start);
@ -365,20 +358,23 @@ sixel_overwrite_by_rectangle(
_sixel_overwrite_by_rectangle(term, start, col, height, width);
}
/* Row numbers are absolute */
static void
_sixel_overwrite_by_row(struct terminal *term, int row, int col, int width)
/* Row numbers are relative to grid offset */
void
sixel_overwrite_by_row(struct terminal *term, int _row, int col, int width)
{
assert(col >= 0);
assert(row >= 0);
assert(row < term->grid->num_rows);
assert(_row >= 0);
assert(_row < term->rows);
assert(col >= 0);
assert(col + width <= term->grid->num_cols);
if (likely(tll_length(term->grid->sixel_images) == 0))
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) {
struct sixel *six = &it->item;
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 */
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) {
const int col_start = six->pos.col;
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
sixel_overwrite_by_row(struct terminal *term, int row, int col, 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_at_cursor(struct terminal *term, int width)
{
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
@ -475,7 +469,7 @@ sixel_unhook(struct terminal *term)
term_formfeed(term);
render_refresh(term);
tll_push_back(term->grid->sixel_images, image);
sixel_insert(term, image);
pixel_row_idx += 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_all(struct terminal *term);
/*
* Deletes all sixels that are touched by the specified row(s). Used
* 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);
void sixel_scroll_up(struct terminal *term, int rows);
void sixel_scroll_down(struct terminal *term, int rows);
/*
* 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(
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_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_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;
term->grid->offset += rows;
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++)
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->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;
term->grid->offset -= rows;
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++)
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->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_insert(term, width);
sixel_overwrite_at_cursor(term);
sixel_overwrite_at_cursor(term, width);
/* *Must* get current cell *after* linewrap+insert */
struct row *row = term->grid->cur_row;