render: remove most of the special handling of cursor rendering

Previously, we had to explicitly render the old cursor cell *before*
applying scrolling damage.

We then rendered all the dirty rows, *without* rendering the cursor -
even if the cursor cell was among the dirty rows.

Finally, when everything else was done, we explicitly rendered the
cursor cell.

This meant a lot of code, and unnecessary render_cell() calls, along
with unnecessary wl_surface_damage_buffer() calls.

This was a necessary in the early design of foot, but not anymore.

We can simply mark both the old cursor cell, and the current one, as
dirty and let the normal rendering framework render it. All we need to
do is pass the cursor column to render_row(), so that it can pass
has_cursor=true in the appropriate call to render_cell(). We pass -1
here for all rows, except the cursor's row, where we pass the actual
cursor column.

With this, there's no need to calculate whether the cursor is visible
or not; just mark it's cell as dirty, and if that row is visible, the
normal rendering will take care of it.

This also simplifies the state needed to be saved between two frames;
we only need a row pointer, and the cursor column index.

Part of https://codeberg.org/dnkl/foot/issues/35
This commit is contained in:
Daniel Eklöf 2020-07-12 12:56:10 +02:00
parent 0bdbf45418
commit 2bdd0a7c80
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
2 changed files with 52 additions and 83 deletions

130
render.c
View file

