diff --git a/config.c b/config.c index 707f7d93..08156d16 100644 --- a/config.c +++ b/config.c @@ -107,26 +107,28 @@ get_config_path(void) } static bool -str_to_color(const char *s, uint32_t *color, const char *path, int lineno) +str_to_ulong(const char *s, int base, unsigned long *res) { if (s == NULL) return false; errno = 0; char *end = NULL; - unsigned long res = strtoul(s, &end, 16); - if (errno != 0) { + *res = strtoul(s, &end, base); + return errno == 0 && *end == '\0'; +} + +static bool +str_to_color(const char *s, uint32_t *color, const char *path, int lineno) +{ + unsigned long value; + if (!str_to_ulong(s, 16, &value)) { LOG_ERRNO("%s:%d: invalid color: %s", path, lineno, s); return false; } - if (*end != '\0') { - LOG_ERR("%s:%d: invalid color: %s", path, lineno, s); - return false; - } - - *color = res & 0xffffff; + *color = value & 0xffffff; return true; } @@ -145,8 +147,21 @@ parse_section_main(const char *key, const char *value, struct config *conf, } else if (strcmp(key, "font") == 0) { - free(conf->font); - conf->font = strdup(value); + //free(conf->font); + //conf->font = strdup(value); + char *copy = strdup(value); + for (const char *font = strtok(copy, ","); font != NULL; font = strtok(NULL, ",")) + tll_push_back(conf->fonts, strdup(font)); + free(copy); + } + + else if (strcmp(key, "workers") == 0) { + unsigned long count; + if (!str_to_ulong(value, 10, &count)) { + LOG_ERR("%s:%d: expected an integer: %s", path, lineno, value); + return false; + } + conf->render_worker_count = count; } else { @@ -393,7 +408,7 @@ config_load(struct config *conf) *conf = (struct config) { .term = strdup("foot"), .shell = get_shell(), - .font = strdup("monospace"), + .fonts = tll_init(), .colors = { .fg = default_foreground, @@ -427,6 +442,8 @@ config_load(struct config *conf) .cursor = 0, }, }, + + .render_worker_count = sysconf(_SC_NPROCESSORS_ONLN), }; char *path = get_config_path(); @@ -449,6 +466,7 @@ config_load(struct config *conf) fclose(f); out: + tll_push_back(conf->fonts, strdup("monospace")); free(path); return ret; } @@ -458,5 +476,6 @@ config_free(struct config conf) { free(conf.term); free(conf.shell); - free(conf.font); + //free(conf.font); + tll_free_and_free(conf.fonts, free); } diff --git a/config.h b/config.h index 68d6f0a1..0686127e 100644 --- a/config.h +++ b/config.h @@ -4,11 +4,12 @@ #include #include "terminal.h" +#include "tllist.h" struct config { char *term; char *shell; - char *font; + tll(char *) fonts; struct { uint32_t fg; @@ -24,6 +25,8 @@ struct config { uint32_t cursor; } color; } cursor; + + size_t render_worker_count; }; bool config_load(struct config *conf); diff --git a/csi.c b/csi.c index 5f37663c..a82f03f3 100644 --- a/csi.c +++ b/csi.c @@ -126,7 +126,7 @@ csi_sgr(struct terminal *term) case 35: case 36: case 37: - term->vt.attrs.foreground = 1 << 31 | term->colors.regular[param - 30]; + term->vt.attrs.foreground = 1 << 30 | term->colors.regular[param - 30]; break; case 38: { @@ -141,7 +141,7 @@ csi_sgr(struct terminal *term) color = term->colors.bright[idx - 8]; else color = colors256[idx]; - term->vt.attrs.foreground = 1 << 31 | color; + term->vt.attrs.foreground = 1 << 30 | color; i += 2; } @@ -152,7 +152,7 @@ csi_sgr(struct terminal *term) uint8_t r = term->vt.params.v[i + 2].value; uint8_t g = term->vt.params.v[i + 3].value; uint8_t b = term->vt.params.v[i + 4].value; - term->vt.attrs.foreground = 1 << 31 | r << 16 | g << 8 | b; + term->vt.attrs.foreground = 1 << 30 | r << 16 | g << 8 | b; i += 4; } @@ -168,7 +168,7 @@ csi_sgr(struct terminal *term) /* 6 - CS tolerance */ /* 7 - color space associated with tolerance */ - term->vt.attrs.foreground = 1 << 31 | r << 16 | g << 8 | b; + term->vt.attrs.foreground = 1 << 30 | r << 16 | g << 8 | b; } else { LOG_ERR("invalid CSI SGR sequence"); abort(); @@ -195,7 +195,7 @@ csi_sgr(struct terminal *term) case 45: case 46: case 47: - term->vt.attrs.background = 1 << 31 | term->colors.regular[param - 40]; + term->vt.attrs.background = 1 << 30 | term->colors.regular[param - 40]; break; case 48: { @@ -211,7 +211,7 @@ csi_sgr(struct terminal *term) color = term->colors.bright[idx - 8]; else color = colors256[idx]; - term->vt.attrs.background = 1 << 31 | color; + term->vt.attrs.background = 1 << 30 | color; i += 2; } @@ -221,7 +221,7 @@ csi_sgr(struct terminal *term) uint8_t r = term->vt.params.v[i + 2].value; uint8_t g = term->vt.params.v[i + 3].value; uint8_t b = term->vt.params.v[i + 4].value; - term->vt.attrs.background = 1 << 31 | r << 16 | g << 8 | b; + term->vt.attrs.background = 1 << 30 | r << 16 | g << 8 | b; i += 4; } @@ -238,7 +238,7 @@ csi_sgr(struct terminal *term) /* 6 - CS tolerance */ /* 7 - color space associated with tolerance */ - term->vt.attrs.background = 1 << 31 | r << 16 | g << 8 | b; + term->vt.attrs.background = 1 << 30 | r << 16 | g << 8 | b; } else { LOG_ERR("invalid CSI SGR sequence"); abort(); @@ -265,7 +265,7 @@ csi_sgr(struct terminal *term) case 95: case 96: case 97: - term->vt.attrs.foreground = 1 << 31 | term->colors.bright[param - 90]; + term->vt.attrs.foreground = 1 << 30 | term->colors.bright[param - 90]; break; /* Regular background colors */ @@ -277,7 +277,7 @@ csi_sgr(struct terminal *term) case 105: case 106: case 107: - term->vt.attrs.background = 1 << 31 | term->colors.bright[param - 100]; + term->vt.attrs.background = 1 << 30 | term->colors.bright[param - 100]; break; default: @@ -487,6 +487,9 @@ csi_dispatch(struct terminal *term, uint8_t final) memmove(&term->grid->cur_row->cells[term->cursor.col], &term->grid->cur_row->cells[term->cursor.col + count], remaining * sizeof(term->grid->cur_row->cells[0])); + + for (size_t c = 0; c < remaining; c++) + term->grid->cur_row->cells[term->cursor.col + c].attrs.clean = 0; term->grid->cur_row->dirty = true; /* Erase the remainder of the line */ @@ -511,6 +514,8 @@ csi_dispatch(struct terminal *term, uint8_t final) memmove(&term->grid->cur_row->cells[term->cursor.col + count], &term->grid->cur_row->cells[term->cursor.col], remaining * sizeof(term->grid->cur_row->cells[0])); + for (size_t c = 0; c < remaining; c++) + term->grid->cur_row->cells[term->cursor.col + count + c].attrs.clean = 0; term->grid->cur_row->dirty = true; /* Erase (insert space characters) */ diff --git a/font.c b/font.c index c6a118c9..6842a439 100644 --- a/font.c +++ b/font.c @@ -1,33 +1,42 @@ #include "font.h" #include +#include #include #include #include +#include #include #define LOG_MODULE "font" +#define LOG_ENABLE_DBG 0 #include "log.h" #define min(x, y) ((x) < (y) ? (x) : (y)) static FT_Library ft_lib; +static mtx_t ft_lock; + +static const size_t cache_size = 512; static void __attribute__((constructor)) init(void) { FcInit(); FT_Init_FreeType(&ft_lib); + mtx_init(&ft_lock, mtx_plain); } static void __attribute__((destructor)) fini(void) { - FcFini(); + mtx_destroy(&ft_lock); FT_Done_FreeType(ft_lib); + FcFini(); } +#if 0 static void font_populate_glyph_cache(struct font *font) { @@ -35,12 +44,25 @@ font_populate_glyph_cache(struct font *font) for (size_t i = 0; i < 256; i++) font_glyph_for_utf8(font, &(char){i}, &font->cache[i]); } +#endif -bool -font_from_name(const char *name, struct font *font) +static bool +from_name(const char *base_name, const font_list_t *fallbacks, const char *attributes, struct font *font, bool is_fallback) { memset(font, 0, sizeof(*font)); + size_t attr_len = attributes == NULL ? 0 : strlen(attributes); + bool have_attrs = attr_len > 0; + + char name[strlen(base_name) + (have_attrs ? 1 : 0) + attr_len + 1]; + strcpy(name, base_name); + if (have_attrs){ + strcat(name, ":"); + strcat(name, attributes); + } + + LOG_DBG("instantiating %s", name); + FcPattern *pattern = FcNameParse((const unsigned char *)name); if (pattern == NULL) { LOG_ERR("%s: failed to lookup font", name); @@ -86,8 +108,10 @@ font_from_name(const char *name, struct font *font) LOG_DBG("loading: %s", face_file); + mtx_lock(&ft_lock); FT_Face ft_face; FT_Error ft_err = FT_New_Face(ft_lib, (const char *)face_file, 0, &ft_face); + mtx_unlock(&ft_lock); if (ft_err != 0) LOG_ERR("%s: failed to create FreeType face", face_file); @@ -122,11 +146,13 @@ font_from_name(const char *name, struct font *font) else if (fc_hinting && fc_hintstyle == FC_HINT_SLIGHT) load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LIGHT; else if (fc_rgba == FC_RGBA_RGB) { - LOG_WARN("unimplemented: subpixel antialiasing"); + if (!is_fallback) + LOG_WARN("unimplemented: subpixel antialiasing"); // load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD; load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_NORMAL; } else if (fc_rgba == FC_RGBA_VRGB) { - LOG_WARN("unimplemented: subpixel antialiasing"); + if (!is_fallback) + LOG_WARN("unimplemented: subpixel antialiasing"); //load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD_V; load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_NORMAL; } else @@ -144,15 +170,17 @@ font_from_name(const char *name, struct font *font) if (!fc_antialias) render_flags |= FT_RENDER_MODE_MONO; else { - if (false) - ; -#if 0 - if (fc_rgba == FC_RGBA_RGB) - render_flags |= FT_RENDER_MODE_LCD; - else if (fc_rgba == FC_RGBA_VRGB) - render_flags |= FT_RENDER_MODE_LCD_V; -#endif - else + if (fc_rgba == FC_RGBA_RGB) { + if (!is_fallback) + LOG_WARN("unimplemented: subpixel antialiasing"); + //render_flags |= FT_RENDER_MODE_LCD; + render_flags |= FT_RENDER_MODE_NORMAL; + } else if (fc_rgba == FC_RGBA_VRGB) { + if (!is_fallback) + LOG_WARN("unimplemented: subpixel antialiasing"); + //render_flags |= FT_RENDER_MODE_LCD_V; + render_flags |= FT_RENDER_MODE_NORMAL; + } else render_flags |= FT_RENDER_MODE_NORMAL; } @@ -173,25 +201,64 @@ font_from_name(const char *name, struct font *font) font->face = ft_face; font->load_flags = load_flags; font->render_flags = render_flags; - font_populate_glyph_cache(font); + font->is_fallback = is_fallback; + + if (fallbacks != NULL) { + tll_foreach(*fallbacks, it) { + size_t len = strlen(it->item) + (have_attrs ? 1 : 0) + attr_len + 1; + char *fallback = malloc(len); + + strcpy(fallback, it->item); + if (have_attrs) { + strcat(fallback, ":"); + strcat(fallback, attributes); + } + + LOG_DBG("%s: adding fallback: %s", name, fallback); + tll_push_back(font->fallbacks, fallback); + } + } + + if (is_fallback) + return true; + + //font_populate_glyph_cache(font); + font->cache = calloc(cache_size, sizeof(font->cache[0])); return true; } bool -font_glyph_for_utf8(struct font *font, const char *utf8, - struct glyph *glyph) +font_from_name(font_list_t names, const char *attributes, struct font *font) { - mbstate_t ps = {0}; - wchar_t wc; - if (mbrtowc(&wc, utf8, 4, &ps) < 0) { - LOG_ERR("FAILED: %.4s", utf8); + if (tll_length(names) == 0) return false; + + font_list_t fallbacks = tll_init(); + bool skip_first = true; + tll_foreach(names, it) { + if (skip_first) { + skip_first = false; + continue; + } + + tll_push_back(fallbacks, it->item); } - wprintf(L"CONVERTED: %.1s\n", &wc); + bool ret = from_name(tll_front(names), &fallbacks, attributes, font, false); - mtx_lock(&font->lock); + tll_free(fallbacks); + return ret; +} +static size_t +hash_index(wchar_t wc) +{ + return wc % cache_size; +} + +static bool +glyph_for_wchar(struct font *font, wchar_t wc, struct glyph *glyph) +{ /* * LCD filter is per library instance. Thus we need to re-set it * every time... @@ -204,6 +271,28 @@ font_glyph_for_utf8(struct font *font, const char *utf8, goto err; FT_UInt idx = FT_Get_Char_Index(font->face, wc); + if (idx == 0) { + /* LOG_DBG("no glyph found for %02x %02x %02x %02x", */ + /* (unsigned char)utf8[0], (unsigned char)utf8[1], */ + /* (unsigned char)utf8[2], (unsigned char)utf8[3]); */ + + /* Try fallback fonts */ + tll_foreach(font->fallbacks, it) { + struct font fallback; + if (from_name(it->item, NULL, "", &fallback, true)) { + if (glyph_for_wchar(&fallback, wc, glyph)) { + font_destroy(&fallback); + return true; + } + + font_destroy(&fallback); + } + } + + if (font->is_fallback) + return false; + } + err = FT_Load_Glyph(font->face, idx, font->load_flags); if (err != 0) { LOG_ERR("load failed"); @@ -267,36 +356,102 @@ font_glyph_for_utf8(struct font *font, const char *utf8, } *glyph = (struct glyph){ + .wc = wc, .data = data, .surf = surf, .left = font->face->glyph->bitmap_left, .top = font->face->glyph->bitmap_top, - - .format = cr_format, - .width = bitmap->width, - .height = bitmap->rows, - .stride = stride, }; - mtx_unlock(&font->lock); + return true; err: - mtx_unlock(&font->lock); return false; } +const struct glyph * +font_glyph_for_utf8(struct font *font, const char *utf8) +{ + mtx_lock(&font->lock); + + mbstate_t ps = {0}; + wchar_t wc; + if (mbrtowc(&wc, utf8, 4, &ps) < 0) { + LOG_DBG("failed to convert utf-8 sequence %02x %02x %02x %02x to unicode", + (unsigned char)utf8[0], (unsigned char)utf8[1], + (unsigned char)utf8[2], (unsigned char)utf8[3]); + mtx_unlock(&font->lock); + return NULL; + } + + assert(font->cache != NULL); + size_t hash_idx = hash_index(wc); + hash_entry_t *hash_entry = font->cache[hash_idx]; + + if (hash_entry != NULL) { + tll_foreach(*hash_entry, it) { + if (it->item.wc == wc) { + mtx_unlock(&font->lock); + return &it->item; + } + } + } + + struct glyph glyph; + if (!glyph_for_wchar(font, wc, &glyph)) { + mtx_unlock(&font->lock); + return NULL; + } + + if (hash_entry == NULL) { + hash_entry = calloc(1, sizeof(*hash_entry)); + + assert(font->cache[hash_idx] == NULL); + font->cache[hash_idx] = hash_entry; + } + + assert(hash_entry != NULL); + tll_push_back(*hash_entry, glyph); + + mtx_unlock(&font->lock); + return &tll_back(*hash_entry); +} + void font_destroy(struct font *font) { - if (font->face != NULL) - FT_Done_Face(font->face); + tll_free_and_free(font->fallbacks, free); + if (font->face != NULL) { + mtx_lock(&ft_lock); + FT_Done_Face(font->face); + mtx_unlock(&ft_lock); + } + + if (font->cache != NULL) { + for (size_t i = 0; i < cache_size; i++) { + if (font->cache[i] == NULL) + continue; + + tll_foreach(*font->cache[i], it) { + cairo_surface_destroy(it->item.surf); + free(it->item.data); + } + + tll_free(*font->cache[i]); + free(font->cache[i]); + } + free(font->cache); + } + +#if 0 for (size_t i = 0; i < 256; i++) { if (font->cache[i].surf != NULL) cairo_surface_destroy(font->cache[i].surf); if (font->cache[i].data != NULL) free(font->cache[i].data); } - +#endif + mtx_destroy(&font->lock); } diff --git a/font.h b/font.h index 192640a9..0f2c9833 100644 --- a/font.h +++ b/font.h @@ -3,9 +3,56 @@ #include #include -#include "terminal.h" +#include +#include FT_FREETYPE_H +#include FT_LCD_FILTER_H +#include -bool font_from_name(const char *name, struct font *result); -bool font_glyph_for_utf8( - struct font *font, const char *utf8, struct glyph *glyph); +#include "tllist.h" +//#include "terminal.h" + +typedef tll(const char *) font_list_t; + +struct glyph { + wchar_t wc; + + void *data; + cairo_surface_t *surf; + int left; + int top; + +#if 0 + int format; + int width; + int height; + int stride; +#endif +}; + +typedef tll(struct glyph) hash_entry_t; + +struct font { + FT_Face face; + int load_flags; + int render_flags; + FT_LcdFilter lcd_filter; + struct { + double position; + double thickness; + } underline; + struct { + double position; + double thickness; + } strikeout; + + bool is_fallback; + tll(char *) fallbacks; + + //struct glyph cache[256]; + hash_entry_t **cache; + mtx_t lock; +}; + +bool font_from_name(font_list_t names, const char *attributes, struct font *result); +const struct glyph *font_glyph_for_utf8(struct font *font, const char *utf8); void font_destroy(struct font *font); diff --git a/grid.c b/grid.c index 47e09065..2ff82bd7 100644 --- a/grid.c +++ b/grid.c @@ -29,6 +29,8 @@ grid_row_alloc(int cols) { struct row *row = malloc(sizeof(*row)); row->cells = calloc(cols, sizeof(row->cells[0])); + for (size_t c = 0; c < cols; c++) + row->cells[c].attrs.clean = 1; row->dirty = false; /* TODO: parameter? */ return row; } diff --git a/main.c b/main.c index 7d0fa355..3228e8f8 100644 --- a/main.c +++ b/main.c @@ -11,9 +11,9 @@ #include #include +#include #include -#include #include #include #include @@ -291,8 +291,8 @@ main(int argc, char *const *argv) break; case 'f': - free(conf.font); - conf.font = strdup(optarg); + tll_free_and_free(conf.fonts, free); + tll_push_back(conf.fonts, strdup(optarg)); break; case 'h': @@ -388,8 +388,18 @@ main(int argc, char *const *argv) .normal = {.damage = tll_init(), .scroll_damage = tll_init()}, .alt = {.damage = tll_init(), .scroll_damage = tll_init()}, .grid = &term.normal, + .render = { + .workers = { + .count = conf.render_worker_count, + .queue = tll_init(), + }, + }, }; + LOG_INFO("using %zu rendering threads", term.render.workers.count); + + struct render_worker_context worker_context[term.render.workers.count]; + /* Initialize 'current' colors from the default colors */ term.colors.fg = term.colors.default_fg; term.colors.bg = term.colors.default_bg; @@ -409,21 +419,33 @@ main(int argc, char *const *argv) thrd_t keyboard_repeater_id; thrd_create(&keyboard_repeater_id, &keyboard_repeater, &term); - if (!font_from_name(conf.font, &term.fonts[0])) - goto out; + sem_init(&term.render.workers.start, 0, 0); + sem_init(&term.render.workers.done, 0, 0); + mtx_init(&term.render.workers.lock, mtx_plain); + cnd_init(&term.render.workers.cond); - { - char fname[1024]; - snprintf(fname, sizeof(fname), "%s:style=bold", conf.font); - font_from_name(fname, &term.fonts[1]); - - snprintf(fname, sizeof(fname), "%s:style=italic", conf.font); - font_from_name(fname, &term.fonts[2]); - - snprintf(fname, sizeof(fname), "%s:style=bold italic", conf.font); - font_from_name(fname, &term.fonts[3]); + term.render.workers.threads = calloc(term.render.workers.count, sizeof(term.render.workers.threads[0])); + for (size_t i = 0; i < term.render.workers.count; i++) { + worker_context[i].term = &term; + worker_context[i].my_id = 1 + i; + thrd_create(&term.render.workers.threads[i], &render_worker_thread, &worker_context[i]); } + font_list_t font_names = tll_init(); + tll_foreach(conf.fonts, it) + tll_push_back(font_names, it->item); + + if (!font_from_name(font_names, "", &term.fonts[0])) { + tll_free(font_names); + goto out; + } + + font_from_name(font_names, "style=bold", &term.fonts[1]); + font_from_name(font_names, "style=italic", &term.fonts[2]); + font_from_name(font_names, "style=bold italic", &term.fonts[3]); + + tll_free(font_names); + /* Underline position and size */ for (size_t i = 0; i < sizeof(term.fonts) / sizeof(term.fonts[0]); i++) { struct font *f = &term.fonts[i]; @@ -716,7 +738,7 @@ main(int argc, char *const *argv) } if (fds[1].revents & POLLIN) { - uint8_t data[8192]; + uint8_t data[24 * 1024]; ssize_t count = read(term.ptmx, data, sizeof(data)); if (count < 0) { if (errno != EAGAIN) @@ -809,9 +831,11 @@ main(int argc, char *const *argv) for (int r = 0; r < term.rows; r++) { struct row *row = grid_row_in_view(term.grid, r); for (int col = 0; col < term.cols; col++) { - if (row->cells[col].attrs.blink) { + struct cell *cell = &row->cells[col]; + + if (cell->attrs.blink) { + cell->attrs.clean = 0; row->dirty = true; - break; } } } @@ -827,6 +851,15 @@ out: cnd_signal(&term.kbd.repeat.cond); mtx_unlock(&term.kbd.repeat.mutex); + mtx_lock(&term.render.workers.lock); + assert(tll_length(term.render.workers.queue) == 0); + for (size_t i = 0; i < term.render.workers.count; i++) { + sem_post(&term.render.workers.start); + tll_push_back(term.render.workers.queue, -2); + } + cnd_broadcast(&term.render.workers.cond); + mtx_unlock(&term.render.workers.lock); + shm_fini(); if (term.render.frame_callback != NULL) wl_callback_destroy(term.render.frame_callback); @@ -910,6 +943,17 @@ out: thrd_join(keyboard_repeater_id, NULL); cnd_destroy(&term.kbd.repeat.cond); mtx_destroy(&term.kbd.repeat.mutex); + + for (size_t i = 0; i < term.render.workers.count; i++) + thrd_join(term.render.workers.threads[i], NULL); + free(term.render.workers.threads); + cnd_destroy(&term.render.workers.cond); + mtx_destroy(&term.render.workers.lock); + sem_destroy(&term.render.workers.start); + sem_destroy(&term.render.workers.done); + assert(tll_length(term.render.workers.queue) == 0); + tll_free(term.render.workers.queue); + close(term.kbd.repeat.pipe_read_fd); close(term.kbd.repeat.pipe_write_fd); diff --git a/render.c b/render.c index cf6c013e..b2d6c16e 100644 --- a/render.c +++ b/render.c @@ -76,45 +76,52 @@ gseq_flush(struct terminal *term, struct buffer *buf) } static void -draw_underline(const struct terminal *term, struct buffer *buf, +draw_underline(const struct terminal *term, struct buffer *buf, size_t buf_idx, const struct font *font, struct rgb color, double x, double y) { //const struct font *font = attrs_to_font(term, &cell->attrs); double baseline = y + term->fextents.height - term->fextents.descent; double width = font->underline.thickness; double y_under = baseline - font->underline.position - width / 2.; + cairo_t *cr = buf->cairo[buf_idx]; - cairo_set_source_rgb(buf->cairo, color.r, color.g, color.b); - cairo_set_line_width(buf->cairo, width); - cairo_move_to(buf->cairo, x, round(y_under) + 0.5); - cairo_rel_line_to(buf->cairo, term->cell_width, 0); - cairo_stroke(buf->cairo); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgb(cr, color.r, color.g, color.b); + cairo_set_line_width(cr, width); + cairo_move_to(cr, x, round(y_under) + 0.5); + cairo_rel_line_to(cr, term->cell_width, 0); + cairo_stroke(cr); } static void -draw_bar(const struct terminal *term, struct buffer *buf, struct rgb color, - double x, double y) +draw_bar(const struct terminal *term, struct buffer *buf, size_t buf_idx, + struct rgb color, double x, double y) { - cairo_set_source_rgb(buf->cairo, color.r, color.g, color.b); - cairo_set_line_width(buf->cairo, 1.0); - cairo_move_to(buf->cairo, x + 0.5, y); - cairo_rel_line_to(buf->cairo, 0, term->cell_height); - cairo_stroke(buf->cairo); + cairo_t *cr = buf->cairo[buf_idx]; + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgb(cr, color.r, color.g, color.b); + cairo_set_line_width(cr, 1.0); + cairo_move_to(cr, x + 0.5, y); + cairo_rel_line_to(cr, 0, term->cell_height); + cairo_stroke(cr); } static void -draw_strikeout(const struct terminal *term, struct buffer *buf, +draw_strikeout(const struct terminal *term, struct buffer *buf, size_t buf_idx, const struct font *font, struct rgb color, double x, double y) { double baseline = y + term->fextents.height - term->fextents.descent; double width = font->strikeout.thickness; double y_strike = baseline - font->strikeout.position - width / 2.; + cairo_t *cr = buf->cairo[buf_idx]; - cairo_set_source_rgb(buf->cairo, color.r, color.g, color.b); - cairo_set_line_width(buf->cairo, width); - cairo_move_to(buf->cairo, x, round(y_strike) + 0.5); - cairo_rel_line_to(buf->cairo, term->cell_width, 0); - cairo_stroke(buf->cairo); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgb(cr, color.r, color.g, color.b); + cairo_set_line_width(cr, width); + cairo_move_to(cr, x, round(y_strike) + 0.5); + cairo_rel_line_to(cr, term->cell_width, 0); + cairo_stroke(cr); } static bool @@ -166,9 +173,15 @@ arm_blink_timer(struct terminal *term) } static void -render_cell(struct terminal *term, struct buffer *buf, const struct cell *cell, - int col, int row, bool has_cursor) +render_cell(struct terminal *term, struct buffer *buf, size_t buf_idx, + struct cell *cell, int col, int row, bool has_cursor) { + if (cell->attrs.clean) + return; + + cell->attrs.clean = 1; + + cairo_t *cr = buf->cairo[buf_idx]; double width = term->cell_width; double height = term->cell_height; double x = col * width; @@ -177,10 +190,10 @@ render_cell(struct terminal *term, struct buffer *buf, const struct cell *cell, bool block_cursor = has_cursor && term->cursor_style == CURSOR_BLOCK; bool is_selected = coord_is_selected(term, col, row); - uint32_t _fg = cell->attrs.foreground >> 31 + uint32_t _fg = cell->attrs.foreground >> 30 ? cell->attrs.foreground : !term->reverse ? term->colors.fg : term->colors.bg; - uint32_t _bg = cell->attrs.background >> 31 + uint32_t _bg = cell->attrs.background >> 30 ? cell->attrs.background : !term->reverse ? term->colors.bg : term->colors.fg; @@ -208,18 +221,19 @@ render_cell(struct terminal *term, struct buffer *buf, const struct cell *cell, } /* Background */ - cairo_set_source_rgb(buf->cairo, bg.r, bg.g, bg.b); - cairo_rectangle(buf->cairo, x, y, width, height); - cairo_fill(buf->cairo); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgb(cr, bg.r, bg.g, bg.b); + cairo_rectangle(cr, x, y, width, height); + cairo_fill(cr); /* Non-block cursors */ if (has_cursor) { struct rgb cursor_color = color_hex_to_rgb(term->cursor_color.cursor); if (term->cursor_style == CURSOR_BAR) - draw_bar(term, buf, cursor_color, x, y); + draw_bar(term, buf, buf_idx, cursor_color, x, y); else if (term->cursor_style == CURSOR_UNDERLINE) draw_underline( - term, buf, attrs_to_font(term, &cell->attrs), cursor_color, x, y); + term, buf, buf_idx, attrs_to_font(term, &cell->attrs), cursor_color, x, y); } if (cell->attrs.blink && !term->blink.active) { @@ -232,10 +246,10 @@ render_cell(struct terminal *term, struct buffer *buf, const struct cell *cell, /* Underline */ if (cell->attrs.underline) - draw_underline(term, buf, attrs_to_font(term, &cell->attrs), fg, x, y); + draw_underline(term, buf, buf_idx, attrs_to_font(term, &cell->attrs), fg, x, y); if (cell->attrs.strikethrough) - draw_strikeout(term, buf, attrs_to_font(term, &cell->attrs), fg, x, y); + draw_strikeout(term, buf, buf_idx, attrs_to_font(term, &cell->attrs), fg, x, y); /* * cairo_show_glyphs() apparently works *much* faster when @@ -259,28 +273,12 @@ render_cell(struct terminal *term, struct buffer *buf, const struct cell *cell, } struct font *font = attrs_to_font(term, &cell->attrs); - - struct glyph *glyph = NULL; - if (strnlen(cell->c, 4) == 1) { - if (font->cache[(unsigned char)cell->c[0]].surf != NULL) - glyph = &font->cache[(unsigned char)cell->c[0]]; - } - - struct glyph _glyph; - if (glyph == NULL) { - if (!font_glyph_for_utf8(font, cell->c, &_glyph)) - return; - glyph = &_glyph; - } - - assert(glyph != NULL); - cairo_set_source_rgb(buf->cairo, fg.r, fg.g, fg.b); - cairo_set_operator(buf->cairo, CAIRO_OPERATOR_OVER); - cairo_mask_surface(buf->cairo, glyph->surf, x + glyph->left, y + term->fextents.ascent - glyph->top); - - if (glyph == &_glyph) { - cairo_surface_destroy(_glyph.surf); - free(_glyph.data); + const struct glyph *glyph = font_glyph_for_utf8(font, cell->c); + if (glyph != NULL) { + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgb(cr, fg.r, fg.g, fg.b); + cairo_mask_surface( + cr, glyph->surf, x + glyph->left, y + term->fextents.ascent - glyph->top); } } @@ -303,11 +301,11 @@ grid_render_scroll(struct terminal *term, struct buffer *buf, buf->size); if (height > 0) { - cairo_surface_flush(buf->cairo_surface); - uint8_t *raw = cairo_image_surface_get_data(buf->cairo_surface); + cairo_surface_flush(buf->cairo_surface[0]); + uint8_t *raw = cairo_image_surface_get_data(buf->cairo_surface[0]); memmove(raw + dst_y * stride, raw + src_y * stride, height * stride); - cairo_surface_mark_dirty(buf->cairo_surface); + cairo_surface_mark_dirty(buf->cairo_surface[0]); wl_surface_damage_buffer(term->wl.surface, 0, dst_y, width, height); } @@ -332,26 +330,74 @@ grid_render_scroll_reverse(struct terminal *term, struct buffer *buf, buf->size); if (height > 0) { - cairo_surface_flush(buf->cairo_surface); - uint8_t *raw = cairo_image_surface_get_data(buf->cairo_surface); + cairo_surface_flush(buf->cairo_surface[0]); + uint8_t *raw = cairo_image_surface_get_data(buf->cairo_surface[0]); memmove(raw + dst_y * stride, raw + src_y * stride, height * stride); - cairo_surface_mark_dirty(buf->cairo_surface); + cairo_surface_mark_dirty(buf->cairo_surface[0]); wl_surface_damage_buffer(term->wl.surface, 0, dst_y, width, height); } } static void -render_row(struct terminal *term, struct buffer *buf, struct row *row, int row_no) +render_row(struct terminal *term, struct buffer *buf, size_t buf_idx, struct row *row, int row_no) { for (int col = 0; col < term->cols; col++) - render_cell(term, buf, &row->cells[col], col, row_no, false); + render_cell(term, buf, buf_idx, &row->cells[col], col, row_no, false); +#if 0 wl_surface_damage_buffer( term->wl.surface, 0, row_no * term->cell_height, term->width, term->cell_height); +#endif +} + +int +render_worker_thread(void *_ctx) +{ + struct render_worker_context *ctx = _ctx; + struct terminal *term = ctx->term; + const int my_id = ctx->my_id; + + sem_t *start = &term->render.workers.start; + sem_t *done = &term->render.workers.done; + mtx_t *lock = &term->render.workers.lock; + cnd_t *cond = &term->render.workers.cond; + + while (true) { + sem_wait(start); + + struct buffer *buf = term->render.workers.buf; + bool frame_done = false; + + while (!frame_done) { + mtx_lock(lock); + while (tll_length(term->render.workers.queue) == 0) + cnd_wait(cond, lock); + + int row_no = tll_pop_front(term->render.workers.queue); + mtx_unlock(lock); + + switch (row_no) { + default: + assert(buf != NULL); + render_row(term, buf, my_id, grid_row_in_view(term->grid, row_no), row_no); + break; + + case -1: + frame_done = true; + sem_post(done); + break; + + case -2: + return 0; + } + } + }; + + return -1; } static void frame_callback( @@ -374,8 +420,8 @@ grid_render(struct terminal *term) assert(term->width > 0); assert(term->height > 0); - struct buffer *buf = shm_get_buffer(term->wl.shm, term->width, term->height); - cairo_set_operator(buf->cairo, CAIRO_OPERATOR_SOURCE); + struct buffer *buf = shm_get_buffer(term->wl.shm, term->width, term->height, 1 + term->render.workers.count); + cairo_set_operator(buf->cairo[0], CAIRO_OPERATOR_SOURCE); gseq.g = gseq.glyphs; gseq.count = 0; @@ -384,9 +430,12 @@ grid_render(struct terminal *term) /* Erase old cursor (if we rendered a cursor last time) */ if (term->render.last_cursor.cell != NULL) { + struct cell *hack = (struct cell *)term->render.last_cursor.cell; + hack->attrs.clean = 0; render_cell( - term, buf, - term->render.last_cursor.cell, + term, buf, 0, + //term->render.last_cursor.cell, + hack, term->render.last_cursor.in_view.col, term->render.last_cursor.in_view.row, false); @@ -424,11 +473,11 @@ grid_render(struct terminal *term) uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg; struct rgb bg = color_hex_to_rgb(_bg); - cairo_set_source_rgb(buf->cairo, bg.r, bg.g, bg.b); + cairo_set_source_rgb(buf->cairo[0], bg.r, bg.g, bg.b); - cairo_rectangle(buf->cairo, rmargin, 0, rmargin_width, term->height); - cairo_rectangle(buf->cairo, 0, bmargin, term->width, bmargin_height); - cairo_fill(buf->cairo); + cairo_rectangle(buf->cairo[0], rmargin, 0, rmargin_width, term->height); + cairo_rectangle(buf->cairo[0], 0, bmargin, term->width, bmargin_height); + cairo_fill(buf->cairo[0]); wl_surface_damage_buffer( term->wl.surface, rmargin, 0, rmargin_width, term->height); @@ -456,20 +505,38 @@ grid_render(struct terminal *term) tll_remove(term->grid->scroll_damage, it); } + term->render.workers.buf = buf; + for (size_t i = 0; i < term->render.workers.count; i++) + sem_post(&term->render.workers.start); + + assert(tll_length(term->render.workers.queue) == 0); + for (int r = 0; r < term->rows; r++) { struct row *row = grid_row_in_view(term->grid, r); if (!row->dirty) continue; - //LOG_WARN("rendering line: %d", r); + mtx_lock(&term->render.workers.lock); + tll_push_back(term->render.workers.queue, r); + cnd_signal(&term->render.workers.cond); + mtx_unlock(&term->render.workers.lock); row->dirty = false; all_clean = false; - render_row(term, buf, row, r); + wl_surface_damage_buffer( + term->wl.surface, + 0, r * term->cell_height, + term->width, term->cell_height); } + mtx_lock(&term->render.workers.lock); + for (size_t i = 0; i < term->render.workers.count; i++) + tll_push_back(term->render.workers.queue, -1); + cnd_broadcast(&term->render.workers.cond); + mtx_unlock(&term->render.workers.lock); + if (term->blink.active) { /* Check if there are still any visible blinking cells */ bool none_is_blinking = true; @@ -516,6 +583,10 @@ grid_render(struct terminal *term) cursor_is_visible = true; } + 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. */ @@ -528,11 +599,12 @@ grid_render(struct terminal *term) term->cursor.col, view_aligned_row}; struct row *row = grid_row_in_view(term->grid, view_aligned_row); + struct cell *cell = &row->cells[term->cursor.col]; - term->render.last_cursor.cell = &row->cells[term->cursor.col]; + cell->attrs.clean = 0; + term->render.last_cursor.cell = cell; render_cell( - term, buf, term->render.last_cursor.cell, - term->cursor.col, view_aligned_row, true); + term, buf, 0, cell, term->cursor.col, view_aligned_row, true); wl_surface_damage_buffer( term->wl.surface, @@ -549,10 +621,10 @@ grid_render(struct terminal *term) } if (term->flash.active) { - cairo_set_source_rgba(buf->cairo, 1.0, 1.0, 0.0, 0.5); - cairo_set_operator(buf->cairo, CAIRO_OPERATOR_OVER); - cairo_rectangle(buf->cairo, 0, 0, term->width, term->height); - cairo_fill(buf->cairo); + cairo_set_source_rgba(buf->cairo[0], 1.0, 1.0, 0.0, 0.5); + cairo_set_operator(buf->cairo[0], CAIRO_OPERATOR_OVER); + cairo_rectangle(buf->cairo[0], 0, 0, term->width, term->height); + cairo_fill(buf->cairo[0]); wl_surface_damage_buffer( term->wl.surface, 0, 0, term->width, term->height); @@ -561,7 +633,7 @@ grid_render(struct terminal *term) assert(term->grid->offset >= 0 && term->grid->offset < term->grid->num_rows); assert(term->grid->view >= 0 && term->grid->view < term->grid->num_rows); - cairo_surface_flush(buf->cairo_surface); + cairo_surface_flush(buf->cairo_surface[0]); wl_surface_attach(term->wl.surface, buf->wl_buf, 0, 0); assert(term->render.frame_callback == NULL); diff --git a/render.h b/render.h index fd923e7d..0b7a40ca 100644 --- a/render.h +++ b/render.h @@ -10,3 +10,9 @@ void render_resize(struct terminal *term, int width, int height); void render_set_title(struct terminal *term, const char *title); void render_update_cursor_surface(struct terminal *term); void render_refresh(struct terminal *term); + +struct render_worker_context { + int my_id; + struct terminal *term; +}; +int render_worker_thread(void *_ctx); diff --git a/shm.c b/shm.c index dd85ef3f..ca078544 100644 --- a/shm.c +++ b/shm.c @@ -27,8 +27,10 @@ static const struct wl_buffer_listener buffer_listener = { }; struct buffer * -shm_get_buffer(struct wl_shm *shm, int width, int height) +shm_get_buffer(struct wl_shm *shm, int width, int height, size_t copies) { + assert(copies >= 1); + tll_foreach(buffers, it) { if (it->item.width != width || it->item.height != height) continue; @@ -57,8 +59,8 @@ shm_get_buffer(struct wl_shm *shm, int width, int height) struct wl_shm_pool *pool = NULL; struct wl_buffer *buf = NULL; - cairo_surface_t *cairo_surface = NULL; - cairo_t *cairo = NULL; + cairo_surface_t **cairo_surface = NULL; + cairo_t **cairo = NULL; /* Backing memory for SHM */ pool_fd = memfd_create("f00sel-wayland-shm-buffer-pool", MFD_CLOEXEC); @@ -99,19 +101,25 @@ shm_get_buffer(struct wl_shm *shm, int width, int height) close(pool_fd); pool_fd = -1; /* Create a cairo surface around the mmapped memory */ - cairo_surface = cairo_image_surface_create_for_data( - mmapped, CAIRO_FORMAT_ARGB32, width, height, stride); - if (cairo_surface_status(cairo_surface) != CAIRO_STATUS_SUCCESS) { - LOG_ERR("failed to create cairo surface: %s", - cairo_status_to_string(cairo_surface_status(cairo_surface))); - goto err; - } + cairo_surface = calloc(copies, sizeof(cairo_surface[0])); + cairo = calloc(copies, sizeof(cairo[0])); - cairo = cairo_create(cairo_surface); - if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS) { - LOG_ERR("failed to create cairo context: %s", - cairo_status_to_string(cairo_status(cairo))); - goto err; + for (size_t i = 0; i < copies; i++) { + cairo_surface[i] = cairo_image_surface_create_for_data( + mmapped, CAIRO_FORMAT_ARGB32, width, height, stride); + + if (cairo_surface_status(cairo_surface[i]) != CAIRO_STATUS_SUCCESS) { + LOG_ERR("failed to create cairo surface: %s", + cairo_status_to_string(cairo_surface_status(cairo_surface[i]))); + goto err; + } + + cairo[i] = cairo_create(cairo_surface[i]); + if (cairo_status(cairo[i]) != CAIRO_STATUS_SUCCESS) { + LOG_ERR("failed to create cairo context: %s", + cairo_status_to_string(cairo_status(cairo[i]))); + goto err; + } } /* Push to list of available buffers, but marked as 'busy' */ @@ -124,6 +132,7 @@ shm_get_buffer(struct wl_shm *shm, int width, int height) .size = size, .mmapped = mmapped, .wl_buf = buf, + .copies = copies, .cairo_surface = cairo_surface, .cairo = cairo} ) @@ -134,10 +143,18 @@ shm_get_buffer(struct wl_shm *shm, int width, int height) return ret; err: - if (cairo != NULL) - cairo_destroy(cairo); - if (cairo_surface != NULL) - cairo_surface_destroy(cairo_surface); + if (cairo != NULL) { + for (size_t i = 0; i < copies; i++) + if (cairo[i] != NULL) + cairo_destroy(cairo[i]); + free(cairo); + } + if (cairo_surface != NULL) { + for (size_t i = 0; i < copies; i++) + if (cairo_surface[i] != NULL) + cairo_surface_destroy(cairo_surface[i]); + free(cairo_surface); + } if (buf != NULL) wl_buffer_destroy(buf); if (pool != NULL) @@ -156,8 +173,18 @@ shm_fini(void) tll_foreach(buffers, it) { struct buffer *buf = &it->item; - cairo_destroy(buf->cairo); - cairo_surface_destroy(buf->cairo_surface); + if (buf->cairo != NULL) { + for (size_t i = 0; i < buf->copies; i++) + if (buf->cairo[i] != NULL) + cairo_destroy(buf->cairo[i]); + free(buf->cairo); + } + if (buf->cairo_surface != NULL) { + for (size_t i = 0; i < buf->copies; i++) + if (buf->cairo_surface[i] != NULL) + cairo_surface_destroy(buf->cairo_surface[i]); + free(buf->cairo_surface); + } wl_buffer_destroy(buf->wl_buf); munmap(buf->mmapped, buf->size); diff --git a/shm.h b/shm.h index 35360db6..9982cb8d 100644 --- a/shm.h +++ b/shm.h @@ -16,9 +16,10 @@ struct buffer { struct wl_buffer *wl_buf; - cairo_surface_t *cairo_surface; - cairo_t *cairo; + size_t copies; + cairo_surface_t **cairo_surface; + cairo_t **cairo; }; -struct buffer *shm_get_buffer(struct wl_shm *shm, int width, int height); +struct buffer *shm_get_buffer(struct wl_shm *shm, int width, int height, size_t copies); void shm_fini(void); diff --git a/terminal.c b/terminal.c index 871e6dcb..d6742259 100644 --- a/terminal.c +++ b/terminal.c @@ -20,29 +20,36 @@ void term_damage_rows(struct terminal *term, int start, int end) { assert(start <= end); - for (int r = start; r <= end; r++) - grid_row(term->grid, r)->dirty = true; + for (int r = start; r <= end; r++) { + struct row *row = grid_row(term->grid, r); + row->dirty = true; + for (int c = 0; c < term->grid->num_cols; c++) + row->cells[c].attrs.clean = 0; + } } void term_damage_rows_in_view(struct terminal *term, int start, int end) { assert(start <= end); - for (int r = start; r <= end; r++) - grid_row_in_view(term->grid, r)->dirty = true; + for (int r = start; r <= end; r++) { + struct row *row = grid_row_in_view(term->grid, r); + row->dirty = true; + for (int c = 0; c < term->grid->num_cols; c++) + row->cells[c].attrs.clean = 0; + } } void term_damage_all(struct terminal *term) { - term_damage_rows(term, 0, term->rows); + term_damage_rows(term, 0, term->rows - 1); } void term_damage_view(struct terminal *term) { - for (int i = 0; i < term->rows; i++) - grid_row_in_view(term->grid, i)->dirty = true; + term_damage_rows_in_view(term, 0, term->rows - 1); } void @@ -73,9 +80,10 @@ erase_cell_range(struct terminal *term, struct row *row, int start, int end) assert(start < term->cols); assert(end < term->cols); - if (unlikely(term->vt.attrs.background >> 31)) { + if (unlikely(term->vt.attrs.background >> 30)) { for (int col = start; col <= end; col++) { row->cells[col].c[0] = '\0'; + row->cells[col].attrs.clean = 0; row->cells[col].attrs.background = term->vt.attrs.background; } } else { diff --git a/terminal.h b/terminal.h index 86b92d17..a49f7bd5 100644 --- a/terminal.h +++ b/terminal.h @@ -5,9 +5,7 @@ #include #include - -#include -#include FT_FREETYPE_H +#include #include #include @@ -15,7 +13,7 @@ #include #include - +#include "font.h" #include "tllist.h" #define likely(c) __builtin_expect(!!(c), 1) @@ -63,8 +61,11 @@ struct attributes { uint8_t conceal:1; uint8_t reverse:1; - uint32_t foreground; - uint32_t background; + uint32_t clean:1; + uint32_t foreground:31; + + uint32_t reserved:1; + uint32_t background:31; } __attribute__((packed)); struct cell { @@ -206,31 +207,6 @@ struct primary { uint32_t serial; }; -struct glyph { - void *data; - cairo_surface_t *surf; - int left; - int top; -}; - -struct font { - FT_Face face; - int load_flags; - int render_flags; - FT_LcdFilter lcd_filter; - struct { - double position; - double thickness; - } underline; - struct { - double position; - double thickness; - } strikeout; - - struct glyph cache[256]; - mtx_t lock; -}; - enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR }; struct terminal { @@ -341,6 +317,17 @@ struct terminal { struct { struct wl_callback *frame_callback; + struct { + size_t count; + sem_t start; + sem_t done; + cnd_t cond; + mtx_t lock; + tll(int) queue; + thrd_t *threads; + struct buffer *buf; + } workers; + /* Last rendered cursor position */ struct { struct coord actual; /* Absolute */ diff --git a/vt.c b/vt.c index 7ee59acb..399583ea 100644 --- a/vt.c +++ b/vt.c @@ -698,7 +698,6 @@ pre_print(struct terminal *term) static inline void post_print(struct terminal *term) { - term->grid->cur_row->dirty = true; if (term->cursor.col < term->cols - 1) term_cursor_right(term, 1); else @@ -716,13 +715,6 @@ print_insert(struct terminal *term) &row[term->cursor.col + 1], &row[term->cursor.col], term->cols - term->cursor.col - 1); - -#if 0 - term_damage_update( - term, term->cursor.linear + 1, term->cols - term->cursor.col - 1); -#else - row->dirty = true; -#endif } } @@ -737,6 +729,7 @@ action_print_utf8(struct terminal *term) term_damage_update(term, term->cursor.linear, 1); #else row->dirty = true; + cell->attrs.clean = 0; #endif print_insert(term); @@ -761,6 +754,7 @@ action_print(struct terminal *term, uint8_t c) term_damage_update(term, term->cursor.linear, 1); #else row->dirty = true; + cell->attrs.clean = 0; #endif print_insert(term);