Back to Projects
TUIX v0.3Beta

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 coordinates based on the current terminal size. In v0.3, buffers with a valid parent_uid are resolved relative to their parent buffer, enabling parent/child layouts.

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. In v0.3, compositing uses traversal-chain layering: root buffers are ordered by z-index and each parent/child subtree is traversed before painting, preserving hierarchical layering.

Hierarchy SafetyInvalid parent links are sanitized during compositing. Self-parent and missing-parent cases are treated safely, and child buffers are reparented safely when their parent is removed.

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.

Scene Instrumentation and Compaction

v0.3 adds scene activity instrumentation and conservative compaction APIs. Instead of eager global caching, the recommended strategy is data-first: read scene stats, then compact cold scenes when thresholds are met.

from tuix.core import scenes

stats = scenes.get_scene_stats(b"main")
freed = scenes.compact_scene_pixels(b"main")
compacted = scenes.compact_cold_scenes(120, 64 * 1024, keep_active_scene=True)

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.