@ -834,10 +834,11 @@ render_sixel_images(struct terminal *term, pixman_image_t *pix)
}
static void
render_row(struct terminal *term, pixman_image_t *pix, struct row *row, int row_no)
render_row(struct terminal *term, pixman_image_t *pix, struct row *row,
int row_no, int cursor_col)
{
for (int col = term->cols - 1; col >= 0; col--)
render_cell(term, pix, row, col, row_no, false);
render_cell(term, pix, row, col, row_no, cursor_col == col);
}
int
@ -873,11 +874,22 @@ render_worker_thread(void *_ctx)
int row_no = tll_pop_front(term->render.workers.queue);
mtx_unlock(lock);
/* Translate offset-relative cursor row to view-relative */
struct coord cursor = term->grid->cursor.point;
cursor.row += term->grid->offset;
cursor.row -= term->grid->view;
cursor.row &= term->grid->num_rows - 1;
switch (row_no) {
default:
default: {
assert(buf != NULL);
render_row(term, buf->pix[my_id], grid_row_in_view(term->grid, row_no), row_no);
struct row *row = grid_row_in_view(term->grid, row_no);
int cursor_col = cursor.row == row_no ? cursor.col : -1;
render_row(term, buf->pix[my_id], row, row_no, cursor_col);
break;
}
case -1:
frame_done = true;
@ -1337,28 +1349,6 @@ grid_render(struct terminal *term)
term->render.was_searching = term->is_searching;
}
/* Erase old cursor (if we rendered a cursor last time) */
if (term->render.last_cursor.row != NULL) {
struct row *row = term->render.last_cursor.row;
struct coord at = term->render.last_cursor.in_view;
term->render.last_cursor.row = NULL;
struct cell *cell = &row->cells[at.col];
/* If cell is already dirty, it will be rendered anyway */
if (cell->attrs.clean) {
cell->attrs.clean = 0;
int cols = render_cell(term, buf->pix[0], row, at.col, at.row, false);
wl_surface_damage_buffer(
term->window->surface,
term->margins.left + at.col * term->cell_width,
term->margins.top + at.row * term->cell_height,
cols * term->cell_width, term->cell_height);
}
}
tll_foreach(term->grid->scroll_damage, it) {
switch (it->item.type) {
case DAMAGE_SCROLL:
@ -1402,6 +1392,28 @@ grid_render(struct terminal *term)
*/
selection_dirty_cells(term);
/* Mark old cursor cell as dirty, to force it to be re-rendered */
if (term->render.last_cursor.row != NULL) {
struct row *row = term->render.last_cursor.row;
struct cell *cell = &row->cells[term->render.last_cursor.col];
cell->attrs.clean = 0;
row->dirty = true;
}
/* Remember current cursor position, for the next frame */
term->render.last_cursor.row = grid_row(term->grid, term->grid->cursor.point.row);
term->render.last_cursor.col = term->grid->cursor.point.col;
/* Mark current cursor cell as dirty, to ensure it is rendered */
if (!term->hide_cursor) {
const struct coord *cursor = &term->grid->cursor.point;
struct row *row = grid_row(term->grid, cursor->row);
struct cell *cell = &row->cells[cursor->col];
cell->attrs.clean = 0;
row->dirty = true;
}
if (term->render.workers.count > 0) {
term->render.workers.buf = buf;
@ -1434,6 +1446,10 @@ grid_render(struct terminal *term)
tll_push_back(term->render.workers.queue, -1);
cnd_broadcast(&term->render.workers.cond);
mtx_unlock(&term->render.workers.lock);
for (size_t i = 0; i < term->render.workers.count; i++)
sem_wait(&term->render.workers.done);
term->render.workers.buf = NULL;
} else {
for (int r = 0; r < term->rows; r++) {
struct row *row = grid_row_in_view(term->grid, r);
@ -1441,7 +1457,14 @@ grid_render(struct terminal *term)
if (!row->dirty)
continue;
render_row(term, buf->pix[0], row, r);
/* Translate offset-relative row to view-relative */
struct coord cursor = term->grid->cursor.point;
cursor.row += term->grid->offset;
cursor.row -= term->grid->view;
cursor.row &= term->grid->num_rows - 1;
int cursor_col = cursor.row == r ? cursor.col : -1;
render_row(term, buf->pix[0], row, r, cursor_col);
row->dirty = false;
wl_surface_damage_buffer(
@ -1451,59 +1474,6 @@ grid_render(struct terminal *term)
}
}
/*
* Determine if we need to render a cursor or not. The cursor
* could be hidden. Or it could have been scrolled out of view.
*/
bool cursor_is_visible = false;
int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1);
int cursor_row = (term->grid->offset + term->grid->cursor.point.row) & (term->grid->num_rows - 1);
if (view_end >= term->grid->view) {
/* Not wrapped */
if (cursor_row >= term->grid->view && cursor_row <= view_end)
cursor_is_visible = true;
} else {
/* Wrapped */
if (cursor_row >= term->grid->view || cursor_row <= view_end)
cursor_is_visible = true;
}
/*
* Wait for workers to finish before we render the cursor. This is
* because the cursor cell might be dirty, in which case a worker
* will render it (but without the cursor).
*/
if (term->render.workers.count > 0) {
for (size_t i = 0; i < term->render.workers.count; i++)
sem_wait(&term->render.workers.done);
term->render.workers.buf = NULL;
}
if (cursor_is_visible && !term->hide_cursor) {
/* Remember cursor coordinates so that we can erase it next
* time. Note that we need to re-align it against the view. */
int view_aligned_row
= (cursor_row - term->grid->view + term->grid->num_rows) & (term->grid->num_rows - 1);
term->render.last_cursor.actual = term->grid->cursor.point;
term->render.last_cursor.in_view = (struct coord) {
term->grid->cursor.point.col, view_aligned_row};
struct row *row = grid_row_in_view(term->grid, view_aligned_row);
struct cell *cell = &row->cells[term->grid->cursor.point.col];
cell->attrs.clean = 0;
term->render.last_cursor.row = row;
int cols_updated = render_cell(
term, buf->pix[0], row, term->grid->cursor.point.col, view_aligned_row, true);
wl_surface_damage_buffer(
term->window->surface,
term->margins.left + term->grid->cursor.point.col * term->cell_width,
term->margins.top + view_aligned_row * term->cell_height,
cols_updated * term->cell_width, term->cell_height);
}
render_sixel_images(term, buf->pix[0]);
if (term->flash.active) {

View file

@ -378,9 +378,8 @@ struct terminal {
/* Last rendered cursor position */
struct {
struct coord actual; /* Absolute */
struct coord in_view; /* Offset by view */
struct row *row; /* Actual row TODO: remove */
struct row *row;
int col;
} last_cursor;
struct buffer *last_buf; /* Buffer we rendered to last time */