diff --git a/grid.c b/grid.c index d0b6db20..02db2f48 100644 --- a/grid.c +++ b/grid.c @@ -10,6 +10,81 @@ #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) +static bool +damage_merge_range(struct grid *grid, const struct damage *dmg) +{ + if (tll_length(grid->damage) == 0) + return false;; + + struct damage *old = &tll_back(grid->damage); + if (old->type != dmg->type) + return false; + + const int start = dmg->range.start; + const int end = start + dmg->range.length; + + const int prev_start = old->range.start; + const int prev_end = prev_start + old->range.length; + + if ((start >= prev_start && start <= prev_end) || + (end >= prev_start && end <= prev_end) || + (start <= prev_start && end >= prev_end)) + { + /* The two damage ranges intersect */ + int new_start = min(start, prev_start); + int new_end = max(end, prev_end); + + old->range.start = new_start; + old->range.length = new_end - new_start; + + assert(old->range.start >= 0); + assert(old->range.start < grid->rows * grid->cols); + assert(old->range.length >= 0); + assert(old->range.start + old->range.length <= grid->rows * grid->cols); + return true; + } + + return false; +} + +void +grid_damage_update(struct grid *grid, int start, int length) +{ + struct damage dmg = { + .type = DAMAGE_UPDATE, + .range = {.start = start, .length = length}, + }; + + assert(dmg.range.start >= 0); + assert(dmg.range.start < grid->rows * grid->cols); + assert(dmg.range.length >= 0); + assert(dmg.range.start + dmg.range.length <= grid->rows * grid->cols); + + if (damage_merge_range(grid, &dmg)) + return; + + tll_push_back(grid->damage, dmg); +} + +void +grid_damage_erase(struct grid *grid, int start, int length) +{ + struct damage dmg = { + .type = DAMAGE_ERASE, + .range = {.start = start, .length = length}, + }; + + assert(dmg.range.start >= 0); + assert(dmg.range.start < grid->rows * grid->cols); + assert(dmg.range.length >= 0); + assert(dmg.range.start + dmg.range.length <= grid->rows * grid->cols); + + if (damage_merge_range(grid, &dmg)) + return; + + tll_push_back(grid->damage, dmg); +} + void grid_erase(struct grid *grid, int start, int end) { @@ -21,11 +96,9 @@ grid_erase(struct grid *grid, int start, int end) cell->attrs.foreground = grid->foreground; cell->attrs.background = grid->background; - - cell->dirty = true; } - grid->dirty = true; + grid_damage_erase(grid, start, end - start); } int @@ -46,9 +119,8 @@ grid_cursor_to(struct grid *grid, int row, int col) assert(new_linear >= 0); assert(new_linear < grid->rows * grid->cols); - grid->cells[grid->linear_cursor].dirty = true; - grid->cells[new_linear].dirty = true; - grid->dirty = true; + grid_damage_update(grid, grid->linear_cursor, 1); + grid_damage_update(grid, new_linear, 1); grid->print_needs_wrap = false; grid->linear_cursor = new_linear; @@ -98,7 +170,12 @@ grid_scroll(struct grid *grid, int rows) int count = cell_end - cell_start; LOG_DBG("moving %d cells from %d", count, cell_start); - memmove(&grid->cells[0], &grid->cells[cell_start], count * sizeof(grid->cells[0])); + memmove( + &grid->cells[0], &grid->cells[cell_start], + count * sizeof(grid->cells[0])); + + tll_free(grid->damage); + + grid_damage_update(grid, 0, count); grid_erase(grid, cell_end - rows * grid->cols, grid->rows * grid->cols); - grid->all_dirty = true; } diff --git a/grid.h b/grid.h index 5b016830..e8abbd89 100644 --- a/grid.h +++ b/grid.h @@ -2,6 +2,9 @@ #include "terminal.h" +void grid_damage_update(struct grid *grid, int start, int length); +void grid_damage_erase(struct grid *grid, int start, int length); + void grid_erase(struct grid *grid, int start, int end); void grid_cursor_to(struct grid *grid, int row, int col); diff --git a/main.c b/main.c index d162e3b7..9a0e1519 100644 --- a/main.c +++ b/main.c @@ -24,6 +24,9 @@ #include "terminal.h" #include "vt.h" #include "input.h" +#include "grid.h" + +#define min(x, y) ((x) < (y) ? (x) : (y)) static const uint32_t default_foreground = 0xffffffff; static const uint32_t default_background = 0x000000ff; @@ -68,7 +71,9 @@ static const struct wl_callback_listener frame_listener = { static void grid_render(struct context *c) { - assert(c->term.grid.dirty); + if (tll_length(c->term.grid.damage) == 0) + return; + assert(c->width > 0); assert(c->height > 0); @@ -80,80 +85,166 @@ grid_render(struct context *c) cairo_set_operator(buf->cairo, CAIRO_OPERATOR_SOURCE); cairo_set_scaled_font(buf->cairo, c->font); - if (c->term.grid.all_dirty) { - br = (double)((default_background >> 24) & 0xff) / 255.0; - bg = (double)((default_background >> 16) & 0xff) / 255.0; - bb = (double)((default_background >> 8) & 0xff) / 255.0; - cairo_set_source_rgba(buf->cairo, br, bg, bb, 1.0); - cairo_rectangle(buf->cairo, 0, 0, buf->width, buf->height); - cairo_fill(buf->cairo); - } + tll_foreach(c->term.grid.damage, it) { + switch (it->item.type) { + case DAMAGE_ERASE: { + LOG_DBG("damage: ERASE: %d -> %d", + it->item.range.start, it->item.range.start + it->item.range.length); + br = (double)((default_background >> 24) & 0xff) / 255.0; + bg = (double)((default_background >> 16) & 0xff) / 255.0; + bb = (double)((default_background >> 8) & 0xff) / 255.0; + cairo_set_source_rgba(buf->cairo, br, bg, bb, 1.0); - for (int row = 0; row < c->term.grid.rows; row++) { - for (int col = 0; col < c->term.grid.cols; col++) { - int cell_idx = row * c->term.grid.cols + col; - struct cell *cell = &c->term.grid.cells[cell_idx]; + const int cols = c->term.grid.cols; - if (!cell->dirty && !c->term.grid.all_dirty) - continue; + int start = it->item.range.start; + int left = it->item.range.length; - cell->dirty = false; + int row = start / cols; + int col = start % cols; - bool has_cursor = c->term.grid.linear_cursor == cell_idx; + /* Partial initial line */ + if (col != 0) { + int cell_count = min(left, cols - col); - int y_ofs = row * c->term.grid.cell_height + c->fextents.ascent; - int x_ofs = col * c->term.grid.cell_width; + int x = col * c->term.grid.cell_width; + int y = row * c->term.grid.cell_height; + int width = cell_count * c->term.grid.cell_width; + int height = c->term.grid.cell_height; - int damage_x = x_ofs; - int damage_y = y_ofs - c->fextents.ascent; + cairo_rectangle(buf->cairo, x, y, width, height); + cairo_fill(buf->cairo); + wl_surface_damage_buffer(c->wl.surface, x, y, width, height); - //LOG_DBG("cell %dx%d dirty: c=0x%02x (%c)", - // row, col, cell->c[0], cell->c[0]); + start += cell_count; + left -= cell_count; - br = (double)((cell->attrs.background >> 24) & 0xff) / 255.0; - bg = (double)((cell->attrs.background >> 16) & 0xff) / 255.0; - bb = (double)((cell->attrs.background >> 8) & 0xff) / 255.0; - - fr = (double)((cell->attrs.foreground >> 24) & 0xff) / 255.0; - fg = (double)((cell->attrs.foreground >> 16) & 0xff) / 255.0; - fb = (double)((cell->attrs.foreground >> 8) & 0xff) / 255.0; - - if (has_cursor) - cairo_set_source_rgba(buf->cairo, fr, fg, fb, 1.0); - else - cairo_set_source_rgba(buf->cairo, br, bg, bb, 1.0); - - cairo_rectangle( - buf->cairo, damage_x, damage_y, - c->term.grid.cell_width, c->term.grid.cell_height); - cairo_fill(buf->cairo); - - cairo_glyph_t *glyphs = NULL; - int num_glyphs = 0; - - cairo_status_t status = cairo_scaled_font_text_to_glyphs( - c->font, x_ofs, y_ofs, cell->c, strlen(cell->c), - &glyphs, &num_glyphs, NULL, NULL, NULL); - - //assert(status == CAIRO_STATUS_SUCCESS); - if (status != CAIRO_STATUS_SUCCESS) { - if (glyphs != NULL) - cairo_glyph_free(glyphs); - continue; + row = start / cols; + col = start % cols; } - if (has_cursor) - cairo_set_source_rgba(buf->cairo, br, bg, bb, 1.0); - else - cairo_set_source_rgba(buf->cairo, fr, fg, fb, 1.0); - cairo_show_glyphs(buf->cairo, glyphs, num_glyphs); - cairo_glyph_free(glyphs); + assert(left == 0 || col == 0); - wl_surface_damage_buffer( - c->wl.surface, damage_x, damage_y, - c->term.grid.cell_width, c->term.grid.cell_height); + /* One or more full lines left */ + if (left >= cols) { + int line_count = left / cols; + + int x = 0; + int y = row * c->term.grid.cell_height; + int width = buf->width; + int height = line_count * c->term.grid.cell_height; + + cairo_rectangle(buf->cairo, x, y, width, height); + cairo_fill(buf->cairo); + wl_surface_damage_buffer(c->wl.surface, x, y, width, height); + + start += line_count * cols; + left -= line_count * cols; + + row += line_count; + col = 0; + } + + assert(left == 0 || col == 0); + assert(left < cols); + + /* Partial last line */ + if (left > 0) { + int x = 0; + int y = row * c->term.grid.cell_height; + int width = left * c->term.grid.cell_width; + int height = c->term.grid.cell_height; + + cairo_rectangle(buf->cairo, x, y, width, height); + cairo_fill(buf->cairo); + wl_surface_damage_buffer(c->wl.surface, x, y, width, height); + } + + break; } + + case DAMAGE_UPDATE: { + LOG_DBG("damage: UPDATE: %d -> %d", + it->item.range.start, it->item.range.start + it->item.range.length); + + const int cols = c->term.grid.cols; + + for (int linear_cursor = it->item.range.start, + row = it->item.range.start / cols, + col = it->item.range.start % cols; + linear_cursor < it->item.range.start + it->item.range.length; + linear_cursor++, + col = (col + 1) % cols, + row += col == 0 ? 1 : 0) + { + //LOG_DBG("UPDATE: %d (%dx%d)", linear_cursor, row, col); + + const struct cell *cell = &c->term.grid.cells[linear_cursor]; + bool has_cursor = c->term.grid.linear_cursor == linear_cursor; + + int x = col * c->term.grid.cell_width; + int y = row * c->term.grid.cell_height; + int width = c->term.grid.cell_width; + int height = c->term.grid.cell_height; + + + //LOG_DBG("cell %dx%d dirty: c=0x%02x (%c)", + // row, col, cell->c[0], cell->c[0]); + + br = (double)((cell->attrs.background >> 24) & 0xff) / 255.0; + bg = (double)((cell->attrs.background >> 16) & 0xff) / 255.0; + bb = (double)((cell->attrs.background >> 8) & 0xff) / 255.0; + + fr = (double)((cell->attrs.foreground >> 24) & 0xff) / 255.0; + fg = (double)((cell->attrs.foreground >> 16) & 0xff) / 255.0; + fb = (double)((cell->attrs.foreground >> 8) & 0xff) / 255.0; + + if (has_cursor) + cairo_set_source_rgba(buf->cairo, fr, fg, fb, 1.0); + else + cairo_set_source_rgba(buf->cairo, br, bg, bb, 1.0); + + /* Background */ + cairo_rectangle(buf->cairo, x, y, width, height); + cairo_fill(buf->cairo); + + cairo_glyph_t *glyphs = NULL; + int num_glyphs = 0; + + cairo_status_t status = cairo_scaled_font_text_to_glyphs( + c->font, x, y + c->fextents.ascent, + cell->c, strlen(cell->c), &glyphs, &num_glyphs, + NULL, NULL, NULL); + + if (status != CAIRO_STATUS_SUCCESS) { + if (glyphs != NULL) + cairo_glyph_free(glyphs); + continue; + } + + if (has_cursor) + cairo_set_source_rgba(buf->cairo, br, bg, bb, 1.0); + else + cairo_set_source_rgba(buf->cairo, fr, fg, fb, 1.0); + cairo_show_glyphs(buf->cairo, glyphs, num_glyphs); + cairo_glyph_free(glyphs); + + //wl_surface_damage_buffer(c->wl.surface, x, y, width, height); + } + wl_surface_damage_buffer( + c->wl.surface, + 0, (it->item.range.start / cols) * c->term.grid.cell_height, + buf->width, (it->item.range.length + cols - 1) / cols * c->term.grid.cell_height); + break; + } + + case DAMAGE_SCROLL: + assert(false); + break; + } + + tll_remove(c->term.grid.damage, it); } wl_surface_attach(c->wl.surface, buf->wl_buf, 0, 0); @@ -163,7 +254,6 @@ grid_render(struct context *c) c->frame_is_scheduled = true; wl_surface_commit(c->wl.surface); - c->term.grid.dirty = c->term.grid.all_dirty = false; } static void @@ -173,9 +263,7 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da c->frame_is_scheduled = false; wl_callback_destroy(wl_callback); - - if (c->term.grid.dirty) - grid_render(c); + grid_render(c); } static void @@ -194,7 +282,7 @@ resize(struct context *c, int width, int height) c->term.grid.cols = c->width / c->term.grid.cell_width; c->term.grid.rows = c->height / c->term.grid.cell_height; c->term.grid.cells = realloc(c->term.grid.cells, - c->term.grid.cols * c->term.grid.rows * sizeof(c->term.grid.cells[0])); + c->term.grid.cols * c->term.grid.rows * sizeof(c->term.grid.cells[0])); size_t new_cells_len = c->term.grid.cols * c->term.grid.rows; for (size_t i = old_cells_len; i < new_cells_len; i++) { @@ -218,14 +306,16 @@ resize(struct context *c, int width, int height) if (ioctl(c->term.ptmx, TIOCSWINSZ, &(struct winsize){ .ws_row = c->term.grid.rows, - .ws_col = c->term.grid.cols, - .ws_xpixel = c->width, - .ws_ypixel = c->height}) == -1) + .ws_col = c->term.grid.cols, + .ws_xpixel = c->width, + .ws_ypixel = c->height}) == -1) { LOG_ERRNO("TIOCSWINSZ"); } - c->term.grid.dirty = c->term.grid.all_dirty = true; + tll_free(c->term.grid.damage); + assert(tll_length(c->term.grid.damage) == 0); + grid_damage_update(&c->term.grid, 0, c->term.grid.rows * c->term.grid.cols); if (!c->frame_is_scheduled) grid_render(c); @@ -497,7 +587,8 @@ main(int argc, const char *const *argv) .state = 1, }, .grid = {.foreground = default_foreground, - .background = default_background}, + .background = default_background, + .damage = tll_init()}, .kbd = { .repeat = { .pipe_read_fd = repeat_pipe_fds[0], @@ -579,7 +670,6 @@ main(int argc, const char *const *argv) /* TODO: use font metrics to calculate initial size from ROWS x COLS */ const int default_width = 300; const int default_height = 300; - c.term.grid.dirty = c.term.grid.all_dirty = true; resize(&c, default_width, default_height); wl_display_dispatch_pending(c.wl.display); @@ -635,7 +725,7 @@ main(int argc, const char *const *argv) //LOG_DBG("%.*s", (int)count, data); vt_from_slave(&c.term, data, count); - if (c.term.grid.dirty && !c.frame_is_scheduled) + if (!c.frame_is_scheduled) grid_render(&c); } diff --git a/terminal.h b/terminal.h index 51c5af3d..ad40dc3e 100644 --- a/terminal.h +++ b/terminal.h @@ -9,6 +9,8 @@ #include #include +#include "tllist.h" + struct attributes { bool bold; bool italic; @@ -22,11 +24,22 @@ struct attributes { }; struct cell { - bool dirty; char c[5]; struct attributes attrs; }; +enum damage_type {DAMAGE_UPDATE, DAMAGE_ERASE, DAMAGE_SCROLL}; +struct damage { + enum damage_type type; + union { + struct { + int start; + int length; + } range; /* DAMAGE_UPDATE, DAMAGE_ERASE */ + int lines; /* DAMAGE_SCROLL */ + }; +}; + struct grid { int cols; int rows; @@ -45,8 +58,7 @@ struct grid { uint32_t foreground; uint32_t background; - bool dirty; - bool all_dirty; + tll(struct damage) damage; }; struct vt { diff --git a/vt.c b/vt.c index bdb9946f..95df06d2 100644 --- a/vt.c +++ b/vt.c @@ -200,8 +200,7 @@ action(struct terminal *term, enum action action, uint8_t c) } struct cell *cell = &term->grid.cells[term->grid.linear_cursor]; - - cell->dirty = true; + grid_damage_update(&term->grid, term->grid.linear_cursor, 1); if (term->vt.utf8.idx > 0) { //LOG_DBG("print: UTF8: %.*s", (int)term->vt.utf8.idx, term->vt.utf8.data); @@ -209,7 +208,7 @@ action(struct terminal *term, enum action action, uint8_t c) cell->c[term->vt.utf8.idx] = '\0'; memset(&term->vt.utf8, 0, sizeof(term->vt.utf8)); } else { - LOG_DBG("print: ASCII: %c", c); + //LOG_DBG("print: ASCII: %c", c); cell->c[0] = c; cell->c[1] = '\0'; } @@ -221,7 +220,6 @@ action(struct terminal *term, enum action action, uint8_t c) else term->grid.print_needs_wrap = true; - term->grid.dirty = true; break; }