gallery/Cargo.toml
Greg Pomerantz 5a8284e7a9 Port the gallery from Slint to egui (egui 0.31 / egui_glow / winit 0.30 / glow 0.16)
Slint-on-Mali was conclusively janky at its base level (sltest: 0.33-0.83%
slot drops on a scene with no app logic); egui on the same phone measured
3-7x cleaner (egtest: 0.12%, same referee/scene).

Architecture (see HANDOFF §8.11-8.12):
- winit 0.30 ApplicationHandler on android-activity 0.6, raw EGL (ES3),
  egui_glow::EguiGlow. winit 0.30 never delivers RedrawRequested on
  Android → the frame driver is app-owned: World::tick runs every loop
  iteration from about_to_wait (liveness), render when dirty or
  animating, park ~8 ms (one vblank) at rest.
- Virtualized grid painter (3 cols, visible rows ±4), GPU-resident
  textures, turbojpeg worker pool decoding off-thread.
- CRITICAL pixel-pipeline fix: RGB8→Vec<Color32> conversion happens on
  the WORKER (egui 0.31's from_rgba_unmultiplied is O(n) per-pixel,
  8-60 ms/2 MB on the UI thread → 9.57% drops + phone OOM crash).
  UI thread only MOVES the vector into ColorImage.
- Fling: per-event EMA velocity (frame-based dies at slow touch sampling),
  clamp 14000 px/s; decay exp(-7.42 dt).
- Painter::image uv is NORMALIZED (0..1) — a screen-space uv made every
  cell a solid rectangle (the emulator's solid-color test images hid it).
- Memory caps for a personal phone: full cache 6 entries / 16 MP.

Verified on emulator: grid, scroll, fling, viewer, back-key. Phone
deploy: thumbnails render, 8580 files. Pending: user finger test +
fr_capture phone run (per-pixel fix expected to cut render cost
18.8 ms → <8.3 ms).

Deletes: vendor/ (instrumented Slint tree), shim/, build.rs, gputex,
vkshim, baseline60, main.slint — the Slint zero-jank campaign is
concluded in docs/BUGS/.
2026-10-31 13:50:46 -04:00

107 lines
3.8 KiB
TOML

[package]
name = "gallery"
version = "0.1.0"
edition = "2021"
# Android: cdylib so the NDK toolchain's -soname flag (libgallery.so) does
# not conflict with the lib crate's (libgallery.rlib). The NativeActivity
# loader finds the .so; android_main is the entry point (src/lib.rs).
[lib]
name = "gallery"
crate-type = ["cdylib", "lib"]
# The desktop entry (cargo run).
[[bin]]
name = "gallery"
path = "src/main.rs"
[dependencies]
# UI: egui + egui_glow (OpenGL ES 3.0 on Android via EGL) + egui-winit.
# egtest proved this stack at 120 Hz on the phone: 0.12% slot drops
# (vs 0.33-0.83% for every Slint variant, same referee, same scene).
# See docs/BUGS/scroll-jank-120hz.md §egui and harnesses/egtest.
egui = "0.31"
egui_glow = { version = "0.31", features = ["winit"] }
egui-winit = "0.31"
winit = { version = "0.30", features = ["android-native-activity"] }
android-activity = "0.6"
glow = "0.16"
libc = "0.2"
# EGL context + surface for egui_glow (Android: eglCreateWindowSurface over
# the native_window). egui_glow takes the API as a trait object — the
# context must live (in `World`) until the painter drops.
egl = "0.2"
# App layer (renderer-agnostic; carried over 1:1 from the Slint build):
# rusqlite with `bundled` (vendor libsqlite3.a — no system library).
# Build needs the NDK clang (cc crate); scripts/build.sh exports it.
rusqlite = { version = "0.41.0", features = ["bundled"] }
# M2: libjpeg-turbo for thumbnails + viewer. The crate compiles libjpeg-
# turbo from source (CMake) — needs the NDK CMake toolchain on Android,
# nasm on x86_64. See docs/THUMBS.md + HANDOFF.md §10.
turbojpeg = { version = "1.5.1", features = ["cmake"] }
# M2: EXIF orientation (JPGs) for decode-time rotation.
kamadak-exif = "0.6.1"
# M2 (PNGs only): the `image` crate's PNG decoder for non-JPG files.
image = { version = "0.25.10", default-features = false, features = ["png"] }
# M3: SHA1 of (path, mtime, size) as the thumbnail cache key.
sha1 = "0.11.0"
log = "0.4"
parking_lot = "0.12"
[profile.release]
# Thin LTO keeps builds minutes (not tens of minutes) while keeping release
# quality for the hot paths; the decode workers are C (libjpeg-turbo
# SIMD), so the Rust side is not the decode bottleneck.
lto = "thin"
codegen-units = 1
opt-level = 3
[profile.release.package."*"]
opt-level = 2
[features]
# FR frame-gap lines (docs/BUGS tooling): the fr_capture.py referee.
framelog = []
[package.metadata.android]
package = "wow.gmp.gallery"
build_targets = ["x86_64-linux-android", "aarch64-linux-android"]
[package.metadata.android.sdk]
min_sdk_version = 26
target_sdk_version = 35
[package.metadata.android.application]
label = "Gallery"
# Not debuggable: a debuggable app shows the system 16 KB compatibility
# dialog on EVERY cold launch — that dialog is its own activity, it pauses
# us, and its lifecycle was a source of lost surfaces / black windows.
debuggable = false
[[package.metadata.android.application.activity]]
name = "android.app.NativeActivity"
config_changes = "orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|touchscreen"
[[package.metadata.android.application.activity.meta_data]]
name = "android.app.lib_name"
value = "gallery"
[[package.metadata.android.application.activity.intent_filter]]
actions = ["android.intent.action.MAIN"]
categories = ["android.intent.category.LAUNCHER"]
# --- Permissions (runtime flow: HANDOFF.md §7) ---
[[package.metadata.android.uses_permission]]
name = "android.permission.READ_MEDIA_IMAGES"
[[package.metadata.android.uses_permission]]
name = "android.permission.READ_EXTERNAL_STORAGE"
max_sdk_version = 32
# "All files access" — same special-access pattern as passgo; needed to
# browse arbitrary directories outside the media collection.
[[package.metadata.android.uses_permission]]
name = "android.permission.MANAGE_EXTERNAL_STORAGE"