gallery/scripts/inject_vkshim.sh
Greg Pomerantz 2f4768f354 Fix 120Hz scroll jank end-to-end: render-ahead present + full-cache eviction off the UI thread
Three root causes, all fixed and verified on the Pixel 9 Pro:

1. Present phase (backend + renderer): VSYNC_LOCKED_RENDER defers the
   render to the vsync wake; RENDER_AHEAD presents the previous frame at
   the start of render() (phase = wake + ~1.1 ms) and holds the new
   frame one slot. Crash fix: a held wgpu frame must be PRESENTED, never
   merely dropped, before the next acquire (AlreadyAcquired panic at
   launch otherwise); discard_pending_frame presents too.

2. Full-res prefetch churn (src/lib.rs): the 8-slot full cache cycled
   through a ~20-cell viewport forever (137 decodes/20 s) and each
   eviction free()'d a 50 MB Vec on the UI thread (20-52 ms CPU spikes,
   ~7/s = the residual SLOWEV NonMain jank). Fix: 60 s tombstone
   cooldown on evicted paths (idle prefetch skips, urgent tap bypasses)
   + off-thread reaper for the freed blobs (insert: 21 ms -> 4 us).

3. (earlier) Skia final flush + Mailbox present mode (vendor, M3).

Instrumentation (all behind --features perf): OTHR/INVK/INVK_TAG/FLCK
lines for cross-thread closure timing and push-site tagging.

Warm measured swipe, 8580-file grid: cadence median 8.33 ms (exact
120 Hz), p99 15.5 ms, 5/552 gaps >16.7 ms (0.9%, max 28.3 ms, zero
>33 ms); PRES phase median ~994 us, zero late frames. Docs updated
(docs/BUGS/scroll-jank-120hz.md, full scorecard + line reference).

Also: vendored i-slint-core + wgpu-hal (vkshim interposition patch),
vkshim profiling tooling, backend made a non-optional dep, debuggable
flag for simpleperf (REMOVE before shipping).
2026-10-30 02:49:23 -04:00

56 lines
2.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Inject the Vulkan interposer (shim/vkshim.c, built to shim/out/libvulkan.so)
# into a gallery APK as lib/<abi>/libvulkan.so, so the app's linker namespace
# serves it to wgpu-hal's dlopen("libvulkan.so") INSTEAD of /system/lib64.
#
# scripts/inject_vkshim.sh <abi> <in.apk> <out.apk>
# abi: arm64-v8a | x86_64
#
# Steps: python adds the .so as an UNCOMPRESSED (STORED) zip entry (required
# for extractNativeLibs=false), zipalign page-aligns it, apksigner re-signs
# (the plain zip write invalidates the old signature).
set -euo pipefail
cd "$(dirname "$0")/.."
ABI=${1:?usage: inject_vkshim.sh <abi> <in.apk> <out.apk>}
IN=${2:?missing in.apk}
OUT=${3:?missing out.apk}
case $ABI in
arm64-v8a) SHIM=shim/out/libvulkan.so ;;
x86_64)
NDK=$HOME/android-sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/bin
$NDK/x86_64-linux-android26-clang -shared -fPIC -O2 shim/vkshim.c -o shim/out/libvulkan.so.x86_64 -lunwind
SHIM=shim/out/libvulkan.so.x86_64 ;;
*) echo "bad abi: $ABI" >&2; exit 1 ;;
esac
SDK=$HOME/android-sdk
BT=$SDK/build-tools/35.0.0
[ -x "$BT/zipalign" ] || { echo "build-tools zipalign missing: $BT" >&2; exit 1; }
python3 - "$IN" "$OUT" "$ABI" "$SHIM" <<'EOF'
import sys, zipfile, shutil, os
src, dst, abi, shim = sys.argv[1:5]
# Copy the APK, replacing/adding our entry, forcing STORED for the shim.
with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w") as zout:
for item in zin.infolist():
if item.filename == f"lib/{abi}/libvulkan.so":
continue # replaced by ours
zout.writestr(item, zin.read(item.filename))
with open(shim, "rb") as f:
data = f.read()
info = zipfile.ZipInfo(f"lib/{abi}/libvulkan.so")
info.compress_type = zipfile.ZIP_STORED
info.external_attr = 0o755 << 16
zout.writestr(info, data)
EOF
"$BT/zipalign" -f 4 "$OUT" "${OUT%.apk}_aligned.apk"
mv -f "${OUT%.apk}_aligned.apk" "$OUT"
"$BT/apksigner" sign --ks "$HOME/.android/debug.keystore" --ks-pass pass:android \
--out "${OUT%.apk}_signed.apk" "$OUT"
mv -f "${OUT%.apk}_signed.apk" "$OUT"
"$BT/apksigner" verify --print-certs "$OUT" | head -2
echo "OK: $OUT ($(du -h "$OUT" | cut -f1))"