From 2f1c7c82ba729eb39f252e2c4bc77d52fdb8f4fb Mon Sep 17 00:00:00 2001 From: Pavel Butsykin Date: Fri, 27 Feb 2026 00:29:27 +0400 Subject: [PATCH 1/7] Enable -Wall -Wextra -Werror for vtrenderlib Signed-off-by: Pavel Butsykin --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a5caac..400d8c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ project(vtrenderlib C) add_library(vtrenderlib STATIC vtrenderlib.c) +target_compile_options(vtrenderlib PRIVATE -Wall -Wextra -Werror) + # Public include directory for consumers target_include_directories(vtrenderlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) From edb3d7e1c7823927118a3e545b432136b33cb09e Mon Sep 17 00:00:00 2001 From: Pavel Butsykin Date: Fri, 27 Feb 2026 00:33:38 +0400 Subject: [PATCH 2/7] Fix -Werror=sign-compare warning in sendseq The cast to size_t is safe here since res == -1 is checked first. Signed-off-by: Pavel Butsykin --- vtrenderlib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vtrenderlib.c b/vtrenderlib.c index 7af0c18..29a4eca 100644 --- a/vtrenderlib.c +++ b/vtrenderlib.c @@ -261,7 +261,7 @@ struct vtr_canvas* vtr_canvas_create(int ttyfd) static int sendseq(int ttyfd, const char* seq, size_t nbytes) { ssize_t res = write(ttyfd, seq, nbytes); - if (res == -1 || res != nbytes) { + if (res == -1 || (size_t)res != nbytes) { return -1; } From 35be0e54553eb010974149fff1f62ebb16fda1a6 Mon Sep 17 00:00:00 2001 From: Pavel Butsykin Date: Fri, 27 Feb 2026 00:44:10 +0400 Subject: [PATCH 3/7] Remove redundant >= 0 checks on unsigned types in scan_line Comparison is always true due to limited range of data type [-Werror=type-limits]. Signed-off-by: Pavel Butsykin --- vtrenderlib.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vtrenderlib.c b/vtrenderlib.c index 29a4eca..288f5e3 100644 --- a/vtrenderlib.c +++ b/vtrenderlib.c @@ -564,10 +564,10 @@ static float calc_slope(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1) static void scan_line(struct vtr_stencil_buf* sb, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, enum vtr_color fgc) { - assert(x0 >= 0 && x0 < sb->xdots); - assert(x1 >= 0 && x1 < sb->xdots); - assert(y0 >= 0 && y0 < sb->ydots); - assert(y1 >= 0 && y1 < sb->ydots); + assert(x0 < sb->xdots); + assert(x1 < sb->xdots); + assert(y0 < sb->ydots); + assert(y1 < sb->ydots); float m = calc_slope(x0, y0, x1, y1); int hdir = (x0 < x1 ? 1 : -1); From b5b9fdf6f55705f19555e22e8c24373250aaf917 Mon Sep 17 00:00:00 2001 From: Pavel Butsykin Date: Fri, 27 Feb 2026 00:58:52 +0400 Subject: [PATCH 4/7] Annotate intentional switch fallthrough in vtr_trace_polyc Signed-off-by: Pavel Butsykin --- vtrenderlib.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vtrenderlib.c b/vtrenderlib.c index 288f5e3..afef5b4 100644 --- a/vtrenderlib.c +++ b/vtrenderlib.c @@ -761,7 +761,9 @@ int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_ve // Intercepts can repeat if 2 edges meet at current y, ignore that switch(ncepts) { case 2: if (xcepts[1] == xcept) continue; + /* fallthrough */ case 1: if (xcepts[0] == xcept) continue; + /* fallthrough */ case 0: break; } From 9c6667dd48cc61e071d9cc6214492f4b3862803e Mon Sep 17 00:00:00 2001 From: Pavel Butsykin Date: Fri, 27 Feb 2026 01:10:00 +0400 Subject: [PATCH 5/7] Fix PTY slave/master typo in escape sequence data flow comment Signed-off-by: Pavel Butsykin --- vtrenderlib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vtrenderlib.c b/vtrenderlib.c index afef5b4..5245a57 100644 --- a/vtrenderlib.c +++ b/vtrenderlib.c @@ -18,8 +18,8 @@ // - VT/xterm escape sequences work in the same manner: // 1. Shell/app: 0x1B 0x5B 0x33 0x31 0x6D # ESC [ 3 1 m → “set foreground color to red” -> written to PTY slave via STDOUT_FILENO // 2. Kernel PTY layer: handles line discipline, raw mode (bytes pass through unchanged) vs canonical mode (control chars handled), -// forwards data to PTY slave. -// 3. Terminal emulator: reads data from PTY slave, recognizes `ESC [ 31 m`, changes rendering color, paints to the screen. +// forwards data to PTY master. +// 3. Terminal emulator: reads data from PTY master, recognizes `ESC [ 31 m`, changes rendering color, paints to the screen. // // From 22e07992831337b6472c9cf48b0ef9016df18eaa Mon Sep 17 00:00:00 2001 From: Pavel Butsykin Date: Fri, 27 Feb 2026 17:55:54 +0400 Subject: [PATCH 6/7] =?UTF-8?q?Fix=20unused=20parameter=20=E2=80=98seqcap?= =?UTF-8?q?=E2=80=99=20for=20NDEBUG=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pavel Butsykin --- vtrenderlib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vtrenderlib.c b/vtrenderlib.c index 5245a57..b25cfa4 100644 --- a/vtrenderlib.c +++ b/vtrenderlib.c @@ -406,7 +406,7 @@ static size_t set_pos_s(char* seq, size_t seqcap, uint16_t row, uint16_t col) return nwritten; } -static size_t draw_cell_s(char* seq, size_t seqcap, uint8_t mask) +static size_t draw_cell_s(char* seq, __attribute__((unused)) size_t seqcap, uint8_t mask) { assert(seqcap >= 3); @@ -417,7 +417,7 @@ static size_t draw_cell_s(char* seq, size_t seqcap, uint8_t mask) return 3; } -static size_t set_foreground_color_s(char* seq, size_t seqcap, uint8_t fgc) +static size_t set_foreground_color_s(char* seq, __attribute__((unused)) size_t seqcap, uint8_t fgc) { assert(fgc >= VTR_COLOR_DEFAULT && fgc < VTR_COLOR_TOTAL); assert(seqcap >= 5); @@ -431,7 +431,7 @@ static size_t set_foreground_color_s(char* seq, size_t seqcap, uint8_t fgc) return 5; } -static size_t put_char_s(char* seq, size_t seqcap, char chr) +static size_t put_char_s(char* seq, __attribute__((unused)) size_t seqcap, char chr) { assert(seqcap >= 1); seq[0] = chr; From 4a909106885d4b0078bc60783a8f2e5f23eb249a Mon Sep 17 00:00:00 2001 From: Pavel Butsykin Date: Fri, 27 Feb 2026 01:25:11 +0400 Subject: [PATCH 7/7] Add 256 color indexed palette support Use the 256-color escape sequence ESC[38;5;INDEXm for all foreground colors, replacing the legacy SGR 30-37 path. The existing enum vtr_color values remain valid because the standard ANSI colors 0-7 share the same palette indices in the 256-color mode. VTR_COLOR_DEFAULT still emits ESC[39m to reset to the terminal default color. Palette indices are stored as idx+1 in vtr_fgcolor_t since value 0 is reserved for VTR_COLOR_DEFAULT. This means index 255 would overflow uint8_t, so it's mapped to VTR_COLOR_WHITE (the closest color match). Signed-off-by: Pavel Butsykin --- demos/boids/main.c | 4 ++-- vtrenderlib.c | 55 +++++++++++++++++++++++++++++++++------------- vtrenderlib.h | 19 ++++++++++++---- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/demos/boids/main.c b/demos/boids/main.c index 98cdc37..7e77680 100644 --- a/demos/boids/main.c +++ b/demos/boids/main.c @@ -96,7 +96,7 @@ struct vt_boid int cur_heading_time; // rendering data - enum vtr_color color; + vtr_fgcolor_t color; struct vec2f trail[VT_BOID_TRAIL_SIZE]; size_t trail_idx; size_t trail_len; @@ -241,7 +241,7 @@ static void debug_print(uint16_t row, uint16_t col, const char* fmt, ...) va_end(args); } -static void debug_vec(struct vec2f origin, struct vec2f vec, float scale, enum vtr_color fgc) +static void debug_vec(struct vec2f origin, struct vec2f vec, float scale, vtr_fgcolor_t fgc) { struct vtr_vertex start = vec2f_project(origin); struct vtr_vertex end = vec2f_project(vec2f_mul_add(origin, vec, scale)); diff --git a/vtrenderlib.c b/vtrenderlib.c index b25cfa4..1daa098 100644 --- a/vtrenderlib.c +++ b/vtrenderlib.c @@ -121,10 +121,13 @@ #define VT_CELL_YDOTS ((uint16_t)4) #define VT_CELL_XDOTS ((uint16_t)2) -// We preallocate enough space to hold the biggest number of draw calls (3 + 6 colored bytes per escape seq) +// We preallocate enough space to hold the biggest number of draw calls (3 + 11 colored bytes per escape seq) // plus some slack to hold a cursor reset commands. #define VT_MIN_SEQLIST_SLACK ((size_t)64) -#define VT_SEQLIST_BUFFER_SIZE(rows, cols) MAX((size_t)((rows + 1) * cols + 1) * 9, VT_MIN_SEQLIST_SLACK) +#define VT_COLOR_SEQ_MAXLEN ((size_t)11) +#define VT_DRAW_SEQ_LEN ((size_t)3) +#define VT_CELL_SEQS_MAX (VT_COLOR_SEQ_MAXLEN + VT_DRAW_SEQ_LEN) +#define VT_SEQLIST_BUFFER_SIZE(rows, cols) MAX((size_t)((rows + 1) * cols + 1) * VT_CELL_SEQS_MAX, VT_MIN_SEQLIST_SLACK) struct vtr_stencil_buf { @@ -417,18 +420,40 @@ static size_t draw_cell_s(char* seq, __attribute__((unused)) size_t seqcap, uint return 3; } +static size_t u8_to_dec(char* dst, uint8_t v) +{ + if (v >= 100) { + dst[0] = '0' + v / 100; + dst[1] = '0' + (v / 10) % 10; + dst[2] = '0' + v % 10; + return 3; + } + if (v >= 10) { + dst[0] = '0' + v / 10; + dst[1] = '0' + v % 10; + return 2; + } + dst[0] = '0' + v; + return 1; +} + static size_t set_foreground_color_s(char* seq, __attribute__((unused)) size_t seqcap, uint8_t fgc) { - assert(fgc >= VTR_COLOR_DEFAULT && fgc < VTR_COLOR_TOTAL); - assert(seqcap >= 5); + assert(seqcap >= VT_COLOR_SEQ_MAXLEN); + + if (fgc == VTR_COLOR_DEFAULT) { + memcpy(seq, "\x1b[39m", 5); + return 5; + } + const char fgc256[] = "\x1b[38;5;"; + size_t fgc256_len = sizeof(fgc256) - 1; - seq[0] = 0x1b; - seq[1] = '['; - seq[2] = '3'; - seq[3] = (fgc == VTR_COLOR_DEFAULT ? '9' : '0' + fgc - 1); - seq[4] = 'm'; + memcpy(seq, fgc256, fgc256_len); + size_t n = u8_to_dec(&seq[fgc256_len], fgc - 1); + seq[fgc256_len + n] = 'm'; - return 5; + assert(fgc256_len + n + 1 <= VT_COLOR_SEQ_MAXLEN); + return fgc256_len + n + 1; } static size_t put_char_s(char* seq, __attribute__((unused)) size_t seqcap, char chr) @@ -517,7 +542,7 @@ int vtr_swap_buffers(struct vtr_canvas* vt) return sendseq(vt->fd, vt->seqlist, seqlen); } -static void render_dot(struct vtr_stencil_buf* sb, uint16_t x, uint16_t y, enum vtr_color fgc) +static void render_dot(struct vtr_stencil_buf* sb, uint16_t x, uint16_t y, vtr_fgcolor_t fgc) { assert(x < sb->xdots && y < sb->ydots); @@ -546,7 +571,7 @@ void vtr_render_dot(struct vtr_canvas* vt, int x, int y) vtr_render_dotc(vt, x, y, VTR_COLOR_DEFAULT); } -void vtr_render_dotc(struct vtr_canvas* vt, int x, int y, enum vtr_color fgc) +void vtr_render_dotc(struct vtr_canvas* vt, int x, int y, vtr_fgcolor_t fgc) { if (point_test(vt, x, y)) { render_dot(vt->cur_sb, x, y, fgc); @@ -562,7 +587,7 @@ static float calc_slope(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1) return (x1 == x0 ? INFINITY : ((float)y1 - y0) / ((float)x1 - x0)); } -static void scan_line(struct vtr_stencil_buf* sb, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, enum vtr_color fgc) +static void scan_line(struct vtr_stencil_buf* sb, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, vtr_fgcolor_t fgc) { assert(x0 < sb->xdots); assert(x1 < sb->xdots); @@ -637,7 +662,7 @@ void vtr_scan_line(struct vtr_canvas* vt, int x0, int y0, int x1, int y1) vtr_scan_linec(vt, x0, y0, x1, y1, VTR_COLOR_DEFAULT); } -void vtr_scan_linec(struct vtr_canvas* vt, int x0, int y0, int x1, int y1, enum vtr_color fgc) +void vtr_scan_linec(struct vtr_canvas* vt, int x0, int y0, int x1, int y1, vtr_fgcolor_t fgc) { int dx = x1 - x0, dy = y1 - y0; int xmin = 0, ymin = 0; @@ -688,7 +713,7 @@ int vtr_trace_poly(struct vtr_canvas* vt, size_t nvertices, const struct vtr_ver return vtr_trace_polyc(vt, nvertices, vlist, VTR_COLOR_DEFAULT); } -int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vlist, enum vtr_color fgc) +int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vlist, vtr_fgcolor_t fgc) { assert(vt); assert(vlist); diff --git a/vtrenderlib.h b/vtrenderlib.h index cb3552d..477efe2 100644 --- a/vtrenderlib.h +++ b/vtrenderlib.h @@ -77,27 +77,38 @@ enum vtr_color VTR_COLOR_CYAN, VTR_COLOR_WHITE, - VTR_COLOR_TOTAL // always last + VTR_COLOR_SGR8_TOTAL // always last }; +/* + * Foreground color type. Accepts enum vtr_color or VTR_COLOR256(idx) values. + */ +typedef uint8_t vtr_fgcolor_t; + +/* + * 256-color palette (ITU-T T.416 / ISO/IEC 8613-6). + * Index 255 (light grey) is mapped to VTR_COLOR_WHITE since 0 is reserved for VTR_COLOR_DEFAULT. + */ +#define VTR_COLOR256(_idx) ((vtr_fgcolor_t)((_idx) >= 255 ? VTR_COLOR_WHITE : (_idx) + 1)) + /** * Render a dot at given VT coordinates. */ void vtr_render_dot(struct vtr_canvas* vt, int x, int y); -void vtr_render_dotc(struct vtr_canvas* vt, int x, int y, enum vtr_color fgc); +void vtr_render_dotc(struct vtr_canvas* vt, int x, int y, vtr_fgcolor_t fgc); /** * Scan a line given two dot coordinates. */ void vtr_scan_line(struct vtr_canvas* vt, int x0, int y0, int x1, int y1); -void vtr_scan_linec(struct vtr_canvas* vt, int x0, int y0, int x1, int y1, enum vtr_color fgc); +void vtr_scan_linec(struct vtr_canvas* vt, int x0, int y0, int x1, int y1, vtr_fgcolor_t fgc); /** * Trace a polygon path given a list of vertices. * The last vertex is traced back to the first one and the resulting polygon filled. */ int vtr_trace_poly(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vertexlist); -int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vertexlist, enum vtr_color fgc); +int vtr_trace_polyc(struct vtr_canvas* vt, size_t nvertices, const struct vtr_vertex* vertexlist, vtr_fgcolor_t fgc); /** * Print some text at the specified location.