Back to Projects
TUIX v0.2.0Beta

Last Updated: 2026-05-20

Rendering Pipeline

TUIX renders terminal UI through a multi-stage pipeline: geometry resolution, content building, compositing, and delta rendering. All stages run in C for maximum throughput.

Pipeline Stages

  1. Geometry Resolution — multiply proportional modifiers by terminal dimensions
  2. Content Building — each widget's build_content produces a pixel buffer
  3. Compositing — layer all scene buffers onto the final framebuffer (painter's order)
  4. Delta Rendering — hash rows, skip unchanged, emit only modified ANSI output

Geometry Resolution

Before building content, the geometry resolver scales each widget's float modifiers to absolute pixel coordinates based on the current terminal size. This runs every frame, so widgets automatically adapt to terminal resizing.

buffer.width       = terminal_width  × width_mod
buffer.height      = terminal_height × height_mod
buffer.margin_left = terminal_width  × margin_left_mod
buffer.margin_top  = terminal_height × margin_top_mod

If a widget's builder defines an on_resize callback, it is called after geometry resolution with the new width and height.

Pixel Model

Every cell in a buffer is a TuixPixel containing: an 8-byte UTF-8 symbol, foreground and background RGB colors, style flags (bold, italic, underline, dim), and quantization cache fields.

FieldSizeDescription
sym8 bytesUTF-8 character (padded with null bytes)
styles.fg3 bytesForeground RGB (0–255 per channel)
styles.bg3 bytesBackground RGB (0–255 per channel)
styles.bold1 byteBold text flag
styles.italic1 byteItalic text flag
styles.underlined1 byteUnderline text flag
styles.dim1 byteDim text flag
q_fg, q_bg6 bytesQuantized color cache (RGB565)
q_cached1 byteWhether quantized cache is valid

Compositing

The compositor allocates a final framebuffer sized to terminal_width × (terminal_height - 1). The bottom row is reserved for log output. It fills the buffer with blank pixels (white space on black background), then iterates through the active scene's buffers in creation order. For each buffer, it clips to screen bounds and copies pixels into the final buffer via memcpy.

Delta Rendering

The renderer compares the current frame against the previous frame to skip unchanged content:

  1. Hash each row using FNV-1a on the raw (unquantized) pixel data
  2. Compare against the previous frame's row hashes
  3. Only process rows with changed hashes
  4. For changed rows, quantize colors (RGB → RGB565 via a 128KB precomputed LUT)
  5. Group consecutive pixels with identical styles into a single SGR escape sequence
  6. Emit cursor positioning (\e[Y;XH) only when needed (skip for sequential cells)
  7. Stream output in 256KB chunks

Color Output Modes

The color quantization LUT maps RGB565 values to the closest available terminal color. Three output modes are used automatically based on the best match:

ModeFormatColors
ANSI16\e[30-37m / \e[90-97m16 basic terminal colors
ANSI256\e[38;5;Nm256-color palette
Truecolor\e[38;2;R;G;BmFull 24-bit RGB (16.7M colors)

Halfblock Rendering (Optional Module)

The source distribution includes a halfblock renderer (rendering_halfblocks.c) that uses the Unicode upper half block character (▀ U+2580) to achieve 2× vertical resolution. Two pixel rows are packed into one terminal row by using the foreground color for the top half and the background color for the bottom half.

Optional ModuleThe halfblock renderer is not compiled into the default build. Advanced users can include it by adding rendering_halfblocks.c to their custom build configuration.

Resize Handling

Terminal dimensions are read every frame via platform-specific APIs (ReadConsoleScreenBufferInfo on Windows, ioctl TIOCGWINSZ on POSIX). When a size change is detected, the compositor reallocates its static final buffer, all widget geometries are recalculated, and a full redraw is triggered.