Merge branch 'multithreaded-renderer'

This commit is contained in:
Daniel Eklöf 2019-07-30 18:08:27 +02:00
commit f39d848368
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
14 changed files with 598 additions and 228 deletions

View file

@ -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);
}

View file

@ -4,11 +4,12 @@
#include <stdbool.h>
#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);

25
csi.c
View file

@ -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) */

221
font.c
View file

@ -1,33 +1,42 @@
#include "font.h"
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <wchar.h>
#include <assert.h>
#include <threads.h>
#include <fontconfig/fontconfig.h>
#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);
}

55
font.h
View file

@ -3,9 +3,56 @@
#include <stdbool.h>
#include <threads.h>
#include "terminal.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_LCD_FILTER_H
#include <cairo.h>
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);

2
grid.c
View file

@ -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;
}

80
main.c
View file

@ -11,9 +11,9 @@
#include <errno.h>
#include <sys/timerfd.h>
#include <sys/sysinfo.h>
#include <freetype/tttables.h>
#include <cairo-ft.h>
#include <wayland-client.h>
#include <wayland-cursor.h>
#include <xdg-shell.h>
@ -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);

228
render.c
View file

@ -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);

View file

@ -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);

69
shm.c
View file

@ -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);

7
shm.h
View file

@ -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);

View file

@ -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 {

View file

@ -5,9 +5,7 @@
#include <stddef.h>
#include <threads.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <semaphore.h>
#include <cairo.h>
#include <wayland-client.h>
@ -15,7 +13,7 @@
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#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 */

10
vt.c
View file

@ -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);