Fast and reasonably complete (framebuffer) terminal emulator (Zig fork)
1#include <stdint.h> 2#include <stddef.h> 3 4#include "../term.h" 5#include "framebuffer.h" 6 7void *memset(void *, int, size_t); 8void *memcpy(void *, const void *, size_t); 9 10static void fbterm_swap_palette(struct term_context *_ctx) { 11 struct fbterm_context *ctx = (void *)_ctx; 12 uint32_t tmp = ctx->text_bg; 13 ctx->text_bg = ctx->text_fg; 14 ctx->text_fg = tmp; 15} 16 17static void plot_char(struct term_context *_ctx, struct fbterm_char *c, size_t x, size_t y) { 18 struct fbterm_context *ctx = (void *)_ctx; 19 20 if (x >= _ctx->cols || y >= _ctx->rows) { 21 return; 22 } 23 24 x = ctx->offset_x + x * ctx->glyph_width; 25 y = ctx->offset_y + y * ctx->glyph_height; 26 27 bool *glyph = &ctx->font_bool[c->c * ctx->font_height * ctx->font_width]; 28 // naming: fx,fy for font coordinates, gx,gy for glyph coordinates 29 for (size_t gy = 0; gy < ctx->glyph_height; gy++) { 30 uint8_t fy = gy / ctx->font_scale_y; 31 volatile uint32_t *fb_line = ctx->framebuffer + x + (y + gy) * (ctx->pitch / 4); 32 uint32_t *canvas_line = ctx->canvas + x + (y + gy) * ctx->width; 33 for (size_t fx = 0; fx < ctx->font_width; fx++) { 34 bool draw = glyph[fy * ctx->font_width + fx]; 35 for (size_t i = 0; i < ctx->font_scale_x; i++) { 36 size_t gx = ctx->font_scale_x * fx + i; 37 uint32_t bg = c->bg == 0xffffffff ? canvas_line[gx] : c->bg; 38 uint32_t fg = c->fg == 0xffffffff ? canvas_line[gx] : c->fg; 39 fb_line[gx] = draw ? fg : bg; 40 } 41 } 42 } 43} 44 45static void plot_char_fast(struct term_context *_ctx, struct fbterm_char *old, struct fbterm_char *c, size_t x, size_t y) { 46 struct fbterm_context *ctx = (void *)_ctx; 47 48 if (x >= _ctx->cols || y >= _ctx->rows) { 49 return; 50 } 51 52 x = ctx->offset_x + x * ctx->glyph_width; 53 y = ctx->offset_y + y * ctx->glyph_height; 54 55 bool *new_glyph = &ctx->font_bool[c->c * ctx->font_height * ctx->font_width]; 56 bool *old_glyph = &ctx->font_bool[old->c * ctx->font_height * ctx->font_width]; 57 for (size_t gy = 0; gy < ctx->glyph_height; gy++) { 58 uint8_t fy = gy / ctx->font_scale_y; 59 volatile uint32_t *fb_line = ctx->framebuffer + x + (y + gy) * (ctx->pitch / 4); 60 uint32_t *canvas_line = ctx->canvas + x + (y + gy) * ctx->width; 61 for (size_t fx = 0; fx < ctx->font_width; fx++) { 62 bool old_draw = old_glyph[fy * ctx->font_width + fx]; 63 bool new_draw = new_glyph[fy * ctx->font_width + fx]; 64 if (old_draw == new_draw) 65 continue; 66 for (size_t i = 0; i < ctx->font_scale_x; i++) { 67 size_t gx = ctx->font_scale_x * fx + i; 68 uint32_t bg = c->bg == 0xffffffff ? canvas_line[gx] : c->bg; 69 uint32_t fg = c->fg == 0xffffffff ? canvas_line[gx] : c->fg; 70 fb_line[gx] = new_draw ? fg : bg; 71 } 72 } 73 } 74} 75 76static inline bool compare_char(struct fbterm_char *a, struct fbterm_char *b) { 77 return !(a->c != b->c || a->bg != b->bg || a->fg != b->fg); 78} 79 80static void push_to_queue(struct term_context *_ctx, struct fbterm_char *c, size_t x, size_t y) { 81 struct fbterm_context *ctx = (void *)_ctx; 82 83 if (x >= _ctx->cols || y >= _ctx->rows) { 84 return; 85 } 86 87 size_t i = y * _ctx->cols + x; 88 89 struct fbterm_queue_item *q = ctx->map[i]; 90 91 if (q == NULL) { 92 if (compare_char(&ctx->grid[i], c)) { 93 return; 94 } 95 q = &ctx->queue[ctx->queue_i++]; 96 q->x = x; 97 q->y = y; 98 ctx->map[i] = q; 99 } 100 101 q->c = *c; 102} 103 104static void fbterm_revscroll(struct term_context *_ctx) { 105 struct fbterm_context *ctx = (void *)_ctx; 106 107 for (size_t i = (_ctx->scroll_bottom_margin - 1) * _ctx->cols - 1; ; i--) { 108 struct fbterm_char *c; 109 struct fbterm_queue_item *q = ctx->map[i]; 110 if (q != NULL) { 111 c = &q->c; 112 } else { 113 c = &ctx->grid[i]; 114 } 115 push_to_queue(_ctx, c, (i + _ctx->cols) % _ctx->cols, (i + _ctx->cols) / _ctx->cols); 116 if (i == _ctx->scroll_top_margin * _ctx->cols) { 117 break; 118 } 119 } 120 121 // Clear the first line of the screen. 122 struct fbterm_char empty; 123 empty.c = ' '; 124 empty.fg = ctx->text_fg; 125 empty.bg = ctx->text_bg; 126 for (size_t i = _ctx->scroll_top_margin * _ctx->cols; 127 i < (_ctx->scroll_top_margin + 1) * _ctx->cols; i++) { 128 push_to_queue(_ctx, &empty, i % _ctx->cols, i / _ctx->cols); 129 } 130} 131 132static void fbterm_scroll(struct term_context *_ctx) { 133 struct fbterm_context *ctx = (void *)_ctx; 134 135 for (size_t i = (_ctx->scroll_top_margin + 1) * _ctx->cols; 136 i < _ctx->scroll_bottom_margin * _ctx->cols; i++) { 137 struct fbterm_char *c; 138 struct fbterm_queue_item *q = ctx->map[i]; 139 if (q != NULL) { 140 c = &q->c; 141 } else { 142 c = &ctx->grid[i]; 143 } 144 push_to_queue(_ctx, c, (i - _ctx->cols) % _ctx->cols, (i - _ctx->cols) / _ctx->cols); 145 } 146 147 // Clear the last line of the screen. 148 struct fbterm_char empty; 149 empty.c = ' '; 150 empty.fg = ctx->text_fg; 151 empty.bg = ctx->text_bg; 152 for (size_t i = (_ctx->scroll_bottom_margin - 1) * _ctx->cols; 153 i < _ctx->scroll_bottom_margin * _ctx->cols; i++) { 154 push_to_queue(_ctx, &empty, i % _ctx->cols, i / _ctx->cols); 155 } 156} 157 158static void fbterm_clear(struct term_context *_ctx, bool move) { 159 struct fbterm_context *ctx = (void *)_ctx; 160 161 struct fbterm_char empty; 162 empty.c = ' '; 163 empty.fg = ctx->text_fg; 164 empty.bg = ctx->text_bg; 165 for (size_t i = 0; i < _ctx->rows * _ctx->cols; i++) { 166 push_to_queue(_ctx, &empty, i % _ctx->cols, i / _ctx->cols); 167 } 168 169 if (move) { 170 ctx->cursor_x = 0; 171 ctx->cursor_y = 0; 172 } 173} 174 175static void fbterm_enable_cursor(struct term_context *_ctx) { 176 struct fbterm_context *ctx = (void *)_ctx; 177 178 ctx->cursor_status = true; 179} 180 181static bool fbterm_disable_cursor(struct term_context *_ctx) { 182 struct fbterm_context *ctx = (void *)_ctx; 183 184 bool ret = ctx->cursor_status; 185 ctx->cursor_status = false; 186 return ret; 187} 188 189static void fbterm_set_cursor_pos(struct term_context *_ctx, size_t x, size_t y) { 190 struct fbterm_context *ctx = (void *)_ctx; 191 192 if (x >= _ctx->cols) { 193 if ((int)x < 0) { 194 x = 0; 195 } else { 196 x = _ctx->cols - 1; 197 } 198 } 199 if (y >= _ctx->rows) { 200 if ((int)y < 0) { 201 y = 0; 202 } else { 203 y = _ctx->rows - 1; 204 } 205 } 206 ctx->cursor_x = x; 207 ctx->cursor_y = y; 208} 209 210static void fbterm_get_cursor_pos(struct term_context *_ctx, size_t *x, size_t *y) { 211 struct fbterm_context *ctx = (void *)_ctx; 212 213 *x = ctx->cursor_x; 214 *y = ctx->cursor_y; 215} 216 217static void fbterm_move_character(struct term_context *_ctx, size_t new_x, size_t new_y, size_t old_x, size_t old_y) { 218 struct fbterm_context *ctx = (void *)_ctx; 219 220 if (old_x >= _ctx->cols || old_y >= _ctx->rows 221 || new_x >= _ctx->cols || new_y >= _ctx->rows) { 222 return; 223 } 224 225 size_t i = old_x + old_y * _ctx->cols; 226 227 struct fbterm_char *c; 228 struct fbterm_queue_item *q = ctx->map[i]; 229 if (q != NULL) { 230 c = &q->c; 231 } else { 232 c = &ctx->grid[i]; 233 } 234 235 push_to_queue(_ctx, c, new_x, new_y); 236} 237 238static void fbterm_set_text_fg(struct term_context *_ctx, size_t fg) { 239 struct fbterm_context *ctx = (void *)_ctx; 240 241 ctx->text_fg = ctx->ansi_colours[fg]; 242} 243 244static void fbterm_set_text_bg(struct term_context *_ctx, size_t bg) { 245 struct fbterm_context *ctx = (void *)_ctx; 246 247 ctx->text_bg = ctx->ansi_colours[bg]; 248} 249 250static void fbterm_set_text_fg_bright(struct term_context *_ctx, size_t fg) { 251 struct fbterm_context *ctx = (void *)_ctx; 252 253 ctx->text_fg = ctx->ansi_bright_colours[fg]; 254} 255 256static void fbterm_set_text_bg_bright(struct term_context *_ctx, size_t bg) { 257 struct fbterm_context *ctx = (void *)_ctx; 258 259 ctx->text_bg = ctx->ansi_bright_colours[bg]; 260} 261 262static void fbterm_set_text_fg_rgb(struct term_context *_ctx, uint32_t fg) { 263 struct fbterm_context *ctx = (void *)_ctx; 264 265 ctx->text_fg = fg; 266} 267 268static void fbterm_set_text_bg_rgb(struct term_context *_ctx, uint32_t bg) { 269 struct fbterm_context *ctx = (void *)_ctx; 270 271 ctx->text_bg = bg; 272} 273 274static void fbterm_set_text_fg_default(struct term_context *_ctx) { 275 struct fbterm_context *ctx = (void *)_ctx; 276 277 ctx->text_fg = ctx->default_fg; 278} 279 280static void fbterm_set_text_bg_default(struct term_context *_ctx) { 281 struct fbterm_context *ctx = (void *)_ctx; 282 283 ctx->text_bg = 0xffffffff; 284} 285 286static void draw_cursor(struct term_context *_ctx) { 287 struct fbterm_context *ctx = (void *)_ctx; 288 289 size_t i = ctx->cursor_x + ctx->cursor_y * _ctx->cols; 290 struct fbterm_char c; 291 struct fbterm_queue_item *q = ctx->map[i]; 292 if (q != NULL) { 293 c = q->c; 294 } else { 295 c = ctx->grid[i]; 296 } 297 uint32_t tmp = c.fg; 298 c.fg = c.bg; 299 c.bg = tmp; 300 plot_char(_ctx, &c, ctx->cursor_x, ctx->cursor_y); 301 if (q != NULL) { 302 ctx->grid[i] = q->c; 303 ctx->map[i] = NULL; 304 } 305} 306 307static void fbterm_double_buffer_flush(struct term_context *_ctx) { 308 struct fbterm_context *ctx = (void *)_ctx; 309 310 if (ctx->cursor_status) { 311 draw_cursor(_ctx); 312 } 313 314 for (size_t i = 0; i < ctx->queue_i; i++) { 315 struct fbterm_queue_item *q = &ctx->queue[i]; 316 size_t offset = q->y * _ctx->cols + q->x; 317 if (ctx->map[offset] == NULL) { 318 continue; 319 } 320 struct fbterm_char *old = &ctx->grid[offset]; 321 if (q->c.bg == old->bg && q->c.fg == old->fg) { 322 plot_char_fast(_ctx, old, &q->c, q->x, q->y); 323 } else { 324 plot_char(_ctx, &q->c, q->x, q->y); 325 } 326 ctx->grid[offset] = q->c; 327 ctx->map[offset] = NULL; 328 } 329 330 if ((ctx->old_cursor_x != ctx->cursor_x || ctx->old_cursor_y != ctx->cursor_y) || ctx->cursor_status == false) { 331 plot_char(_ctx, &ctx->grid[ctx->old_cursor_x + ctx->old_cursor_y * _ctx->cols], ctx->old_cursor_x, ctx->old_cursor_y); 332 } 333 334 ctx->old_cursor_x = ctx->cursor_x; 335 ctx->old_cursor_y = ctx->cursor_y; 336 337 ctx->queue_i = 0; 338} 339 340static void fbterm_raw_putchar(struct term_context *_ctx, uint8_t c) { 341 struct fbterm_context *ctx = (void *)_ctx; 342 343 struct fbterm_char ch; 344 ch.c = c; 345 ch.fg = ctx->text_fg; 346 ch.bg = ctx->text_bg; 347 push_to_queue(_ctx, &ch, ctx->cursor_x++, ctx->cursor_y); 348 if (ctx->cursor_x == _ctx->cols && (ctx->cursor_y < _ctx->scroll_bottom_margin - 1 || _ctx->scroll_enabled)) { 349 ctx->cursor_x = 0; 350 ctx->cursor_y++; 351 } 352 if (ctx->cursor_y == _ctx->scroll_bottom_margin) { 353 ctx->cursor_y--; 354 fbterm_scroll(_ctx); 355 } 356} 357 358static void fbterm_full_refresh(struct term_context *_ctx) { 359 struct fbterm_context *ctx = (void *)_ctx; 360 361 for (size_t y = 0; y < ctx->height; y++) { 362 for (size_t x = 0; x < ctx->width; x++) { 363 ctx->framebuffer[y * (ctx->pitch / sizeof(uint32_t)) + x] = ctx->canvas[y * ctx->width + x]; 364 } 365 } 366 367 for (size_t i = 0; i < (size_t)_ctx->rows * _ctx->cols; i++) { 368 size_t x = i % _ctx->cols; 369 size_t y = i / _ctx->cols; 370 371 plot_char(_ctx, &ctx->grid[i], x, y); 372 } 373 374 if (ctx->cursor_status) { 375 draw_cursor(_ctx); 376 } 377} 378 379static void fbterm_deinit(struct term_context *_ctx, void (*_free)(void *, size_t)) { 380 struct fbterm_context *ctx = (void *)_ctx; 381 382 _free(ctx->font_bits, ctx->font_bits_size); 383 _free(ctx->font_bool, ctx->font_bool_size); 384 _free(ctx->grid, ctx->grid_size); 385 _free(ctx->queue, ctx->queue_size); 386 _free(ctx->map, ctx->map_size); 387 _free(ctx->canvas, ctx->canvas_size); 388} 389 390struct term_context *fbterm_init( 391 void *(*_malloc)(size_t), 392 uint32_t *framebuffer, size_t width, size_t height, size_t pitch, 393 uint32_t *canvas, 394 uint32_t *ansi_colours, uint32_t *ansi_bright_colours, 395 uint32_t *default_bg, uint32_t *default_fg, 396 void *font, size_t font_width, size_t font_height, size_t font_spacing, 397 size_t font_scale_x, size_t font_scale_y, 398 size_t margin 399) { 400 struct fbterm_context *ctx = _malloc(sizeof(struct fbterm_context)); 401 402 struct term_context *_ctx = (void *)ctx; 403 404 memset(ctx, 0, sizeof(struct fbterm_context)); 405 406 ctx->cursor_status = true; 407 408 if (ansi_colours != NULL) { 409 memcpy(ctx->ansi_colours, ansi_colours, sizeof(ctx->ansi_colours)); 410 } else { 411 ctx->ansi_colours[0] = 0x00000000; // black 412 ctx->ansi_colours[1] = 0x00aa0000; // red 413 ctx->ansi_colours[2] = 0x0000aa00; // green 414 ctx->ansi_colours[3] = 0x00aa5500; // brown 415 ctx->ansi_colours[4] = 0x000000aa; // blue 416 ctx->ansi_colours[5] = 0x00aa00aa; // magenta 417 ctx->ansi_colours[6] = 0x0000aaaa; // cyan 418 ctx->ansi_colours[7] = 0x00aaaaaa; // grey 419 } 420 421 if (ansi_bright_colours != NULL) { 422 memcpy(ctx->ansi_bright_colours, ansi_bright_colours, sizeof(ctx->ansi_bright_colours)); 423 } else { 424 ctx->ansi_bright_colours[0] = 0x00555555; // black 425 ctx->ansi_bright_colours[1] = 0x00ff5555; // red 426 ctx->ansi_bright_colours[2] = 0x0055ff55; // green 427 ctx->ansi_bright_colours[3] = 0x00ffff55; // brown 428 ctx->ansi_bright_colours[4] = 0x005555ff; // blue 429 ctx->ansi_bright_colours[5] = 0x00ff55ff; // magenta 430 ctx->ansi_bright_colours[6] = 0x0055ffff; // cyan 431 ctx->ansi_bright_colours[7] = 0x00ffffff; // grey 432 } 433 434 if (default_bg != NULL) { 435 ctx->default_bg = *default_bg; 436 } else { 437 ctx->default_bg = 0x00000000; // background (black) 438 } 439 440 if (default_fg != NULL) { 441 ctx->default_fg = *default_fg; 442 } else { 443 ctx->default_fg = 0x00aaaaaa; // foreground (grey) 444 } 445 446 ctx->text_fg = ctx->default_fg; 447 ctx->text_bg = 0xffffffff; 448 449 ctx->framebuffer = (void *)framebuffer; 450 ctx->width = width; 451 ctx->height = height; 452 ctx->pitch = pitch; 453 454#define FONT_BYTES ((font_width * font_height * FBTERM_FONT_GLYPHS) / 8) 455 456 if (font != NULL) { 457 ctx->font_width = font_width; 458 ctx->font_height = font_height; 459 ctx->font_bits = _malloc(FONT_BYTES); 460 memcpy(ctx->font_bits, font, FONT_BYTES); 461 } else { 462 ctx->font_width = font_width = 8; 463 ctx->font_height = font_height = 16; 464 font_spacing = 1; 465 ctx->font_bits = _malloc(FONT_BYTES); 466 // XXX memcpy(ctx->font_bits, builtin_font, FONT_BYTES); 467 } 468 469 ctx->font_bits_size = FONT_BYTES; 470 471#undef FONT_BYTES 472 473 ctx->font_width += font_spacing; 474 475 ctx->font_bool_size = FBTERM_FONT_GLYPHS * font_height * ctx->font_width * sizeof(bool); 476 ctx->font_bool = _malloc(ctx->font_bool_size); 477 478 for (size_t i = 0; i < FBTERM_FONT_GLYPHS; i++) { 479 uint8_t *glyph = &ctx->font_bits[i * font_height]; 480 481 for (size_t y = 0; y < font_height; y++) { 482 // NOTE: the characters in VGA fonts are always one byte wide. 483 // 9 dot wide fonts have 8 dots and one empty column, except 484 // characters 0xC0-0xDF replicate column 9. 485 for (size_t x = 0; x < 8; x++) { 486 size_t offset = i * font_height * ctx->font_width + y * ctx->font_width + x; 487 488 if ((glyph[y] & (0x80 >> x))) { 489 ctx->font_bool[offset] = true; 490 } else { 491 ctx->font_bool[offset] = false; 492 } 493 } 494 // fill columns above 8 like VGA Line Graphics Mode does 495 for (size_t x = 8; x < ctx->font_width; x++) { 496 size_t offset = i * font_height * ctx->font_width + y * ctx->font_width + x; 497 498 if (i >= 0xc0 && i <= 0xdf) { 499 ctx->font_bool[offset] = (glyph[y] & 1); 500 } else { 501 ctx->font_bool[offset] = false; 502 } 503 } 504 } 505 } 506 507 ctx->font_scale_x = font_scale_x; 508 ctx->font_scale_y = font_scale_y; 509 510 ctx->glyph_width = ctx->font_width * font_scale_x; 511 ctx->glyph_height = font_height * font_scale_y; 512 513 _ctx->cols = (ctx->width - margin * 2) / ctx->glyph_width; 514 _ctx->rows = (ctx->height - margin * 2) / ctx->glyph_height; 515 516 ctx->offset_x = margin + ((ctx->width - margin * 2) % ctx->glyph_width) / 2; 517 ctx->offset_y = margin + ((ctx->height - margin * 2) % ctx->glyph_height) / 2; 518 519 ctx->grid_size = _ctx->rows * _ctx->cols * sizeof(struct fbterm_char); 520 ctx->grid = _malloc(ctx->grid_size); 521 522 ctx->queue_size = _ctx->rows * _ctx->cols * sizeof(struct fbterm_queue_item); 523 ctx->queue = _malloc(ctx->queue_size); 524 ctx->queue_i = 0; 525 526 ctx->map_size = _ctx->rows * _ctx->cols * sizeof(struct fbterm_queue_item *); 527 ctx->map = _malloc(ctx->map_size); 528 529 ctx->canvas_size = ctx->width * ctx->height * sizeof(uint32_t); 530 ctx->canvas = _malloc(ctx->canvas_size); 531 if (canvas != NULL) { 532 memcpy(ctx->canvas, canvas, ctx->canvas_size); 533 } else { 534 for (size_t i = 0; i < ctx->width * ctx->height; i++) { 535 ctx->canvas[i] = ctx->default_bg; 536 } 537 } 538 539 _ctx->raw_putchar = fbterm_raw_putchar; 540 _ctx->clear = fbterm_clear; 541 _ctx->enable_cursor = fbterm_enable_cursor; 542 _ctx->disable_cursor = fbterm_disable_cursor; 543 _ctx->set_cursor_pos = fbterm_set_cursor_pos; 544 _ctx->get_cursor_pos = fbterm_get_cursor_pos; 545 _ctx->set_text_fg = fbterm_set_text_fg; 546 _ctx->set_text_bg = fbterm_set_text_bg; 547 _ctx->set_text_fg_bright = fbterm_set_text_fg_bright; 548 _ctx->set_text_bg_bright = fbterm_set_text_bg_bright; 549 _ctx->set_text_fg_rgb = fbterm_set_text_fg_rgb; 550 _ctx->set_text_bg_rgb = fbterm_set_text_bg_rgb; 551 _ctx->set_text_fg_default = fbterm_set_text_fg_default; 552 _ctx->set_text_bg_default = fbterm_set_text_bg_default; 553 _ctx->move_character = fbterm_move_character; 554 _ctx->scroll = fbterm_scroll; 555 _ctx->revscroll = fbterm_revscroll; 556 _ctx->swap_palette = fbterm_swap_palette; 557 _ctx->double_buffer_flush = fbterm_double_buffer_flush; 558 _ctx->full_refresh = fbterm_full_refresh; 559 _ctx->deinit = fbterm_deinit; 560 561 term_context_reinit(_ctx); 562 563 fbterm_clear(_ctx, true); 564 fbterm_full_refresh(_ctx); 565 566 return _ctx; 567}