Commit: 7deeb70cf86620e1730ba033ae13625249e51849
Parent: 726eefc583ae3f8b971a69903f41ff16fe92777e
Author: Randy Palamar
Date: Sat, 6 Jul 2024 12:32:06 -0600
push cells to CPU side framebuffer
Diffstat:
M | build.sh | | | 1 | + |
M | main.c | | | 2 | +- |
M | os_unix.c | | | 54 | +++++++++++++++++++++++++++++++++++++++++++++++++++--- |
M | terminal.c | | | 164 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- |
M | util.h | | | 30 | +++++++++++++++++++++++------- |
M | vtgl.c | | | 152 | +++++++++++++++++++++++++------------------------------------------------------ |
6 files changed, 275 insertions(+), 128 deletions(-)
diff --git a/build.sh b/build.sh
@@ -7,6 +7,7 @@ ldflags="$ldflags -lfreetype $(pkg-config --static --libs glfw3 gl)"
# Hot Reloading/Debugging
cflags="$cflags -D_DEBUG -Wno-unused-function -Wno-undefined-internal"
+#cflags="$cflags -fsanitize=address,undefined"
libcflags="$cflags -fPIC -Wno-unused-function"
libldflags="$ldflags -shared"
diff --git a/main.c b/main.c
@@ -264,7 +264,7 @@ check_shaders(GLCtx *gl, Arena a)
}
static void
-line_buf_alloc(LineBuf *lb, Arena *a, u8 *start_position, CursorState state, size capacity)
+line_buf_alloc(LineBuf *lb, Arena *a, u8 *start_position, CellStyle state, size capacity)
{
lb->cap = capacity;
lb->filled = 0;
diff --git a/os_unix.c b/os_unix.c
@@ -127,6 +127,54 @@ os_alloc_ring_buffer(RingBuf *rb, size capacity)
}
static void
+os_alloc_framebuffer(Framebuffer *fb, u32 rows, u32 cols)
+{
+ ASSERT(sizeof(Cell) == 16);
+
+ size pagesize = sysconf(_SC_PAGESIZE);
+
+ size fb_needed_space = rows * cols * sizeof(Cell);
+ if (fb->cells_alloc_size < fb_needed_space) {
+ if (fb_needed_space % pagesize != 0)
+ fb_needed_space += pagesize - fb_needed_space % pagesize;
+
+ Cell *new = mmap(0, fb_needed_space, PROT_READ|PROT_WRITE,
+ MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+ /* TODO: properly handle this case */
+ ASSERT(new != MAP_FAILED);
+
+ if (fb->cells)
+ munmap(fb->cells, fb->cells_alloc_size);
+
+ fb->cells = new;
+ fb->cells_alloc_size = fb_needed_space;
+ }
+
+ size rows_needed_space = rows * sizeof(Row);
+ if (fb->rows_alloc_size < rows_needed_space) {
+ if (rows_needed_space % pagesize != 0)
+ rows_needed_space += pagesize - rows_needed_space % pagesize;
+
+ Row *new = mmap(0, rows_needed_space, PROT_READ|PROT_WRITE,
+ MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+ /* TODO: properly handle this case */
+ ASSERT(new != MAP_FAILED);
+
+ if (fb->rows)
+ munmap(fb->rows, fb->rows_alloc_size);
+
+ fb->rows = new;
+ fb->rows_alloc_size = rows_needed_space;
+ }
+
+ fb->cells_count = rows * cols;
+ fb->rows_count = rows;
+
+ for (u32 i = 0; i < fb->rows_count; i++)
+ fb->rows[i] = fb->cells + i * cols;
+}
+
+static void
execsh(char *defcmd)
{
char *sh;
@@ -242,11 +290,11 @@ os_child_put_char(os_child c, u32 cp)
}
static void
-os_set_term_size(os_child c, uv2 size, i32 x, i32 y)
+os_set_term_size(os_child c, u32 rows, u32 cols, i32 x, i32 y)
{
struct winsize ws;
- ws.ws_col = size.w;
- ws.ws_row = size.h;
+ ws.ws_col = cols;
+ ws.ws_row = rows;
ws.ws_xpixel = x;
ws.ws_ypixel = y;
if (ioctl(c.fd, TIOCSWINSZ, &ws) < 0)
diff --git a/terminal.c b/terminal.c
@@ -60,7 +60,7 @@ line_length(Line *l)
}
static void
-feed_line(LineBuf *lb, u8 *position, CursorState cursor_state)
+feed_line(LineBuf *lb, u8 *position, CellStyle cursor_state)
{
lb->buf[lb->widx++].end = position;
lb->widx = lb->widx >= lb->cap ? 0 : lb->widx;
@@ -73,6 +73,57 @@ feed_line(LineBuf *lb, u8 *position, CursorState cursor_state)
}
static void
+fb_clear_region(Term *t, u32 r1, u32 r2, u32 c1, u32 c2)
+{
+ u32 tmp;
+ if (r1 > r2) {
+ tmp = r1;
+ r1 = r2;
+ r2 = tmp;
+ }
+ if (c1 > c2) {
+ tmp = c1;
+ c1 = c2;
+ c2 = tmp;
+ }
+ CLAMP(c1, 0, t->size.w - 1);
+ CLAMP(c2, 0, t->size.w - 1);
+ CLAMP(r1, 0, t->size.h - 1);
+ CLAMP(r2, 0, t->size.h - 1);
+
+ for (u32 r = r1; r <= r2; r++) {
+ for (u32 c = c1; c <= c2; c++) {
+ t->fb.rows[r][c].style = t->cursor.state;
+ t->fb.rows[r][c].cp = ' ';
+ }
+ }
+}
+
+static void
+fb_scroll_down(Term *t, u32 top, u32 n)
+{
+ // TODO: CLAMP?
+ fb_clear_region(t, t->size.h - 1 - (n - 1), t->size.h - 1, 0, t->size.w);
+ for (u32 i = t->size.h - 1; i >= top + n; i--) {
+ Row tmp = t->fb.rows[i];
+ t->fb.rows[i] = t->fb.rows[i - n];
+ t->fb.rows[i - n] = tmp;
+ }
+}
+
+static void
+fb_scroll_up(Term *t, u32 top, u32 n)
+{
+ // TODO: CLAMP?
+ fb_clear_region(t, top, top + n - 1, 0, t->size.w);
+ for (u32 i = top; i < t->size.h - n; i++) {
+ Row tmp = t->fb.rows[i];
+ t->fb.rows[i] = t->fb.rows[i + n];
+ t->fb.rows[i + n] = tmp;
+ }
+}
+
+static void
cursor_reset(Term *t)
{
t->cursor.state.fg = (Colour){.rgba = 0x1e9e33ff};
@@ -98,6 +149,7 @@ cursor_step_column(Term *t, i32 step)
if (t->cursor.row < t->size.h)
return;
t->cursor.row = t->size.h - 1;
+ fb_scroll_up(t, 0, 1);
}
static void
@@ -131,15 +183,20 @@ dump_csi(CSI *csi)
static void
erase_in_display(Term *t, CSI *csi)
{
- switch(csi->argv[0]) {
+ Cursor *c = &t->cursor;
+ switch (csi->argv[0]) {
case 0: /* Erase Below (default) */
- /* TODO: can we actually ignore this? */
+ fb_clear_region(t, c->row, c->row, c->col, t->size.w);
+ if (c->row < t->size.h - 1)
+ fb_clear_region(t, c->row + 1, t->size.h, 0, t->size.w);
break;
case 1: /* Erase Above */
- t->gl.flags |= DISCARD_BUFFER;
+ if (c->row > 0)
+ fb_clear_region(t, 0, c->row - 1, 0, t->size.w);
+ fb_clear_region(t, c->row, c->row, 0, c->col);
break;
case 2: /* Erase All */
- t->gl.flags |= DISCARD_BUFFER;
+ fb_clear_region(t, 0, t->size.h, 0, t->size.w);
break;
case 3: /* Erase Saved Lines (xterm) */
/* NOTE: ignored; we don't save lines in the way xterm does */
@@ -155,13 +212,13 @@ erase_in_line(Term *t, CSI *csi)
Cursor *c = &t->cursor;
switch (csi->argv[0]) {
case 0: /* Erase to Right */
- push_empty_cell_rect(t, c->row, c->row, c->col, t->size.w);
+ fb_clear_region(t, c->row, c->row, c->col, t->size.w);
break;
case 1: /* Erase to Left */
- push_empty_cell_rect(t, c->row, c->row, 0, c->col);
+ fb_clear_region(t, c->row, c->row, 0, c->col);
break;
case 2: /* Erase All */
- push_empty_cell_rect(t, c->row, c->row, 0, t->size.w);
+ fb_clear_region(t, c->row, c->row, 0, t->size.w);
break;
default: ASSERT(0);
}
@@ -193,7 +250,7 @@ set_mode(Term *t, CSI *csi, b32 set)
static void
set_colours(Term *t, CSI *csi)
{
- CursorState *cs = &t->cursor.state;
+ CellStyle *cs = &t->cursor.state;
for (i32 i = 0; i < csi->argc; i++) {
switch (csi->argv[i]) {
case 0: cursor_reset(t); break;
@@ -310,10 +367,12 @@ handle_escape(Term *t, s8 *raw)
/* TODO: MODE_APPKEYPAD */
break;
case 'M': /* RI -- Reverse Index */
- /* TODO: if the cursor is currently at the top of the terminal this
- * needs to shift everything down by one row. Otherwise this just
- * shifts the moves the cursor up one row */
- asm("nop");
+ if (t->cursor.row == 0) {
+ fb_scroll_down(t, 0, 1);
+ } else {
+ cursor_move_to(t, t->cursor.row - 1, t->cursor.col);
+ result = HESC_CURSOR_MOVED;
+ }
break;
default:
fprintf(stderr, "unknown escape sequence: ESC %c (0x%02x)\n", cp, cp);
@@ -395,3 +454,82 @@ split_raw_input_to_lines(Term *t, s8 raw)
t->unprocessed_bytes = 0;
return parsed_lines;
}
+
+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
+push_newline(Term *t)
+{
+ t->cursor.row++;
+ if (t->cursor.row >= t->size.h) {
+ t->cursor.row = t->size.h - 1;
+ fb_scroll_up(t, 0, 1);
+ }
+}
+
+static void
+push_tab(Term *t)
+{
+ u32 col = t->cursor.col;
+ u32 advance = g_tabstop - col % g_tabstop;
+ fb_clear_region(t, t->cursor.row, t->cursor.row, t->cursor.col, t->cursor.col + advance);
+ cursor_step_column(t, advance);
+}
+
+static void
+push_line(Term *t, Line *line)
+{
+ s8 l = line_to_s8(line);
+
+ t->cursor.state = line->cursor_state;
+
+ Cell *c;
+ while (l.len) {
+ /* TODO: handle unicode case */
+ u32 cp = get_ascii(&l);
+ switch (cp) {
+ case 0x1B: handle_escape(t, &l); break;
+ case '\r': t->cursor.col = 0; break;
+ case '\n': push_newline(t); break;
+ case '\t': push_tab(t); break;
+ case '\b':
+ cursor_move_to(t, t->cursor.row, t->cursor.col - 1);
+ break;
+ default:
+ /* TODO properly make sure characters are printable */
+ CLAMP(cp, ' ', '~');
+ c = &t->fb.rows[t->cursor.row][t->cursor.col];
+ c->cp = cp;
+ c->style = t->cursor.state;
+ /* TODO: properly advance cursor */
+ cursor_step_column(t, 1);
+ }
+ }
+}
+
+static void
+blit_lines(Term *t)
+{
+ size line_count = t->size.h;
+ if (line_count > t->log_lines.filled)
+ line_count = 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;
+
+ /* TODO: Performance!!! */
+ fb_clear_region(t, 0, t->size.h, 0, t->size.w);
+
+ /* NOTE: for now we assume that we blit the whole screen everytime */
+ t->cursor.row = 0;
+ t->cursor.col = 0;
+ for (size i = 0; i <= line_count; i++) {
+ push_line(t, t->log_lines.buf + line_off + i);
+ }
+}
diff --git a/util.h b/util.h
@@ -89,12 +89,29 @@ enum CellAttr {
typedef struct {
Colour fg, bg;
- enum CellAttr attr;
-} CursorState;
+ u32 attr;
+} CellStyle;
+
+typedef struct {
+ u32 cp;
+ CellStyle style;
+} Cell;
+
+typedef Cell *Row;
+
+typedef struct {
+ Cell *cells;
+ size cells_count;
+ size cells_alloc_size;
+
+ Row *rows;
+ size rows_count;
+ size rows_alloc_size;
+} Framebuffer;
typedef struct {
u32 row, col;
- CursorState state;
+ CellStyle state;
} Cursor;
/* NOTE: virtual memory ring buffer */
@@ -108,7 +125,7 @@ typedef struct {
typedef struct {
u8 *start, *end;
b32 has_unicode;
- CursorState cursor_state;
+ CellStyle cursor_state;
} Line;
typedef struct {
@@ -141,7 +158,6 @@ typedef struct {
X(vertscale)
enum gl_flags {
- DISCARD_BUFFER = 1 << 0,
UPDATE_RENDER_UNIFORMS = 1 << 29,
UPDATE_POST_UNIFORMS = 1 << 30,
};
@@ -217,6 +233,8 @@ typedef struct {
LineBuf log_lines;
size unprocessed_bytes;
+ Framebuffer fb;
+
os_child child;
uv2 size;
@@ -225,8 +243,6 @@ typedef struct {
FT_Library ftlib;
} Term;
-static void push_empty_cell_rect(Term *, u32 minrow, u32 maxrow, u32 mincol, u32 maxcol);
-
#include "config.h"
#include "font.c"
#include "terminal.c"
diff --git a/vtgl.c b/vtgl.c
@@ -34,22 +34,11 @@ clear_colour(void)
}
static void
-resize(GLCtx *gl)
+resize(Term *t)
{
- v2 ws = gl->window_size;
-
- f32 pmat[4 * 4] = {
- 2.0/ws.w, 0.0, 0.0, -1.0,
- 0.0, 2.0/ws.h, 0.0, -1.0,
- 0.0, 0.0, -1.0, 0.0,
- 0.0, 0.0, 0.0, 1.0,
- };
-
- glViewport(0, 0, ws.w, ws.h);
- glUseProgram(gl->programs[SHADER_RENDER]);
- glUniformMatrix4fv(gl->render.Pmat, 1, GL_TRUE, pmat);
- glUseProgram(gl->programs[SHADER_POST]);
- glUniformMatrix4fv(gl->post.Pmat, 1, GL_TRUE, pmat);
+ v2 ws = t->gl.window_size;
+ os_alloc_framebuffer(&t->fb, t->size.h, t->size.w);
+ os_set_term_size(t->child, t->size.h, t->size.w, ws.w, ws.h);
}
static void
@@ -181,11 +170,10 @@ draw_rectangle(GLCtx *gl, Rect r, Colour colour)
}
static void
-push_cell(Term *t, u32 cp, Rect r, Colour bg, Colour fg)
+push_cell(GLCtx *gl, Cell c, Rect r, f32 font_text_dy)
{
- GLCtx *gl = &t->gl;
Glyph g;
- i32 depth_idx = get_gpu_glyph_index(gl, cp, &g);
+ i32 depth_idx = get_gpu_glyph_index(gl, c.cp, &g);
v2 texscale[2];
texscale[0] = (v2){0};
@@ -205,21 +193,19 @@ push_cell(Term *t, u32 cp, Rect r, Colour bg, Colour fg)
v2 texture_position;
texture_position.x = r.pos.x + g.delta.x;
- texture_position.y = r.pos.y + g.delta.y + t->fa.deltay;
+ texture_position.y = r.pos.y + g.delta.y + font_text_dy;
vertscale[1] = (v2){.x = g.size.w, .y = g.size.h};
vertoff[1] = texture_position;
- u32 attr = t->cursor.state.attr;
-
- if (attr & ATTR_FAINT)
- fg.a = 0.5 * 255;
-
u32 colours[4];
- colours[0] = (attr & ATTR_INVERSE)? bg.rgba : fg.rgba;
- colours[1] = (attr & ATTR_INVERSE)? fg.rgba : bg.rgba;
- colours[2] = (attr & ATTR_INVERSE)? bg.rgba : fg.rgba;
- colours[3] = (attr & ATTR_INVERSE)? fg.rgba : bg.rgba;
+ colours[0] = (c.style.attr & ATTR_INVERSE)? c.style.bg.rgba : c.style.fg.rgba;
+ colours[1] = (c.style.attr & ATTR_INVERSE)? c.style.fg.rgba : c.style.bg.rgba;
+ colours[2] = (c.style.attr & ATTR_INVERSE)? c.style.bg.rgba : c.style.fg.rgba;
+ colours[3] = (c.style.attr & ATTR_INVERSE)? c.style.fg.rgba : c.style.bg.rgba;
+
+ if (c.style.attr & ATTR_FAINT)
+ c.style.fg.a = 0.5 * 255;
glUniform2uiv(gl->render.texcolour, 2, colours);
glUniform1iv(gl->render.charmap, 2, charmap);
@@ -230,79 +216,19 @@ push_cell(Term *t, u32 cp, Rect r, Colour bg, Colour 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
-push_tab(Term *t)
+render_framebuffer(Term *t)
{
- u32 col = t->cursor.col;
- u32 advance = g_tabstop - col % g_tabstop;
- cursor_step_column(t, advance);
-}
-
-static void
-push_line(Term *t, Line *line, v2 start_pos)
-{
- s8 l = line_to_s8(line);
-
- t->cursor.state = line->cursor_state;
-
v2 cs = get_cell_size(t);
- Rect cr = {.pos = start_pos, .size = cs};
-
- while (l.len) {
- /* TODO: handle unicode case */
- u32 cp = get_ascii(&l);
- switch (cp) {
- case 0x1B: handle_escape(t, &l); break;
- case '\r': t->cursor.col = 0; break;
- case '\n': t->cursor.row++; break;
- case '\t': push_tab(t); break;
- case '\b':
- cursor_move_to(t, t->cursor.row, t->cursor.col - 1);
- break;
- default:
- /* TODO properly make characters are printable */
- CLAMP(cp, ' ', '~');
- cr.pos.w = cs.w * t->cursor.col;
- cr.pos.h = t->gl.window_size.h - (t->cursor.row + 1) * cs.h;
- push_cell(t, cp, cr, t->cursor.state.bg, t->cursor.state.fg);
- /* TODO: properly advance cursor */
- cursor_step_column(t, 1);
- }
+ Rect cr = {.pos = { .y = t->gl.window_size.h - cs.h }, .size = cs};
- if (t->gl.flags & DISCARD_BUFFER) {
- t->gl.flags &= ~DISCARD_BUFFER;
- clear_colour();
+ for (u32 r = 0; r < t->size.h; r++) {
+ for (u32 c = 0; c < t->size.w; c++) {
+ push_cell(&t->gl, t->fb.rows[r][c], cr, t->fa.deltay);
+ cr.pos.x += cs.w;
}
- }
-}
-
-static void
-blit_lines(Term *t)
-{
- size line_count = t->size.h - 1;
- CLAMP(line_count, 0, t->log_lines.filled);
- v2 cs = get_cell_size(t);
- /* TODO: handle case where widx has wrapped around */
- ASSERT(t->log_lines.widx >= line_count);
- size line_off = t->log_lines.widx - line_count;
-
- /* NOTE: for now we assume that we blit the whole screen everytime */
- t->cursor.row = 0;
- t->cursor.col = 0;
- for (size i = 0; i <= line_count; i++) {
- v2 pos = {
- .x = t->cursor.col * cs.w,
- .y = t->gl.window_size.h - (t->cursor.row + 1) * cs.h
- };
- push_line(t, t->log_lines.buf + line_off + i, pos);
+ cr.pos.x = 0;
+ cr.pos.y -= cs.h;
}
}
@@ -311,23 +237,36 @@ static void
fb_callback(GLFWwindow *win, i32 w, i32 h)
{
Term *t = glfwGetWindowUserPointer(win);
- t->gl.window_size.w = w;
- t->gl.window_size.h = h;
+ t->gl.window_size = (v2){.w = w, .h = h};
v2 cs = get_cell_size(t);
t->size.w = (u32)(w / cs.w);
t->size.h = (u32)(h / cs.h);
- /* NOTE: The terminal size still needs to have the correct number
- * of rows so that terminal programs know how to size themselves. */
- os_set_term_size(t->child, (uv2){.w = t->size.w, .h = t->size.h}, w, h);
+ /* TODO: Proper padding */
+ //v2 ws = {.x = t->size.w * cs.w, .y = t->size.h * cs.h};
+ //f32 xpad = (w - ws.w) / 2;
+ //f32 ypad = (h - ws.h) / 2;
+ //glViewport(xpad, ypad, ws.w, ws.h);
+ glViewport(0, 0, w, h);
glActiveTexture(GL_TEXTURE0 + t->gl.fb_tex_unit);
glBindTexture(GL_TEXTURE_2D, t->gl.fb_tex);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, t->gl.window_size.w, t->gl.window_size.h,
- 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
- resize(&t->gl);
+ f32 pmat[4 * 4] = {
+ 2.0/w, 0.0, 0.0, -1.0,
+ 0.0, 2.0/h, 0.0, -1.0,
+ 0.0, 0.0, -1.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0,
+ };
+
+ glUseProgram(t->gl.programs[SHADER_RENDER]);
+ glUniformMatrix4fv(t->gl.render.Pmat, 1, GL_TRUE, pmat);
+ glUseProgram(t->gl.programs[SHADER_POST]);
+ glUniformMatrix4fv(t->gl.post.Pmat, 1, GL_TRUE, pmat);
+
+ resize(t);
}
static void
@@ -414,8 +353,11 @@ do_terminal(Term *t, Arena a)
//glUseProgram(t->gl.programs[SHADER_RENDER]);
glUniform1i(t->gl.render.texslot, 0);
glBindFramebuffer(GL_FRAMEBUFFER, t->gl.fb);
+
clear_colour();
+
blit_lines(t);
+ render_framebuffer(t);
v2 cell_size = get_cell_size(t);
v2 cursor_pos = {
@@ -456,6 +398,8 @@ do_terminal(Term *t, Arena a)
p_scale *= -1.0f;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ clear_colour();
glUseProgram(t->gl.programs[SHADER_POST]);
glUniform1i(t->gl.post.texslot, t->gl.fb_tex_unit);
glUniform1f(t->gl.post.param, param);