todo-app/docs/DOCS-desktop-performance-findings.md
Krzysztof kuhy Rudnicki 59ee407d80
Some checks failed
file-length / file length <= 250 lines (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
release-apk / release (APK) (push) Has been cancelled
Adopt the four markdown namespaces and gate them
Renames this repo's markdown onto README.md / CLAUDE*.md / DOCS*.md /
TODO*.md so a future session can tell a live task from a finished
record without reading the file to find out.

TODO files carry the literal "REMOVE ME AFTER FINISH" and are deleted
when the work lands. Enforced by the md-naming pre-commit hook and CI
workflow, both delegating to the shared gate in kuhyx/utils so this
repo's idea of a valid name cannot drift from every other repo's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A47aX5DJwmA3QFn3pkEWi7
2026-08-24 15:54:35 +02:00

3.7 KiB

Linux desktop performance: measured findings

Investigation date: 2026-07-20. Machine: Arch, X11 + i3, NVIDIA proprietary 610.43.03, RTX 3090. DP-0 = 3840x2160 @144 Hz (primary), HDMI-0 = 2560x1440 @144 Hz. Flutter 3.44.6 stable.

Conclusion

The previous session was right: Flutter's Linux desktop embedder cannot drive this machine's 4K display, and switching technology for the desktop app is justified.

Two independent defects, neither fixable in application code:

  1. Hard 60 FPS cap. Flutter's Linux embedder schedules frames at a fixed 60 FPS regardless of the monitor (upstream #183703, P2, no PR).
  2. ~20 FPS at 4K. Far worse, and the actual cause of the reported lag. At 3840x2160 the app achieves 18.6 fps while each frame costs only ~3 ms of a 16.7 ms budget. The stock flutter create counter app behaves identically, so this is the toolkit, not todo.

Application code is not implicated anywhere: build (Dart) time is 0.1-0.4 ms in every configuration and at every window size measured.

An earlier revision of this document concluded that a rewrite was not warranted. That conclusion was based only on defect 1 and is superseded by the 4K measurements below.

Evidence

Measured with a temporary in-app instrument (lib/frame_stats.dart, armed by TODO_FRAME_STATS=1) reporting frame-to-frame cadence alongside build/raster times, driven by a perpetual animation so frames are produced continuously.

Configuration Cadence p50 Achieved build p50 raster p50
Release, Skia GL (default) 16.67 ms 60.0 fps 0.30 ms 1.50 ms
Release, Impeller 16.67 ms 60.0 fps 0.19 ms 1.33 ms
Release, Impeller + Vulkan 16.67 ms 60.0 fps 0.26 ms 1.33 ms
Driver vsync off (__GL_SYNC_TO_VBLANK=0) 16.67 ms 60.0 fps 0.28 ms 1.40 ms
picom compositor + Skia GL 16.67 ms 60.0 fps 0.27 ms 1.62 ms
picom compositor + Impeller 16.67 ms 60.0 fps 0.30 ms 1.44 ms
Stock flutter create app (no todo code) 16.67 ms 60.0 fps 0.12 ms 1.35 ms
Same Flutter code as a web build, in Chrome 7.00 ms 142.9 fps — —

Retracted: an earlier revision of this table had a "real typing + scrolling" row. It was invalid. It used xdotool type --window, which sends synthetic XSendEvent keystrokes that GTK/Flutter ignore; the keystrokes actually landed in a different application. That run measured an idle app. Interaction is measured properly in the next section instead.

Confounds on the web row, stated for honesty: it ran on HDMI-0 (1440p@144) with a game occupying the 4K GPU, which is why its p95 was 13.90 ms. The desktop 60.0 fps lock, by contrast, is clean, jitter-free and monitor-independent, so the comparison's conclusion holds — but the web number should be re-confirmed on the 4K monitor with the GPU idle.

Key readings:

  • Cadence p50 equals p95 at exactly 16.67 ms in every desktop run — a hard lock with zero jitter, not contention.
  • Total per-frame work is ~1.8 ms against a 6.94 ms 144 Hz budget. The renderer has 74% headroom at 144 Hz and 89% at 60 Hz.
  • Under real typing and scrolling, one frame exceeded the 144 Hz budget in 14 seconds and zero exceeded 60 Hz. There is no dropped-frame jank.
  • The stock counter app behaves identically, so this is not todo's code.
  • Neither renderer choice, nor driver vsync, nor a compositor moves the number.

Part 2 — the interaction-lag half of this investigation, and the decision that came out of it — is in desktop-performance-interaction-lag.md.