Initial workspace: GPU context, compute pass, adaptive Slint shell
Establishes the v0.1 foundations on both platforms: - dr-types: SourceRef (never a filesystem path — Android SAF has none), Format, Availability, Validator with ETag quote normalisation - dr-gpu: wgpu device, compute pass writing a storage texture, resize - dr-ui: Slint shell with FR-UI-1 adaptive layout, computed in Rust to avoid a binding loop - docker/android: pinned toolchain, verified producing API 28 ARM binaries Measured the cost of the temporary CPU readback path (dr-gpu bench): compute is 0.06-0.28ms across sizes while readback is 0.63-7.43ms, so readback is 90-96% of frame time and scales with area. Recorded in ARCH §6.1 — this is why spike S1 is the priority. Mitigations pending S1: reuse the staging buffer, apply at most one resize per frame, and cap render resolution at 2048 on the long edge. 10 tests passing; core crates cross-compile for aarch64-linux-android.
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
# DarkRoom — reproducible Android build environment
|
||||
#
|
||||
# Pins the entire toolchain: JDK, Android SDK, NDK, Rust, and the four Android
|
||||
# targets. Both CI and local builds use this image, so "works on my machine"
|
||||
# and "works in CI" are the same machine.
|
||||
#
|
||||
# Build: podman build -t darkroom-android:latest docker/android
|
||||
# Use: ./docker/android/build.sh cargo ndk -t arm64-v8a build --release
|
||||
|
||||
FROM docker.io/library/debian:bookworm-slim
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Versions — pinned deliberately. Bumping any of these is a reviewable change,
|
||||
# not something that drifts underneath the build.
|
||||
# ---------------------------------------------------------------------------
|
||||
ARG JDK_VERSION=17
|
||||
# Compile SDK / target API.
|
||||
ARG ANDROID_API=36
|
||||
# Minimum supported API — the level native code links against (NFR-COMPAT-1).
|
||||
# 28 (Android 9) matches the floor where Vulkan support is dependable.
|
||||
# cargo-ndk otherwise defaults to 21, which is far below what this app needs.
|
||||
ARG MIN_API=28
|
||||
ARG BUILD_TOOLS=36.0.0
|
||||
ARG NDK_VERSION=27.2.12479018
|
||||
ARG CMDLINE_TOOLS=13114758
|
||||
# Must satisfy cargo-ndk's MSRV (4.1.x needs >= 1.86) as well as our own crates.
|
||||
ARG RUST_VERSION=1.92.0
|
||||
|
||||
# JDK 17, not the host's 25: the Android Gradle Plugin supports 17 as its
|
||||
# stable target, and newer JDKs regularly break Gradle in ways that cost more
|
||||
# time than they save.
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
ANDROID_HOME=/opt/android-sdk \
|
||||
ANDROID_SDK_ROOT=/opt/android-sdk \
|
||||
JAVA_HOME=/usr/lib/jvm/java-${JDK_VERSION}-openjdk-amd64 \
|
||||
CARGO_HOME=/opt/cargo \
|
||||
RUSTUP_HOME=/opt/rustup \
|
||||
PATH=/opt/cargo/bin:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:$PATH
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# System packages
|
||||
# ---------------------------------------------------------------------------
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl unzip git \
|
||||
openjdk-${JDK_VERSION}-jdk-headless \
|
||||
# Slint / winit build-time needs
|
||||
pkg-config libfontconfig1-dev \
|
||||
# native deps that may need building for host-side tooling
|
||||
build-essential cmake python3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Android SDK + NDK
|
||||
# ---------------------------------------------------------------------------
|
||||
RUN mkdir -p ${ANDROID_HOME}/cmdline-tools \
|
||||
&& curl -fsSL -o /tmp/tools.zip \
|
||||
"https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS}_latest.zip" \
|
||||
&& unzip -q /tmp/tools.zip -d ${ANDROID_HOME}/cmdline-tools \
|
||||
&& mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest \
|
||||
&& rm /tmp/tools.zip
|
||||
|
||||
RUN yes | sdkmanager --licenses > /dev/null 2>&1 || true \
|
||||
&& sdkmanager --install \
|
||||
"platform-tools" \
|
||||
"platforms;android-${ANDROID_API}" \
|
||||
"build-tools;${BUILD_TOOLS}" \
|
||||
"ndk;${NDK_VERSION}" \
|
||||
> /dev/null
|
||||
|
||||
ENV ANDROID_NDK_HOME=${ANDROID_HOME}/ndk/${NDK_VERSION} \
|
||||
ANDROID_NDK_ROOT=${ANDROID_HOME}/ndk/${NDK_VERSION}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Rust + Android targets
|
||||
#
|
||||
# All four ABIs. arm64-v8a covers essentially every current device; the others
|
||||
# exist so an ABI-specific build break is caught here rather than at release.
|
||||
# ---------------------------------------------------------------------------
|
||||
RUN curl -fsSL https://sh.rustup.rs | sh -s -- \
|
||||
-y --no-modify-path --profile minimal --default-toolchain ${RUST_VERSION} \
|
||||
&& rustup target add \
|
||||
aarch64-linux-android \
|
||||
armv7-linux-androideabi \
|
||||
x86_64-linux-android \
|
||||
i686-linux-android \
|
||||
&& rustup component add rustfmt clippy \
|
||||
&& cargo install cargo-ndk --locked \
|
||||
&& chmod -R a+rwX ${CARGO_HOME} ${RUSTUP_HOME}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Linker configuration
|
||||
#
|
||||
# cargo-ndk normally handles this, but setting it explicitly means plain
|
||||
# `cargo build --target …` works too, which matters for tooling that shells
|
||||
# out to cargo directly (rust-analyzer, cargo-metadata).
|
||||
# ---------------------------------------------------------------------------
|
||||
ENV NDK_BIN=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin
|
||||
|
||||
# Linkers target MIN_API, not ANDROID_API — the binary must run on the oldest
|
||||
# supported device, while the SDK compiles against the newest.
|
||||
ENV CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${NDK_BIN}/aarch64-linux-android${MIN_API}-clang \
|
||||
CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=${NDK_BIN}/armv7a-linux-androideabi${MIN_API}-clang \
|
||||
CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=${NDK_BIN}/x86_64-linux-android${MIN_API}-clang \
|
||||
CARGO_TARGET_I686_LINUX_ANDROID_LINKER=${NDK_BIN}/i686-linux-android${MIN_API}-clang
|
||||
|
||||
# cargo-ndk reads this; without it it defaults to API 21.
|
||||
ENV CARGO_NDK_PLATFORM=${MIN_API} \
|
||||
ANDROID_PLATFORM=${MIN_API}
|
||||
|
||||
# Shared cargo registry cache — bind-mount over this to persist across runs.
|
||||
VOLUME ["/opt/cargo/registry"]
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
# Rootless podman maps the host user into the container, so the image must not
|
||||
# assume a fixed uid. Keep world-writable toolchain dirs and let the caller
|
||||
# pass --user.
|
||||
RUN chmod -R a+rwX ${ANDROID_HOME}
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
@@ -0,0 +1,73 @@
|
||||
# Android build environment
|
||||
|
||||
Reproducible container for cross-compiling DarkRoom to Android. CI and local builds use the same
|
||||
image, so a break appears in one place rather than two.
|
||||
|
||||
## Use
|
||||
|
||||
```bash
|
||||
# Cross-compile for a device ABI
|
||||
./docker/android/build.sh cargo ndk -t arm64-v8a build --release
|
||||
|
||||
# All four ABIs
|
||||
./docker/android/build.sh cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -t x86 build --release
|
||||
|
||||
# Type-check without linking
|
||||
./docker/android/build.sh cargo check --target aarch64-linux-android
|
||||
|
||||
# Interactive shell
|
||||
./docker/android/build.sh
|
||||
|
||||
# After editing the Dockerfile
|
||||
./docker/android/build.sh --rebuild
|
||||
```
|
||||
|
||||
Prefers `podman`, falls back to `docker`. First run builds the image, which takes several minutes;
|
||||
after that it is cached.
|
||||
|
||||
## Pinned versions
|
||||
|
||||
| Component | Version | Why this one |
|
||||
|---|---|---|
|
||||
| JDK | 17 | AGP's stable target. The host's JDK 25 breaks Gradle. |
|
||||
| Compile SDK | API 36 | Required for Play distribution |
|
||||
| **Min API** | **28** (Android 9) | Floor where Vulkan support is dependable (NFR-COMPAT-1) |
|
||||
| Build tools | 36.0.0 | Matches compile SDK |
|
||||
| NDK | 27.2.12479018 (r27c) | Current LTS |
|
||||
| Rust | 1.92.0 | Slint 1.17 requires it; cargo-ndk 4.1.x needs ≥ 1.86 |
|
||||
| cargo-ndk | 4.1.2 | |
|
||||
|
||||
Bumping any of these is a reviewable change, not silent drift.
|
||||
|
||||
### Compile SDK versus min API
|
||||
|
||||
These are different and both matter. `ANDROID_API` (36) is what the SDK compiles against — the
|
||||
newest APIs available. `MIN_API` (28) is what native code *links* against, and determines the
|
||||
oldest device that can run the result.
|
||||
|
||||
cargo-ndk defaults to **API 21** if not told otherwise, which is far below what this app needs.
|
||||
`CARGO_NDK_PLATFORM` and the per-target linker paths both pin it to `MIN_API`. Verify with:
|
||||
|
||||
```bash
|
||||
file target/aarch64-linux-android/release/*.so
|
||||
# → "... for Android 28, built by NDK r27c"
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
The cargo registry and target directory persist in `~/.cache/darkroom-android`, so rebuilds do not
|
||||
re-download the crate index. Clear with:
|
||||
|
||||
```bash
|
||||
rm -rf ~/.cache/darkroom-android
|
||||
```
|
||||
|
||||
The Android target dir is kept separate from the host's `target/` — sharing one directory between
|
||||
host and container builds causes constant rebuilds as they invalidate each other's fingerprints.
|
||||
|
||||
## What is not here
|
||||
|
||||
No emulator, no `adb` device access. This image cross-compiles; it does not run or deploy. Device
|
||||
testing (spikes S2 and S10, which need two GPU vendors) happens on real hardware — the emulator's
|
||||
GPU behaviour is not representative of Adreno or Mali, which is precisely what those spikes exist
|
||||
to test.
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run a command inside the DarkRoom Android build container.
|
||||
#
|
||||
# ./docker/android/build.sh cargo ndk -t arm64-v8a build --release
|
||||
# ./docker/android/build.sh cargo check --target aarch64-linux-android
|
||||
# ./docker/android/build.sh # interactive shell
|
||||
#
|
||||
# Builds the image on first use. Rebuild after editing the Dockerfile with:
|
||||
# ./docker/android/build.sh --rebuild
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE="darkroom-android:latest"
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO="$(cd "${HERE}/../.." && pwd)"
|
||||
|
||||
# Prefer podman (rootless by default); fall back to docker.
|
||||
if command -v podman >/dev/null 2>&1; then
|
||||
ENGINE=podman
|
||||
elif command -v docker >/dev/null 2>&1; then
|
||||
ENGINE=docker
|
||||
else
|
||||
echo "error: neither podman nor docker found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == "--rebuild" ]]; then
|
||||
shift
|
||||
"${ENGINE}" build -t "${IMAGE}" "${HERE}"
|
||||
elif ! "${ENGINE}" image exists "${IMAGE}" 2>/dev/null && \
|
||||
! "${ENGINE}" image inspect "${IMAGE}" >/dev/null 2>&1; then
|
||||
echo "==> building ${IMAGE} (first run; several minutes)"
|
||||
"${ENGINE}" build -t "${IMAGE}" "${HERE}"
|
||||
fi
|
||||
|
||||
# Persist the cargo registry and target dir across runs, or every build
|
||||
# re-downloads the crate index.
|
||||
CACHE="${XDG_CACHE_HOME:-${HOME}/.cache}/darkroom-android"
|
||||
mkdir -p "${CACHE}/registry" "${CACHE}/target"
|
||||
|
||||
ARGS=(
|
||||
--rm
|
||||
-v "${REPO}:/work:z"
|
||||
-v "${CACHE}/registry:/opt/cargo/registry:z"
|
||||
-v "${CACHE}/target:/work/target-android:z"
|
||||
-e CARGO_TARGET_DIR=/work/target-android
|
||||
-w /work
|
||||
)
|
||||
|
||||
# Rootless podman already maps the host user; docker needs it stated.
|
||||
if [[ "${ENGINE}" == "docker" ]]; then
|
||||
ARGS+=(--user "$(id -u):$(id -g)")
|
||||
fi
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
ARGS+=(-it)
|
||||
set -- /bin/bash
|
||||
fi
|
||||
|
||||
exec "${ENGINE}" run "${ARGS[@]}" "${IMAGE}" "$@"
|
||||
Reference in New Issue
Block a user