UiRenderNode::new used to let a wgpu validation error reach the default
uncaptured-error handler and panic, which is what aborted the P0 bench APK
on Iris's phone in AndroidRenderer::new with only "wgpu error: Validation
Error" surviving into the truncated crash report. It now wraps creation in
wgpu error scopes and returns Result<Self, String>; the Android backend
turns a failure into the adapter's identity, the limits/downlevel flags a
layout validates against, and wgpu's own error chain, logged as one logcat
line and shown on screen (IrisView.showRendererError) instead of crashing.
Auditing every bind-group-layout entry against wgpu-core's own validation
source names the likely cause: masks_layout's move_offsets storage buffer
is visible to the vertex stage, which Vulkan grants unconditionally but
GLES gates on the driver's own vertex-stage SSBO support -- and the
delivered APK was built with force-gles, a flag meant only to force the
*emulator* onto GLES for one frame-time measurement, that build-apk.sh's
default feature list applied to every arm64 build regardless of target.
Its default no longer includes force-gles.
Testing the diagnostic (by inducing an artificial validation error) also
found and fixed a real reentrancy bug: calling Activity.setContentView
synchronously from inside a ViewPeer callback re-enters the same peer's
RefCell borrow through onFocusChanged, aborting with "RefCell already
borrowed". Deferred through the same push_dynamic_deferred_callback
mechanism raise_if_enabled already uses.
Full audit, verification, and the named hypothesis are in RUST.md's P0
box ("iris bench crash on the phone, 2026-09-06"); the API change is in
IRIS.md. Nobody on this session has the phone, so this is unconfirmed
against real hardware -- the point of (1) is that the next run says so
either way.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
91 lines
3.9 KiB
Bash
Executable File
91 lines
3.9 KiB
Bash
Executable File
#!/bin/sh
|
|
# Builds iris-android-app end to end: the cdylib (cargo ndk, straight into
|
|
# app/src/main/jniLibs/) then the APK (Gradle). Written to stop re-typing
|
|
# the same incantation by hand every time (ANDROID_HOME/NDK exports, the
|
|
# cargo ndk invocation, the keystore env for a release build, apksigner/
|
|
# aapt2 verification) -- see docs/RUST.md's P0 box. Same shape as `app/
|
|
# build-apk.sh` (the Compose app's own build script) and `app/
|
|
# iris-scroll.sh` (no coordinates, set -eu, exit 0 on success).
|
|
#
|
|
# Usage: ./build-apk.sh [debug|release] [--abi arm64-v8a|x86_64] [--features "a b c"]
|
|
# debug/release default to debug (matches this-machine-android's "the
|
|
# emulator stays on debug" rule -- pass `release` explicitly for a phone
|
|
# build). --abi defaults to arm64-v8a (a phone/real device); pass
|
|
# x86_64 for this checkout's own AVD. --features defaults to
|
|
# "transcript-screen bench" -- deliberately *without* `force-gles`, unlike
|
|
# an earlier version of this default. `force-gles` (`iris/Cargo.toml`'s
|
|
# own doc) exists only to force the emulator off its default software
|
|
# Vulkan and onto GLES for one specific measurement (RUST.md's I5, "Where
|
|
# iris's frame time goes") -- it was never meant to reach a real device,
|
|
# but this script's old default put it in every arm64 build regardless,
|
|
# so the P0 bench APK delivered to Iris's phone forced GLES there too.
|
|
# That is the named hypothesis in RUST.md's P0 box ("iris bench crash on
|
|
# the phone, 2026-09-06"): a real Vulkan driver is what a phone should
|
|
# run, and GLES is the backend the same box's own SwiftShader finding
|
|
# already flagged as the fragile one for this shader's storage buffers.
|
|
# Pass `--features "transcript-screen force-gles bench"` explicitly for
|
|
# an emulator backend-isolation run; never for a build meant for a phone.
|
|
set -eu
|
|
cd "$(dirname "$0")"
|
|
|
|
BUILD_TYPE="debug"
|
|
ABI="arm64-v8a"
|
|
FEATURES="transcript-screen bench"
|
|
case "${1:-}" in
|
|
debug|release) BUILD_TYPE="$1"; shift ;;
|
|
esac
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--abi) ABI="$2"; shift 2 ;;
|
|
--features) FEATURES="$2"; shift 2 ;;
|
|
*) echo "build-apk.sh: unknown argument: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
SDK_ROOT="$HOME/Android/Sdk"
|
|
export ANDROID_HOME="$SDK_ROOT"
|
|
export ANDROID_SDK_ROOT="$SDK_ROOT"
|
|
NDK_DIR=$(ls -d "$SDK_ROOT"/ndk/*/ 2>/dev/null | sort -V | tail -1)
|
|
if [ -z "$NDK_DIR" ]; then
|
|
echo "build-apk.sh: no NDK found under $SDK_ROOT/ndk" >&2
|
|
exit 1
|
|
fi
|
|
export ANDROID_NDK_HOME="$NDK_DIR"
|
|
|
|
echo "build-apk.sh: cargo ndk -t $ABI build ${BUILD_TYPE:+(${BUILD_TYPE})} --features \"$FEATURES\""
|
|
if [ "$BUILD_TYPE" = "release" ]; then
|
|
cargo ndk -t "$ABI" -P 26 -o app/src/main/jniLibs/ build --release --features "$FEATURES"
|
|
else
|
|
cargo ndk -t "$ABI" -P 26 -o app/src/main/jniLibs/ build --features "$FEATURES"
|
|
fi
|
|
|
|
GRADLE_TASK="assembleDebug"
|
|
APK_DIR="app/build/outputs/apk/debug"
|
|
APK_NAME="app-debug.apk"
|
|
if [ "$BUILD_TYPE" = "release" ]; then
|
|
GRADLE_TASK="assembleRelease"
|
|
APK_DIR="app/build/outputs/apk/release"
|
|
APK_NAME="app-release.apk"
|
|
# Same key `app/build-apk.sh` (the Compose app) generates once under
|
|
# ~/.config/ai-app/release.jks -- see AGENTS.md's "Checking your work".
|
|
export AI_APP_KEYSTORE="$HOME/.config/ai-app/release.jks"
|
|
if [ ! -f "$AI_APP_KEYSTORE" ]; then
|
|
echo "build-apk.sh: no release key at $AI_APP_KEYSTORE -- run app/build-apk.sh once first" >&2
|
|
exit 1
|
|
fi
|
|
export AI_APP_KEYSTORE_PASSWORD
|
|
AI_APP_KEYSTORE_PASSWORD=$(cat "$AI_APP_KEYSTORE.password")
|
|
fi
|
|
|
|
gradle ":app:$GRADLE_TASK" --console=plain
|
|
|
|
APK_PATH="$(pwd)/$APK_DIR/$APK_NAME"
|
|
BUILD_TOOLS=$(ls -d "$SDK_ROOT"/build-tools/*/ | sort -V | tail -1)
|
|
echo "--- aapt2 dump badging ---"
|
|
"${BUILD_TOOLS}aapt2" dump badging "$APK_PATH" | head -5
|
|
if [ "$BUILD_TYPE" = "release" ]; then
|
|
echo "--- apksigner verify ---"
|
|
"${BUILD_TOOLS}apksigner" verify --print-certs "$APK_PATH"
|
|
fi
|
|
echo "$APK_PATH"
|