vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: cfe9a297ff93b377d8a78f04f36587d2367d15d2
Parent: b49aacb829b15c2bb4ffcfee5d42fc818af811ef
Author: Randy Palamar
Date:   Sun, 23 Jun 2024 15:55:45 -0600

split incoming data to lines and blit some to screen

Diffstat:
Mbuild.sh | 2+-
Mmain.c | 18++++++++++++++++--
Aterminal.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.h | 32++++++++++++++++++++++++++++++++
Mvtgl.c | 68+++++++++++++++++++++++++++++++++-----------------------------------
5 files changed, 221 insertions(+), 38 deletions(-)

diff --git a/build.sh b/build.sh @@ -8,7 +8,7 @@ ldflags="$ldflags -lfreetype $(pkg-config --static --libs glfw3 gl)" # Hot Reloading/Debugging cflags="$cflags -D_DEBUG -Wno-unused-function" -libcflags="$cflags -fPIC" +libcflags="$cflags -fPIC -Wno-unused-function" libldflags="$ldflags -shared" cc $libcflags vtgl.c -o vtgl.so $libldflags diff --git a/main.c b/main.c @@ -6,7 +6,8 @@ #include "util.h" -#define BACKLOG_SIZE (16 * MEGABYTE) +#define BACKLOG_SIZE (16 * MEGABYTE) +#define BACKLOG_LINES (1024UL) #ifndef _DEBUG static void do_debug(GLCtx *gl) { } @@ -231,6 +232,17 @@ check_shaders(GLCtx *gl, Arena a) } } +static void +line_buf_alloc(LineBuf *lb, Arena *a, u8 *start_position, size capacity) +{ + lb->cap = capacity; + lb->filled = 0; + lb->widx = 0; + lb->buf = alloc(a, Line, capacity); + lb->buf[0].start = start_position; + lb->buf[0].end = start_position; +} + i32 main(void) { @@ -241,9 +253,11 @@ main(void) "/usr/share/fonts/gofont/Go-Mono.ttf", }; init_window(&term); - init_fonts(&term, font_paths, ARRAY_COUNT(font_paths), 64, &memory); + init_fonts(&term, font_paths, ARRAY_COUNT(font_paths), 48, &memory); os_alloc_ring_buffer(&term.log, BACKLOG_SIZE); + line_buf_alloc(&term.log_lines, &memory, term.log.buf, BACKLOG_LINES); + term.child = os_fork_child("/bin/sh"); do_debug(&term.gl); diff --git a/terminal.c b/terminal.c @@ -0,0 +1,139 @@ +#include <immintrin.h> + +static const u8 utf8overhangmask[32] = { + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static s8 +consume(s8 raw, size count) +{ + raw.data += count; + raw.len -= count; + return raw; +} + +static u8 +peek(s8 raw, size i) +{ + ASSERT(i < raw.len); + return raw.data[i]; +} + +static u32 +get_ascii(s8 *raw) +{ + ASSERT(raw->len > 0); + u32 result = raw->data[0]; + *raw = consume(*raw, 1); + return result; +} + +static size +line_length(Line *l) +{ + ASSERT(l->start <= l->end); + return l->end - l->start; +} + +static void +feed_line(LineBuf *lb, u8 *position, CursorState cursor_state) +{ + lb->buf[lb->widx++].end = position; + lb->widx = lb->widx >= lb->cap ? 0 : lb->widx; + lb->filled += lb->filled <= lb->widx; + + lb->buf[lb->widx].start = position; + lb->buf[lb->widx].end = position; + lb->buf[lb->widx].has_unicode = 0; + lb->buf[lb->widx].cursor_state = cursor_state; +} + +enum handle_escape_return { + HESC_NORMAL_RETURN, + HESC_NEEDS_MORE_BYTES, + HESC_CURSOR_MOVED, +}; +static enum handle_escape_return +handle_escape(s8 *raw) +{ + enum handle_escape_return result = HESC_NORMAL_RETURN; + + return result; +} + +static size +split_raw_input_to_lines(Term *t, s8 raw) +{ + LineBuf *lb = &t->log_lines; + size parsed_lines = 0; + __m128i nl = _mm_set1_epi8('\n'); + __m128i esc = _mm_set1_epi8(0x1B); + __m128i uni = _mm_set1_epi8(0x80); + + #define SPLIT_LONG 4096 + while (raw.len) { + __m128i hasutf8 = _mm_setzero_si128(); + size count = raw.len > SPLIT_LONG ? SPLIT_LONG : raw.len; + u8 *data = raw.data; + while (count >= 16) { + __m128i vdat = _mm_loadu_si128((__m128i_u *)data); + __m128i hasnl = _mm_cmpeq_epi8(vdat, nl); + __m128i hasesc = _mm_cmpeq_epi8(vdat, esc); + __m128i hasuni = _mm_and_si128(vdat, uni); + __m128i hasproc = _mm_or_si128(hasuni, _mm_or_si128(hasnl, hasesc)); + i32 needsproc = _mm_movemask_epi8(hasproc); + + if (needsproc) { + u32 advance = _tzcnt_u32(needsproc); + __m128i utf8mask = _mm_loadu_si128((__m128i_u *)(utf8overhangmask + 16 - advance)); + hasuni = _mm_and_si128(utf8mask, hasuni); + hasutf8 = _mm_or_si128(hasutf8, hasuni); + count -= advance; + data += advance; + break; + } + + hasutf8 = _mm_or_si128(hasutf8, hasuni); + count -= 16; + data += 16; + } + lb->buf[lb->widx].has_unicode |= _mm_movemask_epi8(hasutf8); + raw = consume(raw, data - raw.data); + + if (peek(raw, 0) == 0x1B) { + s8 old = raw; + raw = consume(raw, 1); + switch (handle_escape(&raw)) { + case HESC_NEEDS_MORE_BYTES: + t->unprocessed_bytes = old.len; + return parsed_lines; + case HESC_CURSOR_MOVED: + parsed_lines++; + feed_line(lb, old.data, t->cursor.state); + break; + default: break; + } + } else { + u32 c = get_ascii(&raw); + if (c == '\n') { + parsed_lines++; + feed_line(lb, raw.data, t->cursor.state); + } else if (c & 0x80) { + lb->buf[lb->widx].has_unicode = 1; + } + } + + lb->buf[lb->widx].end = raw.data; + //if (tv->lines.buf[tv->lines.widx].end < tv->lines.buf[tv->lines.widx].start) + // tv->lines.buf[tv->lines.widx].end += tv->log.cap; + if (line_length(lb->buf + lb->widx) > SPLIT_LONG) { + parsed_lines++; + feed_line(lb, raw.data, t->cursor.state); + } + } + t->unprocessed_bytes = 0; + return parsed_lines; +} diff --git a/util.h b/util.h @@ -69,6 +69,20 @@ typedef struct { u8 *beg, *end; } Arena; typedef struct { size len; u8 *data; } s8; #define s8(s) (s8){.len = ARRAY_COUNT(s) - 1, .data = (u8 *)s} +enum CellAttr { + ATTR_NULL = 0, +}; + +typedef struct { + Colour fg, bg; + enum CellAttr attr; +} CursorState; + +typedef struct { + u32 row, col; + CursorState state; +} Cursor; + /* NOTE: virtual memory ring buffer */ typedef struct { size cap; @@ -77,6 +91,19 @@ typedef struct { u8 *buf; } RingBuf; +typedef struct { + u8 *start, *end; + b32 has_unicode; + CursorState cursor_state; +} Line; + +typedef struct { + size cap; + size filled; + size widx; + Line *buf; +} LineBuf; + #define GL_UNIFORMS \ X(Pmat) \ X(charmap) \ @@ -149,12 +176,17 @@ typedef struct { FontAtlas fa; RingBuf log; + LineBuf log_lines; + size unprocessed_bytes; os_child child; + Cursor cursor; + FT_Library ftlib; } Term; #include "font.c" +#include "terminal.c" #endif /* _UTIL_H_ */ diff --git a/vtgl.c b/vtgl.c @@ -185,6 +185,30 @@ draw_cell(GLCtx *gl, u32 cp, Rect r, u32 bg, u32 fg) glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 2); } +static s8 +line_to_s8(Line *l) +{ + ASSERT(l->start <= l->end); + s8 result = {.len = l->end - l->start, .data = l->start}; + return result; +} + +static void +blit_lines(Term *t) +{ + size line_count = 12; + CLAMP(line_count, 0, t->log_lines.filled); + + /* TODO: handle case where widx has wrapped around */ + ASSERT(t->log_lines.widx >= line_count); + size line_off = t->log_lines.widx - line_count; + v2 pos = {.y = t->gl.window_size.h - t->fa.size.h - 8}; + for (size i = 0; i <= line_count; i++) { + s8 line = line_to_s8(t->log_lines.buf + line_off + i); + draw_text(&t->gl, line, pos, (Colour){.rgba = 0x1e9e33ff}, 1); + pos.y -= (t->fa.size.h + 8); + } +} /* NOTE: called when the window was resized */ static void @@ -237,42 +261,16 @@ do_terminal(Term *t, Arena a) if (os_child_data_available(t->child)) { if (os_child_exited(t->child)) glfwSetWindowShouldClose(t->gl.window, GL_TRUE); - os_read_from_child(t->child, &t->log); - } - /* TODO: actually parse this data */ - s8 text = {.len = t->log.widx, .data = t->log.buf}; - v2 cpos = { .x = 0, .y = t->gl.window_size.h/2 }; - draw_text(&t->gl, text, cpos, (Colour){.rgba = 0x1e9e33ff}, 1); - - Rect r1 = {.pos = {.x = 20, .y = 20}, .size = {.x = 200, .y = 100}}; - Rect r3 = {.pos = {.x = 100, .y = 600}, .size = {.x = 50, .y = 100}}; - static Rect r2 = {.pos = {.x = 50, .y = 300}, .size = {.x = 100, .y = 100}}; - static v2 r2dir = {.x = 1, .y = -1 }; - - r2.pos.x += r2dir.x * t->gl.dt * 100; - r2.pos.y += r2dir.y * t->gl.dt * 140; - if (r2.pos.x + r2.size.x > (f32)t->gl.window_size.w) { - r2.pos.x = (f32)t->gl.window_size.w - r2.size.x; - r2dir.x *= -1; - } - if (r2.pos.x < 0) { - r2.pos.x = 0; - r2dir.x *= -1; - } - - if (r2.pos.y + r2.size.y > (f32)t->gl.window_size.h) { - r2.pos.y = (f32)t->gl.window_size.h - r2.size.y; - r2dir.y *= -1; - } - - if (r2.pos.y < 0) { - r2.pos.y = 0; - r2dir.y *= -1; + t->unprocessed_bytes += os_read_from_child(t->child, &t->log); + s8 raw = { + .len = t->unprocessed_bytes, + .data = t->log.buf + (t->log.widx - t->unprocessed_bytes) + }; + size parsed_lines = split_raw_input_to_lines(t, raw); + /* TODO: think about only blitting update lines? */ + (void)parsed_lines; } - - draw_rectangle(&t->gl, r1, (Colour){.rgba = 0x7f0000ff}); - draw_cell(&t->gl, 'E', r2, 0x007f00ff, 0x000000ff); - draw_rectangle(&t->gl, r3, (Colour){.rgba = 0x00007fff}); + blit_lines(t); { s8 fps = s8alloc(&a, 64);