‹ Term

GPU rendering benchmark

Term’s terminal rendering moved to an EGL/GLES3 instanced renderer — here is the method, the data and the captures, measured on real hardware.

5.3ms
GPU frame time · 188 fps
2.7×
vs. the CPU path
0.2ms
GL draw / frame
Results

Same device (a Huawei Pura 70), same corpus, A/B against the CPU baseline using the built-in Log frame stats developer switch.

Workload CPU path GPU path Speed-up
Full-screen scroll (firehose) 14.6 ms · 68 fps 5.3 ms · 188 fps 2.7×
Slow scroll (~5 rows/frame) 12.8 ms · 76 fps 6.0 ms · 167 fps 2.1×
logFrameStats · Huawei Pura 70 · 3 MB ANSI-SGR + CJK corpus on a loop · 2026-07

GPU frame time is almost independent of how much scrolled — every frame is a full redraw, and that is cheap for a GPU. The CPU path’s sensitivity to the number of scrolled rows simply disappears.

GPU frame breakdown

About 1000 instanced quads per frame. Glyphs are rasterised into a texture atlas once, and every later frame only re-emits mesh instances.

Stage Average What it does
build 2.15 ms CPU side: walk the grid → instance quads + rasterise new glyphs into the atlas + upload
draw 0.22 ms GL instanced draw
swap 2.95 ms eglSwapBuffers (present)
Frame total 5.32 ms ≈ 188 fps
Re-measured 2026-07-17 · after the build micro-optimisations (ASCII slot table, merged bg+glyph walk, allocation-free atlas signature)

Two 14 MB memory shuffles are gone: no more scroll-shifting the shadow bitmap (~6 ms) and no more copy into the dmabuf (~5.6 ms) — that ~11 ms is the entire saving. The GPU rasterises in place, straight onto the EGL surface.

Captures

The corpus is coloured ANSI-SGR mixed with Chinese, Korean and ASCII, written to the session’s TTY by the benchmark script — no need to touch the phone.

Full-screen firehose scroll: a full screen of coloured ANSI plus Chinese/Korean/ASCII scrolling continuously
P1 · Full-screen scroll — continuous scrolling, every frame a total redraw.
Idle: partial presentation frames driven only by the blinking cursor
P2 · Idle — only the cursor blinks; partial presentation.
Single-row updates: typing at 8 Hz with the rows above preserved pixel for pixel
P3 · Single-row update — typing at 8 Hz, rows above preserved pixel for pixel.
Benchmark script

The script automates all three phases (firehose scroll / idle blink / single-row update) by writing to the TTY of a session on the remote host — zero interaction on the phone.

gpubench.shDownload ↓
# corpus: coloured ANSI-SGR + Chinese/Korean/ASCII, 400 rows
corpus() {
  i=0
  while [ $i -lt 400 ]; do
    printf "\033[3%dm%04d 端末描画性能測定 中文渲染基准 가나다라마 \033[1mBOLD\033[0m ascii-abcdefghijklmnop %05d\n" \
      $((i % 8)) $i $((i * 37))
    i=$((i + 1))
  done
}
C="$(corpus)"

# P1 firehose scroll, 30s (full frames, total damage)
end=$((SECONDS + 30))
while [ $SECONDS -lt $end ]; do printf "%s\n" "$C" > "$T"; done

# P2 idle, 60s (blink only → partial presentation frames)
sleep 60

# P3 single-row update, 20s (~8 Hz, typing-shaped partial frames)
end=$((SECONDS + 20))
while [ $SECONDS -lt $end ]; do
  printf "\rtyping simulation %06d" $n > "$T"; n=$((n + 1)); sleep 0.12
done
How to reproduce
  1. Open a session to the target host; copy gpubench.sh there and find the session’s TTY with who -u.
  2. Run ./gpubench.sh /dev/ttysNNN (the session’s tty).
  3. Collect: hdc -t <target> shell hilog | grep "connrender: gpu" — one stats window every 60 frames.

Note: the P2/P3 stats windows run at low DVFS clocks (only 2–9 draws per second keeps the clock at its lowest step), so they are not directly comparable with the firehose numbers — only compare like with like. P1’s first window includes a one-off burst of CJK glyph rasterisation, which is expected.