gallery/scripts/jank_ab.sh
Greg Pomerantz ed52109dba GPU-resident thumbnails: worker uploads to Slint's wgpu device (gputex)
Root cause of scroll jank: first-present of each Image uploaded pixels
inside the render frame (~11-30 ms per 400px texture, 1.3 visible
hitches/s at 120 Hz; grey-image A/B: 20 -> 1). Slint has no render
thread and no pre-upload API.

Fix: decode worker creates the Rgba8Unorm texture on Slint's own wgpu
device (captured from GraphicsAPI::WGPU30 at RenderingSetup) and submits
the async upload; UI wraps it in Image::try_from(tex). Zero pixel work
in the render frame. CPU from_rgba8 fallback retained.

Measured (Pixel 9 Pro 120 Hz, 400 px, per-frame FR hook):
  before: >10ms renders 20 in 15s (max 30 ms), 3 borderline 8-9 ms
  after:  0 mid-scroll renders > 4.4 ms (median 1.9, p95 2.9);
          only post-idle first-frames expensive (invisible)

Also: FR per-frame logging via set_rendering_notifier; APLY timestamp.
2026-10-28 10:57:15 -04:00

93 lines
3.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# One-shot jank A/B for the phone (usage: ADB_SERIAL=10.0.0.5:5555 ./scripts/jank_ab.sh)
#
# What it does:
# 1. Installs the release APK (build with ./scripts/build.sh aarch64 first).
# 2. Verifies thread priorities (thumb-* at nice 2, slint vsync at 0).
# 3. Runs TWO 30-second scroll windows — real textures, then grey 1x1
# images — with logcat capture + cadence analysis in between.
# THE USER MUST SCROLL WITH A FINGER during each window
# (moderate drag, no fling — the reported repro).
# 4. Prints a comparison: % of scroll events on the 8.3 ms grid,
# stall count (gap > 16 ms), worst gaps.
#
# Interpretation:
# real janky + grey smooth -> texture/GPU path is the culprit
# both janky -> base render/present/event path
# both smooth -> the priority fix worked; ship it
set -u
E="adb ${ADB_SERIAL:+-s $ADB_SERIAL}"
APK=target/release/apk/gallery.apk
PKG=wow.gmp.gallery
CMD=/sdcard/Download/.gallery_cmd
GREY=/sdcard/Download/.gallery_grey
PERF=/sdcard/Download/.gallery_perf.log
[ -f "$APK" ] || { echo "build first: ./scripts/build.sh aarch64"; exit 1; }
scroll_window() { # $1 = label, $2 = tag, $3 = seconds
local out="/tmp/jank_$2.txt"
$E shell "echo -n '' > $PERF" 2>/dev/null || true
echo "### $1 — scroll now (moderate drag, no fling)..."
sleep "$3"
$E pull "$PERF" "$out" >/dev/null 2>&1
python3 - "$out" << 'EOF'
import re, sys
ev = []
for line in open(sys.argv[1], errors="ignore"):
m = re.match(r"SCR +([\d.]+)", line)
if m: ev.append(float(m.group(1)))
if len(ev) < 10:
print(f" {sys.argv[1]}: only {len(ev)} scroll events — not enough data"); sys.exit()
gaps = [ev[i+1]-ev[i] for i in range(len(ev)-1)]
# drop the first gap (window edge) and any multi-event same-ms catch-ups
# are kept — they ARE the jank signature
gaps = gaps[1:]
on_grid = sum(1 for g in gaps if 6.5 <= g <= 10.5)
stalls = [g for g in gaps if g > 16]
import collections
buckets = collections.Counter()
for g in sorted(stalls): buckets['16-25' if g<25 else '25-40' if g<40 else '40-65' if g<65 else '>65'] += 1
print(f" events={len(ev)} on ~8ms grid={100*on_grid/len(gaps):.0f}% "
f"stalls(>16ms)={len(stalls)} p95={sorted(gaps)[int(len(gaps)*0.95)]:.1f}ms "
f"worst={max(gaps):.1f}ms stall buckets: {dict(buckets)}")
# tick-stall context: how many TICK stalls, any APLY nearby
EOF
echo " TICK-stall lines: $(grep -c '^TICK' $out 2>/dev/null) APLY lines: $(grep -c '^APLY' $out 2>/dev/null)"
}
check_prios() {
local pid; pid=$($E shell pidof $PKG)
echo "### thread priorities (pid $pid)"
$E shell "for t in /proc/$pid/task/*; do c=\$(cat \$t/comm 2>/dev/null); case \$c in slint*|thumb*) n=\$(awk -F')' '{print \$2}' \$t/stat | awk '{print \$17}'); echo \" \$c nice=\$n\";; esac; done"
}
install_and_start() { # $1 = "grey" to enable the grey knob before launch
$E install -r "$APK" >/dev/null
if [ "${1:-}" = grey ]; then $E shell "touch $GREY"; else $E shell "rm -f $GREY"; fi
$E shell am force-stop $PKG
$E shell am start -n $PKG/android.app.NativeActivity >/dev/null
sleep 8
}
echo "=== installing $APK ==="
install_and_start
check_prios
echo
echo "=== window 1: REAL textures (30 s) ==="
scroll_window "real" real 30
echo
echo "=== restarting with grey 1x1 images ==="
install_and_start grey
check_prios
echo
echo "=== window 2: GREY images (30 s) ==="
$E logcat -d 2>/dev/null | grep "grey images ON" || echo " WARNING: grey knob not active!"
scroll_window "grey" grey 30
$E shell "rm -f $GREY $CMD"
echo
echo "done. Compare the two summaries above."
echo " (raw captures: /tmp/jank_real.txt /tmp/jank_grey.txt)"