Theme-row repaint as cached card sprites, 2026-10-12
Task 14.4 follow-up in change the-shell-swaps-themes-without-a-python-stall.
The operator reported, on the panel, that “it’s really janky moving between theme cards and it shouldn’t be”. This directory records the board measurements behind the fix, the install, and the operator’s response.
The source is branch perf/theme-row-paint, in worktree
/home/jadams/tmp/k230-row-paint. The commits are:
f11c86fe: spinner pulses and thumbnail arrivals repaint only the picker rows;4d17b3c5: carousel cards are drawn from cached sprites cut by an A8 mask.
Times are UTC.
Evidence classes:
- software-injected swipes over SSH, with counters-only runtime traces;
- one
perf record -e cpu-clockprofile on the board; - one native
grimcapture; - coherent-installer serial checks;
- one operator report after using the panel by finger.
No camera observation is claimed.
Measurement
swap-and-swipe-used.py ran as root over SSH. For each run it does the following:
- Points
shell-uiat a given shell ELF through a non-persistent/rundrop-in. - Sets
K230_TRACE_COUNTERS_ONLY=1with a 55 s window. - Opens Settings → Themes.
- Injects eight horizontal swipes on the theme row through a temporary
K230 injected touchscreen.
Runs that left an empty trace are not counted. Those runs overlapped a restart and were repeated.
| Label | Shell ELF | Trace files |
|---|---|---|
| old | installed k2z9z61k… (master 24804e19) | old-3, old-4, old-5 |
| first fix | ljrsf4… (f11c86fe) | new-2, new-3, new-4 |
| sprite | b0yyyda8… (4d17b3c5) | sprite-1, sprite-2 |
The table shows picker_row_repaint in the moving phase, plus
scene_rebuild counts in the held and moving phases. All traces report
dropped: 0.
| Run | Repaints | CPU / repaint | Wall / repaint | Max wall | Full rebuilds |
|---|---|---|---|---|---|
| old-3 | 49 | 81.0 ms | 98.6 ms | 125.9 ms | 6 |
| old-4 | 51 | 79.4 ms | 101.0 ms | 124.7 ms | 6 |
| old-5 | 51 | 79.8 ms | 101.7 ms | 131.5 ms | 6 |
| new-2 | 46 | 75.1 ms | 120.2 ms | 246.4 ms | 0 |
| new-3 | 54 | 74.6 ms | 95.5 ms | 142.9 ms | 0 |
| new-4 | 44 | 77.1 ms | 122.3 ms | 225.6 ms | 0 |
| sprite-1 | 91 | 30.1 ms | 42.9 ms | 66.1 ms | 0 |
| sprite-2 | 92 | 30.4 ms | 43.5 ms | 87.4 ms | 0 |
The first fix removed the mid-swipe full rebuilds but barely changed the cost of each row repaint.
A board profile of the first-fix build (1,736 cpu-clock samples) showed:
memsetat 24.6%, of which 13.4% was underpaint_carousel’s path fill throughgeneral_composite_rect;- pixman bilinear fetches at about 17%.
On this RISC-V pixman, a clipped or anti-aliased fill whose source is a scaled surface takes the general per-span path. The sprite commit instead renders each card once into a cached surface and blits it untransformed. The scaled image, the scrim and the skewed rounded outline mask are all baked into that surface.
The sprite build roughly halves the repaint wall time and draws about 1.8 times as many frames during the same swipes. It is not 60 fps: the two cards that change size during a swipe are re-rendered each frame.
sprite-build-picker-native.png is a native
grim -s 0.5 capture of the sprite build at rest. The theme cards, the
Backgrounds row and the labels draw correctly.
The kanagawa theme shown there was applied durably at 08:11:43 through the
theme socket, during one of the old-build runs. It was either an operator tap
or a stray injected tap. It was left in place and is the baseline appearance
of the install below.
Install
| Identity | Value |
|---|---|
| Bundle | /nix/store/60azv75vjpgcshjwh9kdhz12h4r9i8d0-k230-coherent-shell-boot-files |
| System | /nix/store/rav09qp6dwsnbjd2jrbcs595g9ayn3xf-nixos-system-nixos-26.11.20260919.20b1ddd |
| Rust shell ELF | /nix/store/b0yyyda8l7nqix1jb95kbmhh9s5mqnj0-k230-shell-rust-riscv64-unknown-linux-gnu-0.1.0/bin/k230-shell-rust |
Kernel, Image, initrd.uimg | unchanged from the baseline |
| Baseline | default-backgrounds install cwfx0amr… (../default-backgrounds-2026-10-12/) |
The build ran with
flock /tmp/k230-nix-build.lock nix build .#kernelMainlineDrmShellBootFiles --out-link ~/.local/state/tdisplay-k230/retained-builds/row-paint-2026-10-12/candidate2 --print-out-paths --max-jobs 1 --cores 4
at 4d17b3c5 and exited 0.
The /run drop-in was removed, and shell-ui was restarted on the installed
k2z9z61k… before the preflight.
The coherent installer then ran with the default-backgrounds recipes. Only the
paths, the baseline, and the saved kanagawa appearance in
expected-runtime.json differ (see the *-used.py
files). Every step passed:
- Preflight: boot
a563b9fb-…, 14:37:47 (preflight.json). - Stage: (stage.log, transfer.json, staged-state.json, host-inspection.json).
- Candidate boot: (candidate-boot.json). The
runtime check ran on boot
777f5a20-…at 14:42:40 (candidate-runtime.json). - Qualification: (deployment-authorization.json).
- Install and ordinary autoboot: (ordinary-boot.json).
The runtime check ran on boot
49fb3850-…at 14:45:55 (ordinary-runtime.json). The current system and profile are bothrav09qp6…, the four shell services are active, and the saved appearance is unchanged.
The operator tried the build through the /run drop-in, before this install.
No finger observation was made on the installed boot.
Operator report
After using the sprite build on the panel (temporarily swapped in through
the /run drop-in), the operator wrote: “it’s good to go land it”.
This is a real-finger acceptance of the theme-row feel for landing. It does not separately state the four curve properties that task 18.4 names, and it does not cover the matched comparison of tasks 11.3 and 13.