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}